From 36f412add1eea06d75c7ba115c47fff1f3da9860 Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Wed, 25 Nov 2020 08:34:56 +0000 Subject: [PATCH 1/1] Import libtorrent-rasterbar_1.2.9.orig.tar.gz [dgit import orig libtorrent-rasterbar_1.2.9.orig.tar.gz] --- AUTHORS | 29 + CMakeLists.txt | 872 + COPYING | 28 + ChangeLog | 1915 ++ Jamfile | 822 + Jamroot.jam | 0 LICENSE | 83 + LibtorrentRasterbarConfig.cmake.in | 9 + Makefile.am | 145 + Makefile.in | 1080 + NEWS | 1 + README.rst | 64 + aclocal.m4 | 1425 + bindings/CMakeLists.txt | 3 + bindings/Makefile.am | 4 + bindings/Makefile.in | 676 + bindings/README.txt | 3 + bindings/python/CMakeLists.txt | 147 + bindings/python/Jamfile | 247 + bindings/python/Makefile.am | 50 + bindings/python/Makefile.in | 552 + bindings/python/client.py | 379 + bindings/python/compile_cmd.in | 1 + bindings/python/compile_flags.in | 1 + bindings/python/link_flags.in | 1 + bindings/python/make_torrent.py | 61 + bindings/python/setup.py | 195 + bindings/python/setup.py.cmake.in | 15 + bindings/python/simple_client.py | 34 + bindings/python/src/alert.cpp | 1129 + bindings/python/src/boost_python.hpp | 37 + bindings/python/src/bytes.hpp | 22 + bindings/python/src/converters.cpp | 519 + bindings/python/src/create_torrent.cpp | 273 + bindings/python/src/datetime.cpp | 138 + bindings/python/src/entry.cpp | 179 + bindings/python/src/error_code.cpp | 219 + bindings/python/src/fingerprint.cpp | 51 + bindings/python/src/gil.hpp | 92 + bindings/python/src/ip_filter.cpp | 32 + bindings/python/src/magnet_uri.cpp | 93 + bindings/python/src/module.cpp | 56 + bindings/python/src/optional.hpp | 31 + bindings/python/src/peer_info.cpp | 158 + bindings/python/src/session.cpp | 1240 + bindings/python/src/session_settings.cpp | 131 + bindings/python/src/sha1_hash.cpp | 43 + bindings/python/src/string.cpp | 70 + bindings/python/src/torrent_handle.cpp | 662 + bindings/python/src/torrent_info.cpp | 398 + bindings/python/src/torrent_status.cpp | 137 + bindings/python/src/utility.cpp | 105 + bindings/python/src/version.cpp | 21 + build-aux/compile | 348 + build-aux/config.guess | 1480 + build-aux/config.rpath | 684 + build-aux/config.sub | 1801 ++ build-aux/depcomp | 791 + build-aux/install-sh | 518 + build-aux/ltmain.sh | 11251 ++++++++ build-aux/missing | 215 + build-aux/test-driver | 148 + cmake/Modules/FindLibGcrypt.cmake | 127 + cmake/Modules/GeneratePkgConfig.cmake | 175 + .../generate-pkg-config.cmake.in | 52 + .../GeneratePkgConfig/pkg-config.cmake.in | 8 + .../target-compile-settings.cmake.in | 13 + cmake/Modules/LibtorrentMacros.cmake | 80 + cmake/Modules/ucm_flags.cmake | 118 + configure | 23504 ++++++++++++++++ configure.ac | 668 + docs/bitcoin.png | Bin 0 -> 3411 bytes docs/building.html | 821 + docs/building.rst | 645 + docs/client_test.html | 111 + docs/client_test.rst | 47 + docs/complete_bit_prefixes.png | Bin 0 -> 7795 bytes docs/contributing.html | 130 + docs/contributing.rst | 60 + docs/cwnd.png | Bin 0 -> 18998 bytes docs/cwnd_thumb.png | Bin 0 -> 30207 bytes docs/delays.png | Bin 0 -> 10875 bytes docs/delays_thumb.png | Bin 0 -> 10938 bytes docs/dht_extensions.html | 117 + docs/dht_extensions.rst | 68 + docs/dht_rss.html | 405 + docs/dht_rss.rst | 396 + docs/dht_sec.html | 294 + docs/dht_sec.rst | 251 + docs/dht_store.html | 508 + docs/dht_store.rst | 480 + docs/disk_cache.diagram | 12 + docs/disk_cache.png | Bin 0 -> 3689 bytes docs/examples.html | 564 + docs/examples.rst | 46 + docs/extension_protocol.html | 471 + docs/extension_protocol.rst | 324 + docs/features.html | 363 + docs/features.rst | 336 + docs/fuzzing.html | 154 + docs/fuzzing.rst | 102 + docs/hacking.diagram | 30 + docs/hacking.html | 241 + docs/hacking.png | Bin 0 -> 15692 bytes docs/hash_distribution.png | Bin 0 -> 9035 bytes docs/header.rst | 3 + docs/img/bg.png | Bin 0 -> 2773 bytes docs/img/blue_bottom.png | Bin 0 -> 13137 bytes docs/img/blue_top.png | Bin 0 -> 2953 bytes docs/img/dotline.gif | Bin 0 -> 181 bytes docs/img/minus.gif | Bin 0 -> 262 bytes docs/img/orange.png | Bin 0 -> 24833 bytes docs/index.html | 184 + docs/index.rst | 208 + docs/ip_id_v4.png | Bin 0 -> 5825 bytes docs/ip_id_v6.png | Bin 0 -> 6416 bytes docs/manual-ref.html | 3194 +++ docs/manual-ref.rst | 1330 + docs/merkle_tree.png | Bin 0 -> 18792 bytes docs/our_delay_base.png | Bin 0 -> 34999 bytes docs/our_delay_base_thumb.png | Bin 0 -> 55460 bytes docs/projects.html | 173 + docs/projects.rst | 198 + docs/python_binding.html | 227 + docs/python_binding.rst | 157 + docs/read_disk_buffers.diagram | 16 + docs/read_disk_buffers.png | Bin 0 -> 8371 bytes docs/reference-Alerts.html | 3474 +++ docs/reference-Bdecoding.html | 397 + docs/reference-Bencoding.html | 348 + docs/reference-Core.html | 4513 +++ docs/reference-Create_Torrents.html | 453 + docs/reference-Custom_Storage.html | 535 + docs/reference-DHT.html | 564 + docs/reference-Error_Codes.html | 1388 + docs/reference-Filter.html | 235 + docs/reference-Plugins.html | 752 + docs/reference-Session.html | 1244 + docs/reference-Settings.html | 4578 +++ docs/reference-Storage.html | 693 + docs/reference-Utility.html | 323 + docs/reference-ed25519.html | 170 + docs/reference.html | 344 + docs/rst.css | 241 + docs/screenshot.png | Bin 0 -> 501532 bytes docs/screenshot_thumb.png | Bin 0 -> 124732 bytes docs/settings.rst | 3089 ++ docs/single-page-ref.html | 22039 +++++++++++++++ docs/stats_counters.rst | 2123 ++ docs/storage.png | Bin 0 -> 12360 bytes docs/streaming.html | 154 + docs/streaming.rst | 128 + docs/style.css | 154 + docs/todo.html | 11816 ++++++++ docs/troubleshooting.dot | 108 + docs/troubleshooting.html | 79 + docs/troubleshooting.png | Bin 0 -> 350876 bytes docs/troubleshooting.rst | 23 + docs/troubleshooting_thumb.png | Bin 0 -> 40618 bytes docs/tuning-ref.html | 437 + docs/tuning.rst | 398 + docs/tutorial.html | 477 + docs/tutorial.rst | 304 + docs/udp_tracker_protocol.html | 578 + docs/udp_tracker_protocol.rst | 320 + docs/upgrade_to_1.2-ref.html | 220 + docs/utp.html | 359 + docs/utp.rst | 342 + docs/utp_stack.diagram | 9 + docs/utp_stack.png | Bin 0 -> 2017 bytes docs/write_disk_buffers.diagram | 15 + docs/write_disk_buffers.png | Bin 0 -> 8665 bytes ed25519/readme.md | 165 + ed25519/src/add_scalar.cpp | 75 + ed25519/src/fe.cpp | 1490 + ed25519/src/fe.h | 41 + ed25519/src/fixedint.h | 10 + ed25519/src/ge.cpp | 472 + ed25519/src/ge.h | 74 + ed25519/src/key_exchange.cpp | 88 + ed25519/src/keypair.cpp | 24 + ed25519/src/precomp_data.h | 1392 + ed25519/src/sc.cpp | 816 + ed25519/src/sc.h | 13 + ed25519/src/sign.cpp | 37 + ed25519/src/verify.cpp | 84 + ed25519/test.c | 149 + examples/CMakeLists.txt | 22 + examples/Jamfile | 48 + examples/Makefile.am | 35 + examples/Makefile.in | 854 + examples/bt-get.cpp | 81 + examples/bt-get2.cpp | 160 + examples/client_test.cpp | 2160 ++ examples/cmake/FindLibtorrentRasterbar.cmake | 186 + examples/connection_tester.cpp | 1130 + examples/custom_storage.cpp | 125 + examples/dump_torrent.cpp | 155 + examples/make_torrent.cpp | 347 + examples/print.cpp | 546 + examples/print.hpp | 53 + examples/session_view.cpp | 159 + examples/session_view.hpp | 107 + examples/simple_client.cpp | 63 + examples/stats_counters.cpp | 50 + examples/torrent_view.cpp | 477 + examples/torrent_view.hpp | 111 + examples/upnp_test.cpp | 116 + include/libtorrent/Makefile.am | 251 + include/libtorrent/Makefile.in | 868 + include/libtorrent/add_torrent_params.hpp | 387 + include/libtorrent/address.hpp | 89 + include/libtorrent/alert.hpp | 351 + include/libtorrent/alert_manager.hpp | 175 + include/libtorrent/alert_types.hpp | 2989 ++ include/libtorrent/announce_entry.hpp | 244 + include/libtorrent/assert.hpp | 130 + include/libtorrent/aux_/aligned_storage.hpp | 60 + include/libtorrent/aux_/aligned_union.hpp | 71 + include/libtorrent/aux_/alloca.hpp | 112 + .../libtorrent/aux_/allocating_handler.hpp | 166 + include/libtorrent/aux_/array.hpp | 47 + include/libtorrent/aux_/bind_to_device.hpp | 137 + .../libtorrent/aux_/block_cache_reference.hpp | 57 + include/libtorrent/aux_/byteswap.hpp | 90 + include/libtorrent/aux_/container_wrapper.hpp | 137 + .../libtorrent/aux_/cppint_import_export.hpp | 253 + include/libtorrent/aux_/cpuid.hpp | 47 + include/libtorrent/aux_/deferred_handler.hpp | 87 + include/libtorrent/aux_/deprecated.hpp | 88 + include/libtorrent/aux_/deque.hpp | 47 + include/libtorrent/aux_/dev_random.hpp | 76 + .../libtorrent/aux_/disable_warnings_pop.hpp | 43 + .../libtorrent/aux_/disable_warnings_push.hpp | 112 + include/libtorrent/aux_/disk_job_fence.hpp | 120 + include/libtorrent/aux_/escape_string.hpp | 113 + include/libtorrent/aux_/export.hpp | 102 + include/libtorrent/aux_/ffs.hpp | 70 + include/libtorrent/aux_/file_progress.hpp | 99 + include/libtorrent/aux_/generate_peer_id.hpp | 48 + include/libtorrent/aux_/has_block.hpp | 56 + .../aux_/instantiate_connection.hpp | 56 + include/libtorrent/aux_/io.hpp | 179 + include/libtorrent/aux_/ip_notifier.hpp | 57 + include/libtorrent/aux_/keepalive.hpp | 102 + .../libtorrent/aux_/listen_socket_handle.hpp | 88 + include/libtorrent/aux_/lsd.hpp | 54 + include/libtorrent/aux_/merkle.hpp | 47 + include/libtorrent/aux_/noexcept_movable.hpp | 64 + include/libtorrent/aux_/numeric_cast.hpp | 68 + include/libtorrent/aux_/openssl.hpp | 109 + include/libtorrent/aux_/path.hpp | 192 + include/libtorrent/aux_/portmap.hpp | 100 + include/libtorrent/aux_/proxy_settings.hpp | 92 + include/libtorrent/aux_/range.hpp | 70 + include/libtorrent/aux_/route.h | 478 + include/libtorrent/aux_/scope_end.hpp | 64 + include/libtorrent/aux_/session_call.hpp | 51 + include/libtorrent/aux_/session_impl.hpp | 1404 + include/libtorrent/aux_/session_interface.hpp | 328 + include/libtorrent/aux_/session_settings.hpp | 149 + .../libtorrent/aux_/session_udp_sockets.hpp | 77 + include/libtorrent/aux_/set_socket_buffer.hpp | 91 + include/libtorrent/aux_/socket_type.hpp | 352 + include/libtorrent/aux_/storage_piece_set.hpp | 68 + include/libtorrent/aux_/storage_utils.hpp | 108 + include/libtorrent/aux_/string_ptr.hpp | 76 + include/libtorrent/aux_/suggest_piece.hpp | 127 + include/libtorrent/aux_/throw.hpp | 54 + include/libtorrent/aux_/time.hpp | 47 + include/libtorrent/aux_/torrent_impl.hpp | 80 + include/libtorrent/aux_/unique_ptr.hpp | 70 + include/libtorrent/aux_/vector.hpp | 47 + .../libtorrent/aux_/win_crypto_provider.hpp | 142 + include/libtorrent/aux_/win_util.hpp | 84 + include/libtorrent/aux_/windows.hpp | 50 + include/libtorrent/bandwidth_limit.hpp | 101 + include/libtorrent/bandwidth_manager.hpp | 91 + include/libtorrent/bandwidth_queue_entry.hpp | 74 + include/libtorrent/bandwidth_socket.hpp | 48 + include/libtorrent/bdecode.hpp | 450 + include/libtorrent/bencode.hpp | 406 + include/libtorrent/bitfield.hpp | 324 + include/libtorrent/block_cache.hpp | 556 + include/libtorrent/bloom_filter.hpp | 79 + include/libtorrent/broadcast_socket.hpp | 165 + include/libtorrent/bt_peer_connection.hpp | 495 + include/libtorrent/buffer.hpp | 165 + include/libtorrent/chained_buffer.hpp | 233 + include/libtorrent/choker.hpp | 57 + include/libtorrent/close_reason.hpp | 156 + include/libtorrent/config.hpp | 578 + include/libtorrent/copy_ptr.hpp | 67 + include/libtorrent/crc32c.hpp | 47 + include/libtorrent/create_torrent.hpp | 484 + include/libtorrent/deadline_timer.hpp | 55 + include/libtorrent/debug.hpp | 229 + include/libtorrent/disk_buffer_holder.hpp | 123 + include/libtorrent/disk_buffer_pool.hpp | 141 + include/libtorrent/disk_interface.hpp | 248 + include/libtorrent/disk_io_job.hpp | 222 + include/libtorrent/disk_io_thread.hpp | 586 + include/libtorrent/disk_io_thread_pool.hpp | 144 + include/libtorrent/disk_job_pool.hpp | 75 + include/libtorrent/disk_observer.hpp | 52 + include/libtorrent/download_priority.hpp | 57 + include/libtorrent/ed25519.hpp | 27 + include/libtorrent/entry.hpp | 368 + include/libtorrent/enum_net.hpp | 222 + include/libtorrent/error.hpp | 52 + include/libtorrent/error_code.hpp | 563 + include/libtorrent/extensions.hpp | 519 + include/libtorrent/extensions/smart_ban.hpp | 58 + include/libtorrent/extensions/ut_metadata.hpp | 60 + include/libtorrent/extensions/ut_pex.hpp | 60 + include/libtorrent/file.hpp | 199 + include/libtorrent/file_pool.hpp | 116 + include/libtorrent/file_storage.hpp | 648 + include/libtorrent/fingerprint.hpp | 103 + include/libtorrent/flags.hpp | 141 + include/libtorrent/fwd.hpp | 300 + include/libtorrent/gzip.hpp | 136 + include/libtorrent/hasher.hpp | 127 + include/libtorrent/hasher512.hpp | 127 + include/libtorrent/heterogeneous_queue.hpp | 259 + include/libtorrent/hex.hpp | 109 + include/libtorrent/http_connection.hpp | 253 + include/libtorrent/http_parser.hpp | 165 + include/libtorrent/http_seed_connection.hpp | 113 + include/libtorrent/http_stream.hpp | 120 + .../libtorrent/http_tracker_connection.hpp | 94 + include/libtorrent/i2p_stream.hpp | 239 + include/libtorrent/identify_client.hpp | 103 + include/libtorrent/index_range.hpp | 72 + include/libtorrent/invariant_check.hpp | 88 + include/libtorrent/io.hpp | 189 + include/libtorrent/io_service.hpp | 56 + include/libtorrent/io_service_fwd.hpp | 73 + include/libtorrent/ip_filter.hpp | 351 + include/libtorrent/ip_voter.hpp | 134 + .../libtorrent/kademlia/announce_flags.hpp | 61 + include/libtorrent/kademlia/dht_observer.hpp | 95 + include/libtorrent/kademlia/dht_settings.hpp | 185 + include/libtorrent/kademlia/dht_state.hpp | 76 + include/libtorrent/kademlia/dht_storage.hpp | 238 + include/libtorrent/kademlia/dht_tracker.hpp | 218 + .../libtorrent/kademlia/direct_request.hpp | 93 + include/libtorrent/kademlia/dos_blocker.hpp | 93 + include/libtorrent/kademlia/ed25519.hpp | 102 + include/libtorrent/kademlia/find_data.hpp | 87 + include/libtorrent/kademlia/get_item.hpp | 93 + include/libtorrent/kademlia/get_peers.hpp | 110 + include/libtorrent/kademlia/io.hpp | 62 + include/libtorrent/kademlia/item.hpp | 126 + include/libtorrent/kademlia/msg.hpp | 100 + include/libtorrent/kademlia/node.hpp | 286 + include/libtorrent/kademlia/node_entry.hpp | 93 + include/libtorrent/kademlia/node_id.hpp | 74 + include/libtorrent/kademlia/observer.hpp | 153 + include/libtorrent/kademlia/put_data.hpp | 89 + include/libtorrent/kademlia/refresh.hpp | 63 + include/libtorrent/kademlia/routing_table.hpp | 334 + include/libtorrent/kademlia/rpc_manager.hpp | 141 + .../libtorrent/kademlia/sample_infohashes.hpp | 79 + .../kademlia/traversal_algorithm.hpp | 172 + include/libtorrent/kademlia/types.hpp | 97 + include/libtorrent/lazy_entry.hpp | 430 + include/libtorrent/link.hpp | 82 + include/libtorrent/linked_list.hpp | 158 + include/libtorrent/lsd.hpp | 92 + include/libtorrent/magnet_uri.hpp | 89 + include/libtorrent/natpmp.hpp | 210 + include/libtorrent/netlink.hpp | 200 + include/libtorrent/operations.hpp | 252 + include/libtorrent/optional.hpp | 51 + include/libtorrent/packet_buffer.hpp | 118 + include/libtorrent/packet_pool.hpp | 221 + include/libtorrent/parse_url.hpp | 55 + include/libtorrent/part_file.hpp | 128 + include/libtorrent/pe_crypto.hpp | 162 + include/libtorrent/peer.hpp | 70 + include/libtorrent/peer_class.hpp | 158 + include/libtorrent/peer_class_set.hpp | 69 + include/libtorrent/peer_class_type_filter.hpp | 145 + include/libtorrent/peer_connection.hpp | 1226 + include/libtorrent/peer_connection_handle.hpp | 156 + .../libtorrent/peer_connection_interface.hpp | 83 + include/libtorrent/peer_id.hpp | 43 + include/libtorrent/peer_info.hpp | 463 + include/libtorrent/peer_list.hpp | 271 + include/libtorrent/peer_request.hpp | 58 + include/libtorrent/performance_counters.hpp | 510 + include/libtorrent/pex_flags.hpp | 60 + include/libtorrent/piece_block.hpp | 67 + include/libtorrent/piece_block_progress.hpp | 59 + include/libtorrent/piece_picker.hpp | 876 + include/libtorrent/platform_util.hpp | 13 + include/libtorrent/portmap.hpp | 57 + include/libtorrent/proxy_base.hpp | 280 + include/libtorrent/puff.hpp | 35 + include/libtorrent/random.hpp | 67 + include/libtorrent/read_resume_data.hpp | 59 + include/libtorrent/receive_buffer.hpp | 224 + include/libtorrent/request_blocks.hpp | 56 + include/libtorrent/resolve_links.hpp | 87 + include/libtorrent/resolver.hpp | 99 + include/libtorrent/resolver_interface.hpp | 76 + include/libtorrent/session.hpp | 380 + include/libtorrent/session_handle.hpp | 1133 + include/libtorrent/session_settings.hpp | 113 + include/libtorrent/session_stats.hpp | 79 + include/libtorrent/session_status.hpp | 236 + include/libtorrent/session_types.hpp | 55 + include/libtorrent/settings_pack.hpp | 1928 ++ include/libtorrent/sha1.hpp | 42 + include/libtorrent/sha1_hash.hpp | 313 + include/libtorrent/sha512.hpp | 30 + include/libtorrent/sliding_average.hpp | 91 + include/libtorrent/socket.hpp | 267 + include/libtorrent/socket_io.hpp | 144 + include/libtorrent/socks5_stream.hpp | 201 + include/libtorrent/span.hpp | 191 + include/libtorrent/ssl_stream.hpp | 322 + include/libtorrent/stack_allocator.hpp | 93 + include/libtorrent/stat.hpp | 285 + include/libtorrent/stat_cache.hpp | 108 + include/libtorrent/storage.hpp | 415 + include/libtorrent/storage_defs.hpp | 142 + include/libtorrent/string_util.hpp | 141 + include/libtorrent/string_view.hpp | 109 + include/libtorrent/tailqueue.hpp | 186 + include/libtorrent/time.hpp | 91 + include/libtorrent/timestamp_history.hpp | 86 + include/libtorrent/torrent.hpp | 1767 ++ include/libtorrent/torrent_flags.hpp | 303 + include/libtorrent/torrent_handle.hpp | 1298 + include/libtorrent/torrent_info.hpp | 736 + include/libtorrent/torrent_peer.hpp | 274 + include/libtorrent/torrent_peer_allocator.hpp | 107 + include/libtorrent/torrent_status.hpp | 605 + include/libtorrent/tracker_manager.hpp | 429 + include/libtorrent/udp_socket.hpp | 172 + include/libtorrent/udp_tracker_connection.hpp | 131 + include/libtorrent/union_endpoint.hpp | 114 + include/libtorrent/units.hpp | 171 + include/libtorrent/upnp.hpp | 378 + include/libtorrent/utf8.hpp | 52 + include/libtorrent/utp_socket_manager.hpp | 195 + include/libtorrent/utp_stream.hpp | 515 + include/libtorrent/vector_utils.hpp | 59 + include/libtorrent/version.hpp | 59 + include/libtorrent/web_connection_base.hpp | 140 + include/libtorrent/web_peer_connection.hpp | 145 + include/libtorrent/write_resume_data.hpp | 50 + include/libtorrent/xml_parse.hpp | 70 + libtorrent-rasterbar.pc.in | 16 + m4/ax_boost_base.m4 | 302 + m4/ax_boost_python.m4 | 121 + m4/ax_boost_system.m4 | 121 + m4/ax_check_openssl.m4 | 124 + m4/ax_cxx_compile_stdcxx.m4 | 951 + m4/ax_cxx_compile_stdcxx_11.m4 | 39 + m4/ax_pthread.m4 | 507 + m4/ax_python_devel.m4 | 327 + m4/gettext-lib.m4 | 1123 + m4/iconv.m4 | 288 + m4/libtool.m4 | 8394 ++++++ m4/ltoptions.m4 | 437 + m4/ltsugar.m4 | 124 + m4/ltversion.m4 | 23 + m4/lt~obsolete.m4 | 99 + m4/pkgconfig.m4 | 343 + setup.py | 6 + src/Makefile.am | 182 + src/Makefile.in | 2866 ++ src/add_torrent_params.cpp | 97 + src/alert.cpp | 2732 ++ src/alert_manager.cpp | 142 + src/announce_entry.cpp | 164 + src/assert.cpp | 403 + src/bandwidth_limit.cpp | 105 + src/bandwidth_manager.cpp | 221 + src/bandwidth_queue_entry.cpp | 78 + src/bdecode.cpp | 1143 + src/bitfield.cpp | 230 + src/block_cache.cpp | 1791 ++ src/bloom_filter.cpp | 76 + src/broadcast_socket.cpp | 358 + src/bt_peer_connection.cpp | 3484 +++ src/chained_buffer.cpp | 171 + src/choker.cpp | 387 + src/close_reason.cpp | 166 + src/cpuid.cpp | 159 + src/crc32c.cpp | 149 + src/create_torrent.cpp | 774 + src/disk_buffer_holder.cpp | 92 + src/disk_buffer_pool.cpp | 380 + src/disk_io_job.cpp | 147 + src/disk_io_thread.cpp | 3521 +++ src/disk_io_thread_pool.cpp | 204 + src/disk_job_fence.cpp | 236 + src/disk_job_pool.cpp | 109 + src/entry.cpp | 754 + src/enum_net.cpp | 1417 + src/error_code.cpp | 348 + src/escape_string.cpp | 645 + src/ffs.cpp | 183 + src/file.cpp | 1264 + src/file_pool.cpp | 312 + src/file_progress.cpp | 184 + src/file_storage.cpp | 1357 + src/fingerprint.cpp | 102 + src/generate_peer_id.cpp | 56 + src/gzip.cpp | 264 + src/hasher.cpp | 157 + src/hasher512.cpp | 147 + src/hex.cpp | 104 + src/http_connection.cpp | 889 + src/http_parser.cpp | 632 + src/http_seed_connection.cpp | 468 + src/http_stream.cpp | 145 + src/http_tracker_connection.cpp | 601 + src/i2p_stream.cpp | 499 + src/identify_client.cpp | 430 + src/instantiate_connection.cpp | 145 + src/ip_filter.cpp | 76 + src/ip_notifier.cpp | 443 + src/ip_voter.cpp | 192 + src/kademlia/dht_settings.cpp | 114 + src/kademlia/dht_state.cpp | 133 + src/kademlia/dht_storage.cpp | 629 + src/kademlia/dht_tracker.cpp | 705 + src/kademlia/dos_blocker.cpp | 113 + src/kademlia/ed25519.cpp | 126 + src/kademlia/find_data.cpp | 193 + src/kademlia/get_item.cpp | 217 + src/kademlia/get_peers.cpp | 327 + src/kademlia/item.cpp | 215 + src/kademlia/msg.cpp | 136 + src/kademlia/node.cpp | 1250 + src/kademlia/node_entry.cpp | 63 + src/kademlia/node_id.cpp | 213 + src/kademlia/put_data.cpp | 114 + src/kademlia/refresh.cpp | 104 + src/kademlia/routing_table.cpp | 1230 + src/kademlia/rpc_manager.cpp | 517 + src/kademlia/sample_infohashes.cpp | 147 + src/kademlia/traversal_algorithm.cpp | 671 + src/lazy_bdecode.cpp | 665 + src/listen_socket_handle.cpp | 74 + src/lsd.cpp | 338 + src/magnet_uri.cpp | 345 + src/merkle.cpp | 69 + src/natpmp.cpp | 926 + src/openssl.cpp | 82 + src/packet_buffer.cpp | 192 + src/parse_url.cpp | 165 + src/part_file.cpp | 391 + src/path.cpp | 955 + src/pe_crypto.cpp | 415 + src/peer_class.cpp | 125 + src/peer_class_set.cpp | 72 + src/peer_connection.cpp | 6632 +++++ src/peer_connection_handle.cpp | 355 + src/peer_info.cpp | 85 + src/peer_list.cpp | 1323 + src/performance_counters.cpp | 156 + src/piece_picker.cpp | 3838 +++ src/platform_util.cpp | 139 + src/proxy_base.cpp | 53 + src/proxy_settings.cpp | 67 + src/puff.cpp | 844 + src/random.cpp | 145 + src/read_resume_data.cpp | 396 + src/receive_buffer.cpp | 336 + src/request_blocks.cpp | 319 + src/resolve_links.cpp | 134 + src/resolver.cpp | 179 + src/session.cpp | 467 + src/session_call.cpp | 80 + src/session_handle.cpp | 1277 + src/session_impl.cpp | 7276 +++++ src/session_settings.cpp | 65 + src/session_stats.cpp | 600 + src/settings_pack.cpp | 793 + src/sha1.cpp | 328 + src/sha1_hash.cpp | 147 + src/sha512.cpp | 289 + src/smart_ban.cpp | 336 + src/socket_io.cpp | 168 + src/socket_type.cpp | 427 + src/socks5_stream.cpp | 419 + src/stack_allocator.cpp | 143 + src/stat.cpp | 45 + src/stat_cache.cpp | 143 + src/storage.cpp | 879 + src/storage_piece_set.cpp | 61 + src/storage_utils.cpp | 631 + src/string_util.cpp | 396 + src/time.cpp | 40 + src/timestamp_history.cpp | 105 + src/torrent.cpp | 11224 ++++++++ src/torrent_handle.cpp | 872 + src/torrent_info.cpp | 1702 ++ src/torrent_peer.cpp | 282 + src/torrent_peer_allocator.cpp | 116 + src/torrent_status.cpp | 58 + src/tracker_manager.cpp | 498 + src/udp_socket.cpp | 981 + src/udp_tracker_connection.cpp | 776 + src/upnp.cpp | 1666 ++ src/ut_metadata.cpp | 645 + src/ut_pex.cpp | 655 + src/utf8.cpp | 172 + src/utp_socket_manager.cpp | 352 + src/utp_stream.cpp | 3752 +++ src/version.cpp | 42 + src/web_connection_base.cpp | 209 + src/web_peer_connection.cpp | 1232 + src/write_resume_data.cpp | 260 + src/xml_parse.cpp | 170 + test/CMakeLists.txt | 45 + test/Jamfile | 254 + test/Makefile.am | 289 + test/Makefile.in | 2625 ++ test/bittorrent_peer.cpp | 560 + test/bittorrent_peer.hpp | 110 + test/corrupt.gz | Bin 0 -> 296 bytes test/dht_server.cpp | 182 + test/dht_server.hpp | 42 + test/enum_if.cpp | 132 + test/http_proxy.py | 550 + test/invalid1.gz | Bin 0 -> 27 bytes test/main.cpp | 588 + test/make_torrent.cpp | 208 + test/make_torrent.hpp | 67 + test/mutable_test_torrents/test1.torrent | 3 + .../test1_pad_files.torrent | 2 + .../test1_single.torrent | 2 + .../test1_single_padded.torrent | 2 + test/mutable_test_torrents/test2.torrent | 1 + .../test2_pad_files.torrent | Bin 0 -> 499 bytes test/mutable_test_torrents/test3.torrent | Bin 0 -> 366 bytes .../test3_pad_files.torrent | 4 + test/peer_server.cpp | 169 + test/peer_server.hpp | 47 + test/print_alerts.cpp | 72 + test/print_alerts.hpp | 43 + test/root1.xml | 135 + test/root2.xml | 1 + test/root3.xml | 1 + test/settings.cpp | 92 + test/settings.hpp | 37 + test/setup_transfer.cpp | 1110 + test/setup_transfer.hpp | 123 + test/socks.py | 317 + test/ssl/cert_request.pem | 12 + test/ssl/dhparams.pem | 13 + test/ssl/invalid_peer_certificate.pem | 82 + test/ssl/invalid_peer_private_key.pem | 30 + test/ssl/peer_certificate.pem | 82 + test/ssl/peer_private_key.pem | 30 + test/ssl/regenerate_test_certificate.sh | 39 + test/ssl/root_ca_cert.pem | 79 + test/ssl/root_ca_private.pem | 30 + test/ssl/server.pem | 84 + test/swarm_suite.cpp | 237 + test/swarm_suite.hpp | 48 + test/test.cpp | 96 + test/test.hpp | 204 + test/test_alert_manager.cpp | 356 + test/test_alert_types.cpp | 344 + test/test_alloca.cpp | 73 + test/test_auto_unchoke.cpp | 161 + test/test_bandwidth_limiter.cpp | 517 + test/test_bdecode.cpp | 1300 + test/test_bencoding.cpp | 745 + test/test_bitfield.cpp | 428 + test/test_block_cache.cpp | 515 + test/test_bloom_filter.cpp | 136 + test/test_buffer.cpp | 309 + test/test_checking.cpp | 370 + test/test_crc32.cpp | 70 + test/test_create_torrent.cpp | 121 + test/test_dht.cpp | 4056 +++ test/test_dht_storage.cpp | 508 + test/test_direct_dht.cpp | 146 + test/test_dos_blocker.cpp | 104 + test/test_ed25519.cpp | 289 + test/test_enum_net.cpp | 301 + test/test_fast_extension.cpp | 1129 + test/test_fence.cpp | 243 + test/test_ffs.cpp | 138 + test/test_file.cpp | 693 + test/test_file_progress.cpp | 140 + test/test_file_storage.cpp | 780 + test/test_flags.cpp | 201 + test/test_generate_peer_id.cpp | 56 + test/test_gzip.cpp | 100 + test/test_hasher.cpp | 125 + test/test_hasher512.cpp | 91 + test/test_heterogeneous_queue.cpp | 338 + test/test_http_connection.cpp | 260 + test/test_http_parser.cpp | 758 + test/test_identify_client.cpp | 48 + test/test_io.cpp | 292 + test/test_ip_filter.cpp | 261 + test/test_ip_voter.cpp | 222 + test/test_linked_list.cpp | 201 + test/test_listen_socket.cpp | 533 + test/test_lsd.cpp | 126 + test/test_magnet.cpp | 609 + test/test_merkle.cpp | 107 + test/test_packet_buffer.cpp | 185 + test/test_part_file.cpp | 140 + test/test_pe_crypto.cpp | 160 + test/test_peer_classes.cpp | 150 + test/test_peer_list.cpp | 976 + test/test_peer_priority.cpp | 99 + test/test_pex.cpp | 169 + test/test_piece_picker.cpp | 2368 ++ test/test_primitives.cpp | 189 + test/test_priority.cpp | 618 + test/test_privacy.cpp | 343 + test/test_read_piece.cpp | 158 + test/test_read_resume.cpp | 325 + test/test_receive_buffer.cpp | 256 + test/test_recheck.cpp | 118 + test/test_remap_files.cpp | 216 + test/test_remove_torrent.cpp | 209 + test/test_resolve_links.cpp | 180 + test/test_resume.cpp | 1578 ++ test/test_session.cpp | 626 + test/test_session_params.cpp | 162 + test/test_settings_pack.cpp | 262 + test/test_sha1_hash.cpp | 146 + test/test_sliding_average.cpp | 117 + test/test_socket_io.cpp | 217 + test/test_span.cpp | 151 + test/test_ssl.cpp | 643 + test/test_stack_allocator.cpp | 141 + test/test_stat_cache.cpp | 87 + test/test_storage.cpp | 1541 + test/test_string.cpp | 564 + test/test_tailqueue.cpp | 169 + test/test_threads.cpp | 128 + test/test_time.cpp | 104 + test/test_time_critical.cpp | 41 + test/test_timestamp_history.cpp | 57 + test/test_torrent.cpp | 858 + test/test_torrent_info.cpp | 1135 + test/test_torrents/absolute_filename.torrent | 1 + test/test_torrents/backslash_path.torrent | 1 + test/test_torrents/bad_name.torrent | Bin 0 -> 375 bytes test/test_torrents/base.torrent | 1 + test/test_torrents/creation_date.torrent | 1 + test/test_torrents/duplicate_files.torrent | 1 + .../test_torrents/duplicate_web_seeds.torrent | 1 + test/test_torrents/empty_httpseed.torrent | 1 + test/test_torrents/empty_path.torrent | 1 + test/test_torrents/empty_path_multi.torrent | 1 + test/test_torrents/hidden_parent_path.torrent | 1 + test/test_torrents/httpseed.torrent | 1 + test/test_torrents/invalid_file_size.torrent | 1 + test/test_torrents/invalid_filename.torrent | 1 + test/test_torrents/invalid_filename2.torrent | 1 + test/test_torrents/invalid_info.torrent | 1 + test/test_torrents/invalid_merkle.torrent | 1 + test/test_torrents/invalid_name.torrent | 1 + test/test_torrents/invalid_name2.torrent | 1 + test/test_torrents/invalid_name3.torrent | 1 + test/test_torrents/invalid_path_list.torrent | 1 + test/test_torrents/invalid_piece_len.torrent | 1 + test/test_torrents/invalid_pieces.torrent | 1 + test/test_torrents/invalid_root_hash.torrent | 1 + test/test_torrents/invalid_root_hash2.torrent | 1 + test/test_torrents/invalid_symlink.torrent | 1 + test/test_torrents/large.torrent | Bin 0 -> 100144 bytes test/test_torrents/long_name.torrent | 1 + test/test_torrents/many_pieces.torrent | 1 + test/test_torrents/missing_path_list.torrent | 1 + test/test_torrents/missing_piece_len.torrent | 1 + test/test_torrents/negative_file_size.torrent | 1 + test/test_torrents/negative_piece_len.torrent | 1 + test/test_torrents/negative_size.torrent | 1 + test/test_torrents/no_creation_date.torrent | 1 + test/test_torrents/no_files.torrent | 1 + test/test_torrents/no_name.torrent | 1 + .../overlapping_symlinks.torrent | Bin 0 -> 6119 bytes test/test_torrents/pad_file.torrent | 1 + test/test_torrents/pad_file_no_path.torrent | 1 + test/test_torrents/parent_path.torrent | 1 + test/test_torrents/root_hash.torrent | 1 + test/test_torrents/sample.torrent | Bin 0 -> 505 bytes test/test_torrents/single_multi_file.torrent | 1 + test/test_torrents/slash_path.torrent | 1 + test/test_torrents/slash_path2.torrent | 1 + test/test_torrents/slash_path3.torrent | 1 + test/test_torrents/string.torrent | 1 + test/test_torrents/symlink1.torrent | 1 + test/test_torrents/symlink2.torrent | 1 + test/test_torrents/symlink_zero_size.torrent | 1 + test/test_torrents/unaligned_pieces.torrent | 1 + test/test_torrents/unordered.torrent | 1 + test/test_torrents/url_list.torrent | 1 + test/test_torrents/url_list2.torrent | 1 + test/test_torrents/url_list3.torrent | 1 + test/test_torrents/url_seed.torrent | 1 + test/test_torrents/url_seed_multi.torrent | 1 + .../url_seed_multi_single_file.torrent | 1 + .../url_seed_multi_space.torrent | 1 + .../url_seed_multi_space_nolist.torrent | 1 + test/test_torrents/whitespace_url.torrent | 1 + test/test_tracker.cpp | 724 + test/test_transfer.cpp | 537 + test/test_upnp.cpp | 342 + test/test_url_seed.cpp | 68 + test/test_utf8.cpp | 142 + test/test_utils.cpp | 54 + test/test_utils.hpp | 51 + test/test_utp.cpp | 156 + test/test_web_seed.cpp | 52 + test/test_web_seed_ban.cpp | 62 + test/test_web_seed_chunked.cpp | 62 + test/test_web_seed_http.cpp | 62 + test/test_web_seed_http_pw.cpp | 62 + test/test_web_seed_redirect.cpp | 102 + test/test_web_seed_socks4.cpp | 62 + test/test_web_seed_socks5.cpp | 62 + test/test_web_seed_socks5_no_peers.cpp | 52 + test/test_web_seed_socks5_pw.cpp | 62 + test/test_xml.cpp | 439 + test/udp_tracker.cpp | 271 + test/udp_tracker.hpp | 44 + test/utf8_test.txt | 150 + test/web_seed_suite.cpp | 418 + test/web_seed_suite.hpp | 43 + test/web_server.py | 218 + test/zeroes.gz | Bin 0 -> 538 bytes tools/CMakeLists.txt | 5 + tools/Jamfile | 46 + tools/Makefile.am | 30 + tools/Makefile.in | 728 + tools/dht_put.cpp | 426 + tools/parse_dht_log.py | 341 + tools/parse_dht_rtt.py | 55 + tools/parse_dht_stats.py | 64 + tools/parse_peer_log.py | 75 + tools/parse_sample.py | 162 + tools/parse_session_stats.py | 670 + tools/parse_utp_log.py | 417 + tools/session_log_alerts.cpp | 69 + 852 files changed, 359994 insertions(+) create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 Jamfile create mode 100644 Jamroot.jam create mode 100644 LICENSE create mode 100644 LibtorrentRasterbarConfig.cmake.in create mode 100644 Makefile.am create mode 100644 Makefile.in create mode 100644 NEWS create mode 100644 README.rst create mode 100644 aclocal.m4 create mode 100644 bindings/CMakeLists.txt create mode 100644 bindings/Makefile.am create mode 100644 bindings/Makefile.in create mode 100644 bindings/README.txt create mode 100644 bindings/python/CMakeLists.txt create mode 100644 bindings/python/Jamfile create mode 100644 bindings/python/Makefile.am create mode 100644 bindings/python/Makefile.in create mode 100755 bindings/python/client.py create mode 100644 bindings/python/compile_cmd.in create mode 100644 bindings/python/compile_flags.in create mode 100644 bindings/python/link_flags.in create mode 100755 bindings/python/make_torrent.py create mode 100644 bindings/python/setup.py create mode 100644 bindings/python/setup.py.cmake.in create mode 100755 bindings/python/simple_client.py create mode 100644 bindings/python/src/alert.cpp create mode 100644 bindings/python/src/boost_python.hpp create mode 100644 bindings/python/src/bytes.hpp create mode 100644 bindings/python/src/converters.cpp create mode 100644 bindings/python/src/create_torrent.cpp create mode 100644 bindings/python/src/datetime.cpp create mode 100644 bindings/python/src/entry.cpp create mode 100644 bindings/python/src/error_code.cpp create mode 100644 bindings/python/src/fingerprint.cpp create mode 100644 bindings/python/src/gil.hpp create mode 100644 bindings/python/src/ip_filter.cpp create mode 100644 bindings/python/src/magnet_uri.cpp create mode 100644 bindings/python/src/module.cpp create mode 100644 bindings/python/src/optional.hpp create mode 100644 bindings/python/src/peer_info.cpp create mode 100644 bindings/python/src/session.cpp create mode 100644 bindings/python/src/session_settings.cpp create mode 100644 bindings/python/src/sha1_hash.cpp create mode 100644 bindings/python/src/string.cpp create mode 100644 bindings/python/src/torrent_handle.cpp create mode 100644 bindings/python/src/torrent_info.cpp create mode 100644 bindings/python/src/torrent_status.cpp create mode 100644 bindings/python/src/utility.cpp create mode 100644 bindings/python/src/version.cpp create mode 100755 build-aux/compile create mode 100755 build-aux/config.guess create mode 100644 build-aux/config.rpath create mode 100755 build-aux/config.sub create mode 100755 build-aux/depcomp create mode 100755 build-aux/install-sh create mode 100644 build-aux/ltmain.sh create mode 100755 build-aux/missing create mode 100755 build-aux/test-driver create mode 100644 cmake/Modules/FindLibGcrypt.cmake create mode 100644 cmake/Modules/GeneratePkgConfig.cmake create mode 100644 cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in create mode 100644 cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in create mode 100644 cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in create mode 100644 cmake/Modules/LibtorrentMacros.cmake create mode 100644 cmake/Modules/ucm_flags.cmake create mode 100755 configure create mode 100644 configure.ac create mode 100644 docs/bitcoin.png create mode 100644 docs/building.html create mode 100644 docs/building.rst create mode 100644 docs/client_test.html create mode 100644 docs/client_test.rst create mode 100644 docs/complete_bit_prefixes.png create mode 100644 docs/contributing.html create mode 100644 docs/contributing.rst create mode 100644 docs/cwnd.png create mode 100644 docs/cwnd_thumb.png create mode 100644 docs/delays.png create mode 100644 docs/delays_thumb.png create mode 100644 docs/dht_extensions.html create mode 100644 docs/dht_extensions.rst create mode 100644 docs/dht_rss.html create mode 100644 docs/dht_rss.rst create mode 100644 docs/dht_sec.html create mode 100644 docs/dht_sec.rst create mode 100644 docs/dht_store.html create mode 100644 docs/dht_store.rst create mode 100644 docs/disk_cache.diagram create mode 100644 docs/disk_cache.png create mode 100644 docs/examples.html create mode 100644 docs/examples.rst create mode 100644 docs/extension_protocol.html create mode 100644 docs/extension_protocol.rst create mode 100644 docs/features.html create mode 100644 docs/features.rst create mode 100644 docs/fuzzing.html create mode 100644 docs/fuzzing.rst create mode 100644 docs/hacking.diagram create mode 100644 docs/hacking.html create mode 100644 docs/hacking.png create mode 100644 docs/hash_distribution.png create mode 100644 docs/header.rst create mode 100644 docs/img/bg.png create mode 100644 docs/img/blue_bottom.png create mode 100644 docs/img/blue_top.png create mode 100644 docs/img/dotline.gif create mode 100644 docs/img/minus.gif create mode 100644 docs/img/orange.png create mode 100644 docs/index.html create mode 100644 docs/index.rst create mode 100644 docs/ip_id_v4.png create mode 100644 docs/ip_id_v6.png create mode 100644 docs/manual-ref.html create mode 100644 docs/manual-ref.rst create mode 100644 docs/merkle_tree.png create mode 100644 docs/our_delay_base.png create mode 100644 docs/our_delay_base_thumb.png create mode 100644 docs/projects.html create mode 100644 docs/projects.rst create mode 100644 docs/python_binding.html create mode 100644 docs/python_binding.rst create mode 100644 docs/read_disk_buffers.diagram create mode 100644 docs/read_disk_buffers.png create mode 100644 docs/reference-Alerts.html create mode 100644 docs/reference-Bdecoding.html create mode 100644 docs/reference-Bencoding.html create mode 100644 docs/reference-Core.html create mode 100644 docs/reference-Create_Torrents.html create mode 100644 docs/reference-Custom_Storage.html create mode 100644 docs/reference-DHT.html create mode 100644 docs/reference-Error_Codes.html create mode 100644 docs/reference-Filter.html create mode 100644 docs/reference-Plugins.html create mode 100644 docs/reference-Session.html create mode 100644 docs/reference-Settings.html create mode 100644 docs/reference-Storage.html create mode 100644 docs/reference-Utility.html create mode 100644 docs/reference-ed25519.html create mode 100644 docs/reference.html create mode 100644 docs/rst.css create mode 100644 docs/screenshot.png create mode 100644 docs/screenshot_thumb.png create mode 100644 docs/settings.rst create mode 100644 docs/single-page-ref.html create mode 100644 docs/stats_counters.rst create mode 100644 docs/storage.png create mode 100644 docs/streaming.html create mode 100644 docs/streaming.rst create mode 100644 docs/style.css create mode 100644 docs/todo.html create mode 100644 docs/troubleshooting.dot create mode 100644 docs/troubleshooting.html create mode 100644 docs/troubleshooting.png create mode 100644 docs/troubleshooting.rst create mode 100644 docs/troubleshooting_thumb.png create mode 100644 docs/tuning-ref.html create mode 100644 docs/tuning.rst create mode 100644 docs/tutorial.html create mode 100644 docs/tutorial.rst create mode 100644 docs/udp_tracker_protocol.html create mode 100644 docs/udp_tracker_protocol.rst create mode 100644 docs/upgrade_to_1.2-ref.html create mode 100644 docs/utp.html create mode 100644 docs/utp.rst create mode 100644 docs/utp_stack.diagram create mode 100644 docs/utp_stack.png create mode 100644 docs/write_disk_buffers.diagram create mode 100644 docs/write_disk_buffers.png create mode 100644 ed25519/readme.md create mode 100644 ed25519/src/add_scalar.cpp create mode 100644 ed25519/src/fe.cpp create mode 100644 ed25519/src/fe.h create mode 100644 ed25519/src/fixedint.h create mode 100644 ed25519/src/ge.cpp create mode 100644 ed25519/src/ge.h create mode 100644 ed25519/src/key_exchange.cpp create mode 100644 ed25519/src/keypair.cpp create mode 100644 ed25519/src/precomp_data.h create mode 100644 ed25519/src/sc.cpp create mode 100644 ed25519/src/sc.h create mode 100644 ed25519/src/sign.cpp create mode 100644 ed25519/src/verify.cpp create mode 100644 ed25519/test.c create mode 100644 examples/CMakeLists.txt create mode 100644 examples/Jamfile create mode 100644 examples/Makefile.am create mode 100644 examples/Makefile.in create mode 100644 examples/bt-get.cpp create mode 100644 examples/bt-get2.cpp create mode 100644 examples/client_test.cpp create mode 100644 examples/cmake/FindLibtorrentRasterbar.cmake create mode 100644 examples/connection_tester.cpp create mode 100644 examples/custom_storage.cpp create mode 100644 examples/dump_torrent.cpp create mode 100644 examples/make_torrent.cpp create mode 100644 examples/print.cpp create mode 100644 examples/print.hpp create mode 100644 examples/session_view.cpp create mode 100644 examples/session_view.hpp create mode 100644 examples/simple_client.cpp create mode 100644 examples/stats_counters.cpp create mode 100644 examples/torrent_view.cpp create mode 100644 examples/torrent_view.hpp create mode 100644 examples/upnp_test.cpp create mode 100644 include/libtorrent/Makefile.am create mode 100644 include/libtorrent/Makefile.in create mode 100644 include/libtorrent/add_torrent_params.hpp create mode 100644 include/libtorrent/address.hpp create mode 100644 include/libtorrent/alert.hpp create mode 100644 include/libtorrent/alert_manager.hpp create mode 100644 include/libtorrent/alert_types.hpp create mode 100644 include/libtorrent/announce_entry.hpp create mode 100644 include/libtorrent/assert.hpp create mode 100644 include/libtorrent/aux_/aligned_storage.hpp create mode 100644 include/libtorrent/aux_/aligned_union.hpp create mode 100644 include/libtorrent/aux_/alloca.hpp create mode 100644 include/libtorrent/aux_/allocating_handler.hpp create mode 100644 include/libtorrent/aux_/array.hpp create mode 100644 include/libtorrent/aux_/bind_to_device.hpp create mode 100644 include/libtorrent/aux_/block_cache_reference.hpp create mode 100644 include/libtorrent/aux_/byteswap.hpp create mode 100644 include/libtorrent/aux_/container_wrapper.hpp create mode 100644 include/libtorrent/aux_/cppint_import_export.hpp create mode 100644 include/libtorrent/aux_/cpuid.hpp create mode 100644 include/libtorrent/aux_/deferred_handler.hpp create mode 100644 include/libtorrent/aux_/deprecated.hpp create mode 100644 include/libtorrent/aux_/deque.hpp create mode 100644 include/libtorrent/aux_/dev_random.hpp create mode 100644 include/libtorrent/aux_/disable_warnings_pop.hpp create mode 100644 include/libtorrent/aux_/disable_warnings_push.hpp create mode 100644 include/libtorrent/aux_/disk_job_fence.hpp create mode 100644 include/libtorrent/aux_/escape_string.hpp create mode 100644 include/libtorrent/aux_/export.hpp create mode 100644 include/libtorrent/aux_/ffs.hpp create mode 100644 include/libtorrent/aux_/file_progress.hpp create mode 100644 include/libtorrent/aux_/generate_peer_id.hpp create mode 100644 include/libtorrent/aux_/has_block.hpp create mode 100644 include/libtorrent/aux_/instantiate_connection.hpp create mode 100644 include/libtorrent/aux_/io.hpp create mode 100644 include/libtorrent/aux_/ip_notifier.hpp create mode 100644 include/libtorrent/aux_/keepalive.hpp create mode 100644 include/libtorrent/aux_/listen_socket_handle.hpp create mode 100644 include/libtorrent/aux_/lsd.hpp create mode 100644 include/libtorrent/aux_/merkle.hpp create mode 100644 include/libtorrent/aux_/noexcept_movable.hpp create mode 100644 include/libtorrent/aux_/numeric_cast.hpp create mode 100644 include/libtorrent/aux_/openssl.hpp create mode 100644 include/libtorrent/aux_/path.hpp create mode 100644 include/libtorrent/aux_/portmap.hpp create mode 100644 include/libtorrent/aux_/proxy_settings.hpp create mode 100644 include/libtorrent/aux_/range.hpp create mode 100644 include/libtorrent/aux_/route.h create mode 100644 include/libtorrent/aux_/scope_end.hpp create mode 100644 include/libtorrent/aux_/session_call.hpp create mode 100644 include/libtorrent/aux_/session_impl.hpp create mode 100644 include/libtorrent/aux_/session_interface.hpp create mode 100644 include/libtorrent/aux_/session_settings.hpp create mode 100644 include/libtorrent/aux_/session_udp_sockets.hpp create mode 100644 include/libtorrent/aux_/set_socket_buffer.hpp create mode 100644 include/libtorrent/aux_/socket_type.hpp create mode 100644 include/libtorrent/aux_/storage_piece_set.hpp create mode 100644 include/libtorrent/aux_/storage_utils.hpp create mode 100644 include/libtorrent/aux_/string_ptr.hpp create mode 100644 include/libtorrent/aux_/suggest_piece.hpp create mode 100644 include/libtorrent/aux_/throw.hpp create mode 100644 include/libtorrent/aux_/time.hpp create mode 100644 include/libtorrent/aux_/torrent_impl.hpp create mode 100644 include/libtorrent/aux_/unique_ptr.hpp create mode 100644 include/libtorrent/aux_/vector.hpp create mode 100644 include/libtorrent/aux_/win_crypto_provider.hpp create mode 100644 include/libtorrent/aux_/win_util.hpp create mode 100644 include/libtorrent/aux_/windows.hpp create mode 100644 include/libtorrent/bandwidth_limit.hpp create mode 100644 include/libtorrent/bandwidth_manager.hpp create mode 100644 include/libtorrent/bandwidth_queue_entry.hpp create mode 100644 include/libtorrent/bandwidth_socket.hpp create mode 100644 include/libtorrent/bdecode.hpp create mode 100644 include/libtorrent/bencode.hpp create mode 100644 include/libtorrent/bitfield.hpp create mode 100644 include/libtorrent/block_cache.hpp create mode 100644 include/libtorrent/bloom_filter.hpp create mode 100644 include/libtorrent/broadcast_socket.hpp create mode 100644 include/libtorrent/bt_peer_connection.hpp create mode 100644 include/libtorrent/buffer.hpp create mode 100644 include/libtorrent/chained_buffer.hpp create mode 100644 include/libtorrent/choker.hpp create mode 100644 include/libtorrent/close_reason.hpp create mode 100644 include/libtorrent/config.hpp create mode 100644 include/libtorrent/copy_ptr.hpp create mode 100644 include/libtorrent/crc32c.hpp create mode 100644 include/libtorrent/create_torrent.hpp create mode 100644 include/libtorrent/deadline_timer.hpp create mode 100644 include/libtorrent/debug.hpp create mode 100644 include/libtorrent/disk_buffer_holder.hpp create mode 100644 include/libtorrent/disk_buffer_pool.hpp create mode 100644 include/libtorrent/disk_interface.hpp create mode 100644 include/libtorrent/disk_io_job.hpp create mode 100644 include/libtorrent/disk_io_thread.hpp create mode 100644 include/libtorrent/disk_io_thread_pool.hpp create mode 100644 include/libtorrent/disk_job_pool.hpp create mode 100644 include/libtorrent/disk_observer.hpp create mode 100644 include/libtorrent/download_priority.hpp create mode 100644 include/libtorrent/ed25519.hpp create mode 100644 include/libtorrent/entry.hpp create mode 100644 include/libtorrent/enum_net.hpp create mode 100644 include/libtorrent/error.hpp create mode 100644 include/libtorrent/error_code.hpp create mode 100644 include/libtorrent/extensions.hpp create mode 100644 include/libtorrent/extensions/smart_ban.hpp create mode 100644 include/libtorrent/extensions/ut_metadata.hpp create mode 100644 include/libtorrent/extensions/ut_pex.hpp create mode 100644 include/libtorrent/file.hpp create mode 100644 include/libtorrent/file_pool.hpp create mode 100644 include/libtorrent/file_storage.hpp create mode 100644 include/libtorrent/fingerprint.hpp create mode 100644 include/libtorrent/flags.hpp create mode 100644 include/libtorrent/fwd.hpp create mode 100644 include/libtorrent/gzip.hpp create mode 100644 include/libtorrent/hasher.hpp create mode 100644 include/libtorrent/hasher512.hpp create mode 100644 include/libtorrent/heterogeneous_queue.hpp create mode 100644 include/libtorrent/hex.hpp create mode 100644 include/libtorrent/http_connection.hpp create mode 100644 include/libtorrent/http_parser.hpp create mode 100644 include/libtorrent/http_seed_connection.hpp create mode 100644 include/libtorrent/http_stream.hpp create mode 100644 include/libtorrent/http_tracker_connection.hpp create mode 100644 include/libtorrent/i2p_stream.hpp create mode 100644 include/libtorrent/identify_client.hpp create mode 100644 include/libtorrent/index_range.hpp create mode 100644 include/libtorrent/invariant_check.hpp create mode 100644 include/libtorrent/io.hpp create mode 100644 include/libtorrent/io_service.hpp create mode 100644 include/libtorrent/io_service_fwd.hpp create mode 100644 include/libtorrent/ip_filter.hpp create mode 100644 include/libtorrent/ip_voter.hpp create mode 100644 include/libtorrent/kademlia/announce_flags.hpp create mode 100644 include/libtorrent/kademlia/dht_observer.hpp create mode 100644 include/libtorrent/kademlia/dht_settings.hpp create mode 100644 include/libtorrent/kademlia/dht_state.hpp create mode 100644 include/libtorrent/kademlia/dht_storage.hpp create mode 100644 include/libtorrent/kademlia/dht_tracker.hpp create mode 100644 include/libtorrent/kademlia/direct_request.hpp create mode 100644 include/libtorrent/kademlia/dos_blocker.hpp create mode 100644 include/libtorrent/kademlia/ed25519.hpp create mode 100644 include/libtorrent/kademlia/find_data.hpp create mode 100644 include/libtorrent/kademlia/get_item.hpp create mode 100644 include/libtorrent/kademlia/get_peers.hpp create mode 100644 include/libtorrent/kademlia/io.hpp create mode 100644 include/libtorrent/kademlia/item.hpp create mode 100644 include/libtorrent/kademlia/msg.hpp create mode 100644 include/libtorrent/kademlia/node.hpp create mode 100644 include/libtorrent/kademlia/node_entry.hpp create mode 100644 include/libtorrent/kademlia/node_id.hpp create mode 100644 include/libtorrent/kademlia/observer.hpp create mode 100644 include/libtorrent/kademlia/put_data.hpp create mode 100644 include/libtorrent/kademlia/refresh.hpp create mode 100644 include/libtorrent/kademlia/routing_table.hpp create mode 100644 include/libtorrent/kademlia/rpc_manager.hpp create mode 100644 include/libtorrent/kademlia/sample_infohashes.hpp create mode 100644 include/libtorrent/kademlia/traversal_algorithm.hpp create mode 100644 include/libtorrent/kademlia/types.hpp create mode 100644 include/libtorrent/lazy_entry.hpp create mode 100644 include/libtorrent/link.hpp create mode 100644 include/libtorrent/linked_list.hpp create mode 100644 include/libtorrent/lsd.hpp create mode 100644 include/libtorrent/magnet_uri.hpp create mode 100644 include/libtorrent/natpmp.hpp create mode 100644 include/libtorrent/netlink.hpp create mode 100644 include/libtorrent/operations.hpp create mode 100644 include/libtorrent/optional.hpp create mode 100644 include/libtorrent/packet_buffer.hpp create mode 100644 include/libtorrent/packet_pool.hpp create mode 100644 include/libtorrent/parse_url.hpp create mode 100644 include/libtorrent/part_file.hpp create mode 100644 include/libtorrent/pe_crypto.hpp create mode 100644 include/libtorrent/peer.hpp create mode 100644 include/libtorrent/peer_class.hpp create mode 100644 include/libtorrent/peer_class_set.hpp create mode 100644 include/libtorrent/peer_class_type_filter.hpp create mode 100644 include/libtorrent/peer_connection.hpp create mode 100644 include/libtorrent/peer_connection_handle.hpp create mode 100644 include/libtorrent/peer_connection_interface.hpp create mode 100644 include/libtorrent/peer_id.hpp create mode 100644 include/libtorrent/peer_info.hpp create mode 100644 include/libtorrent/peer_list.hpp create mode 100644 include/libtorrent/peer_request.hpp create mode 100644 include/libtorrent/performance_counters.hpp create mode 100644 include/libtorrent/pex_flags.hpp create mode 100644 include/libtorrent/piece_block.hpp create mode 100644 include/libtorrent/piece_block_progress.hpp create mode 100644 include/libtorrent/piece_picker.hpp create mode 100644 include/libtorrent/platform_util.hpp create mode 100644 include/libtorrent/portmap.hpp create mode 100644 include/libtorrent/proxy_base.hpp create mode 100644 include/libtorrent/puff.hpp create mode 100644 include/libtorrent/random.hpp create mode 100644 include/libtorrent/read_resume_data.hpp create mode 100644 include/libtorrent/receive_buffer.hpp create mode 100644 include/libtorrent/request_blocks.hpp create mode 100644 include/libtorrent/resolve_links.hpp create mode 100644 include/libtorrent/resolver.hpp create mode 100644 include/libtorrent/resolver_interface.hpp create mode 100644 include/libtorrent/session.hpp create mode 100644 include/libtorrent/session_handle.hpp create mode 100644 include/libtorrent/session_settings.hpp create mode 100644 include/libtorrent/session_stats.hpp create mode 100644 include/libtorrent/session_status.hpp create mode 100644 include/libtorrent/session_types.hpp create mode 100644 include/libtorrent/settings_pack.hpp create mode 100644 include/libtorrent/sha1.hpp create mode 100644 include/libtorrent/sha1_hash.hpp create mode 100644 include/libtorrent/sha512.hpp create mode 100644 include/libtorrent/sliding_average.hpp create mode 100644 include/libtorrent/socket.hpp create mode 100644 include/libtorrent/socket_io.hpp create mode 100644 include/libtorrent/socks5_stream.hpp create mode 100644 include/libtorrent/span.hpp create mode 100644 include/libtorrent/ssl_stream.hpp create mode 100644 include/libtorrent/stack_allocator.hpp create mode 100644 include/libtorrent/stat.hpp create mode 100644 include/libtorrent/stat_cache.hpp create mode 100644 include/libtorrent/storage.hpp create mode 100644 include/libtorrent/storage_defs.hpp create mode 100644 include/libtorrent/string_util.hpp create mode 100644 include/libtorrent/string_view.hpp create mode 100644 include/libtorrent/tailqueue.hpp create mode 100644 include/libtorrent/time.hpp create mode 100644 include/libtorrent/timestamp_history.hpp create mode 100644 include/libtorrent/torrent.hpp create mode 100644 include/libtorrent/torrent_flags.hpp create mode 100644 include/libtorrent/torrent_handle.hpp create mode 100644 include/libtorrent/torrent_info.hpp create mode 100644 include/libtorrent/torrent_peer.hpp create mode 100644 include/libtorrent/torrent_peer_allocator.hpp create mode 100644 include/libtorrent/torrent_status.hpp create mode 100644 include/libtorrent/tracker_manager.hpp create mode 100644 include/libtorrent/udp_socket.hpp create mode 100644 include/libtorrent/udp_tracker_connection.hpp create mode 100644 include/libtorrent/union_endpoint.hpp create mode 100644 include/libtorrent/units.hpp create mode 100644 include/libtorrent/upnp.hpp create mode 100644 include/libtorrent/utf8.hpp create mode 100644 include/libtorrent/utp_socket_manager.hpp create mode 100644 include/libtorrent/utp_stream.hpp create mode 100644 include/libtorrent/vector_utils.hpp create mode 100644 include/libtorrent/version.hpp create mode 100644 include/libtorrent/web_connection_base.hpp create mode 100644 include/libtorrent/web_peer_connection.hpp create mode 100644 include/libtorrent/write_resume_data.hpp create mode 100644 include/libtorrent/xml_parse.hpp create mode 100644 libtorrent-rasterbar.pc.in create mode 100644 m4/ax_boost_base.m4 create mode 100644 m4/ax_boost_python.m4 create mode 100644 m4/ax_boost_system.m4 create mode 100644 m4/ax_check_openssl.m4 create mode 100644 m4/ax_cxx_compile_stdcxx.m4 create mode 100644 m4/ax_cxx_compile_stdcxx_11.m4 create mode 100644 m4/ax_pthread.m4 create mode 100644 m4/ax_python_devel.m4 create mode 100644 m4/gettext-lib.m4 create mode 100644 m4/iconv.m4 create mode 100644 m4/libtool.m4 create mode 100644 m4/ltoptions.m4 create mode 100644 m4/ltsugar.m4 create mode 100644 m4/ltversion.m4 create mode 100644 m4/lt~obsolete.m4 create mode 100644 m4/pkgconfig.m4 create mode 100644 setup.py create mode 100644 src/Makefile.am create mode 100644 src/Makefile.in create mode 100644 src/add_torrent_params.cpp create mode 100644 src/alert.cpp create mode 100644 src/alert_manager.cpp create mode 100644 src/announce_entry.cpp create mode 100644 src/assert.cpp create mode 100644 src/bandwidth_limit.cpp create mode 100644 src/bandwidth_manager.cpp create mode 100644 src/bandwidth_queue_entry.cpp create mode 100644 src/bdecode.cpp create mode 100644 src/bitfield.cpp create mode 100644 src/block_cache.cpp create mode 100644 src/bloom_filter.cpp create mode 100644 src/broadcast_socket.cpp create mode 100644 src/bt_peer_connection.cpp create mode 100644 src/chained_buffer.cpp create mode 100644 src/choker.cpp create mode 100644 src/close_reason.cpp create mode 100644 src/cpuid.cpp create mode 100644 src/crc32c.cpp create mode 100644 src/create_torrent.cpp create mode 100644 src/disk_buffer_holder.cpp create mode 100644 src/disk_buffer_pool.cpp create mode 100644 src/disk_io_job.cpp create mode 100644 src/disk_io_thread.cpp create mode 100644 src/disk_io_thread_pool.cpp create mode 100644 src/disk_job_fence.cpp create mode 100644 src/disk_job_pool.cpp create mode 100644 src/entry.cpp create mode 100644 src/enum_net.cpp create mode 100644 src/error_code.cpp create mode 100644 src/escape_string.cpp create mode 100644 src/ffs.cpp create mode 100644 src/file.cpp create mode 100644 src/file_pool.cpp create mode 100644 src/file_progress.cpp create mode 100644 src/file_storage.cpp create mode 100644 src/fingerprint.cpp create mode 100644 src/generate_peer_id.cpp create mode 100644 src/gzip.cpp create mode 100644 src/hasher.cpp create mode 100644 src/hasher512.cpp create mode 100644 src/hex.cpp create mode 100644 src/http_connection.cpp create mode 100644 src/http_parser.cpp create mode 100644 src/http_seed_connection.cpp create mode 100644 src/http_stream.cpp create mode 100644 src/http_tracker_connection.cpp create mode 100644 src/i2p_stream.cpp create mode 100644 src/identify_client.cpp create mode 100644 src/instantiate_connection.cpp create mode 100644 src/ip_filter.cpp create mode 100644 src/ip_notifier.cpp create mode 100644 src/ip_voter.cpp create mode 100644 src/kademlia/dht_settings.cpp create mode 100644 src/kademlia/dht_state.cpp create mode 100644 src/kademlia/dht_storage.cpp create mode 100644 src/kademlia/dht_tracker.cpp create mode 100644 src/kademlia/dos_blocker.cpp create mode 100644 src/kademlia/ed25519.cpp create mode 100644 src/kademlia/find_data.cpp create mode 100644 src/kademlia/get_item.cpp create mode 100644 src/kademlia/get_peers.cpp create mode 100644 src/kademlia/item.cpp create mode 100644 src/kademlia/msg.cpp create mode 100644 src/kademlia/node.cpp create mode 100644 src/kademlia/node_entry.cpp create mode 100644 src/kademlia/node_id.cpp create mode 100644 src/kademlia/put_data.cpp create mode 100644 src/kademlia/refresh.cpp create mode 100644 src/kademlia/routing_table.cpp create mode 100644 src/kademlia/rpc_manager.cpp create mode 100644 src/kademlia/sample_infohashes.cpp create mode 100644 src/kademlia/traversal_algorithm.cpp create mode 100644 src/lazy_bdecode.cpp create mode 100644 src/listen_socket_handle.cpp create mode 100644 src/lsd.cpp create mode 100644 src/magnet_uri.cpp create mode 100644 src/merkle.cpp create mode 100644 src/natpmp.cpp create mode 100644 src/openssl.cpp create mode 100644 src/packet_buffer.cpp create mode 100644 src/parse_url.cpp create mode 100644 src/part_file.cpp create mode 100644 src/path.cpp create mode 100644 src/pe_crypto.cpp create mode 100644 src/peer_class.cpp create mode 100644 src/peer_class_set.cpp create mode 100644 src/peer_connection.cpp create mode 100644 src/peer_connection_handle.cpp create mode 100644 src/peer_info.cpp create mode 100644 src/peer_list.cpp create mode 100644 src/performance_counters.cpp create mode 100644 src/piece_picker.cpp create mode 100644 src/platform_util.cpp create mode 100644 src/proxy_base.cpp create mode 100644 src/proxy_settings.cpp create mode 100644 src/puff.cpp create mode 100644 src/random.cpp create mode 100644 src/read_resume_data.cpp create mode 100644 src/receive_buffer.cpp create mode 100644 src/request_blocks.cpp create mode 100644 src/resolve_links.cpp create mode 100644 src/resolver.cpp create mode 100644 src/session.cpp create mode 100644 src/session_call.cpp create mode 100644 src/session_handle.cpp create mode 100644 src/session_impl.cpp create mode 100644 src/session_settings.cpp create mode 100644 src/session_stats.cpp create mode 100644 src/settings_pack.cpp create mode 100644 src/sha1.cpp create mode 100644 src/sha1_hash.cpp create mode 100644 src/sha512.cpp create mode 100644 src/smart_ban.cpp create mode 100644 src/socket_io.cpp create mode 100644 src/socket_type.cpp create mode 100644 src/socks5_stream.cpp create mode 100644 src/stack_allocator.cpp create mode 100644 src/stat.cpp create mode 100644 src/stat_cache.cpp create mode 100644 src/storage.cpp create mode 100644 src/storage_piece_set.cpp create mode 100644 src/storage_utils.cpp create mode 100644 src/string_util.cpp create mode 100644 src/time.cpp create mode 100644 src/timestamp_history.cpp create mode 100644 src/torrent.cpp create mode 100644 src/torrent_handle.cpp create mode 100644 src/torrent_info.cpp create mode 100644 src/torrent_peer.cpp create mode 100644 src/torrent_peer_allocator.cpp create mode 100644 src/torrent_status.cpp create mode 100644 src/tracker_manager.cpp create mode 100644 src/udp_socket.cpp create mode 100644 src/udp_tracker_connection.cpp create mode 100644 src/upnp.cpp create mode 100644 src/ut_metadata.cpp create mode 100644 src/ut_pex.cpp create mode 100644 src/utf8.cpp create mode 100644 src/utp_socket_manager.cpp create mode 100644 src/utp_stream.cpp create mode 100644 src/version.cpp create mode 100644 src/web_connection_base.cpp create mode 100644 src/web_peer_connection.cpp create mode 100644 src/write_resume_data.cpp create mode 100644 src/xml_parse.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/Jamfile create mode 100644 test/Makefile.am create mode 100644 test/Makefile.in create mode 100644 test/bittorrent_peer.cpp create mode 100644 test/bittorrent_peer.hpp create mode 100644 test/corrupt.gz create mode 100644 test/dht_server.cpp create mode 100644 test/dht_server.hpp create mode 100644 test/enum_if.cpp create mode 100755 test/http_proxy.py create mode 100644 test/invalid1.gz create mode 100644 test/main.cpp create mode 100644 test/make_torrent.cpp create mode 100644 test/make_torrent.hpp create mode 100644 test/mutable_test_torrents/test1.torrent create mode 100644 test/mutable_test_torrents/test1_pad_files.torrent create mode 100644 test/mutable_test_torrents/test1_single.torrent create mode 100644 test/mutable_test_torrents/test1_single_padded.torrent create mode 100644 test/mutable_test_torrents/test2.torrent create mode 100644 test/mutable_test_torrents/test2_pad_files.torrent create mode 100644 test/mutable_test_torrents/test3.torrent create mode 100644 test/mutable_test_torrents/test3_pad_files.torrent create mode 100644 test/peer_server.cpp create mode 100644 test/peer_server.hpp create mode 100644 test/print_alerts.cpp create mode 100644 test/print_alerts.hpp create mode 100644 test/root1.xml create mode 100644 test/root2.xml create mode 100644 test/root3.xml create mode 100644 test/settings.cpp create mode 100644 test/settings.hpp create mode 100644 test/setup_transfer.cpp create mode 100644 test/setup_transfer.hpp create mode 100755 test/socks.py create mode 100644 test/ssl/cert_request.pem create mode 100644 test/ssl/dhparams.pem create mode 100644 test/ssl/invalid_peer_certificate.pem create mode 100644 test/ssl/invalid_peer_private_key.pem create mode 100644 test/ssl/peer_certificate.pem create mode 100644 test/ssl/peer_private_key.pem create mode 100755 test/ssl/regenerate_test_certificate.sh create mode 100644 test/ssl/root_ca_cert.pem create mode 100644 test/ssl/root_ca_private.pem create mode 100644 test/ssl/server.pem create mode 100644 test/swarm_suite.cpp create mode 100644 test/swarm_suite.hpp create mode 100644 test/test.cpp create mode 100644 test/test.hpp create mode 100644 test/test_alert_manager.cpp create mode 100644 test/test_alert_types.cpp create mode 100644 test/test_alloca.cpp create mode 100644 test/test_auto_unchoke.cpp create mode 100644 test/test_bandwidth_limiter.cpp create mode 100644 test/test_bdecode.cpp create mode 100644 test/test_bencoding.cpp create mode 100644 test/test_bitfield.cpp create mode 100644 test/test_block_cache.cpp create mode 100644 test/test_bloom_filter.cpp create mode 100644 test/test_buffer.cpp create mode 100644 test/test_checking.cpp create mode 100644 test/test_crc32.cpp create mode 100644 test/test_create_torrent.cpp create mode 100644 test/test_dht.cpp create mode 100644 test/test_dht_storage.cpp create mode 100644 test/test_direct_dht.cpp create mode 100644 test/test_dos_blocker.cpp create mode 100644 test/test_ed25519.cpp create mode 100644 test/test_enum_net.cpp create mode 100644 test/test_fast_extension.cpp create mode 100644 test/test_fence.cpp create mode 100644 test/test_ffs.cpp create mode 100644 test/test_file.cpp create mode 100644 test/test_file_progress.cpp create mode 100644 test/test_file_storage.cpp create mode 100644 test/test_flags.cpp create mode 100644 test/test_generate_peer_id.cpp create mode 100644 test/test_gzip.cpp create mode 100644 test/test_hasher.cpp create mode 100644 test/test_hasher512.cpp create mode 100644 test/test_heterogeneous_queue.cpp create mode 100644 test/test_http_connection.cpp create mode 100644 test/test_http_parser.cpp create mode 100644 test/test_identify_client.cpp create mode 100644 test/test_io.cpp create mode 100644 test/test_ip_filter.cpp create mode 100644 test/test_ip_voter.cpp create mode 100644 test/test_linked_list.cpp create mode 100644 test/test_listen_socket.cpp create mode 100644 test/test_lsd.cpp create mode 100644 test/test_magnet.cpp create mode 100644 test/test_merkle.cpp create mode 100644 test/test_packet_buffer.cpp create mode 100644 test/test_part_file.cpp create mode 100644 test/test_pe_crypto.cpp create mode 100644 test/test_peer_classes.cpp create mode 100644 test/test_peer_list.cpp create mode 100644 test/test_peer_priority.cpp create mode 100644 test/test_pex.cpp create mode 100644 test/test_piece_picker.cpp create mode 100644 test/test_primitives.cpp create mode 100644 test/test_priority.cpp create mode 100644 test/test_privacy.cpp create mode 100644 test/test_read_piece.cpp create mode 100644 test/test_read_resume.cpp create mode 100644 test/test_receive_buffer.cpp create mode 100644 test/test_recheck.cpp create mode 100644 test/test_remap_files.cpp create mode 100644 test/test_remove_torrent.cpp create mode 100644 test/test_resolve_links.cpp create mode 100644 test/test_resume.cpp create mode 100644 test/test_session.cpp create mode 100644 test/test_session_params.cpp create mode 100644 test/test_settings_pack.cpp create mode 100644 test/test_sha1_hash.cpp create mode 100644 test/test_sliding_average.cpp create mode 100644 test/test_socket_io.cpp create mode 100644 test/test_span.cpp create mode 100644 test/test_ssl.cpp create mode 100644 test/test_stack_allocator.cpp create mode 100644 test/test_stat_cache.cpp create mode 100644 test/test_storage.cpp create mode 100644 test/test_string.cpp create mode 100644 test/test_tailqueue.cpp create mode 100644 test/test_threads.cpp create mode 100644 test/test_time.cpp create mode 100644 test/test_time_critical.cpp create mode 100644 test/test_timestamp_history.cpp create mode 100644 test/test_torrent.cpp create mode 100644 test/test_torrent_info.cpp create mode 100644 test/test_torrents/absolute_filename.torrent create mode 100644 test/test_torrents/backslash_path.torrent create mode 100644 test/test_torrents/bad_name.torrent create mode 100644 test/test_torrents/base.torrent create mode 100644 test/test_torrents/creation_date.torrent create mode 100644 test/test_torrents/duplicate_files.torrent create mode 100644 test/test_torrents/duplicate_web_seeds.torrent create mode 100644 test/test_torrents/empty_httpseed.torrent create mode 100644 test/test_torrents/empty_path.torrent create mode 100644 test/test_torrents/empty_path_multi.torrent create mode 100644 test/test_torrents/hidden_parent_path.torrent create mode 100644 test/test_torrents/httpseed.torrent create mode 100644 test/test_torrents/invalid_file_size.torrent create mode 100644 test/test_torrents/invalid_filename.torrent create mode 100644 test/test_torrents/invalid_filename2.torrent create mode 100644 test/test_torrents/invalid_info.torrent create mode 100644 test/test_torrents/invalid_merkle.torrent create mode 100644 test/test_torrents/invalid_name.torrent create mode 100644 test/test_torrents/invalid_name2.torrent create mode 100644 test/test_torrents/invalid_name3.torrent create mode 100644 test/test_torrents/invalid_path_list.torrent create mode 100644 test/test_torrents/invalid_piece_len.torrent create mode 100644 test/test_torrents/invalid_pieces.torrent create mode 100644 test/test_torrents/invalid_root_hash.torrent create mode 100644 test/test_torrents/invalid_root_hash2.torrent create mode 100644 test/test_torrents/invalid_symlink.torrent create mode 100644 test/test_torrents/large.torrent create mode 100644 test/test_torrents/long_name.torrent create mode 100644 test/test_torrents/many_pieces.torrent create mode 100644 test/test_torrents/missing_path_list.torrent create mode 100644 test/test_torrents/missing_piece_len.torrent create mode 100644 test/test_torrents/negative_file_size.torrent create mode 100644 test/test_torrents/negative_piece_len.torrent create mode 100644 test/test_torrents/negative_size.torrent create mode 100644 test/test_torrents/no_creation_date.torrent create mode 100644 test/test_torrents/no_files.torrent create mode 100644 test/test_torrents/no_name.torrent create mode 100644 test/test_torrents/overlapping_symlinks.torrent create mode 100644 test/test_torrents/pad_file.torrent create mode 100644 test/test_torrents/pad_file_no_path.torrent create mode 100644 test/test_torrents/parent_path.torrent create mode 100644 test/test_torrents/root_hash.torrent create mode 100644 test/test_torrents/sample.torrent create mode 100644 test/test_torrents/single_multi_file.torrent create mode 100644 test/test_torrents/slash_path.torrent create mode 100644 test/test_torrents/slash_path2.torrent create mode 100644 test/test_torrents/slash_path3.torrent create mode 100644 test/test_torrents/string.torrent create mode 100644 test/test_torrents/symlink1.torrent create mode 100755 test/test_torrents/symlink2.torrent create mode 100644 test/test_torrents/symlink_zero_size.torrent create mode 100644 test/test_torrents/unaligned_pieces.torrent create mode 100644 test/test_torrents/unordered.torrent create mode 100644 test/test_torrents/url_list.torrent create mode 100644 test/test_torrents/url_list2.torrent create mode 100644 test/test_torrents/url_list3.torrent create mode 100644 test/test_torrents/url_seed.torrent create mode 100644 test/test_torrents/url_seed_multi.torrent create mode 100644 test/test_torrents/url_seed_multi_single_file.torrent create mode 100644 test/test_torrents/url_seed_multi_space.torrent create mode 100644 test/test_torrents/url_seed_multi_space_nolist.torrent create mode 100644 test/test_torrents/whitespace_url.torrent create mode 100644 test/test_tracker.cpp create mode 100644 test/test_transfer.cpp create mode 100644 test/test_upnp.cpp create mode 100644 test/test_url_seed.cpp create mode 100644 test/test_utf8.cpp create mode 100644 test/test_utils.cpp create mode 100644 test/test_utils.hpp create mode 100644 test/test_utp.cpp create mode 100644 test/test_web_seed.cpp create mode 100644 test/test_web_seed_ban.cpp create mode 100644 test/test_web_seed_chunked.cpp create mode 100644 test/test_web_seed_http.cpp create mode 100644 test/test_web_seed_http_pw.cpp create mode 100644 test/test_web_seed_redirect.cpp create mode 100644 test/test_web_seed_socks4.cpp create mode 100644 test/test_web_seed_socks5.cpp create mode 100644 test/test_web_seed_socks5_no_peers.cpp create mode 100644 test/test_web_seed_socks5_pw.cpp create mode 100644 test/test_xml.cpp create mode 100644 test/udp_tracker.cpp create mode 100644 test/udp_tracker.hpp create mode 100644 test/utf8_test.txt create mode 100644 test/web_seed_suite.cpp create mode 100644 test/web_seed_suite.hpp create mode 100644 test/web_server.py create mode 100644 test/zeroes.gz create mode 100644 tools/CMakeLists.txt create mode 100644 tools/Jamfile create mode 100644 tools/Makefile.am create mode 100644 tools/Makefile.in create mode 100644 tools/dht_put.cpp create mode 100755 tools/parse_dht_log.py create mode 100755 tools/parse_dht_rtt.py create mode 100755 tools/parse_dht_stats.py create mode 100755 tools/parse_peer_log.py create mode 100755 tools/parse_sample.py create mode 100755 tools/parse_session_stats.py create mode 100755 tools/parse_utp_log.py create mode 100644 tools/session_log_alerts.cpp diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..f5c305c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,29 @@ +Written by Arvid Norberg. Copyright (c) 2003-2018 + +Contributions by: +Andrei Kurushin +Steven Siloti +Alden Torres +Thomas Fischer +Massaroddel +Tianhao Qiu. +Shyam +Magnus Jonsson +Daniel Wallin +Cory Nelson +Stas Khirman +Ryan Norton +Andrew Resch + +Thanks to (github user) nervoir for bug reports + +Building and maintainance of the autotools scripts: +Michael Wojciechowski +Peter Koeleman + +Thanks to Reimond Retz for bugfixes, suggestions and testing + +Thanks to University of UmeŒ for providing development and test hardware. + +Project is hosted by sourceforge. + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9efd451 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,872 @@ +cmake_minimum_required(VERSION 3.12.0 FATAL_ERROR) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) +include(LibtorrentMacros) + +read_version("${CMAKE_CURRENT_SOURCE_DIR}/include/libtorrent/version.hpp" VER_MAJOR VER_MINOR VER_TINY) + +project(libtorrent + DESCRIPTION "Bittorrent library" + VERSION ${VER_MAJOR}.${VER_MINOR}.${VER_TINY} +) +set (SOVERSION "10") + +include(GNUInstallDirs) +include(GeneratePkgConfig) + +set(libtorrent_include_files + add_torrent_params + address + alert + alert_manager + alert_types + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bandwidth_socket + bdecode + bencode + bitfield + block_cache + bloom_filter + broadcast_socket + bt_peer_connection + buffer + chained_buffer + choker + close_reason + config + copy_ptr + crc32c + create_torrent + deadline_timer + debug + disk_buffer_holder + disk_buffer_pool + disk_interface + disk_io_job + disk_io_thread + disk_io_thread_pool + disk_job_pool + disk_observer + download_priority + ed25519 + entry + enum_net + error + error_code + extensions + file + file_pool + file_storage + fingerprint + flags + fwd + gzip + hasher + hasher512 + heterogeneous_queue + hex + http_connection + http_parser + http_seed_connection + http_stream + http_tracker_connection + i2p_stream + identify_client + index_range + invariant_check + io + io_service + io_service_fwd + ip_filter + ip_voter + lazy_entry + link + linked_list + lsd + magnet_uri + natpmp + netlink + operations + optional + packet_buffer + packet_pool + parse_url + part_file + pe_crypto + peer + peer_class + peer_class_set + peer_class_type_filter + peer_connection + peer_connection_handle + peer_connection_interface + peer_id + peer_info + peer_list + peer_request + performance_counters + pex_flags + piece_block + piece_block_progress + piece_picker + platform_util + portmap + proxy_base + puff + random + read_resume_data + receive_buffer + request_blocks + resolve_links + resolver + resolver_interface + session + session_handle + session_settings + session_stats + session_status + session_types + settings_pack + sha1 + sha1_hash + sha512 + sliding_average + socket + socket_io + socks5_stream + span + ssl_stream + stack_allocator + stat + stat_cache + storage + storage_defs + string_util + string_view + tailqueue + time + timestamp_history + torrent + torrent_flags + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + tracker_manager + udp_socket + udp_tracker_connection + union_endpoint + units + upnp + utf8 + utp_socket_manager + utp_stream + vector_utils + version + web_connection_base + web_peer_connection + write_resume_data + xml_parse) + +set(libtorrent_kademlia_include_files + announce_flags + dht_observer + dht_settings + dht_state + dht_storage + dht_tracker + direct_request + dos_blocker + ed25519 + find_data + get_item + get_peers + io + item + msg + node + node_entry + node_id + observer + put_data + refresh + routing_table + rpc_manager + sample_infohashes + traversal_algorithm + types) + +set(libtorrent_extensions_include_files + smart_ban + ut_metadata + ut_pex) + +set(libtorrent_aux_include_files + aligned_storage + aligned_union + alloca + allocating_handler + array + bind_to_device + keepalive + block_cache_reference + byteswap + cppint_import_export + cpuid + deferred_handler + deprecated + deque + dev_random + disable_warnings_pop + disable_warnings_push + disk_job_fence + escape_string + export + ffs + file_progress + has_block + instantiate_connection + io + ip_notifier + listen_socket_handle + lsd + merkle + noexcept_movable + numeric_cast + openssl + path + portmap + proxy_settings + range + route + scope_end + session_call + session_impl + session_interface + session_settings + session_udp_sockets + set_socket_buffer + socket_type + storage_piece_set + storage_utils + string_ptr + suggest_piece + throw + time + torrent_impl + unique_ptr + vector + win_crypto_provider + win_util) + +set(sources + web_connection_base + alert + alert_manager + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bdecode + bitfield + block_cache + bloom_filter + chained_buffer + choker + close_reason + cpuid + crc32c + create_torrent + disk_buffer_holder + entry + error_code + file_storage + file_progress + generate_peer_id + lazy_bdecode + escape_string + string_util + file + path + fingerprint + gzip + hasher + sha1 + hex + http_connection + http_stream + http_parser + i2p_stream + identify_client + ip_filter + ip_notifier + ip_voter + listen_socket_handle + performance_counters + peer_class + peer_class_set + peer_connection + bt_peer_connection + web_peer_connection + http_seed_connection + peer_connection_handle + instantiate_connection + merkle + natpmp + openssl + part_file + packet_buffer + piece_picker + platform_util + proxy_base + peer_list + puff + random + receive_buffer + read_resume_data + write_resume_data + request_blocks + resolve_links + resolver + session + session_call + session_handle + session_impl + session_settings + proxy_settings + session_stats + settings_pack + sha1_hash + socket_io + socket_type + socks5_stream + stat + stat_cache + storage + storage_piece_set + storage_utils + time + timestamp_history + torrent + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + tracker_manager + http_tracker_connection + utf8 + udp_tracker_connection + udp_socket + upnp + utp_socket_manager + utp_stream + file_pool + lsd + disk_io_job + disk_job_fence + disk_job_pool + disk_buffer_pool + disk_io_thread + disk_io_thread_pool + enum_net + broadcast_socket + magnet_uri + parse_url + xml_parse + version + ffs + add_torrent_params + peer_info + stack_allocator + +# -- extensions -- + ut_pex + ut_metadata + smart_ban +) + +# -- kademlia -- +set(kademlia_sources + dht_state + dht_storage + dos_blocker + dht_tracker + msg + node + node_entry + refresh + rpc_manager + find_data + put_data + node_id + routing_table + traversal_algorithm + item + get_peers + get_item + ed25519 + sample_infohashes + dht_settings +) + +# -- ed25519 -- +set(ed25519_sources + add_scalar + fe + ge + key_exchange + keypair + sc + sign + verify +) + +list(TRANSFORM sources PREPEND "src/") +list(TRANSFORM kademlia_sources PREPEND "src/kademlia/") +list(TRANSFORM ed25519_sources PREPEND "ed25519/src/") +list(TRANSFORM libtorrent_include_files PREPEND "include/libtorrent/") +list(TRANSFORM libtorrent_extensions_include_files PREPEND "include/libtorrent/extensions/") +list(TRANSFORM libtorrent_aux_include_files PREPEND "include/libtorrent/aux_/") +list(TRANSFORM libtorrent_kademlia_include_files PREPEND "include/libtorrent/kademlia/") + +# these options control target creation and thus have to be declared before the add_library() call +feature_option(BUILD_SHARED_LIBS "build libtorrent as a shared library" ON) +feature_option(static_runtime "build libtorrent with static runtime" OFF) + +find_public_dependency(Threads REQUIRED) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options( + -Weverything + -Wno-documentation + -Wno-c++98-compat-pedantic + -Wno-padded + -Wno-global-constructors + -Wno-exit-time-destructors + -Wno-weak-vtables) +elseif(CMAKE_CXX_COMPILER_ID MATCHES GNU) + add_compile_options( + -Wall + -Wextra + -Wpedantic + -Wparentheses + -Wvla + -Wc++11-compat + -Wno-format-zero-length + -ftemplate-depth=512) +elseif(MSVC) + add_compile_options( + /W4 + # C4251: 'identifier' : class 'type' needs to have dll-interface to be + # used by clients of class 'type2' + /wd4251 + # C4275: non DLL-interface classkey 'identifier' used as base for + # DLL-interface classkey 'identifier' + /wd4275 + # C4373: virtual function overrides, previous versions of the compiler + # did not override when parameters only differed by const/volatile qualifiers + /wd4373 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + /wd4268 + # C4503: 'identifier': decorated name length exceeded, name was truncated + /wd4503) +endif() + +if(static_runtime) + include(ucm_flags) + ucm_set_runtime(STATIC) + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME ON) + set(OPENSSL_MSVC_STATIC_RT ON) +endif() + +if (NOT BUILD_SHARED_LIBS) + set(Boost_USE_STATIC_LIBS ON) + set(OPENSSL_USE_STATIC_LIBS ON) +endif() + +add_library(torrent-rasterbar + ${sources} + ${libtorrent_include_files} + ${libtorrent_extensions_include_files} + ${libtorrent_aux_include_files} +) + +# C++ 11 support is required +target_compile_features(torrent-rasterbar + PUBLIC + cxx_std_11 + cxx_rvalue_references + cxx_reference_qualified_functions + cxx_nonstatic_member_init + cxx_variadic_templates + cxx_generalized_initializers + cxx_static_assert + cxx_auto_type + cxx_trailing_return_types + cxx_lambdas + cxx_decltype + cxx_right_angle_brackets + cxx_default_function_template_args + cxx_alias_templates + cxx_extern_templates + cxx_nullptr + cxx_strong_enums + cxx_enum_forward_declarations + cxx_attributes + cxx_constexpr + cxx_alignas + cxx_alignof + cxx_delegating_constructors + cxx_inheriting_constructors + cxx_explicit_conversions + cxx_raw_string_literals + cxx_user_literals + cxx_defaulted_functions + cxx_deleted_functions + cxx_extended_friend_declarations + cxx_sizeof_member + cxx_inline_namespaces + cxx_unrestricted_unions + cxx_local_type_template_args + cxx_range_for + cxx_final + cxx_override + cxx_noexcept + cxx_defaulted_move_initializers +) + +if (BUILD_SHARED_LIBS) + target_compile_definitions(torrent-rasterbar + PRIVATE TORRENT_BUILDING_SHARED + INTERFACE TORRENT_LINKING_SHARED + ) +endif() + +set_target_properties(torrent-rasterbar + PROPERTIES + CXX_VISIBILITY_PRESET "hidden" + VISIBILITY_INLINES_HIDDEN "true" + VERSION ${PROJECT_VERSION} + SOVERSION ${SOVERSION} +) + +target_include_directories(torrent-rasterbar PUBLIC + $ + $ +) + +target_compile_definitions(torrent-rasterbar + PUBLIC + $<$:TORRENT_USE_ASSERTS> + BOOST_ASIO_ENABLE_CANCELIO + PRIVATE + TORRENT_BUILDING_LIBRARY + _FILE_OFFSET_BITS=64 + BOOST_EXCEPTION_DISABLE + BOOST_ASIO_HAS_STD_CHRONO +) + +target_link_libraries(torrent-rasterbar + PUBLIC + Threads::Threads +) + +# Unconditional platform-specific settings +if (WIN32) + target_link_libraries(torrent-rasterbar + PRIVATE + wsock32 ws2_32 Iphlpapi + debug dbghelp + ) + + add_definitions(-D_WIN32_WINNT=0x0600) # target Windows Vista or later + + target_compile_definitions(torrent-rasterbar + PUBLIC WIN32_LEAN_AND_MEAN # prevent winsock1 to be included + ) + + if (MSVC) + target_compile_definitions(torrent-rasterbar + PUBLIC + BOOST_ALL_NO_LIB + _SCL_SECURE_NO_DEPRECATE _CRT_SECURE_NO_DEPRECATE # disable bogus deprecation warnings on msvc8 + ) + target_compile_options(torrent-rasterbar + PRIVATE + /Zc:wchar_t /Zc:forScope # these compiler settings just make the compiler standard conforming + /MP # for multi-core compilation + /bigobj # increase the number of sections for obj files + ) + set_target_properties(torrent-rasterbar PROPERTIES LINK_FLAGS_RELEASE "/OPT:ICF=5 /OPT:REF") + endif() +endif() + +if (ANDROID) + target_link_libraries(torrent-rasterbar PRIVATE ${CMAKE_DL_LIBS}) +endif() + +if (APPLE) + # for ip_notifier + target_link_libraries(torrent-rasterbar PRIVATE "-framework CoreFoundation" "-framework SystemConfiguration") +endif() + +feature_option(build_tests "build tests" OFF) +feature_option(build_examples "build examples" OFF) +feature_option(build_tools "build tools" OFF) +feature_option(python-bindings "build python bindings" OFF) + +# these options require existing target +feature_option(dht "enable support for Mainline DHT" ON) +if(NOT build_tests) # tests require deprecated symbols + target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME deprecated-functions DEFAULT ON + DESCRIPTION "enable deprecated functions for backwards compatibility" DISABLED TORRENT_NO_DEPRECATE) +endif() +feature_option(encryption "Enables encryption in libtorrent" ON) +feature_option(exceptions "build with exception support" ON) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME extensions DEFAULT ON + DESCRIPTION "Enables protocol extensions" DISABLED TORRENT_DISABLE_EXTENSIONS) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME i2p DEFAULT ON + DESCRIPTION "build with I2P support" DISABLED TORRENT_USE_I2P=0) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME logging DEFAULT ON + DESCRIPTION "build with logging" DISABLED TORRENT_DISABLE_LOGGING) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME mutable-torrents DEFAULT ON + DESCRIPTION "Enables mutable torrent support" DISABLED TORRENT_DISABLE_MUTABLE_TORRENTS) +target_optional_compile_definitions(torrent-rasterbar PUBLIC FEATURE NAME streaming DEFAULT ON + DESCRIPTION "Enables support for piece deadline" DISABLED TORRENT_DISABLE_STREAMING) + +find_public_dependency(Iconv) +if(MSVC) + set(iconv_package_type OPTIONAL) +else() + set(iconv_package_type RECOMMENDED) +endif() + +set_package_properties(Iconv + PROPERTIES + URL "https://www.gnu.org/software/libiconv/" + DESCRIPTION "GNU encoding conversion library" + TYPE ${iconv_package_type} + PURPOSE "Convert strings between various encodings" +) + +if(Iconv_FOUND) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_ICONV) + target_link_libraries(torrent-rasterbar PRIVATE Iconv::Iconv) +endif() + +find_public_dependency(OpenSSL) +set_package_properties(OpenSSL + PROPERTIES + URL "https://www.openssl.org/" + DESCRIPTION "Full-strength general purpose cryptography library" + TYPE RECOMMENDED + PURPOSE "Provides HTTPS support to libtorrent" +) + +if(TARGET OpenSSL::SSL) + # TODO: needed until https://gitlab.kitware.com/cmake/cmake/issues/19263 is fixed + if(WIN32 AND OPENSSL_USE_STATIC_LIBS) + target_link_libraries(torrent-rasterbar PRIVATE crypt32) + endif() + + target_link_libraries(torrent-rasterbar PUBLIC OpenSSL::SSL) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_OPENSSL) +endif() + +if(TARGET OpenSSL::Crypto) + target_link_libraries(torrent-rasterbar PUBLIC OpenSSL::Crypto) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_LIBCRYPTO) +else() + find_public_dependency(LibGcrypt) + set_package_properties(LibGcrypt + PROPERTIES + URL "https://www.gnupg.org/software/libgcrypt/index.html" + DESCRIPTION "A general purpose cryptographic library" + TYPE RECOMMENDED + PURPOSE "Use GCrypt instead of the built-in functions for RC4 and SHA1" + ) + if (LibGcrypt_FOUND) + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_LIBGCRYPT) + target_link_libraries(torrent-rasterbar PRIVATE LibGcrypt::LibGcrypt) + endif() +endif() + +if (encryption) + target_sources(torrent-rasterbar PRIVATE src/pe_crypto) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_ENCRYPTION) +endif() + +if (dht) + target_sources(torrent-rasterbar PRIVATE + ${kademlia_sources} + ${ed25519_sources} + ${libtorrent_kademlia_include_files} + src/hasher512 + src/sha512 + ) +else() + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_DISABLE_DHT) +endif() + +# Boost +find_public_dependency(Boost REQUIRED COMPONENTS system) +target_include_directories(torrent-rasterbar PUBLIC ${Boost_INCLUDE_DIRS}) +target_link_libraries(torrent-rasterbar PUBLIC ${Boost_SYSTEM_LIBRARY}) + +if (exceptions) + if (MSVC) + target_compile_options(torrent-rasterbar PUBLIC /EHsc) + else (MSVC) + target_compile_options(torrent-rasterbar PUBLIC -fexceptions) + endif (MSVC) +else() + if (MSVC) + target_compile_definitions(torrent-rasterbar PUBLIC _HAS_EXCEPTIONS=0) + else (MSVC) + target_compile_options(torrent-rasterbar PUBLIC -fno-exceptions) + endif (MSVC) +endif() + +# developer options +option(developer-options "Activates options useful for a developer") +if(developer-options) + set(asserts "auto" CACHE STRING "use assertions") + set_property(CACHE asserts PROPERTY STRINGS auto on off production system) + if ("${asserts}" MATCHES "on|production|system") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_ASSERTS=1) + endif() + if ("${asserts}" STREQUAL "production") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_PRODUCTION_ASSERTS=1) + elseif("${asserts}" STREQUAL "system") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_SYSTEM_ASSERTS=1) + endif() + + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME asio-debugging DEFAULT OFF + ENABLED TORRENT_ASIO_DEBUGGING) + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME picker-debugging DEFAULT OFF + ENABLED TORRENT_DEBUG_REFCOUNTS) + set(invariant-checks "off" CACHE STRING "") + set_property(CACHE invariant-checks PROPERTY STRINGS off on full) + if (invariant-checks MATCHES "on|full") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_USE_INVARIANT_CHECKS=1) + endif() + if (invariant-checks STREQUAL "full") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_EXPENSIVE_INVARIANT_CHECKS) + endif() + + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME utp-log DEFAULT OFF + ENABLED TORRENT_UTP_LOG_ENABLE) + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME simulate-slow-read DEFAULT OFF + ENABLED TORRENT_SIMULATE_SLOW_READ) + option(debug-iterators "" OFF) + if (debug-iterators) + if (MSVC) + target_compile_definitions(torrent-rasterbar PUBLIC _ITERATOR_DEBUG_LEVEL=2) + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_definitions(torrent-rasterbar PUBLIC _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC) + endif() + endif() + target_optional_compile_definitions(torrent-rasterbar PUBLIC NAME profile-calls DEFAULT OFF + ENABLED TORRENT_PROFILE_CALLS=1) +endif() + +# This is best effort attempt to propagate whether the library was built with +# C++11 or not. It affects the ABI of entry. A client building with C++14 and +# linking against a libtorrent binary built with C++11 can still define +# TORRENT_CXX11_ABI +if ("${CMAKE_CXX_STANDARD}" STREQUAL "11") + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_CXX11_ABI) +endif() + +# There is little to none support for using pkg-config with MSVC and most users won't bother with it. +# However, msys is a linux-like platform on Windows that do support/prefer using pkg-config. +if (NOT MSVC) + generate_and_install_pkg_config_file(torrent-rasterbar libtorrent-rasterbar) +endif() + +include(CheckCXXCompilerFlag) + +add_subdirectory(bindings) + +install(TARGETS torrent-rasterbar EXPORT LibtorrentRasterbarTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) +install(DIRECTORY include/libtorrent DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h*") + +# === generate a CMake Config File === +include(CMakePackageConfigHelpers) +set(ConfigPackageLocation ${CMAKE_INSTALL_LIBDIR}/cmake/LibtorrentRasterbar) +string(REGEX REPLACE "([^;]+)" "find_dependency(\\1)" _find_dependency_calls "${_package_dependencies}") +string(REPLACE ";" "\n" _find_dependency_calls "${_find_dependency_calls}") + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfigVersion.cmake" + VERSION ${libtorrent_VERSION} + COMPATIBILITY SameMinorVersion +) + +export(EXPORT LibtorrentRasterbarTargets + FILE "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarTargets.cmake" + NAMESPACE LibtorrentRasterbar:: +) + +configure_package_config_file(LibtorrentRasterbarConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfig.cmake" + INSTALL_DESTINATION "${ConfigPackageLocation}" + NO_SET_AND_CHECK_MACRO + NO_CHECK_REQUIRED_COMPONENTS_MACRO +) + +install(EXPORT LibtorrentRasterbarTargets + NAMESPACE + LibtorrentRasterbar:: + DESTINATION + ${ConfigPackageLocation} +) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/LibtorrentRasterbar/LibtorrentRasterbarConfigVersion.cmake" + DESTINATION + ${ConfigPackageLocation} +) + +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/examples/cmake/FindLibtorrentRasterbar.cmake + DESTINATION + ${CMAKE_INSTALL_DATADIR}/cmake/Modules +) + +# === build tools === +if (build_tools) + add_subdirectory(tools) +endif() + +# === build examples === +if (build_examples) + add_subdirectory(examples) +endif() + +# === build tests === +if(build_tests) + enable_testing() + # this will make some internal functions available in the DLL interface + target_compile_definitions(torrent-rasterbar PUBLIC TORRENT_EXPORT_EXTRA) + add_subdirectory(test) +endif() + +feature_summary(DEFAULT_DESCRIPTION WHAT ALL) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e666c6d --- /dev/null +++ b/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..820bf15 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1915 @@ +1.2.9 released + + * add macro TORRENT_CXX11_ABI for clients building with C++14 against + libtorrent build with C++11 + * refreshed m4 scripts for autotools + * removed deprecated wstring overloads on non-windows systems + * drop dependency on Unicode's ConvertUTF code (which had a license + incompatible with Debian) + * fix bugs exposed on big-endian systems + * fix detection of hard-links not being supported by filesystem + * fixed resume data regression for seeds with prio 0 files + +1.2.8 released + + * validate UTF-8 encoding of client version strings from peers + * don't time out tracker announces as eagerly while resolving hostnames + * fix NAT-PMP shutdown issue + * improve hostname lookup by merging identical lookups + * fix network route enumeration for large routing tables + * fixed issue where pop_alerts() could return old, invalid alerts + * fix issue when receiving have-all message before the metadata + * don't leave lingering part files handles open + * disallow calling add_piece() during checking + * fix incorrect filename truncation at multi-byte character + * always announce listen port 1 when using a proxy + +1.2.7 released + + * add set_alert_fd in python binding, to supersede set_alert_notify + * fix bug in part files > 2 GiB + * add function to clear the peer list for a torrent + * fix resume data functions to save/restore more torrent flags + * limit number of concurrent HTTP announces + * fix queue position for force_rechecking a torrent that is not auto-managed + * improve rate-based choker documentation, and minor tweak + * undeprecate upnp_ignore_nonrouters (but refering to devices on our subnet) + * increase default tracker timeout + * retry failed socks5 server connections + * allow UPnP lease duration to be changed after device discovery + * fix IPv6 address change detection on Windows + +1.2.6 released + + * fix peer timeout logic + * simplify proxy handling. A proxy now overrides listen_interfaces + * fix issues when configured to use a non-default choking algorithm + * fix issue in reading resume data + * revert NXDOMAIN change from 1.2.4 + * don't open any listen sockets if listen_interfaces is empty or misconfigured + * fix bug in auto disk cache size logic + * fix issue with outgoing_interfaces setting, where bind() would be called twice + * add build option to disable share-mode + * support validation of HTTPS trackers + * deprecate strict super seeding mode + * make UPnP port-mapping lease duration configurable + * deprecate the bittyrant choking algorithm + * add build option to disable streaming + +1.2.5 release + + * announce port=1 instead of port=0, when there is no listen port + * fix LSD over IPv6 + * support TCP_NOTSENT_LOWAT on Linux + * fix correct interface binding of local service discovery multicast + * fix issue with knowing which interfaces to announce to trackers and DHT + * undeprecate settings_pack::dht_upload_rate_limit + +1.2.4 release + + * fix binding TCP and UDP sockets to the same port, when specifying port 0 + * fix announce_to_all_trackers and announce_to_all_tiers behavior + * fix suggest_read_cache setting + * back-off tracker hostname looksups resulting in NXDOMAIN + * lower SOCKS5 UDP keepalive timeout + * fix external IP voting for multi-homed DHT nodes + * deprecate broadcast_lsd setting. Just use multicast + * deprecate upnp_ignore_nonrouters setting + * don't attempt sending event=stopped if event=start never succeeded + * make sure &key= stays consistent between different source IPs (as mandated by BEP7) + * fix binding sockets to outgoing interface + * add new socks5_alert to trouble shoot SOCKS5 proxies + +1.2.3 release + + * fix erroneous event=completed tracker announce when checking files + * promote errors in parsing listen_interfaces to post listen_failed_alert + * fix bug in protocol encryption/obfuscation + * fix buffer overflow in SOCKS5 UDP logic + * fix issue of rapid calls to file_priority() clobbering each other + * clear tracker errors on success + * optimize setting with unlimited unchoke slots + * fixed restoring of trackers, comment, creation date and created-by in resume data + * fix handling of torrents with too large pieces + * fixed division by zero in anti-leech choker + * fixed bug in torrent_info::swap + +1.2.2 release + + * fix cases where the disable_hash_checks setting was not honored + * fix updating of is_finished torrent status, when changing piece priorities + * fix regression in &left= reporting when adding a seeding torrent + * fix integer overflow in http parser + * improve sanitation of symlinks, to support more complex link targets + * add DHT routing table affinity for BEP 42 nodes + * add torrent_info constructor overloads to control torrent file limits + * feature to disable DHT, PEX and LSD per torrent + * fix issue where trackers from magnet links were not included in create_torrent() + * make peer_info::client a byte array in python binding + * pick contiguous pieces from peers with high download rate + * fix error handling of moving storage to a drive letter that isn't mounted + * fix HTTP Host header when using proxy + +1.2.1 release + + * add dht_pkt_alert and alerts_dropped_alert to python bindings + * fix python bindins for block_uploaded_alert + * optimize resolving duplicate filenames in loading torrent files + * fix python binding of dht_settings + * tighten up various input validation checks + * fix create_torrent python binding + * update symlinks to conform to BEP 47 + * fix python bindings for peer_info + * support creating symlinks, for torrents with symlinks in them + * fix error in seed_mode flag + * support magnet link parameters with number siffixes + * consistently use "lt" namespace in examples and documentation + * fix Mingw build to use native cryptoAPI + * uPnP/NAT-PMP errors no longer set the client's advertised listen port to zero + +1.2 release + + * requires boost >= 1.58 to build + * tweak heuristic of how to interpret url seeds in multi-file torrents + * support &ipv4= tracker argument for private torrents + * renamed debug_notification to connect_notification + * when updating listen sockets, only post alerts for new ones + * deprecate anonymous_mode_alert + * deprecated force_proxy setting (when set, the proxy is always used) + * add support for Port Control Protocol (PCP) + * deliver notification of alerts being dropped via alerts_dropped_alert + * deprecated alert::progress_notification alert category, split into + finer grained categories + * update plugin interface functions for improved type-safety + * implemented support magnet URI extension, select specific file indices + for download, BEP53 + * make tracker keys multi-homed. remove set_key() function on session. + * add flags()/set_flags()/unset_flags() to torrent_handle, deprecate individual functions + * added alert for block being sent to the send buffer + * drop support for windows compilers without std::wstring + * implemented support for DHT infohash indexing, BEP51 + * removed deprecated support for file_base in file_storage + * added support for running separate DHT nodes on each network interface + * added support for establishing UTP connections on any network interface + * added support for sending tracker announces on every network interface + * introduce "lt" namespace alias + * need_save_resume_data() will no longer return true every 15 minutes + * make the file_status interface explicitly public types + * added resolver_cache_timeout setting for internal host name resolver + * make parse_magnet_uri take a string_view instead of std::string + * deprecate add_torrent_params::url field. use parse_magnet_uri instead + * optimize download queue management + * deprecated (undocumented) file:// urls + * add limit for number of web seed connections + * added support for retrieval of DHT live nodes + * complete UNC path support + * add packets pool allocator + * remove disk buffer pool allocator + * fix last_upload and last_download overflow after 9 hours in past + * python binding add more add_torrent_params fields and an invalid key check + * introduce introduce distinct types for peer_class_t, piece_index_t and + file_index_t. + * fix crash caused by empty bitfield + * removed disk-access-log build configuration + * removed mmap_cache feature + * strengthened type safety in handling of piece and file indices + * deprecate identify_client() and fingerprint type + * make sequence number for mutable DHT items backed by std::int64_t + * tweaked storage_interface to have stronger type safety + * deprecate relative times in torrent_status, replaced by std::chrono::time_point + * refactor in alert types to use more const fields and more clear API + * changed session_stats_alert counters type to signed (std::int64_t) + * remove torrent eviction/ghost torrent feature + * include target in DHT lookups, when queried from the session + * improve support for HTTP redirects for web seeds + * use string_view in entry interface + * deprecate "send_stats" property on trackers (since lt_tracker extension has + been removed) + * remove deprecate session_settings API (use settings_pack instead) + * improve file layout optimization when creating torrents with padfiles + * remove remote_dl_rate feature + * source code migration from boost::shared_ptr to std::shared_ptr + * storage_interface API changed to use span and references + * changes in public API to work with std::shared_ptr + * extensions API changed to use span and std::shared_ptr + * plugin API changed to handle DHT requests using string_view + * removed support for lt_trackers and metadata_transfer extensions + (pre-dating ut_metadata) + * support windows' CryptoAPI for SHA-1 + * separated ssl and crypto options in build + * remove lazy-bitfield feature + * simplified suggest-read-cache feature to not depend on disk threads + * removed option to disable contiguous receive buffers + * deprecated public to_hex() and from_hex() functions + * separated address and port fields in listen alerts + * added support for parsing new x.pe parameter from BEP 9 + * peer_blocked_alert now derives from peer_alert + * transitioned exception types to system_error + * made alerts move-only + * move files one-by-one when moving storage for a torrent + * removed RSS support + * removed feature to resolve country for peers + * added support for BEP 32, "IPv6 extension for DHT" + * overhauled listen socket and UDP socket handling, improving multi-home + support and bind-to-device + * resume data is now communicated via add_torrent_params objects + * added new read_resume_data()/write_resume_data functions to write bencoded, + backwards compatible resume files + * removed deprecated fields from add_torrent_params + * deprecate "resume_data" field in add_torrent_params + * improved support for bind-to-device + * deprecated ssl_listen, SSL sockets are specified in listen_interfaces now + * improved support for listening on multiple sockets and interfaces + * resume data no longer has timestamps of files + * require C++11 to build libtorrent + + * replace use of boost-endian with boost-predef + +1.1.12 release + + * uTP performance fixes + +1.1.11 release + + * fix move_storage with save_path with a trailing slash + * fix tracker announce issue, advertising port 0 in secondary IPv6 announce + * fix missing boost/noncopyable.hpp includes + * fix python binding for torrent_info::creation_date() + +1.1.10 release + + * fix issue in udp_socket with unusual socket failure + * split progress_notification alert category into file-, piece- and block progress + * utp close-reason fix + * exposed default add_torrent_params flags to python bindings + * fix redundant flushes of partfile metadata + * add option to ignore min-interval from trackers on force-reannounce + * raise default setting for active_limit + * fall back to copy+remove if rename_file fails + * improve handling of filesystems not supporting fallocate() + * force-proxy no longer disables DHT + * improve connect-boost feature, to make new torrents quickly connect peers + +1.1.9 release + + * save both file and piece priorities in resume file + * added missing stats_metric python binding + * uTP connections are no longer exempt from rate limits by default + * fix exporting files from partfile while seeding + * fix potential deadlock on Windows, caused by performing restricted + tasks from within DllMain + * fix issue when subsequent file priority updates cause torrent to stop + +1.1.8 release + + * coalesce reads and writes by default on windows + * fixed disk I/O performance of checking hashes and creating torrents + * fix race condition in part_file + * fix part_file open mode compatibility test + * fixed race condition in random number generator + * fix race condition in stat_cache (disk storage) + * improve error handling of failing to change file priority + The API for custom storage implementations was altered + * set the hidden attribute when creating the part file + * fix tracker announces reporting more data downloaded than the size of the torrent + * fix recent regression with force_proxy setting + +1.1.7 release + + * don't perform DNS lookups for the DHT bootstrap unless DHT is enabled + * fix issue where setting file/piece priority would stop checking + * expose post_dht_stats() to python binding + * fix backwards compatibility to downloads without partfiles + * improve part-file related error messages + * fix reporting &redundant= in tracker announces + * fix tie-break in duplicate peer connection disconnect logic + * fix issue with SSL tracker connections left in CLOSE_WAIT state + * defer truncating existing files until the first time we write to them + * fix issue when receiving a torrent with 0-sized padfiles as magnet link + * fix issue resuming 1.0.x downloads with a file priority 0 + * fix torrent_status::next_announce + * fix pad-file scalability issue + * made coalesce_reads/coalesce_writes settings take effect on linux and windows + * use unique peer_ids per connection + * fix iOS build on recent SDK + * fix tracker connection bind issue for IPv6 trackers + * fix error handling of some merkle torrents + * fix error handling of unsupported hard-links + +1.1.6 release + + * deprecate save_encryption_settings (they are part of the normal settings) + * add getters for peer_class_filter and peer_class_type_filter + * make torrent_handler::set_priority() to use peer_classes + * fix support for boost-1.66 (requires C++11) + * fix i2p support + * fix loading resume data when in seed mode + * fix part-file creation race condition + * fix issue with initializing settings on session construction + * fix issue with receiving interested before metadata + * fix IPv6 tracker announce issue + * restore path sanitization behavior of ":" + * fix listen socket issue when disabling "force_proxy" mode + * fix full allocation failure on APFS + +1.1.5 release + + * fix infinite loop when parsing certain invalid magnet links + * fix parsing of torrents with certain invalid filenames + * fix leak of torrent_peer objecs (entries in peer_list) + * fix leak of peer_class objects (when setting per-torrent rate limits) + * expose peer_class API to python binding + * fix integer overflow in whole_pieces_threshold logic + * fix uTP path MTU discovery issue on windows (DF bit was not set correctly) + * fix python binding for torrent_handle, to be hashable + * fix IPv6 tracker support by performing the second announce in more cases + * fix utf-8 encoding check in torrent parser + * fix infinite loop when parsing maliciously crafted torrents + * fix invalid read in parse_int in bdecoder (CVE-2017-9847) + * fix issue with very long tracker- and web seed URLs + * don't attempt to create empty files on startup, if they already exist + * fix force-recheck issue (new files would not be picked up) + * fix inconsistency in file_priorities and override_resume_data behavior + * fix paused torrents not generating a state update when their ul/dl rate + transitions to zero + +1.1.4 release + + * corrected missing const qualifiers on bdecode_node + * fix changing queue position of paused torrents (1.1.3 regression) + * fix re-check issue after move_storage + * handle invalid arguments to set_piece_deadline() + * move_storage did not work for torrents without metadata + * improve shutdown time by only announcing to trackers whose IP we know + * fix python3 portability issue in python binding + * delay 5 seconds before reconnecting socks5 proxy for UDP ASSOCIATE + * fix NAT-PMP crash when removing a mapping at the wrong time + * improve path sanitization (filter unicode text direction characters) + * deprecate partial_piece_info::piece_state + * bind upnp requests to correct local address + * save resume data when removing web seeds + * fix proxying of https connections + * fix race condition in disk I/O storage class + * fix http connection timeout on multi-homed hosts + * removed depdendency on boost::uintptr_t for better compatibility + * fix memory leak in the disk cache + * fix double free in disk cache + * forward declaring libtorrent types is discouraged. a new fwd.hpp header is provided + +1.1.3 release + + * removed (broken) support for incoming connections over socks5 + * restore announce_entry's timestamp fields to posix time in python binding + * deprecate torrent_added_alert (in favor of add_torrent_alert) + * fix python binding for parse_magnet_uri + * fix minor robustness issue in DHT bootstrap logic + * fix issue where torrent_status::num_seeds could be negative + * document deprecation of dynamic loading/unloading of torrents + * include user-agent in tracker announces in anonymous_mode for private torrents + * add support for IPv6 peers from udp trackers + * correctly URL encode the IPv6 argument to trackers + * fix default file pool size on windows + * fix bug where settings_pack::file_pool_size setting was not being honored + * add feature to periodically close files (to make windows clear disk cache) + * fix bug in torrent_handle::file_status + * fix issue with peers not updated on metadata from magnet links + +1.1.2 release + + * default TOS marking to 0x20 + * fix invalid access when leaving seed-mode with outstanding hash jobs + * fix ABI compatibility issue introduced with preformatted entry type + * add web_seed_name_lookup_retry to session_settings + * slightly improve proxy settings backwards compatibility + * add function to get default settings + * updating super seeding would include the torrent in state_update_alert + * fix issue where num_seeds could be greater than num_peers in torrent_status + * finished non-seed torrents can also be in super-seeding mode + * fix issue related to unloading torrents + * fixed finished-time calculation + * add missing min_memory_usage() and high_performance_seed() settings presets to python + * fix stat cache issue that sometimes would produce incorrect resume data + * storage optimization to peer classes + * fix torrent name in alerts of builds with deprecated functions + * make torrent_info::is_valid() return false if torrent failed to load + * fix per-torrent rate limits for >256 peer classes + * don't load user_agent and peer_fingerprint from session_state + * fix file rename issue with name prefix matching torrent name + * fix division by zero when setting tick_interval > 1000 + * fix move_storage() to its own directory (would delete the files) + * fix socks5 support for UDP + * add setting urlseed_max_request_bytes to handle large web seed requests + * fix python build with CC/CXX environment + * add trackers from add_torrent_params/magnet links to separate tiers + * fix resumedata check issue with files with priority 0 + * deprecated mmap_cache feature + * add utility function for generating peer ID fingerprint + * fix bug in last-seen-complete + * remove file size limit in torrent_info filename constructor + * fix tail-padding for last file in create_torrent + * don't send user-agent in metadata http downloads or UPnP requests when + in anonymous mode + * fix internal resolve links lookup for mutable torrents + * hint DHT bootstrap nodes of actual bootstrap request + +1.1.1 release + + * update puff.c for gzip inflation (CVE-2016-7164) + * add dht_bootstrap_node a setting in settings_pack (and add default) + * make pad-file and symlink support conform to BEP47 + * fix piece picker bug that could result in division by zero + * fix value of current_tracker when all tracker failed + * deprecate lt_trackers extension + * remove load_asnum_db and load_country_db from python bindings + * fix crash in session::get_ip_filter when not having set one + * fix filename escaping when repairing torrents with broken web seeds + * fix bug where file_completed_alert would not be posted unless file_progress + had been queries by the client + * move files one-by-one when moving storage for a torrent + * fix bug in enum_net() for BSD and Mac + * fix bug in python binding of announce_entry + * fixed bug related to flag_merge_resume_http_seeds flag in add_torrent_params + * fixed inverted priority of incoming piece suggestions + * optimize allow-fast logic + * fix issue where FAST extension messages were not used during handshake + * fixed crash on invalid input in http_parser + * upgraded to libtommath 1.0 + * fixed parsing of IPv6 endpoint with invalid port character separator + * added limited support for new x.pe parameter from BEP 9 + * fixed dht stats counters that weren't being updated + * make sure add_torrent_alert is always posted before other alerts for + the torrent + * fixed peer-class leak when settings per-torrent rate limits + * added a new "preformatted" type to bencode entry variant type + * improved Socks5 support and test coverage + * fix set_settings in python binding + * Added missing alert categories in python binding + * Added dht_get_peers_reply_alert alert in python binding + * fixed updating the node id reported to peers after changing IPs + +1.1.0 release + + * improve robustness and performance of uTP PMTU discovery + * fix duplicate ACK issue in uTP + * support filtering which parts of session state are loaded by load_state() + * deprecate support for adding torrents by HTTP URL + * allow specifying which tracker to scrape in scrape_tracker + * tracker response alerts from user initiated announces/scrapes are now + posted regardless of alert mask + * improve DHT performance when changing external IP (primarily affects + bootstrapping). + * add feature to stop torrents immediately after checking files is done + * make all non-auto managed torrents exempt from queuing logic, including + checking torrents. + * add option to not proxy tracker connections through proxy + * removed sparse-regions feature + * support using 0 disk threads (to perform disk I/O in network thread) + * removed deprecated handle_alert template + * enable logging build config by default (but alert mask disabled by default) + * deprecated RSS API + * experimental support for BEP 38, "mutable torrents" + * replaced lazy_bdecode with a new bdecoder that's a lot more efficient + * deprecate time functions, expose typedefs of boost::chrono in the + libtorrent namespace instead + * deprecate file_base feature in file_storage/torrent_info + * changed default piece and file priority to 4 (previously 1) + * improve piece picker support for reverse picking (used for snubbed peers) + to not cause priority inversion for regular peers + * improve piece picker to better support torrents with very large pieces + and web seeds. (request large contiguous ranges, but not necessarily a + whole piece). + * deprecated session_status and session::status() in favor of performance + counters. + * improve support for HTTP where one direction of the socket is shut down. + * remove internal fields from web_seed_entry + * separate crypto library configuration and whether to support + bittorrent protocol encryption + * simplify bittorrent protocol encryption by just using internal RC4 + implementation. + * optimize copying torrent_info and file_storage objects + * cancel non-critical DNS lookups when shutting down, to cut down on + shutdown delay. + * greatly simplify the debug logging infrastructure. logs are now delivered + as alerts, and log level is controlled by the alert mask. + * removed auto_expand_choker. use rate_based_choker instead + * optimize UDP tracker packet handling + * support SSL over uTP connections + * support web seeds that resolve to multiple IPs + * added auto-sequential feature. download well-seeded torrents in-order + * removed built-in GeoIP support (this functionality is orthogonal to + libtorrent) + * deprecate proxy settings in favor of regular settings + * deprecate separate settings for peer protocol encryption + * support specifying listen interfaces and outgoing interfaces as device + names (eth0, en2, tun0 etc.) + * support for using purgrable memory as disk cache on Mac OS. + * be more aggressive in corking sockets, to coalesce messages into larger + packets. + * pre-emptively unchoke peers to save one round-trip at connection start-up. + * add session constructor overload that takes a settings_pack + * torrent_info is no longer an intrusive_ptr type. It is held by shared_ptr. + This is a non-backwards compatible change + * move listen interface and port to the settings + * move use_interfaces() to be a setting + * extend storage interface to allow deferred flushing and flush the part-file + metadata periodically + * make statistics propagate instantly rather than on the second tick + * support for partfiles, where partial pieces belonging to skipped files are + put + * support using multiple threads for socket operations (especially useful for + high performance SSL connections) + * allow setting rate limits for arbitrary peer groups. Generalizes + per-torrent rate limits, and local peer limits + * improved disk cache complexity O(1) instead of O(log(n)) + * add feature to allow storing disk cache blocks in an mmapped file + (presumably on an SSD) + * optimize peer connection distribution logic across torrents to scale + better with many torrents + * replaced std::map with boost::unordered_map for torrent list, to scale + better with many torrents + * optimized piece picker + * optimized disk cache + * optimized .torrent file parsing + * optimized initialization of storage when adding a torrent + * added support for adding torrents asynchronously (for improved startup + performance) + * added support for asynchronous disk I/O + * almost completely changed the storage interface (for custom storage) + * added support for hashing pieces in multiple threads + + * fix padfile issue + * fix PMTUd bug + * update puff to fix gzip crash + +1.0.10 release + + * fixed inverted priority of incoming piece suggestions + * fixed crash on invalid input in http_parser + * added a new "preformatted" type to bencode entry variant type + * fix division by zero in super-seeding logic + +1.0.9 release + + * fix issue in checking outgoing interfaces (when that option is enabled) + * python binding fix for boost-1.60.0 + * optimize enumeration of network interfaces on windows + * improve reliability of binding listen sockets + * support SNI in https web seeds and trackers + * fix unhandled exception in DHT when receiving a DHT packet over IPv6 + +1.0.8 release + + * fix bug where web seeds were not used for torrents added by URL + * fix support for symlinks on windows + * fix long filename issue (on unixes) + * fixed performance bug in DHT torrent eviction + * fixed win64 build (GetFileAttributesEx) + * fixed bug when deleting files for magnet links before they had metadata + +1.0.7 release + + * fix bug where loading settings via load_state() would not trigger all + appropriate actions + * fix bug where 32 bit builds could use more disk cache than the virtual + address space (when set to automatic) + * fix support for torrents with > 500'000 pieces + * fix ip filter bug when banning peers + * fix IPv6 IP address resolution in URLs + * introduce run-time check for torrent info-sections beeing too large + * fix web seed bug when using proxy and proxy-peer-connections=false + * fix bug in magnet link parser + * introduce add_torrent_params flags to merge web seeds with resume data + (similar to trackers) + * fix bug where dont_count_slow_torrents could not be disabled + * fix fallocate hack on linux (fixes corruption on some architectures) + * fix auto-manage bug with announce to tracker/lsd/dht limits + * improve DHT routing table to not create an unbalanced tree + * fix bug in uTP that would cause any connection taking more than one second + to connect be timed out (introduced in the vulnerability path) + * fixed falling back to sending UDP packets direct when socks proxy fails + * fixed total_wanted bug (when setting file priorities in add_torrent_params) + * fix python3 compatibility with sha1_hash + +1.0.6 release + + * fixed uTP vulnerability + * make utf8 conversions more lenient + * fix loading of piece priorities from resume data + * improved seed-mode handling (seed-mode will now automatically be left when + performing operations implying it's not a seed) + * fixed issue with file priorities and override resume data + * fix request queue size performance issue + * slightly improve UDP tracker performance + * fix http scrape + * add missing port mapping functions to python binding + * fix bound-checking issue in bdecoder + * expose missing dht_settings fields to python + * add function to query the DHT settings + * fix bug in 'dont_count_slow_torrents' feature, which would start too many + torrents + +1.0.5 release + + * improve ip_voter to avoid flapping + * fixed bug when max_peerlist_size was set to 0 + * fix issues with missing exported symbols when building dll + * fix division by zero bug in edge case while connecting peers + +1.0.4 release + + * fix bug in python binding for file_progress on torrents with no metadata + * fix assert when removing a connected web seed + * fix bug in tracker timeout logic + * switch UPnP post back to HTTP 1.1 + * support conditional DHT get + * OpenSSL build fixes + * fix DHT scrape bug + +1.0.3 release + + * python binding build fix for boost-1.57.0 + * add --enable-export-all option to configure script, to export all symbols + from libtorrent + * fix if_nametoindex build error on windows + * handle overlong utf-8 sequences + * fix link order bug in makefile for python binding + * fix bug in interest calculation, causing premature disconnects + * tweak flag_override_resume_data semantics to make more sense (breaks + backwards compatibility of edge-cases) + * improve DHT bootstrapping and periodic refresh + * improve DHT maintanence performance (by pinging instead of full lookups) + * fix bug in DHT routing table node-id prefix optimization + * fix incorrect behavior of flag_use_resume_save_path + * fix protocol race-condition in super seeding mode + * support read-only DHT nodes + * remove unused partial hash DHT lookups + * remove potentially privacy leaking extension (non-anonymous mode) + * peer-id connection ordering fix in anonymous mode + * mingw fixes + +1.0.2 release + + * added missing force_proxy to python binding + * anonymous_mode defaults to false + * make DHT DOS detection more forgiving to bursts + * support IPv6 multicast in local service discovery + * simplify CAS function in DHT put + * support IPv6 traffic class (via the TOS setting) + * made uTP re-enter slow-start after time-out + * fixed uTP upload performance issue + * fix missing support for DHT put salt + +1.0.1 release + + * fix alignment issue in bitfield + * improved error handling of gzip + * fixed crash when web seeds redirect + * fix compiler warnings + +1.0 release + + * fix bugs in convert_to/from_native() on windows + * fix support for web servers not supporting keepalive + * support storing save_path in resume data + * don't use full allocation on network drives (on windows) + * added clear_piece_deadlines() to remove all piece deadlines + * improve queuing logic of inactive torrents (dont_count_slow_torrents) + * expose optimistic unchoke logic to plugins + * fix issue with large UDP packets on windows + * remove set_ratio() feature + * improve piece_deadline/streaming + * honor pieces with priority 7 in sequential download mode + * simplified building python bindings + * make ignore_non_routers more forgiving in the case there are no UPnP + devices at a known router. Should improve UPnP compatibility. + * include reason in peer_blocked_alert + * support magnet links wrapped in .torrent files + * rate limiter optimization + * rate limiter overflow fix (for very high limits) + * non-auto-managed torrents no longer count against the torrent limits + * handle DHT error responses correctly + * allow force_announce to only affect a single tracker + * add moving_storage field to torrent_status + * expose UPnP and NAT-PMP mapping in session object + * DHT refactoring and support for storing arbitrary data with put and get + * support building on android + * improved support for web seeds that don't support keep-alive + * improve DHT routing table to return better nodes (lower RTT and closer + to target) + * don't use pointers to resume_data and file_priorities in + add_torrent_params + * allow moving files to absolute paths, out of the download directory + * make move_storage more generic to allow both overwriting files as well + as taking existing ones + * fix choking issue at high upload rates + * optimized rate limiter + * make disk cache pool allocator configurable + * fix library ABI to not depend on logging being enabled + * use hex encoding instead of base32 in create_magnet_uri + * include name, save_path and torrent_file in torrent_status, for + improved performance + * separate anonymous mode and force-proxy mode, and tighten it up a bit + * add per-tracker scrape information to announce_entry + * report errors in read_piece_alert + * DHT memory optimization + * improve DHT lookup speed + * improve support for windows XP and earlier + * introduce global connection priority for improved swarm performance + * make files deleted alert non-discardable + * make built-in sha functions not conflict with libcrypto + * improve web seed hash failure case + * improve DHT lookup times + * uTP path MTU discovery improvements + * optimized the torrent creator optimizer to scale significantly better + with more files + * fix uTP edge case where udp socket buffer fills up + * fix nagle implementation in uTP + + * fix bug in error handling in protocol encryption + +0.16.18 release + + * fix uninitialized values in DHT DOS mitigation + * fix error handling in file::phys_offset + * fix bug in HTTP scrape response parsing + * enable TCP keepalive for socks5 connection for UDP associate + * fix python3 support + * fix bug in lt_donthave extension + * expose i2p_alert to python. cleaning up of i2p connection code + * fixed overflow and download performance issue when downloading at high rates + * fixed bug in add_torrent_alert::message for magnet links + * disable optimistic disconnects when connection limit is low + * improved error handling of session::listen_on + * suppress initial 'completed' announce to trackers added with replace_trackers + after becoming a seed + * SOCKS4 fix for trying to connect over IPv6 + * fix saving resume data when removing all trackers + * fix bug in udp_socket when changing socks5 proxy quickly + +0.16.17 release + + * don't fall back on wildcard port in UPnP + * fix local service discovery for magnet links + * fix bitfield issue in file_storage + * added work-around for MingW issue in file I/O + * fixed sparse file detection on windows + * fixed bug in gunzip + * fix to use proxy settings when adding .torrent file from URL + * fix resume file issue related to daylight savings time on windows + * improve error checking in lazy_bdecode + +0.16.16 release + + * add missing add_files overload to the python bindings + * improve error handling in http gunzip + * fix debug logging for banning web seeds + * improve support for de-selected files in full allocation mode + * fix dht_bootstrap_alert being posted + * SetFileValidData fix on windows (prevents zero-fill) + * fix minor lock_files issue on unix + +0.16.15 release + + * fix mingw time_t 64 bit issue + * fix use of SetFileValidData on windows + * fix crash when using full allocation storage mode + * improve error_code and error_category support in python bindings + * fix python binding for external_ip_alert + +0.16.14 release + + * make lt_tex more robust against bugs and malicious behavior + * HTTP chunked encoding fix + * expose file_granularity flag to python bindings + * fix DHT memory error + * change semantics of storage allocation to allocate on first write rather + than on startup (behaves better with changing file priorities) + * fix resend logic in response to uTP SACK messages + * only act on uTP RST packets with correct ack_nr + * make uTP errors log in normal log mode (not require verbose) + * deduplicate web seed entries from torrent files + * improve error reporting from lazy_decode() + +0.16.13 release + + * fix auto-manage issue when pausing session + * fix bug in non-sparse mode on windows, causing incorrect file errors to + be generated + * fix set_name() on file_storage actually affecting save paths + * fix large file support issue on mingw + * add some error handling to set_piece_hashes() + * fix completed-on timestamp to not be clobbered on each startup + * fix deadlock caused by some UDP tracker failures + * fix potential integer overflow issue in timers on windows + * minor fix to peer_proportional mixed_mode algorithm (TCP limit could go + too low) + * graceful pause fix + * i2p fixes + * fix issue when loading certain malformed .torrent files + * pass along host header with http proxy requests and possible + http_connection shutdown hang + +0.16.12 release + + * fix building with C++11 + * fix IPv6 support in UDP socket (uTP) + * fix mingw build issues + * increase max allowed outstanding piece requests from peers + * uTP performance improvement. only fast retransmit one packet at a time + * improve error message for 'file too short' + * fix piece-picker stat bug when only selecting some files for download + * fix bug in async_add_torrent when settings file_priorities + * fix boost-1.42 support for python bindings + * fix memory allocation issue (virtual addres space waste) on windows + +0.16.11 release + + * fix web seed URL double escape issue + * fix string encoding issue in alert messages + * fix SSL authentication issue + * deprecate std::wstring overloads. long live utf-8 + * improve time-critical pieces feature (streaming) + * introduce bandwidth exhaustion attack-mitigation in allowed-fast pieces + * python binding fix issue where torrent_info objects where destructing when + their torrents were deleted + * added missing field to scrape_failed_alert in python bindings + * GCC 4.8 fix + * fix proxy failure semantics with regards to anonymous mode + * fix round-robin seed-unchoke algorithm + * add bootstrap.sh to generage configure script and run configure + * fix bug in SOCK5 UDP support + * fix issue where torrents added by URL would not be started immediately + +0.16.10 release + + * fix encryption level handle invalid values + * add a number of missing functions to the python binding + * fix typo in Jamfile for building shared libraries + * prevent tracker exchange for magnet links before metadata is received + * fix crash in make_magnet_uri when generating links longer than 1024 + characters + * fix hanging issue when closing files on windows (completing a download) + * fix piece picking edge case that could cause torrents to get stuck at + hash failure + * try unencrypted connections first, and fall back to encryption if it + fails (performance improvement) + * add missing functions to python binding (flush_cache(), remap_files() + and orig_files()) + * improve handling of filenames that are invalid on windows + * support 'implied_port' in DHT announce_peer + * don't use pool allocator for disk blocks (cache may now return pages + to the kernel) + +0.16.9 release + + * fix long filename truncation on windows + * distinguish file open mode when checking files and downloading/seeding + with bittorrent. updates storage interface + * improve file_storage::map_file when dealing with invalid input + * improve handling of invalid utf-8 sequences in strings in torrent files + * handle more cases of broken .torrent files + * fix bug filename collision resolver + * fix bug in filename utf-8 verification + * make need_save_resume() a bit more robust + * fixed sparse flag manipulation on windows + * fixed streaming piece picking issue + +0.16.8 release + + * make rename_file create missing directories for new filename + * added missing python function: parse_magnet_uri + * fix alerts.all_categories in python binding + * fix torrent-abort issue which would cancel name lookups of other torrents + * make torrent file parser reject invalid path elements earlier + * fixed piece picker bug when using pad-files + * fix read-piece response for cancelled deadline-pieces + * fixed file priority vector-overrun + * fix potential packet allocation alignment issue in utp + * make 'close_redudnant_connections' cover more cases + * set_piece_deadline() also unfilters the piece (if its priority is 0) + * add work-around for bug in windows vista and earlier in + GetOverlappedResult + * fix traversal algorithm leak in DHT + * fix string encoding conversions on windows + * take torrent_handle::query_pieces into account in torrent_handle::statue() + * honor trackers responding with 410 + * fixed merkle tree torrent creation bug + * fixed crash with empty url-lists in torrent files + * added missing max_connections() function to python bindings + +0.16.7 release + + * fix string encoding in error messages + * handle error in read_piece and set_piece_deadline when torrent is removed + * DHT performance improvement + * attempt to handle ERROR_CANT_WAIT disk error on windows + * improve peers exchanged over PEX + * fixed rare crash in ut_metadata extension + * fixed files checking issue + * added missing pop_alerts() to python bindings + * fixed typos in configure script, inversing some feature-enable/disable flags + * added missing flag_update_subscribe to python bindings + * active_dht_limit, active_tracker_limit and active_lsd_limit now + interpret -1 as infinite + +0.16.6 release + + * fixed verbose log error for NAT holepunching + * fix a bunch of typos in python bindings + * make get_settings available in the python binding regardless of + deprecated functions + * fix typo in python settings binding + * fix possible dangling pointer use in peer list + * fix support for storing arbitrary data in the DHT + * fixed bug in uTP packet circle buffer + * fix potential crash when using torrent_handle::add_piece + * added missing add_torrent_alert to python binding + +0.16.5 release + + * udp socket refcounter fix + * added missing async_add_torrent to python bindings + * raised the limit for bottled http downloads to 2 MiB + * add support for magnet links and URLs in python example client + * fixed typo in python bindings' add_torrent_params + * introduce a way to add built-in plugins from python + * consistently disconnect the same peer when two peers simultaneously connect + * fix local endpoint queries for uTP connections + * small optimization to local peer discovery to ignore our own broadcasts + * try harder to bind the udp socket (uTP, DHT, UDP-trackers, LSD) to the + same port as TCP + * relax file timestamp requirements for accepting resume data + * fix performance issue in web seed downloader (coalescing of blocks + sometimes wouldn't work) + * web seed fixes (better support for torrents without trailing / in + web seeds) + * fix some issues with SSL over uTP connections + * fix UDP trackers trying all endpoints behind the hostname + +0.16.4 release + + * raise the default number of torrents allowed to announce to trackers + to 1600 + * improve uTP slow start behavior + * fixed UDP socket error causing it to fail on Win7 + * update use of boost.system to not use deprecated functions + * fix GIL issue in python bindings. Deprecated extension support in python + * fixed bug where setting upload slots to -1 would not mean infinite + * extend the UDP tracker protocol to include the request string from the + tracker URL + * fix mingw build for linux crosscompiler + +0.16.3 release + + * fix python binding backwards compatibility in replace_trackers + * fix possible starvation in metadata extension + * fix crash when creating torrents and optimizing file order with pad files + * disable support for large MTUs in uTP until it is more reliable + * expose post_torrent_updates and state_update_alert to python bindings + * fix incorrect SSL error messages + * fix windows build of shared library with openssl + * fix race condition causing shutdown hang + +0.16.2 release + + * fix permissions issue on linux with noatime enabled for non-owned files + * use random peer IDs in anonymous mode + * fix move_storage bugs + * fix unnecessary dependency on boost.date_time when building boost.asio as separate compilation + * always use SO_REUSEADDR and deprecate the flag to turn it on + * add python bindings for SSL support + * minor uTP tweaks + * fix end-game mode issue when some files are selected to not be downloaded + * improve uTP slow start + * make uTP less aggressive resetting cwnd when idle + +0.16.1 release + + * fixed crash when providing corrupt resume data + * fixed support for boost-1.44 + * fixed reversed semantics of queue_up() and queue_down() + * added missing functions to python bindings (file_priority(), set_dht_settings()) + * fixed low_prio_disk support on linux + * fixed time critical piece accounting in the request queue + * fixed semantics of rate_limit_utp to also ignore per-torrent limits + * fixed piece sorting bug of deadline pieces + * fixed python binding build on Mac OS and BSD + * fixed UNC path normalization (on windows, unless UNC paths are disabled) + * fixed possible crash when enabling multiple connections per IP + * fixed typo in win vista specific code, breaking the build + * change default of rate_limit_utp to true + * fixed DLL export issue on windows (when building a shared library linking statically against boost) + * fixed FreeBSD build + * fixed web seed performance issue with pieces > 1 MiB + * fixed unchoke logic when using web seeds + * fixed compatibility with older versions of boost (down to boost 1.40) + +0.16 release + + * support torrents with more than 262000 pieces + * make tracker back-off configurable + * don't restart the swarm after downloading metadata from magnet links + * lower the default tracker retry intervals + * support banning web seeds sending corrupt data + * don't let hung outgoing connection attempts block incoming connections + * improve SSL torrent support by using SNI and a single SSL listen socket + * improved peer exchange performance by sharing incoming connections which advertize listen port + * deprecate set_ratio(), and per-peer rate limits + * add web seed support for torrents with pad files + * introduced a more scalable API for torrent status updates (post_torrent_updates()) and updated client_test to use it + * updated the API to add_torrent_params turning all bools into flags of a flags field + * added async_add_torrent() function to significantly improve performance when + adding many torrents + * change peer_states to be a bitmask (bw_limit, bw_network, bw_disk) + * changed semantics of send_buffer_watermark_factor to be specified as a percentage + * add incoming_connection_alert for logging all successful incoming connections + * feature to encrypt peer connections with a secret AES-256 key stored in .torrent file + * deprecated compact storage allocation + * close files in separate thread on systems where close() may block (Mac OS X for instance) + * don't create all directories up front when adding torrents + * support DHT scrape + * added support for fadvise/F_RDADVISE for improved disk read performance + * introduced pop_alerts() which pops the entire alert queue in a single call + * support saving metadata in resume file, enable it by default for magnet links + * support for receiving multi announce messages for local peer discovery + * added session::listen_no_system_port flag to prevent libtorrent from ever binding the listen socket to port 0 + * added option to not recheck on missing or incomplete resume data + * extended stats logging with statistics=on builds + * added new session functions to more efficiently query torrent status + * added alerts for added and removed torrents + * expanded plugin interface to support session wide states + * made the metadata block requesting algorithm more robust against hash check failures + * support a separate option to use proxies for peers or not + * pausing the session now also pauses checking torrents + * moved alert queue size limit into session_settings + * added support for DHT rss feeds (storing only) + * added support for RSS feeds + * fixed up some edge cases in DHT routing table and improved unit test of it + * added error category and error codes for HTTP errors + * made the DHT implementation slightly more robust against routing table poisoning and node ID spoofing + * support chunked encoding in http downloads (http_connection) + * support adding torrents by url to the .torrent file + * support CDATA tags in xml parser + * use a python python dictionary for settings instead of session_settings object (in python bindings) + * optimized metadata transfer (magnet link) startup time (shaved off about 1 second) + * optimized swarm startup time (shaved off about 1 second) + * support DHT name lookup + * optimized memory usage of torrent_info and file_storage, forcing some API changes + around file_storage and file_entry + * support trackerid tracker extension + * graceful peer disconnect mode which finishes transactions before disconnecting peers + * support chunked encoding for web seeds + * uTP protocol support + * resistance towards certain flood attacks + * support chunked encoding for web seeds (only for BEP 19, web seeds) + * optimized session startup time + * support SSL for web seeds, through all proxies + * support extending web seeds with custom authorization and extra headers + * settings that are not changed from the default values are not saved + in the session state + * made seeding choking algorithm configurable + * deprecated setters for max connections, max half-open, upload and download + rates and unchoke slots. These are now set through session_settings + * added functions to query an individual peer's upload and download limit + * full support for BEP 21 (event=paused) + * added share-mode feature for improving share ratios + * merged all proxy settings into a single one + * improved SOCKS5 support by proxying hostname lookups + * improved support for multi-homed clients + * added feature to not count downloaded bytes from web seeds in stats + * added alert for incoming local service discovery messages + * added option to set file priorities when adding torrents + * removed the session mutex for improved performance + * added upload and download activity timer stats for torrents + * made the reuse-address flag configurable on the listen socket + * moved UDP trackers over to use a single socket + * added feature to make asserts log to a file instead of breaking the process + (production asserts) + * optimized disk I/O cache clearing + * added feature to ask a torrent if it needs to save its resume data or not + * added setting to ignore file modification time when loading resume files + * support more fine-grained torrent states between which peer sources it + announces to + * supports calculating sha1 file-hashes when creating torrents + * made the send_buffer_watermark performance warning more meaningful + * supports complete_ago extension + * dropped zlib as a dependency and builds using puff.c instead + * made the default cache size depend on available physical RAM + * added flags to torrent::status() that can filter which values are calculated + * support 'explicit read cache' which keeps a specific set of pieces + in the read cache, without implicitly caching other pieces + * support sending suggest messages based on what's in the read cache + * clear sparse flag on files that complete on windows + * support retry-after header for web seeds + * replaced boost.filesystem with custom functions + * replaced dependency on boost.thread by asio's internal thread primitives + * added support for i2p torrents + * cleaned up usage of MAX_PATH and related macros + * made it possible to build libtorrent without RTTI support + * added support to build with libgcrypt and a shipped version of libtommath + * optimized DHT routing table memory usage + * optimized disk cache to work with large caches + * support variable number of optimistic unchoke slots and to dynamically + adjust based on the total number of unchoke slots + * support for BitTyrant choker algorithm + * support for automatically start torrents when they receive an + incoming connection + * added more detailed instrumentation of the disk I/O thread + +0.15.11 release + + * fixed web seed bug, sometimes causing infinite loops + * fixed race condition when setting session_settings immediately after creating session + * give up immediately when failing to open a listen socket (report the actual error) + * restored ABI compatibility with 0.15.9 + * added missing python bindings for create_torrent and torrent_info + +0.15.10 release + + * fix 'parameter incorrect' issue when using unbuffered IO on windows + * fixed UDP socket error handling on windows + * fixed peer_tos (type of service) setting + * fixed crash when loading resume file with more files than the torrent in it + * fix invalid-parameter error on windows when disabling filesystem disk cache + * fix connection queue issue causing shutdown delays + * fixed mingw build + * fix overflow bug in progress_ppm field + * don't filter local peers received from a non-local tracker + * fix python deadlock when using python extensions + * fixed small memory leak in DHT + +0.15.9 release + + * added some functions missing from the python binding + * fixed rare piece picker bug + * fixed invalid torrent_status::finished_time + * fixed bugs in dont-have and upload-only extension messages + * don't open files in random-access mode (speeds up hashing) + +0.15.8 release + + * allow NULL to be passed to create_torrent::set_comment and create_torrent::set_creator + * fix UPnP issue for routers with multiple PPPoE connections + * fix issue where event=stopped announces wouldn't be sent when closing session + * fix possible hang in file::readv() on windows + * fix CPU busy loop issue in tracker announce logic + * honor IOV_MAX when using writev and readv + * don't post 'operation aborted' UDP errors when changing listen port + * fix tracker retry logic, where in some configurations the next tier would not be tried + * fixed bug in http seeding logic (introduced in 0.15.7) + * add support for dont-have extension message + * fix for set_piece_deadline + * add reset_piece_deadline function + * fix merkle tree torrent assert + +0.15.7 release + + * exposed set_peer_id to python binding + * improve support for merkle tree torrent creation + * exposed comparison operators on torrent_handle to python + * exposed alert error_codes to python + * fixed bug in announce_entry::next_announce_in and min_announce_in + * fixed sign issue in set_alert_mask signature + * fixed unaligned disk access for unbuffered I/O in windows + * support torrents whose name is empty + * fixed connection limit to take web seeds into account as well + * fixed bug when receiving a have message before having the metadata + * fixed python bindings build with disabled DHT support + * fixed BSD file allocation issue + * fixed bug in session::delete_files option to remove_torrent + +0.15.6 release + + * fixed crash in udp trackers when using SOCKS5 proxy + * fixed reconnect delay when leaving upload only mode + * fixed default values being set incorrectly in add_torrent_params through add_magnet_uri in python bindings + * implemented unaligned write (for unbuffered I/O) + * fixed broadcast_lsd option + * fixed udp-socket race condition when using a proxy + * end-game mode optimizations + * fixed bug in udp_socket causing it to issue two simultaneous async. read operations + * fixed mingw build + * fixed minor bug in metadata block requester (for magnet links) + * fixed race condition in iconv string converter + * fixed error handling in torrent_info constructor + * fixed bug in torrent_info::remap_files + * fix python binding for wait_for_alert + * only apply privileged port filter to DHT-only peers + +0.15.5 release + + * support DHT extension to report external IPs + * fixed rare crash in http_connection's error handling + * avoid connecting to peers listening on ports < 1024 + * optimized piece picking to not cause busy loops in some end-game modes + * fixed python bindings for tcp::endpoint + * fixed edge case of pad file support + * limit number of torrents tracked by DHT + * fixed bug when allow_multiple_connections_per_ip was enabled + * potential WOW64 fix for unbuffered I/O (windows) + * expose set_alert_queue_size_limit to python binding + * support dht nodes in magnet links + * support 100 Continue HTTP responses + * changed default choker behavior to use 8 unchoke slots (instead of being rate based) + * fixed error reporting issue in disk I/O thread + * fixed file allocation issues on linux + * fixed filename encoding and decoding issue on platforms using iconv + * reports redundant downloads to tracker, fixed downloaded calculation to + be more stable when not including redundant. Improved redundant data accounting + to be more accurate + * fixed bugs in http seed connection and added unit test for it + * fixed error reporting when fallocate fails + * deprecate support for separate proxies for separate kinds of connections + +0.15.4 release + + * fixed piece picker issue triggered by hash failure and timed out requests to the piece + * fixed optimistic unchoke issue when setting per torrent unchoke limits + * fixed UPnP shutdown issue + * fixed UPnP DeletePortmapping issue + * fixed NAT-PMP issue when adding the same mapping multiple times + * no peers from tracker when stopping is no longer an error + * improved web seed retry behavior + * fixed announce issue + +0.15.3 release + + * fixed announce bug where event=completed would not be sent if it violated the + min-announce of the tracker + * fixed limitation in rate limiter + * fixed build error with boost 1.44 + +0.15.2 release + + * updated compiler to msvc 2008 for python binding + * restored default fail_limit to unlimited on all trackers + * fixed rate limit bug for DHT + * fixed SOCKS5 bug for routing UDP packets + * fixed bug on windows when verifying resume data for a torrent where + one of its directories had been removed + * fixed race condition in peer-list with DHT + * fix force-reannounce and tracker retry issue + +0.15.1 release + + * fixed rare crash when purging the peer list + * fixed race condition around m_abort in session_impl + * fixed bug in web_peer_connection which could cause a hang when downloading + from web servers + * fixed bug in metadata extensions combined with encryption + * refactored socket reading code to not use async. operations unnecessarily + * some timer optimizations + * removed the reuse-address flag on the listen socket + * fixed bug where local peer discovery and DHT wouldn't be announced to without trackers + * fixed bug in bdecoder when decoding invalid messages + * added build warning when building with UNICODE but the standard library + doesn't provide std::wstring + * fixed add_node python binding + * fixed issue where trackers wouldn't tried immediately when the previous one failed + * fixed synchronization issue between download queue and piece picker + * fixed bug in udp tracker scrape response parsing + * fixed bug in the disk thread that could get triggered under heavy load + * fixed bug in add_piece() that would trigger asserts + * fixed vs 2010 build + * recognizes more clients in identify_client() + * fixed bug where trackers wouldn't be retried if they failed + * slight performance fix in disk elevator algorithm + * fixed potential issue where a piece could be checked twice + * fixed build issue on windows related to GetCompressedSize() + * fixed deadlock when starting torrents with certain invalid tracker URLs + * fixed iterator bug in disk I/O thread + * fixed FIEMAP support on linux + * fixed strict aliasing warning on gcc + * fixed inconsistency when creating torrents with symlinks + * properly detect windows version to initialize half-open connection limit + * fixed bug in url encoder where $ would not be encoded + +0.15 release + + * introduced a session state save mechanism. load_state() and save_state(). + this saves all session settings and state (except torrents) + * deprecated dht_state functions and merged it with the session state + * added support for multiple trackers in magnet links + * added support for explicitly flushing the disk cache + * added torrent priority to affect bandwidth allocation for its peers + * reduced the number of floating point operations (to better support + systems without FPU) + * added new alert when individual files complete + * added support for storing symbolic links in .torrent files + * added support for uTorrent interpretation of multi-tracker torrents + * handle torrents with duplicate filenames + * piece timeouts are adjusted to download rate limits + * encodes urls in torrent files that needs to be encoded + * fixed not passing &supportcrypto=1 when encryption is disabled + * introduced an upload mode, which torrents are switched into when + it hits a disk write error, instead of stopping the torrent. + this lets libtorrent keep uploading the parts it has when it + encounters a disk-full error for instance + * improved disk error handling and expanded use of error_code in + error reporting. added a bandwidth state, bw_disk, when waiting + for the disk io thread to catch up writing buffers + * improved read cache memory efficiency + * added another cache flush algorithm to write the largest + contiguous blocks instead of the least recently used + * introduced a mechanism to be lighter on the disk when checking torrents + * applied temporary memory storage optimization to when checking + a torrent as well + * removed hash_for_slot() from storage_interface. It is now implemented + by using the readv() function from the storage implementation + * improved IPv6 support by announcing twice when necessary + * added feature to set a separate global rate limit for local peers + * added preset settings for low memory environments and seed machines + min_memory_usage() and high_performance_seeder() + * optimized overall memory usage for DHT nodes and requests, peer + entries and disk buffers + * change in API for block_info in partial_piece_info, instead of + accessing 'peer', call 'peer()' + * added support for fully automatic unchoker (no need to specify + number of upload slots). This is on by default + * added support for changing socket buffer sizes through + session_settings + * added support for merkle hash tree torrents (.merkle.torrent) + * added 'seed mode', which assumes that all files are complete + and checks hashes lazily, as blocks are requested + * added new extension for file attributes (executable and hidden) + * added support for unbuffered I/O for aligned files + * added workaround for sparse file issue on Windows Vista + * added new lt_trackers extension to exchange trackers between + peers + * added support for BEP 17 http seeds + * added read_piece() to read pieces from torrent storage + * added option for udp tracker preference + * added super seeding + * added add_piece() function to inject data from external sources + * add_tracker() function added to torrent_handle + * if there is no working tracker, current_tracker is the + tracker that is currently being tried + * torrents that are checking can now be paused, which will + pause the checking + * introduced another torrent state, checking_resume_data, which + the torrent is in when it's first added, and is comparing + the files on disk with the resume data + * DHT bandwidth usage optimizations + * rate limited DHT send socket + * tracker connections are now also subject to IP filtering + * improved optimistic unchoke logic + * added monitoring of the DHT lookups + * added bandwidth reports for estimated TCP/IP overhead and DHT + * includes DHT traffic in the rate limiter + * added support for bitcomet padding files + * improved support for sparse files on windows + * added ability to give seeding torrents preference to active slots + * added torrent_status::finished_time + * automatically caps files and connections by default to rlimit + * added session::is_dht_running() function + * added torrent_handle::force_dht_announce() + * added torrent_info::remap_files() + * support min_interval tracker extension + * added session saving and loading functions + * added support for min-interval in tracker responses + * only keeps one outstanding duplicate request per peer + reduces waste download, specifically when streaming + * added support for storing per-peer rate limits across reconnects + * improved fallocate support + * fixed magnet link issue when using resume data + * support disk I/O priority settings + * added info_hash to torrent_deleted_alert + * improved LSD performance and made the interval configurable + * improved UDP tracker support by caching connect tokens + * fast piece optimization + +release 0.14.10 + + * fixed udp tracker race condition + * added support for torrents with odd piece sizes + * fixed issue with disk read cache not being cleared when removing torrents + * made the DHT socket bind to the same interface as the session + * fixed issue where an http proxy would not be used on redirects + * Solaris build fixes + * disabled buggy disconnect_peers feature + +release 0.14.9 + + * disabled feature to drop requests after having been skipped too many times + * fixed range request bug for files larger than 2 GB in web seeds + * don't crash when trying to create torrents with 0 files + * fixed big_number __init__ in python bindings + * fixed optimistic unchoke timer + * fixed bug where torrents with incorrectly formatted web seed URLs would be + connected multiple times + * fixed MinGW support + * fixed DHT bootstrapping issue + * fixed UDP over SOCKS5 issue + * added support for "corrupt" tracker announce + * made end-game mode less aggressive + +release 0.14.8 + + * ignore unkown metadata messages + * fixed typo that would sometimes prevent queued torrents to be checked + * fixed bug in auto-manager where active_downloads and active_seeds would + sometimes be used incorrectly + * force_recheck() no longer crashes on torrents with no metadata + * fixed broadcast socket regression from 0.14.7 + * fixed hang in NATPMP when shut down while waiting for a response + * fixed some more error handling in bdecode + +release 0.14.7 + + * fixed deadlock in natpmp + * resume data alerts are always posted, regardless of alert mask + * added wait_for_alert to python binding + * improved invalid filename character replacement + * improved forward compatibility in DHT + * added set_piece_hashes that takes a callback to the python binding + * fixed division by zero in get_peer_info() + * fixed bug where pieces may have been requested before the metadata + was received + * fixed incorrect error when deleting files from a torrent where + not all files have been created + * announces torrents immediately to the DHT when it's started + * fixed bug in add_files that would fail to recurse if the path + ended with a / + * fixed bug in error handling when parsing torrent files + * fixed file checking bug when renaming a file before checking the torrent + * fixed race conditon when receiving metadata from swarm + * fixed assert in ut_metadata plugin + * back-ported some fixes for building with no exceptions + * fixed create_torrent when passing in a path ending with / + * fixed move_storage when source doesn't exist + * fixed DHT state save bug for node-id + * fixed typo in python binding session_status struct + * broadcast sockets now join every network interface (used for UPnP and + local peer discovery) + +release 0.14.6 + + * various missing include fixes to be buildable with boost 1.40 + * added missing functions to python binding related to torrent creation + * fixed to add filename on web seed urls that lack it + * fixed BOOST_ASIO_HASH_MAP_BUCKETS define for boost 1.39 + * fixed checking of fast and suggest messages when used with magnet links + * fixed bug where web seeds would not disconnect if being resolved when + the torrent was paused + * fixed download piece performance bug in piece picker + * fixed bug in connect candidate counter + * replaces invalid filename characters with . + * added --with-libgeoip option to configure script to allow building and + linking against system wide library + * fixed potential pure virtual function call in extensions on shutdown + * fixed disk buffer leak in smart_ban extension + +release 0.14.5 + + * fixed bug when handling malformed webseed urls and an http proxy + * fixed bug when setting unlimited upload or download rates for torrents + * fix to make torrent_status::list_peers more accurate. + * fixed memory leak in disk io thread when not using the cache + * fixed bug in connect candidate counter + * allow 0 upload slots + * fixed bug in rename_file(). The new name would not always be saved in + the resume data + * fixed resume data compatibility with 0.13 + * fixed rare piece-picker bug + * fixed bug where one allowed-fast message would be sent even when + disabled + * fixed race condition in UPnP which could lead to crash + * fixed inversed seed_time ratio logic + * added get_ip_filter() to session + +release 0.14.4 + + * connect candidate calculation fix + * tightened up disk cache memory usage + * fixed magnet link parser to accept hex-encoded info-hashes + * fixed inverted logic when picking which peers to connect to + (should mean a slight performance improvement) + * fixed a bug where a failed rename_file() would leave the storage + in an error state which would pause the torrent + * fixed case when move_storage() would fail. Added a new alert + to be posted when it does + * fixed crash bug when shutting down while checking a torrent + * fixed handling of web seed urls that didn't end with a + slash for multi-file torrents + * lowered the default connection speed to 10 connection attempts + per second + * optimized memory usage when checking files fails + * fixed bug when checking a torrent twice + * improved handling of out-of-memory conditions in disk I/O thread + * fixed bug when force-checking a torrent with partial pieces + * fixed memory leak in disk cache + * fixed torrent file path vulnerability + * fixed upnp + * fixed bug when dealing with clients that drop requests (i.e. BitComet) + fixes assert as well + +release 0.14.3 + + * added python binding for create_torrent + * fixed boost-1.38 build + * fixed bug where web seeds would be connected before the files + were checked + * fixed filename bug when using wide characters + * fixed rare crash in peer banning code + * fixed potential HTTP compatibility issue + * fixed UPnP crash + * fixed UPnP issue where the control url contained the base url + * fixed a replace_trackers bug + * fixed bug where the DHT port mapping would not be removed when + changing DHT port + * fixed move_storage bug when files were renamed to be moved out + of the root directory + * added error handling for set_piece_hashes + * fixed missing include in enum_if.cpp + * fixed dual IP stack issue + * fixed issue where renamed files were sometimes not saved in resume data + * accepts tracker responses with no 'peers' field, as long as 'peers6' + is present + * fixed CIDR-distance calculation in the precense of IPv6 peers + * save partial resume data for torrents that are queued for checking + or checking, to maintain stats and renamed files + * Don't try IPv6 on windows if it's not installed + * move_storage fix + * fixed potential crash on shutdown + * fixed leaking exception from bdecode on malformed input + * fixed bug where connection would hang when receiving a keepalive + * fixed bug where an asio exception could be thrown when resolving + peer countries + * fixed crash when shutting down while checking a torrent + * fixed potential crash in connection_queue when a peer_connection + fail to open its socket + +release 0.14.2 + + * added missing functions to the python bindings torrent_info::map_file, + torrent_info::map_block and torrent_info::file_at_offset. + * removed support for boost-1.33 and earlier (probably didn't work) + * fixed potential freezes issues at shutdown + * improved error message for python setup script + * fixed bug when torrent file included announce-list, but no valid + tracker urls + * fixed bug where the files requested from web seeds would be the + renamed file names instead of the original file names in the torrent. + * documentation fix of queing section + * fixed potential issue in udp_socket (affected udp tracker support) + * made name, comment and created by also be subject to utf-8 error + correction (filenames already were) + * fixed dead-lock when settings DHT proxy + * added missing export directives to lazy_entry + * fixed disk cache expiry settings bug (if changed, it would be set + to the cache size) + * fixed bug in http_connection when binding to a particular IP + * fixed typo in python binding (torrent_handle::piece_prioritize should + be torrent_handle::piece_priorities) + * fixed race condition when saving DHT state + * fixed bugs related to lexical_cast being locale dependent + * added support for SunPro C++ compiler + * fixed bug where messeges sometimes could be encrypted in the + wrong order, for encrypted connections. + * fixed race condition where torrents could get stuck waiting to + get checked + * fixed mapped files bug where it wouldn't be properly restored + from resume data properly + * removed locale dependency in xml parser (caused asserts on windows) + * fixed bug when talking to https 1.0 servers + * fixed UPnP bug that could cause stack overflow + +release 0.14.1 + + * added converter for python unicode strings to utf-8 paths + * fixed bug in http downloader where the host field did not + include the port number + * fixed headers to not depend on NDEBUG, which would prohibit + linking a release build of libtorrent against a debug application + * fixed bug in disk I/O thread that would make the thread + sometimes quit when an error occurred + * fixed DHT bug + * fixed potential shutdown crash in disk_io_thread + * fixed usage of deprecated boost.filsystem functions + * fixed http_connection unit test + * fixed bug in DHT when a DHT state was loaded + * made rate limiter change in 0.14 optional (to take estimated + TCP/IP overhead into account) + * made the python plugin buildable through the makefile + * fixed UPnP bug when url base ended with a slash and + path started with a slash + * fixed various potentially leaking exceptions + * fixed problem with removing torrents that are checking + * fixed documentation bug regarding save_resume_data() + * added missing documentation on torrent creation + * fixed bugs in python client examples + * fixed missing dependency in package-config file + * fixed shared geoip linking in Jamfile + * fixed python bindings build on windows and made it possible + to generate a windows installer + * fixed bug in NAT-PMP implementation + +release 0.14 + + * deprecated add_torrent() in favor of a new add_torrent() + that takes a struct with parameters instead. Torrents + are paused and auto managed by default. + * removed 'connecting_to_tracker' torrent state. This changes + the enum values for the other states. + * Improved seeding and choking behavior. + * Fixed rare buffer overrun bug when calling get_download_queue + * Fixed rare bug where torrent could be put back into downloading + state even though it was finished, after checking files. + * Fixed rename_file to work before the file on disk has been + created. + * Fixed bug in tracker connections in case of errors caused + in the connection constructor. + * Updated alert system to be filtered by category instead of + severity level. Alerts can generate a message through + alert::message(). + * Session constructor will now start dht, upnp, natpmp, lsd by + default. Flags can be passed in to the constructor to not + do this, if these features are to be enabled and disabled + at a later point. + * Removed 'connecting_to_tracker' torrent state + * Fix bug where FAST pieces were cancelled on choke + * Fixed problems with restoring piece states when hash failed. + * Minimum peer reconnect time fix. Peers with no failures would + reconnect immediately. + * Improved web seed error handling + * DHT announce fixes and off-by-one loop fix + * Fixed UPnP xml parse bug where it would ignore the port number + for the control url. + * Fixed bug in torrent writer where the private flag was added + outside of the info dictionary + * Made the torrent file parser less strict of what goes in the + announce-list entry + * Fixed type overflow bug where some statistics was incorrectly + reported for file larger than 2 GB + * boost-1.35 support + * Fixed bug in statistics from web server peers where it sometimes + could report too many bytes downloaded. + * Fixed bug where statistics from the last second was lost when + disconnecting a peer. + * receive buffer optimizations (memcpy savings and memory savings) + * Support for specifying the TOS byte for peer traffic. + * Basic support for queueing of torrents. + * Better bias to give connections to downloading torrents + with fewer peers. + * Optimized resource usage (removed the checking thread) + * Support to bind outgoing connections to specific ports + * Disk cache support. + * New, more memory efficient, piece picker with sequential download + support (instead of the more complicated sequential download threshold). + * Auto Upload slots. Automtically opens up more slots if + upload limit is not met. + * Improved NAT-PMP support by querying the default gateway + * Improved UPnP support by ignoring routers not on the clients subnet. + +release 0.13 + + * Added scrape support + * Added add_extension() to torrent_handle. Can instantiate + extensions for torrents while downloading + * Added support for remove_torrent to delete the files as well + * Fixed issue with failing async_accept on windows + * DHT improvements, proper error messages are now returned when + nodes sends bad packets + * Optimized the country table used to resolve country of peers + * Copying optimization for sending data. Data is no longer copied from + the disk I/O buffer to the send buffer. + * Buffer optimization to use a raw buffer instead of std::vector + * Improved file storage to use sparse files + * Updated python bindings + * Added more clients to the identifiable clients list. + * Torrents can now be started in paused state (to better support queuing) + * Improved IPv6 support (support for IPv6 extension to trackers and + listens on both IPv6 and IPv4 interfaces). + * Improved asserts used. Generates a stacktrace on linux + * Piece picker optimizations and improvements + * Improved unchoker, connection limit and rate limiter + * Support for FAST extension + * Fixed invalid calculation in DHT node distance + * Fixed bug in URL parser that failed to parse IPv6 addresses + * added peer download rate approximation + * added port filter for outgoing connection (to prevent + triggering firewalls) + * made most parameters configurable via session_settings + * added encryption support + * added parole mode for peers whose data fails the hash check. + * optimized heap usage in piece-picker and web seed downloader. + * fixed bug in DHT where older write tokens weren't accepted. + * added support for sparse files. + * introduced speed categories for peers and pieces, to separate + slow and fast peers. + * added a half-open tcp connection limit that takes all connections + in to account, not just peer connections. + * added alerts for filtered IPs. + * added support for SOCKS4 and 5 proxies and HTTP CONNECT proxies. + * fixed proper distributed copies calculation. + * added option to use openssl for sha-1 calculations. + * optimized the piece picker in the case where a peer is a seed. + * added support for local peer discovery + * removed the dependency on the compiled boost.date_time library + * deprecated torrent_info::print() + * added UPnP support + * fixed problem where peer interested flags were not updated correctly + when pieces were filtered + * improvements to ut_pex messages, including support for seed flag + * prioritizes upload bandwidth to peers that might send back data + * the following functions have been deprecated: + void torrent_handle::filter_piece(int index, bool filter) const; + void torrent_handle::filter_pieces(std::vector const& pieces) const; + bool torrent_handle::is_piece_filtered(int index) const; + std::vector torrent_handle::filtered_pieces() const; + void torrent_handle::filter_files(std::vector const& files) const; + + instead, use the piece_priority functions. + + * added support for NAT-PMP + * added support for piece priorities. Piece filtering is now set as + a priority + * Fixed crash when last piece was smaller than one block and reading + fastresume data for that piece + * Makefiles should do a better job detecting boost + * Fixed crash when all tracker urls are removed + * Log files can now be created at user supplied path + * Log files failing to create is no longer fatal + * Fixed dead-lock in torrent_handle + * Made it build with boost 1.34 on windows + * Fixed bug in URL parser that failed to parse IPv6 addresses + * Fixed bug in DHT, related to IPv6 nodes + * DHT accepts transaction IDs that have garbage appended to them + * DHT logs messages that it fails to decode + +release 0.12 + + * fixes to make the DHT more compatible + * http seed improvements including error reporting and url encoding issues. + * fixed bug where directories would be left behind when moving storage + in some cases. + * fixed crashing bug when restarting or stopping the DHT. + * added python binding, using boost.python + * improved character conversion on windows when strings are not utf-8. + * metadata extension now respects the private flag in the torrent. + * made the DHT to only be used as a fallback to trackers by default. + * added support for HTTP redirection support for web seeds. + * fixed race condition when accessing a torrent that was checking its + fast resume data. + * fixed a bug in the DHT which could be triggered if the network was + dropped or extremely rare cases. + * if the download rate is limited, web seeds will now only use left-over + bandwidth after all bt peers have used up as much bandwidth as they can. + * added the possibility to have libtorrent resolve the countries of + the peers in torrents. + * improved the bandwidth limiter (it now implements a leaky bucket/node bucket). + * improved the HTTP seed downloader to report accurate progress. + * added more client peer-id signatures to be recognized. + * added support for HTTP servers that skip the CR before the NL at line breaks. + * fixed bug in the HTTP code that only accepted headers case sensitive. + * fixed bug where one of the session constructors didn't initialize boost.filesystem. + * fixed bug when the initial checking of a torrent fails with an exception. + * fixed bug in DHT code which would send incorrect announce messages. + * fixed bug where the http header parser was case sensitive to the header + names. + * Implemented an optmization which frees the piece_picker once a torrent + turns into a seed. + * Added support for uT peer exchange extension, implemented by Massaroddel. + * Modified the quota management to offer better bandwidth balancing + between peers. + * logging now supports multiple sessions (different sessions now log + to different directories). + * fixed random number generator seed problem, generating the same + peer-id for sessions constructed the same second. + * added an option to accept multiple connections from the same IP. + * improved tracker logging. + * moved the file_pool into session. The number of open files is now + limited per session. + * fixed uninitialized private flag in torrent_info + * fixed long standing issue with file.cpp on windows. Replaced the low level + io functions used on windows. + * made it possible to associate a name with torrents without metadata. + * improved http-downloading performance by requesting entire pieces via + http. + * added plugin interface for extensions. And changed the interface for + enabling extensions. + +release 0.11 + + * added support for incorrectly encoded paths in torrent files + (assumes Latin-1 encoding and converts to UTF-8). + * added support for destructing session objects asynchronously. + * fixed bug with file_progress() with files = 0 bytes + * fixed a race condition bug in udp_tracker_connection that could + cause a crash. + * fixed bug occuring when increasing the sequenced download threshold + with max availability lower than previous threshold. + * fixed an integer overflow bug occuring when built with gcc 4.1.x + * fixed crasing bug when closing while checking a torrent + * fixed bug causing a crash with a torrent with piece length 0 + * added an extension to the DHT network protocol to support the + exchange of nodes with IPv6 addresses. + * modified the ip_filter api slightly to support IPv6 + * modified the api slightly to make sequenced download threshold + a per torrent-setting. + * changed the address type to support IPv6 + * fixed bug in piece picker which would not behave as + expected with regard to sequenced download threshold. + * fixed bug with file_progress() with files > 2 GB. + * added --enable-examples option to configure script. + * fixed problem with the resource distribution algorithm + (controlling e.g upload/download rates). + * fixed incorrect asserts in storage related to torrents with + zero-sized files. + * added support for trackerless torrents (with kademlia DHT). + * support for torrents with the private flag set. + * support for torrents containing bootstrap nodes for the + DHT network. + * fixed problem with the configure script on FreeBSD. + * limits the pipelining used on url-seeds. + * fixed problem where the shutdown always would delay for + session_settings::stop_tracker_timeout seconds. + * session::listen_on() won't reopen the socket in case the port and + interface is the same as the one currently in use. + * added http proxy support for web seeds. + * fixed problem where upload and download stats could become incorrect + in case of high cpu load. + * added more clients to the identifiable list. + * fixed fingerprint parser to cope with latest Mainline versions. + +release 0.10 + + * fixed a bug where the requested number of peers in a tracker request could + be too big. + * fixed a bug where empty files were not created in full allocation mode. + * fixed a bug in storage that would, in rare cases, fail to do a + complete check. + * exposed more settings for tweaking parameters in the piece-picker, + downloader and uploader (http_settings replaced by session_settings). + * tweaked default settings to improve high bandwidth transfers. + * improved the piece picker performance and made it possible to download + popular pieces in sequence to improve disk performance. + * added the possibility to control upload and download limits per peer. + * fixed problem with re-requesting skipped pieces when peer was sending pieces + out of fifo-order. + * added support for http seeding (the GetRight protocol) + * renamed identifiers called 'id' in the public interface to support linking + with Objective.C++ + * changed the extensions protocol to use the new one, which is also + implemented by uTorrent. + * factorized the peer_connection and added web_peer_connection which is + able to download from http-sources. + * converted the network code to use asio (resulted in slight api changes + dealing with network addresses). + * made libtorrent build in vc7 (patches from Allen Zhao) + * fixed bug caused when binding outgoing connections to a non-local interface. + * add_torrent() will now throw if called while the session object is + being closed. + * added the ability to limit the number of simultaneous half-open + TCP connections. Flags in peer_info has been added. + +release 0.9.1 + + * made the session disable file name checks within the boost.filsystem library + * fixed race condition in the sockets + * strings that are invalid utf-8 strings are now decoded with the + local codepage on windows + * added the ability to build libtorrent both as a shared library + * client_test can now monitor a directory for torrent files and automatically + start and stop downloads while running + * fixed problem with file_size() when building on windows with unicode support + * added a new torrent state, allocating + * added a new alert, metadata_failed_alert + * changed the interface to session::add_torrent for some speed optimizations. + * greatly improved the command line control of the example client_test. + * fixed bug where upload rate limit was not being applied. + * files that are being checked will no longer stall files that don't need + checking. + * changed the way libtorrent identifies support for its excentions + to look for 'ext' at the end of the peer-id. + * improved performance by adding a circle buffer for the send buffer. + * fixed bugs in the http tracker connection when using an http proxy. + * fixed problem with storage's file pool when creating torrents and then + starting to seed them. + * hard limit on remote request queue and timeout on requests (a timeout + triggers rerequests). This makes libtorrent work much better with + "broken" clients like BitComet which may ignore requests. + +Initial release 0.9 + + * multitracker support + * serves multiple torrents on a single port and a single thread + * supports http proxies and proxy authentication + * gzipped tracker-responses + * block level piece picker + * queues torrents for file check, instead of checking all of them in parallel + * uses separate threads for checking files and for main downloader + * upload and download rate limits + * piece-wise, unordered, incremental file allocation + * fast resume support + * supports files > 2 gigabytes + * supports the no_peer_id=1 extension + * support for udp-tracker protocol + * number of connections limit + * delays sending have messages + * can resume pieces downloaded in any order + * adjusts the length of the request queue depending on download rate + * supports compact=1 + * selective downloading + * ip filter + diff --git a/Jamfile b/Jamfile new file mode 100644 index 0000000..dc701c1 --- /dev/null +++ b/Jamfile @@ -0,0 +1,822 @@ +# This Jamfile requires boost-build v2 to build. +# The version shipped with boost 1.34.0 + +import modules ; +import path ; +import os ; +import errors ; +import feature : feature ; +import package ; +import virtual-target ; +import cast ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; +CXXFLAGS = [ modules.peek : CXXFLAGS ] ; +LDFLAGS = [ modules.peek : LDFLAGS ] ; + +ECHO "CXXFLAGS =" $(CXXFLAGS) ; +ECHO "LDFLAGS =" $(LDFLAGS) ; +ECHO "OS =" [ os.name ] ; + +if $(BOOST_ROOT) +{ + ECHO "building boost from source directory: " $(BOOST_ROOT) ; + + use-project /boost : $(BOOST_ROOT) ; + alias boost_system : /boost/system//boost_system : : : $(BOOST_ROOT) ; +} +else +{ + local boost-lib-search-path = + /usr/local/opt/boost/lib + ; + + local boost-include-path = + /usr/local/opt/boost/include + ; + + # the names are decorated in MacPorts. + lib boost_system : : darwin boost_system-mt $(boost-lib-search-path) + : : $(boost-include-path) ; + + # we can't add /usr/local/lib to the search path on mac, as that will + # interfere with system libraries (specifically libjpeg) + lib boost_system : : boost_system $(boost-lib-search-path) /usr/local/lib + : : $(boost-include-path) /usr/local/include ; +} + +VERSION = 1.2.9 ; + +rule linking ( properties * ) +{ + local result ; + if on in $(properties) + { + result += /libsimulator//simulator ; + } + + if windows in $(properties) + && ( on in $(properties) + || production in $(properties) + || on in $(properties) ) + { + result += dbghelp ; + } + + # gcrypt libraries, if enabled + if gcrypt in $(properties) + { + result += gcrypt ; + } + else if openssl in $(properties) + { + result += ssl ; + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + else if libcrypto in $(properties) + { + result += crypto ; + if linux in $(properties) + { + result += dl ; + } + } + + if windows in $(properties) + || cygwin in $(properties) + { + # socket functions on windows require winsock libraries + result += ws2_32 + wsock32 + iphlpapi + WIN32_LEAN_AND_MEAN + __USE_W32_SOCKETS + WIN32 + _WIN32 + ; + + # when DHT is enabled, we need ed25519 which in turn + # needs entropy + if ! off in $(properties) + { + result += advapi32 ; + } + } + + if android in $(properties) + { + result += dl ; + } + + if beos in $(properties) + { + result += netkit gcc ; + } + + if haiku in $(properties) + { + result += libnetwork gcc ; + } + + + if solaris in $(properties) + { + result += libsocket libnsl ; + } + + if darwin in $(properties) + || iphone in $(properties) + { + # for ip_notifier + result += CoreFoundation SystemConfiguration ; + } + + if on in $(properties) + { + result += libiconv ; + } + + if ( gcc in $(properties) + || clang in $(properties) ) + && linux in $(properties) + && ( on in $(properties) + || production in $(properties) + || on in $(properties) ) + { + # for backtraces in assertion failures + # which only works on ELF targets with gcc + result += -export-dynamic -rdynamic ; + } + + if static in $(properties) + { + if shared in $(properties) + { + # if libtorrent is being built as a shared library + # but we're linking against boost statically, we still + # need to make boost think it's being built as a shared + # library, so that it properly exports its symbols + result += BOOST_ALL_DYN_LINK ; + result += boost_system/static/BOOST_ALL_DYN_LINK ; + } + else + { + result += boost_system/static ; + } + + if gcc in $(properties) + && ! windows in $(properties) + && shared in $(properties) + { + result += on ; + } + + } + else if shared in $(properties) + { + result += boost_system/shared ; + } + else + { + result += boost_system ; + } + + result += BOOST_ALL_NO_LIB + BOOST_MULTI_INDEX_DISABLE_SERIALIZATION + BOOST_SYSTEM_NO_DEPRECATED + ; + + return $(result) ; +} + +rule warnings ( properties * ) +{ + local result ; + + if off in $(properties) + { + return $(result) ; + } + + if clang in $(properties) + || darwin in $(properties) + { + result += -Weverything ; + result += -Wno-documentation ; + result += -Wno-c++98-compat-pedantic ; + result += -Wno-padded ; + result += -Wno-global-constructors ; +# this warns on any global static object, which are used for error_category +# objects + result += -Wno-exit-time-destructors ; + +# enable these warnings again, once the other ones are dealt with + result += -Wno-weak-vtables ; + + result += -Wno-return-std-move-in-c++11 ; + result += -Wno-unknown-warning-option ; + +# libtorrent uses alloca() carefully + result += -Wno-alloca ; + } + + if gcc in $(properties) + { + result += -Wall ; + result += -Wextra ; + result += -Wpedantic ; +# result += -Wmisleading-indentation ; + result += -Wparentheses ; + result += -Wvla ; + result += -Wc++11-compat ; + result += -Wno-format-zero-length ; + } + + if msvc in $(properties) + { + # on msvc this resolves to /W4 + result += all ; + +# enable these warnings again, once the other ones are dealt with + +# disable warning C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + result += /wd4251 ; +# disable warning C4275: non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + result += /wd4275 ; +# disable warning C4373: virtual function overrides, previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers + result += /wd4373 ; + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + result += /wd4268 ; + # C4503: 'identifier': decorated name length exceeded, name was truncated + result += /wd4503 ; + } + + return $(result) ; +} + +# rule for adding the right source files +# depending on target-os and features +rule building ( properties * ) +{ + local result ; + + if ( off in $(properties) && + ! off in $(properties) ) + { + ECHO "'invariant-check' requires enabled 'asserts' mode. (e.g. specify build params: invariant-check=on asserts=on)" ; + result += no ; + } + + if msvc in $(properties) + { + # allow larger .obj files (with more sections) + result += /bigobj ; + } + + if ( production in $(properties) + || on in $(properties) ) + { + result += src/assert.cpp ; + } + + if on in $(properties) + { + result += src/pe_crypto.cpp ; + } + + if ( darwin in $(properties) + || gcc in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) ) + && shared in $(properties) + # on GCC, enabling debugging in libstdc++ + # breaks the ABI and its ability to appear + # in shared object interfaces, so when it's + # enabled, just export everything (since we're) + # probably not a production build anyway + && ! on in $(properties) + { + # hide non-external symbols + result += -fvisibility=hidden ; + result += -fvisibility-inlines-hidden ; + + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + return $(result) ; +} + +rule tag ( name : type ? : property-set ) +{ + name = [ virtual-target.add-prefix-and-suffix $(name) : $(type) : $(property-set) ] ; + + if $(type) = SHARED_LIB && + ( ! ( [ $(property-set).get ] in windows cygwin ) ) + { + name = $(name).$(VERSION) ; + } + + return $(name) ; +} + +# the search path to pick up the openssl libraries from. This is the +# property of those libraries +rule openssl-lib-path ( properties * ) +{ + local OPENSSL_LIB = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(OPENSSL_LIB) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + OPENSSL_LIB = /usr/local/opt/openssl/lib ; + } + else if windows in $(properties) && $(OPENSSL_LIB) = "" + { + # on windows, assume openssl is installed to c:\OpenSSL-Win32 + if 64 in $(properties) + { OPENSSL_LIB = c:\\OpenSSL-Win64\\lib ; } + else + { OPENSSL_LIB = c:\\OpenSSL-Win32\\lib ; } + } + + local result ; + result += $(OPENSSL_LIB) ; + return $(result) ; +} + +# the include path to pick up openssl headers from. This is the +# usage-requirement for the openssl-related libraries +rule openssl-include-path ( properties * ) +{ + local OPENSSL_INCLUDE = [ feature.get-values : $(properties) ] ; + + if darwin in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # on macOS, default to pick up openssl from the homebrew installation + # brew install openssl + OPENSSL_INCLUDE = /usr/local/opt/openssl/include ; + } + else if windows in $(properties) && $(OPENSSL_INCLUDE) = "" + { + # on windows, assume openssl is installed to c:\OpenSSL-Win32 + if 64 in $(properties) + { OPENSSL_INCLUDE = c:\\OpenSSL-Win64\\include ; } + else + { OPENSSL_INCLUDE = c:\\OpenSSL-Win32\\include ; } + } + + local result ; + result += $(OPENSSL_INCLUDE) ; + return $(result) ; +} + +feature openssl-lib : : free path ; +feature openssl-include : : free path ; + +feature test-coverage : off on : composite propagated link-incompatible ; +feature.compose on : --coverage --coverage ; + +feature predictive-pieces : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_PREDICTIVE_PIECES ; + +feature share-mode : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_SHARE_MODE ; + +feature streaming : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_STREAMING ; + +feature super-seeding : on off : composite propagated ; +feature.compose off : TORRENT_DISABLE_SUPERSEEDING ; + +feature i2p : on off : composite propagated ; +feature.compose on : TORRENT_USE_I2P=1 ; +feature.compose off : TORRENT_USE_I2P=0 ; + +feature iconv : auto on off : composite propagated ; +feature.compose on : TORRENT_USE_ICONV=1 ; +feature.compose off : TORRENT_USE_ICONV=0 ; + +feature asserts : off on production system : composite propagated ; +feature.compose on : TORRENT_USE_ASSERTS=1 ; +feature.compose production : TORRENT_USE_ASSERTS=1 TORRENT_PRODUCTION_ASSERTS=1 ; +feature.compose system : TORRENT_USE_ASSERTS=1 TORRENT_USE_SYSTEM_ASSERTS=1 ; + +feature windows-version : vista win7 win10 xp : composite propagated ; +feature.compose vista : _WIN32_WINNT=0x0600 ; +feature.compose win7 : _WIN32_WINNT=0x0601 ; +feature.compose win10 : _WIN32_WINNT=0x0A00 ; +feature.compose xp : _WIN32_WINNT=0x0501 ; + +feature extensions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_EXTENSIONS ; + +feature asio-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_ASIO_DEBUGGING ; + +feature picker-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_DEBUG_REFCOUNTS ; + +feature simulator : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_BUILD_SIMULATOR ; + +feature invariant-checks : off on full : composite propagated link-incompatible ; +feature.compose on : TORRENT_USE_INVARIANT_CHECKS=1 ; +feature.compose full : TORRENT_USE_INVARIANT_CHECKS=1 TORRENT_EXPENSIVE_INVARIANT_CHECKS ; + +feature utp-log : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_UTP_LOG_ENABLE ; + +feature simulate-slow-read : off on : composite propagated ; +feature.compose on : TORRENT_SIMULATE_SLOW_READ ; + +feature logging : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_LOGGING ; + +feature dht : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_DHT ; + +feature encryption : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_ENCRYPTION ; + +feature mutable-torrents : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_DISABLE_MUTABLE_TORRENTS ; + +feature crypto : built-in openssl libcrypto gcrypt : composite propagated ; +feature.compose openssl : TORRENT_USE_LIBCRYPTO TORRENT_USE_OPENSSL OPENSSL_NO_SSL2 ; +feature.compose libcrypto : TORRENT_USE_LIBCRYPTO ; +feature.compose gcrypt : TORRENT_USE_LIBGCRYPT ; + +feature openssl-version : 1.1 pre1.1 : composite propagated ; + +feature deprecated-functions : on off : composite propagated link-incompatible ; +feature.compose off : TORRENT_NO_DEPRECATE ; + +feature boost-link : default static shared : propagated composite ; + +# msvc enables debug iterators by default in debug builds whereas GCC and +# clang do not, that's why "default" is there. msvc has incorrect noexcept +# constructors on some containers when enabling debug iterators, so it's +# possible to turn them off +feature debug-iterators : default off on : composite propagated link-incompatible ; +feature.compose on : _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC ; +feature.compose off : _ITERATOR_DEBUG_LEVEL=0 ; + +feature fpic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +feature profile-calls : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_PROFILE_CALLS=1 ; + +# controls whether or not to export some internal +# libtorrent functions. Used for unit testing +feature export-extra : off on : composite propagated ; +# export some internal libtorrent functions +# in order to me able to unit test them. +# this is off by default to keep the export +# symbol table reasonably small +feature.compose on : TORRENT_EXPORT_EXTRA ; + + +# this is a trick to get filename paths to targets to become shorter +# making it possible to build on windows, especially mingw seems particular +# for release builds, disable optimizations as they bump GCC over the edge of +# allowed memory usage on travis-ci +variant test_release : release + : production on + full shared off + on multi + on off + ; +variant test_debug : debug + : on + full shared + on multi on + ; +variant test_barebones : debug + : off off off shared + off off + on multi on + ; +variant test_arm : debug + : off off off + off off + on on + ; + +lib advapi32 : : advapi32 ; +lib user32 : : user32 ; +lib shell32 : : shell32 ; +lib gdi32 : : gdi32 ; +lib z : : shared z ; + +# openssl libraries on windows +alias ssl-deps : advapi32 user32 shell32 gdi32 ; + +# pre OpenSSL 1.1 windows +lib crypto : ssl-deps : windows pre1.1 libeay32 + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : windows pre1.1 ssleay32 + crypto @openssl-lib-path : : @openssl-include-path ; + +# OpenSSL 1.1+ windows +lib crypto : ssl-deps : msvc 1.1 libcrypto + @openssl-lib-path : : @openssl-include-path ; +lib ssl : ssl-deps : msvc 1.1 libssl crypto + @openssl-lib-path : : @openssl-include-path ; + +# generic OpenSSL +lib crypto : : crypto z @openssl-lib-path : : + @openssl-include-path ; +lib ssl : : ssl crypto @openssl-lib-path : : + @openssl-include-path ; + +lib dbghelp : : dbghelp ; + +# required for networking on beos +lib netkit : : net /boot/system/lib shared ; +lib gcc : : gcc static ; + +# when using iconv +lib libiconv : : iconv shared /usr/local/lib ; + +# openssl on linux/bsd etc. +lib gcrypt : : gcrypt shared /opt/local/lib ; +lib dl : : shared dl ; + +lib libsocket : : libnsl socket shared /usr/sfw/lib shared ; +lib libnsl : : nsl shared /usr/sfw/lib shared ; +lib libnetwork : : network shared ; + +# socket libraries on windows +lib wsock32 : : wsock32 shared ; +lib ws2_32 : : ws2_32 shared ; +lib iphlpapi : : iphlpapi shared ; + +SOURCES = + alert + alert_manager + announce_entry + assert + bandwidth_limit + bandwidth_manager + bandwidth_queue_entry + bdecode + bitfield + block_cache + bloom_filter + chained_buffer + choker + close_reason + cpuid + crc32c + create_torrent + disk_buffer_holder + disk_buffer_pool + disk_io_job + disk_io_thread + disk_io_thread_pool + disk_job_fence + disk_job_pool + entry + error_code + file_storage + escape_string + string_util + file + path + fingerprint + gzip + hasher + hex + http_connection + http_stream + http_parser + identify_client + ip_filter + ip_notifier + ip_voter + listen_socket_handle + merkle + peer_connection + platform_util + bt_peer_connection + web_connection_base + web_peer_connection + http_seed_connection + peer_connection_handle + i2p_stream + instantiate_connection + lazy_bdecode + natpmp + openssl + packet_buffer + piece_picker + peer_list + proxy_base + puff + random + read_resume_data + write_resume_data + receive_buffer + resolve_links + session + session_handle + session_impl + session_call + settings_pack + sha1 + sha1_hash + socket_io + socket_type + socks5_stream + stat + storage + storage_piece_set + storage_utils + torrent + torrent_handle + torrent_info + torrent_peer + torrent_peer_allocator + torrent_status + time + tracker_manager + http_tracker_connection + udp_tracker_connection + timestamp_history + udp_socket + upnp + utf8 + utp_socket_manager + utp_stream + file_pool + lsd + enum_net + broadcast_socket + magnet_uri + parse_url + xml_parse + version + peer_class + peer_class_set + part_file + stat_cache + request_blocks + session_stats + performance_counters + resolver + session_settings + proxy_settings + file_progress + ffs + add_torrent_params + peer_info + stack_allocator + generate_peer_id + +# -- extensions -- + ut_pex + ut_metadata + smart_ban + ; + +KADEMLIA_SOURCES = + dht_state + dht_storage + dht_tracker + msg + node + node_entry + refresh + rpc_manager + find_data + node_id + routing_table + traversal_algorithm + dos_blocker + get_peers + item + get_item + put_data + ed25519 + sample_infohashes + dht_settings + ; + +ED25519_SOURCES = + add_scalar + fe + ge + key_exchange + keypair + sc + sign + verify + ; + +local usage-requirements = + ./include + ./include/libtorrent + /usr/sfw/include +# freebsd doesn't seem to include this automatically +# and iconv.h is installed there + /usr/local/include + release:NDEBUG + _FILE_OFFSET_BITS=64 +# enable cancel support in asio + BOOST_ASIO_ENABLE_CANCELIO +# make sure asio uses std::chrono + BOOST_ASIO_HAS_STD_CHRONO + @linking +# msvc optimizations + msvc,release:"/OPT:ICF=5" + msvc,release:"/OPT:REF" + + # disable bogus deprecation warnings on msvc8 + msvc:_SCL_SECURE_NO_DEPRECATE + msvc:_CRT_SECURE_NO_DEPRECATE + + $(CXXFLAGS) + $(LDFLAGS) + ; + +project torrent ; + +lib torrent + + : # sources + src/$(SOURCES).cpp + + : # requirements + multi + TORRENT_BUILDING_LIBRARY + shared:TORRENT_BUILDING_SHARED + BOOST_NO_DEPRECATED + shared:BOOST_SYSTEM_SOURCE + + on:src/kademlia/$(KADEMLIA_SOURCES).cpp + on:ed25519/src/$(ED25519_SOURCES).cpp + on:src/hasher512.cpp + on:src/sha512.cpp + + @building + @warnings + $(CXXFLAGS) + + @tag + + $(usage-requirements) + + : # default build + multi + 512 + + : # usage requirements + $(usage-requirements) + shared:TORRENT_LINKING_SHARED + + ; + +headers = [ path.glob-tree include/libtorrent : *.hpp ] ; + +package.install install-torrent-lib + : libtorrent + : + : torrent + : $(headers) + ; + +package.install-data install-cmake-module + : cmake/Modules + : examples/cmake/FindLibtorrentRasterbar.cmake + ; + +alias install : install-torrent-lib install-cmake-module ; + +explicit install ; + +local header_targets ; +for local target in $(headers) +{ + if ! [ path.basename $(target) ] in windows.hpp win_util.hpp win_crypto_provider.hpp torrent_impl.hpp cppint_import_export.hpp + { + # this cast tells boost build that the header files really *are* cpp files + # otherwise the object rule doesn't know which language to interpret them as + obj header-build/$(target).o : [ cast.cast _ cpp : $(target) ] + : /torrent -fsyntax-only ; + explicit header-build/$(target).o ; + header_targets += $(target) ; + } +} + +alias check-headers : header-build/$(header_targets).o ; +explicit check-headers ; diff --git a/Jamroot.jam b/Jamroot.jam new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eddfc20 --- /dev/null +++ b/LICENSE @@ -0,0 +1,83 @@ +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------ + +puff.c +Copyright (C) 2002, 2003 Mark Adler +For conditions of distribution and use, see copyright notice in puff.h +version 1.7, 3 Mar 2003 + +puff.c is a simple inflate written to be an unambiguous way to specify the +deflate format. It is not written for speed but rather simplicity. As a +side benefit, this code might actually be useful when small code is more +important than speed, such as bootstrap applications. For typical deflate +data, zlib's inflate() is about four times as fast as puff(). zlib's +inflate compiles to around 20K on my machine, whereas puff.c compiles to +around 4K on my machine (a PowerPC using GNU cc). If the faster decode() +function here is used, then puff() is only twice as slow as zlib's +inflate(). + +All dynamically allocated memory comes from the stack. The stack required +is less than 2K bytes. This code is compatible with 16-bit int's and +assumes that long's are at least 32 bits. puff.c uses the short data type, +assumed to be 16 bits, for arrays in order to conserve memory. The code +works whether integers are stored big endian or little endian. + +In the comments below are "Format notes" that describe the inflate process +and document some of the less obvious aspects of the format. This source +code is meant to supplement RFC 1951, which formally describes the deflate +format: + + http://www.zlib.org/rfc-deflate.html + +------------------------------------------------------------------------------ + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/LibtorrentRasterbarConfig.cmake.in b/LibtorrentRasterbarConfig.cmake.in new file mode 100644 index 0000000..69aeca1 --- /dev/null +++ b/LibtorrentRasterbarConfig.cmake.in @@ -0,0 +1,9 @@ +# - Config file for the @PROJECT_NAME@ package +# It defines the LibtorrentRasterbar::torrent-rasterbar target to link against + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +@_find_dependency_calls@ + +include("${CMAKE_CURRENT_LIST_DIR}/LibtorrentRasterbarTargets.cmake") diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..d4a5743 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,145 @@ +ACLOCAL_AMFLAGS = -I m4 + +AM_DISTCHECK_CONFIGURE_FLAGS = --enable-tests=yes + +SUBDIRS = include/libtorrent src examples test bindings tools + +DOCS_IMAGES = \ + docs/screenshot.png \ + docs/screenshot_thumb.png \ + docs/cwnd.png \ + docs/cwnd_thumb.png \ + docs/delays.png \ + docs/delays_thumb.png \ + docs/hacking.html \ + docs/merkle_tree.png \ + docs/our_delay_base.png \ + docs/our_delay_base_thumb.png \ + docs/read_disk_buffers.png \ + docs/read_disk_buffers.diagram \ + docs/storage.png \ + docs/todo.html \ + docs/write_disk_buffers.png \ + docs/write_disk_buffers.diagram \ + docs/ip_id_v4.png \ + docs/ip_id_v6.png \ + docs/hash_distribution.png \ + docs/complete_bit_prefixes.png \ + docs/troubleshooting.dot \ + docs/troubleshooting.png \ + docs/troubleshooting_thumb.png \ + docs/hacking.diagram \ + docs/hacking.png \ + docs/disk_cache.diagram \ + docs/disk_cache.png \ + docs/utp_stack.diagram \ + docs/utp_stack.png \ + docs/bitcoin.png \ + docs/style.css \ + docs/rst.css \ + docs/img/bg.png \ + docs/img/blue_bottom.png \ + docs/img/blue_top.png \ + docs/img/dotline.gif \ + docs/img/minus.gif \ + docs/img/orange.png + +DOCS_PAGES = \ + docs/building.html \ + docs/client_test.html \ + docs/contributing.html \ + docs/dht_extensions.html \ + docs/dht_rss.html \ + docs/dht_sec.html \ + docs/dht_store.html \ + docs/examples.html \ + docs/extension_protocol.html \ + docs/features.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning-ref.html \ + docs/fuzzing.html \ + docs/settings.rst \ + docs/stats_counters.rst \ + docs/troubleshooting.html \ + docs/udp_tracker_protocol.html \ + docs/utp.html \ + docs/streaming.html \ + docs/building.rst \ + docs/client_test.rst \ + docs/contributing.rst \ + docs/dht_extensions.rst \ + docs/dht_rss.rst \ + docs/dht_sec.rst \ + docs/dht_store.rst \ + docs/examples.rst \ + docs/extension_protocol.rst \ + docs/features.rst \ + docs/index.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/fuzzing.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/header.rst \ + docs/tutorial.html \ + docs/upgrade_to_1.2-ref.html \ + docs/reference-Alerts.html \ + docs/reference-Bdecoding.html \ + docs/reference-Bencoding.html \ + docs/reference-Core.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Custom_Storage.html \ + docs/reference-DHT.html \ + docs/reference-ed25519.html \ + docs/reference-Error_Codes.html \ + docs/reference-Filter.html \ + docs/reference-Plugins.html \ + docs/reference-Session.html \ + docs/reference-Settings.html \ + docs/reference-Storage.html \ + docs/reference-Utility.html \ + docs/reference.html \ + docs/single-page-ref.html + +ED25519_SOURCE = \ + ed25519/readme.md \ + ed25519/test.c \ + ed25519/src/fe.h \ + ed25519/src/fixedint.h \ + ed25519/src/ge.h \ + ed25519/src/precomp_data.h \ + ed25519/src/sc.h + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + CMakeLists.txt \ + LibtorrentRasterbarConfig.cmake.in \ + cmake/Modules/FindLibGcrypt.cmake \ + cmake/Modules/GeneratePkgConfig.cmake \ + cmake/Modules/LibtorrentMacros.cmake \ + cmake/Modules/ucm_flags.cmake \ + cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in \ + LibtorrentRasterbarConfig.cmake.in \ + setup.py \ + LICENSE \ + README.rst \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) \ + $(ED25519_SOURCE) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libtorrent-rasterbar.pc + +cmakemoduledir = $(datarootdir)/cmake/Modules +cmakemodule_DATA = examples/cmake/FindLibtorrentRasterbar.cmake diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..ea47d40 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,1080 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = . +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ + $(am__configure_deps) $(am__DIST_COMMON) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = libtorrent-rasterbar.pc +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(cmakemoduledir)" \ + "$(DESTDIR)$(pkgconfigdir)" +DATA = $(cmakemodule_DATA) $(pkgconfig_DATA) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + cscope distdir distdir-am dist dist-all distcheck +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +CSCOPE = cscope +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/libtorrent-rasterbar.pc.in \ + $(top_srcdir)/build-aux/compile \ + $(top_srcdir)/build-aux/config.guess \ + $(top_srcdir)/build-aux/config.rpath \ + $(top_srcdir)/build-aux/config.sub \ + $(top_srcdir)/build-aux/install-sh \ + $(top_srcdir)/build-aux/ltmain.sh \ + $(top_srcdir)/build-aux/missing AUTHORS COPYING ChangeLog NEWS \ + build-aux/compile build-aux/config.guess \ + build-aux/config.rpath build-aux/config.sub \ + build-aux/install-sh build-aux/ltmain.sh build-aux/missing +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi +am__post_remove_distdir = $(am__remove_distdir) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +DIST_ARCHIVES = $(distdir).tar.gz +GZIP_ENV = --best +DIST_TARGETS = dist-gzip +distuninstallcheck_listfiles = find . -type f -print +am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ + | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +ACLOCAL_AMFLAGS = -I m4 +AM_DISTCHECK_CONFIGURE_FLAGS = --enable-tests=yes +SUBDIRS = include/libtorrent src examples test bindings tools +DOCS_IMAGES = \ + docs/screenshot.png \ + docs/screenshot_thumb.png \ + docs/cwnd.png \ + docs/cwnd_thumb.png \ + docs/delays.png \ + docs/delays_thumb.png \ + docs/hacking.html \ + docs/merkle_tree.png \ + docs/our_delay_base.png \ + docs/our_delay_base_thumb.png \ + docs/read_disk_buffers.png \ + docs/read_disk_buffers.diagram \ + docs/storage.png \ + docs/todo.html \ + docs/write_disk_buffers.png \ + docs/write_disk_buffers.diagram \ + docs/ip_id_v4.png \ + docs/ip_id_v6.png \ + docs/hash_distribution.png \ + docs/complete_bit_prefixes.png \ + docs/troubleshooting.dot \ + docs/troubleshooting.png \ + docs/troubleshooting_thumb.png \ + docs/hacking.diagram \ + docs/hacking.png \ + docs/disk_cache.diagram \ + docs/disk_cache.png \ + docs/utp_stack.diagram \ + docs/utp_stack.png \ + docs/bitcoin.png \ + docs/style.css \ + docs/rst.css \ + docs/img/bg.png \ + docs/img/blue_bottom.png \ + docs/img/blue_top.png \ + docs/img/dotline.gif \ + docs/img/minus.gif \ + docs/img/orange.png + +DOCS_PAGES = \ + docs/building.html \ + docs/client_test.html \ + docs/contributing.html \ + docs/dht_extensions.html \ + docs/dht_rss.html \ + docs/dht_sec.html \ + docs/dht_store.html \ + docs/examples.html \ + docs/extension_protocol.html \ + docs/features.html \ + docs/index.html \ + docs/manual-ref.html \ + docs/projects.html \ + docs/python_binding.html \ + docs/tuning-ref.html \ + docs/fuzzing.html \ + docs/settings.rst \ + docs/stats_counters.rst \ + docs/troubleshooting.html \ + docs/udp_tracker_protocol.html \ + docs/utp.html \ + docs/streaming.html \ + docs/building.rst \ + docs/client_test.rst \ + docs/contributing.rst \ + docs/dht_extensions.rst \ + docs/dht_rss.rst \ + docs/dht_sec.rst \ + docs/dht_store.rst \ + docs/examples.rst \ + docs/extension_protocol.rst \ + docs/features.rst \ + docs/index.rst \ + docs/manual-ref.rst \ + docs/projects.rst \ + docs/python_binding.rst \ + docs/tuning.rst \ + docs/fuzzing.rst \ + docs/troubleshooting.rst \ + docs/udp_tracker_protocol.rst \ + docs/utp.rst \ + docs/streaming.rst \ + docs/tutorial.rst \ + docs/header.rst \ + docs/tutorial.html \ + docs/upgrade_to_1.2-ref.html \ + docs/reference-Alerts.html \ + docs/reference-Bdecoding.html \ + docs/reference-Bencoding.html \ + docs/reference-Core.html \ + docs/reference-Create_Torrents.html \ + docs/reference-Custom_Storage.html \ + docs/reference-DHT.html \ + docs/reference-ed25519.html \ + docs/reference-Error_Codes.html \ + docs/reference-Filter.html \ + docs/reference-Plugins.html \ + docs/reference-Session.html \ + docs/reference-Settings.html \ + docs/reference-Storage.html \ + docs/reference-Utility.html \ + docs/reference.html \ + docs/single-page-ref.html + +ED25519_SOURCE = \ + ed25519/readme.md \ + ed25519/test.c \ + ed25519/src/fe.h \ + ed25519/src/fixedint.h \ + ed25519/src/ge.h \ + ed25519/src/precomp_data.h \ + ed25519/src/sc.h + +EXTRA_DIST = \ + Jamfile \ + Jamroot.jam \ + CMakeLists.txt \ + LibtorrentRasterbarConfig.cmake.in \ + cmake/Modules/FindLibGcrypt.cmake \ + cmake/Modules/GeneratePkgConfig.cmake \ + cmake/Modules/LibtorrentMacros.cmake \ + cmake/Modules/ucm_flags.cmake \ + cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in \ + cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in \ + LibtorrentRasterbarConfig.cmake.in \ + setup.py \ + LICENSE \ + README.rst \ + $(DOCS_PAGES) \ + $(DOCS_IMAGES) \ + $(ED25519_SOURCE) + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libtorrent-rasterbar.pc +cmakemoduledir = $(datarootdir)/cmake/Modules +cmakemodule_DATA = examples/cmake/FindLibtorrentRasterbar.cmake +all: all-recursive + +.SUFFIXES: +am--refresh: Makefile + @: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): +libtorrent-rasterbar.pc: $(top_builddir)/config.status $(srcdir)/libtorrent-rasterbar.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool config.lt +install-cmakemoduleDATA: $(cmakemodule_DATA) + @$(NORMAL_INSTALL) + @list='$(cmakemodule_DATA)'; test -n "$(cmakemoduledir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(cmakemoduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(cmakemoduledir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(cmakemoduledir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(cmakemoduledir)" || exit $$?; \ + done + +uninstall-cmakemoduleDATA: + @$(NORMAL_UNINSTALL) + @list='$(cmakemodule_DATA)'; test -n "$(cmakemoduledir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(cmakemoduledir)'; $(am__uninstall_files_from_dir) +install-pkgconfigDATA: $(pkgconfig_DATA) + @$(NORMAL_INSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ + done + +uninstall-pkgconfigDATA: + @$(NORMAL_UNINSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscope: cscope.files + test ! -s cscope.files \ + || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) +clean-cscope: + -rm -f cscope.files +cscope.files: clean-cscope cscopelist +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + -rm -f cscope.out cscope.in.out cscope.po.out cscope.files + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz + $(am__post_remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 + $(am__post_remove_distdir) + +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz + $(am__post_remove_distdir) + +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz + $(am__post_remove_distdir) + +dist-tarZ: distdir + @echo WARNING: "Support for distribution archives compressed with" \ + "legacy program 'compress' is deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__post_remove_distdir) + +dist-shar: distdir + @echo WARNING: "Support for shar distribution archives is" \ + "deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz + $(am__post_remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__post_remove_distdir) + +dist dist-all: + $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' + $(am__post_remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir) + chmod u+w $(distdir) + mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build/sub \ + && ../../configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --srcdir=../.. --prefix="$$dc_install_base" \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__post_remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @test -n '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: trying to run $@ with an empty' \ + '$$(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + $(am__cd) '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: check-recursive +all-am: Makefile $(DATA) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(cmakemoduledir)" "$(DESTDIR)$(pkgconfigdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-cmakemoduleDATA install-pkgconfigDATA + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-cmakemoduleDATA uninstall-pkgconfigDATA + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--refresh check check-am clean clean-cscope clean-generic \ + clean-libtool cscope cscopelist-am ctags ctags-am dist \ + dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \ + dist-xz dist-zip distcheck distclean distclean-generic \ + distclean-libtool distclean-tags distcleancheck distdir \ + distuninstallcheck dvi dvi-am html html-am info info-am \ + install install-am install-cmakemoduleDATA install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-pkgconfigDATA install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-cmakemoduleDATA \ + uninstall-pkgconfigDATA + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..58378c1 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ +See ChangeLog diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..5dbab0f --- /dev/null +++ b/README.rst @@ -0,0 +1,64 @@ +libtorrent +---------- + +.. image:: https://travis-ci.org/arvidn/libtorrent.svg?branch=master + :target: https://travis-ci.org/arvidn/libtorrent + +.. image:: https://ci.appveyor.com/api/projects/status/w7teauvub5813mew/branch/master?svg=true + :target: https://ci.appveyor.com/project/arvidn/libtorrent/branch/master + +.. image:: https://img.shields.io/lgtm/alerts/g/arvidn/libtorrent.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/arvidn/libtorrent/alerts/ + +.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libtorrent.svg + :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&q=proj%3Alibtorrent&can=1 + +.. image:: https://codecov.io/github/arvidn/libtorrent/coverage.svg?branch=master + :target: https://codecov.io/github/arvidn/libtorrent?branch=master&view=all#sort=missing&dir=desc + +.. image:: https://img.shields.io/lgtm/grade/cpp/g/arvidn/libtorrent.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/arvidn/libtorrent/context:cpp + +.. image:: https://sonarcloud.io/api/project_badges/measure?project=libtorrent&metric=alert_status + :target: https://sonarcloud.io/dashboard?id=libtorrent + +.. image:: https://sonarcloud.io/api/project_badges/measure?project=libtorrent&metric=security_rating + :target: https://sonarcloud.io/dashboard?id=libtorrent + +.. image:: https://sonarcloud.io/api/project_badges/measure?project=libtorrent&metric=sqale_rating + :target: https://sonarcloud.io/dashboard?id=libtorrent + +.. image:: https://www.openhub.net/p/rasterbar-libtorrent/widgets/project_thin_badge.gif + :target: https://www.openhub.net/p/rasterbar-libtorrent?ref=sample + +.. image:: https://bestpractices.coreinfrastructure.org/projects/3020/badge + :target: https://bestpractices.coreinfrastructure.org/en/projects/3020 + +libtorrent is an open source C++ library implementing the BitTorrent protocol, +along with most popular extensions, making it suitable for real world +deployment. It is configurable to be able to fit both servers and embedded +devices. + +The main goals of libtorrent are to be efficient and easy to use. + +See `libtorrent.org`__ for more detailed build and usage instructions. + +.. __: http://libtorrent.org + +To build with boost-build, make sure boost and boost-build is installed and run: + + b2 + +In the libtorrent root. To build the examples, run ``b2`` in the ``examples`` +directory. + +See `building.html`__ for more details on how to build and which configuration +options are available. For python bindings, see `the python docs`__. + +libtorrent `ABI report`_. + +.. _`ABI report`: https://abi-laboratory.pro/index.php?view=timeline&l=libtorrent + +.. __: docs/building.rst +.. __: docs/python_binding.rst + diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..958cc55 --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,1425 @@ +# generated automatically by aclocal 1.16.1 -*- Autoconf -*- + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. + +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, +[m4_warning([this file was generated for autoconf 2.69. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically 'autoreconf'.])]) + +# Copyright (C) 2002-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.16' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.16.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.16.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to +# '$srcdir', '$srcdir/..', or '$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is '.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ([2.52])dnl + m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], + [$1], [CXX], [depcc="$CXX" am_compiler_list=], + [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], + [$1], [UPC], [depcc="$UPC" am_compiler_list=], + [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + am__universal=false + m4_case([$1], [CC], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac], + [CXX], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac]) + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES. +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE([dependency-tracking], [dnl +AS_HELP_STRING( + [--enable-dependency-tracking], + [do not reject slow dependency extractors]) +AS_HELP_STRING( + [--disable-dependency-tracking], + [speeds up one-time build])]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +AC_SUBST([am__nodep])dnl +_AM_SUBST_NOTMAKE([am__nodep])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[{ + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + # TODO: see whether this extra hack can be removed once we start + # requiring Autoconf 2.70 or later. + AS_CASE([$CONFIG_FILES], + [*\'*], [eval set x "$CONFIG_FILES"], + [*], [set x $CONFIG_FILES]) + shift + # Used to flag and report bootstrapping failures. + am_rc=0 + for am_mf + do + # Strip MF so we end up with the name of the file. + am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile which includes + # dependency-tracking related rules and includes. + # Grep'ing the whole file directly is not great: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ + || continue + am_dirpart=`AS_DIRNAME(["$am_mf"])` + am_filepart=`AS_BASENAME(["$am_mf"])` + AM_RUN_LOG([cd "$am_dirpart" \ + && sed -e '/# am--include-marker/d' "$am_filepart" \ + | $MAKE -f - am--depfiles]) || am_rc=$? + done + if test $am_rc -ne 0; then + AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments + for automatic dependency tracking. Try re-running configure with the + '--disable-dependency-tracking' option to at least be able to build + the package (albeit without support for automatic dependency tracking).]) + fi + AS_UNSET([am_dirpart]) + AS_UNSET([am_filepart]) + AS_UNSET([am_mf]) + AS_UNSET([am_rc]) + rm -f conftest-deps.mk +} +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking is enabled. +# This creates each '.Po' and '.Plo' makefile fragment that we'll need in +# order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. +m4_define([AC_PROG_CC], +m4_defn([AC_PROG_CC]) +[_AM_PROG_CC_C_O +]) + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.65])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[AC_DIAGNOSE([obsolete], + [$0: two- and three-arguments forms are deprecated.]) +m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if( + m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]), + [ok:ok],, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) + AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) +AM_MISSING_PROG([AUTOCONF], [autoconf]) +AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) +AM_MISSING_PROG([AUTOHEADER], [autoheader]) +AM_MISSING_PROG([MAKEINFO], [makeinfo]) +AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# +# +AC_SUBST([mkdir_p], ['$(MKDIR_P)']) +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES([CC])], + [m4_define([AC_PROG_CC], + m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES([CXX])], + [m4_define([AC_PROG_CXX], + m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES([OBJC])], + [m4_define([AC_PROG_OBJC], + m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], + [_AM_DEPENDENCIES([OBJCXX])], + [m4_define([AC_PROG_OBJCXX], + m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl +]) +AC_REQUIRE([AM_SILENT_RULES])dnl +dnl The testsuite driver may need to know about EXEEXT, so add the +dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This +dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. +AC_CONFIG_COMMANDS_PRE(dnl +[m4_provide_if([_AM_COMPILER_EXEEXT], + [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: . + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + AC_MSG_ERROR([Your 'rm' program is bad, sorry.]) + fi +fi +dnl The trailing newline in this macro's definition is deliberate, for +dnl backward compatibility and to allow trailing 'dnl'-style comments +dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. +]) + +dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not +dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further +dnl mangled by Autoconf and run in a shell conditional statement. +m4_define([_AC_COMPILER_EXEEXT], +m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST([install_sh])]) + +# Copyright (C) 2003-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Add --enable-maintainer-mode option to configure. -*- Autoconf -*- +# From Jim Meyering + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MAINTAINER_MODE([DEFAULT-MODE]) +# ---------------------------------- +# Control maintainer-specific portions of Makefiles. +# Default is to disable them, unless 'enable' is passed literally. +# For symmetry, 'disable' may be passed as well. Anyway, the user +# can override the default with the --enable/--disable switch. +AC_DEFUN([AM_MAINTAINER_MODE], +[m4_case(m4_default([$1], [disable]), + [enable], [m4_define([am_maintainer_other], [disable])], + [disable], [m4_define([am_maintainer_other], [enable])], + [m4_define([am_maintainer_other], [enable]) + m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])]) +AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) + dnl maintainer-mode's default is 'disable' unless 'enable' is passed + AC_ARG_ENABLE([maintainer-mode], + [AS_HELP_STRING([--]am_maintainer_other[-maintainer-mode], + am_maintainer_other[ make rules and dependencies not useful + (and sometimes confusing) to the casual installer])], + [USE_MAINTAINER_MODE=$enableval], + [USE_MAINTAINER_MODE=]m4_if(am_maintainer_other, [enable], [no], [yes])) + AC_MSG_RESULT([$USE_MAINTAINER_MODE]) + AM_CONDITIONAL([MAINTAINER_MODE], [test $USE_MAINTAINER_MODE = yes]) + MAINT=$MAINTAINER_MODE_TRUE + AC_SUBST([MAINT])dnl +] +) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MAKE_INCLUDE() +# ----------------- +# Check whether make has an 'include' directive that can support all +# the idioms we need for our automatic dependency tracking code. +AC_DEFUN([AM_MAKE_INCLUDE], +[AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) +cat > confinc.mk << 'END' +am__doit: + @echo this is the am__doit target >confinc.out +.PHONY: am__doit +END +am__include="#" +am__quote= +# BSD make does it like this. +echo '.include "confinc.mk" # ignored' > confmf.BSD +# Other make implementations (GNU, Solaris 10, AIX) do it like this. +echo 'include confinc.mk # ignored' > confmf.GNU +_am_result=no +for s in GNU BSD; do + AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) + AS_CASE([$?:`cat confinc.out 2>/dev/null`], + ['0:this is the am__doit target'], + [AS_CASE([$s], + [BSD], [am__include='.include' am__quote='"'], + [am__include='include' am__quote=''])]) + if test "$am__include" != "#"; then + _am_result="yes ($s style)" + break + fi +done +rm -f confinc.* confmf.* +AC_MSG_RESULT([${_am_result}]) +AC_SUBST([am__include])]) +AC_SUBST([am__quote])]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it is modern enough. +# If it is, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + AC_MSG_WARN(['missing' script is too old or missing]) +fi +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# -------------------- +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), [1])]) + +# _AM_SET_OPTIONS(OPTIONS) +# ------------------------ +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_CC_C_O +# --------------- +# Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC +# to automatically call this. +AC_DEFUN([_AM_PROG_CC_C_O], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([compile])dnl +AC_LANG_PUSH([C])dnl +AC_CACHE_CHECK( + [whether $CC understands -c and -o together], + [am_cv_prog_cc_c_o], + [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i]) +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +AC_LANG_POP([C])]) + +# For backward compatibility. +AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# AM_PATH_PYTHON([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# --------------------------------------------------------------------------- +# Adds support for distributing Python modules and packages. To +# install modules, copy them to $(pythondir), using the python_PYTHON +# automake variable. To install a package with the same name as the +# automake package, install to $(pkgpythondir), or use the +# pkgpython_PYTHON automake variable. +# +# The variables $(pyexecdir) and $(pkgpyexecdir) are provided as +# locations to install python extension modules (shared libraries). +# Another macro is required to find the appropriate flags to compile +# extension modules. +# +# If your package is configured with a different prefix to python, +# users will have to add the install directory to the PYTHONPATH +# environment variable, or create a .pth file (see the python +# documentation for details). +# +# If the MINIMUM-VERSION argument is passed, AM_PATH_PYTHON will +# cause an error if the version of python installed on the system +# doesn't meet the requirement. MINIMUM-VERSION should consist of +# numbers and dots only. +AC_DEFUN([AM_PATH_PYTHON], + [ + dnl Find a Python interpreter. Python versions prior to 2.0 are not + dnl supported. (2.0 was released on October 16, 2000). + m4_define_default([_AM_PYTHON_INTERPRETER_LIST], +[python python2 python3 dnl + python3.9 python3.8 python3.7 python3.6 python3.5 python3.4 python3.3 dnl + python3.2 python3.1 python3.0 dnl + python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 dnl + python2.0]) + + AC_ARG_VAR([PYTHON], [the Python interpreter]) + + m4_if([$1],[],[ + dnl No version check is needed. + # Find any Python interpreter. + if test -z "$PYTHON"; then + AC_PATH_PROGS([PYTHON], _AM_PYTHON_INTERPRETER_LIST, :) + fi + am_display_PYTHON=python + ], [ + dnl A version check is needed. + if test -n "$PYTHON"; then + # If the user set $PYTHON, use it and don't search something else. + AC_MSG_CHECKING([whether $PYTHON version is >= $1]) + AM_PYTHON_CHECK_VERSION([$PYTHON], [$1], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([Python interpreter is too old])]) + am_display_PYTHON=$PYTHON + else + # Otherwise, try each interpreter until we find one that satisfies + # VERSION. + AC_CACHE_CHECK([for a Python interpreter with version >= $1], + [am_cv_pathless_PYTHON],[ + for am_cv_pathless_PYTHON in _AM_PYTHON_INTERPRETER_LIST none; do + test "$am_cv_pathless_PYTHON" = none && break + AM_PYTHON_CHECK_VERSION([$am_cv_pathless_PYTHON], [$1], [break]) + done]) + # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. + if test "$am_cv_pathless_PYTHON" = none; then + PYTHON=: + else + AC_PATH_PROG([PYTHON], [$am_cv_pathless_PYTHON]) + fi + am_display_PYTHON=$am_cv_pathless_PYTHON + fi + ]) + + if test "$PYTHON" = :; then + dnl Run any user-specified action, or abort. + m4_default([$3], [AC_MSG_ERROR([no suitable Python interpreter found])]) + else + + dnl Query Python for its version number. Getting [:3] seems to be + dnl the best way to do this; it's what "site.py" does in the standard + dnl library. + + AC_CACHE_CHECK([for $am_display_PYTHON version], [am_cv_python_version], + [am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[[:3]])"`]) + AC_SUBST([PYTHON_VERSION], [$am_cv_python_version]) + + dnl Use the values of $prefix and $exec_prefix for the corresponding + dnl values of PYTHON_PREFIX and PYTHON_EXEC_PREFIX. These are made + dnl distinct variables so they can be overridden if need be. However, + dnl general consensus is that you shouldn't need this ability. + + AC_SUBST([PYTHON_PREFIX], ['${prefix}']) + AC_SUBST([PYTHON_EXEC_PREFIX], ['${exec_prefix}']) + + dnl At times (like when building shared libraries) you may want + dnl to know which OS platform Python thinks this is. + + AC_CACHE_CHECK([for $am_display_PYTHON platform], [am_cv_python_platform], + [am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"`]) + AC_SUBST([PYTHON_PLATFORM], [$am_cv_python_platform]) + + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[[:3]] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" + + dnl Set up 4 directories: + + dnl pythondir -- where to install python scripts. This is the + dnl site-packages directory, not the python standard library + dnl directory like in previous automake betas. This behavior + dnl is more consistent with lispdir.m4 for example. + dnl Query distutils for this directory. + AC_CACHE_CHECK([for $am_display_PYTHON script directory], + [am_cv_python_pythondir], + [if test "x$prefix" = xNONE + then + am_py_prefix=$ac_default_prefix + else + am_py_prefix=$prefix + fi + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pythondir in + $am_py_prefix*) + am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` + am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"` + ;; + *) + case $am_py_prefix in + /usr|/System*) ;; + *) + am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + ]) + AC_SUBST([pythondir], [$am_cv_python_pythondir]) + + dnl pkgpythondir -- $PACKAGE directory under pythondir. Was + dnl PYTHON_SITE_PACKAGE in previous betas, but this naming is + dnl more consistent with the rest of automake. + + AC_SUBST([pkgpythondir], [\${pythondir}/$PACKAGE]) + + dnl pyexecdir -- directory for installing python extension modules + dnl (shared libraries) + dnl Query distutils for this directory. + AC_CACHE_CHECK([for $am_display_PYTHON extension module directory], + [am_cv_python_pyexecdir], + [if test "x$exec_prefix" = xNONE + then + am_py_exec_prefix=$am_py_prefix + else + am_py_exec_prefix=$exec_prefix + fi + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pyexecdir in + $am_py_exec_prefix*) + am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` + am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"` + ;; + *) + case $am_py_exec_prefix in + /usr|/System*) ;; + *) + am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + ]) + AC_SUBST([pyexecdir], [$am_cv_python_pyexecdir]) + + dnl pkgpyexecdir -- $(pyexecdir)/$(PACKAGE) + + AC_SUBST([pkgpyexecdir], [\${pyexecdir}/$PACKAGE]) + + dnl Run any user-specified action. + $2 + fi + +]) + + +# AM_PYTHON_CHECK_VERSION(PROG, VERSION, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# --------------------------------------------------------------------------- +# Run ACTION-IF-TRUE if the Python interpreter PROG has version >= VERSION. +# Run ACTION-IF-FALSE otherwise. +# This test uses sys.hexversion instead of the string equivalent (first +# word of sys.version), in order to cope with versions such as 2.2c1. +# This supports Python 2.0 or higher. (2.0 was released on October 16, 2000). +AC_DEFUN([AM_PYTHON_CHECK_VERSION], + [prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python 3.0 and a list in 2.x +minver = list(map(int, '$2'.split('.'))) + [[0, 0, 0]] +minverhex = 0 +# xrange is not present in Python 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[[i]] +sys.exit(sys.hexversion < minverhex)" + AS_IF([AM_RUN_LOG([$1 -c "$prog"])], [$3], [$4])]) + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_RUN_LOG(COMMAND) +# ------------------- +# Run COMMAND, save the exit status in ac_status, and log it. +# (This has been adapted from Autoconf's _AC_RUN_LOG macro.) +AC_DEFUN([AM_RUN_LOG], +[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD + ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + (exit $ac_status); }]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[[\\\"\#\$\&\'\`$am_lf]]*) + AC_MSG_ERROR([unsafe absolute working directory name]);; +esac +case $srcdir in + *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) + AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken + alias in your environment]) + fi + if test "$[2]" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT([yes]) +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi +AC_CONFIG_COMMANDS_PRE( + [AC_MSG_CHECKING([that generated files are newer than configure]) + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + AC_MSG_RESULT([done])]) +rm -f conftest.file +]) + +# Copyright (C) 2009-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SILENT_RULES([DEFAULT]) +# -------------------------- +# Enable less verbose build rules; with the default set to DEFAULT +# ("yes" being less verbose, "no" or empty being verbose). +AC_DEFUN([AM_SILENT_RULES], +[AC_ARG_ENABLE([silent-rules], [dnl +AS_HELP_STRING( + [--enable-silent-rules], + [less verbose build output (undo: "make V=1")]) +AS_HELP_STRING( + [--disable-silent-rules], + [verbose build output (undo: "make V=0")])dnl +]) +case $enable_silent_rules in @%:@ ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; +esac +dnl +dnl A few 'make' implementations (e.g., NonStop OS and NextStep) +dnl do not support nested variable expansions. +dnl See automake bug#9928 and bug#10237. +am_make=${MAKE-make} +AC_CACHE_CHECK([whether $am_make supports nested variables], + [am_cv_make_support_nested_variables], + [if AS_ECHO([['TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi]) +if test $am_cv_make_support_nested_variables = yes; then + dnl Using '$V' instead of '$(V)' breaks IRIX make. + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AC_SUBST([AM_V])dnl +AM_SUBST_NOTMAKE([AM_V])dnl +AC_SUBST([AM_DEFAULT_V])dnl +AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl +AC_SUBST([AM_DEFAULT_VERBOSITY])dnl +AM_BACKSLASH='\' +AC_SUBST([AM_BACKSLASH])dnl +_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl +]) + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor 'install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in "make install-strip", and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# -------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of 'v7', 'ustar', or 'pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +# +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' + +m4_if([$1], [v7], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], + + [m4_case([$1], + [ustar], + [# The POSIX 1988 'ustar' format is defined with fixed-size fields. + # There is notably a 21 bits limit for the UID and the GID. In fact, + # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 + # and bug#13588). + am_max_uid=2097151 # 2^21 - 1 + am_max_gid=$am_max_uid + # The $UID and $GID variables are not portable, so we need to resort + # to the POSIX-mandated id(1) utility. Errors in the 'id' calls + # below are definitely unexpected, so allow the users to see them + # (that is, avoid stderr redirection). + am_uid=`id -u || echo unknown` + am_gid=`id -g || echo unknown` + AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) + if test $am_uid -le $am_max_uid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi + AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) + if test $am_gid -le $am_max_gid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi], + + [pax], + [], + + [m4_fatal([Unknown tar format])]) + + AC_MSG_CHECKING([how to create a $1 tar archive]) + + # Go ahead even if we have the value already cached. We do so because we + # need to set the values for the 'am__tar' and 'am__untar' variables. + _am_tools=${am_cv_prog_tar_$1-$_am_tools} + + for _am_tool in $_am_tools; do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works. + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi + done + rm -rf conftest.dir + + AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) + AC_MSG_RESULT([$am_cv_prog_tar_$1])]) + +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + +m4_include([m4/ax_boost_base.m4]) +m4_include([m4/ax_boost_python.m4]) +m4_include([m4/ax_boost_system.m4]) +m4_include([m4/ax_check_openssl.m4]) +m4_include([m4/ax_cxx_compile_stdcxx.m4]) +m4_include([m4/ax_cxx_compile_stdcxx_11.m4]) +m4_include([m4/ax_pthread.m4]) +m4_include([m4/ax_python_devel.m4]) +m4_include([m4/gettext-lib.m4]) +m4_include([m4/iconv.m4]) +m4_include([m4/libtool.m4]) +m4_include([m4/ltoptions.m4]) +m4_include([m4/ltsugar.m4]) +m4_include([m4/ltversion.m4]) +m4_include([m4/lt~obsolete.m4]) +m4_include([m4/pkgconfig.m4]) diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 0000000..5dd618f --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,3 @@ +if (python-bindings) + add_subdirectory(python) +endif() diff --git a/bindings/Makefile.am b/bindings/Makefile.am new file mode 100644 index 0000000..c03bfbd --- /dev/null +++ b/bindings/Makefile.am @@ -0,0 +1,4 @@ + +SUBDIRS = python + +EXTRA_DIST = README.txt CMakeLists.txt diff --git a/bindings/Makefile.in b/bindings/Makefile.in new file mode 100644 index 0000000..53f9214 --- /dev/null +++ b/bindings/Makefile.in @@ -0,0 +1,676 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = bindings +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = python +EXTRA_DIST = README.txt CMakeLists.txt +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bindings/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bindings/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bindings/README.txt b/bindings/README.txt new file mode 100644 index 0000000..977ca8c --- /dev/null +++ b/bindings/README.txt @@ -0,0 +1,3 @@ +Documentation covering building and using the python binding for libtorrent +is located in the main doc directory. See docs/python_binding.html + diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt new file mode 100644 index 0000000..53e0943 --- /dev/null +++ b/bindings/python/CMakeLists.txt @@ -0,0 +1,147 @@ +# To build python bindings we need a python executable and boost python module. Unfortunately, +# their names might not be interlinked and we can not implement a general solution. +# The code below assumes default boost installation, when the module for python 2 is named +# 'python' and the module for python 3 is named 'python3'. +# To customize that one can provide a name for the Boost::python module via +# 'boost-python-module-name' variable when invoking cmake. +# E.g. on Gentoo with python 3.6 and Boost::python library name 'libboost_python-3.6.so' +# the parameter would be -Dboost-python-module-name="python-3.6". + +# The extension module and the cpython executable have to use the same C runtime library. On Windows +# Python is compiled with MSVC and we will test MSVC version to make sure that it is the same for +# the given Python version and our extension module. See https://wiki.python.org/moin/WindowsCompilers +# for details. We provide a flag to skip this test for whatever reason (pass -Dskip-python-runtime-test=True) + +# Sets _ret to a list of python versions (major.minor) that use the same MSVC runtime as this build does +# assumes MSVC was detected already +# See https://en.wikipedia.org/wiki/Microsoft_Visual_C++#Internal_version_numbering +function(_get_compatible_python_versions _ret) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) + list(APPEND _tmp 2.6 2.7 3.0 3.1 3.2) + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 17) + list(APPEND _tmp 3.3 3.4) + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 20) + list(APPEND _tmp 3.5 3.6 3.7 3.8) + endif() + set(${_ret} ${_tmp} PARENT_SCOPE) +endfunction() + + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + _get_compatible_python_versions(Python_ADDITIONAL_VERSIONS) +endif() + +find_package(PythonInterp REQUIRED) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + message(STATUS "Testing found python version. Requested: ${Python_ADDITIONAL_VERSIONS}, found: ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}") + if (NOT "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" IN_LIST Python_ADDITIONAL_VERSIONS) + message(FATAL_ERROR "Incompatible Python and C runtime: MSVC ${CMAKE_CXX_COMPILER_VERSION} and Python ${PYTHON_VERSION_STRING}") + endif() +endif() + +set(Python_ADDITIONAL_VERSIONS "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}") +find_package(PythonLibs REQUIRED) + +if (NOT boost-python-module-name) + # use active python +# if (PYTHON_VERSION_STRING VERSION_GREATER_EQUAL "3") +# set(_boost-python-module-name "python${PYTHON_VERSION_MAJOR}") +# else() + set(_boost-python-module-name "python") # to overwrite possible value from a previous run +# endif() +endif() + +set(boost-python-module-name ${_boost-python-module-name} CACHE STRING "Boost:python module name, e.g. 'python-3.6'") + +find_package(Boost REQUIRED COMPONENTS ${boost-python-module-name}) + +python_add_module(python-libtorrent + src/module.cpp + src/sha1_hash.cpp + src/converters.cpp + src/create_torrent.cpp + src/fingerprint.cpp + src/utility.cpp + src/session.cpp + src/entry.cpp + src/torrent_info.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_status.cpp + src/session_settings.cpp + src/version.cpp + src/alert.cpp + src/datetime.cpp + src/peer_info.cpp + src/ip_filter.cpp + src/magnet_uri.cpp + src/error_code.cpp +) + +set_target_properties(python-libtorrent + PROPERTIES + OUTPUT_NAME libtorrent +) + +target_include_directories(python-libtorrent + PRIVATE + ${PYTHON_INCLUDE_DIRS} +) + +string(TOUPPER "${boost-python-module-name}" boost_python_module_name_uppercase) +target_link_libraries(python-libtorrent + PRIVATE + torrent-rasterbar + ${Boost_${boost_python_module_name_uppercase}_LIBRARY} + # Boost::python adds that but without a path to the library. Therefore we have to either + # provide the path (but, unfortunately, FindPythonLibs.cmake does not return the library dir), + # or give the full file name here (this FindPythonLibs.cmake provides to us). + ${PYTHON_LIBRARIES} +) + +# Bindings module uses deprecated libtorrent features, thus we disable these warnings +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + check_cxx_compiler_flag("-Wno-deprecated-declarations" _WNO_DEPRECATED_DECLARATIONS) + if (_WNO_DEPRECATED_DECLARATIONS) + target_compile_options(python-libtorrent PRIVATE -Wno-deprecated-declarations) + endif() +endif() + +execute_process(COMMAND + ${PYTHON_EXECUTABLE} -c "import distutils.sysconfig; +print(';'.join(map(str, [ + distutils.sysconfig.get_python_lib(plat_specific=True, prefix=''), + distutils.sysconfig.get_config_var('EXT_SUFFIX') +])))" + OUTPUT_VARIABLE _python_sysconfig_vars OUTPUT_STRIP_TRAILING_WHITESPACE) + +list(GET _python_sysconfig_vars 0 PYTHON_SITE_PACKAGES) +list(GET _python_sysconfig_vars 1 PYTHON_EXT_SUFFIX) + +message(STATUS "Python site packages: ${PYTHON_SITE_PACKAGES}") +# python 2 does not provide the 'EXT_SUFFIX' sysconfig variable, so we use cmake default then +if (NOT "${PYTHON_EXT_SUFFIX}" STREQUAL "None") + message(STATUS "Python extension suffix: ${PYTHON_EXT_SUFFIX}") + # we mimic the name, created by setuptools + # example: libtorrent.cpython-36m-x86_64-linux-gnu.so + set_target_properties(python-libtorrent PROPERTIES SUFFIX ${PYTHON_EXT_SUFFIX}) +endif() + +set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.cmake.in") +set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") +set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/timestamp") +set(DEPS python-libtorrent "${SETUP_PY}") + +configure_file(${SETUP_PY_IN} ${SETUP_PY} @ONLY) + +add_custom_command(OUTPUT ${OUTPUT} + COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} build -b "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} egg_info -b "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} + DEPENDS ${DEPS}) + +add_custom_target(python_bindings ALL DEPENDS ${OUTPUT}) + + +install(TARGETS python-libtorrent DESTINATION "${PYTHON_SITE_PACKAGES}") +install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libtorrent.egg-info" DESTINATION "${PYTHON_SITE_PACKAGES}") diff --git a/bindings/python/Jamfile b/bindings/python/Jamfile new file mode 100644 index 0000000..5dd77ac --- /dev/null +++ b/bindings/python/Jamfile @@ -0,0 +1,247 @@ +import python ; +import feature ; +import feature : feature ; +import project ; +import targets ; +import "class" : new ; +import modules ; + +use-project /torrent : ../.. ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; +# this is used to make bjam use the same version of python which is executing setup.py +LIBTORRENT_PYTHON_INTERPRETER = [ modules.peek : LIBTORRENT_PYTHON_INTERPRETER ] ; + +feature lt-visibility : default hidden : composite propagated ; +feature.compose hidden : -fvisibility=hidden -fvisibility-inlines-hidden ; + +feature libtorrent-link : shared static : composite propagated ; +feature libtorrent-python-pic : off on : composite propagated link-incompatible ; +feature.compose on : -fPIC ; + +# this is just to force boost build to pick the desired python target when using LIBTORRENT_PYTHON_INTERPRETER +feature libtorrent-python : on ; + +if $(LIBTORRENT_PYTHON_INTERPRETER) +{ + echo "using python interpreter at: " $(LIBTORRENT_PYTHON_INTERPRETER) ; + using python : : "$(LIBTORRENT_PYTHON_INTERPRETER)" : : : on ; +} + +# copied from boost 1.63's boost python jamfile +rule find-py3-version +{ + local BOOST_VERSION_TAG = [ modules.peek boostcpp : BOOST_VERSION_TAG ] ; + if $(BOOST_VERSION_TAG) >= 1_67 + { + # starting with boost 1.67.0 boost python no longer define a separate + # target for python3 (boost_python3) so then we just use the regular + # boost_python target + return ; + } + local versions = [ feature.values python ] ; + local py3ver ; + for local v in $(versions) + { + if $(v) >= 3.0 + { + py3ver = $(v) ; + } + } + return $(py3ver) ; +} + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; + alias boost_python : /boost/python//boost_python : : : $(BOOST_ROOT) ; + if [ find-py3-version ] + { + alias boost_python3 : /boost/python//boost_python3 : : : $(BOOST_ROOT) ; + } + else + { + alias boost_python3 : boost_python ; + } +} +else +{ + local boost-lib-search-path = + /opt/local/lib + /usr/lib + /usr/local/lib + /sw/lib + /usr/g++/lib + ; + + local boost-include-path = + /opt/local/include + /usr/local/include + /usr/sfw/include + ; + + # the names are decorated in brew + lib boost_python : : darwin boost_python27-mt + : : $(boost-include-path) ; + lib boost_python3 : : darwin boost_python38-mt + : : $(boost-include-path) ; + + lib boost_python : : boost_python + : : $(boost-include-path) ; + lib boost_python3 : : boost_python38 + : : $(boost-include-path) ; +} + + +rule libtorrent_linking ( properties * ) +{ + local result ; + + if ! windows in $(properties) + && gcc in $(properties) + && static in $(properties) + { + result += on ; + } + + if gcc in $(properties) + || darwin in $(properties) + || clang in $(properties) + || clang-darwin in $(properties) + { + result += hidden ; + + if ( gcc in $(properties) ) + { + result += -Wl,-Bsymbolic ; + } + } + + if static in $(properties) + { + ECHO "WARNING: you probably want to specify libtorrent-link=static rather than link=static" ; + } + + if static in $(properties) && linux in $(properties) + { + ECHO "WARNING: you cannot link statically against boost-python on linux, because it links against pthread statically in that case, which is not allowed" ; + } + + local boost_python_lib ; + + for local prop in $(properties) + { + switch $(prop) + { + case 2.* : boost_python_lib = boost_python ; + case 3.* : boost_python_lib = boost_python3 ; + } + } + + if ! $(boost_python_lib) + { + ECHO "WARNING: unknown python version" ; + boost_python_lib = boost_python ; + } + + # linux must link dynamically against boost python because it pulls + # in libpthread, which must be linked dynamically since we're building a .so + # (the static build of libpthread is not position independent) + if shared in $(properties) || linux in $(properties) + { + result += $(boost_python_lib)/shared/off ; + } + else + { + result += $(boost_python_lib)/static/off ; + } + + if shared in $(properties) + { + result += /torrent//torrent/shared ; + } + else + { + result += /torrent//torrent/static ; + } + + return $(result) ; +} + +# this is a copy of the rule from boost-build's python-extension, but without +# specifying no as a mandatory property. That property +# would otherwise cause build failures because it suppresses linking against the +# runtime library and kernel32 on windows + +rule my-python-extension ( name : sources * : requirements * : default-build * : + usage-requirements * ) +{ + requirements += /python//python_for_extensions ; + + local project = [ project.current ] ; + + targets.main-target-alternative + [ new typed-target $(name) : $(project) : PYTHON_EXTENSION + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + ] ; +} + +my-python-extension libtorrent + : # sources + src/module.cpp + src/sha1_hash.cpp + src/converters.cpp + src/create_torrent.cpp + src/fingerprint.cpp + src/utility.cpp + src/session.cpp + src/entry.cpp + src/torrent_info.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_status.cpp + src/session_settings.cpp + src/version.cpp + src/alert.cpp + src/datetime.cpp + src/peer_info.cpp + src/ip_filter.cpp + src/magnet_uri.cpp + src/error_code.cpp + : # requirements + src + gcc:-Wno-deprecated-declarations + darwin:-Wno-deprecated-declarations + darwin:-Wno-unused-command-line-argument + @libtorrent_linking + openssl:/torrent//ssl + openssl:/torrent//crypto + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + : # default-build + all + : # usage-requirements + false + ; + +install stage_module + : libtorrent + : . + PYTHON_EXTENSION + ; + +install stage_dependencies + : /torrent//torrent + boost_python + boost_python3 + : dependencies + on + SHARED_LIB + ; + +explicit stage_module ; +explicit stage_dependencies ; + diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am new file mode 100644 index 0000000..19931b3 --- /dev/null +++ b/bindings/python/Makefile.am @@ -0,0 +1,50 @@ + +EXTRA_DIST = \ + Jamfile \ + CMakeLists.txt \ + setup.py.cmake.in \ + setup.py \ + setup.py.cmake.in \ + client.py \ + simple_client.py \ + make_torrent.py \ + src/alert.cpp \ + src/bytes.hpp \ + src/sha1_hash.cpp \ + src/converters.cpp \ + src/create_torrent.cpp \ + src/datetime.cpp \ + src/entry.cpp \ + src/error_code.cpp \ + src/fingerprint.cpp \ + src/gil.hpp \ + src/ip_filter.cpp \ + src/magnet_uri.cpp \ + src/module.cpp \ + src/optional.hpp \ + src/peer_info.cpp \ + src/boost_python.hpp \ + src/session.cpp \ + src/session_settings.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +if ENABLE_PYTHON_BINDING + +all-local: + $(PYTHON) $(srcdir)/setup.py build + +install-exec-local: + $(PYTHON) $(srcdir)/setup.py install @PYTHON_INSTALL_PARAMS@ + +uninstall-local: + rm -rf $(DESTDIR)$(libdir)/python*/*-packages/*libtorrent* + +clean-local: + $(PYTHON) $(srcdir)/setup.py clean --all + +endif diff --git a/bindings/python/Makefile.in b/bindings/python/Makefile.in new file mode 100644 index 0000000..c8cb0ab --- /dev/null +++ b/bindings/python/Makefile.in @@ -0,0 +1,552 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = bindings/python +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = link_flags compile_flags compile_cmd +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/compile_cmd.in \ + $(srcdir)/compile_flags.in $(srcdir)/link_flags.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = \ + Jamfile \ + CMakeLists.txt \ + setup.py.cmake.in \ + setup.py \ + setup.py.cmake.in \ + client.py \ + simple_client.py \ + make_torrent.py \ + src/alert.cpp \ + src/bytes.hpp \ + src/sha1_hash.cpp \ + src/converters.cpp \ + src/create_torrent.cpp \ + src/datetime.cpp \ + src/entry.cpp \ + src/error_code.cpp \ + src/fingerprint.cpp \ + src/gil.hpp \ + src/ip_filter.cpp \ + src/magnet_uri.cpp \ + src/module.cpp \ + src/optional.hpp \ + src/peer_info.cpp \ + src/boost_python.hpp \ + src/session.cpp \ + src/session_settings.cpp \ + src/string.cpp \ + src/torrent_handle.cpp \ + src/torrent_info.cpp \ + src/torrent_status.cpp \ + src/utility.cpp \ + src/version.cpp + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bindings/python/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign bindings/python/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +link_flags: $(top_builddir)/config.status $(srcdir)/link_flags.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +compile_flags: $(top_builddir)/config.status $(srcdir)/compile_flags.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +compile_cmd: $(top_builddir)/config.status $(srcdir)/compile_cmd.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +@ENABLE_PYTHON_BINDING_FALSE@all-local: +all-am: Makefile all-local +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +@ENABLE_PYTHON_BINDING_FALSE@clean-local: +@ENABLE_PYTHON_BINDING_FALSE@install-exec-local: +@ENABLE_PYTHON_BINDING_FALSE@uninstall-local: +clean: clean-am + +clean-am: clean-generic clean-libtool clean-local mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-exec-local + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-local + +.MAKE: install-am install-strip + +.PHONY: all all-am all-local check check-am clean clean-generic \ + clean-libtool clean-local cscopelist-am ctags-am distclean \ + distclean-generic distclean-libtool distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-exec-local install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am uninstall-local + +.PRECIOUS: Makefile + + +@ENABLE_PYTHON_BINDING_TRUE@all-local: +@ENABLE_PYTHON_BINDING_TRUE@ $(PYTHON) $(srcdir)/setup.py build + +@ENABLE_PYTHON_BINDING_TRUE@install-exec-local: +@ENABLE_PYTHON_BINDING_TRUE@ $(PYTHON) $(srcdir)/setup.py install @PYTHON_INSTALL_PARAMS@ + +@ENABLE_PYTHON_BINDING_TRUE@uninstall-local: +@ENABLE_PYTHON_BINDING_TRUE@ rm -rf $(DESTDIR)$(libdir)/python*/*-packages/*libtorrent* + +@ENABLE_PYTHON_BINDING_TRUE@clean-local: +@ENABLE_PYTHON_BINDING_TRUE@ $(PYTHON) $(srcdir)/setup.py clean --all + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bindings/python/client.py b/bindings/python/client.py new file mode 100755 index 0000000..a0d2790 --- /dev/null +++ b/bindings/python/client.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 + +# Copyright Daniel Wallin 2006. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +import sys +import atexit +import libtorrent as lt +import time +import os.path + + +class WindowsConsole: + def __init__(self): + self.console = Console.getconsole() + + def clear(self): + self.console.page() + + def write(self, str): + self.console.write(str) + + def sleep_and_input(self, seconds): + time.sleep(seconds) + if msvcrt.kbhit(): + return msvcrt.getch() + return None + + +class UnixConsole: + def __init__(self): + self.fd = sys.stdin + self.old = termios.tcgetattr(self.fd.fileno()) + new = termios.tcgetattr(self.fd.fileno()) + new[3] = new[3] & ~termios.ICANON + new[6][termios.VTIME] = 0 + new[6][termios.VMIN] = 1 + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, new) + + atexit.register(self._onexit) + + def _onexit(self): + termios.tcsetattr(self.fd.fileno(), termios.TCSADRAIN, self.old) + + def clear(self): + sys.stdout.write('\033[2J\033[0;0H') + sys.stdout.flush() + + def write(self, str): + sys.stdout.write(str) + sys.stdout.flush() + + def sleep_and_input(self, seconds): + read, __, __ = select.select( + [self.fd.fileno()], [], [], seconds) + if len(read) > 0: + return self.fd.read(1) + return None + + +if os.name == 'nt': + import Console + import msvcrt +else: + import termios + import select + + +def write_line(console, line): + console.write(line) + + +def add_suffix(val): + prefix = ['B', 'kB', 'MB', 'GB', 'TB'] + for i in range(len(prefix)): + if abs(val) < 1000: + if i == 0: + return '%5.3g%s' % (val, prefix[i]) + else: + return '%4.3g%s' % (val, prefix[i]) + val /= 1000 + + return '%6.3gPB' % val + + +def progress_bar(progress, width): + assert(progress <= 1) + progress_chars = int(progress * width + 0.5) + return progress_chars * '#' + (width - progress_chars) * '-' + + +def print_peer_info(console, peers): + + out = (' down (total ) up (total )' + ' q r flags block progress client\n') + + for p in peers: + + out += '%s/s ' % add_suffix(p.down_speed) + out += '(%s) ' % add_suffix(p.total_download) + out += '%s/s ' % add_suffix(p.up_speed) + out += '(%s) ' % add_suffix(p.total_upload) + out += '%2d ' % p.download_queue_length + out += '%2d ' % p.upload_queue_length + + out += 'I' if p.flags & lt.peer_info.interesting else '.' + out += 'C' if p.flags & lt.peer_info.choked else '.' + out += 'i' if p.flags & lt.peer_info.remote_interested else '.' + out += 'c' if p.flags & lt.peer_info.remote_choked else '.' + out += 'e' if p.flags & lt.peer_info.supports_extensions else '.' + out += 'l' if p.flags & lt.peer_info.local_connection else 'r' + out += ' ' + + if p.downloading_piece_index >= 0: + assert(p.downloading_progress <= p.downloading_total) + out += progress_bar(float(p.downloading_progress) / + p.downloading_total, 15) + else: + out += progress_bar(0, 15) + out += ' ' + + if p.flags & lt.peer_info.handshake: + id = 'waiting for handshake' + elif p.flags & lt.peer_info.connecting: + id = 'connecting to peer' + else: + id = p.client + + out += '%s\n' % id[:10] + + write_line(console, out) + + +def print_download_queue(console, download_queue): + + out = "" + + for e in download_queue: + out += '%4d: [' % e['piece_index'] + for b in e['blocks']: + s = b['state'] + if s == 3: + out += '#' + elif s == 2: + out += '=' + elif s == 1: + out += '-' + else: + out += ' ' + out += ']\n' + + write_line(console, out) + + +def add_torrent(ses, filename, options): + atp = lt.add_torrent_params() + if filename.startswith('magnet:'): + atp = lt.parse_magnet_uri(filename) + else: + atp.ti = lt.torrent_info(filename) + try: + atp.resume_data = open(os.path.join(options.save_path, atp.info.name() + '.fastresume'), 'rb').read() + except Exception: + pass + + atp.save_path = options.save_path + atp.storage_mode = lt.storage_mode_t.storage_mode_sparse + atp.flags |= lt.torrent_flags.duplicate_is_error \ + | lt.torrent_flags.auto_managed \ + | lt.torrent_flags.duplicate_is_error + ses.async_add_torrent(atp) + + +def main(): + from optparse import OptionParser + + parser = OptionParser() + + parser.add_option('-p', '--port', type='int', help='set listening port') + + parser.add_option( + '-i', '--listen-interface', type='string', + help='set interface for incoming connections', ) + + parser.add_option( + '-o', '--outgoing-interface', type='string', + help='set interface for outgoing connections') + + parser.add_option( + '-d', '--max-download-rate', type='float', + help='the maximum download rate given in kB/s. 0 means infinite.') + + parser.add_option( + '-u', '--max-upload-rate', type='float', + help='the maximum upload rate given in kB/s. 0 means infinite.') + + parser.add_option( + '-s', '--save-path', type='string', + help='the path where the downloaded file/folder should be placed.') + + parser.add_option( + '-r', '--proxy-host', type='string', + help='sets HTTP proxy host and port (separated by \':\')') + + parser.set_defaults( + port=6881, + listen_interface='0.0.0.0', + outgoing_interface='', + max_download_rate=0, + max_upload_rate=0, + save_path='.', + proxy_host='' + ) + + (options, args) = parser.parse_args() + + if options.port < 0 or options.port > 65525: + options.port = 6881 + + options.max_upload_rate *= 1000 + options.max_download_rate *= 1000 + + if options.max_upload_rate <= 0: + options.max_upload_rate = -1 + if options.max_download_rate <= 0: + options.max_download_rate = -1 + + settings = { + 'user_agent': 'python_client/' + lt.__version__, + 'listen_interfaces': '%s:%d' % (options.listen_interface, options.port), + 'download_rate_limit': int(options.max_download_rate), + 'upload_rate_limit': int(options.max_upload_rate), + 'alert_mask': lt.alert.category_t.all_categories, + 'outgoing_interfaces': options.outgoing_interface, + } + + if options.proxy_host != '': + settings['proxy_hostname'] = options.proxy_host.split(':')[0] + settings['proxy_type'] = lt.proxy_type_t.http + settings['proxy_port'] = options.proxy_host.split(':')[1] + + ses = lt.session(settings) + + # map torrent_handle to torrent_status + torrents = {} + alerts_log = [] + + for f in args: + add_torrent(ses, f, options) + + if os.name == 'nt': + console = WindowsConsole() + else: + console = UnixConsole() + + alive = True + while alive: + console.clear() + + out = '' + + for h, t in torrents.items(): + out += 'name: %-40s\n' % t.name[:40] + + if t.state != lt.torrent_status.seeding: + state_str = ['queued', 'checking', 'downloading metadata', + 'downloading', 'finished', 'seeding', + 'allocating', 'checking fastresume'] + out += state_str[t.state] + ' ' + + out += '%5.4f%% ' % (t.progress * 100) + out += progress_bar(t.progress, 49) + out += '\n' + + out += 'total downloaded: %d Bytes\n' % t.total_done + out += 'peers: %d seeds: %d distributed copies: %d\n' % \ + (t.num_peers, t.num_seeds, t.distributed_copies) + out += '\n' + + out += 'download: %s/s (%s) ' \ + % (add_suffix(t.download_rate), add_suffix(t.total_download)) + out += 'upload: %s/s (%s) ' \ + % (add_suffix(t.upload_rate), add_suffix(t.total_upload)) + + if t.state != lt.torrent_status.seeding: + out += 'info-hash: %s\n' % t.info_hash + out += 'next announce: %s\n' % t.next_announce + out += 'tracker: %s\n' % t.current_tracker + + write_line(console, out) + + print_peer_info(console, t.handle.get_peer_info()) + print_download_queue(console, t.handle.get_download_queue()) + + if t.state != lt.torrent_status.seeding: + try: + out = '\n' + fp = h.file_progress() + ti = t.torrent_file + for f, p in zip(ti.files(), fp): + out += progress_bar(p / float(f.size), 20) + out += ' ' + f.path + '\n' + write_line(console, out) + except Exception: + pass + + write_line(console, 76 * '-' + '\n') + write_line(console, '(q)uit), (p)ause), (u)npause), (r)eannounce\n') + write_line(console, 76 * '-' + '\n') + + alerts = ses.pop_alerts() + for a in alerts: + alerts_log.append(a.message()) + + # add new torrents to our list of torrent_status + if isinstance(a, lt.add_torrent_alert): + h = a.handle + h.set_max_connections(60) + h.set_max_uploads(-1) + torrents[h] = h.status() + + # update our torrent_status array for torrents that have + # changed some of their state + if isinstance(a, lt.state_update_alert): + for s in a.status: + torrents[s.handle] = s + + if len(alerts_log) > 20: + alerts_log = alerts_log[-20:] + + for a in alerts_log: + write_line(console, a + '\n') + + c = console.sleep_and_input(0.5) + + ses.post_torrent_updates() + if not c: + continue + + if c == 'r': + for h in torrents: + h.force_reannounce() + elif c == 'q': + alive = False + elif c == 'p': + for h in torrents: + h.pause() + elif c == 'u': + for h in torrents: + h.resume() + + ses.pause() + for h, t in torrents.items(): + if not h.is_valid() or not t.has_metadata: + continue + h.save_resume_data() + + while len(torrents) > 0: + alerts = ses.pop_alerts() + for a in alerts: + if isinstance(a, lt.save_resume_data_alert): + print(a) + data = lt.write_resume_data_buf(a.params) + h = a.handle + if h in torrents: + open(os.path.join(options.save_path, torrents[h].name + '.fastresume'), 'wb').write(data) + del torrents[h] + + if isinstance(a, lt.save_resume_data_failed_alert): + h = a.handle + if h in torrents: + print('failed to save resume data for ', torrents[h].name) + del torrents[h] + time.sleep(0.5) + + +main() diff --git a/bindings/python/compile_cmd.in b/bindings/python/compile_cmd.in new file mode 100644 index 0000000..0af6791 --- /dev/null +++ b/bindings/python/compile_cmd.in @@ -0,0 +1 @@ +@CXX@ diff --git a/bindings/python/compile_flags.in b/bindings/python/compile_flags.in new file mode 100644 index 0000000..7cf1b35 --- /dev/null +++ b/bindings/python/compile_flags.in @@ -0,0 +1 @@ +-I@top_srcdir@/include @COMPILETIME_OPTIONS@ @CPPFLAGS@ @BOOST_CPPFLAGS@ @CXXFLAGS@ @PYTHON_CXXFLAGS@ @OPENSSL_INCLUDES@ @DEBUGFLAGS@ diff --git a/bindings/python/link_flags.in b/bindings/python/link_flags.in new file mode 100644 index 0000000..bf78615 --- /dev/null +++ b/bindings/python/link_flags.in @@ -0,0 +1 @@ +-L@top_builddir@/src/.libs @LDFLAGS@ @LIBS@ @BOOST_LDFLAGS@ @BOOST_SYSTEM_LIB@ @BOOST_PYTHON_LIB@ @PTHREAD_LIBS@ @OPENSSL_LIBS@ @OPENSSL_LDFLAGS@ diff --git a/bindings/python/make_torrent.py b/bindings/python/make_torrent.py new file mode 100755 index 0000000..85a2229 --- /dev/null +++ b/bindings/python/make_torrent.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + + +import sys +import os +import libtorrent + +if len(sys.argv) < 3: + print('usage make_torrent.py file tracker-url') + sys.exit(1) + +input = os.path.abspath(sys.argv[1]) + +fs = libtorrent.file_storage() + +# def predicate(f): +# print f +# return True +# libtorrent.add_files(fs, input, predicate) + +parent_input = os.path.split(input)[0] + +# if we have a single file, use it because os.walk does not work on a single files +if os.path.isfile(input): + size = os.path.getsize(input) + fs.add_file(input, size) + +for root, dirs, files in os.walk(input): + # skip directories starting with . + if os.path.split(root)[1][0] == '.': + continue + + for f in files: + # skip files starting with . + if f[0] == '.': + continue + + # skip thumbs.db on windows + if f == 'Thumbs.db': + continue + + fname = os.path.join(root[len(parent_input) + 1:], f) + size = os.path.getsize(os.path.join(parent_input, fname)) + print('%10d kiB %s' % (size / 1024, fname)) + fs.add_file(fname, size) + +if fs.num_files() == 0: + print('no files added') + sys.exit(1) + +t = libtorrent.create_torrent(fs, 0, 4 * 1024 * 1024) + +t.add_tracker(sys.argv[2]) +t.set_creator('libtorrent %s' % libtorrent.__version__) + +libtorrent.set_piece_hashes(t, parent_input, lambda x: sys.stdout.write('.')) +sys.stdout.write('\n') + +f = open('out.torrent', 'wb+') +f.write(libtorrent.bencode(t.generate())) +f.close() diff --git a/bindings/python/setup.py b/bindings/python/setup.py new file mode 100644 index 0000000..8512c95 --- /dev/null +++ b/bindings/python/setup.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 + + +from distutils.core import setup, Extension +from distutils.sysconfig import get_config_vars +import os +import platform +import sys +import shutil +import multiprocessing + + +class flags_parser: + def __init__(self): + self.include_dirs = [] + self.library_dirs = [] + self.libraries = [] + + def parse(self, args): + """Parse out the -I -L -l directives + + Returns: + list: All other arguments + """ + ret = [] + for token in args.split(): + prefix = token[:2] + if prefix == '-I': + self.include_dirs.append(token[2:]) + elif prefix == '-L': + self.library_dirs.append(token[2:]) + elif prefix == '-l': + self.libraries.append(token[2:]) + else: + ret.append(token) + return ret + + +def arch(): + if platform.system() == 'Darwin': + __, __, machine = platform.mac_ver() + if machine.startswith('ppc'): + return ['-arch', machine] + return [] + + +def target_specific(): + if platform.system() == 'Darwin': + # On mavericks, clang will fail when unknown arguments are passed in. + # python distutils will pass in arguments it doesn't know about. + return ['-Wno-error=unused-command-line-argument-hard-error-in-future'] + return [] + + +try: + with open('compile_flags') as _file: + extra_cmd = _file.read() +except Exception: + extra_cmd = None + +try: + with open('link_flags') as _file: + ldflags = _file.read() +except Exception: + ldflags = None + +# this is to pull out compiler arguments from the CXX flags set up by the +# configure script. Specifically, the -std=c++11 flag is added to CXX and here +# we pull out everything starting from the first flag (i.e. something starting +# with a '-'). The actual command to call the compiler may be more than one +# word, for instance "ccache g++". +try: + with open('compile_cmd') as _file: + cmd = _file.read().split(' ') + while len(cmd) > 0 and not cmd[0].startswith('-'): + cmd = cmd[1:] + extra_cmd += ' '.join(cmd) +except Exception: + pass + +ext = None +packages = None + +if '--bjam' in sys.argv: + del sys.argv[sys.argv.index('--bjam')] + + if '--help' not in sys.argv \ + and '--help-commands' not in sys.argv: + + toolset = '' + file_ext = '.so' + + if platform.system() == 'Windows': + file_ext = '.pyd' + # See https://wiki.python.org/moin/WindowsCompilers for a table of msvc versions + # used for each python version + # Specify the full version number for 9.0 and 10.0 because apparently + # older versions of boost don't support only specifying the major number and + # there was only one version of msvc with those majors. + # Only specify the major for msvc-14 so that 14.1, 14.11, etc can be used. + # Hopefully people building with msvc-14 are using a new enough version of boost + # for this to work. + if sys.version_info[0:2] in ((2, 6), (2, 7), (3, 0), (3, 1), (3, 2)): + toolset = ' toolset=msvc-9.0' + elif sys.version_info[0:2] in ((3, 3), (3, 4)): + toolset = ' toolset=msvc-10.0' + elif sys.version_info[0:2] in ((3, 5), (3, 6)): + toolset = ' toolset=msvc-14' + else: + # unknown python version, lets hope the user has the right version of msvc configured + toolset = ' toolset=msvc' + + parallel_builds = ' -j%d' % multiprocessing.cpu_count() + if sys.maxsize > 2**32: + address_model = ' address-model=64' + else: + address_model = ' address-model=32' + + # add extra quoting around the path to prevent bjam from parsing it as a list + # if the path has spaces + os.environ['LIBTORRENT_PYTHON_INTERPRETER'] = '"' + sys.executable + '"' + + # build libtorrent using bjam and build the installer with distutils + cmdline = ('b2 libtorrent-link=static boost-link=static release ' + 'optimization=space stage_module --abbreviate-paths' + + address_model + toolset + parallel_builds) + print(cmdline) + if os.system(cmdline) != 0: + print('build failed') + sys.exit(1) + + try: + os.mkdir('build') + except Exception: + pass + try: + shutil.rmtree('build/lib') + except Exception: + pass + try: + os.mkdir('build/lib') + except Exception: + pass + try: + os.mkdir('libtorrent') + except Exception: + pass + shutil.copyfile('libtorrent' + file_ext, + 'build/lib/libtorrent' + file_ext) + + packages = ['libtorrent'] + +else: + # Remove '-Wstrict-prototypes' compiler option, which isn't valid for C++. + cfg_vars = get_config_vars() + for key, value in list(cfg_vars.items()): + if isinstance(value, str): + cfg_vars[key] = value.replace('-Wstrict-prototypes', '') + + src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "src")) + source_list = [os.path.join(src_dir, s) for s in os.listdir(src_dir) if s.endswith(".cpp")] + + flags = flags_parser() + ext_extra = {} + + if ldflags: + # ldflags parsed first to ensure the correct library search path order + ext_extra["extra_link_args"] = flags.parse(ldflags) + arch() + + if extra_cmd: + ext_extra["extra_compile_args"] = flags.parse(extra_cmd) + arch() + target_specific() + + ext = [Extension( + 'libtorrent', + sources=sorted(source_list), + language='c++', + include_dirs=flags.include_dirs, + library_dirs=flags.library_dirs, + libraries=['torrent-rasterbar'] + flags.libraries, + **ext_extra) + ] + +setup( + name='python-libtorrent', + version='1.2.9', + author='Arvid Norberg', + author_email='arvid@libtorrent.org', + description='Python bindings for libtorrent-rasterbar', + long_description='Python bindings for libtorrent-rasterbar', + url='http://libtorrent.org', + platforms=[platform.system() + '-' + platform.machine()], + license='BSD', + packages=packages, + ext_modules=ext +) diff --git a/bindings/python/setup.py.cmake.in b/bindings/python/setup.py.cmake.in new file mode 100644 index 0000000..7fdcfcf --- /dev/null +++ b/bindings/python/setup.py.cmake.in @@ -0,0 +1,15 @@ +from setuptools import setup +import platform + +setup( + name='libtorrent', + version='@libtorrent_VERSION@', + author='Arvid Norberg', + author_email='arvid@libtorrent.org', + description='Python bindings for libtorrent-rasterbar', + long_description='Python bindings for libtorrent-rasterbar', + url='http://libtorrent.org', + platforms=[platform.system() + '-' + platform.machine()], + license='BSD', + package_dir = {'': '@CMAKE_CURRENT_BINARY_DIR@'} +) diff --git a/bindings/python/simple_client.py b/bindings/python/simple_client.py new file mode 100755 index 0000000..df32c9a --- /dev/null +++ b/bindings/python/simple_client.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright Arvid Norberg 2008. Use, modification and distribution is +# subject to the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +from __future__ import print_function + +import libtorrent as lt +import time +import sys + +ses = lt.session({'listen_interfaces': '0.0.0.0:6881'}) + +info = lt.torrent_info(sys.argv[1]) +h = ses.add_torrent({'ti': info, 'save_path': '.'}) +s = h.status() +print('starting', s.name) + +while (not s.is_seeding): + s = h.status() + + print('\r%.2f%% complete (down: %.1f kB/s up: %.1f kB/s peers: %d) %s' % ( + s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000, + s.num_peers, s.state), end=' ') + + alerts = ses.pop_alerts() + for a in alerts: + if a.category() & lt.alert.category_t.error_notification: + print(a) + + sys.stdout.flush() + + time.sleep(1) + +print(h.status().name, 'complete') diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp new file mode 100644 index 0000000..880dc28 --- /dev/null +++ b/bindings/python/src/alert.cpp @@ -0,0 +1,1129 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include // for piece_block +#include +#include +#include +#include "bytes.hpp" + +#include + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +bytes get_buffer(read_piece_alert const& rpa) +{ + return rpa.buffer ? bytes(rpa.buffer.get(), rpa.size) + : bytes(); +} + +list stats_alert_transferred(stats_alert const& alert) +{ + list result; + for (int i = 0; i < alert.num_channels; ++i) { + result.append(alert.transferred[i]); + } + return result; +} + +list get_status_from_update_alert(state_update_alert const& alert) +{ + list result; + + for (std::vector::const_iterator i = alert.status.begin(); i != alert.status.end(); ++i) + { + result.append(*i); + } + return result; +} + +list dht_stats_active_requests(dht_stats_alert const& a) +{ + list result; + + for (std::vector::const_iterator i = a.active_requests.begin(); + i != a.active_requests.end(); ++i) + { + dict d; + + d["type"] = i->type; + d["outstanding_requests"] = i->outstanding_requests; + d["timeouts"] = i->timeouts; + d["responses"] = i->responses; + d["branch_factor"] = i->branch_factor; + d["nodes_left"] = i->nodes_left; + d["last_sent"] = i->last_sent; + d["first_timeout"] = i->first_timeout; + + result.append(d); + } + return result; +} + +list dht_stats_routing_table(dht_stats_alert const& a) +{ + list result; + + for (std::vector::const_iterator i = a.routing_table.begin(); + i != a.routing_table.end(); ++i) + { + dict d; + + d["num_nodes"] = i->num_nodes; + d["num_replacements"] = i->num_replacements; + + result.append(d); + } + return result; +} + +dict dht_immutable_item(dht_immutable_item_alert const& alert) +{ + dict d; + d["key"] = alert.target; + d["value"] = bytes(alert.item.to_string()); + return d; +} + +dict dht_mutable_item(dht_mutable_item_alert const& alert) +{ + dict d; + d["key"] = bytes(alert.key.data(), alert.key.size()); + d["value"] = bytes(alert.item.to_string()); + d["signature"] = bytes(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = bytes(alert.salt); + d["authoritative"] = alert.authoritative; + return d; +} + +dict dht_put_item(dht_put_alert const& alert) +{ + dict d; + if (alert.target.is_all_zeros()) { + d["public_key"] = bytes(alert.public_key.data(), alert.public_key.size()); + d["signature"] = bytes(alert.signature.data(), alert.signature.size()); + d["seq"] = alert.seq; + d["salt"] = bytes(alert.salt); + } else { + d["target"] = alert.target; + } + return d; +} + +dict session_stats_values(session_stats_alert const& alert) +{ + std::vector map = session_stats_metrics(); + dict d; + auto counters = alert.counters(); + + for (stats_metric const& m : map) + { + d[m.name] = counters[m.value_index]; + } + return d; +} + +list dht_live_nodes_nodes(dht_live_nodes_alert const& alert) +{ + list result; + std::vector> const nodes = alert.nodes(); + for (std::pair const& node : nodes) + { + dict d; + d["nid"] = node.first; + d["endpoint"] = node.second; + result.append(d); + } + return result; +} + +list dht_sample_infohashes_nodes(dht_sample_infohashes_alert const& alert) +{ + list result; + std::vector> const nodes = alert.nodes(); + for (std::pair const& node : nodes) + { + dict d; + d["nid"] = node.first; + d["endpoint"] = node.second; + result.append(d); + } + return result; +} + +#if TORRENT_ABI_VERSION == 1 +entry const& get_resume_data_entry(save_resume_data_alert const& self) +{ + return *self.resume_data; +} +#endif + +namespace boost +{ + // some older compilers (like msvc-12.0) end up using + // boost::is_polymorphic inside boost.python applied + // to alert types. This is problematic, since it appears + // to be implemented by deriving from the type, which + // yields a compiler error since most alerts are final. + // this just short-cuts the query to say that all these + // types are indeed polymorphic, no need to derive from + // them. +#define POLY(x) template<> \ + struct is_polymorphic : boost::mpl::true_ {}; + + POLY(torrent_alert) + POLY(tracker_alert) + POLY(torrent_removed_alert) + POLY(read_piece_alert) + POLY(peer_alert) + POLY(tracker_error_alert) + POLY(tracker_warning_alert) + POLY(tracker_reply_alert) + POLY(tracker_announce_alert) + POLY(hash_failed_alert) + POLY(peer_ban_alert) + POLY(peer_error_alert) + POLY(invalid_request_alert) + POLY(torrent_error_alert) + POLY(torrent_finished_alert) + POLY(piece_finished_alert) + POLY(block_finished_alert) + POLY(block_downloading_alert) + POLY(storage_moved_alert) + POLY(storage_moved_failed_alert) + POLY(torrent_deleted_alert) + POLY(torrent_paused_alert) + POLY(torrent_checked_alert) + POLY(url_seed_alert) + POLY(file_error_alert) + POLY(metadata_failed_alert) + POLY(metadata_received_alert) + POLY(listen_failed_alert) + POLY(listen_succeeded_alert) + POLY(portmap_error_alert) + POLY(portmap_alert) + POLY(fastresume_rejected_alert) + POLY(peer_blocked_alert) + POLY(scrape_reply_alert) + POLY(scrape_failed_alert) + POLY(udp_error_alert) + POLY(external_ip_alert) + POLY(save_resume_data_alert) + POLY(file_completed_alert) + POLY(file_renamed_alert) + POLY(file_rename_failed_alert) + POLY(torrent_resumed_alert) + POLY(state_changed_alert) + POLY(state_update_alert) + POLY(i2p_alert) + POLY(dht_immutable_item_alert) + POLY(dht_mutable_item_alert) + POLY(dht_put_alert) + POLY(dht_reply_alert) + POLY(dht_announce_alert) + POLY(dht_get_peers_alert) + POLY(peer_unsnubbed_alert) + POLY(peer_snubbed_alert) + POLY(peer_connect_alert) + POLY(peer_disconnected_alert) + POLY(request_dropped_alert) + POLY(block_timeout_alert) + POLY(unwanted_block_alert) + POLY(torrent_delete_failed_alert) + POLY(save_resume_data_failed_alert) + POLY(performance_alert) + POLY(stats_alert) + POLY(cache_flushed_alert) + POLY(incoming_connection_alert) + POLY(torrent_need_cert_alert) + POLY(add_torrent_alert) + POLY(dht_outgoing_get_peers_alert) + POLY(lsd_error_alert) + POLY(dht_stats_alert) + POLY(incoming_request_alert) + POLY(dht_log_alert) + POLY(dht_pkt_alert) + POLY(dht_get_peers_reply_alert) + POLY(dht_direct_response_alert) + POLY(session_error_alert) + POLY(dht_live_nodes_alert) + POLY(session_stats_header_alert) + POLY(dht_sample_infohashes_alert) + POLY(block_uploaded_alert) + POLY(alerts_dropped_alert) + POLY(session_stats_alert) + POLY(socks5_alert) + +#if TORRENT_ABI_VERSION == 1 + POLY(anonymous_mode_alert) + POLY(torrent_added_alert) + POLY(torrent_update_alert) +#endif + +#ifndef TORRENT_DISABLE_LOGGING + POLY(portmap_log_alert) + POLY(log_alert) + POLY(torrent_log_alert) + POLY(peer_log_alert) + POLY(picker_log_alert) +#endif // TORRENT_DISABLE_LOGGING + +#undef POLY +} + +struct dummy3 {}; +struct dummy12 {}; + +bytes get_pkt_buf(dht_pkt_alert const& alert) +{ + return {alert.pkt_buf().data(), static_cast(alert.pkt_buf().size())}; +} + +list get_dropped_alerts(alerts_dropped_alert const& alert) +{ + list ret; + for (int i = 0; i < int(alert.dropped_alerts.size()); ++i) + ret.append(bool(alert.dropped_alerts[i])); + return ret; +} + +void bind_alert() +{ + using boost::noncopyable; + + using by_value = return_value_policy; + + { + scope alert_scope = class_("alert", no_init) + .def("message", &alert::message) + .def("what", &alert::what) + .def("category", &alert::category) +#if TORRENT_ABI_VERSION == 1 + .def("severity", &alert::severity) +#endif + .def("__str__", &alert::message) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("severity_levels") + .value("debug", alert::debug) + .value("info", alert::info) + .value("warning", alert::warning) + .value("critical", alert::critical) + .value("fatal", alert::fatal) + .value("none", alert::none) + ; +#endif + + scope s = class_("category_t"); + s.attr("error_notification") = alert::error_notification; + s.attr("peer_notification") = alert::peer_notification; + s.attr("port_mapping_notification") = alert::port_mapping_notification; + s.attr("storage_notification") = alert::storage_notification; + s.attr("tracker_notification") = alert::tracker_notification; + s.attr("connect_notification") = alert::connect_notification; + s.attr("status_notification") = alert::status_notification; +#if TORRENT_ABI_VERSION == 1 + s.attr("debug_notification") = alert::debug_notification; + s.attr("progress_notification") = alert::progress_notification; +#endif + s.attr("ip_block_notification") = alert::ip_block_notification; + s.attr("performance_warning") = alert::performance_warning; + s.attr("dht_notification") = alert::dht_notification; + s.attr("stats_notification") = alert::stats_notification; + s.attr("session_log_notification") = alert::session_log_notification; + s.attr("torrent_log_notification") = alert::torrent_log_notification; + s.attr("peer_log_notification") = alert::peer_log_notification; + s.attr("incoming_request_notification") = alert::incoming_request_notification; + s.attr("dht_log_notification") = alert::dht_log_notification; + s.attr("dht_operation_notification") = alert::dht_operation_notification; + s.attr("port_mapping_log_notification") = alert::port_mapping_log_notification; + s.attr("picker_log_notification") = alert::picker_log_notification; + s.attr("file_progress_notification") = alert::file_progress_notification; + s.attr("piece_progress_notification") = alert::piece_progress_notification; + s.attr("upload_notification") = alert::upload_notification; + s.attr("block_progress_notification") = alert::block_progress_notification; + s.attr("all_categories") = alert::all_categories; + } + + { + scope s = class_("alert_category"); + s.attr("error") = alert_category::error; + s.attr("peer") = alert_category::peer; + s.attr("port_mapping") = alert_category::port_mapping; + s.attr("storage") = alert_category::storage; + s.attr("tracker") = alert_category::tracker; + s.attr("connect") = alert_category::connect; + s.attr("status") = alert_category::status; + s.attr("ip_block") = alert_category::ip_block; + s.attr("performance_warning") = alert_category::performance_warning; + s.attr("dht") = alert_category::dht; + s.attr("stats") = alert_category::stats; + s.attr("session_log") = alert_category::session_log; + s.attr("torrent_log") = alert_category::torrent_log; + s.attr("peer_log") = alert_category::peer_log; + s.attr("incoming_request") = alert_category::incoming_request; + s.attr("dht_log") = alert_category::dht_log; + s.attr("dht_operation") = alert_category::dht_operation; + s.attr("port_mapping_log") = alert_category::port_mapping_log; + s.attr("picker_log") = alert_category::picker_log; + s.attr("file_progress") = alert_category::file_progress; + s.attr("piece_progress") = alert_category::piece_progress; + s.attr("upload") = alert_category::upload; + s.attr("block_progress") = alert_category::block_progress; + s.attr("all") = alert_category::all; + } + + enum_("operation_t") + .value("unknown", operation_t::unknown) + .value("bittorrent", operation_t::bittorrent) + .value("iocontrol", operation_t::iocontrol) + .value("getpeername", operation_t::getpeername) + .value("getname", operation_t::getname) + .value("alloc_recvbuf", operation_t::alloc_recvbuf) + .value("alloc_sndbuf", operation_t::alloc_sndbuf) + .value("file_write", operation_t::file_write) + .value("file_read", operation_t::file_read) + .value("file", operation_t::file) + .value("sock_write", operation_t::sock_write) + .value("sock_read", operation_t::sock_read) + .value("sock_open", operation_t::sock_open) + .value("sock_bind", operation_t::sock_bind) + .value("available", operation_t::available) + .value("encryption", operation_t::encryption) + .value("connect", operation_t::connect) + .value("ssl_handshake", operation_t::ssl_handshake) + .value("get_interface", operation_t::get_interface) + .value("sock_listen", operation_t::sock_listen) + .value("sock_bind_to_device", operation_t::sock_bind_to_device) + .value("sock_accept", operation_t::sock_accept) + .value("parse_address", operation_t::parse_address) + .value("enum_if", operation_t::enum_if) + .value("file_stat", operation_t::file_stat) + .value("file_copy", operation_t::file_copy) + .value("file_fallocate", operation_t::file_fallocate) + .value("file_hard_link", operation_t::file_hard_link) + .value("file_remove", operation_t::file_remove) + .value("file_rename", operation_t::file_rename) + .value("file_open", operation_t::file_open) + .value("mkdir", operation_t::mkdir) + .value("check_resume", operation_t::check_resume) + .value("exception", operation_t::exception) + .value("alloc_cache_piece", operation_t::alloc_cache_piece) + .value("partfile_move", operation_t::partfile_move) + .value("partfile_read", operation_t::partfile_read) + .value("partfile_write", operation_t::partfile_write) + .value("hostname_lookup", operation_t::hostname_lookup) + .value("symlink", operation_t::symlink) + .value("handshake", operation_t::handshake) + .value("sock_option", operation_t::sock_option) + ; + + def("operation_name", static_cast(<::operation_name)); + + class_, noncopyable>( + "torrent_alert", no_init) + .add_property("handle", make_getter(&torrent_alert::handle, by_value())) + .add_property("torrent_name", &torrent_alert::torrent_name) + ; + + class_, noncopyable>( + "tracker_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("url", &tracker_alert::url) +#endif + .add_property("local_endpoint", make_getter(&tracker_alert::local_endpoint, by_value())) + .def("tracker_url", &tracker_alert::tracker_url) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "torrent_added_alert", no_init) + ; +#endif + + class_, noncopyable>( + "torrent_removed_alert", no_init) + .def_readonly("info_hash", &torrent_removed_alert::info_hash) + ; + + class_, noncopyable>( + "read_piece_alert", nullptr, no_init) + .def_readonly("error", &read_piece_alert::error) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("ec", &read_piece_alert::ec) +#endif + .add_property("buffer", get_buffer) + .add_property("piece", make_getter(&read_piece_alert::piece, by_value())) + .def_readonly("size", &read_piece_alert::size) + ; + + class_, noncopyable>( + "peer_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&peer_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&peer_alert::endpoint, by_value())) + .def_readonly("pid", &peer_alert::pid) + ; + class_, noncopyable>( + "tracker_error_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &tracker_error_alert::msg) + .def_readonly("status_code", &tracker_error_alert::status_code) +#endif + .def("error_message", &tracker_error_alert::error_message) + .def_readonly("times_in_row", &tracker_error_alert::times_in_row) + .def_readonly("error", &tracker_error_alert::error) + ; + + class_, noncopyable>( + "tracker_warning_alert", no_init); + + class_, noncopyable>( + "tracker_reply_alert", no_init) + .def_readonly("num_peers", &tracker_reply_alert::num_peers) + ; + + class_, noncopyable>( + "tracker_announce_alert", no_init) + .def_readonly("event", &tracker_announce_alert::event) + ; + + class_, noncopyable>( + "hash_failed_alert", no_init) + .add_property("piece_index", make_getter(&hash_failed_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "peer_ban_alert", no_init); + + class_, noncopyable>( + "peer_error_alert", no_init) + .def_readonly("error", &peer_error_alert::error) + .def_readonly("op", &peer_error_alert::op) + ; + + class_, noncopyable>( + "invalid_request_alert", no_init) + .def_readonly("request", &invalid_request_alert::request) + ; + + class_("peer_request") + .add_property("piece", make_getter(&peer_request::piece, by_value())) + .def_readonly("start", &peer_request::start) + .def_readonly("length", &peer_request::length) + .def(self == self) + ; + + class_, noncopyable>( + "torrent_error_alert", no_init) + .def_readonly("error", &torrent_error_alert::error) + ; + + class_, noncopyable>( + "torrent_finished_alert", no_init); + + class_, noncopyable>( + "piece_finished_alert", no_init) + .add_property("piece_index", make_getter(&piece_finished_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_finished_alert", no_init) + .add_property("block_index", make_getter(&block_finished_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_finished_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_downloading_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("peer_speedmsg", &block_downloading_alert::peer_speedmsg) +#endif + .add_property("block_index", make_getter(&block_downloading_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_downloading_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "storage_moved_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("path", &storage_moved_alert::path) +#endif + .def("storage_path", &storage_moved_alert::storage_path) + ; + + class_, noncopyable>( + "storage_moved_failed_alert", no_init) + .def_readonly("error", &storage_moved_failed_alert::error) + .def("file_path", &storage_moved_failed_alert::file_path) + .def_readonly("op", &storage_moved_failed_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &storage_moved_failed_alert::operation) +#endif + ; + + class_, noncopyable>( + "torrent_deleted_alert", no_init) + .def_readonly("info_hash", &torrent_deleted_alert::info_hash) + ; + + class_, noncopyable>( + "torrent_paused_alert", no_init); + + class_, noncopyable>( + "torrent_checked_alert", no_init); + + class_, noncopyable>( + "url_seed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("url", &url_seed_alert::url) + .def_readonly("msg", &url_seed_alert::msg) +#endif + .def_readonly("error", &url_seed_alert::error) + .def("server_url", &url_seed_alert::server_url) + .def("error_message", &url_seed_alert::error_message) + ; + + class_, noncopyable>( + "file_error_alert", no_init) + .def_readonly("error", &file_error_alert::error) + .def("filename", &file_error_alert::filename) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("file", &file_error_alert::file) + .def_readonly("msg", &file_error_alert::msg) +#endif + ; + + class_, noncopyable>( + "metadata_failed_alert", no_init) + .def_readonly("error", &metadata_failed_alert::error) + ; + + class_, noncopyable>( + "metadata_received_alert", no_init); + + class_, noncopyable>( + "listen_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("endpoint", make_getter(&listen_failed_alert::endpoint, by_value())) +#endif + .add_property("address", make_getter(&listen_failed_alert::address, by_value())) + .def_readonly("port", &listen_failed_alert::port) + .def("listen_interface", &listen_failed_alert::listen_interface) + .def_readonly("error", &listen_failed_alert::error) + .def_readonly("op", &listen_failed_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &listen_failed_alert::operation) + .def_readonly("sock_type", &listen_failed_alert::sock_type) +#endif + .def_readonly("socket_type", &listen_failed_alert::socket_type) + ; + + class_, noncopyable>( + "listen_succeeded_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("endpoint", make_getter(&listen_succeeded_alert::endpoint, by_value())) +#endif + .add_property("address", make_getter(&listen_succeeded_alert::address, by_value())) + .def_readonly("port", &listen_succeeded_alert::port) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("sock_type", &listen_succeeded_alert::sock_type) +#endif + .def_readonly("socket_type", &listen_succeeded_alert::socket_type) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("listen_succeded_alert_socket_type_t") + .value("tcp", listen_succeeded_alert::socket_type_t::tcp) + .value("tcp_ssl", listen_succeeded_alert::socket_type_t::tcp_ssl) + .value("udp", listen_succeeded_alert::socket_type_t::udp) + .value("i2p", listen_succeeded_alert::socket_type_t::i2p) + .value("socks5", listen_succeeded_alert::socket_type_t::socks5) + .value("utp_ssl", listen_succeeded_alert::socket_type_t::utp_ssl) + ; + + enum_("listen_failed_alert_socket_type_t") + .value("tcp", listen_failed_alert::socket_type_t::tcp) + .value("tcp_ssl", listen_failed_alert::socket_type_t::tcp_ssl) + .value("udp", listen_failed_alert::socket_type_t::udp) + .value("i2p", listen_failed_alert::socket_type_t::i2p) + .value("socks5", listen_failed_alert::socket_type_t::socks5) + .value("utp_ssl", listen_failed_alert::socket_type_t::utp_ssl) + ; +#endif + + enum_("socket_type_t") + .value("tcp", socket_type_t::tcp) + .value("tcp_ssl", socket_type_t::tcp_ssl) + .value("udp", socket_type_t::udp) + .value("i2p", socket_type_t::i2p) + .value("socks5", socket_type_t::socks5) + .value("utp_ssl", socket_type_t::utp_ssl) + ; + + class_, noncopyable>( + "portmap_error_alert", no_init) + .add_property("mapping", make_getter(&portmap_error_alert::mapping, by_value())) + .def_readonly("error", &portmap_error_alert::error) + .def_readonly("map_transport", &portmap_error_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("map_type", &portmap_error_alert::map_type) + .def_readonly("type", &portmap_error_alert::map_type) + .def_readonly("msg", &portmap_error_alert::msg) +#endif + ; + + class_, noncopyable>("portmap_alert", no_init) + .add_property("mapping", make_getter(&portmap_alert::mapping, by_value())) + .def_readonly("external_port", &portmap_alert::external_port) + .def_readonly("map_protocol", &portmap_alert::map_protocol) + .def_readonly("map_transport", &portmap_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("type", &portmap_alert::map_type) + .def_readonly("map_type", &portmap_alert::map_type) +#endif + ; + +#ifndef TORRENT_DISABLE_LOGGING + + class_, noncopyable>("portmap_log_alert", no_init) + .def_readonly("map_transport", &portmap_log_alert::map_transport) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("type", &portmap_log_alert::map_type) + .def_readonly("msg", &portmap_log_alert::msg) + .def_readonly("map_type", &portmap_log_alert::map_type) +#endif + ; + +#endif // TORRENT_DISABLE_LOGGING + + class_, noncopyable>( + "fastresume_rejected_alert", no_init) + .def_readonly("error", &fastresume_rejected_alert::error) + .def("file_path", &fastresume_rejected_alert::file_path) + .def_readonly("op", &fastresume_rejected_alert::op) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("operation", &fastresume_rejected_alert::operation) + .def_readonly("msg", &fastresume_rejected_alert::msg) +#endif + ; + + class_, noncopyable>( + "peer_blocked_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&peer_blocked_alert::ip, by_value())) +#endif + .add_property("reason", &peer_blocked_alert::reason) + ; + + enum_("reason_t") + .value("ip_filter", peer_blocked_alert::reason_t::ip_filter) + .value("port_filter", peer_blocked_alert::reason_t::port_filter) + .value("i2p_mixed", peer_blocked_alert::reason_t::i2p_mixed) + .value("privileged_ports", peer_blocked_alert::reason_t::privileged_ports) + .value("utp_disabled", peer_blocked_alert::reason_t::utp_disabled) + .value("tcp_disabled", peer_blocked_alert::reason_t::tcp_disabled) + .value("invalid_local_interface", peer_blocked_alert::reason_t::invalid_local_interface) + ; + + class_, noncopyable>( + "scrape_reply_alert", no_init) + .def_readonly("incomplete", &scrape_reply_alert::incomplete) + .def_readonly("complete", &scrape_reply_alert::complete) + ; + + class_, noncopyable>( + "scrape_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &scrape_failed_alert::msg) +#endif + .def("error_message", &scrape_failed_alert::error_message) + .def_readonly("error", &scrape_failed_alert::error) + ; + + class_, noncopyable>( + "udp_error_alert", no_init) + .add_property("endpoint", make_getter(&udp_error_alert::endpoint, by_value())) + .def_readonly("error", &udp_error_alert::error) + ; + + class_, noncopyable>( + "external_ip_alert", no_init) + .add_property("external_address", make_getter(&external_ip_alert::external_address, by_value())) + ; + + class_, noncopyable>( + "save_resume_data_alert", no_init) + .def_readonly("params", &save_resume_data_alert::params) +#if TORRENT_ABI_VERSION == 1 + .add_property("resume_data", make_function(get_resume_data_entry, by_value())) +#endif + ; + + class_, noncopyable>( + "file_completed_alert", no_init) + .add_property("index", make_getter(&file_completed_alert::index, by_value())) + ; + + class_, noncopyable>( + "file_renamed_alert", no_init) + .add_property("index", make_getter(&file_renamed_alert::index, by_value())) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("name", &file_renamed_alert::name) +#endif + .def("new_name", &file_renamed_alert::new_name) + ; + + class_, noncopyable>( + "file_rename_failed_alert", no_init) + .add_property("index", make_getter(&file_rename_failed_alert::index, by_value())) + .def_readonly("error", &file_rename_failed_alert::error) + ; + + class_, noncopyable>( + "torrent_resumed_alert", no_init + ); + + class_, noncopyable>( + "state_changed_alert", no_init) + .def_readonly("state", &state_changed_alert::state) + .def_readonly("prev_state", &state_changed_alert::prev_state) + ; + + class_, noncopyable>( + "state_update_alert", no_init) + .add_property("status", &get_status_from_update_alert) + ; + + class_, noncopyable>( + "i2p_alert", no_init) + .add_property("error", &i2p_alert::error) + ; + + class_, noncopyable>( + "dht_reply_alert", no_init) + .def_readonly("num_peers", &dht_reply_alert::num_peers) + ; + + class_, noncopyable>( + "dht_announce_alert", no_init) + .add_property("ip", make_getter(&dht_announce_alert::ip, by_value())) + .def_readonly("port", &dht_announce_alert::port) + .def_readonly("info_hash", &dht_announce_alert::info_hash) + ; + + class_, noncopyable>( + "dht_get_peers_alert", no_init + ) + .def_readonly("info_hash", &dht_get_peers_alert::info_hash) + ; + + class_, noncopyable>( + "peer_unsnubbed_alert", no_init + ); + + class_, noncopyable>( + "peer_snubbed_alert", no_init + ); + + class_, noncopyable>( + "peer_connect_alert", no_init + ); + + class_, noncopyable>( + "peer_disconnected_alert", no_init) + .def_readonly("socket_type", &peer_disconnected_alert::socket_type) + .def_readonly("op", &peer_disconnected_alert::op) + .def_readonly("error", &peer_disconnected_alert::error) + .def_readonly("reason", &peer_disconnected_alert::reason) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &peer_disconnected_alert::msg) +#endif + ; + + class_, noncopyable>( + "request_dropped_alert", no_init) + .add_property("block_index", make_getter(&request_dropped_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&request_dropped_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "block_timeout_alert", no_init) + .add_property("block_index", make_getter(&block_timeout_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&block_timeout_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "unwanted_block_alert", no_init) + .add_property("block_index", make_getter(&unwanted_block_alert::block_index, by_value())) + .add_property("piece_index", make_getter(&unwanted_block_alert::piece_index, by_value())) + ; + + class_, noncopyable>( + "torrent_delete_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &torrent_delete_failed_alert::msg) +#endif + .def_readonly("error", &torrent_delete_failed_alert::error) + .def_readonly("info_hash", &torrent_delete_failed_alert::info_hash) + ; + + class_, noncopyable>( + "save_resume_data_failed_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("msg", &save_resume_data_failed_alert::msg) +#endif + .def_readonly("error", &save_resume_data_failed_alert::error) + ; + + class_, noncopyable>( + "performance_alert", no_init) + .def_readonly("warning_code", &performance_alert::warning_code) + ; + enum_("performance_warning_t") + .value("outstanding_disk_buffer_limit_reached", performance_alert::outstanding_disk_buffer_limit_reached) + .value("outstanding_request_limit_reached", performance_alert::outstanding_request_limit_reached) + .value("upload_limit_too_low", performance_alert::upload_limit_too_low) + .value("download_limit_too_low", performance_alert::download_limit_too_low) + .value("send_buffer_watermark_too_low", performance_alert::send_buffer_watermark_too_low) + .value("too_many_optimistic_unchoke_slots", performance_alert::too_many_optimistic_unchoke_slots) +#if TORRENT_ABI_VERSION == 1 + .value("bittyrant_with_no_uplimit", performance_alert::bittyrant_with_no_uplimit) +#endif + .value("too_high_disk_queue_limit", performance_alert::too_high_disk_queue_limit) + .value("too_few_outgoing_ports", performance_alert::too_few_outgoing_ports) + .value("too_few_file_descriptors", performance_alert::too_few_file_descriptors) + ; + + class_, noncopyable>( + "stats_alert", no_init) + .add_property("transferred", &stats_alert_transferred) + .def_readonly("interval", &stats_alert::interval) + ; + + enum_("stats_channel") + .value("upload_payload", stats_alert::upload_payload) + .value("upload_protocol", stats_alert::upload_protocol) + .value("upload_ip_protocol", stats_alert::upload_ip_protocol) +#if TORRENT_ABI_VERSION == 1 + .value("upload_dht_protocol", stats_alert::upload_dht_protocol) + .value("upload_tracker_protocol", stats_alert::upload_tracker_protocol) +#endif + .value("download_payload", stats_alert::download_payload) + .value("download_protocol", stats_alert::download_protocol) + .value("download_ip_protocol", stats_alert::download_ip_protocol) +#if TORRENT_ABI_VERSION == 1 + .value("download_dht_protocol", stats_alert::download_dht_protocol) + .value("download_tracker_protocol", stats_alert::download_tracker_protocol) +#endif + ; + + class_, noncopyable>( + "cache_flushed_alert", no_init) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "anonymous_mode_alert", no_init) + .def_readonly("kind", &anonymous_mode_alert::kind) + .def_readonly("str", &anonymous_mode_alert::str) + ; + + enum_("kind") + .value("tracker_no_anonymous", anonymous_mode_alert::tracker_not_anonymous) + ; +#endif // TORRENT_ABI_VERSION + + class_, noncopyable>( + "incoming_connection_alert", no_init) + .def_readonly("socket_type", &incoming_connection_alert::socket_type) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&incoming_connection_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&incoming_connection_alert::endpoint, by_value())) + ; + class_, noncopyable>( + "torrent_need_cert_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("error", &torrent_need_cert_alert::error) +#endif + ; + + class_, noncopyable>( + "add_torrent_alert", no_init) + .def_readonly("error", &add_torrent_alert::error) + .add_property("params", &add_torrent_alert::params) + ; + +#if TORRENT_ABI_VERSION == 1 + class_, noncopyable>( + "torrent_update_alert", no_init) + .def_readonly("old_ih", &torrent_update_alert::old_ih) + .def_readonly("new_ih", &torrent_update_alert::new_ih) + ; +#endif + + class_, noncopyable>( + "dht_outgoing_get_peers_alert", no_init) + .def_readonly("info_hash", &dht_outgoing_get_peers_alert::info_hash) + .def_readonly("obfuscated_info_hash", &dht_outgoing_get_peers_alert::obfuscated_info_hash) +#if TORRENT_ABI_VERSION == 1 + .add_property("ip", make_getter(&dht_outgoing_get_peers_alert::ip, by_value())) +#endif + .add_property("endpoint", make_getter(&dht_outgoing_get_peers_alert::endpoint, by_value())) + ; + + class_, noncopyable>( + "log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", &log_alert::msg) +#endif + .def("log_message", &log_alert::log_message) + ; + + class_, noncopyable>( + "torrent_log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", &torrent_log_alert::msg) +#endif + .def("log_message", &torrent_log_alert::log_message) + ; + + class_, noncopyable>( + "peer_log_alert", no_init) +#if TORRENT_ABI_VERSION == 1 + .def("msg", &peer_log_alert::msg) +#endif + .def("log_message", &peer_log_alert::log_message) + ; + + class_, noncopyable>( + "picker_log_alert", no_init) + .add_property("picker_flags", &picker_log_alert::picker_flags) + .def("blocks", &picker_log_alert::blocks) + ; + + class_, noncopyable>( + "lsd_error_alert", no_init) + .def_readonly("error", &lsd_error_alert::error) + ; + + class_, noncopyable>( + "dht_stats_alert", no_init) + .add_property("active_requests", &dht_stats_active_requests) + .add_property("routing_table", &dht_stats_routing_table) + ; + + class_, noncopyable>("dht_log_alert", no_init) + .def_readonly("module", &dht_log_alert::module) + .def("log_message", &dht_log_alert::log_message) + ; + + class_, noncopyable>( + "dht_pkt_alert", no_init) + .add_property("pkt_buf", &get_pkt_buf) + ; + + class_, noncopyable>( + "dht_immutable_item_alert", no_init) + .def_readonly("target", &dht_immutable_item_alert::target) + .add_property("item", &dht_immutable_item) + ; + + class_, noncopyable>( + "dht_mutable_item_alert", no_init) + .def_readonly("key", &dht_mutable_item_alert::key) + .def_readonly("signature", &dht_mutable_item_alert::signature) + .def_readonly("seq", &dht_mutable_item_alert::seq) + .def_readonly("salt", &dht_mutable_item_alert::salt) + .add_property("item", &dht_mutable_item) + .def_readonly("authoritative", &dht_mutable_item_alert::authoritative) + ; + + class_, noncopyable>( + "dht_put_alert", no_init) + .def_readonly("target", &dht_put_alert::target) + .def_readonly("public_key", &dht_put_alert::public_key) + .def_readonly("signature", &dht_put_alert::signature) + .def_readonly("salt", &dht_put_alert::salt) + .def_readonly("seq", &dht_put_alert::seq) + .def_readonly("num_success", &dht_put_alert::num_success) + ; + + class_, noncopyable>( + "session_stats_alert", no_init) + .add_property("values", &session_stats_values) + ; + + class_, noncopyable>( + "session_stats_header_alert", no_init) + ; + + std::vector (dht_get_peers_reply_alert::*peers)() const = &dht_get_peers_reply_alert::peers; + + class_, noncopyable>( + "dht_get_peers_reply_alert", no_init) + .def_readonly("info_hash", &dht_get_peers_reply_alert::info_hash) + .def("num_peers", &dht_get_peers_reply_alert::num_peers) + .def("peers", peers) + ; + + class_, noncopyable>( + "block_uploaded_alert", no_init) + .add_property("block_index", &block_uploaded_alert::block_index) + .add_property("piece_index", make_getter((&block_uploaded_alert::piece_index), by_value())) + ; + + class_, noncopyable>( + "alerts_dropped_alert", no_init) + .add_property("dropped_alerts", &get_dropped_alerts) + ; + + class_, noncopyable>( + "socks5_alert", no_init) + .def_readonly("error", &socks5_alert::error) + .def_readonly("op", &socks5_alert::op) + .add_property("ip", make_getter(&socks5_alert::ip, by_value())) + ; + + class_, noncopyable>( + "dht_live_nodes_alert", no_init) + .add_property("node_id", &dht_live_nodes_alert::node_id) + .add_property("num_nodes", &dht_live_nodes_alert::num_nodes) + .add_property("nodes", &dht_live_nodes_nodes) + ; + + std::vector (dht_sample_infohashes_alert::*samples)() const = &dht_sample_infohashes_alert::samples; + + class_, noncopyable>( + "dht_sample_infohashes_alert", no_init) + .add_property("endpoint", make_getter(&dht_sample_infohashes_alert::endpoint, by_value())) + .add_property("interval", make_getter(&dht_sample_infohashes_alert::interval, by_value())) + .add_property("num_infohashes", &dht_sample_infohashes_alert::num_infohashes) + .add_property("num_samples", &dht_sample_infohashes_alert::num_samples) + .add_property("samples", samples) + .add_property("num_nodes", &dht_sample_infohashes_alert::num_nodes) + .add_property("nodes", &dht_sample_infohashes_nodes) + ; + + class_, noncopyable>( + "dht_bootstrap_alert", no_init) + ; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/boost_python.hpp b/bindings/python/src/boost_python.hpp new file mode 100644 index 0000000..54f5bfc --- /dev/null +++ b/bindings/python/src/boost_python.hpp @@ -0,0 +1,37 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_PYTHON_HPP +#define BOOST_PYTHON_HPP + +#include +#include +#include + +#include + +// in boost 1.60, placeholders moved into a namespace, just like std +#if BOOST_VERSION >= 106000 +using namespace boost::placeholders; +#endif + +#include +#include + +#include + +#include + +// something in here creates a define for this, presumably to make older +// versions of msvc appear to support snprintf +#ifdef snprintf +#undef snprintf +#endif + +#ifdef vsnprintf +#undef vsnprintf +#endif + +#endif + diff --git a/bindings/python/src/bytes.hpp b/bindings/python/src/bytes.hpp new file mode 100644 index 0000000..27611c0 --- /dev/null +++ b/bindings/python/src/bytes.hpp @@ -0,0 +1,22 @@ +// Copyright Arvid Norberg 2006-2013. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BYTES_HPP +#define BYTES_HPP + +#include + +struct bytes +{ + bytes(char const* s, std::size_t len): arr(s, len) {} + bytes(std::string const& s): arr(s) {} + bytes(std::string&& s): arr(std::move(s)) {} + bytes(bytes const&) = default; + bytes(bytes&&) noexcept = default; + bytes& operator=(bytes&&) & noexcept = default; + bytes() {} + std::string arr; +}; + +#endif diff --git a/bindings/python/src/converters.cpp b/bindings/python/src/converters.cpp new file mode 100644 index 0000000..68ca2cf --- /dev/null +++ b/bindings/python/src/converters.cpp @@ -0,0 +1,519 @@ +// Copyright Andrew Resch 2009. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/session_stats.hpp" // for stats_metric +#include "libtorrent/time.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/disk_interface.hpp" // for open_file_state +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/alert_types.hpp" // for picker_flags_t +#include "libtorrent/session_types.hpp" // for save_state_flags_t +#include "libtorrent/file_storage.hpp" // for file_flags_t +#include "libtorrent/alert.hpp" +#include "libtorrent/create_torrent.hpp" // for create_flags_t +#include "libtorrent/portmap.hpp" // for port_mapping_t +#include "libtorrent/peer_class.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/string_view.hpp" +#include +#include + +using namespace boost::python; +namespace bp = boost::python; + +template +struct endpoint_to_tuple +{ + static PyObject* convert(T const& ep) + { + return incref(bp::make_tuple(ep.address().to_string(), ep.port()).ptr()); + } +}; + +template +struct tuple_to_endpoint +{ + tuple_to_endpoint() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + if (!PyTuple_Check(x)) return nullptr; + if (PyTuple_Size(x) != 2) return nullptr; + extract ip(object(borrowed(PyTuple_GetItem(x, 0)))); + if (!ip.check()) return nullptr; + extract port(object(borrowed(PyTuple_GetItem(x, 1)))); + if (!port.check()) return nullptr; + lt::error_code ec; + lt::address::from_string(ip, ec); + if (ec) return nullptr; + return x; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data) + ->storage.bytes; + + object o(borrowed(x)); + data->convertible = new (storage) T(lt::address::from_string( + extract(o[0])), extract(o[1])); + } +}; + +template +struct pair_to_tuple +{ + static PyObject* convert(const std::pair& p) + { + return incref(bp::make_tuple(p.first, p.second).ptr()); + } +}; + +template +struct address_to_tuple +{ + static PyObject* convert(Addr const& addr) + { + lt::error_code ec; + return incref(bp::object(addr.to_string(ec)).ptr()); + } +}; + +template +struct tuple_to_pair +{ + tuple_to_pair() + { + converter::registry::push_back( + &convertible, &construct, type_id>() + ); + } + + static void* convertible(PyObject* x) + { + return (PyTuple_Check(x) && PyTuple_Size(x) == 2) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::pair>*)data)->storage.bytes; + + object o(borrowed(x)); + std::pair p; + p.first = extract(o[0]); + p.second = extract(o[1]); + data->convertible = new (storage) std::pair(p); + } +}; + +struct from_string_view +{ + static PyObject* convert(lt::string_view v) + { + str ret(v.data(), v.size()); + return incref(ret.ptr()); + } +}; + +struct to_string_view +{ + to_string_view() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { +#if PY_VERSION_HEX >= 0x03020000 + return PyBytes_Check(x) +#else + return PyString_Check(x) +#endif + ? x : PyUnicode_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + lt::string_view>*)data)->storage.bytes; + + if (PyUnicode_Check(x)) + { + data->convertible = new (storage) lt::string_view(PyUnicode_AS_DATA(x), PyUnicode_GET_DATA_SIZE(x)); + } + else + { + data->convertible = new (storage) lt::string_view( +#if PY_VERSION_HEX >= 0x03020000 + PyBytes_AsString(x), PyBytes_Size(x) +#else + PyString_AsString(x), PyString_Size(x) +#endif + ); + } + } +}; + +template +struct map_to_dict +{ + static PyObject* convert(Map const& m) + { + dict ret; + for (auto const& e : m) + ret[e.first] = e.second; + return incref(ret.ptr()); + } +}; + +template> +struct dict_to_map +{ + dict_to_map() + { + converter::registry::push_back( + &convertible, &construct, type_id>() + ); + } + + static void* convertible(PyObject* x) + { + return PyDict_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::map>*)data)->storage.bytes; + + dict o(borrowed(x)); + Map m; + + stl_input_iterator i(o.keys()), end; + for (; i != end; ++i) + { + T1 const& key = *i; + m[key] = extract(o[key]); + } + data->convertible = new (storage) std::map(m); + } +}; + +template +struct vector_to_list +{ + static PyObject* convert(T const& v) + { + list l; + for (int i = 0; i < int(v.size()); ++i) + { + l.append(v[i]); + } + return incref(l.ptr()); + } +}; + +template +struct list_to_vector +{ + list_to_vector() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyList_Check(x) ? x: nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + T>*)data)->storage.bytes; + + T p; + int const size = int(PyList_Size(x)); + p.reserve(size); + for (int i = 0; i < size; ++i) + { + object o(borrowed(PyList_GetItem(x, i))); + p.push_back(extract(o)); + } + data->convertible = new (storage) T(std::move(p)); + } +}; + +template +struct list_to_bitfield +{ + list_to_bitfield() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyList_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + T>*)data)->storage.bytes; + + T p; + int const size = int(PyList_Size(x)); + p.resize(size); + for (int i = 0; i < size; ++i) + { + object o(borrowed(PyList_GetItem(x, i))); + if (extract(o)) p.set_bit(IndexType{i}); + else p.clear_bit(IndexType{i}); + } + data->convertible = new (storage) T(std::move(p)); + } +}; + +template +struct bitfield_to_list +{ + static PyObject* convert(T const& v) + { + list ret; + for (auto const& i : v) + ret.append(i); + return incref(ret.ptr()); + } +}; + +template +struct from_strong_typedef +{ + using underlying_type = typename T::underlying_type; + + static PyObject* convert(const T& v) + { + object o(static_cast(v)); + return incref(o.ptr()); + } +}; + +template +struct to_strong_typedef +{ + using underlying_type = typename T::underlying_type; + + to_strong_typedef() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(extract(object(borrowed(x)))); + } +}; + +template +struct from_bitfield_flag +{ + using underlying_type = typename T::underlying_type; + + static PyObject* convert(T const v) + { + // this is because python uses "long int" to represent integral values + // internally, it cannot hold large unsigned values + auto const val = static_cast(v) + & static_cast(std::numeric_limits::max()); + object o(val); + return incref(o.ptr()); + } +}; + +template +struct to_bitfield_flag +{ + using underlying_type = typename T::underlying_type; + + to_bitfield_flag() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { + return PyNumber_Check(x) ? x : nullptr; + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + data->convertible = new (storage) T(extract(object(borrowed(x)))); + } +}; + +void bind_converters() +{ + // C++ -> python conversions + to_python_converter, pair_to_tuple>(); + to_python_converter, pair_to_tuple>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter, pair_to_tuple>(); + + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter, vector_to_list>>(); + to_python_converter>, vector_to_list>>>(); + + to_python_converter, bitfield_to_list>>(); + to_python_converter>(); + + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter>(); + to_python_converter(); + + // work-around types + to_python_converter, address_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter, endpoint_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter, endpoint_to_tuple< + lt::aux::noexcept_movable>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>, vector_to_list>>>(); + to_python_converter>>, vector_to_list>>>>(); + to_python_converter>, map_to_dict>>>(); + to_python_converter>, map_to_dict>>>(); + to_python_converter, map_to_dict>>(); + +#if TORRENT_ABI_VERSION == 1 + to_python_converter>, vector_to_list>>>(); + list_to_vector>>(); +#endif + + // python -> C++ conversions + tuple_to_pair(); + tuple_to_pair(); + tuple_to_endpoint(); + tuple_to_endpoint(); + tuple_to_pair(); + dict_to_map(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>(); + list_to_vector>>(); + + // work-around types + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>(); + list_to_vector>>>(); + dict_to_map>>(); + dict_to_map>>(); + + // bitfield types + list_to_bitfield, lt::piece_index_t>(); + list_to_bitfield(); + + bitfield_to_list>(); + bitfield_to_list(); + + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_strong_typedef(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_bitfield_flag(); + to_string_view(); +} diff --git a/bindings/python/src/create_torrent.cpp b/bindings/python/src/create_torrent.cpp new file mode 100644 index 0000000..63524ec --- /dev/null +++ b/bindings/python/src/create_torrent.cpp @@ -0,0 +1,273 @@ +// Copyright Daniel Wallin & Arvid Norberg 2009. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include "libtorrent/torrent_info.hpp" +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +namespace +{ + void set_hash(create_torrent& c, piece_index_t p, bytes const& b) + { + c.set_hash(p, sha1_hash(b.arr)); + } + + void set_file_hash(create_torrent& c, file_index_t f, bytes const& b) + { + c.set_file_hash(f, sha1_hash(b.arr)); + } + +#ifndef BOOST_NO_EXCEPTIONS + void set_piece_hashes_callback(create_torrent& c, std::string const& p + , boost::python::object cb) + { + set_piece_hashes(c, p, std::function( + [&](piece_index_t const i) { cb(i); })); + } +#else + void set_piece_hashes_callback(create_torrent& c, std::string const& p + , boost::python::object cb) + { + error_code ec; + set_piece_hashes(c, p, [&](piece_index_t const i) { cb(i); }, ec); + } + + void set_piece_hashes0(create_torrent& c, std::string const & s) + { + error_code ec; + set_piece_hashes(c, s, ec); + } +#endif + + void add_node(create_torrent& ct, std::string const& addr, int port) + { + ct.add_node(std::make_pair(addr, port)); + } + +#if TORRENT_ABI_VERSION == 1 + void add_file_deprecated(file_storage& ct, file_entry const& fe) + { + ct.add_file(fe); + } + + struct FileIter + { + using value_type = lt::file_entry; + using reference = lt::file_entry; + using pointer = lt::file_entry*; + using difference_type = int; + using iterator_category = std::forward_iterator_tag; + + FileIter(file_storage const& fs, file_index_t i) : m_fs(&fs), m_i(i) {} + FileIter(FileIter const&) = default; + FileIter() : m_fs(nullptr), m_i(0) {} + lt::file_entry operator*() const + { return m_fs->at(m_i); } + + FileIter operator++() { m_i++; return *this; } + FileIter operator++(int) { return FileIter(*m_fs, m_i++); } + + bool operator==(FileIter const& rhs) const + { return m_fs == rhs.m_fs && m_i == rhs.m_i; } + + int operator-(FileIter const& rhs) const + { + assert(rhs.m_fs == m_fs); + return m_i - rhs.m_i; + } + + FileIter& operator=(FileIter const&) = default; + + file_storage const* m_fs; + file_index_t m_i; + }; + + FileIter begin_files(file_storage const& self) + { return FileIter(self, file_index_t(0)); } + + FileIter end_files(file_storage const& self) + { return FileIter(self, self.end_file()); } + +#ifdef TORRENT_WINDOWS + void add_file_wstring(file_storage& fs, std::wstring const& file, std::int64_t size + , file_flags_t const flags, std::time_t md, std::string link) + { + fs.add_file(file, size, flags, md, link); + } +#endif +#endif // TORRENT_ABI_VERSION + + void add_files_callback(file_storage& fs, std::string const& file + , boost::python::object cb, create_flags_t const flags) + { + add_files(fs, file, [&](std::string const& i) { return cb(i); }, flags); + } + + void add_file(file_storage& fs, std::string const& file, std::int64_t size + , file_flags_t const flags, std::time_t md, std::string link) + { + fs.add_file(file, size, flags, md, link); + } + + void add_tracker(create_torrent& ct, std::string url, int tier) + { + ct.add_tracker(url, tier); + } + + struct dummy13 {}; + struct dummy14 {}; +} + +void bind_create_torrent() +{ + void (file_storage::*set_name0)(std::string const&) = &file_storage::set_name; + void (file_storage::*rename_file0)(file_index_t, std::string const&) = &file_storage::rename_file; +#if TORRENT_ABI_VERSION == 1 +#ifdef TORRENT_WINDOWS + void (file_storage::*set_name1)(std::wstring const&) = &file_storage::set_name; + void (file_storage::*rename_file1)(file_index_t, std::wstring const&) = &file_storage::rename_file; +#endif +#endif + +#ifndef BOOST_NO_EXCEPTIONS + void (*set_piece_hashes0)(create_torrent&, std::string const&) = &set_piece_hashes; +#endif + void (*add_files0)(file_storage&, std::string const&, create_flags_t) = add_files; + + std::string const& (file_storage::*file_storage_symlink)(file_index_t) const = &file_storage::symlink; + sha1_hash (file_storage::*file_storage_hash)(file_index_t) const = &file_storage::hash; + std::string (file_storage::*file_storage_file_path)(file_index_t, std::string const&) const = &file_storage::file_path; + string_view (file_storage::*file_storage_file_name)(file_index_t) const = &file_storage::file_name; + std::int64_t (file_storage::*file_storage_file_size)(file_index_t) const = &file_storage::file_size; + std::int64_t (file_storage::*file_storage_file_offset)(file_index_t) const = &file_storage::file_offset; + file_flags_t (file_storage::*file_storage_file_flags)(file_index_t) const = &file_storage::file_flags; + +#if TORRENT_ABI_VERSION == 1 + file_entry (file_storage::*at)(int) const = &file_storage::at; +#endif + + // TODO: 3 move this to its own file + { + scope s = class_("file_storage") + .def("is_valid", &file_storage::is_valid) + .def("add_file", add_file, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = "")) + .def("num_files", &file_storage::num_files) +#if TORRENT_ABI_VERSION == 1 + .def("at", at) + .def("add_file", add_file_deprecated, arg("entry")) + .def("__iter__", boost::python::range(&begin_files, &end_files)) + .def("__len__", &file_storage::num_files) +#ifdef TORRENT_WINDOWS + .def("add_file", add_file_wstring, (arg("path"), arg("size"), arg("flags") = 0, arg("mtime") = 0, arg("linkpath") = "")) +#endif +#endif // TORRENT_ABI_VERSION + .def("hash", file_storage_hash) + .def("symlink", file_storage_symlink, return_value_policy()) + .def("file_path", file_storage_file_path, (arg("idx"), arg("save_path") = "")) + .def("file_name", file_storage_file_name) + .def("file_size", file_storage_file_size) + .def("file_offset", file_storage_file_offset) + .def("file_flags", file_storage_file_flags) + + .def("total_size", &file_storage::total_size) + .def("set_num_pieces", &file_storage::set_num_pieces) + .def("num_pieces", &file_storage::num_pieces) + .def("set_piece_length", &file_storage::set_piece_length) + .def("piece_length", &file_storage::piece_length) + .def("piece_size", &file_storage::piece_size) + .def("set_name", set_name0) + .def("rename_file", rename_file0) +#if TORRENT_ABI_VERSION == 1 +#ifdef TORRENT_WINDOWS + .def("set_name", set_name1) + .def("rename_file", rename_file1) +#endif +#endif + .def("name", &file_storage::name, return_value_policy()) + ; + + s.attr("flag_pad_file") = file_storage::flag_pad_file; + s.attr("flag_hidden") = file_storage::flag_hidden; + s.attr("flag_executable") = file_storage::flag_executable; + s.attr("flag_symlink") = file_storage::flag_symlink; + } + + { + scope s = class_("file_flags_t"); + s.attr("flag_pad_file") = file_storage::flag_pad_file; + s.attr("flag_hidden") = file_storage::flag_hidden; + s.attr("flag_executable") = file_storage::flag_executable; + s.attr("flag_symlink") = file_storage::flag_symlink; + } + + { + scope s = class_("create_torrent", no_init) + .def(init()) + .def(init(arg("ti"))) + .def(init((arg("storage"), arg("piece_size") = 0 + , arg("pad_file_limit") = -1, arg("flags") = lt::create_torrent::optimize_alignment))) + + .def("generate", &create_torrent::generate) + + .def("files", &create_torrent::files, return_internal_reference<>()) + .def("set_comment", &create_torrent::set_comment) + .def("set_creator", &create_torrent::set_creator) + .def("set_hash", &set_hash) + .def("set_file_hash", &set_file_hash) + .def("add_url_seed", &create_torrent::add_url_seed) + .def("add_http_seed", &create_torrent::add_http_seed) + .def("add_node", &add_node) + .def("add_tracker", add_tracker, (arg("announce_url"), arg("tier") = 0)) + .def("set_priv", &create_torrent::set_priv) + .def("num_pieces", &create_torrent::num_pieces) + .def("piece_length", &create_torrent::piece_length) + .def("piece_size", &create_torrent::piece_size) + .def("priv", &create_torrent::priv) + .def("set_root_cert", &create_torrent::set_root_cert, (arg("pem"))) + .def("add_collection", &create_torrent::add_collection) + .def("add_similar_torrent", &create_torrent::add_similar_torrent) + + ; + + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + } + + { + scope s = class_("create_torrent_flags_t"); +#if TORRENT_ABI_VERSION == 1 + s.attr("optimize") = create_torrent::optimize; +#endif + s.attr("optimize_alignment") = create_torrent::optimize_alignment; + s.attr("merkle") = create_torrent::merkle; + s.attr("modification_time") = create_torrent::modification_time; + s.attr("symlinks") = create_torrent::symlinks; + } + + def("add_files", add_files0, (arg("fs"), arg("path"), arg("flags") = 0)); + def("add_files", add_files_callback, (arg("fs"), arg("path") + , arg("predicate"), arg("flags") = 0)); + def("set_piece_hashes", set_piece_hashes0); + def("set_piece_hashes", set_piece_hashes_callback); + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/bindings/python/src/datetime.cpp b/bindings/python/src/datetime.cpp new file mode 100644 index 0000000..869e2d9 --- /dev/null +++ b/bindings/python/src/datetime.cpp @@ -0,0 +1,138 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "optional.hpp" +#include +#include "libtorrent/time.hpp" +#include + +using namespace boost::python; + +object datetime_timedelta; +object datetime_datetime; + +template +struct chrono_duration_to_python +{ + static PyObject* convert(Duration const& d) + { + std::int64_t const us = lt::total_microseconds(d); + object result = datetime_timedelta( + 0 // days + , us / 1000000 // seconds + , us % 1000000 // microseconds + ); + + return incref(result.ptr()); + } +}; + +struct time_duration_to_python +{ + static PyObject* convert(boost::posix_time::time_duration const& d) + { + object result = datetime_timedelta( + 0 // days + , 0 // seconds + , d.total_microseconds() + ); + + return incref(result.ptr()); + } +}; + +template struct tag {}; + +lt::time_point now(::tag) +{ return lt::clock_type::now(); } + +lt::time_point32 now(::tag) +{ return lt::time_point_cast(lt::clock_type::now()); } + +template +struct time_point_to_python +{ + static PyObject* convert(T const pt) + { + using std::chrono::system_clock; + using std::chrono::duration_cast; + object result; + if (pt > T()) + { + time_t const tm = system_clock::to_time_t(system_clock::now() + + duration_cast(pt - now(::tag()))); + + std::tm* date = std::localtime(&tm); + result = datetime_datetime( + (int)1900 + date->tm_year + // tm use 0-11 and we need 1-12 + , (int)date->tm_mon + 1 + , (int)date->tm_mday + , date->tm_hour + , date->tm_min + , date->tm_sec + ); + } + else + { + result = object(); + } + return incref(result.ptr()); + } +}; + +struct ptime_to_python +{ + static PyObject* convert(boost::posix_time::ptime const& pt) + { + boost::gregorian::date date = pt.date(); + boost::posix_time::time_duration td = pt.time_of_day(); + + object result = datetime_datetime( + (int)date.year() + , (int)date.month() + , (int)date.day() + , td.hours() + , td.minutes() + , td.seconds() + ); + + return incref(result.ptr()); + } +}; + +void bind_datetime() +{ + object datetime = import("datetime").attr("__dict__"); + + datetime_timedelta = datetime["timedelta"]; + datetime_datetime = datetime["datetime"]; + + to_python_converter(); + + to_python_converter(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + to_python_converter>(); + + optional_to_python(); + optional_to_python(); +} + diff --git a/bindings/python/src/entry.cpp b/bindings/python/src/entry.cpp new file mode 100644 index 0000000..9ffdfab --- /dev/null +++ b/bindings/python/src/entry.cpp @@ -0,0 +1,179 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +struct entry_to_python +{ + static object convert(entry::list_type const& l) + { + list result; + + for (entry::list_type::const_iterator i(l.begin()), e(l.end()); i != e; ++i) + { + result.append(*i); + } + + return std::move(result); + } + + static object convert(entry::dictionary_type const& d) + { + dict result; + + for (entry::dictionary_type::const_iterator i(d.begin()), e(d.end()); i != e; ++i) + result[bytes(i->first)] = i->second; + + return std::move(result); + } + + static object convert0(entry const& e) + { + switch (e.type()) + { + case entry::int_t: + return object(e.integer()); + case entry::string_t: + return object(bytes(e.string())); + case entry::list_t: + return convert(e.list()); + case entry::dictionary_t: + return convert(e.dict()); + case entry::preformatted_t: + { + std::vector const& pre = e.preformatted(); + list l; + for (std::vector::const_iterator i = pre.begin() + , end(pre.end()); i != end; ++i) + l.append(int(*i)); + return tuple(l); + } + default: + return object(); + } + } + + static PyObject* convert(std::shared_ptr const& e) + { + if (!e) + return incref(Py_None); + return convert(*e); + } + + static PyObject* convert(entry const& e) + { + return incref(convert0(e).ptr()); + } +}; + +struct entry_from_python +{ + entry_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* e) + { + return e; + } + + static entry construct0(object e) + { + if (extract(e).check()) + { + dict d = extract(e); + list items(d.items()); + std::size_t length = extract(items.attr("__len__")()); + entry result(entry::dictionary_t); + + for (std::size_t i = 0; i < length; ++i) + { + if (extract(items[i][0]).check()) + { + result.dict().insert( + std::make_pair( + extract(items[i][0])().arr, + construct0(items[i][1]) + ) + ); + } + else + { + result.dict().insert( + std::make_pair( + extract(items[i][0])(), + construct0(items[i][1]) + ) + ); + } + } + + return result; + } + else if (extract(e).check()) + { + list l = extract(e); + + std::size_t length = extract(l.attr("__len__")()); + entry result(entry::list_t); + + for (std::size_t i = 0; i < length; ++i) + { + result.list().push_back(construct0(l[i])); + } + + return result; + } + else if (extract(e).check()) + { + return entry(extract(e)().arr); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + return entry(extract(e)()); + } + else if (extract(e).check()) + { + tuple t = extract(e); + + std::size_t const length = extract(t.attr("__len__")()); + std::vector preformatted(length); + for (std::size_t i = 0; i < length; ++i) + { + preformatted[i] = char(extract(t[i])); + } + + return entry(preformatted); + } + + return entry(); + } + + static void construct(PyObject* e, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + new (storage) entry(construct0(object(borrowed(e)))); + data->convertible = storage; + } +}; + +void bind_entry() +{ + to_python_converter, entry_to_python>(); + to_python_converter(); + entry_from_python(); +} + diff --git a/bindings/python/src/error_code.cpp b/bindings/python/src/error_code.cpp new file mode 100644 index 0000000..1808a84 --- /dev/null +++ b/bindings/python/src/error_code.cpp @@ -0,0 +1,219 @@ +/* + +Copyright (c) 2011, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "boost_python.hpp" +#include +#include +#include +#include + +namespace boost +{ + // this fixe mysterious link error on msvc + template <> + inline boost::system::error_category const volatile* + get_pointer(class boost::system::error_category const volatile* p) + { + return p; + } +} + +#include +#if defined TORRENT_USE_OPENSSL +#include +#endif +#if TORRENT_USE_I2P +#include +#endif + +using namespace boost::python; +using namespace lt; +using boost::system::error_category; + +namespace { + + struct ec_pickle_suite : boost::python::pickle_suite + { + static boost::python::tuple + getinitargs(error_code const&) + { + return boost::python::tuple(); + } + + static boost::python::tuple + getstate(error_code const& ec) + { + return boost::python::make_tuple(ec.value(), ec.category().name()); + } + + static void + setstate(error_code& ec, boost::python::tuple state) + { + using namespace boost::python; + if (len(state) != 2) + { + PyErr_SetObject(PyExc_ValueError, + ("expected 2-item tuple in call to __setstate__; got %s" + % state).ptr()); + throw_error_already_set(); + } + + int const value = extract(state[0]); + std::string const category = extract(state[1]); + if (category == "system") + ec.assign(value, lt::system_category()); + else if (category == "generic") + ec.assign(value, lt::generic_category()); + else if (category == "libtorrent") + ec.assign(value, lt::libtorrent_category()); + else if (category == "http error") + ec.assign(value, lt::http_category()); + else if (category == "UPnP error") + ec.assign(value, lt::upnp_category()); + else if (category == "bdecode error") + ec.assign(value, lt::bdecode_category()); + else if (category == "asio.netdb") + ec.assign(value, boost::asio::error::get_netdb_category()); + else if (category == "asio.addinfo") + ec.assign(value, boost::asio::error::get_addrinfo_category()); + else if (category == "asio.misc") + ec.assign(value, boost::asio::error::get_misc_category()); + else if (category == "asio.misc") + ec.assign(value, boost::asio::error::get_misc_category()); +#if defined TORRENT_USE_OPENSSL + else if (category == "asio.ssl") + ec.assign(value, boost::asio::error::get_ssl_category()); +#endif + else + { + PyErr_SetObject(PyExc_ValueError, + ("unexpected error_category passed to __setstate__; got '%s'" + % object(category)).ptr()); + throw_error_already_set(); + } + } + }; +} + +struct category_holder +{ + category_holder(boost::system::error_category const& cat) : m_cat(&cat) {} + char const* name() const { return m_cat->name(); } + std::string message(int const v) const { return m_cat->message(v); } + + friend bool operator==(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat == *rhs.m_cat; } + + friend bool operator!=(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat != *rhs.m_cat; } + + friend bool operator<(category_holder const lhs, category_holder const rhs) + { return *lhs.m_cat < *rhs.m_cat; } + + boost::system::error_category const& ref() const { return *m_cat; } + operator boost::system::error_category const&() const { return *m_cat; } +private: + boost::system::error_category const* m_cat; +}; + +void error_code_assign(boost::system::error_code& me, int const v, category_holder const cat) +{ + me.assign(v, cat.ref()); +} + +category_holder error_code_category(boost::system::error_code const& me) +{ + return category_holder(me.category()); +} + +#define WRAP_CAT(name) \ + category_holder wrap_ ##name## _category() { return category_holder(name## _category()); } + +WRAP_CAT(libtorrent) +WRAP_CAT(upnp) +WRAP_CAT(http) +WRAP_CAT(socks) +WRAP_CAT(bdecode) +#if TORRENT_USE_I2P +WRAP_CAT(i2p) +#endif +WRAP_CAT(generic) +WRAP_CAT(system) + +#undef WRAP_CAT + +void bind_error_code() +{ + class_("error_category", no_init) + .def("name", &category_holder::name) + .def("message", &category_holder::message) + .def(self == self) + .def(self < self) + .def(self != self) + ; + + class_("error_code") + .def(init<>()) + .def(init()) + .def("message", static_cast(&error_code::message)) + .def("value", &error_code::value) + .def("clear", &error_code::clear) + .def("category", &error_code_category) + .def("assign", &error_code_assign) + .def_pickle(ec_pickle_suite()) + ; + + def("libtorrent_category", &wrap_libtorrent_category); + def("upnp_category", &wrap_upnp_category); + def("http_category", &wrap_http_category); + def("socks_category", &wrap_socks_category); + def("bdecode_category", &wrap_bdecode_category); +#if TORRENT_USE_I2P + def("i2p_category", &wrap_i2p_category); +#endif + +#if TORRENT_ABI_VERSION == 1 + def("get_libtorrent_category", &wrap_libtorrent_category); + def("get_upnp_category", &wrap_upnp_category); + def("get_http_category", &wrap_http_category); + def("get_socks_category", &wrap_socks_category); + def("get_bdecode_category", &wrap_bdecode_category); +#if TORRENT_USE_I2P + def("get_i2p_category", &wrap_i2p_category); +#endif +#endif // TORRENT_ABI_VERSION + + def("generic_category", &wrap_generic_category); + + def("system_category", &wrap_system_category); +} + diff --git a/bindings/python/src/fingerprint.cpp b/bindings/python/src/fingerprint.cpp new file mode 100644 index 0000000..e9be891 --- /dev/null +++ b/bindings/python/src/fingerprint.cpp @@ -0,0 +1,51 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +void bind_fingerprint() +{ + using namespace boost::python; + using namespace lt; + + def("generate_fingerprint", &generate_fingerprint); + +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable: 4996) +#endif + class_("fingerprint", no_init) + .def( + init( + (arg("id"), "major", "minor", "revision", "tag") + ) + ) + .def("__str__", &fingerprint::to_string) + .def_readonly("name", &fingerprint::name) + .def_readonly("major_version", &fingerprint::major_version) + .def_readonly("minor_version", &fingerprint::minor_version) + .def_readonly("revision_version", &fingerprint::revision_version) + .def_readonly("tag_version", &fingerprint::tag_version) + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif // TORRENT_ABI_VERSION +} diff --git a/bindings/python/src/gil.hpp b/bindings/python/src/gil.hpp new file mode 100644 index 0000000..e2da538 --- /dev/null +++ b/bindings/python/src/gil.hpp @@ -0,0 +1,92 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef GIL_070107_HPP +# define GIL_070107_HPP + +#include + +# include +# include +# include +# include + +#include + +//namespace libtorrent { namespace python { + +// RAII helper to release GIL. +struct allow_threading_guard +{ + allow_threading_guard() : save(PyEval_SaveThread()) {} + ~allow_threading_guard() { PyEval_RestoreThread(save); } + PyThreadState* save; +}; + +struct lock_gil +{ + lock_gil() : state(PyGILState_Ensure()) {} + ~lock_gil() { PyGILState_Release(state); } + PyGILState_STATE state; +}; + +template +struct allow_threading +{ + allow_threading(F fn) : fn(fn) {} + template + R operator()(Self&& s, Args&&... args) + { + allow_threading_guard guard; + return (std::forward(s).*fn)(std::forward(args)...); + } + F fn; +}; + +template +struct visitor : boost::python::def_visitor> +{ + visitor(F fn) : fn(fn) {} + + template + void visit_aux( + Class& cl, char const* name + , Options const& options, Signature const& signature) const + { + typedef typename boost::mpl::at_c::type return_type; + + cl.def( + name + , boost::python::make_function( + allow_threading(fn) + , options.policies() + , options.keywords() + , signature + ) + ); + } + + template + void visit(Class& cl, char const* name, Options const& options) const + { + this->visit_aux( + cl, name, options + , boost::python::detail::get_signature(fn, (typename Class::wrapped_type*)0) + ); + } + + F fn; +}; + +// Member function adaptor that releases and aqcuires the GIL +// around the function call. +template +visitor allow_threads(F fn) +{ + return visitor(fn); +} + +//}} // namespace libtorrent::python + +#endif // GIL_070107_HPP diff --git a/bindings/python/src/ip_filter.cpp b/bindings/python/src/ip_filter.cpp new file mode 100644 index 0000000..2f586d4 --- /dev/null +++ b/bindings/python/src/ip_filter.cpp @@ -0,0 +1,32 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +namespace +{ + void add_rule(ip_filter& filter, std::string start, std::string end, int flags) + { + return filter.add_rule(address::from_string(start), address::from_string(end), flags); + } + + int access0(ip_filter& filter, std::string addr) + { + return filter.access(address::from_string(addr)); + } +} + +void bind_ip_filter() +{ + class_("ip_filter") + .def("add_rule", add_rule) + .def("access", access0) + .def("export_filter", allow_threads(&ip_filter::export_filter)) + ; +} diff --git a/bindings/python/src/magnet_uri.cpp b/bindings/python/src/magnet_uri.cpp new file mode 100644 index 0000000..130fac0 --- /dev/null +++ b/bindings/python/src/magnet_uri.cpp @@ -0,0 +1,93 @@ +// Copyright Andrew Resch 2008. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt + +#include "boost_python.hpp" +#include "bytes.hpp" +#include +#include +#include +#include "gil.hpp" +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +extern void dict_to_add_torrent_params(dict params, add_torrent_params& p); + +namespace { + +#if TORRENT_ABI_VERSION == 1 + torrent_handle _add_magnet_uri(lt::session& s, std::string uri, dict params) + { + add_torrent_params p; + + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + p.url = uri; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(p); +#else + error_code ec; + return s.add_torrent(p, ec); +#endif + } +#endif + + dict parse_magnet_uri_dict(std::string const& uri) + { + error_code ec; + add_torrent_params p = parse_magnet_uri(uri, ec); + + if (ec) throw system_error(ec); + + dict ret; + + if (p.ti) ret["ti"] = p.ti; + list tracker_list; + for (std::vector::const_iterator i = p.trackers.begin() + , end(p.trackers.end()); i != end; ++i) + tracker_list.append(*i); + ret["trackers"] = tracker_list; + + list nodes_list; + for (auto const& i : p.dht_nodes) + nodes_list.append(boost::python::make_tuple(i.first, i.second)); + ret["dht_nodes"] = nodes_list; + ret["info_hash"] = bytes(p.info_hash.to_string()); + ret["name"] = p.name; + ret["save_path"] = p.save_path; + ret["storage_mode"] = p.storage_mode; +#if TORRENT_ABI_VERSION == 1 + ret["url"] = p.url; + ret["uuid"] = p.uuid; +#endif + ret["flags"] = p.flags; + return ret; + } + + add_torrent_params parse_magnet_uri_wrap(std::string const& uri) + { + error_code ec; + add_torrent_params p = parse_magnet_uri(uri, ec); + if (ec) throw system_error(ec); + return p; + } + + std::string (*make_magnet_uri0)(torrent_handle const&) = make_magnet_uri; + std::string (*make_magnet_uri1)(torrent_info const&) = make_magnet_uri; +} + +void bind_magnet_uri() +{ +#if TORRENT_ABI_VERSION == 1 + def("add_magnet_uri", &_add_magnet_uri); +#endif + def("make_magnet_uri", make_magnet_uri0); + def("make_magnet_uri", make_magnet_uri1); + def("parse_magnet_uri", parse_magnet_uri_wrap); + def("parse_magnet_uri_dict", parse_magnet_uri_dict); +} diff --git a/bindings/python/src/module.cpp b/bindings/python/src/module.cpp new file mode 100644 index 0000000..baa5722 --- /dev/null +++ b/bindings/python/src/module.cpp @@ -0,0 +1,56 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifdef __GNUC__ +#define BOOST_PYTHON_USE_GCC_SYMBOL_VISIBILITY 1 +#endif + +#include +#include "libtorrent/config.hpp" + +void bind_utility(); +void bind_fingerprint(); +void bind_sha1_hash(); +void bind_session(); +void bind_entry(); +void bind_torrent_info(); +void bind_unicode_string_conversion(); +void bind_torrent_handle(); +void bind_torrent_status(); +void bind_session_settings(); +void bind_version(); +void bind_alert(); +void bind_datetime(); +void bind_peer_info(); +void bind_ip_filter(); +void bind_magnet_uri(); +void bind_converters(); +void bind_create_torrent(); +void bind_error_code(); + +BOOST_PYTHON_MODULE(libtorrent) +{ + Py_Initialize(); + PyEval_InitThreads(); + + bind_converters(); + bind_error_code(); + bind_utility(); + bind_fingerprint(); + bind_sha1_hash(); + bind_entry(); + bind_torrent_handle(); + bind_session(); + bind_torrent_info(); + bind_unicode_string_conversion(); + bind_torrent_status(); + bind_session_settings(); + bind_version(); + bind_alert(); + bind_datetime(); + bind_peer_info(); + bind_ip_filter(); + bind_magnet_uri(); + bind_create_torrent(); +} diff --git a/bindings/python/src/optional.hpp b/bindings/python/src/optional.hpp new file mode 100644 index 0000000..2477924 --- /dev/null +++ b/bindings/python/src/optional.hpp @@ -0,0 +1,31 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef OPTIONAL_070108_HPP +# define OPTIONAL_070108_HPP + +# include "boost_python.hpp" +# include + +template +struct optional_to_python +{ + optional_to_python() + { + boost::python::to_python_converter< + boost::optional, optional_to_python + >(); + } + + static PyObject* convert(boost::optional const& x) + { + if (!x) + return boost::python::incref(Py_None); + + return boost::python::incref(boost::python::object(*x).ptr()); + } +}; + +#endif // OPTIONAL_070108_HPP + diff --git a/bindings/python/src/peer_info.cpp b/bindings/python/src/peer_info.cpp new file mode 100644 index 0000000..bed2617 --- /dev/null +++ b/bindings/python/src/peer_info.cpp @@ -0,0 +1,158 @@ +// Copyright Daniel Wallin 2007. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include "bytes.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace lt; + +std::int64_t get_last_active(peer_info const& pi) +{ + return total_seconds(pi.last_active); +} + +std::int64_t get_last_request(peer_info const& pi) +{ + return total_seconds(pi.last_request); +} + +std::int64_t get_download_queue_time(peer_info const& pi) +{ + return total_seconds(pi.download_queue_time); +} + +tuple get_local_endpoint(peer_info const& pi) +{ + return boost::python::make_tuple(pi.local_endpoint.address().to_string(), pi.local_endpoint.port()); +} + +tuple get_ip(peer_info const& pi) +{ + return boost::python::make_tuple(pi.ip.address().to_string(), pi.ip.port()); +} + +list get_pieces(peer_info const& pi) +{ + list ret; + + for (bitfield::const_iterator i = pi.pieces.begin() + , end(pi.pieces.end()); i != end; ++i) + { + ret.append(*i); + } + return ret; +} + +bytes get_peer_info_client(peer_info const& pi) +{ + return pi.client; +} + +using by_value = return_value_policy; +void bind_peer_info() +{ + scope pi = class_("peer_info") + .add_property("flags", make_getter(&peer_info::flags, by_value())) + .add_property("source", make_getter(&peer_info::source, by_value())) + .add_property("read_state", make_getter(&peer_info::read_state, by_value())) + .add_property("write_state", make_getter(&peer_info::write_state, by_value())) + .add_property("ip", get_ip) + .def_readonly("up_speed", &peer_info::up_speed) + .def_readonly("down_speed", &peer_info::down_speed) + .def_readonly("payload_up_speed", &peer_info::payload_up_speed) + .def_readonly("payload_down_speed", &peer_info::payload_down_speed) + .def_readonly("total_download", &peer_info::total_download) + .def_readonly("total_upload", &peer_info::total_upload) + .def_readonly("pid", &peer_info::pid) + .add_property("pieces", get_pieces) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("upload_limit", &peer_info::upload_limit) + .def_readonly("download_limit", &peer_info::download_limit) + .def_readonly("load_balancing", &peer_info::load_balancing) + .def_readonly("remote_dl_rate", &peer_info::remote_dl_rate) +#endif + .add_property("last_request", get_last_request) + .add_property("last_active", get_last_active) + .add_property("download_queue_time", get_download_queue_time) + .def_readonly("queue_bytes", &peer_info::queue_bytes) + .def_readonly("request_timeout", &peer_info::request_timeout) + .def_readonly("send_buffer_size", &peer_info::send_buffer_size) + .def_readonly("used_send_buffer", &peer_info::used_send_buffer) + .def_readonly("receive_buffer_size", &peer_info::receive_buffer_size) + .def_readonly("used_receive_buffer", &peer_info::used_receive_buffer) + .def_readonly("num_hashfails", &peer_info::num_hashfails) + .def_readonly("download_queue_length", &peer_info::download_queue_length) + .def_readonly("upload_queue_length", &peer_info::upload_queue_length) + .def_readonly("failcount", &peer_info::failcount) + .add_property("downloading_piece_index", make_getter(&peer_info::downloading_piece_index, by_value())) + .add_property("downloading_block_index", make_getter(&peer_info::downloading_block_index, by_value())) + .def_readonly("downloading_progress", &peer_info::downloading_progress) + .def_readonly("downloading_total", &peer_info::downloading_total) + .add_property("client", get_peer_info_client) + .def_readonly("connection_type", &peer_info::connection_type) + .def_readonly("pending_disk_bytes", &peer_info::pending_disk_bytes) + .def_readonly("send_quota", &peer_info::send_quota) + .def_readonly("receive_quota", &peer_info::receive_quota) + .def_readonly("rtt", &peer_info::rtt) + .def_readonly("num_pieces", &peer_info::num_pieces) + .def_readonly("download_rate_peak", &peer_info::download_rate_peak) + .def_readonly("upload_rate_peak", &peer_info::upload_rate_peak) + .def_readonly("progress", &peer_info::progress) + .def_readonly("progress_ppm", &peer_info::progress_ppm) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("estimated_reciprocation_rate", &peer_info::estimated_reciprocation_rate) +#endif + .add_property("local_endpoint", get_local_endpoint) + ; + + // flags + pi.attr("interesting") = peer_info::interesting; + pi.attr("choked") = peer_info::choked; + pi.attr("remote_interested") = peer_info::remote_interested; + pi.attr("remote_choked") = peer_info::remote_choked; + pi.attr("supports_extensions") = peer_info::supports_extensions; + pi.attr("local_connection") = peer_info::local_connection; + pi.attr("handshake") = peer_info::handshake; + pi.attr("connecting") = peer_info::connecting; +#if TORRENT_ABI_VERSION == 1 + pi.attr("queued") = peer_info::queued; +#endif + pi.attr("on_parole") = peer_info::on_parole; + pi.attr("seed") = peer_info::seed; + pi.attr("optimistic_unchoke") = peer_info::optimistic_unchoke; + pi.attr("snubbed") = peer_info::snubbed; + pi.attr("upload_only") = peer_info::upload_only; + pi.attr("endgame_mode") = peer_info::endgame_mode; + pi.attr("holepunched") = peer_info::holepunched; +#ifndef TORRENT_DISABLE_ENCRYPTION + pi.attr("rc4_encrypted") = peer_info::rc4_encrypted; + pi.attr("plaintext_encrypted") = peer_info::plaintext_encrypted; +#endif + + // connection_type + pi.attr("standard_bittorrent") = (int)peer_info::standard_bittorrent; + pi.attr("web_seed") = (int)peer_info::web_seed; + + // source + pi.attr("tracker") = peer_info::tracker; + pi.attr("dht") = peer_info::dht; + pi.attr("pex") = peer_info::pex; + pi.attr("lsd") = peer_info::lsd; + pi.attr("resume_data") = peer_info::resume_data; + + // read/write state + pi.attr("bw_idle") = peer_info::bw_idle; +#if TORRENT_ABI_VERSION == 1 + pi.attr("bw_torrent") = peer_info::bw_torrent; + pi.attr("bw_global") = peer_info::bw_global; +#endif + pi.attr("bw_limit") = peer_info::bw_limit; + pi.attr("bw_network") = peer_info::bw_network; + pi.attr("bw_disk") = peer_info::bw_disk; +} + diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp new file mode 100644 index 0000000..9a380d8 --- /dev/null +++ b/bindings/python/src/session.cpp @@ -0,0 +1,1240 @@ +// Copyright Daniel Wallin, Arvid Norberg 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for sign_mutable_item +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace boost +{ + // this fixes mysterious link error on msvc + template <> + inline lt::alert const volatile* + get_pointer(class lt::alert const volatile* p) + { + return p; + } +} + +#include "gil.hpp" +#include "bytes.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +using namespace boost::python; +using namespace lt; + +namespace +{ +#if TORRENT_ABI_VERSION == 1 + struct dummy {}; + + void listen_on(lt::session& s, int min_, int max_, char const* interface, int flags) + { + allow_threading_guard guard; + error_code ec; + s.listen_on(std::make_pair(min_, max_), ec, interface, flags); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw system_error(ec); +#endif + } + + void outgoing_ports(lt::session& s, int _min, int _max) + { + allow_threading_guard guard; + settings_pack p; + p.set_int(settings_pack::outgoing_port, _min); + p.set_int(settings_pack::num_outgoing_ports, _max - _min); + s.apply_settings(p); + return; + } +#endif // TORRENT_ABI_VERSION + +#ifndef TORRENT_DISABLE_DHT + void add_dht_node(lt::session& s, tuple n) + { + std::string ip = extract(n[0]); + int port = extract(n[1]); + allow_threading_guard guard; + s.add_dht_node(std::make_pair(ip, port)); + } + +#if TORRENT_ABI_VERSION == 1 + void add_dht_router(lt::session& s, std::string router_, int port_) + { + allow_threading_guard guard; + return s.add_dht_router(std::make_pair(router_, port_)); + } +#endif + +#endif // TORRENT_DISABLE_DHT + + void add_extension(lt::session& s, object const& e) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (!extract(e).check()) return; + + std::string name = extract(e); + if (name == "ut_metadata") + s.add_extension(create_ut_metadata_plugin); + else if (name == "ut_pex") + s.add_extension(create_ut_pex_plugin); + else if (name == "smart_ban") + s.add_extension(create_smart_ban_plugin); + +#endif // TORRENT_DISABLE_EXTENSIONS + } + + void make_settings_pack(lt::settings_pack& p, dict const& sett_dict) + { + stl_input_iterator i(sett_dict.keys()), end; + for (; i != end; ++i) + { + std::string const key = *i; + + int sett = setting_by_name(key); + if (sett < 0) + { + PyErr_SetString(PyExc_KeyError, ("unknown name in settings_pack: " + key).c_str()); + throw_error_already_set(); + } + + TORRENT_TRY + { + object const value = sett_dict[key]; + switch (sett & settings_pack::type_mask) + { + case settings_pack::string_type_base: + p.set_str(sett, extract(value)); + break; + case settings_pack::int_type_base: + p.set_int(sett, extract(value)); + break; + case settings_pack::bool_type_base: + p.set_bool(sett, extract(value)); + break; + } + } + TORRENT_CATCH(...) {} + } + } + + dict make_dict(lt::settings_pack const& sett) + { + dict ret; + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) + { + // deprecated settings are still here, they just have empty names + char const* name = name_for_setting(i); + if (name[0] != '\0') ret[name] = sett.get_str(i); + } + + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + char const* name = name_for_setting(i); + if (name[0] != '\0') ret[name] = sett.get_int(i); + } + + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + char const* name = name_for_setting(i); + if (name[0] != '\0') ret[name] = sett.get_bool(i); + } + return ret; + } + + std::shared_ptr make_session(boost::python::dict sett, session_flags_t flags) + { + settings_pack p; + make_settings_pack(p, sett); + return std::make_shared(p, flags); + } + + void session_apply_settings(lt::session& ses, dict const& sett_dict) + { + settings_pack p; + make_settings_pack(p, sett_dict); + allow_threading_guard guard; + ses.apply_settings(p); + } + + dict session_get_settings(lt::session const& ses) + { + settings_pack sett; + { + allow_threading_guard guard; + sett = ses.get_settings(); + } + return make_dict(sett); + } + + dict min_memory_usage_wrapper() + { + settings_pack ret = min_memory_usage(); + return make_dict(ret); + } + + dict default_settings_wrapper() + { + return make_dict(default_settings()); + } + + dict high_performance_seed_wrapper() + { + settings_pack ret = high_performance_seed(); + return make_dict(ret); + } + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + torrent_handle add_torrent_depr(lt::session& s, torrent_info const& ti + , std::string const& save, entry const& resume + , storage_mode_t storage_mode, bool paused) + { + allow_threading_guard guard; + return s.add_torrent(ti, save, resume, storage_mode, paused, default_storage_constructor); + } +#endif +#endif +} + + void dict_to_add_torrent_params(dict params, add_torrent_params& p) + { + list items = params.items(); + int const len = int(boost::python::len(items)); + for (int i = 0; i < len; i++) + { + boost::python::api::object_item item = items[i]; + std::string const key = extract(item[0]); + object const value = item[1]; + // torrent_info objects are always held by a shared_ptr in the + // python binding, skip it if it is a object + if (key == "ti" && value != boost::python::object()) + { + // make a copy here. We don't want to end up holding a python-owned + // object inside libtorrent. If the last reference goes out of scope + // on the C++ side, it will end up freeing the python object + // without holding the GIL and likely crash. + // https://mail.python.org/pipermail/cplusplus-sig/2007-June/012130.html + p.ti = std::make_shared( + extract(value)); + continue; + } + else if(key == "info_hash") + { + p.info_hash = sha1_hash( + bytes(extract(value)).arr.data()); + continue; + } + else if(key == "name") + { + p.name = extract(value); + continue; + } + else if(key == "save_path") + { + p.save_path = extract(value); + continue; + } +#if TORRENT_ABI_VERSION == 1 + else if(key == "resume_data") + { + std::string resume = extract(value); + p.resume_data.assign(resume.begin(), resume.end()); + continue; + } + else if(key == "uuid") + { + p.uuid = extract(value); + continue; + } +#endif + else if(key == "storage_mode") + { + p.storage_mode = extract(value); + continue; + } + else if(key == "trackers") + { + p.trackers = extract>(value); + continue; + } + else if(key == "url_seeds") + { + p.url_seeds = extract>(value); + continue; + } + else if(key == "http_seeds") + { + p.http_seeds = + extract(value); + continue; + } + else if(key == "dht_nodes") + { + p.dht_nodes = + extract>>(value); + continue; + } + else if(key == "banned_peers") + { + p.banned_peers = extract>(value); + continue; + } + else if(key == "peers") + { + p.peers = extract>(value); + continue; + } + else if(key == "flags") + { + p.flags = extract(value); + continue; + } + else if(key == "trackerid") + { + p.trackerid = extract(value); + continue; + } +#if TORRENT_ABI_VERSION == 1 + else if(key == "url") + { + p.url = extract(value); + continue; + } +#endif + else if(key == "renamed_files") + { + p.renamed_files = + extract>(value); + } + else if(key == "file_priorities") + { + p.file_priorities = extract>(value); + } + else + { + PyErr_SetString(PyExc_KeyError, + ("unknown name in torrent params: " + key).c_str()); + throw_error_already_set(); + } + } + } + +namespace +{ + + torrent_handle add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + +#ifndef BOOST_NO_EXCEPTIONS + return s.add_torrent(p); +#else + error_code ec; + return s.add_torrent(p, ec); +#endif + } + + void async_add_torrent(lt::session& s, dict params) + { + add_torrent_params p; + dict_to_add_torrent_params(params, p); + + allow_threading_guard guard; + + s.async_add_torrent(p); + } + +#if TORRENT_ABI_VERSION == 1 + void start_natpmp(lt::session& s) + { + allow_threading_guard guard; + s.start_natpmp(); + } + + void start_upnp(lt::session& s) + { + allow_threading_guard guard; + s.start_upnp(); + } +#endif // TORRENT_ABI_VERSION + + void alert_notify(object cb) try + { + lock_gil lock; + if (cb) + { + cb(); + } + } + catch (boost::python::error_already_set const&) + { + // this callback isn't supposed to throw an error. + // just swallow and ignore the exception + TORRENT_ASSERT_FAIL_VAL("python notify callback threw exception"); + } + + void set_alert_notify(lt::session& s, object cb) + { + s.set_alert_notify(std::bind(&alert_notify, cb)); + } + +#ifdef TORRENT_WINDOWS + void alert_socket_notify(SOCKET const fd) + { + std::uint8_t dummy = 0; + ::send(fd, reinterpret_cast(&dummy), 1, 0); + } +#endif + + void alert_fd_notify(int const fd) + { + std::uint8_t dummy = 0; + while (::write(fd, &dummy, 1) < 0 && errno == EINTR); + } + + void set_alert_fd(lt::session& s, std::intptr_t const fd) + { +#ifdef TORRENT_WINDOWS + auto const sock = static_cast(fd); + int res; + int res_size = sizeof(res); + if (sock != INVALID_SOCKET + && ::getsockopt(sock, SOL_SOCKET, SO_ERROR, + (char *)&res, &res_size) == 0) + { + s.set_alert_notify(std::bind(&alert_socket_notify, sock)); + } + else +#endif + { + s.set_alert_notify(std::bind(&alert_fd_notify, fd)); + } + } + + alert const* + wait_for_alert(lt::session& s, int ms) + { + alert const* a; + { + allow_threading_guard guard; + a = s.wait_for_alert(milliseconds(ms)); + } + return a; + } + + list get_torrents(lt::session& s) + { + std::vector torrents; + { + allow_threading_guard guard; + torrents = s.get_torrents(); + } + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + bool wrap_pred(object pred, torrent_status const& st) + { + return pred(st); + } + + list get_torrent_status(lt::session& s, object pred, int const flags) + { + std::vector torrents + = s.get_torrent_status(std::bind(&wrap_pred, pred, std::placeholders::_1), status_flags_t(flags)); + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + list refresh_torrent_status(lt::session& s, list in_torrents, int const flags) + { + std::vector torrents; + int const n = int(boost::python::len(in_torrents)); + for (int i = 0; i < n; ++i) + torrents.push_back(extract(in_torrents[i])); + + { + allow_threading_guard guard; + s.refresh_torrent_status(&torrents, status_flags_t(flags)); + } + + list ret; + for (std::vector::iterator i = torrents.begin(); i != torrents.end(); ++i) + ret.append(*i); + return ret; + } + + cache_status get_cache_info1(lt::session& s, torrent_handle h, int flags) + { + cache_status ret; + s.get_cache_info(&ret, h, flags); + return ret; + } + + list cached_piece_info_list(std::vector const& v) + { + list pieces; + time_point now = clock_type::now(); + for (std::vector::const_iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + dict d; + d["piece"] = i->piece; + d["last_use"] = total_milliseconds(now - i->last_use) / 1000.f; + d["next_to_hash"] = i->next_to_hash; + d["kind"] = static_cast(i->kind); + pieces.append(d); + } + return pieces; + } + + list cache_status_pieces(cache_status const& cs) + { + return cached_piece_info_list(cs.pieces); + } + +#if TORRENT_ABI_VERSION == 1 + cache_status get_cache_status(lt::session& s) + { + cache_status ret; + s.get_cache_info(&ret); + return ret; + } + + dict get_utp_stats(session_status const& st) + { + dict ret; + ret["num_idle"] = st.utp_stats.num_idle; + ret["num_syn_sent"] = st.utp_stats.num_syn_sent; + ret["num_connected"] = st.utp_stats.num_connected; + ret["num_fin_sent"] = st.utp_stats.num_fin_sent; + ret["num_close_wait"] = st.utp_stats.num_close_wait; + return ret; + } + + list get_cache_info2(lt::session& ses, sha1_hash ih) + { + std::vector ret; + + { + allow_threading_guard guard; + ses.get_cache_info(ih, ret); + } + + return cached_piece_info_list(ret); + } +#endif + + entry save_state(lt::session const& s, std::uint32_t const flags) + { + allow_threading_guard guard; + entry e; + s.save_state(e, save_state_flags_t(flags)); + return e; + } + + list pop_alerts(lt::session& ses) + { + std::vector alerts; + { + allow_threading_guard guard; + ses.pop_alerts(&alerts); + } + + list ret; + for (alert* a : alerts) + { + ret.append(boost::python::ptr(a)); + } + return ret; + } + + void load_state(lt::session& ses, entry const& st, std::uint32_t const flags) + { + allow_threading_guard guard; + + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node e; + error_code ec; + bdecode(&buf[0], &buf[0] + buf.size(), e, ec); + TORRENT_ASSERT(!ec); + ses.load_state(e, save_state_flags_t(flags)); + } + + dict get_peer_class(lt::session& ses, lt::peer_class_t const pc) + { + lt::peer_class_info pci; + { + allow_threading_guard guard; + pci = ses.get_peer_class(pc); + } + dict ret; + ret["ignore_unchoke_slots"] = pci.ignore_unchoke_slots; + ret["connection_limit_factor"] = pci.connection_limit_factor; + ret["label"] = pci.label; + ret["upload_limit"] = pci.upload_limit; + ret["download_limit"] = pci.download_limit; + ret["upload_priority"] = pci.upload_priority; + ret["download_priority"] = pci.download_priority; + return ret; + } + + void set_peer_class(lt::session& ses, peer_class_t const pc, dict info) + { + lt::peer_class_info pci; + stl_input_iterator i(info.keys()), end; + for (; i != end; ++i) + { + std::string const key = *i; + + object const value = info[key]; + if (key == "ignore_unchoke_slots") + { + pci.ignore_unchoke_slots = extract(value); + } + else if (key == "connection_limit_factor") + { + pci.connection_limit_factor = extract(value); + } + else if (key == "label") + { + pci.label = extract(value); + } + else if (key == "upload_limit") + { + pci.upload_limit = extract(value); + } + else if (key == "download_limit") + { + pci.download_limit = extract(value); + } + else if (key == "upload_priority") + { + pci.upload_priority = extract(value); + } + else if (key == "download_priority") + { + pci.download_priority = extract(value); + } + else + { + PyErr_SetString(PyExc_KeyError, ("unknown name in peer_class_info: " + key).c_str()); + throw_error_already_set(); + } + } + + allow_threading_guard guard; + ses.set_peer_class(pc, pci); + } + +#ifndef TORRENT_DISABLE_DHT + void dht_get_mutable_item(lt::session& ses, std::string key, std::string salt) + { + TORRENT_ASSERT(key.size() == 32); + std::array public_key; + std::copy(key.begin(), key.end(), public_key.begin()); + ses.dht_get_item(public_key, salt); + } + + void put_string(entry& e, std::array& sig, std::int64_t& seq + , std::string const& salt, std::string pk, std::string sk + , std::string data) + { + using lt::dht::sign_mutable_item; + + e = data; + std::vector buf; + bencode(std::back_inserter(buf), e); + ++seq; + dht::signature sign = sign_mutable_item(buf, salt + , dht::sequence_number(seq) + , dht::public_key(pk.data()) + , dht::secret_key(sk.data())); + sig = sign.bytes; + } + + void dht_put_mutable_item(lt::session& ses, std::string private_key, std::string public_key, + std::string data, std::string salt) + { + TORRENT_ASSERT(private_key.size() == 64); + TORRENT_ASSERT(public_key.size() == 32); + std::array key; + std::copy(public_key.begin(), public_key.end(), key.begin()); + ses.dht_put_item(key, [&](entry& e, std::array& sig, std::int64_t& seq + , std::string const& salt) { put_string(e, sig, seq, salt + , public_key, private_key, data); } + , salt); + } +#endif + + add_torrent_params read_resume_data_wrapper(bytes const& b) + { + error_code ec; + add_torrent_params p = read_resume_data(b.arr, ec); +#ifndef BOOST_NO_EXCEPTIONS + if (ec) throw system_error(ec); +#endif + return p; + } + + int find_metric_idx_wrap(char const* name) + { + return lt::find_metric_idx(name); + } + + bytes write_resume_data_buf_(add_torrent_params const& atp) + { + bytes ret; + auto buf = write_resume_data_buf(atp); + ret.arr.resize(buf.size()); + std::copy(buf.begin(), buf.end(), ret.arr.begin()); + return ret; + } + +} // anonymous namespace + +struct dummy1 {}; +#if TORRENT_ABI_VERSION == 1 +struct dummy2 {}; +#endif +struct dummy9 {}; +struct dummy10 {}; +struct dummy11 {}; + +void bind_session() +{ +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*dht_get_immutable_item)(sha1_hash const&) = <::session::dht_get_item; + sha1_hash (lt::session::*dht_put_immutable_item)(entry data) = <::session::dht_put_item; +#endif // TORRENT_DISABLE_DHT + +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*start_dht0)() = <::session::start_dht; + void (lt::session::*start_dht1)(entry const&) = <::session::start_dht; +#endif + + class_("session_status") + .def_readonly("has_incoming_connections", &session_status::has_incoming_connections) + + .def_readonly("upload_rate", &session_status::upload_rate) + .def_readonly("download_rate", &session_status::download_rate) + .def_readonly("total_download", &session_status::total_download) + .def_readonly("total_upload", &session_status::total_upload) + + .def_readonly("payload_upload_rate", &session_status::payload_upload_rate) + .def_readonly("payload_download_rate", &session_status::payload_download_rate) + .def_readonly("total_payload_download", &session_status::total_payload_download) + .def_readonly("total_payload_upload", &session_status::total_payload_upload) + + .def_readonly("ip_overhead_upload_rate", &session_status::ip_overhead_upload_rate) + .def_readonly("ip_overhead_download_rate", &session_status::ip_overhead_download_rate) + .def_readonly("total_ip_overhead_download", &session_status::total_ip_overhead_download) + .def_readonly("total_ip_overhead_upload", &session_status::total_ip_overhead_upload) + + .def_readonly("dht_upload_rate", &session_status::dht_upload_rate) + .def_readonly("dht_download_rate", &session_status::dht_download_rate) + .def_readonly("total_dht_download", &session_status::total_dht_download) + .def_readonly("total_dht_upload", &session_status::total_dht_upload) + + .def_readonly("tracker_upload_rate", &session_status::tracker_upload_rate) + .def_readonly("tracker_download_rate", &session_status::tracker_download_rate) + .def_readonly("total_tracker_download", &session_status::total_tracker_download) + .def_readonly("total_tracker_upload", &session_status::total_tracker_upload) + + .def_readonly("total_redundant_bytes", &session_status::total_redundant_bytes) + .def_readonly("total_failed_bytes", &session_status::total_failed_bytes) + + .def_readonly("num_peers", &session_status::num_peers) + .def_readonly("num_unchoked", &session_status::num_unchoked) + .def_readonly("allowed_upload_slots", &session_status::allowed_upload_slots) + + .def_readonly("up_bandwidth_queue", &session_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &session_status::down_bandwidth_queue) + + .def_readonly("up_bandwidth_bytes_queue", &session_status::up_bandwidth_bytes_queue) + .def_readonly("down_bandwidth_bytes_queue", &session_status::down_bandwidth_bytes_queue) + + .def_readonly("optimistic_unchoke_counter", &session_status::optimistic_unchoke_counter) + .def_readonly("unchoke_counter", &session_status::unchoke_counter) + +#ifndef TORRENT_DISABLE_DHT + .def_readonly("dht_nodes", &session_status::dht_nodes) + .def_readonly("dht_node_cache", &session_status::dht_node_cache) + .def_readonly("dht_torrents", &session_status::dht_torrents) + .def_readonly("dht_global_nodes", &session_status::dht_global_nodes) + .def_readonly("active_requests", &session_status::active_requests) + .def_readonly("dht_total_allocations", &session_status::dht_total_allocations) +#endif // TORRENT_DISABLE_DHT + .add_property("utp_stats", &get_utp_stats) + ; + +#ifndef TORRENT_DISABLE_DHT + class_("dht_lookup") + .def_readonly("type", &dht_lookup::type) + .def_readonly("outstanding_requests", &dht_lookup::outstanding_requests) + .def_readonly("timeouts", &dht_lookup::timeouts) + .def_readonly("response", &dht_lookup::responses) + .def_readonly("branch_factor", &dht_lookup::branch_factor) + ; +#endif // TORRENT_DISABLE_DHT +#endif // TORRENT_ABI_VERSION + +#define PROP(val) \ + make_getter(val, return_value_policy()), \ + make_setter(val, return_value_policy()) + + class_("add_torrent_params") + .def_readwrite("version", &add_torrent_params::version) + .def_readwrite("ti", &add_torrent_params::ti) + .add_property("trackers", PROP(&add_torrent_params::trackers)) + .add_property("tracker_tiers", PROP(&add_torrent_params::tracker_tiers)) + .add_property("dht_nodes", PROP(&add_torrent_params::dht_nodes)) + .def_readwrite("name", &add_torrent_params::name) + .def_readwrite("save_path", &add_torrent_params::save_path) + .def_readwrite("storage_mode", &add_torrent_params::storage_mode) +// .def_readwrite("storage", &add_torrent_params::storage) + .add_property("file_priorities", PROP(&add_torrent_params::file_priorities)) + .def_readwrite("trackerid", &add_torrent_params::trackerid) + .add_property("flags", PROP(&add_torrent_params::flags)) + .def_readwrite("info_hash", &add_torrent_params::info_hash) + .def_readwrite("max_uploads", &add_torrent_params::max_uploads) + .def_readwrite("max_connections", &add_torrent_params::max_connections) + .def_readwrite("upload_limit", &add_torrent_params::upload_limit) + .def_readwrite("download_limit", &add_torrent_params::download_limit) + .def_readwrite("total_uploaded", &add_torrent_params::total_uploaded) + .def_readwrite("total_downloaded", &add_torrent_params::total_downloaded) + .def_readwrite("active_time", &add_torrent_params::active_time) + .def_readwrite("finished_time", &add_torrent_params::finished_time) + .def_readwrite("seeding_time", &add_torrent_params::seeding_time) + .def_readwrite("added_time", &add_torrent_params::added_time) + .def_readwrite("completed_time", &add_torrent_params::completed_time) + .def_readwrite("last_seen_complete", &add_torrent_params::last_seen_complete) + .def_readwrite("last_download", &add_torrent_params::last_download) + .def_readwrite("last_upload", &add_torrent_params::last_upload) + .def_readwrite("num_complete", &add_torrent_params::num_complete) + .def_readwrite("num_incomplete", &add_torrent_params::num_incomplete) + .def_readwrite("num_downloaded", &add_torrent_params::num_downloaded) + .add_property("http_seeds", PROP(&add_torrent_params::http_seeds)) + .add_property("url_seeds", PROP(&add_torrent_params::url_seeds)) + .add_property("peers", PROP(&add_torrent_params::peers)) + .add_property("banned_peers", PROP(&add_torrent_params::banned_peers)) + .add_property("unfinished_pieces", PROP(&add_torrent_params::unfinished_pieces)) + .add_property("have_pieces", PROP(&add_torrent_params::have_pieces)) + .add_property("verified_pieces", PROP(&add_torrent_params::verified_pieces)) + .add_property("piece_priorities", PROP(&add_torrent_params::piece_priorities)) + .add_property("merkle_tree", PROP(&add_torrent_params::merkle_tree)) + .add_property("renamed_files", PROP(&add_torrent_params::renamed_files)) + +#if TORRENT_ABI_VERSION == 1 + .def_readwrite("url", &add_torrent_params::url) + .def_readwrite("uuid", &add_torrent_params::uuid) + .add_property("resume_data", PROP(&add_torrent_params::resume_data)) +#endif + ; + + enum_("storage_mode_t") + .value("storage_mode_allocate", storage_mode_allocate) + .value("storage_mode_sparse", storage_mode_sparse) + ; + + { + scope s = class_("options_t"); + s.attr("delete_files") = lt::session::delete_files; + } + + { + scope s = class_("session_flags_t"); + s.attr("add_default_plugins") = lt::session::add_default_plugins; +#if TORRENT_ABI_VERSION == 1 + s.attr("start_default_features") = lt::session::start_default_features; +#endif + } + + { + scope s = class_("torrent_flags"); + s.attr("seed_mode") = torrent_flags::seed_mode; + s.attr("upload_mode") = torrent_flags::upload_mode; + s.attr("share_mode") = torrent_flags::share_mode; + s.attr("apply_ip_filter") = torrent_flags::apply_ip_filter; + s.attr("paused") = torrent_flags::paused; + s.attr("auto_managed") = torrent_flags::auto_managed; + s.attr("duplicate_is_error") = torrent_flags::duplicate_is_error; + s.attr("update_subscribe") = torrent_flags::update_subscribe; + s.attr("super_seeding") = torrent_flags::super_seeding; + s.attr("sequential_download") = torrent_flags::sequential_download; + s.attr("stop_when_ready") = torrent_flags::stop_when_ready; + s.attr("override_trackers") = torrent_flags::override_trackers; + s.attr("override_web_seeds") = torrent_flags::override_web_seeds; + s.attr("disable_dht") = torrent_flags::disable_dht; + s.attr("disable_lsd") = torrent_flags::disable_lsd; + s.attr("disable_pex") = torrent_flags::disable_pex; + s.attr("default_flags") = torrent_flags::default_flags; + } + +#if TORRENT_ABI_VERSION == 1 + { + scope s = class_("add_torrent_params_flags_t"); + s.attr("flag_seed_mode") = add_torrent_params::flag_seed_mode; + s.attr("flag_upload_mode") = add_torrent_params::flag_upload_mode; + s.attr("flag_share_mode") = add_torrent_params::flag_share_mode; + s.attr("flag_apply_ip_filter") = add_torrent_params::flag_apply_ip_filter; + s.attr("flag_paused") = add_torrent_params::flag_paused; + s.attr("flag_auto_managed") = add_torrent_params::flag_auto_managed; + s.attr("flag_duplicate_is_error") = add_torrent_params::flag_duplicate_is_error; + s.attr("flag_update_subscribe") = add_torrent_params::flag_update_subscribe; + s.attr("flag_super_seeding") = add_torrent_params::flag_super_seeding; + s.attr("flag_sequential_download") = add_torrent_params::flag_sequential_download; + s.attr("flag_stop_when_ready") = add_torrent_params::flag_stop_when_ready; + s.attr("flag_override_trackers") = add_torrent_params::flag_override_trackers; + s.attr("flag_override_web_seeds") = add_torrent_params::flag_override_web_seeds; + s.attr("flag_pinned") = add_torrent_params::flag_pinned; + s.attr("flag_override_resume_data") = add_torrent_params::flag_override_resume_data; + s.attr("flag_merge_resume_trackers") = add_torrent_params::flag_merge_resume_trackers; + s.attr("flag_use_resume_save_path") = add_torrent_params::flag_use_resume_save_path; + s.attr("flag_merge_resume_http_seeds") = add_torrent_params::flag_merge_resume_http_seeds; + s.attr("default_flags") = add_torrent_params::flag_default_flags; + } +#endif + + class_("cache_status") + .add_property("pieces", cache_status_pieces) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("blocks_written", &cache_status::blocks_written) + .def_readonly("writes", &cache_status::writes) + .def_readonly("blocks_read", &cache_status::blocks_read) + .def_readonly("blocks_read_hit", &cache_status::blocks_read_hit) + .def_readonly("reads", &cache_status::reads) + .def_readonly("queued_bytes", &cache_status::queued_bytes) + .def_readonly("cache_size", &cache_status::cache_size) + .def_readonly("write_cache_size", &cache_status::write_cache_size) + .def_readonly("read_cache_size", &cache_status::read_cache_size) + .def_readonly("pinned_blocks", &cache_status::pinned_blocks) + .def_readonly("total_used_buffers", &cache_status::total_used_buffers) + .def_readonly("average_read_time", &cache_status::average_read_time) + .def_readonly("average_write_time", &cache_status::average_write_time) + .def_readonly("average_hash_time", &cache_status::average_hash_time) + .def_readonly("average_job_time", &cache_status::average_job_time) + .def_readonly("cumulative_job_time", &cache_status::cumulative_job_time) + .def_readonly("cumulative_read_time", &cache_status::cumulative_read_time) + .def_readonly("cumulative_write_time", &cache_status::cumulative_write_time) + .def_readonly("cumulative_hash_time", &cache_status::cumulative_hash_time) + .def_readonly("total_read_back", &cache_status::total_read_back) + .def_readonly("read_queue_size", &cache_status::read_queue_size) + .def_readonly("blocked_jobs", &cache_status::blocked_jobs) + .def_readonly("queued_jobs", &cache_status::queued_jobs) + .def_readonly("peak_queued", &cache_status::peak_queued) + .def_readonly("pending_jobs", &cache_status::pending_jobs) + .def_readonly("num_jobs", &cache_status::num_jobs) + .def_readonly("num_read_jobs", &cache_status::num_read_jobs) + .def_readonly("num_write_jobs", &cache_status::num_write_jobs) + .def_readonly("arc_mru_size", &cache_status::arc_mru_size) + .def_readonly("arc_mru_ghost_size", &cache_status::arc_mru_ghost_size) + .def_readonly("arc_mfu_size", &cache_status::arc_mfu_size) + .def_readonly("arc_mfu_ghost_size", &cache_status::arc_mfu_ghost_size) +#endif + ; + + enum_("portmap_protocol") + .value("none", lt::portmap_protocol::none) + .value("udp", lt::portmap_protocol::udp) + .value("tcp", lt::portmap_protocol::tcp) + ; + + enum_("portmap_transport") + .value("natpmp", lt::portmap_transport::natpmp) + .value("upnp", lt::portmap_transport::upnp) + ; + + enum_("peer_class_type_filter_socket_type_t") + .value("tcp_socket", peer_class_type_filter::tcp_socket) + .value("utp_socket", peer_class_type_filter::utp_socket) + .value("ssl_tcp_socket", peer_class_type_filter::ssl_tcp_socket) + .value("ssl_utp_socket", peer_class_type_filter::ssl_utp_socket) + .value("i2p_socket", peer_class_type_filter::i2p_socket) + ; + + { + scope s = class_("peer_class_type_filter") + .def(init<>()) + .def("add", <::peer_class_type_filter::add) + .def("remove", <::peer_class_type_filter::remove) + .def("disallow", <::peer_class_type_filter::disallow) + .def("allow", <::peer_class_type_filter::allow) + .def("apply", <::peer_class_type_filter::apply) + ; + s.attr("tcp_socket") = peer_class_type_filter::tcp_socket; + s.attr("utp_socket") = peer_class_type_filter::utp_socket; + s.attr("ssl_tcp_socket") = peer_class_type_filter::ssl_tcp_socket; + s.attr("ssl_utp_socket") = peer_class_type_filter::ssl_utp_socket; + s.attr("i2p_socket") = peer_class_type_filter::i2p_socket; + } + + { + scope s = class_("session", no_init) + .def("__init__", boost::python::make_constructor(&make_session + , default_call_policies() + , (arg("settings") + , arg("flags")=lt::session::add_default_plugins)) + ) +#if TORRENT_ABI_VERSION == 1 + .def( + init(( + arg("fingerprint")=fingerprint("LT",0,1,0,0) + , arg("flags")=lt::session::start_default_features | lt::session::add_default_plugins + , arg("alert_mask")=alert::error_notification)) + ) + .def("outgoing_ports", &outgoing_ports) +#endif + .def("post_torrent_updates", allow_threads(<::session::post_torrent_updates), arg("flags") = 0xffffffff) + .def("post_dht_stats", allow_threads(<::session::post_dht_stats)) + .def("post_session_stats", allow_threads(<::session::post_session_stats)) + .def("is_listening", allow_threads(<::session::is_listening)) + .def("listen_port", allow_threads(<::session::listen_port)) +#ifndef TORRENT_DISABLE_DHT + .def("add_dht_node", &add_dht_node) +#if TORRENT_ABI_VERSION == 1 + .def( + "add_dht_router", &add_dht_router + , (arg("router"), "port") + ) +#endif // TORRENT_ABI_VERSION + .def("is_dht_running", allow_threads(<::session::is_dht_running)) + .def("set_dht_settings", allow_threads(<::session::set_dht_settings)) + .def("get_dht_settings", allow_threads(<::session::get_dht_settings)) + .def("dht_get_immutable_item", allow_threads(dht_get_immutable_item)) + .def("dht_get_mutable_item", &dht_get_mutable_item) + .def("dht_put_immutable_item", allow_threads(dht_put_immutable_item)) + .def("dht_put_mutable_item", &dht_put_mutable_item) + .def("dht_get_peers", allow_threads(<::session::dht_get_peers)) + .def("dht_announce", allow_threads(<::session::dht_announce)) + .def("dht_live_nodes", allow_threads(<::session::dht_live_nodes)) + .def("dht_sample_infohashes", allow_threads(<::session::dht_sample_infohashes)) +#endif // TORRENT_DISABLE_DHT + .def("add_torrent", &add_torrent) + .def("async_add_torrent", &async_add_torrent) + .def("async_add_torrent", static_cast(<::session::async_add_torrent)) + .def("add_torrent", allow_threads(static_cast(<::session::add_torrent))) +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + .def( + "add_torrent", &add_torrent_depr + , ( + arg("resume_data") = entry(), + arg("storage_mode") = storage_mode_sparse, + arg("paused") = false + ) + ) +#endif // TORRENT_ABI_VERSION +#endif // BOOST_NO_EXCEPTIONS + .def("remove_torrent", allow_threads(<::session::remove_torrent), arg("option") = 0) +#if TORRENT_ABI_VERSION == 1 + .def("status", allow_threads(<::session::status)) +#endif + .def("get_settings", &session_get_settings) + .def("apply_settings", &session_apply_settings) +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_ENCRYPTION + .def("set_pe_settings", allow_threads(<::session::set_pe_settings)) + .def("get_pe_settings", allow_threads(<::session::get_pe_settings)) +#endif +#endif + .def("load_state", &load_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("save_state", &save_state, (arg("entry"), arg("flags") = 0xffffffff)) + .def("pop_alerts", &pop_alerts) + .def("wait_for_alert", &wait_for_alert, return_internal_reference<>()) + .def("set_alert_notify", &set_alert_notify) + .def("set_alert_fd", &set_alert_fd) + .def("add_extension", &add_extension) +#if TORRENT_ABI_VERSION == 1 +#if TORRENT_USE_I2P + .def("set_i2p_proxy", allow_threads(<::session::set_i2p_proxy)) + .def("i2p_proxy", allow_threads(<::session::i2p_proxy)) +#endif +#endif + .def("set_ip_filter", allow_threads(<::session::set_ip_filter)) + .def("get_ip_filter", allow_threads(<::session::get_ip_filter)) + .def("find_torrent", allow_threads(<::session::find_torrent)) + .def("get_torrents", &get_torrents) + .def("get_torrent_status", &get_torrent_status, (arg("session"), arg("pred"), arg("flags") = 0)) + .def("refresh_torrent_status", &refresh_torrent_status, (arg("session"), arg("torrents"), arg("flags") = 0)) + .def("pause", allow_threads(<::session::pause)) + .def("resume", allow_threads(<::session::resume)) + .def("is_paused", allow_threads(<::session::is_paused)) + .def("get_cache_info", &get_cache_info1, (arg("handle") = torrent_handle(), arg("flags") = 0)) + .def("add_port_mapping", allow_threads(<::session::add_port_mapping)) + .def("delete_port_mapping", allow_threads(<::session::delete_port_mapping)) + .def("reopen_network_sockets", allow_threads(<::session::reopen_network_sockets)) + .def("set_peer_class_filter", <::session::set_peer_class_filter) + .def("set_peer_class_type_filter", <::session::set_peer_class_type_filter) + .def("create_peer_class", <::session::create_peer_class) + .def("delete_peer_class", <::session::delete_peer_class) + .def("get_peer_class", &get_peer_class) + .def("set_peer_class", &set_peer_class) + +#if TORRENT_ABI_VERSION == 1 + .def("id", allow_threads(<::session::id)) + .def( + "listen_on", &listen_on + , (arg("min"), "max", arg("interface") = (char const*)nullptr, arg("flags") = 0) + ) +#ifndef TORRENT_DISABLE_DHT + .def("start_dht", allow_threads(start_dht0)) + .def("stop_dht", allow_threads(<::session::stop_dht)) + .def("start_dht", allow_threads(start_dht1)) + .def("dht_state", allow_threads(<::session::dht_state)) + .def("set_dht_proxy", allow_threads(<::session::set_dht_proxy)) + .def("dht_proxy", allow_threads(<::session::dht_proxy)) +#endif + .def("set_local_download_rate_limit", allow_threads(<::session::set_local_download_rate_limit)) + .def("local_download_rate_limit", allow_threads(<::session::local_download_rate_limit)) + .def("set_local_upload_rate_limit", allow_threads(<::session::set_local_upload_rate_limit)) + .def("local_upload_rate_limit", allow_threads(<::session::local_upload_rate_limit)) + .def("set_download_rate_limit", allow_threads(<::session::set_download_rate_limit)) + .def("download_rate_limit", allow_threads(<::session::download_rate_limit)) + .def("set_upload_rate_limit", allow_threads(<::session::set_upload_rate_limit)) + .def("upload_rate_limit", allow_threads(<::session::upload_rate_limit)) + .def("set_max_uploads", allow_threads(<::session::set_max_uploads)) + .def("set_max_connections", allow_threads(<::session::set_max_connections)) + .def("max_connections", allow_threads(<::session::max_connections)) + .def("num_connections", allow_threads(<::session::num_connections)) + .def("set_max_half_open_connections", allow_threads(<::session::set_max_half_open_connections)) + .def("set_severity_level", allow_threads(<::session::set_severity_level)) + .def("set_alert_queue_size_limit", allow_threads(<::session::set_alert_queue_size_limit)) + .def("set_alert_mask", allow_threads(<::session::set_alert_mask)) + .def("set_peer_proxy", allow_threads(<::session::set_peer_proxy)) + .def("set_tracker_proxy", allow_threads(<::session::set_tracker_proxy)) + .def("set_web_seed_proxy", allow_threads(<::session::set_web_seed_proxy)) + .def("peer_proxy", allow_threads(<::session::peer_proxy)) + .def("tracker_proxy", allow_threads(<::session::tracker_proxy)) + .def("web_seed_proxy", allow_threads(<::session::web_seed_proxy)) + .def("set_proxy", allow_threads(<::session::set_proxy)) + .def("proxy", allow_threads(<::session::proxy)) + .def("start_upnp", &start_upnp) + .def("stop_upnp", allow_threads(<::session::stop_upnp)) + .def("start_lsd", allow_threads(<::session::start_lsd)) + .def("stop_lsd", allow_threads(<::session::stop_lsd)) + .def("start_natpmp", &start_natpmp) + .def("stop_natpmp", allow_threads(<::session::stop_natpmp)) + .def("get_cache_status", &get_cache_status) + .def("get_cache_info", &get_cache_info2) + .def("set_peer_id", allow_threads(<::session::set_peer_id)) +#endif // TORRENT_ABI_VERSION + ; + + s.attr("tcp") = lt::portmap_protocol::tcp; + s.attr("udp") = lt::portmap_protocol::udp; + + s.attr("global_peer_class_id") = session::global_peer_class_id; + s.attr("tcp_peer_class_id") = session::tcp_peer_class_id; + s.attr("local_peer_class_id") = session::local_peer_class_id; + + s.attr("reopen_map_ports") = lt::session::reopen_map_ports; + + s.attr("delete_files") = lt::session::delete_files; + s.attr("delete_partfile") = lt::session::delete_partfile; + } + +#if TORRENT_ABI_VERSION == 1 + { + scope s = class_("protocol_type"); + s.attr("udp") = lt::portmap_protocol::udp; + s.attr("tcp") = lt::portmap_protocol::tcp; + } +#endif + + { + scope s = class_("save_state_flags_t"); + s.attr("save_settings") = lt::session::save_settings; + s.attr("save_dht_settings") = lt::session::save_dht_settings; + s.attr("save_dht_state") = lt::session::save_dht_state; +#if TORRENT_ABI_VERSION == 1 + s.attr("save_encryption_settings") = lt::session:: save_encryption_settings; + s.attr("save_as_map") = lt::session::save_as_map; + s.attr("save_i2p_proxy") = lt::session::save_i2p_proxy; + s.attr("save_proxy") = lt::session::save_proxy; + s.attr("save_dht_proxy") = lt::session::save_dht_proxy; + s.attr("save_peer_proxy") = lt::session::save_peer_proxy; + s.attr("save_web_proxy") = lt::session::save_web_proxy; + s.attr("save_tracker_proxy") = lt::session::save_tracker_proxy; +#endif + } + +#if TORRENT_ABI_VERSION == 1 + enum_("listen_on_flags_t") + .value("listen_reuse_address", lt::session::listen_reuse_address) + .value("listen_no_system_port", lt::session::listen_no_system_port) + ; +#endif + + def("high_performance_seed", high_performance_seed_wrapper); + def("min_memory_usage", min_memory_usage_wrapper); + def("default_settings", default_settings_wrapper); + def("read_resume_data", read_resume_data_wrapper); + def("write_resume_data", write_resume_data); + def("write_resume_data_buf", write_resume_data_buf_); + + class_("stats_metric") + .def_readonly("name", &stats_metric::name) + .def_readonly("value_index", &stats_metric::value_index) + .def_readonly("type", &stats_metric::type) + ; + + enum_("metric_type_t") + .value("counter", metric_type_t::counter) + .value("gauge", metric_type_t::gauge) + ; + + def("session_stats_metrics", session_stats_metrics); + def("find_metric_idx", find_metric_idx_wrap); + + scope().attr("create_ut_metadata_plugin") = "ut_metadata"; + scope().attr("create_ut_pex_plugin") = "ut_pex"; + scope().attr("create_smart_ban_plugin") = "smart_ban"; +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/session_settings.cpp b/bindings/python/src/session_settings.cpp new file mode 100644 index 0000000..96b2f2d --- /dev/null +++ b/bindings/python/src/session_settings.cpp @@ -0,0 +1,131 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +using namespace boost::python; +using namespace lt; + +void bind_session_settings() +{ + enum_("choking_algorithm_t") + .value("fixed_slots_choker", settings_pack::fixed_slots_choker) +#if TORRENT_ABI_VERSION == 1 + .value("auto_expand_choker", settings_pack::rate_based_choker) +#endif + .value("rate_based_choker", settings_pack::rate_based_choker) +#if TORRENT_ABI_VERSION == 1 + .value("bittyrant_choker", settings_pack::bittyrant_choker) +#endif + ; + + enum_("seed_choking_algorithm_t") + .value("round_robin", settings_pack::round_robin) + .value("fastest_upload", settings_pack::fastest_upload) + .value("anti_leech", settings_pack::anti_leech) + ; + + enum_("suggest_mode_t") + .value("no_piece_suggestions", settings_pack::no_piece_suggestions) + .value("suggest_read_cache", settings_pack::suggest_read_cache) + ; + + enum_("io_buffer_mode_t") + .value("enable_os_cache", settings_pack::enable_os_cache) +#if TORRENT_ABI_VERSION == 1 + .value("disable_os_cache_for_aligned_files", settings_pack::disable_os_cache_for_aligned_files) +#endif + .value("disable_os_cache", settings_pack::disable_os_cache) + ; + + enum_("bandwidth_mixed_algo_t") + .value("prefer_tcp", settings_pack::prefer_tcp) + .value("peer_proportional", settings_pack::peer_proportional) + ; + + enum_("enc_policy") + .value("pe_forced", settings_pack::pe_forced) + .value("pe_enabled", settings_pack::pe_enabled) + .value("pe_disabled", settings_pack::pe_disabled) +#if TORRENT_ABI_VERSION == 1 + .value("forced", settings_pack::pe_forced) + .value("enabled", settings_pack::pe_enabled) + .value("disabled", settings_pack::pe_disabled) +#endif + ; + + enum_("enc_level") + .value("pe_rc4", settings_pack::pe_rc4) + .value("pe_plaintext", settings_pack::pe_plaintext) + .value("pe_both", settings_pack::pe_both) +#if TORRENT_ABI_VERSION == 1 + .value("rc4", settings_pack::pe_rc4) + .value("plaintext", settings_pack::pe_plaintext) + .value("both", settings_pack::pe_both) +#endif + ; + + { + scope s = enum_("proxy_type_t") + .value("none", settings_pack::none) + .value("socks4", settings_pack::socks4) + .value("socks5", settings_pack::socks5) + .value("socks5_pw", settings_pack::socks5_pw) + .value("http", settings_pack::http) + .value("http_pw", settings_pack::http_pw) + .value("i2p_proxy", settings_pack::i2p_proxy) + ; + +#if TORRENT_ABI_VERSION == 1 + scope().attr("proxy_type") = s; + + class_("proxy_settings") + .def_readwrite("hostname", &proxy_settings::hostname) + .def_readwrite("port", &proxy_settings::port) + .def_readwrite("password", &proxy_settings::password) + .def_readwrite("username", &proxy_settings::username) + .def_readwrite("type", &proxy_settings::type) + .def_readwrite("proxy_peer_connections", &proxy_settings::proxy_peer_connections) + .def_readwrite("proxy_hostnames", &proxy_settings::proxy_hostnames) + ; +#endif + } + +#ifndef TORRENT_DISABLE_DHT + class_("dht_settings") + .def_readwrite("max_peers_reply", &dht::dht_settings::max_peers_reply) + .def_readwrite("search_branching", &dht::dht_settings::search_branching) +#if TORRENT_ABI_VERSION == 1 + .def_readwrite("service_port", &dht::dht_settings::service_port) +#endif + .def_readwrite("max_fail_count", &dht::dht_settings::max_fail_count) + .def_readwrite("max_torrents", &dht::dht_settings::max_torrents) + .def_readwrite("max_dht_items", &dht::dht_settings::max_dht_items) + .def_readwrite("restrict_routing_ips", &dht::dht_settings::restrict_routing_ips) + .def_readwrite("restrict_search_ips", &dht::dht_settings::restrict_search_ips) + .def_readwrite("max_torrent_search_reply", &dht::dht_settings::max_torrent_search_reply) + .def_readwrite("extended_routing_table", &dht::dht_settings::extended_routing_table) + .def_readwrite("aggressive_lookups", &dht::dht_settings::aggressive_lookups) + .def_readwrite("privacy_lookups", &dht::dht_settings::privacy_lookups) + .def_readwrite("enforce_node_id", &dht::dht_settings::enforce_node_id) + .def_readwrite("ignore_dark_internet", &dht::dht_settings::ignore_dark_internet) + .def_readwrite("block_timeout", &dht::dht_settings::block_timeout) + .def_readwrite("block_ratelimit", &dht::dht_settings::block_ratelimit) + .def_readwrite("read_only", &dht::dht_settings::read_only) + .def_readwrite("item_lifetime", &dht::dht_settings::item_lifetime) + ; +#endif + +#if TORRENT_ABI_VERSION == 1 + class_("pe_settings") + .def_readwrite("out_enc_policy", &pe_settings::out_enc_policy) + .def_readwrite("in_enc_policy", &pe_settings::in_enc_policy) + .def_readwrite("allowed_enc_level", &pe_settings::allowed_enc_level) + .def_readwrite("prefer_rc4", &pe_settings::prefer_rc4) + ; +#endif + +} diff --git a/bindings/python/src/sha1_hash.cpp b/bindings/python/src/sha1_hash.cpp new file mode 100644 index 0000000..081a7c5 --- /dev/null +++ b/bindings/python/src/sha1_hash.cpp @@ -0,0 +1,43 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include + +#include "bytes.hpp" + +long get_hash(boost::python::object o) +{ + using namespace boost::python; + return long(PyObject_Hash(str(o).ptr())); +} + +using namespace lt; + +bytes sha1_hash_bytes(const sha1_hash& bn) { + return bytes(bn.to_string()); +} + +void bind_sha1_hash() +{ + using namespace boost::python; + using namespace lt; + + class_("sha1_hash") + .def(self == self) + .def(self != self) + .def(self < self) + .def(self_ns::str(self)) + .def(init()) + .def("clear", &sha1_hash::clear) + .def("is_all_zeros", &sha1_hash::is_all_zeros) + .def("to_string", &sha1_hash::to_string) + .def("__hash__", get_hash) + .def("to_bytes", sha1_hash_bytes) + ; + + scope().attr("peer_id") = scope().attr("sha1_hash"); +} + diff --git a/bindings/python/src/string.cpp b/bindings/python/src/string.cpp new file mode 100644 index 0000000..c5674b6 --- /dev/null +++ b/bindings/python/src/string.cpp @@ -0,0 +1,70 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; + +struct unicode_from_python +{ + unicode_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id() + ); + } + + static void* convertible(PyObject* x) + { +#if PY_VERSION_HEX >= 0x03020000 + return PyBytes_Check(x) ? x : PyUnicode_Check(x) ? x : nullptr; +#else + return PyString_Check(x) ? x : PyUnicode_Check(x) ? x : nullptr; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage< + std::string>*)data)->storage.bytes; + + if (PyUnicode_Check(x)) + { + PyObject* utf8 = PyUnicode_AsUTF8String(x); + if (utf8 == nullptr) + { + new (storage) std::string(); + } + else + { +#if PY_VERSION_HEX >= 0x03000000 + new (storage) std::string(PyBytes_AsString(utf8) + , PyBytes_Size(utf8)); +#else + new (storage) std::string(PyString_AsString(utf8) + , PyString_Size(utf8)); +#endif + Py_DECREF(utf8); + } + } + else + { +#if PY_VERSION_HEX >= 0x03000000 + new (storage) std::string(PyBytes_AsString(x) + , PyBytes_Size(x)); +#else + new (storage) std::string(PyString_AsString(x) + , PyString_Size(x)); +#endif + } + data->convertible = storage; + } +}; + +void bind_unicode_string_conversion() +{ + unicode_from_python(); +} + diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp new file mode 100644 index 0000000..8e5a7b2 --- /dev/null +++ b/bindings/python/src/torrent_handle.cpp @@ -0,0 +1,662 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include "bytes.hpp" +#include +#include +#include +#include +#include +#include "libtorrent/announce_entry.hpp" +#include +#include +#include "gil.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning c4996: x: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +namespace +{ + + list url_seeds(torrent_handle& handle) + { + list ret; + std::set urls; + { + allow_threading_guard guard; + urls = handle.url_seeds(); + } + + for (std::set::iterator i(urls.begin()) + , end(urls.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list http_seeds(torrent_handle& handle) + { + list ret; + std::set urls; + { + allow_threading_guard guard; + urls = handle.http_seeds(); + } + + for (std::set::iterator i(urls.begin()) + , end(urls.end()); i != end; ++i) + ret.append(*i); + return ret; + } + + list piece_availability(torrent_handle& handle) + { + list ret; + std::vector avail; + { + allow_threading_guard guard; + handle.piece_availability(avail); + } + + for (auto const a : avail) + ret.append(a); + return ret; + } + + list piece_priorities(torrent_handle& handle) + { + list ret; + std::vector prio; + { + allow_threading_guard guard; + prio = handle.get_piece_priorities(); + } + + for (auto const p : prio) + ret.append(p); + return ret; + } + +} // namespace unnamed + +list file_progress(torrent_handle& handle, int flags) +{ + std::vector p; + + { + allow_threading_guard guard; + std::shared_ptr ti = handle.torrent_file(); + if (ti) + { + p.reserve(ti->num_files()); + handle.file_progress(p, flags); + } + } + + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; +} + +list get_peer_info(torrent_handle const& handle) +{ + std::vector pi; + + { + allow_threading_guard guard; + handle.get_peer_info(pi); + } + + list result; + + for (std::vector::iterator i = pi.begin(); i != pi.end(); ++i) + result.append(*i); + + return result; +} + +namespace +{ + template + T extract_fn(object o) + { + return boost::python::extract(o); + } +} + +void prioritize_pieces(torrent_handle& info, object o) +{ + stl_input_iterator begin(o), end; + if (begin == end) return; + + // determine which overload should be selected. the one taking a list of + // priorities or the one taking a list of piece -> priority mappings + bool const is_piece_list = extract>(*begin).check(); + + if (is_piece_list) + { + std::vector> piece_list; + std::transform(begin, end, std::back_inserter(piece_list) + , &extract_fn>); + info.prioritize_pieces(piece_list); + } + else + { + std::vector priority_vector; + std::transform(begin, end, std::back_inserter(priority_vector) + , &extract_fn); + info.prioritize_pieces(priority_vector); + } +} + +void prioritize_files(torrent_handle& info, object o) +{ + stl_input_iterator begin(o), end; + info.prioritize_files(std::vector(begin, end)); +} + +list file_priorities(torrent_handle& handle) +{ + list ret; + std::vector priorities = handle.get_file_priorities(); + + for (auto const p : priorities) + ret.append(p); + + return ret; +} + +download_priority_t file_prioritity0(torrent_handle& h, file_index_t index) +{ + return h.file_priority(index); +} + +void file_prioritity1(torrent_handle& h, file_index_t index, download_priority_t prio) +{ + return h.file_priority(index, prio); +} + +void dict_to_announce_entry(dict d, announce_entry& ae) +{ + ae.url = extract(d["url"]); + if (d.has_key("tier")) + ae.tier = extract(d["tier"]); + if (d.has_key("fail_limit")) + ae.fail_limit = extract(d["fail_limit"]); +} + +void replace_trackers(torrent_handle& h, object trackers) +{ + object iter(trackers.attr("__iter__")()); + + std::vector result; + + for (;;) + { + handle<> entry(allow_null(PyIter_Next(iter.ptr()))); + + if (entry == handle<>()) + break; + + if (extract(object(entry)).check()) + { + result.push_back(extract(object(entry))); + } + else + { + dict d; + d = extract(object(entry)); + announce_entry ae; + dict_to_announce_entry(d, ae); + result.push_back(ae); + } + } + + allow_threading_guard guard; + h.replace_trackers(result); +} + +void add_tracker(torrent_handle& h, dict d) +{ + announce_entry ae; + dict_to_announce_entry(d, ae); + h.add_tracker(ae); +} + +namespace +{ +#if defined BOOST_ASIO_HAS_STD_CHRONO + using std::chrono::system_clock; +#else + using boost::chrono::system_clock; +#endif + + time_t to_ptime(time_point tpt) + { + return system_clock::to_time_t(system_clock::now() + + duration_cast(tpt - clock_type::now())); + } +} + +list trackers(torrent_handle& h) +{ + list ret; + std::vector const trackers = h.trackers(); + for (std::vector::const_iterator i = trackers.begin(), end(trackers.end()); i != end; ++i) + { + dict d; + d["url"] = i->url; + d["trackerid"] = i->trackerid; + d["tier"] = i->tier; + d["fail_limit"] = i->fail_limit; + d["source"] = i->source; + d["verified"] = i->verified; + +#if TORRENT_ABI_VERSION == 1 + if (!i->endpoints.empty()) + { + announce_endpoint const& aep = i->endpoints.front(); + d["message"] = aep.message; + dict last_error; + last_error["value"] = aep.last_error.value(); + last_error["category"] = aep.last_error.category().name(); + d["last_error"] = last_error; + if (aep.next_announce > min_time()) { + d["next_announce"] = to_ptime(aep.next_announce); + } + else { + d["next_announce"] = object(); + } + if (aep.min_announce > min_time()) { + d["min_announce"] = to_ptime(aep.min_announce); + } + else { + d["min_announce"] = object(); + } + d["scrape_incomplete"] = aep.scrape_incomplete; + d["scrape_complete"] = aep.scrape_complete; + d["scrape_downloaded"] = aep.scrape_downloaded; + d["fails"] = aep.fails; + d["updating"] = aep.updating; + d["start_sent"] = aep.start_sent; + d["complete_sent"] = aep.complete_sent; + } + else + { + d["message"] = std::string(); + dict last_error; + last_error["value"] = 0; + last_error["category"] = ""; + d["last_error"] = last_error; + d["next_announce"] = object(); + d["min_announce"] = object(); + d["scrape_incomplete"] = 0; + d["scrape_complete"] = 0; + d["scrape_downloaded"] = 0; + d["fails"] = 0; + d["updating"] = false; + d["start_sent"] = false; + d["complete_sent"] = false; + } +#endif + + list aeps; + for (auto const& aep : i->endpoints) + { + dict e; + e["message"] = aep.message; + e["local_address"] = boost::python::make_tuple(aep.local_endpoint.address().to_string(), aep.local_endpoint.port()); + dict last_error; + last_error["value"] = aep.last_error.value(); + last_error["category"] = aep.last_error.category().name(); + e["last_error"] = last_error; + if (aep.next_announce > min_time()) { + e["next_announce"] = to_ptime(aep.next_announce); + } + else { + e["next_announce"] = object(); + } + if (aep.min_announce > min_time()) { + e["min_announce"] = to_ptime(aep.min_announce); + } + else { + e["min_announce"] = object(); + } + e["scrape_incomplete"] = aep.scrape_incomplete; + e["scrape_complete"] = aep.scrape_complete; + e["scrape_downloaded"] = aep.scrape_downloaded; + e["fails"] = aep.fails; + e["updating"] = aep.updating; + e["start_sent"] = aep.start_sent; + e["complete_sent"] = aep.complete_sent; + aeps.append(e); + } + d["endpoints"] = aeps; + +#if TORRENT_ABI_VERSION == 1 + d["send_stats"] = i->send_stats; +#endif + ret.append(d); + } + return ret; +} + +list get_download_queue(torrent_handle& handle) +{ + list ret; + + std::vector downloading; + + { + allow_threading_guard guard; + handle.get_download_queue(downloading); + } + + for (std::vector::iterator i = downloading.begin() + , end(downloading.end()); i != end; ++i) + { + dict partial_piece; + partial_piece["piece_index"] = i->piece_index; + partial_piece["blocks_in_piece"] = i->blocks_in_piece; + list block_list; + for (int k = 0; k < i->blocks_in_piece; ++k) + { + dict block_info; + block_info["state"] = i->blocks[k].state; + block_info["num_peers"] = i->blocks[k].num_peers; + block_info["bytes_progress"] = i->blocks[k].bytes_progress; + block_info["block_size"] = i->blocks[k].block_size; + block_info["peer"] = boost::python::make_tuple( + i->blocks[k].peer().address().to_string() + , i->blocks[k].peer().port()); + block_list.append(block_info); + } + partial_piece["blocks"] = block_list; + + ret.append(partial_piece); + } + + return ret; +} + +void set_metadata(torrent_handle& handle, std::string const& buf) +{ + handle.set_metadata(buf); +} + +#if TORRENT_ABI_VERSION == 1 + +std::shared_ptr get_torrent_info(torrent_handle const& h) +{ + allow_threading_guard guard; + return h.torrent_file(); +} + +#endif // TORRENT_ABI_VERSION + +void add_piece_str(torrent_handle& th, piece_index_t piece, char const *data + , add_piece_flags_t const flags) +{ + th.add_piece(piece, data, flags); +} + +void add_piece_bytes(torrent_handle& th, piece_index_t piece, bytes data + , add_piece_flags_t const flags) +{ + th.add_piece(piece, data.arr.c_str(), flags); +} + +class dummy5 {}; +class dummy {}; +class dummy4 {}; +class dummy6 {}; +class dummy7 {}; +class dummy8 {}; +class dummy15 {}; + +using by_value = return_value_policy; +void bind_torrent_handle() +{ + // arguments are: number of seconds and tracker index + void (torrent_handle::*force_reannounce0)(int, int, reannounce_flags_t) const = &torrent_handle::force_reannounce; + +#if TORRENT_ABI_VERSION == 1 + bool (torrent_handle::*super_seeding0)() const = &torrent_handle::super_seeding; + void (torrent_handle::*super_seeding1)(bool) const = &torrent_handle::super_seeding; +#endif + void (torrent_handle::*set_flags0)(torrent_flags_t) const = &torrent_handle::set_flags; + void (torrent_handle::*set_flags1)(torrent_flags_t, torrent_flags_t) const = &torrent_handle::set_flags; + + download_priority_t (torrent_handle::*piece_priority0)(piece_index_t) const = &torrent_handle::piece_priority; + void (torrent_handle::*piece_priority1)(piece_index_t, download_priority_t) const = &torrent_handle::piece_priority; + + void (torrent_handle::*move_storage0)(std::string const&, lt::move_flags_t) const = &torrent_handle::move_storage; + void (torrent_handle::*rename_file0)(file_index_t, std::string const&) const = &torrent_handle::rename_file; + +#if TORRENT_ABI_VERSION == 1 +#ifdef TORRENT_WINDOWS + void (torrent_handle::*move_storage1)(std::wstring const&, int) const = &torrent_handle::move_storage; + void (torrent_handle::*rename_file1)(file_index_t, std::wstring const&) const = &torrent_handle::rename_file; +#endif +#endif + + std::vector (torrent_handle::*file_status0)() const = &torrent_handle::file_status; + +#define _ allow_threads + + enum_("move_flags_t") + .value("always_replace_files", move_flags_t::always_replace_files) + .value("fail_if_exist", move_flags_t::fail_if_exist) + .value("dont_replace", move_flags_t::dont_replace) + ; + +#if TORRENT_ABI_VERSION == 1 + enum_("deprecated_move_flags_t") + .value("always_replace_files", deprecated_move_flags_t::always_replace_files) + .value("fail_if_exist", deprecated_move_flags_t::fail_if_exist) + .value("dont_replace", deprecated_move_flags_t::dont_replace) + ; +#endif + + { + scope s = class_("torrent_handle") + .def(self == self) + .def(self != self) + .def(self < self) + .def("__hash__", (std::size_t (*)(torrent_handle const&))&libtorrent::hash_value) + .def("get_peer_info", get_peer_info) + .def("status", _(&torrent_handle::status), arg("flags") = 0xffffffff) + .def("get_download_queue", get_download_queue) + .def("file_progress", file_progress, arg("flags") = 0) + .def("trackers", trackers) + .def("replace_trackers", replace_trackers) + .def("add_tracker", add_tracker) + .def("add_url_seed", _(&torrent_handle::add_url_seed)) + .def("remove_url_seed", _(&torrent_handle::remove_url_seed)) + .def("url_seeds", url_seeds) + .def("add_http_seed", _(&torrent_handle::add_http_seed)) + .def("remove_http_seed", _(&torrent_handle::remove_http_seed)) + .def("http_seeds", http_seeds) + .def("torrent_file", _(&torrent_handle::torrent_file)) + .def("set_metadata", set_metadata) + .def("is_valid", _(&torrent_handle::is_valid)) + .def("pause", _(&torrent_handle::pause), arg("flags") = 0) + .def("resume", _(&torrent_handle::resume)) + .def("clear_error", _(&torrent_handle::clear_error)) + .def("queue_position", _(&torrent_handle::queue_position)) + .def("queue_position_up", _(&torrent_handle::queue_position_up)) + .def("queue_position_down", _(&torrent_handle::queue_position_down)) + .def("queue_position_top", _(&torrent_handle::queue_position_top)) + .def("queue_position_bottom", _(&torrent_handle::queue_position_bottom)) + + .def("add_piece", add_piece_str) + .def("add_piece", add_piece_bytes) + .def("read_piece", _(&torrent_handle::read_piece)) + .def("have_piece", _(&torrent_handle::have_piece)) + .def("set_piece_deadline", _(&torrent_handle::set_piece_deadline) + , (arg("index"), arg("deadline"), arg("flags") = 0)) + .def("reset_piece_deadline", _(&torrent_handle::reset_piece_deadline), (arg("index"))) + .def("clear_piece_deadlines", _(&torrent_handle::clear_piece_deadlines), (arg("index"))) + .def("piece_availability", &piece_availability) + .def("piece_priority", _(piece_priority0)) + .def("piece_priority", _(piece_priority1)) + .def("prioritize_pieces", &prioritize_pieces) + .def("get_piece_priorities", &piece_priorities) + .def("prioritize_files", &prioritize_files) + .def("get_file_priorities", &file_priorities) + .def("file_priority", &file_prioritity0) + .def("file_priority", &file_prioritity1) + .def("file_status", _(file_status0)) + .def("save_resume_data", _(&torrent_handle::save_resume_data), arg("flags") = 0) + .def("need_save_resume_data", _(&torrent_handle::need_save_resume_data)) + .def("force_reannounce", _(force_reannounce0) + , (arg("seconds") = 0, arg("tracker_idx") = -1, arg("flags") = reannounce_flags_t{})) +#ifndef TORRENT_DISABLE_DHT + .def("force_dht_announce", _(&torrent_handle::force_dht_announce)) +#endif + .def("scrape_tracker", _(&torrent_handle::scrape_tracker), arg("index") = -1) + .def("flush_cache", &torrent_handle::flush_cache) + .def("set_upload_limit", _(&torrent_handle::set_upload_limit)) + .def("upload_limit", _(&torrent_handle::upload_limit)) + .def("set_download_limit", _(&torrent_handle::set_download_limit)) + .def("download_limit", _(&torrent_handle::download_limit)) + .def("connect_peer", &torrent_handle::connect_peer, (arg("endpoint"), arg("source")=0, arg("flags")=0xd)) + .def("set_max_uploads", &torrent_handle::set_max_uploads) + .def("max_uploads", _(&torrent_handle::max_uploads)) + .def("set_max_connections", &torrent_handle::set_max_connections) + .def("max_connections", _(&torrent_handle::max_connections)) + .def("move_storage", _(move_storage0), (arg("path"), arg("flags") = move_flags_t::always_replace_files)) + .def("info_hash", _(&torrent_handle::info_hash)) + .def("force_recheck", _(&torrent_handle::force_recheck)) + .def("rename_file", _(rename_file0)) + .def("set_ssl_certificate", &torrent_handle::set_ssl_certificate, (arg("cert"), arg("private_key"), arg("dh_params"), arg("passphrase")="")) + .def("flags", _(&torrent_handle::flags)) + .def("set_flags", _(set_flags0)) + .def("set_flags", _(set_flags1)) + .def("unset_flags", _(&torrent_handle::unset_flags)) + // deprecated +#if TORRENT_ABI_VERSION == 1 + .def("piece_priorities", &piece_priorities) + .def("file_priorities", &file_priorities) + .def("stop_when_ready", _(&torrent_handle::stop_when_ready)) + .def("super_seeding", super_seeding1) + .def("auto_managed", _(&torrent_handle::auto_managed)) + .def("set_priority", _(&torrent_handle::set_priority)) + .def("get_torrent_info", &get_torrent_info) + .def("super_seeding", super_seeding0) + .def("write_resume_data", _(&torrent_handle::write_resume_data)) + .def("is_seed", _(&torrent_handle::is_seed)) + .def("is_finished", _(&torrent_handle::is_finished)) + .def("has_metadata", _(&torrent_handle::has_metadata)) + .def("use_interface", &torrent_handle::use_interface) + .def("name", _(&torrent_handle::name)) + .def("is_paused", _(&torrent_handle::is_paused)) + .def("is_auto_managed", _(&torrent_handle::is_auto_managed)) + .def("set_upload_mode", _(&torrent_handle::set_upload_mode)) + .def("set_share_mode", _(&torrent_handle::set_share_mode)) + .def("apply_ip_filter", &torrent_handle::apply_ip_filter) + .def("set_sequential_download", _(&torrent_handle::set_sequential_download)) + .def("set_peer_upload_limit", &torrent_handle::set_peer_upload_limit) + .def("set_peer_download_limit", &torrent_handle::set_peer_download_limit) + .def("set_ratio", _(&torrent_handle::set_ratio)) + .def("save_path", _(&torrent_handle::save_path)) + .def("set_tracker_login", &torrent_handle::set_tracker_login) +#ifdef TORRENT_WINDOWS + .def("move_storage", _(move_storage1), (arg("path"), arg("flags") = always_replace_files)) + .def("rename_file", _(rename_file1)) +#endif +#endif + ; + + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + s.attr("piece_granularity") = int(torrent_handle::piece_granularity); + s.attr("graceful_pause") = torrent_handle::graceful_pause; + s.attr("flush_disk_cache") = torrent_handle::flush_disk_cache; + s.attr("save_info_dict") = torrent_handle::save_info_dict; + s.attr("only_if_modified") = torrent_handle::only_if_modified; + s.attr("alert_when_available") = torrent_handle::alert_when_available; + s.attr("query_distributed_copies") = torrent_handle::query_distributed_copies; + s.attr("query_accurate_download_counters") = torrent_handle::query_accurate_download_counters; + s.attr("query_last_seen_complete") = torrent_handle::query_last_seen_complete; + s.attr("query_pieces") = torrent_handle::query_pieces; + s.attr("query_verified_pieces") = torrent_handle::query_verified_pieces; + } + + class_("open_file_state") + .add_property("file_index", make_getter((&open_file_state::file_index), by_value())) + .def_readonly("last_use", &open_file_state::last_use) + .def_readonly("open_mode", &open_file_state::open_mode) + ; + + { + scope s = class_("file_open_mode"); + s.attr("read_only") = file_open_mode::read_only; + s.attr("write_only") = file_open_mode::write_only; + s.attr("read_write") = file_open_mode::read_write; + s.attr("rw_mask") = file_open_mode::rw_mask; + s.attr("sparse") = file_open_mode::sparse; + s.attr("no_atime") = file_open_mode::no_atime; + s.attr("random_access") = file_open_mode::random_access; +#if TORRENT_ABI_VERSION == 1 + s.attr("locked") = 0; +#endif + } + + enum_("file_progress_flags") + .value("piece_granularity", torrent_handle::piece_granularity) + ; + + { + scope s = class_("add_piece_flags_t"); + s.attr("overwrite_existing") = torrent_handle::overwrite_existing; + } + + { + scope s = class_("pause_flags_t"); + s.attr("graceful_pause") = torrent_handle::graceful_pause; + } + + { + scope s = class_("save_resume_flags_t"); + s.attr("flush_disk_cache") = torrent_handle::flush_disk_cache; + s.attr("save_info_dict") = torrent_handle::save_info_dict; + s.attr("only_if_modified") = torrent_handle::only_if_modified; + } + + { + scope s = class_("reannounce_flags_t"); + s.attr("ignore_min_interval") = torrent_handle::ignore_min_interval; + } + + { + scope s = class_("deadline_flags_t"); + s.attr("alert_when_available") = torrent_handle::alert_when_available; + } + + { + scope s = class_("status_flags_t"); + s.attr("query_distributed_copies") = torrent_handle::query_distributed_copies; + s.attr("query_accurate_download_counters") = torrent_handle::query_accurate_download_counters; + s.attr("query_last_seen_complete") = torrent_handle::query_last_seen_complete; + s.attr("query_pieces") = torrent_handle::query_pieces; + s.attr("query_verified_pieces") = torrent_handle::query_verified_pieces; + } + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp new file mode 100644 index 0000000..c5b8ea0 --- /dev/null +++ b/bindings/python/src/torrent_info.cpp @@ -0,0 +1,398 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +#include "boost_python.hpp" +#include + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/announce_entry.hpp" +#include "bytes.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +using namespace boost::python; +using namespace lt; + +namespace +{ + + std::vector::const_iterator begin_trackers(torrent_info& i) + { + return i.trackers().begin(); + } + + std::vector::const_iterator end_trackers(torrent_info& i) + { + return i.trackers().end(); + } + + void add_node(torrent_info& ti, char const* hostname, int port) + { + ti.add_node(std::make_pair(hostname, port)); + } + + list nodes(torrent_info const& ti) + { + list result; + + for (auto const& i : ti.nodes()) + result.append(boost::python::make_tuple(i.first, i.second)); + + return result; + } + + list get_web_seeds(torrent_info const& ti) + { + std::vector const& ws = ti.web_seeds(); + list ret; + for (std::vector::const_iterator i = ws.begin() + , end(ws.end()); i != end; ++i) + { + dict d; + d["url"] = i->url; + d["type"] = i->type; + d["auth"] = i->auth; + ret.append(d); + } + + return ret; + } + + void set_web_seeds(torrent_info& ti, list ws) + { + std::vector web_seeds; + int const len = static_cast(boost::python::len(ws)); + for (int i = 0; i < len; i++) + { + dict e = extract(ws[i]); + int const type = extract(e["type"]); + web_seeds.push_back(web_seed_entry( + extract(e["url"]) + , static_cast(type) + , extract(e["auth"]))); + } + ti.set_web_seeds(web_seeds); + } + + list get_merkle_tree(torrent_info const& ti) + { + std::vector const& mt = ti.merkle_tree(); + list ret; + for (std::vector::const_iterator i = mt.begin() + , end(mt.end()); i != end; ++i) + { + ret.append(bytes(i->to_string())); + } + return ret; + } + + void set_merkle_tree(torrent_info& ti, list hashes) + { + std::vector h; + for (int i = 0, e = int(len(hashes)); i < e; ++i) + h.push_back(sha1_hash(bytes(extract(hashes[i])).arr.data())); + + ti.set_merkle_tree(h); + } + + bytes hash_for_piece(torrent_info const& ti, piece_index_t i) + { + return bytes(ti.hash_for_piece(i).to_string()); + } + + bytes metadata(torrent_info const& ti) + { + return bytes(ti.metadata().get(), ti.metadata_size()); + } + + list map_block(torrent_info& ti, piece_index_t piece, std::int64_t offset, int size) + { + std::vector p = ti.map_block(piece, offset, size); + list result; + + for (std::vector::iterator i(p.begin()), e(p.end()); i != e; ++i) + result.append(*i); + + return result; + } + +#if TORRENT_ABI_VERSION == 1 + // Create getters for announce_entry data members with non-trivial types which need converting. + lt::time_point get_next_announce(announce_entry const& ae) + { return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().next_announce); } + lt::time_point get_min_announce(announce_entry const& ae) + { return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().min_announce); } + // announce_entry data member bit-fields. + int get_fails(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().fails; } + bool get_updating(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().updating; } + bool get_start_sent(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().start_sent; } + bool get_complete_sent(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().complete_sent; } + // announce_entry method requires lt::time_point. + bool can_announce(announce_entry const& ae, bool is_seed) { + // an entry without endpoints implies it has never been announced so it can be now + if (ae.endpoints.empty()) return true; + lt::time_point now = lt::clock_type::now(); + return ae.endpoints.front().can_announce(now, is_seed, ae.fail_limit); + } + bool is_working(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().is_working(); } +#endif + int get_source(announce_entry const& ae) { return ae.source; } + bool get_verified(announce_entry const& ae) { return ae.verified; } + +#if TORRENT_ABI_VERSION == 1 + std::string get_message(announce_entry const& ae) + { return ae.endpoints.empty() ? "" : ae.endpoints.front().message; } + error_code get_last_error(announce_entry const& ae) + { return ae.endpoints.empty() ? error_code() : ae.endpoints.front().last_error; } + int get_scrape_incomplete(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_incomplete; } + int get_scrape_complete(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_complete; } + int get_scrape_downloaded(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_downloaded; } + int next_announce_in(announce_entry const&) { return 0; } + int min_announce_in(announce_entry const&) { return 0; } + bool get_send_stats(announce_entry const& ae) { return ae.send_stats; } + std::int64_t get_size(file_entry const& fe) { return fe.size; } + std::int64_t get_offset(file_entry const& fe) { return fe.offset; } + bool get_pad_file(file_entry const& fe) { return fe.pad_file; } + bool get_executable_attribute(file_entry const& fe) { return fe.executable_attribute; } + bool get_hidden_attribute(file_entry const& fe) { return fe.hidden_attribute; } + bool get_symlink_attribute(file_entry const& fe) { return fe.symlink_attribute; } +#endif + +load_torrent_limits dict_to_limits(dict limits) +{ + load_torrent_limits ret; + + list items = limits.items(); + int const len = int(boost::python::len(limits)); + for (int i = 0; i < len; i++) + { + boost::python::api::object_item item = items[i]; + std::string const key = extract(item[0]); + object const value = item[1]; + + if (key == "max_buffer_size") + { + ret.max_buffer_size = extract(value); + continue; + } + else if (key == "max_pieces") + { + ret.max_pieces = extract(value); + continue; + } + else if (key == "max_decode_depth") + { + ret.max_decode_depth = extract(value); + continue; + } + else if (key == "max_decode_tokens") + { + ret.max_decode_tokens = extract(value); + continue; + } + } + return ret; +} + +} // namespace unnamed + +std::shared_ptr buffer_constructor0(bytes b) +{ + return std::make_shared(b.arr, from_span); +} + +std::shared_ptr buffer_constructor1(bytes b, dict limits) +{ + std::shared_ptr ret = std::make_shared(b.arr + , dict_to_limits(limits), from_span); + return ret; +} + +std::shared_ptr file_constructor0(std::string const& filename) +{ + return std::make_shared(filename); +} + +std::shared_ptr file_constructor1(std::string const& filename, dict limits) +{ + return std::make_shared(filename, dict_to_limits(limits)); +} + +std::shared_ptr bencoded_constructor0(entry const& ent) +{ + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, lt::from_span); +} + +std::shared_ptr bencoded_constructor1(entry const& ent, dict limits) +{ + std::vector buf; + bencode(std::back_inserter(buf), ent); + return std::make_shared(buf, dict_to_limits(limits) + , lt::from_span); +} + +using by_value = return_value_policy; +void bind_torrent_info() +{ + return_value_policy copy; + + void (torrent_info::*rename_file0)(file_index_t, std::string const&) = &torrent_info::rename_file; +#if TORRENT_ABI_VERSION == 1 +#ifdef TORRENT_WINDOWS + void (torrent_info::*rename_file1)(file_index_t, std::wstring const&) = &torrent_info::rename_file; +#endif +#endif + + class_("file_slice") + .add_property("file_index", make_getter((&file_slice::file_index), by_value())) + .def_readwrite("offset", &file_slice::offset) + .def_readwrite("size", &file_slice::size) + ; + + enum_("tracker_source") + .value("source_torrent", announce_entry::source_torrent) + .value("source_client", announce_entry::source_client) + .value("source_magnet_link", announce_entry::source_magnet_link) + .value("source_tex", announce_entry::source_tex) + ; + + using add_tracker1 = void (torrent_info::*)(std::string const&, int, announce_entry::tracker_source); + + class_>("torrent_info", no_init) + .def(init(arg("info_hash"))) + .def("__init__", make_constructor(&bencoded_constructor0)) + .def("__init__", make_constructor(&bencoded_constructor1)) + .def("__init__", make_constructor(&buffer_constructor0)) + .def("__init__", make_constructor(&buffer_constructor1)) + .def("__init__", make_constructor(&file_constructor0)) + .def("__init__", make_constructor(&file_constructor1)) + .def(init((arg("ti")))) + +#if TORRENT_ABI_VERSION == 1 +#ifdef TORRENT_WINDOWS + .def(init((arg("file")))) +#endif +#endif + + .def("add_tracker", (add_tracker1)&torrent_info::add_tracker, arg("url"), arg("tier") = 0, arg("source") = announce_entry::source_client) + .def("add_url_seed", &torrent_info::add_url_seed) + .def("add_http_seed", &torrent_info::add_http_seed) + .def("web_seeds", get_web_seeds) + .def("set_web_seeds", set_web_seeds) + + .def("name", &torrent_info::name, copy) + .def("comment", &torrent_info::comment, copy) + .def("creator", &torrent_info::creator, copy) + .def("total_size", &torrent_info::total_size) + .def("piece_length", &torrent_info::piece_length) + .def("num_pieces", &torrent_info::num_pieces) + .def("info_hash", &torrent_info::info_hash, copy) + .def("hash_for_piece", &hash_for_piece) + .def("merkle_tree", get_merkle_tree) + .def("set_merkle_tree", set_merkle_tree) + .def("piece_size", &torrent_info::piece_size) + + .def("similar_torrents", &torrent_info::similar_torrents) + .def("collections", &torrent_info::collections) + .def("ssl_cert", &torrent_info::ssl_cert) + .def("num_files", &torrent_info::num_files) + .def("rename_file", rename_file0) + .def("remap_files", &torrent_info::remap_files) + .def("files", &torrent_info::files, return_internal_reference<>()) + .def("orig_files", &torrent_info::orig_files, return_internal_reference<>()) +#if TORRENT_ABI_VERSION == 1 + .def("file_at", &torrent_info::file_at) + .def("file_at_offset", &torrent_info::file_at_offset) +#ifdef TORRENT_WINDOWS + .def("rename_file", rename_file1) +#endif +#endif // TORRENT_ABI_VERSION + + .def("is_valid", &torrent_info::is_valid) + .def("priv", &torrent_info::priv) + .def("is_i2p", &torrent_info::is_i2p) + .def("is_merkle_torrent", &torrent_info::is_merkle_torrent) + .def("trackers", range(begin_trackers, end_trackers)) + + .def("creation_date", &torrent_info::creation_date) + + .def("add_node", &add_node) + .def("nodes", &nodes) + .def("metadata", &metadata) + .def("metadata_size", &torrent_info::metadata_size) + .def("map_block", map_block) + .def("map_file", &torrent_info::map_file) + ; + +#if TORRENT_ABI_VERSION == 1 + class_("file_entry") + .def_readwrite("path", &file_entry::path) + .def_readwrite("symlink_path", &file_entry::symlink_path) + .def_readwrite("filehash", &file_entry::filehash) + .def_readwrite("mtime", &file_entry::mtime) + .add_property("pad_file", &get_pad_file) + .add_property("executable_attribute", &get_executable_attribute) + .add_property("hidden_attribute", &get_hidden_attribute) + .add_property("symlink_attribute", &get_symlink_attribute) + .add_property("offset", &get_offset) + .add_property("size", &get_size) + ; +#endif + + class_("announce_entry", init()) + .def_readwrite("url", &announce_entry::url) + .def_readonly("trackerid", &announce_entry::trackerid) +#if TORRENT_ABI_VERSION == 1 + .add_property("message", &get_message) + .add_property("last_error", &get_last_error) + .add_property("next_announce", &get_next_announce) + .add_property("min_announce", &get_min_announce) + .add_property("scrape_incomplete", &get_scrape_incomplete) + .add_property("scrape_complete", &get_scrape_complete) + .add_property("scrape_downloaded", &get_scrape_downloaded) +#endif + .def_readwrite("tier", &announce_entry::tier) + .def_readwrite("fail_limit", &announce_entry::fail_limit) + .add_property("source", &get_source) + .add_property("verified", &get_verified) +#if TORRENT_ABI_VERSION == 1 + .add_property("fails", &get_fails) + .add_property("updating", &get_updating) + .add_property("start_sent", &get_start_sent) + .add_property("complete_sent", &get_complete_sent) + .add_property("send_stats", &get_send_stats) + .def("next_announce_in", &next_announce_in) + .def("min_announce_in", &min_announce_in) + .def("can_announce", &can_announce) + .def("is_working", &is_working) +#endif + .def("reset", &announce_entry::reset) + .def("trim", &announce_entry::trim) + ; + + implicitly_convertible, std::shared_ptr>(); + boost::python::register_ptr_to_python>(); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/bindings/python/src/torrent_status.cpp b/bindings/python/src/torrent_status.cpp new file mode 100644 index 0000000..b9acf00 --- /dev/null +++ b/bindings/python/src/torrent_status.cpp @@ -0,0 +1,137 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include + +using namespace boost::python; +using namespace lt; + +using by_value = return_value_policy; +std::shared_ptr get_torrent_file(torrent_status const& st) +{ + return st.torrent_file.lock(); +} + +void bind_torrent_status() +{ + scope status = class_("torrent_status") + .def(self == self) + .def_readonly("handle", &torrent_status::handle) + .def_readonly("info_hash", &torrent_status::info_hash) + .add_property("torrent_file", &get_torrent_file) + .def_readonly("state", &torrent_status::state) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("paused", &torrent_status::paused) + .def_readonly("stop_when_ready", &torrent_status::stop_when_ready) + .def_readonly("auto_managed", &torrent_status::auto_managed) + .def_readonly("sequential_download", &torrent_status::sequential_download) +#endif + .def_readonly("is_seeding", &torrent_status::is_seeding) + .def_readonly("is_finished", &torrent_status::is_finished) + .def_readonly("has_metadata", &torrent_status::has_metadata) + .def_readonly("progress", &torrent_status::progress) + .def_readonly("progress_ppm", &torrent_status::progress_ppm) + .add_property("next_announce", make_getter(&torrent_status::next_announce, by_value())) +#if TORRENT_ABI_VERSION == 1 + .add_property("announce_interval", make_getter(&torrent_status::announce_interval, by_value())) +#endif + .def_readonly("current_tracker", &torrent_status::current_tracker) + .def_readonly("total_download", &torrent_status::total_download) + .def_readonly("total_upload", &torrent_status::total_upload) + .def_readonly("total_payload_download", &torrent_status::total_payload_download) + .def_readonly("total_payload_upload", &torrent_status::total_payload_upload) + .def_readonly("total_failed_bytes", &torrent_status::total_failed_bytes) + .def_readonly("total_redundant_bytes", &torrent_status::total_redundant_bytes) + .def_readonly("download_rate", &torrent_status::download_rate) + .def_readonly("upload_rate", &torrent_status::upload_rate) + .def_readonly("download_payload_rate", &torrent_status::download_payload_rate) + .def_readonly("upload_payload_rate", &torrent_status::upload_payload_rate) + .def_readonly("num_seeds", &torrent_status::num_seeds) + .def_readonly("num_peers", &torrent_status::num_peers) + .def_readonly("num_complete", &torrent_status::num_complete) + .def_readonly("num_incomplete", &torrent_status::num_incomplete) + .def_readonly("list_seeds", &torrent_status::list_seeds) + .def_readonly("list_peers", &torrent_status::list_peers) + .def_readonly("connect_candidates", &torrent_status::connect_candidates) + .add_property("pieces", make_getter(&torrent_status::pieces, by_value())) + .add_property("verified_pieces", make_getter(&torrent_status::verified_pieces, by_value())) + .def_readonly("num_pieces", &torrent_status::num_pieces) + .def_readonly("total_done", &torrent_status::total_done) + .def_readonly("total_wanted_done", &torrent_status::total_wanted_done) + .def_readonly("total_wanted", &torrent_status::total_wanted) + .def_readonly("distributed_full_copies", &torrent_status::distributed_full_copies) + .def_readonly("distributed_fraction", &torrent_status::distributed_fraction) + .def_readonly("distributed_copies", &torrent_status::distributed_copies) + .def_readonly("block_size", &torrent_status::block_size) + .def_readonly("num_uploads", &torrent_status::num_uploads) + .def_readonly("num_connections", &torrent_status::num_connections) + .def_readonly("uploads_limit", &torrent_status::uploads_limit) + .def_readonly("connections_limit", &torrent_status::connections_limit) + .def_readonly("storage_mode", &torrent_status::storage_mode) + .def_readonly("up_bandwidth_queue", &torrent_status::up_bandwidth_queue) + .def_readonly("down_bandwidth_queue", &torrent_status::down_bandwidth_queue) + .def_readonly("all_time_upload", &torrent_status::all_time_upload) + .def_readonly("all_time_download", &torrent_status::all_time_download) + .def_readonly("seed_rank", &torrent_status::seed_rank) + .def_readonly("has_incoming", &torrent_status::has_incoming) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("seed_mode", &torrent_status::seed_mode) + .def_readonly("upload_mode", &torrent_status::upload_mode) + .def_readonly("share_mode", &torrent_status::share_mode) + .def_readonly("super_seeding", &torrent_status::super_seeding) + .def_readonly("active_time", &torrent_status::active_time) + .def_readonly("finished_time", &torrent_status::finished_time) + .def_readonly("seeding_time", &torrent_status::seeding_time) + .def_readonly("last_scrape", &torrent_status::last_scrape) + .def_readonly("error", &torrent_status::error) + .def_readonly("priority", &torrent_status::priority) + .def_readonly("time_since_upload", &torrent_status::time_since_upload) + .def_readonly("time_since_download", &torrent_status::time_since_download) +#endif + .def_readonly("errc", &torrent_status::errc) + .add_property("error_file", make_getter(&torrent_status::error_file, by_value())) + .def_readonly("name", &torrent_status::name) + .def_readonly("save_path", &torrent_status::save_path) + .def_readonly("added_time", &torrent_status::added_time) + .def_readonly("completed_time", &torrent_status::completed_time) + .def_readonly("last_seen_complete", &torrent_status::last_seen_complete) + .add_property("queue_position", make_getter(&torrent_status::queue_position, by_value())) + .def_readonly("need_save_resume", &torrent_status::need_save_resume) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("ip_filter_applies", &torrent_status::ip_filter_applies) +#endif + .def_readonly("moving_storage", &torrent_status::moving_storage) +#if TORRENT_ABI_VERSION == 1 + .def_readonly("is_loaded", &torrent_status::is_loaded) +#endif + .def_readonly("announcing_to_trackers", &torrent_status::announcing_to_trackers) + .def_readonly("announcing_to_lsd", &torrent_status::announcing_to_lsd) + .def_readonly("announcing_to_dht", &torrent_status::announcing_to_dht) + .def_readonly("info_hash", &torrent_status::info_hash) + .add_property("last_upload", make_getter(&torrent_status::last_upload, by_value())) + .add_property("last_download", make_getter(&torrent_status::last_download, by_value())) + .add_property("active_duration", make_getter(&torrent_status::active_duration, by_value())) + .add_property("finished_duration", make_getter(&torrent_status::finished_duration, by_value())) + .add_property("seeding_duration", make_getter(&torrent_status::seeding_duration, by_value())) + .add_property("flags", make_getter(&torrent_status::flags, by_value())) + ; + + enum_("states") +#if TORRENT_ABI_VERSION == 1 + .value("queued_for_checking", torrent_status::queued_for_checking) +#endif + .value("checking_files", torrent_status::checking_files) + .value("downloading_metadata", torrent_status::downloading_metadata) + .value("downloading", torrent_status::downloading) + .value("finished", torrent_status::finished) + .value("seeding", torrent_status::seeding) + .value("allocating", torrent_status::allocating) + .value("checking_resume_data", torrent_status::checking_resume_data) + .export_values() + ; +} + diff --git a/bindings/python/src/utility.cpp b/bindings/python/src/utility.cpp new file mode 100644 index 0000000..f5795e9 --- /dev/null +++ b/bindings/python/src/utility.cpp @@ -0,0 +1,105 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include +#include +#include +#include "bytes.hpp" + +using namespace boost::python; +using namespace lt; + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif + +struct bytes_to_python +{ + static PyObject* convert(bytes const& p) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *ret = PyBytes_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#else + PyObject *ret = PyString_FromStringAndSize(p.arr.c_str(), p.arr.size()); +#endif + return ret; + } +}; + +struct bytes_from_python +{ + bytes_from_python() + { + converter::registry::push_back( + &convertible, &construct, type_id()); + } + + static void* convertible(PyObject* x) + { +#if PY_MAJOR_VERSION >= 3 + return PyBytes_Check(x) ? x : NULL; +#else + return PyString_Check(x) ? x : nullptr; +#endif + } + + static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data) + { +#if PY_MAJOR_VERSION >= 3 + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + ret->arr.resize(PyBytes_Size(x)); + memcpy(&ret->arr[0], PyBytes_AsString(x), ret->arr.size()); + data->convertible = storage; +#else + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + bytes* ret = new (storage) bytes(); + ret->arr.resize(PyString_Size(x)); + memcpy(&ret->arr[0], PyString_AsString(x), ret->arr.size()); + data->convertible = storage; +#endif + } +}; + +#if TORRENT_ABI_VERSION == 1 +object client_fingerprint_(peer_id const& id) +{ + boost::optional result = client_fingerprint(id); + return result ? object(*result) : object(); +} +#endif + +entry bdecode_(bytes const& data) +{ + return bdecode(data.arr); +} + +bytes bencode_(entry const& e) +{ + bytes result; + bencode(std::back_inserter(result.arr), e); + return result; +} + +void bind_utility() +{ + // TODO: it would be nice to install converters for sha1_hash as well + to_python_converter(); + bytes_from_python(); + +#if TORRENT_ABI_VERSION == 1 + def("identify_client", <::identify_client); + def("client_fingerprint", &client_fingerprint_); +#endif + def("bdecode", &bdecode_); + def("bencode", &bencode_); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/bindings/python/src/version.cpp b/bindings/python/src/version.cpp new file mode 100644 index 0000000..e38976c --- /dev/null +++ b/bindings/python/src/version.cpp @@ -0,0 +1,21 @@ +// Copyright Daniel Wallin 2006. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "boost_python.hpp" +#include + +using namespace boost::python; +using lt::version; + +void bind_version() +{ + scope().attr("__version__") = version(); + +#if TORRENT_ABI_VERSION == 1 + scope().attr("version") = LIBTORRENT_VERSION; + scope().attr("version_major") = LIBTORRENT_VERSION_MAJOR; + scope().attr("version_minor") = LIBTORRENT_VERSION_MINOR; +#endif +} + diff --git a/build-aux/compile b/build-aux/compile new file mode 100755 index 0000000..99e5052 --- /dev/null +++ b/build-aux/compile @@ -0,0 +1,348 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ + icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/config.guess b/build-aux/config.guess new file mode 100755 index 0000000..f50dcdb --- /dev/null +++ b/build-aux/config.guess @@ -0,0 +1,1480 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2018 Free Software Foundation, Inc. + +timestamp='2018-02-24' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess +# +# Please send patches to . + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2018 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > "$dummy.c" ; + for c in cc gcc c89 c99 ; do + if ($c -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "$UNAME_SYSTEM" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval "$set_cc_for_build" + cat <<-EOF > "$dummy.c" + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`" + + # If ldd exists, use it to detect musl libc. + if command -v ldd >/dev/null && \ + ldd --version 2>&1 | grep -q ^musl + then + LIBC=musl + fi + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + "/sbin/$sysctl" 2>/dev/null || \ + "/usr/sbin/$sysctl" 2>/dev/null || \ + echo unknown)` + case "$UNAME_MACHINE_ARCH" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` + machine="${arch}${endian}"-unknown + ;; + *) machine="$UNAME_MACHINE_ARCH"-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently (or will in the future) and ABI. + case "$UNAME_MACHINE_ARCH" in + earm*) + os=netbsdelf + ;; + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval "$set_cc_for_build" + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case "$UNAME_MACHINE_ARCH" in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "$UNAME_VERSION" in + Debian*) + release='-gnu' + ;; + *) + release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "$machine-${os}${release}${abi}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" + exit ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" + exit ;; + *:MidnightBSD:*:*) + echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" + exit ;; + *:ekkoBSD:*:*) + echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" + exit ;; + *:SolidBSD:*:*) + echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:MirBSD:*:*) + echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:Sortix:*:*) + echo "$UNAME_MACHINE"-unknown-sortix + exit ;; + *:Redox:*:*) + echo "$UNAME_MACHINE"-unknown-redox + exit ;; + mips:OSF1:*.*) + echo mips-dec-osf1 + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE=alpha ;; + "EV4.5 (21064)") + UNAME_MACHINE=alpha ;; + "LCA4 (21066/21068)") + UNAME_MACHINE=alpha ;; + "EV5 (21164)") + UNAME_MACHINE=alphaev5 ;; + "EV5.6 (21164A)") + UNAME_MACHINE=alphaev56 ;; + "EV5.6 (21164PC)") + UNAME_MACHINE=alphapca56 ;; + "EV5.7 (21164PC)") + UNAME_MACHINE=alphapca57 ;; + "EV6 (21264)") + UNAME_MACHINE=alphaev6 ;; + "EV6.7 (21264A)") + UNAME_MACHINE=alphaev67 ;; + "EV6.8CB (21264C)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8AL (21264B)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8CX (21264D)") + UNAME_MACHINE=alphaev68 ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE=alphaev69 ;; + "EV7 (21364)") + UNAME_MACHINE=alphaev7 ;; + "EV7.9 (21364A)") + UNAME_MACHINE=alphaev79 ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo "$UNAME_MACHINE"-dec-osf"`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`" + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix"$UNAME_RELEASE" + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo "$UNAME_MACHINE"-ibm-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux"$UNAME_RELEASE" + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval "$set_cc_for_build" + SUN_ARCH=i386 + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + echo "$SUN_ARCH"-pc-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos"`echo "$UNAME_RELEASE"|sed -e 's/-/_/'`" + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos"$UNAME_RELEASE" + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos"$UNAME_RELEASE" + ;; + sun4) + echo sparc-sun-sunos"$UNAME_RELEASE" + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos"$UNAME_RELEASE" + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint"$UNAME_RELEASE" + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint"$UNAME_RELEASE" + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint"$UNAME_RELEASE" + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten"$UNAME_RELEASE" + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten"$UNAME_RELEASE" + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix"$UNAME_RELEASE" + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix"$UNAME_RELEASE" + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix"$UNAME_RELEASE" + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && + dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`"$dummy" "$dummyarg"` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos"$UNAME_RELEASE" + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ "$UNAME_PROCESSOR" = mc88100 ] || [ "$UNAME_PROCESSOR" = mc88110 ] + then + if [ "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx ] || \ + [ "$TARGET_BINARY_INTERFACE"x = x ] + then + echo m88k-dg-dgux"$UNAME_RELEASE" + else + echo m88k-dg-dguxbcs"$UNAME_RELEASE" + fi + else + echo i586-dg-dgux"$UNAME_RELEASE" + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix"`echo "$UNAME_RELEASE"|sed -e 's/-/_/g'`" + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/lslpp ] ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$IBM_ARCH"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` + case "$UNAME_MACHINE" in + 9000/31?) HP_ARCH=m68000 ;; + 9000/[34]??) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "$sc_cpu_version" in + 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 + 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "$sc_kernel_bits" in + 32) HP_ARCH=hppa2.0n ;; + 64) HP_ARCH=hppa2.0w ;; + '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "$HP_ARCH" = "" ]; then + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ "$HP_ARCH" = hppa2.0w ] + then + eval "$set_cc_for_build" + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH=hppa2.0w + else + HP_ARCH=hppa64 + fi + fi + echo "$HP_ARCH"-hp-hpux"$HPUX_REV" + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux"$HPUX_REV" + exit ;; + 3050*:HI-UX:*:*) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo "$UNAME_MACHINE"-unknown-osf1mk + else + echo "$UNAME_MACHINE"-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:BSD/OS:*:*) + echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case "$UNAME_PROCESSOR" in + amd64) + UNAME_PROCESSOR=x86_64 ;; + i386) + UNAME_PROCESSOR=i586 ;; + esac + echo "$UNAME_PROCESSOR"-unknown-freebsd"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" + exit ;; + i*:CYGWIN*:*) + echo "$UNAME_MACHINE"-pc-cygwin + exit ;; + *:MINGW64*:*) + echo "$UNAME_MACHINE"-pc-mingw64 + exit ;; + *:MINGW*:*) + echo "$UNAME_MACHINE"-pc-mingw32 + exit ;; + *:MSYS*:*) + echo "$UNAME_MACHINE"-pc-msys + exit ;; + i*:PW*:*) + echo "$UNAME_MACHINE"-pc-pw32 + exit ;; + *:Interix*:*) + case "$UNAME_MACHINE" in + x86) + echo i586-pc-interix"$UNAME_RELEASE" + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix"$UNAME_RELEASE" + exit ;; + IA64) + echo ia64-unknown-interix"$UNAME_RELEASE" + exit ;; + esac ;; + i*:UWIN*:*) + echo "$UNAME_MACHINE"-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + *:GNU:*:*) + # the GNU system + echo "`echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,'`-unknown-$LIBC`echo "$UNAME_RELEASE"|sed -e 's,/.*$,,'`" + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" + exit ;; + i*86:Minix:*:*) + echo "$UNAME_MACHINE"-pc-minix + exit ;; + aarch64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC=gnulibc1 ; fi + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arm*:Linux:*:*) + eval "$set_cc_for_build" + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi + else + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + cris:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + crisv32:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + e2k:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + frv:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + hexagon:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:Linux:*:*) + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + exit ;; + ia64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + k1om:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m32r*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m68*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU'`" + test "x$CPU" != x && { echo "$CPU-unknown-linux-$LIBC"; exit; } + ;; + mips64el:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-"$LIBC" + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-"$LIBC" + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-"$LIBC" + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; + PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; + *) echo hppa-unknown-linux-"$LIBC" ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-"$LIBC" + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-"$LIBC" + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-"$LIBC" + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-"$LIBC" + exit ;; + riscv32:Linux:*:* | riscv64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" + exit ;; + sh64*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sh*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + tile*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + vax:Linux:*:*) + echo "$UNAME_MACHINE"-dec-linux-"$LIBC" + exit ;; + x86_64:Linux:*:*) + if objdump -f /bin/sh | grep -q elf32-x86-64; then + echo "$UNAME_MACHINE"-pc-linux-"$LIBC"x32 + else + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + fi + exit ;; + xtensa*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo "$UNAME_MACHINE"-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo "$UNAME_MACHINE"-unknown-stop + exit ;; + i*86:atheos:*:*) + echo "$UNAME_MACHINE"-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo "$UNAME_MACHINE"-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos"$UNAME_RELEASE" + exit ;; + i*86:*DOS:*:*) + echo "$UNAME_MACHINE"-pc-msdosdjgpp + exit ;; + i*86:*:4.*:*) + UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}{$UNAME_VERSION}" + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configure will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos"$UNAME_RELEASE" + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos"$UNAME_RELEASE" + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv"$UNAME_RELEASE" + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo "$UNAME_MACHINE"-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo "$UNAME_MACHINE"-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux"$UNAME_RELEASE" + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv"$UNAME_RELEASE" + else + echo mips-unknown-sysv"$UNAME_RELEASE" + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux"$UNAME_RELEASE" + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux"$UNAME_RELEASE" + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux"$UNAME_RELEASE" + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux"$UNAME_RELEASE" + exit ;; + SX-ACE:SUPER-UX:*:*) + echo sxace-nec-superux"$UNAME_RELEASE" + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Rhapsody:*:*) + echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval "$set_cc_for_build" + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test "`echo "$UNAME_RELEASE" | sed -e 's/\..*//'`" -le 10 ; then + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc + if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_PPC >/dev/null + then + UNAME_PROCESSOR=powerpc + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi + echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-*:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSR-*:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSV-*:NONSTOP_KERNEL:*:*) + echo nsv-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSX-*:NONSTOP_KERNEL:*:*) + echo nsx-tandem-nsk"$UNAME_RELEASE" + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = 386; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo "$UNAME_MACHINE"-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux"$UNAME_RELEASE" + exit ;; + *:DragonFly:*:*) + echo "$UNAME_MACHINE"-unknown-dragonfly"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "$UNAME_MACHINE" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo "$UNAME_MACHINE"-pc-skyos"`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`" + exit ;; + i*86:rdos:*:*) + echo "$UNAME_MACHINE"-pc-rdos + exit ;; + i*86:AROS:*:*) + echo "$UNAME_MACHINE"-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo "$UNAME_MACHINE"-unknown-esx + exit ;; + amd64:Isilon\ OneFS:*:*) + echo x86_64-unknown-onefs + exit ;; +esac + +echo "$0: unable to guess system type" >&2 + +case "$UNAME_MACHINE:$UNAME_SYSTEM" in + mips:Linux | mips64:Linux) + # If we got here on MIPS GNU/Linux, output extra information. + cat >&2 <&2 </dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = "$UNAME_MACHINE" +UNAME_RELEASE = "$UNAME_RELEASE" +UNAME_SYSTEM = "$UNAME_SYSTEM" +UNAME_VERSION = "$UNAME_VERSION" +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-functions 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/build-aux/config.rpath b/build-aux/config.rpath new file mode 100644 index 0000000..24be79c --- /dev/null +++ b/build-aux/config.rpath @@ -0,0 +1,684 @@ +#! /bin/sh +# Output a system dependent set of variables, describing how to set the +# run time search path of shared libraries in an executable. +# +# Copyright 1996-2020 Free Software Foundation, Inc. +# Taken from GNU libtool, 2001 +# Originally by Gordon Matzigkeit , 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# The first argument passed to this file is the canonical host specification, +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld +# should be set by the caller. +# +# The set of defined variables is at the end of this script. + +# Known limitations: +# - On IRIX 6.5 with CC="cc", the run time search patch must not be longer +# than 256 bytes, otherwise the compiler driver will dump core. The only +# known workaround is to choose shorter directory names for the build +# directory and/or the installation directory. + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a +shrext=.so + +host="$1" +host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` + +# Code taken from libtool.m4's _LT_CC_BASENAME. + +for cc_temp in $CC""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` + +# Code taken from libtool.m4's _LT_COMPILER_PIC. + +wl= +if test "$GCC" = yes; then + wl='-Wl,' +else + case "$host_os" in + aix*) + wl='-Wl,' + ;; + mingw* | cygwin* | pw32* | os2* | cegcc*) + ;; + hpux9* | hpux10* | hpux11*) + wl='-Wl,' + ;; + irix5* | irix6* | nonstopux*) + wl='-Wl,' + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu) + case $cc_basename in + ecc*) + wl='-Wl,' + ;; + icc* | ifort*) + wl='-Wl,' + ;; + lf95*) + wl='-Wl,' + ;; + nagfor*) + wl='-Wl,-Wl,,' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + wl='-Wl,' + ;; + ccc*) + wl='-Wl,' + ;; + xl* | bgxl* | bgf* | mpixl*) + wl='-Wl,' + ;; + como) + wl='-lopt=' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ F* | *Sun*Fortran*) + wl= + ;; + *Sun\ C*) + wl='-Wl,' + ;; + esac + ;; + esac + ;; + newsos6) + ;; + *nto* | *qnx*) + ;; + osf3* | osf4* | osf5*) + wl='-Wl,' + ;; + rdos*) + ;; + solaris*) + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + wl='-Qoption ld ' + ;; + *) + wl='-Wl,' + ;; + esac + ;; + sunos4*) + wl='-Qoption ld ' + ;; + sysv4 | sysv4.2uw2* | sysv4.3*) + wl='-Wl,' + ;; + sysv4*MP*) + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + wl='-Wl,' + ;; + unicos*) + wl='-Wl,' + ;; + uts4*) + ;; + esac +fi + +# Code taken from libtool.m4's _LT_LINKER_SHLIBS. + +hardcode_libdir_flag_spec= +hardcode_libdir_separator= +hardcode_direct=no +hardcode_minus_L=no + +case "$host_os" in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; +esac + +ld_shlibs=yes +if test "$with_gnu_ld" = yes; then + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + # Unlike libtool, we use -rpath here, not --rpath, since the documented + # option of GNU ld is called -rpath, not --rpath. + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + case "$host_os" in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs=no + fi + ;; + amigaos*) + case "$host_cpu" in + powerpc) + ;; + m68k) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + cygwin* | mingw* | pw32* | cegcc*) + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + haiku*) + ;; + interix[3-9]*) + hardcode_direct=no + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + netbsd*) + ;; + solaris*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' + else + ld_shlibs=no + fi + ;; + esac + ;; + sunos4*) + hardcode_direct=yes + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + esac + if test "$ld_shlibs" = no; then + hardcode_libdir_flag_spec= + fi +else + case "$host_os" in + aix3*) + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$GCC" = yes; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + else + aix_use_runtimelinking=no + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + fi + hardcode_direct=yes + hardcode_libdir_separator=':' + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + fi + # Begin _LT_AC_SYS_LIBPATH_AIX. + echo 'int main () { return 0; }' > conftest.c + ${CC} ${LDFLAGS} conftest.c -o conftest + aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` + if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` + fi + if test -z "$aix_libpath"; then + aix_libpath="/usr/lib:/lib" + fi + rm -f conftest.c conftest + # End _LT_AC_SYS_LIBPATH_AIX. + if test "$aix_use_runtimelinking" = yes; then + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' + else + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + fi + fi + ;; + amigaos*) + case "$host_cpu" in + powerpc) + ;; + m68k) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + bsdi[45]*) + ;; + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec=' ' + libext=lib + ;; + darwin* | rhapsody*) + hardcode_direct=no + if { case $cc_basename in ifort*) true;; *) test "$GCC" = yes;; esac; }; then + : + else + ld_shlibs=no + fi + ;; + dgux*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + freebsd2.[01]*) + hardcode_direct=yes + hardcode_minus_L=yes + ;; + freebsd* | dragonfly*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + hpux9*) + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + hpux10*) + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + hpux11*) + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + ;; + *) + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + irix5* | irix6* | nonstopux*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + netbsd*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + newsos6) + hardcode_direct=yes + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + *nto* | *qnx*) + ;; + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + else + case "$host_os" in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + *) + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + esac + fi + else + ld_shlibs=no + fi + ;; + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + osf3*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + osf4* | osf5*) + if test "$GCC" = yes; then + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + else + # Both cc and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + hardcode_libdir_separator=: + ;; + solaris*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + sunos4*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + ;; + sysv4) + case $host_vendor in + sni) + hardcode_direct=yes # is this really true??? + ;; + siemens) + hardcode_direct=no + ;; + motorola) + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + ;; + sysv4.3*) + ;; + sysv4*MP*) + if test -d /usr/nec; then + ld_shlibs=yes + fi + ;; + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + ;; + sysv5* | sco3.2v5* | sco5v6*) + hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + hardcode_libdir_separator=':' + ;; + uts4*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + *) + ld_shlibs=no + ;; + esac +fi + +# Check dynamic linker characteristics +# Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER. +# Unlike libtool.m4, here we don't care about _all_ names of the library, but +# only about the one the linker finds when passed -lNAME. This is the last +# element of library_names_spec in libtool.m4, or possibly two of them if the +# linker has special search rules. +library_names_spec= # the last element of library_names_spec in libtool.m4 +libname_spec='lib$name' +case "$host_os" in + aix3*) + library_names_spec='$libname.a' + ;; + aix[4-9]*) + library_names_spec='$libname$shrext' + ;; + amigaos*) + case "$host_cpu" in + powerpc*) + library_names_spec='$libname$shrext' ;; + m68k) + library_names_spec='$libname.a' ;; + esac + ;; + beos*) + library_names_spec='$libname$shrext' + ;; + bsdi[45]*) + library_names_spec='$libname$shrext' + ;; + cygwin* | mingw* | pw32* | cegcc*) + shrext=.dll + library_names_spec='$libname.dll.a $libname.lib' + ;; + darwin* | rhapsody*) + shrext=.dylib + library_names_spec='$libname$shrext' + ;; + dgux*) + library_names_spec='$libname$shrext' + ;; + freebsd[23].*) + library_names_spec='$libname$shrext$versuffix' + ;; + freebsd* | dragonfly*) + library_names_spec='$libname$shrext' + ;; + gnu*) + library_names_spec='$libname$shrext' + ;; + haiku*) + library_names_spec='$libname$shrext' + ;; + hpux9* | hpux10* | hpux11*) + case $host_cpu in + ia64*) + shrext=.so + ;; + hppa*64*) + shrext=.sl + ;; + *) + shrext=.sl + ;; + esac + library_names_spec='$libname$shrext' + ;; + interix[3-9]*) + library_names_spec='$libname$shrext' + ;; + irix5* | irix6* | nonstopux*) + library_names_spec='$libname$shrext' + case "$host_os" in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; + *) libsuff= shlibsuff= ;; + esac + ;; + esac + ;; + linux*oldld* | linux*aout* | linux*coff*) + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu) + library_names_spec='$libname$shrext' + ;; + knetbsd*-gnu) + library_names_spec='$libname$shrext' + ;; + netbsd*) + library_names_spec='$libname$shrext' + ;; + newsos6) + library_names_spec='$libname$shrext' + ;; + *nto* | *qnx*) + library_names_spec='$libname$shrext' + ;; + openbsd*) + library_names_spec='$libname$shrext$versuffix' + ;; + os2*) + libname_spec='$name' + shrext=.dll + library_names_spec='$libname.a' + ;; + osf3* | osf4* | osf5*) + library_names_spec='$libname$shrext' + ;; + rdos*) + ;; + solaris*) + library_names_spec='$libname$shrext' + ;; + sunos4*) + library_names_spec='$libname$shrext$versuffix' + ;; + sysv4 | sysv4.3*) + library_names_spec='$libname$shrext' + ;; + sysv4*MP*) + library_names_spec='$libname$shrext' + ;; + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + library_names_spec='$libname$shrext' + ;; + tpf*) + library_names_spec='$libname$shrext' + ;; + uts4*) + library_names_spec='$libname$shrext' + ;; +esac + +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' +escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` +shlibext=`echo "$shrext" | sed -e 's,^\.,,'` +escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` + +LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' <. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS + +Canonicalize a configuration name. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2018 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo "$1" + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ + kopensolaris*-gnu* | cloudabi*-eabi* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + android-linux) + os=-linux-android + basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; + *) + basic_machine=`echo "$1" | sed 's/-[^-]*$//'` + if [ "$basic_machine" != "$1" ] + then os=`echo "$1" | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray | -microblaze*) + os= + basic_machine=$1 + ;; + -bluegene*) + os=-cnk + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-sequent/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | aarch64 | aarch64_be \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | ba \ + | be32 | be64 \ + | bfin \ + | c4x | c8051 | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | e2k | epiphany \ + | fido | fr30 | frv | ft32 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i860 | i960 | ia16 | ia64 \ + | ip2k | iq2000 \ + | k1om \ + | le32 | le64 \ + | lm32 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64octeon | mips64octeonel \ + | mips64orion | mips64orionel \ + | mips64r5900 | mips64r5900el \ + | mips64vr | mips64vrel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa32r6 | mipsisa32r6el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64r6 | mipsisa64r6el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ + | nds32 | nds32le | nds32be \ + | nios | nios2 | nios2eb | nios2el \ + | ns16k | ns32k \ + | open8 | or1k | or1knd | or32 \ + | pdp10 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ + | pru \ + | pyramid \ + | riscv32 | riscv64 \ + | rl78 | rx \ + | score \ + | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | ubicom32 \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ + | visium \ + | wasm32 \ + | x86 | xc16x | xstormy16 | xtensa \ + | z8k | z80) + basic_machine=$basic_machine-unknown + ;; + c54x) + basic_machine=tic54x-unknown + ;; + c55x) + basic_machine=tic55x-unknown + ;; + c6x) + basic_machine=tic6x-unknown + ;; + leon|leon[3-9]) + basic_machine=sparc-$basic_machine + ;; + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65) + ;; + ms1) + basic_machine=mt-unknown + ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | aarch64-* | aarch64_be-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | ba-* \ + | be32-* | be64-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* \ + | c8051-* | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | e2k-* | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ + | i*86-* | i860-* | i960-* | ia16-* | ia64-* \ + | ip2k-* | iq2000-* \ + | k1om-* \ + | le32-* | le64-* \ + | lm32-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64octeon-* | mips64octeonel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64r5900-* | mips64r5900el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa32r6-* | mipsisa32r6el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64r6-* | mipsisa64r6el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nds32-* | nds32le-* | nds32be-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | or1k*-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ + | pru-* \ + | pyramid-* \ + | riscv32-* | riscv64-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \ + | tahoe-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tile*-* \ + | tron-* \ + | ubicom32-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ + | visium-* \ + | wasm32-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-* | z80-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-pc + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aros) + basic_machine=i386-pc + os=-aros + ;; + asmjs) + basic_machine=asmjs-unknown + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + bluegene*) + basic_machine=powerpc-ibm + os=-cnk + ;; + c54x-*) + basic_machine=tic54x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c55x-*) + basic_machine=tic55x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c6x-*) + basic_machine=tic6x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + cegcc) + basic_machine=arm-unknown + os=-cegcc + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16 | cr16-*) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dicos) + basic_machine=i686-pc + os=-dicos + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2*) + basic_machine=m68k-bull + os=-sysv3 + ;; + e500v[12]) + basic_machine=powerpc-unknown + os=$os"spe" + ;; + e500v[12]-*) + basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=$os"spe" + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; + i*86v32) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + leon-*|leon[3-9]-*) + basic_machine=sparc-`echo "$basic_machine" | sed 's/-.*//'` + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + microblaze*) + basic_machine=microblaze-xilinx + ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; + mingw32) + basic_machine=i686-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + moxiebox) + basic_machine=moxie-unknown + os=-moxiebox + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo "$basic_machine" | sed -e 's/ms1-/mt-/'` + ;; + msys) + basic_machine=i686-pc + os=-msys + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + neo-tandem) + basic_machine=neo-tandem + ;; + nse-tandem) + basic_machine=nse-tandem + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + nsv-tandem) + basic_machine=nsv-tandem + ;; + nsx-tandem) + basic_machine=nsx-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc | ppcbe) basic_machine=powerpc-unknown + ;; + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tile*) + basic_machine=$basic_machine-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + x64) + basic_machine=x86_64-pc + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo "$basic_machine" | sed 's/^xscale/arm/'` + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo "$basic_machine" | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo "$basic_machine" | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases that might get confused + # with valid system types. + # -solaris* is a basic system type, with this one exception. + -auroraux) + os=-auroraux + ;; + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # es1800 is here to avoid being matched by es* (a different OS) + -es1800*) + os=-ose + ;; + # Now accept the basic system types. + # The portable systems comes first. + # Each alternative MUST end in a * to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ + | -sym* | -kopensolaris* | -plan9* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* | -aros* | -cloudabi* | -sortix* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -knetbsd* | -mirbsd* | -netbsd* \ + | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* | -cegcc* | -glidix* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \ + | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox* | -bme* \ + | -midnightbsd*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -xray | -os68k* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo "$os" | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo "$os" | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo "$os" | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4*) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -zvmoe) + os=-zvmoe + ;; + -dicos*) + os=-dicos + ;; + -pikeos*) + # Until real need of OS specific support for + # particular features comes up, bare metal + # configurations are quite functional. + case $basic_machine in + arm*) + os=-eabi + ;; + *) + os=-elf + ;; + esac + ;; + -nacl*) + ;; + -ios) + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`"$1"\': system \`"$os"\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + c8051-*) + os=-elf + ;; + hexagon-*) + os=-elf + ;; + tic54x-*) + os=-coff + ;; + tic55x-*) + os=-coff + ;; + tic6x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + pru-*) + os=-elf + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -cnk*|-aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo "$basic_machine" | sed "s/unknown/$vendor/"` + ;; +esac + +echo "$basic_machine$os" +exit + +# Local variables: +# eval: (add-hook 'write-file-functions 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/build-aux/depcomp b/build-aux/depcomp new file mode 100755 index 0000000..65cbf70 --- /dev/null +++ b/build-aux/depcomp @@ -0,0 +1,791 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputting dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +# Get the directory component of the given path, and save it in the +# global variables '$dir'. Note that this directory component will +# be either empty or ending with a '/' character. This is deliberate. +set_dir_from () +{ + case $1 in + */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; + *) dir=;; + esac +} + +# Get the suffix-stripped basename of the given path, and save it the +# global variable '$base'. +set_base_from () +{ + base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` +} + +# If no dependency file was actually created by the compiler invocation, +# we still have to create a dummy depfile, to avoid errors with the +# Makefile "include basename.Plo" scheme. +make_dummy_depfile () +{ + echo "#dummy" > "$depfile" +} + +# Factor out some common post-processing of the generated depfile. +# Requires the auxiliary global variable '$tmpdepfile' to be set. +aix_post_process_depfile () +{ + # If the compiler actually managed to produce a dependency file, + # post-process it. + if test -f "$tmpdepfile"; then + # Each line is of the form 'foo.o: dependency.h'. + # Do two passes, one to just change these to + # $object: dependency.h + # and one to simply output + # dependency.h: + # which is needed to avoid the deleted-header problem. + { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" + sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" + } > "$depfile" + rm -f "$tmpdepfile" + else + make_dummy_depfile + fi +} + +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' +# Character ranges might be problematic outside the C locale. +# These definitions help. +upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ +lower=abcdefghijklmnopqrstuvwxyz +digits=0123456789 +alpha=${upper}${lower} + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Avoid interferences from the environment. +gccflag= dashmflag= + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvisualcpp +fi + +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The second -e expression handles DOS-style file names with drive + # letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the "deleted header file" problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. +## Some versions of gcc put a space before the ':'. On the theory +## that the space means something, we add a space to the output as +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like '#:fec' to the end of the + # dependency line. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ + | tr "$nl" ' ' >> "$depfile" + echo >> "$depfile" + # The second pass generates a dummy entry for each header file. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" + ;; + +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts '$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + aix_post_process_depfile + ;; + +tcc) + # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 + # FIXME: That version still under development at the moment of writing. + # Make that this statement remains true also for stable, released + # versions. + # It will wrap lines (doesn't matter whether long or short) with a + # trailing '\', as in: + # + # foo.o : \ + # foo.c \ + # foo.h \ + # + # It will put a trailing '\' even on the last line, and will use leading + # spaces rather than leading tabs (at least since its commit 0394caf7 + # "Emit spaces for -MD"). + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. + # We have to change lines of the first kind to '$object: \'. + sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" + # And for each line of the second kind, we have to emit a 'dep.h:' + # dummy dependency, to avoid the deleted-header problem. + sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" + rm -f "$tmpdepfile" + ;; + +## The order of this option in the case statement is important, since the +## shell code in configure will try each of these formats in the order +## listed in this file. A plain '-MD' option would be understood by many +## compilers, so we must ensure this comes after the gcc and icc options. +pgcc) + # Portland's C compiler understands '-MD'. + # Will always output deps to 'file.d' where file is the root name of the + # source file under compilation, even if file resides in a subdirectory. + # The object file name does not affect the name of the '.d' file. + # pgcc 10.2 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using '\' : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + set_dir_from "$object" + # Use the source, not the object, to determine the base name, since + # that's sadly what pgcc will do too. + set_base_from "$source" + tmpdepfile=$base.d + + # For projects that build the same source file twice into different object + # files, the pgcc approach of using the *source* file root name can cause + # problems in parallel builds. Use a locking strategy to avoid stomping on + # the same $tmpdepfile. + lockdir=$base.d-lock + trap " + echo '$0: caught signal, cleaning up...' >&2 + rmdir '$lockdir' + exit 1 + " 1 2 13 15 + numtries=100 + i=$numtries + while test $i -gt 0; do + # mkdir is a portable test-and-set. + if mkdir "$lockdir" 2>/dev/null; then + # This process acquired the lock. + "$@" -MD + stat=$? + # Release the lock. + rmdir "$lockdir" + break + else + # If the lock is being held by a different process, wait + # until the winning process is done or we timeout. + while test -d "$lockdir" && test $i -gt 0; do + sleep 1 + i=`expr $i - 1` + done + fi + i=`expr $i - 1` + done + trap - 1 2 13 15 + if test $i -le 0; then + echo "$0: failed to acquire lock after $numtries attempts" >&2 + echo "$0: check lockdir '$lockdir'" >&2 + exit 1 + fi + + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" + # Add 'dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in 'foo.d' instead, so we check for that too. + # Subdirectories are respected. + set_dir_from "$object" + set_base_from "$object" + + if test "$libtool" = yes; then + # Libtool generates 2 separate objects for the 2 libraries. These + # two compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir$base.o.d # libtool 1.5 + tmpdepfile2=$dir.libs/$base.o.d # Likewise. + tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + # Same post-processing that is required for AIX mode. + aix_post_process_depfile + ;; + +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + echo >> "$depfile" # make sure the fragment doesn't end with a backslash + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for ':' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. + "$@" $dashmflag | + sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this sed invocation + # correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process the last invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed '1,2d' "$tmpdepfile" \ + | tr ' ' "$nl" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E \ + | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + | sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/install-sh b/build-aux/install-sh new file mode 100755 index 0000000..8175c64 --- /dev/null +++ b/build-aux/install-sh @@ -0,0 +1,518 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2018-03-11.20; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" + +# Set DOITPROG to "echo" to test this script. + +doit=${DOITPROG-} +doit_exec=${doit:-exec} + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +is_target_a_directory=possibly + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) is_target_a_directory=never;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dstbase=`basename "$src"` + case $dst in + */) dst=$dst$dstbase;; + *) dst=$dst/$dstbase;; + esac + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + case $dstdir in + */) dstdirslash=$dstdir;; + *) dstdirslash=$dstdir/;; + esac + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + # Note that $RANDOM variable is not portable (e.g. dash); Use it + # here however when possible just to lower collision chance. + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + + trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 + + # Because "mkdir -p" follows existing symlinks and we likely work + # directly in world-writeable /tmp, make sure that the '$tmpdir' + # directory is successfully created first before we actually test + # 'mkdir -p' feature. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=${dstdirslash}_inst.$$_ + rmtmp=${dstdirslash}_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/ltmain.sh b/build-aux/ltmain.sh new file mode 100644 index 0000000..0cb7f90 --- /dev/null +++ b/build-aux/ltmain.sh @@ -0,0 +1,11251 @@ +#! /bin/sh +## DO NOT EDIT - This file generated from ./build-aux/ltmain.in +## by inline-source v2014-01-03.01 + +# libtool (GNU libtool) 2.4.6 +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit , 1996 + +# Copyright (C) 1996-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, +# if you distribute this file as part of a program or library that +# is built using GNU Libtool, you may include this file under the +# same distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +PROGRAM=libtool +PACKAGE=libtool +VERSION="2.4.6 Debian-2.4.6-14" +package_revision=2.4.6 + + +## ------ ## +## Usage. ## +## ------ ## + +# Run './libtool --help' for help with using this script from the +# command line. + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# After configure completes, it has a better idea of some of the +# shell tools we need than the defaults used by the functions shared +# with bootstrap, so set those here where they can still be over- +# ridden by the user, but otherwise take precedence. + +: ${AUTOCONF="autoconf"} +: ${AUTOMAKE="automake"} + + +## -------------------------- ## +## Source external libraries. ## +## -------------------------- ## + +# Much of our low-level functionality needs to be sourced from external +# libraries, which are installed to $pkgauxdir. + +# Set a version string for this script. +scriptversion=2015-01-20.17; # UTC + +# General shell script boiler plate, and helper functions. +# Written by Gary V. Vaughan, 2004 + +# Copyright (C) 2004-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +# As a special exception to the GNU General Public License, if you distribute +# this file as part of a program or library that is built using GNU Libtool, +# you may include this file under the same distribution terms that you use +# for the rest of that program. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# Evaluate this file near the top of your script to gain access to +# the functions and variables defined here: +# +# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh +# +# If you need to override any of the default environment variable +# settings, do that before evaluating this file. + + +## -------------------- ## +## Shell normalisation. ## +## -------------------- ## + +# Some shells need a little help to be as Bourne compatible as possible. +# Before doing anything else, make sure all that help has been provided! + +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac +fi + +# NLS nuisances: We save the old values in case they are required later. +_G_user_locale= +_G_safe_locale= +for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES +do + eval "if test set = \"\${$_G_var+set}\"; then + save_$_G_var=\$$_G_var + $_G_var=C + export $_G_var + _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" + _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" + fi" +done + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Make sure IFS has a sensible default +sp=' ' +nl=' +' +IFS="$sp $nl" + +# There are apparently some retarded systems that use ';' as a PATH separator! +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + + +## ------------------------- ## +## Locate command utilities. ## +## ------------------------- ## + + +# func_executable_p FILE +# ---------------------- +# Check that FILE is an executable regular file. +func_executable_p () +{ + test -f "$1" && test -x "$1" +} + + +# func_path_progs PROGS_LIST CHECK_FUNC [PATH] +# -------------------------------------------- +# Search for either a program that responds to --version with output +# containing "GNU", or else returned by CHECK_FUNC otherwise, by +# trying all the directories in PATH with each of the elements of +# PROGS_LIST. +# +# CHECK_FUNC should accept the path to a candidate program, and +# set $func_check_prog_result if it truncates its output less than +# $_G_path_prog_max characters. +func_path_progs () +{ + _G_progs_list=$1 + _G_check_func=$2 + _G_PATH=${3-"$PATH"} + + _G_path_prog_max=0 + _G_path_prog_found=false + _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:} + for _G_dir in $_G_PATH; do + IFS=$_G_save_IFS + test -z "$_G_dir" && _G_dir=. + for _G_prog_name in $_G_progs_list; do + for _exeext in '' .EXE; do + _G_path_prog=$_G_dir/$_G_prog_name$_exeext + func_executable_p "$_G_path_prog" || continue + case `"$_G_path_prog" --version 2>&1` in + *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; + *) $_G_check_func $_G_path_prog + func_path_progs_result=$func_check_prog_result + ;; + esac + $_G_path_prog_found && break 3 + done + done + done + IFS=$_G_save_IFS + test -z "$func_path_progs_result" && { + echo "no acceptable sed could be found in \$PATH" >&2 + exit 1 + } +} + + +# We want to be able to use the functions in this file before configure +# has figured out where the best binaries are kept, which means we have +# to search for them ourselves - except when the results are already set +# where we skip the searches. + +# Unless the user overrides by setting SED, search the path for either GNU +# sed, or the sed that truncates its output the least. +test -z "$SED" && { + _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for _G_i in 1 2 3 4 5 6 7; do + _G_sed_script=$_G_sed_script$nl$_G_sed_script + done + echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed + _G_sed_script= + + func_check_prog_sed () + { + _G_path_prog=$1 + + _G_count=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo '' >> conftest.nl + "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin + rm -f conftest.sed + SED=$func_path_progs_result +} + + +# Unless the user overrides by setting GREP, search the path for either GNU +# grep, or the grep that truncates its output the least. +test -z "$GREP" && { + func_check_prog_grep () + { + _G_path_prog=$1 + + _G_count=0 + _G_path_prog_max=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo 'GREP' >> conftest.nl + "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin + GREP=$func_path_progs_result +} + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# All uppercase variable names are used for environment variables. These +# variables can be overridden by the user before calling a script that +# uses them if a suitable command of that name is not already available +# in the command search PATH. + +: ${CP="cp -f"} +: ${ECHO="printf %s\n"} +: ${EGREP="$GREP -E"} +: ${FGREP="$GREP -F"} +: ${LN_S="ln -s"} +: ${MAKE="make"} +: ${MKDIR="mkdir"} +: ${MV="mv -f"} +: ${RM="rm -f"} +: ${SHELL="${CONFIG_SHELL-/bin/sh}"} + + +## -------------------- ## +## Useful sed snippets. ## +## -------------------- ## + +sed_dirname='s|/[^/]*$||' +sed_basename='s|^.*/||' + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s|\([`"$\\]\)|\\\1|g' + +# Same as above, but do not quote variable references. +sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution that turns a string into a regex matching for the +# string literally. +sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' + +# Sed substitution that converts a w32 file name or path +# that contains forward slashes, into one that contains +# (escaped) backslashes. A very naive implementation. +sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' + +# Re-'\' parameter expansions in output of sed_double_quote_subst that +# were '\'-ed in input to the same. If an odd number of '\' preceded a +# '$' in input to sed_double_quote_subst, that '$' was protected from +# expansion. Since each input '\' is now two '\'s, look for any number +# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. +_G_bs='\\' +_G_bs2='\\\\' +_G_bs4='\\\\\\\\' +_G_dollar='\$' +sed_double_backslash="\ + s/$_G_bs4/&\\ +/g + s/^$_G_bs2$_G_dollar/$_G_bs&/ + s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g + s/\n//g" + + +## ----------------- ## +## Global variables. ## +## ----------------- ## + +# Except for the global variables explicitly listed below, the following +# functions in the '^func_' namespace, and the '^require_' namespace +# variables initialised in the 'Resource management' section, sourcing +# this file will not pollute your global namespace with anything +# else. There's no portable way to scope variables in Bourne shell +# though, so actually running these functions will sometimes place +# results into a variable named after the function, and often use +# temporary variables in the '^_G_' namespace. If you are careful to +# avoid using those namespaces casually in your sourcing script, things +# should continue to work as you expect. And, of course, you can freely +# overwrite any of the functions or variables defined here before +# calling anything to customize them. + +EXIT_SUCCESS=0 +EXIT_FAILURE=1 +EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. +EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. + +# Allow overriding, eg assuming that you follow the convention of +# putting '$debug_cmd' at the start of all your functions, you can get +# bash to show function call trace with: +# +# debug_cmd='echo "${FUNCNAME[0]} $*" >&2' bash your-script-name +debug_cmd=${debug_cmd-":"} +exit_cmd=: + +# By convention, finish your script with: +# +# exit $exit_status +# +# so that you can set exit_status to non-zero if you want to indicate +# something went wrong during execution without actually bailing out at +# the point of failure. +exit_status=$EXIT_SUCCESS + +# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh +# is ksh but when the shell is invoked as "sh" and the current value of +# the _XPG environment variable is not equal to 1 (one), the special +# positional parameter $0, within a function call, is the name of the +# function. +progpath=$0 + +# The name of this program. +progname=`$ECHO "$progpath" |$SED "$sed_basename"` + +# Make sure we have an absolute progpath for reexecution: +case $progpath in + [\\/]*|[A-Za-z]:\\*) ;; + *[\\/]*) + progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` + progdir=`cd "$progdir" && pwd` + progpath=$progdir/$progname + ;; + *) + _G_IFS=$IFS + IFS=${PATH_SEPARATOR-:} + for progdir in $PATH; do + IFS=$_G_IFS + test -x "$progdir/$progname" && break + done + IFS=$_G_IFS + test -n "$progdir" || progdir=`pwd` + progpath=$progdir/$progname + ;; +esac + + +## ----------------- ## +## Standard options. ## +## ----------------- ## + +# The following options affect the operation of the functions defined +# below, and should be set appropriately depending on run-time para- +# meters passed on the command line. + +opt_dry_run=false +opt_quiet=false +opt_verbose=false + +# Categories 'all' and 'none' are always available. Append any others +# you will pass as the first argument to func_warning from your own +# code. +warning_categories= + +# By default, display warnings according to 'opt_warning_types'. Set +# 'warning_func' to ':' to elide all warnings, or func_fatal_error to +# treat the next displayed warning as a fatal error. +warning_func=func_warn_and_continue + +# Set to 'all' to display all warnings, 'none' to suppress all +# warnings, or a space delimited list of some subset of +# 'warning_categories' to display only the listed warnings. +opt_warning_types=all + + +## -------------------- ## +## Resource management. ## +## -------------------- ## + +# This section contains definitions for functions that each ensure a +# particular resource (a file, or a non-empty configuration variable for +# example) is available, and if appropriate to extract default values +# from pertinent package files. Call them using their associated +# 'require_*' variable to ensure that they are executed, at most, once. +# +# It's entirely deliberate that calling these functions can set +# variables that don't obey the namespace limitations obeyed by the rest +# of this file, in order that that they be as useful as possible to +# callers. + + +# require_term_colors +# ------------------- +# Allow display of bold text on terminals that support it. +require_term_colors=func_require_term_colors +func_require_term_colors () +{ + $debug_cmd + + test -t 1 && { + # COLORTERM and USE_ANSI_COLORS environment variables take + # precedence, because most terminfo databases neglect to describe + # whether color sequences are supported. + test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} + + if test 1 = "$USE_ANSI_COLORS"; then + # Standard ANSI escape sequences + tc_reset='' + tc_bold=''; tc_standout='' + tc_red=''; tc_green='' + tc_blue=''; tc_cyan='' + else + # Otherwise trust the terminfo database after all. + test -n "`tput sgr0 2>/dev/null`" && { + tc_reset=`tput sgr0` + test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` + tc_standout=$tc_bold + test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` + test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` + test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` + test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` + test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` + } + fi + } + + require_term_colors=: +} + + +## ----------------- ## +## Function library. ## +## ----------------- ## + +# This section contains a variety of useful functions to call in your +# scripts. Take note of the portable wrappers for features provided by +# some modern shells, which will fall back to slower equivalents on +# less featureful shells. + + +# func_append VAR VALUE +# --------------------- +# Append VALUE onto the existing contents of VAR. + + # We should try to minimise forks, especially on Windows where they are + # unreasonably slow, so skip the feature probes when bash or zsh are + # being used: + if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then + : ${_G_HAVE_ARITH_OP="yes"} + : ${_G_HAVE_XSI_OPS="yes"} + # The += operator was introduced in bash 3.1 + case $BASH_VERSION in + [12].* | 3.0 | 3.0*) ;; + *) + : ${_G_HAVE_PLUSEQ_OP="yes"} + ;; + esac + fi + + # _G_HAVE_PLUSEQ_OP + # Can be empty, in which case the shell is probed, "yes" if += is + # useable or anything else if it does not work. + test -z "$_G_HAVE_PLUSEQ_OP" \ + && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ + && _G_HAVE_PLUSEQ_OP=yes + +if test yes = "$_G_HAVE_PLUSEQ_OP" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_append () + { + $debug_cmd + + eval "$1+=\$2" + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_append () + { + $debug_cmd + + eval "$1=\$$1\$2" + } +fi + + +# func_append_quoted VAR VALUE +# ---------------------------- +# Quote VALUE and append to the end of shell variable VAR, separated +# by a space. +if test yes = "$_G_HAVE_PLUSEQ_OP"; then + eval 'func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1+=\\ \$func_quote_for_eval_result" + }' +else + func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1=\$$1\\ \$func_quote_for_eval_result" + } +fi + + +# func_append_uniq VAR VALUE +# -------------------------- +# Append unique VALUE onto the existing contents of VAR, assuming +# entries are delimited by the first character of VALUE. For example: +# +# func_append_uniq options " --another-option option-argument" +# +# will only append to $options if " --another-option option-argument " +# is not already present somewhere in $options already (note spaces at +# each end implied by leading space in second argument). +func_append_uniq () +{ + $debug_cmd + + eval _G_current_value='`$ECHO $'$1'`' + _G_delim=`expr "$2" : '\(.\)'` + + case $_G_delim$_G_current_value$_G_delim in + *"$2$_G_delim"*) ;; + *) func_append "$@" ;; + esac +} + + +# func_arith TERM... +# ------------------ +# Set func_arith_result to the result of evaluating TERMs. + test -z "$_G_HAVE_ARITH_OP" \ + && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ + && _G_HAVE_ARITH_OP=yes + +if test yes = "$_G_HAVE_ARITH_OP"; then + eval 'func_arith () + { + $debug_cmd + + func_arith_result=$(( $* )) + }' +else + func_arith () + { + $debug_cmd + + func_arith_result=`expr "$@"` + } +fi + + +# func_basename FILE +# ------------------ +# Set func_basename_result to FILE with everything up to and including +# the last / stripped. +if test yes = "$_G_HAVE_XSI_OPS"; then + # If this shell supports suffix pattern removal, then use it to avoid + # forking. Hide the definitions single quotes in case the shell chokes + # on unsupported syntax... + _b='func_basename_result=${1##*/}' + _d='case $1 in + */*) func_dirname_result=${1%/*}$2 ;; + * ) func_dirname_result=$3 ;; + esac' + +else + # ...otherwise fall back to using sed. + _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' + _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` + if test "X$func_dirname_result" = "X$1"; then + func_dirname_result=$3 + else + func_append func_dirname_result "$2" + fi' +fi + +eval 'func_basename () +{ + $debug_cmd + + '"$_b"' +}' + + +# func_dirname FILE APPEND NONDIR_REPLACEMENT +# ------------------------------------------- +# Compute the dirname of FILE. If nonempty, add APPEND to the result, +# otherwise set result to NONDIR_REPLACEMENT. +eval 'func_dirname () +{ + $debug_cmd + + '"$_d"' +}' + + +# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT +# -------------------------------------------------------- +# Perform func_basename and func_dirname in a single function +# call: +# dirname: Compute the dirname of FILE. If nonempty, +# add APPEND to the result, otherwise set result +# to NONDIR_REPLACEMENT. +# value returned in "$func_dirname_result" +# basename: Compute filename of FILE. +# value retuned in "$func_basename_result" +# For efficiency, we do not delegate to the functions above but instead +# duplicate the functionality here. +eval 'func_dirname_and_basename () +{ + $debug_cmd + + '"$_b"' + '"$_d"' +}' + + +# func_echo ARG... +# ---------------- +# Echo program name prefixed message. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_echo_all ARG... +# -------------------- +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + + +# func_echo_infix_1 INFIX ARG... +# ------------------------------ +# Echo program name, followed by INFIX on the first line, with any +# additional lines not showing INFIX. +func_echo_infix_1 () +{ + $debug_cmd + + $require_term_colors + + _G_infix=$1; shift + _G_indent=$_G_infix + _G_prefix="$progname: $_G_infix: " + _G_message=$* + + # Strip color escape sequences before counting printable length + for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" + do + test -n "$_G_tc" && { + _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` + _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` + } + done + _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes + + func_echo_infix_1_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_infix_1_IFS + $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 + _G_prefix=$_G_indent + done + IFS=$func_echo_infix_1_IFS +} + + +# func_error ARG... +# ----------------- +# Echo program name prefixed message to standard error. +func_error () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 +} + + +# func_fatal_error ARG... +# ----------------------- +# Echo program name prefixed message to standard error, and exit. +func_fatal_error () +{ + $debug_cmd + + func_error "$*" + exit $EXIT_FAILURE +} + + +# func_grep EXPRESSION FILENAME +# ----------------------------- +# Check whether EXPRESSION matches any line of FILENAME, without output. +func_grep () +{ + $debug_cmd + + $GREP "$1" "$2" >/dev/null 2>&1 +} + + +# func_len STRING +# --------------- +# Set func_len_result to the length of STRING. STRING may not +# start with a hyphen. + test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_len () + { + $debug_cmd + + func_len_result=${#1} + }' +else + func_len () + { + $debug_cmd + + func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` + } +fi + + +# func_mkdir_p DIRECTORY-PATH +# --------------------------- +# Make sure the entire path to DIRECTORY-PATH is available. +func_mkdir_p () +{ + $debug_cmd + + _G_directory_path=$1 + _G_dir_list= + + if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then + + # Protect directory names starting with '-' + case $_G_directory_path in + -*) _G_directory_path=./$_G_directory_path ;; + esac + + # While some portion of DIR does not yet exist... + while test ! -d "$_G_directory_path"; do + # ...make a list in topmost first order. Use a colon delimited + # list incase some portion of path contains whitespace. + _G_dir_list=$_G_directory_path:$_G_dir_list + + # If the last portion added has no slash in it, the list is done + case $_G_directory_path in */*) ;; *) break ;; esac + + # ...otherwise throw away the child directory and loop + _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` + done + _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` + + func_mkdir_p_IFS=$IFS; IFS=: + for _G_dir in $_G_dir_list; do + IFS=$func_mkdir_p_IFS + # mkdir can fail with a 'File exist' error if two processes + # try to create one of the directories concurrently. Don't + # stop in that case! + $MKDIR "$_G_dir" 2>/dev/null || : + done + IFS=$func_mkdir_p_IFS + + # Bail out if we (or some other process) failed to create a directory. + test -d "$_G_directory_path" || \ + func_fatal_error "Failed to create '$1'" + fi +} + + +# func_mktempdir [BASENAME] +# ------------------------- +# Make a temporary directory that won't clash with other running +# libtool processes, and avoids race conditions if possible. If +# given, BASENAME is the basename for that directory. +func_mktempdir () +{ + $debug_cmd + + _G_template=${TMPDIR-/tmp}/${1-$progname} + + if test : = "$opt_dry_run"; then + # Return a directory name, but don't create it in dry-run mode + _G_tmpdir=$_G_template-$$ + else + + # If mktemp works, use that first and foremost + _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` + + if test ! -d "$_G_tmpdir"; then + # Failing that, at least try and use $RANDOM to avoid a race + _G_tmpdir=$_G_template-${RANDOM-0}$$ + + func_mktempdir_umask=`umask` + umask 0077 + $MKDIR "$_G_tmpdir" + umask $func_mktempdir_umask + fi + + # If we're not in dry-run mode, bomb out on failure + test -d "$_G_tmpdir" || \ + func_fatal_error "cannot create temporary directory '$_G_tmpdir'" + fi + + $ECHO "$_G_tmpdir" +} + + +# func_normal_abspath PATH +# ------------------------ +# Remove doubled-up and trailing slashes, "." path components, +# and cancel out any ".." path components in PATH after making +# it an absolute path. +func_normal_abspath () +{ + $debug_cmd + + # These SED scripts presuppose an absolute path with a trailing slash. + _G_pathcar='s|^/\([^/]*\).*$|\1|' + _G_pathcdr='s|^/[^/]*||' + _G_removedotparts=':dotsl + s|/\./|/|g + t dotsl + s|/\.$|/|' + _G_collapseslashes='s|/\{1,\}|/|g' + _G_finalslash='s|/*$|/|' + + # Start from root dir and reassemble the path. + func_normal_abspath_result= + func_normal_abspath_tpath=$1 + func_normal_abspath_altnamespace= + case $func_normal_abspath_tpath in + "") + # Empty path, that just means $cwd. + func_stripname '' '/' "`pwd`" + func_normal_abspath_result=$func_stripname_result + return + ;; + # The next three entries are used to spot a run of precisely + # two leading slashes without using negated character classes; + # we take advantage of case's first-match behaviour. + ///*) + # Unusual form of absolute path, do nothing. + ;; + //*) + # Not necessarily an ordinary path; POSIX reserves leading '//' + # and for example Cygwin uses it to access remote file shares + # over CIFS/SMB, so we conserve a leading double slash if found. + func_normal_abspath_altnamespace=/ + ;; + /*) + # Absolute path, do nothing. + ;; + *) + # Relative path, prepend $cwd. + func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath + ;; + esac + + # Cancel out all the simple stuff to save iterations. We also want + # the path to end with a slash for ease of parsing, so make sure + # there is one (and only one) here. + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` + while :; do + # Processed it all yet? + if test / = "$func_normal_abspath_tpath"; then + # If we ascended to the root using ".." the result may be empty now. + if test -z "$func_normal_abspath_result"; then + func_normal_abspath_result=/ + fi + break + fi + func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcar"` + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcdr"` + # Figure out what to do with it + case $func_normal_abspath_tcomponent in + "") + # Trailing empty path component, ignore it. + ;; + ..) + # Parent dir; strip last assembled component from result. + func_dirname "$func_normal_abspath_result" + func_normal_abspath_result=$func_dirname_result + ;; + *) + # Actual path component, append it. + func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" + ;; + esac + done + # Restore leading double-slash if one was found on entry. + func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result +} + + +# func_notquiet ARG... +# -------------------- +# Echo program name prefixed message only when not in quiet mode. +func_notquiet () +{ + $debug_cmd + + $opt_quiet || func_echo ${1+"$@"} + + # A bug in bash halts the script if the last line of a function + # fails when set -e is in force, so we need another command to + # work around that: + : +} + + +# func_relative_path SRCDIR DSTDIR +# -------------------------------- +# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. +func_relative_path () +{ + $debug_cmd + + func_relative_path_result= + func_normal_abspath "$1" + func_relative_path_tlibdir=$func_normal_abspath_result + func_normal_abspath "$2" + func_relative_path_tbindir=$func_normal_abspath_result + + # Ascend the tree starting from libdir + while :; do + # check if we have found a prefix of bindir + case $func_relative_path_tbindir in + $func_relative_path_tlibdir) + # found an exact match + func_relative_path_tcancelled= + break + ;; + $func_relative_path_tlibdir*) + # found a matching prefix + func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" + func_relative_path_tcancelled=$func_stripname_result + if test -z "$func_relative_path_result"; then + func_relative_path_result=. + fi + break + ;; + *) + func_dirname $func_relative_path_tlibdir + func_relative_path_tlibdir=$func_dirname_result + if test -z "$func_relative_path_tlibdir"; then + # Have to descend all the way to the root! + func_relative_path_result=../$func_relative_path_result + func_relative_path_tcancelled=$func_relative_path_tbindir + break + fi + func_relative_path_result=../$func_relative_path_result + ;; + esac + done + + # Now calculate path; take care to avoid doubling-up slashes. + func_stripname '' '/' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + func_stripname '/' '/' "$func_relative_path_tcancelled" + if test -n "$func_stripname_result"; then + func_append func_relative_path_result "/$func_stripname_result" + fi + + # Normalisation. If bindir is libdir, return '.' else relative path. + if test -n "$func_relative_path_result"; then + func_stripname './' '' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + fi + + test -n "$func_relative_path_result" || func_relative_path_result=. + + : +} + + +# func_quote_for_eval ARG... +# -------------------------- +# Aesthetically quote ARGs to be evaled later. +# This function returns two values: +# i) func_quote_for_eval_result +# double-quoted, suitable for a subsequent eval +# ii) func_quote_for_eval_unquoted_result +# has all characters that are still active within double +# quotes backslashified. +func_quote_for_eval () +{ + $debug_cmd + + func_quote_for_eval_unquoted_result= + func_quote_for_eval_result= + while test 0 -lt $#; do + case $1 in + *[\\\`\"\$]*) + _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; + *) + _G_unquoted_arg=$1 ;; + esac + if test -n "$func_quote_for_eval_unquoted_result"; then + func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" + else + func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" + fi + + case $_G_unquoted_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting, command substitution and variable expansion + # for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_quoted_arg=\"$_G_unquoted_arg\" + ;; + *) + _G_quoted_arg=$_G_unquoted_arg + ;; + esac + + if test -n "$func_quote_for_eval_result"; then + func_append func_quote_for_eval_result " $_G_quoted_arg" + else + func_append func_quote_for_eval_result "$_G_quoted_arg" + fi + shift + done +} + + +# func_quote_for_expand ARG +# ------------------------- +# Aesthetically quote ARG to be evaled later; same as above, +# but do not quote variable references. +func_quote_for_expand () +{ + $debug_cmd + + case $1 in + *[\\\`\"]*) + _G_arg=`$ECHO "$1" | $SED \ + -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; + *) + _G_arg=$1 ;; + esac + + case $_G_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting and command substitution for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_arg=\"$_G_arg\" + ;; + esac + + func_quote_for_expand_result=$_G_arg +} + + +# func_stripname PREFIX SUFFIX NAME +# --------------------------------- +# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_stripname () + { + $debug_cmd + + # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are + # positional parameters, so assign one to ordinary variable first. + func_stripname_result=$3 + func_stripname_result=${func_stripname_result#"$1"} + func_stripname_result=${func_stripname_result%"$2"} + }' +else + func_stripname () + { + $debug_cmd + + case $2 in + .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; + *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; + esac + } +fi + + +# func_show_eval CMD [FAIL_EXP] +# ----------------------------- +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. +func_show_eval () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + func_quote_for_expand "$_G_cmd" + eval "func_notquiet $func_quote_for_expand_result" + + $opt_dry_run || { + eval "$_G_cmd" + _G_status=$? + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_show_eval_locale CMD [FAIL_EXP] +# ------------------------------------ +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. Use the saved locale for evaluation. +func_show_eval_locale () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + $opt_quiet || { + func_quote_for_expand "$_G_cmd" + eval "func_echo $func_quote_for_expand_result" + } + + $opt_dry_run || { + eval "$_G_user_locale + $_G_cmd" + _G_status=$? + eval "$_G_safe_locale" + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_tr_sh +# ---------- +# Turn $1 into a string suitable for a shell variable name. +# Result is stored in $func_tr_sh_result. All characters +# not in the set a-zA-Z0-9_ are replaced with '_'. Further, +# if $1 begins with a digit, a '_' is prepended as well. +func_tr_sh () +{ + $debug_cmd + + case $1 in + [0-9]* | *[!a-zA-Z0-9_]*) + func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` + ;; + * ) + func_tr_sh_result=$1 + ;; + esac +} + + +# func_verbose ARG... +# ------------------- +# Echo program name prefixed message in verbose mode only. +func_verbose () +{ + $debug_cmd + + $opt_verbose && func_echo "$*" + + : +} + + +# func_warn_and_continue ARG... +# ----------------------------- +# Echo program name prefixed warning message to standard error. +func_warn_and_continue () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 +} + + +# func_warning CATEGORY ARG... +# ---------------------------- +# Echo program name prefixed warning message to standard error. Warning +# messages can be filtered according to CATEGORY, where this function +# elides messages where CATEGORY is not listed in the global variable +# 'opt_warning_types'. +func_warning () +{ + $debug_cmd + + # CATEGORY must be in the warning_categories list! + case " $warning_categories " in + *" $1 "*) ;; + *) func_internal_error "invalid warning category '$1'" ;; + esac + + _G_category=$1 + shift + + case " $opt_warning_types " in + *" $_G_category "*) $warning_func ${1+"$@"} ;; + esac +} + + +# func_sort_ver VER1 VER2 +# ----------------------- +# 'sort -V' is not generally available. +# Note this deviates from the version comparison in automake +# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a +# but this should suffice as we won't be specifying old +# version formats or redundant trailing .0 in bootstrap.conf. +# If we did want full compatibility then we should probably +# use m4_version_compare from autoconf. +func_sort_ver () +{ + $debug_cmd + + printf '%s\n%s\n' "$1" "$2" \ + | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n +} + +# func_lt_ver PREV CURR +# --------------------- +# Return true if PREV and CURR are in the correct order according to +# func_sort_ver, otherwise false. Use it like this: +# +# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." +func_lt_ver () +{ + $debug_cmd + + test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: +#! /bin/sh + +# Set a version string for this script. +scriptversion=2015-10-07.11; # UTC + +# A portable, pluggable option parser for Bourne shell. +# Written by Gary V. Vaughan, 2010 + +# Copyright (C) 2010-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# This file is a library for parsing options in your shell scripts along +# with assorted other useful supporting features that you can make use +# of too. +# +# For the simplest scripts you might need only: +# +# #!/bin/sh +# . relative/path/to/funclib.sh +# . relative/path/to/options-parser +# scriptversion=1.0 +# func_options ${1+"$@"} +# eval set dummy "$func_options_result"; shift +# ...rest of your script... +# +# In order for the '--version' option to work, you will need to have a +# suitably formatted comment like the one at the top of this file +# starting with '# Written by ' and ending with '# warranty; '. +# +# For '-h' and '--help' to work, you will also need a one line +# description of your script's purpose in a comment directly above the +# '# Written by ' line, like the one at the top of this file. +# +# The default options also support '--debug', which will turn on shell +# execution tracing (see the comment above debug_cmd below for another +# use), and '--verbose' and the func_verbose function to allow your script +# to display verbose messages only when your user has specified +# '--verbose'. +# +# After sourcing this file, you can plug processing for additional +# options by amending the variables from the 'Configuration' section +# below, and following the instructions in the 'Option parsing' +# section further down. + +## -------------- ## +## Configuration. ## +## -------------- ## + +# You should override these variables in your script after sourcing this +# file so that they reflect the customisations you have added to the +# option parser. + +# The usage line for option parsing errors and the start of '-h' and +# '--help' output messages. You can embed shell variables for delayed +# expansion at the time the message is displayed, but you will need to +# quote other shell meta-characters carefully to prevent them being +# expanded when the contents are evaled. +usage='$progpath [OPTION]...' + +# Short help message in response to '-h' and '--help'. Add to this or +# override it after sourcing this library to reflect the full set of +# options your script accepts. +usage_message="\ + --debug enable verbose shell tracing + -W, --warnings=CATEGORY + report the warnings falling in CATEGORY [all] + -v, --verbose verbosely report processing + --version print version information and exit + -h, --help print short or long help message and exit +" + +# Additional text appended to 'usage_message' in response to '--help'. +long_help_message=" +Warning categories include: + 'all' show all warnings + 'none' turn off all the warnings + 'error' warnings are treated as fatal errors" + +# Help message printed before fatal option parsing errors. +fatal_help="Try '\$progname --help' for more information." + + + +## ------------------------- ## +## Hook function management. ## +## ------------------------- ## + +# This section contains functions for adding, removing, and running hooks +# to the main code. A hook is just a named list of of function, that can +# be run in order later on. + +# func_hookable FUNC_NAME +# ----------------------- +# Declare that FUNC_NAME will run hooks added with +# 'func_add_hook FUNC_NAME ...'. +func_hookable () +{ + $debug_cmd + + func_append hookable_fns " $1" +} + + +# func_add_hook FUNC_NAME HOOK_FUNC +# --------------------------------- +# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must +# first have been declared "hookable" by a call to 'func_hookable'. +func_add_hook () +{ + $debug_cmd + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not accept hook functions." ;; + esac + + eval func_append ${1}_hooks '" $2"' +} + + +# func_remove_hook FUNC_NAME HOOK_FUNC +# ------------------------------------ +# Remove HOOK_FUNC from the list of functions called by FUNC_NAME. +func_remove_hook () +{ + $debug_cmd + + eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' +} + + +# func_run_hooks FUNC_NAME [ARG]... +# --------------------------------- +# Run all hook functions registered to FUNC_NAME. +# It is assumed that the list of hook functions contains nothing more +# than a whitespace-delimited list of legal shell function names, and +# no effort is wasted trying to catch shell meta-characters or preserve +# whitespace. +func_run_hooks () +{ + $debug_cmd + + _G_rc_run_hooks=false + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not support hook funcions.n" ;; + esac + + eval _G_hook_fns=\$$1_hooks; shift + + for _G_hook in $_G_hook_fns; do + if eval $_G_hook '"$@"'; then + # store returned options list back into positional + # parameters for next 'cmd' execution. + eval _G_hook_result=\$${_G_hook}_result + eval set dummy "$_G_hook_result"; shift + _G_rc_run_hooks=: + fi + done + + $_G_rc_run_hooks && func_run_hooks_result=$_G_hook_result +} + + + +## --------------- ## +## Option parsing. ## +## --------------- ## + +# In order to add your own option parsing hooks, you must accept the +# full positional parameter list in your hook function, you may remove/edit +# any options that you action, and then pass back the remaining unprocessed +# options in '_result', escaped suitably for +# 'eval'. In this case you also must return $EXIT_SUCCESS to let the +# hook's caller know that it should pay attention to +# '_result'. Returning $EXIT_FAILURE signalizes that +# arguments are left untouched by the hook and therefore caller will ignore the +# result variable. +# +# Like this: +# +# my_options_prep () +# { +# $debug_cmd +# +# # Extend the existing usage message. +# usage_message=$usage_message' +# -s, --silent don'\''t print informational messages +# ' +# # No change in '$@' (ignored completely by this hook). There is +# # no need to do the equivalent (but slower) action: +# # func_quote_for_eval ${1+"$@"} +# # my_options_prep_result=$func_quote_for_eval_result +# false +# } +# func_add_hook func_options_prep my_options_prep +# +# +# my_silent_option () +# { +# $debug_cmd +# +# args_changed=false +# +# # Note that for efficiency, we parse as many options as we can +# # recognise in a loop before passing the remainder back to the +# # caller on the first unrecognised argument we encounter. +# while test $# -gt 0; do +# opt=$1; shift +# case $opt in +# --silent|-s) opt_silent=: +# args_changed=: +# ;; +# # Separate non-argument short options: +# -s*) func_split_short_opt "$_G_opt" +# set dummy "$func_split_short_opt_name" \ +# "-$func_split_short_opt_arg" ${1+"$@"} +# shift +# args_changed=: +# ;; +# *) # Make sure the first unrecognised option "$_G_opt" +# # is added back to "$@", we could need that later +# # if $args_changed is true. +# set dummy "$_G_opt" ${1+"$@"}; shift; break ;; +# esac +# done +# +# if $args_changed; then +# func_quote_for_eval ${1+"$@"} +# my_silent_option_result=$func_quote_for_eval_result +# fi +# +# $args_changed +# } +# func_add_hook func_parse_options my_silent_option +# +# +# my_option_validation () +# { +# $debug_cmd +# +# $opt_silent && $opt_verbose && func_fatal_help "\ +# '--silent' and '--verbose' options are mutually exclusive." +# +# false +# } +# func_add_hook func_validate_options my_option_validation +# +# You'll also need to manually amend $usage_message to reflect the extra +# options you parse. It's preferable to append if you can, so that +# multiple option parsing hooks can be added safely. + + +# func_options_finish [ARG]... +# ---------------------------- +# Finishing the option parse loop (call 'func_options' hooks ATM). +func_options_finish () +{ + $debug_cmd + + _G_func_options_finish_exit=false + if func_run_hooks func_options ${1+"$@"}; then + func_options_finish_result=$func_run_hooks_result + _G_func_options_finish_exit=: + fi + + $_G_func_options_finish_exit +} + + +# func_options [ARG]... +# --------------------- +# All the functions called inside func_options are hookable. See the +# individual implementations for details. +func_hookable func_options +func_options () +{ + $debug_cmd + + _G_rc_options=false + + for my_func in options_prep parse_options validate_options options_finish + do + if eval func_$my_func '${1+"$@"}'; then + eval _G_res_var='$'"func_${my_func}_result" + eval set dummy "$_G_res_var" ; shift + _G_rc_options=: + fi + done + + # Save modified positional parameters for caller. As a top-level + # options-parser function we always need to set the 'func_options_result' + # variable (regardless the $_G_rc_options value). + if $_G_rc_options; then + func_options_result=$_G_res_var + else + func_quote_for_eval ${1+"$@"} + func_options_result=$func_quote_for_eval_result + fi + + $_G_rc_options +} + + +# func_options_prep [ARG]... +# -------------------------- +# All initialisations required before starting the option parse loop. +# Note that when calling hook functions, we pass through the list of +# positional parameters. If a hook function modifies that list, and +# needs to propagate that back to rest of this script, then the complete +# modified list must be put in 'func_run_hooks_result' before +# returning $EXIT_SUCCESS (otherwise $EXIT_FAILURE is returned). +func_hookable func_options_prep +func_options_prep () +{ + $debug_cmd + + # Option defaults: + opt_verbose=false + opt_warning_types= + + _G_rc_options_prep=false + if func_run_hooks func_options_prep ${1+"$@"}; then + _G_rc_options_prep=: + # save modified positional parameters for caller + func_options_prep_result=$func_run_hooks_result + fi + + $_G_rc_options_prep +} + + +# func_parse_options [ARG]... +# --------------------------- +# The main option parsing loop. +func_hookable func_parse_options +func_parse_options () +{ + $debug_cmd + + func_parse_options_result= + + _G_rc_parse_options=false + # this just eases exit handling + while test $# -gt 0; do + # Defer to hook functions for initial option parsing, so they + # get priority in the event of reusing an option name. + if func_run_hooks func_parse_options ${1+"$@"}; then + eval set dummy "$func_run_hooks_result"; shift + _G_rc_parse_options=: + fi + + # Break out of the loop if we already parsed every option. + test $# -gt 0 || break + + _G_match_parse_options=: + _G_opt=$1 + shift + case $_G_opt in + --debug|-x) debug_cmd='set -x' + func_echo "enabling shell trace mode" + $debug_cmd + ;; + + --no-warnings|--no-warning|--no-warn) + set dummy --warnings none ${1+"$@"} + shift + ;; + + --warnings|--warning|-W) + if test $# = 0 && func_missing_arg $_G_opt; then + _G_rc_parse_options=: + break + fi + case " $warning_categories $1" in + *" $1 "*) + # trailing space prevents matching last $1 above + func_append_uniq opt_warning_types " $1" + ;; + *all) + opt_warning_types=$warning_categories + ;; + *none) + opt_warning_types=none + warning_func=: + ;; + *error) + opt_warning_types=$warning_categories + warning_func=func_fatal_error + ;; + *) + func_fatal_error \ + "unsupported warning category: '$1'" + ;; + esac + shift + ;; + + --verbose|-v) opt_verbose=: ;; + --version) func_version ;; + -\?|-h) func_usage ;; + --help) func_help ;; + + # Separate optargs to long options (plugins may need this): + --*=*) func_split_equals "$_G_opt" + set dummy "$func_split_equals_lhs" \ + "$func_split_equals_rhs" ${1+"$@"} + shift + ;; + + # Separate optargs to short options: + -W*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + # Separate non-argument short options: + -\?*|-h*|-v*|-x*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "-$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + --) _G_rc_parse_options=: ; break ;; + -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; + *) set dummy "$_G_opt" ${1+"$@"}; shift + _G_match_parse_options=false + break + ;; + esac + + $_G_match_parse_options && _G_rc_parse_options=: + done + + + if $_G_rc_parse_options; then + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + func_parse_options_result=$func_quote_for_eval_result + fi + + $_G_rc_parse_options +} + + +# func_validate_options [ARG]... +# ------------------------------ +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +func_hookable func_validate_options +func_validate_options () +{ + $debug_cmd + + _G_rc_validate_options=false + + # Display all warnings if -W was not given. + test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" + + if func_run_hooks func_validate_options ${1+"$@"}; then + # save modified positional parameters for caller + func_validate_options_result=$func_run_hooks_result + _G_rc_validate_options=: + fi + + # Bail if the options were screwed! + $exit_cmd $EXIT_FAILURE + + $_G_rc_validate_options +} + + + +## ----------------- ## +## Helper functions. ## +## ----------------- ## + +# This section contains the helper functions used by the rest of the +# hookable option parser framework in ascii-betical order. + + +# func_fatal_help ARG... +# ---------------------- +# Echo program name prefixed message to standard error, followed by +# a help hint, and exit. +func_fatal_help () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + eval \$ECHO \""$fatal_help"\" + func_error ${1+"$@"} + exit $EXIT_FAILURE +} + + +# func_help +# --------- +# Echo long help message to standard output and exit. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message" + exit 0 +} + + +# func_missing_arg ARGNAME +# ------------------------ +# Echo program name prefixed message to standard error and set global +# exit_cmd. +func_missing_arg () +{ + $debug_cmd + + func_error "Missing argument for '$1'." + exit_cmd=exit +} + + +# func_split_equals STRING +# ------------------------ +# Set func_split_equals_lhs and func_split_equals_rhs shell variables after +# splitting STRING at the '=' sign. +test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=${1%%=*} + func_split_equals_rhs=${1#*=} + test "x$func_split_equals_lhs" = "x$1" \ + && func_split_equals_rhs= + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` + func_split_equals_rhs= + test "x$func_split_equals_lhs" = "x$1" \ + || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` + } +fi #func_split_equals + + +# func_split_short_opt SHORTOPT +# ----------------------------- +# Set func_split_short_opt_name and func_split_short_opt_arg shell +# variables after splitting SHORTOPT after the 2nd character. +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_arg=${1#??} + func_split_short_opt_name=${1%"$func_split_short_opt_arg"} + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` + func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` + } +fi #func_split_short_opt + + +# func_usage +# ---------- +# Echo short help message to standard output and exit. +func_usage () +{ + $debug_cmd + + func_usage_message + $ECHO "Run '$progname --help |${PAGER-more}' for full usage" + exit 0 +} + + +# func_usage_message +# ------------------ +# Echo short help message to standard output. +func_usage_message () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + echo + $SED -n 's|^# || + /^Written by/{ + x;p;x + } + h + /^Written by/q' < "$progpath" + echo + eval \$ECHO \""$usage_message"\" +} + + +# func_version +# ------------ +# Echo version message to standard output and exit. +func_version () +{ + $debug_cmd + + printf '%s\n' "$progname $scriptversion" + $SED -n ' + /(C)/!b go + :more + /\./!{ + N + s|\n# | | + b more + } + :go + /^# Written by /,/# warranty; / { + s|^# || + s|^# *$|| + s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| + p + } + /^# Written by / { + s|^# || + p + } + /^warranty; /q' < "$progpath" + + exit $? +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: + +# Set a version string. +scriptversion='(GNU libtool) 2.4.6' + + +# func_echo ARG... +# ---------------- +# Libtool also displays the current mode in messages, so override +# funclib.sh func_echo with this custom definition. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_warning ARG... +# ------------------- +# Libtool warnings are not categorized, so override funclib.sh +# func_warning with this simpler definition. +func_warning () +{ + $debug_cmd + + $warning_func ${1+"$@"} +} + + +## ---------------- ## +## Options parsing. ## +## ---------------- ## + +# Hook in the functions to make sure our own options are parsed during +# the option parsing loop. + +usage='$progpath [OPTION]... [MODE-ARG]...' + +# Short help message in response to '-h'. +usage_message="Options: + --config show all configuration variables + --debug enable verbose shell tracing + -n, --dry-run display commands without modifying any files + --features display basic configuration information and exit + --mode=MODE use operation mode MODE + --no-warnings equivalent to '-Wnone' + --preserve-dup-deps don't remove duplicate dependency libraries + --quiet, --silent don't print informational messages + --tag=TAG use configuration variables from tag TAG + -v, --verbose print more informational messages than default + --version print version information + -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] + -h, --help, --help-all print short, long, or detailed help message +" + +# Additional text appended to 'usage_message' in response to '--help'. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message + +MODE must be one of the following: + + clean remove files from the build directory + compile compile a source file into a libtool object + execute automatically set library path, then run a program + finish complete the installation of libtool libraries + install install libraries or executables + link create a library or an executable + uninstall remove libraries from an installed directory + +MODE-ARGS vary depending on the MODE. When passed as first option, +'--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that. +Try '$progname --help --mode=MODE' for a more detailed description of MODE. + +When reporting a bug, please describe a test case to reproduce it and +include the following information: + + host-triplet: $host + shell: $SHELL + compiler: $LTCC + compiler flags: $LTCFLAGS + linker: $LD (gnu? $with_gnu_ld) + version: $progname $scriptversion Debian-2.4.6-14 + automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` + autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` + +Report bugs to . +GNU libtool home page: . +General help using GNU software: ." + exit 0 +} + + +# func_lo2o OBJECT-NAME +# --------------------- +# Transform OBJECT-NAME from a '.lo' suffix to the platform specific +# object suffix. + +lo2o=s/\\.lo\$/.$objext/ +o2lo=s/\\.$objext\$/.lo/ + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_lo2o () + { + case $1 in + *.lo) func_lo2o_result=${1%.lo}.$objext ;; + * ) func_lo2o_result=$1 ;; + esac + }' + + # func_xform LIBOBJ-OR-SOURCE + # --------------------------- + # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise) + # suffix to a '.lo' libtool-object suffix. + eval 'func_xform () + { + func_xform_result=${1%.*}.lo + }' +else + # ...otherwise fall back to using sed. + func_lo2o () + { + func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"` + } + + func_xform () + { + func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'` + } +fi + + +# func_fatal_configuration ARG... +# ------------------------------- +# Echo program name prefixed message to standard error, followed by +# a configuration failure hint, and exit. +func_fatal_configuration () +{ + func__fatal_error ${1+"$@"} \ + "See the $PACKAGE documentation for more information." \ + "Fatal configuration error." +} + + +# func_config +# ----------- +# Display the configuration for all the tags in this script. +func_config () +{ + re_begincf='^# ### BEGIN LIBTOOL' + re_endcf='^# ### END LIBTOOL' + + # Default configuration. + $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath" + + # Now print the configurations for the tags. + for tagname in $taglist; do + $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath" + done + + exit $? +} + + +# func_features +# ------------- +# Display the features supported by this script. +func_features () +{ + echo "host: $host" + if test yes = "$build_libtool_libs"; then + echo "enable shared libraries" + else + echo "disable shared libraries" + fi + if test yes = "$build_old_libs"; then + echo "enable static libraries" + else + echo "disable static libraries" + fi + + exit $? +} + + +# func_enable_tag TAGNAME +# ----------------------- +# Verify that TAGNAME is valid, and either flag an error and exit, or +# enable the TAGNAME tag. We also add TAGNAME to the global $taglist +# variable here. +func_enable_tag () +{ + # Global variable: + tagname=$1 + + re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$" + re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$" + sed_extractcf=/$re_begincf/,/$re_endcf/p + + # Validate tagname. + case $tagname in + *[!-_A-Za-z0-9,/]*) + func_fatal_error "invalid tag name: $tagname" + ;; + esac + + # Don't test for the "default" C tag, as we know it's + # there but not specially marked. + case $tagname in + CC) ;; + *) + if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then + taglist="$taglist $tagname" + + # Evaluate the configuration. Be careful to quote the path + # and the sed script, to avoid splitting on whitespace, but + # also don't use non-portable quotes within backquotes within + # quotes we have to do it in 2 steps: + extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"` + eval "$extractedcf" + else + func_error "ignoring unknown tag $tagname" + fi + ;; + esac +} + + +# func_check_version_match +# ------------------------ +# Ensure that we are using m4 macros, and libtool script from the same +# release of libtool. +func_check_version_match () +{ + if test "$package_revision" != "$macro_revision"; then + if test "$VERSION" != "$macro_version"; then + if test -z "$macro_version"; then + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from an older release. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from $PACKAGE $macro_version. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + fi + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision, +$progname: but the definition of this LT_INIT comes from revision $macro_revision. +$progname: You should recreate aclocal.m4 with macros from revision $package_revision +$progname: of $PACKAGE $VERSION and run autoconf again. +_LT_EOF + fi + + exit $EXIT_MISMATCH + fi +} + + +# libtool_options_prep [ARG]... +# ----------------------------- +# Preparation for options parsed by libtool. +libtool_options_prep () +{ + $debug_mode + + # Option defaults: + opt_config=false + opt_dlopen= + opt_dry_run=false + opt_help=false + opt_mode= + opt_preserve_dup_deps=false + opt_quiet=false + + nonopt= + preserve_args= + + _G_rc_lt_options_prep=: + + # Shorthand for --mode=foo, only valid as the first argument + case $1 in + clean|clea|cle|cl) + shift; set dummy --mode clean ${1+"$@"}; shift + ;; + compile|compil|compi|comp|com|co|c) + shift; set dummy --mode compile ${1+"$@"}; shift + ;; + execute|execut|execu|exec|exe|ex|e) + shift; set dummy --mode execute ${1+"$@"}; shift + ;; + finish|finis|fini|fin|fi|f) + shift; set dummy --mode finish ${1+"$@"}; shift + ;; + install|instal|insta|inst|ins|in|i) + shift; set dummy --mode install ${1+"$@"}; shift + ;; + link|lin|li|l) + shift; set dummy --mode link ${1+"$@"}; shift + ;; + uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) + shift; set dummy --mode uninstall ${1+"$@"}; shift + ;; + *) + _G_rc_lt_options_prep=false + ;; + esac + + if $_G_rc_lt_options_prep; then + # Pass back the list of options. + func_quote_for_eval ${1+"$@"} + libtool_options_prep_result=$func_quote_for_eval_result + fi + + $_G_rc_lt_options_prep +} +func_add_hook func_options_prep libtool_options_prep + + +# libtool_parse_options [ARG]... +# --------------------------------- +# Provide handling for libtool specific options. +libtool_parse_options () +{ + $debug_cmd + + _G_rc_lt_parse_options=false + + # Perform our own loop to consume as many options as possible in + # each iteration. + while test $# -gt 0; do + _G_match_lt_parse_options=: + _G_opt=$1 + shift + case $_G_opt in + --dry-run|--dryrun|-n) + opt_dry_run=: + ;; + + --config) func_config ;; + + --dlopen|-dlopen) + opt_dlopen="${opt_dlopen+$opt_dlopen +}$1" + shift + ;; + + --preserve-dup-deps) + opt_preserve_dup_deps=: ;; + + --features) func_features ;; + + --finish) set dummy --mode finish ${1+"$@"}; shift ;; + + --help) opt_help=: ;; + + --help-all) opt_help=': help-all' ;; + + --mode) test $# = 0 && func_missing_arg $_G_opt && break + opt_mode=$1 + case $1 in + # Valid mode arguments: + clean|compile|execute|finish|install|link|relink|uninstall) ;; + + # Catch anything else as an error + *) func_error "invalid argument for $_G_opt" + exit_cmd=exit + break + ;; + esac + shift + ;; + + --no-silent|--no-quiet) + opt_quiet=false + func_append preserve_args " $_G_opt" + ;; + + --no-warnings|--no-warning|--no-warn) + opt_warning=false + func_append preserve_args " $_G_opt" + ;; + + --no-verbose) + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --silent|--quiet) + opt_quiet=: + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --tag) test $# = 0 && func_missing_arg $_G_opt && break + opt_tag=$1 + func_append preserve_args " $_G_opt $1" + func_enable_tag "$1" + shift + ;; + + --verbose|-v) opt_quiet=false + opt_verbose=: + func_append preserve_args " $_G_opt" + ;; + + # An option not handled by this hook function: + *) set dummy "$_G_opt" ${1+"$@"} ; shift + _G_match_lt_parse_options=false + break + ;; + esac + $_G_match_lt_parse_options && _G_rc_lt_parse_options=: + done + + if $_G_rc_lt_parse_options; then + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + libtool_parse_options_result=$func_quote_for_eval_result + fi + + $_G_rc_lt_parse_options +} +func_add_hook func_parse_options libtool_parse_options + + + +# libtool_validate_options [ARG]... +# --------------------------------- +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +libtool_validate_options () +{ + # save first non-option argument + if test 0 -lt $#; then + nonopt=$1 + shift + fi + + # preserve --debug + test : = "$debug_cmd" || func_append preserve_args " --debug" + + case $host in + # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452 + # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788 + *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*) + # don't eliminate duplications in $postdeps and $predeps + opt_duplicate_compiler_generated_deps=: + ;; + *) + opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps + ;; + esac + + $opt_help || { + # Sanity checks first: + func_check_version_match + + test yes != "$build_libtool_libs" \ + && test yes != "$build_old_libs" \ + && func_fatal_configuration "not configured to build any kind of library" + + # Darwin sucks + eval std_shrext=\"$shrext_cmds\" + + # Only execute mode is allowed to have -dlopen flags. + if test -n "$opt_dlopen" && test execute != "$opt_mode"; then + func_error "unrecognized option '-dlopen'" + $ECHO "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Change the help message to a mode-specific one. + generic_help=$help + help="Try '$progname --help --mode=$opt_mode' for more information." + } + + # Pass back the unparsed argument list + func_quote_for_eval ${1+"$@"} + libtool_validate_options_result=$func_quote_for_eval_result +} +func_add_hook func_validate_options libtool_validate_options + + +# Process options as early as possible so that --help and --version +# can return quickly. +func_options ${1+"$@"} +eval set dummy "$func_options_result"; shift + + + +## ----------- ## +## Main. ## +## ----------- ## + +magic='%%%MAGIC variable%%%' +magic_exe='%%%MAGIC EXE variable%%%' + +# Global variables. +extracted_archives= +extracted_serial=0 + +# If this variable is set in any of the actions, the command in it +# will be execed at the end. This prevents here-documents from being +# left over by shells. +exec_cmd= + + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + +# func_generated_by_libtool +# True iff stdin has been generated by Libtool. This function is only +# a basic sanity check; it will hardly flush out determined imposters. +func_generated_by_libtool_p () +{ + $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1 +} + +# func_lalib_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_lalib_p () +{ + test -f "$1" && + $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_lalib_unsafe_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function implements the same check as func_lalib_p without +# resorting to external programs. To this end, it redirects stdin and +# closes it afterwards, without saving the original file descriptor. +# As a safety measure, use it only where a negative result would be +# fatal anyway. Works if 'file' does not exist. +func_lalib_unsafe_p () +{ + lalib_p=no + if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then + for lalib_p_l in 1 2 3 4 + do + read lalib_p_line + case $lalib_p_line in + \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;; + esac + done + exec 0<&5 5<&- + fi + test yes = "$lalib_p" +} + +# func_ltwrapper_script_p file +# True iff FILE is a libtool wrapper script +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_script_p () +{ + test -f "$1" && + $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_ltwrapper_executable_p file +# True iff FILE is a libtool wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_executable_p () +{ + func_ltwrapper_exec_suffix= + case $1 in + *.exe) ;; + *) func_ltwrapper_exec_suffix=.exe ;; + esac + $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1 +} + +# func_ltwrapper_scriptname file +# Assumes file is an ltwrapper_executable +# uses $file to determine the appropriate filename for a +# temporary ltwrapper_script. +func_ltwrapper_scriptname () +{ + func_dirname_and_basename "$1" "" "." + func_stripname '' '.exe' "$func_basename_result" + func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper +} + +# func_ltwrapper_p file +# True iff FILE is a libtool wrapper script or wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_p () +{ + func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1" +} + + +# func_execute_cmds commands fail_cmd +# Execute tilde-delimited COMMANDS. +# If FAIL_CMD is given, eval that upon failure. +# FAIL_CMD may read-access the current command in variable CMD! +func_execute_cmds () +{ + $debug_cmd + + save_ifs=$IFS; IFS='~' + for cmd in $1; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + func_show_eval "$cmd" "${2-:}" + done + IFS=$save_ifs +} + + +# func_source file +# Source FILE, adding directory component if necessary. +# Note that it is not necessary on cygwin/mingw to append a dot to +# FILE even if both FILE and FILE.exe exist: automatic-append-.exe +# behavior happens only for exec(3), not for open(2)! Also, sourcing +# 'FILE.' does not work on cygwin managed mounts. +func_source () +{ + $debug_cmd + + case $1 in + */* | *\\*) . "$1" ;; + *) . "./$1" ;; + esac +} + + +# func_resolve_sysroot PATH +# Replace a leading = in PATH with a sysroot. Store the result into +# func_resolve_sysroot_result +func_resolve_sysroot () +{ + func_resolve_sysroot_result=$1 + case $func_resolve_sysroot_result in + =*) + func_stripname '=' '' "$func_resolve_sysroot_result" + func_resolve_sysroot_result=$lt_sysroot$func_stripname_result + ;; + esac +} + +# func_replace_sysroot PATH +# If PATH begins with the sysroot, replace it with = and +# store the result into func_replace_sysroot_result. +func_replace_sysroot () +{ + case $lt_sysroot:$1 in + ?*:"$lt_sysroot"*) + func_stripname "$lt_sysroot" '' "$1" + func_replace_sysroot_result='='$func_stripname_result + ;; + *) + # Including no sysroot. + func_replace_sysroot_result=$1 + ;; + esac +} + +# func_infer_tag arg +# Infer tagged configuration to use if any are available and +# if one wasn't chosen via the "--tag" command line option. +# Only attempt this if the compiler in the base compile +# command doesn't match the default compiler. +# arg is usually of the form 'gcc ...' +func_infer_tag () +{ + $debug_cmd + + if test -n "$available_tags" && test -z "$tagname"; then + CC_quoted= + for arg in $CC; do + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case $@ in + # Blanks in the command may have been stripped by the calling shell, + # but not from the CC environment variable when configure was run. + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;; + # Blanks at the start of $base_compile will cause this to fail + # if we don't check for them as well. + *) + for z in $available_tags; do + if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then + # Evaluate the configuration. + eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" + CC_quoted= + for arg in $CC; do + # Double-quote args containing other shell metacharacters. + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case "$@ " in + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) + # The compiler in the base compile command matches + # the one in the tagged configuration. + # Assume this is the tagged configuration we want. + tagname=$z + break + ;; + esac + fi + done + # If $tagname still isn't set, then no tagged configuration + # was found and let the user know that the "--tag" command + # line option must be used. + if test -z "$tagname"; then + func_echo "unable to infer tagged configuration" + func_fatal_error "specify a tag with '--tag'" +# else +# func_verbose "using $tagname tagged configuration" + fi + ;; + esac + fi +} + + + +# func_write_libtool_object output_name pic_name nonpic_name +# Create a libtool object file (analogous to a ".la" file), +# but don't create it if we're doing a dry run. +func_write_libtool_object () +{ + write_libobj=$1 + if test yes = "$build_libtool_libs"; then + write_lobj=\'$2\' + else + write_lobj=none + fi + + if test yes = "$build_old_libs"; then + write_oldobj=\'$3\' + else + write_oldobj=none + fi + + $opt_dry_run || { + cat >${write_libobj}T </dev/null` + if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then + func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" | + $SED -e "$sed_naive_backslashify"` + else + func_convert_core_file_wine_to_w32_result= + fi + fi +} +# end: func_convert_core_file_wine_to_w32 + + +# func_convert_core_path_wine_to_w32 ARG +# Helper function used by path conversion functions when $build is *nix, and +# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly +# configured wine environment available, with the winepath program in $build's +# $PATH. Assumes ARG has no leading or trailing path separator characters. +# +# ARG is path to be converted from $build format to win32. +# Result is available in $func_convert_core_path_wine_to_w32_result. +# Unconvertible file (directory) names in ARG are skipped; if no directory names +# are convertible, then the result may be empty. +func_convert_core_path_wine_to_w32 () +{ + $debug_cmd + + # unfortunately, winepath doesn't convert paths, only file names + func_convert_core_path_wine_to_w32_result= + if test -n "$1"; then + oldIFS=$IFS + IFS=: + for func_convert_core_path_wine_to_w32_f in $1; do + IFS=$oldIFS + func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f" + if test -n "$func_convert_core_file_wine_to_w32_result"; then + if test -z "$func_convert_core_path_wine_to_w32_result"; then + func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result + else + func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result" + fi + fi + done + IFS=$oldIFS + fi +} +# end: func_convert_core_path_wine_to_w32 + + +# func_cygpath ARGS... +# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when +# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2) +# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or +# (2), returns the Cygwin file name or path in func_cygpath_result (input +# file name or path is assumed to be in w32 format, as previously converted +# from $build's *nix or MSYS format). In case (3), returns the w32 file name +# or path in func_cygpath_result (input file name or path is assumed to be in +# Cygwin format). Returns an empty string on error. +# +# ARGS are passed to cygpath, with the last one being the file name or path to +# be converted. +# +# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH +# environment variable; do not put it in $PATH. +func_cygpath () +{ + $debug_cmd + + if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then + func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null` + if test "$?" -ne 0; then + # on failure, ensure result is empty + func_cygpath_result= + fi + else + func_cygpath_result= + func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'" + fi +} +#end: func_cygpath + + +# func_convert_core_msys_to_w32 ARG +# Convert file name or path ARG from MSYS format to w32 format. Return +# result in func_convert_core_msys_to_w32_result. +func_convert_core_msys_to_w32 () +{ + $debug_cmd + + # awkward: cmd appends spaces to result + func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null | + $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"` +} +#end: func_convert_core_msys_to_w32 + + +# func_convert_file_check ARG1 ARG2 +# Verify that ARG1 (a file name in $build format) was converted to $host +# format in ARG2. Otherwise, emit an error message, but continue (resetting +# func_to_host_file_result to ARG1). +func_convert_file_check () +{ + $debug_cmd + + if test -z "$2" && test -n "$1"; then + func_error "Could not determine host file name corresponding to" + func_error " '$1'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback: + func_to_host_file_result=$1 + fi +} +# end func_convert_file_check + + +# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH +# Verify that FROM_PATH (a path in $build format) was converted to $host +# format in TO_PATH. Otherwise, emit an error message, but continue, resetting +# func_to_host_file_result to a simplistic fallback value (see below). +func_convert_path_check () +{ + $debug_cmd + + if test -z "$4" && test -n "$3"; then + func_error "Could not determine the host path corresponding to" + func_error " '$3'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback. This is a deliberately simplistic "conversion" and + # should not be "improved". See libtool.info. + if test "x$1" != "x$2"; then + lt_replace_pathsep_chars="s|$1|$2|g" + func_to_host_path_result=`echo "$3" | + $SED -e "$lt_replace_pathsep_chars"` + else + func_to_host_path_result=$3 + fi + fi +} +# end func_convert_path_check + + +# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG +# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT +# and appending REPL if ORIG matches BACKPAT. +func_convert_path_front_back_pathsep () +{ + $debug_cmd + + case $4 in + $1 ) func_to_host_path_result=$3$func_to_host_path_result + ;; + esac + case $4 in + $2 ) func_append func_to_host_path_result "$3" + ;; + esac +} +# end func_convert_path_front_back_pathsep + + +################################################## +# $build to $host FILE NAME CONVERSION FUNCTIONS # +################################################## +# invoked via '$to_host_file_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# Result will be available in $func_to_host_file_result. + + +# func_to_host_file ARG +# Converts the file name ARG from $build format to $host format. Return result +# in func_to_host_file_result. +func_to_host_file () +{ + $debug_cmd + + $to_host_file_cmd "$1" +} +# end func_to_host_file + + +# func_to_tool_file ARG LAZY +# converts the file name ARG from $build format to toolchain format. Return +# result in func_to_tool_file_result. If the conversion in use is listed +# in (the comma separated) LAZY, no conversion takes place. +func_to_tool_file () +{ + $debug_cmd + + case ,$2, in + *,"$to_tool_file_cmd",*) + func_to_tool_file_result=$1 + ;; + *) + $to_tool_file_cmd "$1" + func_to_tool_file_result=$func_to_host_file_result + ;; + esac +} +# end func_to_tool_file + + +# func_convert_file_noop ARG +# Copy ARG to func_to_host_file_result. +func_convert_file_noop () +{ + func_to_host_file_result=$1 +} +# end func_convert_file_noop + + +# func_convert_file_msys_to_w32 ARG +# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_file_result. +func_convert_file_msys_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_to_host_file_result=$func_convert_core_msys_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_w32 + + +# func_convert_file_cygwin_to_w32 ARG +# Convert file name ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_file_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # because $build is cygwin, we call "the" cygpath in $PATH; no need to use + # LT_CYGPATH in this case. + func_to_host_file_result=`cygpath -m "$1"` + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_cygwin_to_w32 + + +# func_convert_file_nix_to_w32 ARG +# Convert file name ARG from *nix to w32 format. Requires a wine environment +# and a working winepath. Returns result in func_to_host_file_result. +func_convert_file_nix_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_file_wine_to_w32 "$1" + func_to_host_file_result=$func_convert_core_file_wine_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_w32 + + +# func_convert_file_msys_to_cygwin ARG +# Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_file_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_cygpath -u "$func_convert_core_msys_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_cygwin + + +# func_convert_file_nix_to_cygwin ARG +# Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed +# in a wine environment, working winepath, and LT_CYGPATH set. Returns result +# in func_to_host_file_result. +func_convert_file_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # convert from *nix to w32, then use cygpath to convert from w32 to cygwin. + func_convert_core_file_wine_to_w32 "$1" + func_cygpath -u "$func_convert_core_file_wine_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_cygwin + + +############################################# +# $build to $host PATH CONVERSION FUNCTIONS # +############################################# +# invoked via '$to_host_path_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# The result will be available in $func_to_host_path_result. +# +# Path separators are also converted from $build format to $host format. If +# ARG begins or ends with a path separator character, it is preserved (but +# converted to $host format) on output. +# +# All path conversion functions are named using the following convention: +# file name conversion function : func_convert_file_X_to_Y () +# path conversion function : func_convert_path_X_to_Y () +# where, for any given $build/$host combination the 'X_to_Y' value is the +# same. If conversion functions are added for new $build/$host combinations, +# the two new functions must follow this pattern, or func_init_to_host_path_cmd +# will break. + + +# func_init_to_host_path_cmd +# Ensures that function "pointer" variable $to_host_path_cmd is set to the +# appropriate value, based on the value of $to_host_file_cmd. +to_host_path_cmd= +func_init_to_host_path_cmd () +{ + $debug_cmd + + if test -z "$to_host_path_cmd"; then + func_stripname 'func_convert_file_' '' "$to_host_file_cmd" + to_host_path_cmd=func_convert_path_$func_stripname_result + fi +} + + +# func_to_host_path ARG +# Converts the path ARG from $build format to $host format. Return result +# in func_to_host_path_result. +func_to_host_path () +{ + $debug_cmd + + func_init_to_host_path_cmd + $to_host_path_cmd "$1" +} +# end func_to_host_path + + +# func_convert_path_noop ARG +# Copy ARG to func_to_host_path_result. +func_convert_path_noop () +{ + func_to_host_path_result=$1 +} +# end func_convert_path_noop + + +# func_convert_path_msys_to_w32 ARG +# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_path_result. +func_convert_path_msys_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from ARG. MSYS + # behavior is inconsistent here; cygpath turns them into '.;' and ';.'; + # and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_msys_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_msys_to_w32 + + +# func_convert_path_cygwin_to_w32 ARG +# Convert path ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_path_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"` + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_cygwin_to_w32 + + +# func_convert_path_nix_to_w32 ARG +# Convert path ARG from *nix to w32 format. Requires a wine environment and +# a working winepath. Returns result in func_to_host_file_result. +func_convert_path_nix_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_path_wine_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_nix_to_w32 + + +# func_convert_path_msys_to_cygwin ARG +# Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_path_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_msys_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_msys_to_cygwin + + +# func_convert_path_nix_to_cygwin ARG +# Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a +# a wine environment, working winepath, and LT_CYGPATH set. Returns result in +# func_to_host_file_result. +func_convert_path_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from + # ARG. msys behavior is inconsistent here, cygpath turns them + # into '.;' and ';.', and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_nix_to_cygwin + + +# func_dll_def_p FILE +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with _LT_DLL_DEF_P in libtool.m4 +func_dll_def_p () +{ + $debug_cmd + + func_dll_def_p_tmp=`$SED -n \ + -e 's/^[ ]*//' \ + -e '/^\(;.*\)*$/d' \ + -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \ + -e q \ + "$1"` + test DEF = "$func_dll_def_p_tmp" +} + + +# func_mode_compile arg... +func_mode_compile () +{ + $debug_cmd + + # Get the compilation command and the source file. + base_compile= + srcfile=$nonopt # always keep a non-empty value in "srcfile" + suppress_opt=yes + suppress_output= + arg_mode=normal + libobj= + later= + pie_flag= + + for arg + do + case $arg_mode in + arg ) + # do not "continue". Instead, add this to base_compile + lastarg=$arg + arg_mode=normal + ;; + + target ) + libobj=$arg + arg_mode=normal + continue + ;; + + normal ) + # Accept any command-line options. + case $arg in + -o) + test -n "$libobj" && \ + func_fatal_error "you cannot specify '-o' more than once" + arg_mode=target + continue + ;; + + -pie | -fpie | -fPIE) + func_append pie_flag " $arg" + continue + ;; + + -shared | -static | -prefer-pic | -prefer-non-pic) + func_append later " $arg" + continue + ;; + + -no-suppress) + suppress_opt=no + continue + ;; + + -Xcompiler) + arg_mode=arg # the next one goes into the "base_compile" arg list + continue # The current "srcfile" will either be retained or + ;; # replaced later. I would guess that would be a bug. + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + lastarg= + save_ifs=$IFS; IFS=, + for arg in $args; do + IFS=$save_ifs + func_append_quoted lastarg "$arg" + done + IFS=$save_ifs + func_stripname ' ' '' "$lastarg" + lastarg=$func_stripname_result + + # Add the arguments to base_compile. + func_append base_compile " $lastarg" + continue + ;; + + *) + # Accept the current argument as the source file. + # The previous "srcfile" becomes the current argument. + # + lastarg=$srcfile + srcfile=$arg + ;; + esac # case $arg + ;; + esac # case $arg_mode + + # Aesthetically quote the previous argument. + func_append_quoted base_compile "$lastarg" + done # for arg + + case $arg_mode in + arg) + func_fatal_error "you must specify an argument for -Xcompile" + ;; + target) + func_fatal_error "you must specify a target with '-o'" + ;; + *) + # Get the name of the library object. + test -z "$libobj" && { + func_basename "$srcfile" + libobj=$func_basename_result + } + ;; + esac + + # Recognize several different file suffixes. + # If the user specifies -o file.o, it is replaced with file.lo + case $libobj in + *.[cCFSifmso] | \ + *.ada | *.adb | *.ads | *.asm | \ + *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \ + *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup) + func_xform "$libobj" + libobj=$func_xform_result + ;; + esac + + case $libobj in + *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;; + *) + func_fatal_error "cannot determine name of library object from '$libobj'" + ;; + esac + + func_infer_tag $base_compile + + for arg in $later; do + case $arg in + -shared) + test yes = "$build_libtool_libs" \ + || func_fatal_configuration "cannot build a shared library" + build_old_libs=no + continue + ;; + + -static) + build_libtool_libs=no + build_old_libs=yes + continue + ;; + + -prefer-pic) + pic_mode=yes + continue + ;; + + -prefer-non-pic) + pic_mode=no + continue + ;; + esac + done + + func_quote_for_eval "$libobj" + test "X$libobj" != "X$func_quote_for_eval_result" \ + && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \ + && func_warning "libobj name '$libobj' may not contain shell special characters." + func_dirname_and_basename "$obj" "/" "" + objname=$func_basename_result + xdir=$func_dirname_result + lobj=$xdir$objdir/$objname + + test -z "$base_compile" && \ + func_fatal_help "you must specify a compilation command" + + # Delete any leftover library objects. + if test yes = "$build_old_libs"; then + removelist="$obj $lobj $libobj ${libobj}T" + else + removelist="$lobj $libobj ${libobj}T" + fi + + # On Cygwin there's no "real" PIC flag so we must build both object types + case $host_os in + cygwin* | mingw* | pw32* | os2* | cegcc*) + pic_mode=default + ;; + esac + if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then + # non-PIC code in shared libraries is not supported + pic_mode=default + fi + + # Calculate the filename of the output object if compiler does + # not support -o with -c + if test no = "$compiler_c_o"; then + output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext + lockfile=$output_obj.lock + else + output_obj= + need_locks=no + lockfile= + fi + + # Lock this critical section if it is needed + # We use this script file to make the link, it avoids creating a new file + if test yes = "$need_locks"; then + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + elif test warn = "$need_locks"; then + if test -f "$lockfile"; then + $ECHO "\ +*** ERROR, $lockfile exists and contains: +`cat $lockfile 2>/dev/null` + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + func_append removelist " $output_obj" + $ECHO "$srcfile" > "$lockfile" + fi + + $opt_dry_run || $RM $removelist + func_append removelist " $lockfile" + trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15 + + func_to_tool_file "$srcfile" func_convert_file_msys_to_w32 + srcfile=$func_to_tool_file_result + func_quote_for_eval "$srcfile" + qsrcfile=$func_quote_for_eval_result + + # Only build a PIC object if we are building libtool libraries. + if test yes = "$build_libtool_libs"; then + # Without this assignment, base_compile gets emptied. + fbsd_hideous_sh_bug=$base_compile + + if test no != "$pic_mode"; then + command="$base_compile $qsrcfile $pic_flag" + else + # Don't build PIC code + command="$base_compile $qsrcfile" + fi + + func_mkdir_p "$xdir$objdir" + + if test -z "$output_obj"; then + # Place PIC objects in $objdir + func_append command " -o $lobj" + fi + + func_show_eval_locale "$command" \ + 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed, then go on to compile the next one + if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then + func_show_eval '$MV "$output_obj" "$lobj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + + # Allow error messages only from the first compilation. + if test yes = "$suppress_opt"; then + suppress_output=' >/dev/null 2>&1' + fi + fi + + # Only build a position-dependent object if we build old libraries. + if test yes = "$build_old_libs"; then + if test yes != "$pic_mode"; then + # Don't build PIC code + command="$base_compile $qsrcfile$pie_flag" + else + command="$base_compile $qsrcfile $pic_flag" + fi + if test yes = "$compiler_c_o"; then + func_append command " -o $obj" + fi + + # Suppress compiler output if we already did a PIC compilation. + func_append command "$suppress_output" + func_show_eval_locale "$command" \ + '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed + if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then + func_show_eval '$MV "$output_obj" "$obj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + fi + + $opt_dry_run || { + func_write_libtool_object "$libobj" "$objdir/$objname" "$objname" + + # Unlock the critical section if it was locked + if test no != "$need_locks"; then + removelist=$lockfile + $RM "$lockfile" + fi + } + + exit $EXIT_SUCCESS +} + +$opt_help || { + test compile = "$opt_mode" && func_mode_compile ${1+"$@"} +} + +func_mode_help () +{ + # We need to display help for each of the modes. + case $opt_mode in + "") + # Generic help is extracted from the usage comments + # at the start of this file. + func_help + ;; + + clean) + $ECHO \ +"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE... + +Remove files from the build directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, object or program, all the files associated +with it are deleted. Otherwise, only FILE itself is deleted using RM." + ;; + + compile) + $ECHO \ +"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE + +Compile a source file into a libtool library object. + +This mode accepts the following additional options: + + -o OUTPUT-FILE set the output file name to OUTPUT-FILE + -no-suppress do not suppress compiler output for multiple passes + -prefer-pic try to build PIC objects only + -prefer-non-pic try to build non-PIC objects only + -shared do not build a '.o' file suitable for static linking + -static only build a '.o' file suitable for static linking + -Wc,FLAG pass FLAG directly to the compiler + +COMPILE-COMMAND is a command to be used in creating a 'standard' object file +from the given SOURCEFILE. + +The output file name is determined by removing the directory component from +SOURCEFILE, then substituting the C source code suffix '.c' with the +library object suffix, '.lo'." + ;; + + execute) + $ECHO \ +"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]... + +Automatically set library path, then run a program. + +This mode accepts the following additional options: + + -dlopen FILE add the directory containing FILE to the library path + +This mode sets the library path environment variable according to '-dlopen' +flags. + +If any of the ARGS are libtool executable wrappers, then they are translated +into their corresponding uninstalled binary, and any of their required library +directories are added to the library path. + +Then, COMMAND is executed, with ARGS as arguments." + ;; + + finish) + $ECHO \ +"Usage: $progname [OPTION]... --mode=finish [LIBDIR]... + +Complete the installation of libtool libraries. + +Each LIBDIR is a directory that contains libtool libraries. + +The commands that this mode executes may require superuser privileges. Use +the '--dry-run' option if you just want to see what would be executed." + ;; + + install) + $ECHO \ +"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND... + +Install executables or libraries. + +INSTALL-COMMAND is the installation command. The first component should be +either the 'install' or 'cp' program. + +The following components of INSTALL-COMMAND are treated specially: + + -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation + +The rest of the components are interpreted as arguments to that command (only +BSD-compatible install options are recognized)." + ;; + + link) + $ECHO \ +"Usage: $progname [OPTION]... --mode=link LINK-COMMAND... + +Link object files or libraries together to form another library, or to +create an executable program. + +LINK-COMMAND is a command using the C compiler that you would use to create +a program from several object files. + +The following components of LINK-COMMAND are treated specially: + + -all-static do not do any dynamic linking at all + -avoid-version do not add a version suffix if possible + -bindir BINDIR specify path to binaries directory (for systems where + libraries must be found in the PATH setting at runtime) + -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime + -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols + -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) + -export-symbols SYMFILE + try to export only the symbols listed in SYMFILE + -export-symbols-regex REGEX + try to export only the symbols matching REGEX + -LLIBDIR search LIBDIR for required installed libraries + -lNAME OUTPUT-FILE requires the installed library libNAME + -module build a library that can dlopened + -no-fast-install disable the fast-install mode + -no-install link a not-installable executable + -no-undefined declare that a library does not refer to external symbols + -o OUTPUT-FILE create OUTPUT-FILE from the specified objects + -objectlist FILE use a list of object files found in FILE to specify objects + -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes) + -precious-files-regex REGEX + don't remove output files matching REGEX + -release RELEASE specify package release information + -rpath LIBDIR the created library will eventually be installed in LIBDIR + -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries + -shared only do dynamic linking of libtool libraries + -shrext SUFFIX override the standard shared library file extension + -static do not do any dynamic linking of uninstalled libtool libraries + -static-libtool-libs + do not do any dynamic linking of libtool libraries + -version-info CURRENT[:REVISION[:AGE]] + specify library version info [each variable defaults to 0] + -weak LIBNAME declare that the target provides the LIBNAME interface + -Wc,FLAG + -Xcompiler FLAG pass linker-specific FLAG directly to the compiler + -Wl,FLAG + -Xlinker FLAG pass linker-specific FLAG directly to the linker + -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC) + +All other options (arguments beginning with '-') are ignored. + +Every other argument is treated as a filename. Files ending in '.la' are +treated as uninstalled libtool libraries, other files are standard or library +object files. + +If the OUTPUT-FILE ends in '.la', then a libtool library is created, +only library objects ('.lo' files) may be specified, and '-rpath' is +required, except when creating a convenience library. + +If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created +using 'ar' and 'ranlib', or on Windows using 'lib'. + +If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file +is created, otherwise an executable program is created." + ;; + + uninstall) + $ECHO \ +"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... + +Remove libraries from an installation directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, all the files associated with it are deleted. +Otherwise, only FILE itself is deleted using RM." + ;; + + *) + func_fatal_help "invalid operation mode '$opt_mode'" + ;; + esac + + echo + $ECHO "Try '$progname --help' for more information about other modes." +} + +# Now that we've collected a possible --mode arg, show help if necessary +if $opt_help; then + if test : = "$opt_help"; then + func_mode_help + else + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + func_mode_help + done + } | $SED -n '1p; 2,$s/^Usage:/ or: /p' + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + echo + func_mode_help + done + } | + $SED '1d + /^When reporting/,/^Report/{ + H + d + } + $x + /information about other modes/d + /more detailed .*MODE/d + s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/' + fi + exit $? +fi + + +# func_mode_execute arg... +func_mode_execute () +{ + $debug_cmd + + # The first argument is the command name. + cmd=$nonopt + test -z "$cmd" && \ + func_fatal_help "you must specify a COMMAND" + + # Handle -dlopen flags immediately. + for file in $opt_dlopen; do + test -f "$file" \ + || func_fatal_help "'$file' is not a file" + + dir= + case $file in + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$lib' is not a valid libtool archive" + + # Read the libtool library. + dlname= + library_names= + func_source "$file" + + # Skip this library if it cannot be dlopened. + if test -z "$dlname"; then + # Warn if it was a shared library. + test -n "$library_names" && \ + func_warning "'$file' was not linked with '-export-dynamic'" + continue + fi + + func_dirname "$file" "" "." + dir=$func_dirname_result + + if test -f "$dir/$objdir/$dlname"; then + func_append dir "/$objdir" + else + if test ! -f "$dir/$dlname"; then + func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'" + fi + fi + ;; + + *.lo) + # Just add the directory containing the .lo file. + func_dirname "$file" "" "." + dir=$func_dirname_result + ;; + + *) + func_warning "'-dlopen' is ignored for non-libtool libraries and objects" + continue + ;; + esac + + # Get the absolute pathname. + absdir=`cd "$dir" && pwd` + test -n "$absdir" && dir=$absdir + + # Now add the directory to shlibpath_var. + if eval "test -z \"\$$shlibpath_var\""; then + eval "$shlibpath_var=\"\$dir\"" + else + eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" + fi + done + + # This variable tells wrapper scripts just to set shlibpath_var + # rather than running their programs. + libtool_execute_magic=$magic + + # Check if any of the arguments is a wrapper script. + args= + for file + do + case $file in + -* | *.la | *.lo ) ;; + *) + # Do a test to see if this is really a libtool program. + if func_ltwrapper_script_p "$file"; then + func_source "$file" + # Transform arg to wrapped name. + file=$progdir/$program + elif func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + func_source "$func_ltwrapper_scriptname_result" + # Transform arg to wrapped name. + file=$progdir/$program + fi + ;; + esac + # Quote arguments (to preserve shell metacharacters). + func_append_quoted args "$file" + done + + if $opt_dry_run; then + # Display what would be done. + if test -n "$shlibpath_var"; then + eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\"" + echo "export $shlibpath_var" + fi + $ECHO "$cmd$args" + exit $EXIT_SUCCESS + else + if test -n "$shlibpath_var"; then + # Export the shlibpath_var. + eval "export $shlibpath_var" + fi + + # Restore saved environment variables + for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + do + eval "if test \"\${save_$lt_var+set}\" = set; then + $lt_var=\$save_$lt_var; export $lt_var + else + $lt_unset $lt_var + fi" + done + + # Now prepare to actually exec the command. + exec_cmd=\$cmd$args + fi +} + +test execute = "$opt_mode" && func_mode_execute ${1+"$@"} + + +# func_mode_finish arg... +func_mode_finish () +{ + $debug_cmd + + libs= + libdirs= + admincmds= + + for opt in "$nonopt" ${1+"$@"} + do + if test -d "$opt"; then + func_append libdirs " $opt" + + elif test -f "$opt"; then + if func_lalib_unsafe_p "$opt"; then + func_append libs " $opt" + else + func_warning "'$opt' is not a valid libtool archive" + fi + + else + func_fatal_error "invalid argument '$opt'" + fi + done + + if test -n "$libs"; then + if test -n "$lt_sysroot"; then + sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"` + sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;" + else + sysroot_cmd= + fi + + # Remove sysroot references + if $opt_dry_run; then + for lib in $libs; do + echo "removing references to $lt_sysroot and '=' prefixes from $lib" + done + else + tmpdir=`func_mktempdir` + for lib in $libs; do + $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \ + > $tmpdir/tmp-la + mv -f $tmpdir/tmp-la $lib + done + ${RM}r "$tmpdir" + fi + fi + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + for libdir in $libdirs; do + if test -n "$finish_cmds"; then + # Do each command in the finish commands. + func_execute_cmds "$finish_cmds" 'admincmds="$admincmds +'"$cmd"'"' + fi + if test -n "$finish_eval"; then + # Do the single finish_eval. + eval cmds=\"$finish_eval\" + $opt_dry_run || eval "$cmds" || func_append admincmds " + $cmds" + fi + done + fi + + # Exit here if they wanted silent mode. + $opt_quiet && exit $EXIT_SUCCESS + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + echo "----------------------------------------------------------------------" + echo "Libraries have been installed in:" + for libdir in $libdirs; do + $ECHO " $libdir" + done + echo + echo "If you ever happen to want to link against installed libraries" + echo "in a given directory, LIBDIR, you must either use libtool, and" + echo "specify the full pathname of the library, or use the '-LLIBDIR'" + echo "flag during linking and do at least one of the following:" + if test -n "$shlibpath_var"; then + echo " - add LIBDIR to the '$shlibpath_var' environment variable" + echo " during execution" + fi + if test -n "$runpath_var"; then + echo " - add LIBDIR to the '$runpath_var' environment variable" + echo " during linking" + fi + if test -n "$hardcode_libdir_flag_spec"; then + libdir=LIBDIR + eval flag=\"$hardcode_libdir_flag_spec\" + + $ECHO " - use the '$flag' linker flag" + fi + if test -n "$admincmds"; then + $ECHO " - have your system administrator run these commands:$admincmds" + fi + if test -f /etc/ld.so.conf; then + echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'" + fi + echo + + echo "See any operating system documentation about shared libraries for" + case $host in + solaris2.[6789]|solaris2.1[0-9]) + echo "more information, such as the ld(1), crle(1) and ld.so(8) manual" + echo "pages." + ;; + *) + echo "more information, such as the ld(1) and ld.so(8) manual pages." + ;; + esac + echo "----------------------------------------------------------------------" + fi + exit $EXIT_SUCCESS +} + +test finish = "$opt_mode" && func_mode_finish ${1+"$@"} + + +# func_mode_install arg... +func_mode_install () +{ + $debug_cmd + + # There may be an optional sh(1) argument at the beginning of + # install_prog (especially on Windows NT). + if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" || + # Allow the use of GNU shtool's install command. + case $nonopt in *shtool*) :;; *) false;; esac + then + # Aesthetically quote it. + func_quote_for_eval "$nonopt" + install_prog="$func_quote_for_eval_result " + arg=$1 + shift + else + install_prog= + arg=$nonopt + fi + + # The real first argument should be the name of the installation program. + # Aesthetically quote it. + func_quote_for_eval "$arg" + func_append install_prog "$func_quote_for_eval_result" + install_shared_prog=$install_prog + case " $install_prog " in + *[\\\ /]cp\ *) install_cp=: ;; + *) install_cp=false ;; + esac + + # We need to accept at least all the BSD install flags. + dest= + files= + opts= + prev= + install_type= + isdir=false + stripme= + no_mode=: + for arg + do + arg2= + if test -n "$dest"; then + func_append files " $dest" + dest=$arg + continue + fi + + case $arg in + -d) isdir=: ;; + -f) + if $install_cp; then :; else + prev=$arg + fi + ;; + -g | -m | -o) + prev=$arg + ;; + -s) + stripme=" -s" + continue + ;; + -*) + ;; + *) + # If the previous option needed an argument, then skip it. + if test -n "$prev"; then + if test X-m = "X$prev" && test -n "$install_override_mode"; then + arg2=$install_override_mode + no_mode=false + fi + prev= + else + dest=$arg + continue + fi + ;; + esac + + # Aesthetically quote the argument. + func_quote_for_eval "$arg" + func_append install_prog " $func_quote_for_eval_result" + if test -n "$arg2"; then + func_quote_for_eval "$arg2" + fi + func_append install_shared_prog " $func_quote_for_eval_result" + done + + test -z "$install_prog" && \ + func_fatal_help "you must specify an install program" + + test -n "$prev" && \ + func_fatal_help "the '$prev' option requires an argument" + + if test -n "$install_override_mode" && $no_mode; then + if $install_cp; then :; else + func_quote_for_eval "$install_override_mode" + func_append install_shared_prog " -m $func_quote_for_eval_result" + fi + fi + + if test -z "$files"; then + if test -z "$dest"; then + func_fatal_help "no file or destination specified" + else + func_fatal_help "you must specify a destination" + fi + fi + + # Strip any trailing slash from the destination. + func_stripname '' '/' "$dest" + dest=$func_stripname_result + + # Check to see that the destination is a directory. + test -d "$dest" && isdir=: + if $isdir; then + destdir=$dest + destname= + else + func_dirname_and_basename "$dest" "" "." + destdir=$func_dirname_result + destname=$func_basename_result + + # Not a directory, so check to see that there is only one file specified. + set dummy $files; shift + test "$#" -gt 1 && \ + func_fatal_help "'$dest' is not a directory" + fi + case $destdir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + for file in $files; do + case $file in + *.lo) ;; + *) + func_fatal_help "'$destdir' must be an absolute directory name" + ;; + esac + done + ;; + esac + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + staticlibs= + future_libdirs= + current_libdirs= + for file in $files; do + + # Do each installation. + case $file in + *.$libext) + # Do the static libraries later. + func_append staticlibs " $file" + ;; + + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$file' is not a valid libtool archive" + + library_names= + old_library= + relink_command= + func_source "$file" + + # Add the libdir to current_libdirs if it is the destination. + if test "X$destdir" = "X$libdir"; then + case "$current_libdirs " in + *" $libdir "*) ;; + *) func_append current_libdirs " $libdir" ;; + esac + else + # Note the libdir as a future libdir. + case "$future_libdirs " in + *" $libdir "*) ;; + *) func_append future_libdirs " $libdir" ;; + esac + fi + + func_dirname "$file" "/" "" + dir=$func_dirname_result + func_append dir "$objdir" + + if test -n "$relink_command"; then + # Determine the prefix the user has applied to our future dir. + inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"` + + # Don't allow the user to place us outside of our expected + # location b/c this prevents finding dependent libraries that + # are installed to the same prefix. + # At present, this check doesn't affect windows .dll's that + # are installed into $libdir/../bin (currently, that works fine) + # but it's something to keep an eye on. + test "$inst_prefix_dir" = "$destdir" && \ + func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir" + + if test -n "$inst_prefix_dir"; then + # Stick the inst_prefix_dir data into the link command. + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"` + else + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"` + fi + + func_warning "relinking '$file'" + func_show_eval "$relink_command" \ + 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"' + fi + + # See the names of the shared library. + set dummy $library_names; shift + if test -n "$1"; then + realname=$1 + shift + + srcname=$realname + test -n "$relink_command" && srcname=${realname}T + + # Install the shared library and build the symlinks. + func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \ + 'exit $?' + tstripme=$stripme + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + case $realname in + *.dll.a) + tstripme= + ;; + esac + ;; + os2*) + case $realname in + *_dll.a) + tstripme= + ;; + esac + ;; + esac + if test -n "$tstripme" && test -n "$striplib"; then + func_show_eval "$striplib $destdir/$realname" 'exit $?' + fi + + if test "$#" -gt 0; then + # Delete the old symlinks, and create new ones. + # Try 'ln -sf' first, because the 'ln' binary might depend on + # the symlink we replace! Solaris /bin/ln does not understand -f, + # so we also need to try rm && ln -s. + for linkname + do + test "$linkname" != "$realname" \ + && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })" + done + fi + + # Do each command in the postinstall commands. + lib=$destdir/$realname + func_execute_cmds "$postinstall_cmds" 'exit $?' + fi + + # Install the pseudo-library for information purposes. + func_basename "$file" + name=$func_basename_result + instname=$dir/${name}i + func_show_eval "$install_prog $instname $destdir/$name" 'exit $?' + + # Maybe install the static library, too. + test -n "$old_library" && func_append staticlibs " $dir/$old_library" + ;; + + *.lo) + # Install (i.e. copy) a libtool object. + + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # Deduce the name of the destination old-style object file. + case $destfile in + *.lo) + func_lo2o "$destfile" + staticdest=$func_lo2o_result + ;; + *.$objext) + staticdest=$destfile + destfile= + ;; + *) + func_fatal_help "cannot copy a libtool object to '$destfile'" + ;; + esac + + # Install the libtool object if requested. + test -n "$destfile" && \ + func_show_eval "$install_prog $file $destfile" 'exit $?' + + # Install the old object if enabled. + if test yes = "$build_old_libs"; then + # Deduce the name of the old-style object file. + func_lo2o "$file" + staticobj=$func_lo2o_result + func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?' + fi + exit $EXIT_SUCCESS + ;; + + *) + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # If the file is missing, and there is a .exe on the end, strip it + # because it is most likely a libtool script we actually want to + # install + stripped_ext= + case $file in + *.exe) + if test ! -f "$file"; then + func_stripname '' '.exe' "$file" + file=$func_stripname_result + stripped_ext=.exe + fi + ;; + esac + + # Do a test to see if this is really a libtool program. + case $host in + *cygwin* | *mingw*) + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + wrapper=$func_ltwrapper_scriptname_result + else + func_stripname '' '.exe' "$file" + wrapper=$func_stripname_result + fi + ;; + *) + wrapper=$file + ;; + esac + if func_ltwrapper_script_p "$wrapper"; then + notinst_deplibs= + relink_command= + + func_source "$wrapper" + + # Check the variables that should have been set. + test -z "$generated_by_libtool_version" && \ + func_fatal_error "invalid libtool wrapper script '$wrapper'" + + finalize=: + for lib in $notinst_deplibs; do + # Check to see that each library is installed. + libdir= + if test -f "$lib"; then + func_source "$lib" + fi + libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'` + if test -n "$libdir" && test ! -f "$libfile"; then + func_warning "'$lib' has not been installed in '$libdir'" + finalize=false + fi + done + + relink_command= + func_source "$wrapper" + + outputname= + if test no = "$fast_install" && test -n "$relink_command"; then + $opt_dry_run || { + if $finalize; then + tmpdir=`func_mktempdir` + func_basename "$file$stripped_ext" + file=$func_basename_result + outputname=$tmpdir/$file + # Replace the output file specification. + relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'` + + $opt_quiet || { + func_quote_for_expand "$relink_command" + eval "func_echo $func_quote_for_expand_result" + } + if eval "$relink_command"; then : + else + func_error "error: relink '$file' with the above command before installing it" + $opt_dry_run || ${RM}r "$tmpdir" + continue + fi + file=$outputname + else + func_warning "cannot relink '$file'" + fi + } + else + # Install the binary that we compiled earlier. + file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"` + fi + fi + + # remove .exe since cygwin /usr/bin/install will append another + # one anyway + case $install_prog,$host in + */usr/bin/install*,*cygwin*) + case $file:$destfile in + *.exe:*.exe) + # this is ok + ;; + *.exe:*) + destfile=$destfile.exe + ;; + *:*.exe) + func_stripname '' '.exe' "$destfile" + destfile=$func_stripname_result + ;; + esac + ;; + esac + func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?' + $opt_dry_run || if test -n "$outputname"; then + ${RM}r "$tmpdir" + fi + ;; + esac + done + + for file in $staticlibs; do + func_basename "$file" + name=$func_basename_result + + # Set up the ranlib parameters. + oldlib=$destdir/$name + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + + func_show_eval "$install_prog \$file \$oldlib" 'exit $?' + + if test -n "$stripme" && test -n "$old_striplib"; then + func_show_eval "$old_striplib $tool_oldlib" 'exit $?' + fi + + # Do each command in the postinstall commands. + func_execute_cmds "$old_postinstall_cmds" 'exit $?' + done + + test -n "$future_libdirs" && \ + func_warning "remember to run '$progname --finish$future_libdirs'" + + if test -n "$current_libdirs"; then + # Maybe just do a dry run. + $opt_dry_run && current_libdirs=" -n$current_libdirs" + exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs' + else + exit $EXIT_SUCCESS + fi +} + +test install = "$opt_mode" && func_mode_install ${1+"$@"} + + +# func_generate_dlsyms outputname originator pic_p +# Extract symbols from dlprefiles and create ${outputname}S.o with +# a dlpreopen symbol table. +func_generate_dlsyms () +{ + $debug_cmd + + my_outputname=$1 + my_originator=$2 + my_pic_p=${3-false} + my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'` + my_dlsyms= + + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + if test -n "$NM" && test -n "$global_symbol_pipe"; then + my_dlsyms=${my_outputname}S.c + else + func_error "not configured to extract global symbols from dlpreopened files" + fi + fi + + if test -n "$my_dlsyms"; then + case $my_dlsyms in + "") ;; + *.c) + # Discover the nlist of each of the dlfiles. + nlist=$output_objdir/$my_outputname.nm + + func_show_eval "$RM $nlist ${nlist}S ${nlist}T" + + # Parse the name list into a source file. + func_verbose "creating $output_objdir/$my_dlsyms" + + $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\ +/* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */ +/* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */ + +#ifdef __cplusplus +extern \"C\" { +#endif + +#if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4)) +#pragma GCC diagnostic ignored \"-Wstrict-prototypes\" +#endif + +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* External symbol declarations for the compiler. */\ +" + + if test yes = "$dlself"; then + func_verbose "generating symbol list for '$output'" + + $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist" + + # Add our own program objects to the symbol list. + progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP` + for progfile in $progfiles; do + func_to_tool_file "$progfile" func_convert_file_msys_to_w32 + func_verbose "extracting global C symbols from '$func_to_tool_file_result'" + $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'" + done + + if test -n "$exclude_expsyms"; then + $opt_dry_run || { + eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + if test -n "$export_symbols_regex"; then + $opt_dry_run || { + eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + export_symbols=$output_objdir/$outputname.exp + $opt_dry_run || { + $RM $export_symbols + eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' + ;; + esac + } + else + $opt_dry_run || { + eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' + eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' + ;; + esac + } + fi + fi + + for dlprefile in $dlprefiles; do + func_verbose "extracting global C symbols from '$dlprefile'" + func_basename "$dlprefile" + name=$func_basename_result + case $host in + *cygwin* | *mingw* | *cegcc* ) + # if an import library, we need to obtain dlname + if func_win32_import_lib_p "$dlprefile"; then + func_tr_sh "$dlprefile" + eval "curr_lafile=\$libfile_$func_tr_sh_result" + dlprefile_dlbasename= + if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then + # Use subshell, to avoid clobbering current variable values + dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"` + if test -n "$dlprefile_dlname"; then + func_basename "$dlprefile_dlname" + dlprefile_dlbasename=$func_basename_result + else + # no lafile. user explicitly requested -dlpreopen . + $sharedlib_from_linklib_cmd "$dlprefile" + dlprefile_dlbasename=$sharedlib_from_linklib_result + fi + fi + $opt_dry_run || { + if test -n "$dlprefile_dlbasename"; then + eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"' + else + func_warning "Could not compute DLL name from $name" + eval '$ECHO ": $name " >> "$nlist"' + fi + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | + $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'" + } + else # not an import lib + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + fi + ;; + *) + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + ;; + esac + done + + $opt_dry_run || { + # Make sure we have at least an empty file. + test -f "$nlist" || : > "$nlist" + + if test -n "$exclude_expsyms"; then + $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T + $MV "$nlist"T "$nlist" + fi + + # Try sorting and uniquifying the output. + if $GREP -v "^: " < "$nlist" | + if sort -k 3 /dev/null 2>&1; then + sort -k 3 + else + sort +2 + fi | + uniq > "$nlist"S; then + : + else + $GREP -v "^: " < "$nlist" > "$nlist"S + fi + + if test -f "$nlist"S; then + eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"' + else + echo '/* NONE */' >> "$output_objdir/$my_dlsyms" + fi + + func_show_eval '$RM "${nlist}I"' + if test -n "$global_symbol_to_import"; then + eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I' + fi + + echo >> "$output_objdir/$my_dlsyms" "\ + +/* The mapping between symbol names and symbols. */ +typedef struct { + const char *name; + void *address; +} lt_dlsymlist; +extern LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[];\ +" + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ +static void lt_syminit(void) +{ + LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols; + for (; symbol->name; ++symbol) + {" + $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms" + echo >> "$output_objdir/$my_dlsyms" "\ + } +}" + fi + echo >> "$output_objdir/$my_dlsyms" "\ +LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[] = +{ {\"$my_originator\", (void *) 0}," + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ + {\"@INIT@\", (void *) <_syminit}," + fi + + case $need_lib_prefix in + no) + eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + *) + eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + esac + echo >> "$output_objdir/$my_dlsyms" "\ + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt_${my_prefix}_LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif\ +" + } # !$opt_dry_run + + pic_flag_for_symtable= + case "$compile_command " in + *" -static "*) ;; + *) + case $host in + # compiling the symbol table file with pic_flag works around + # a FreeBSD bug that causes programs to crash when -lm is + # linked before any other PIC object. But we must not use + # pic_flag when linking with -static. The problem exists in + # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. + *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) + pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;; + *-*-hpux*) + pic_flag_for_symtable=" $pic_flag" ;; + *) + $my_pic_p && pic_flag_for_symtable=" $pic_flag" + ;; + esac + ;; + esac + symtab_cflags= + for arg in $LTCFLAGS; do + case $arg in + -pie | -fpie | -fPIE) ;; + *) func_append symtab_cflags " $arg" ;; + esac + done + + # Now compile the dynamic symbol file. + func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?' + + # Clean up the generated files. + func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"' + + # Transform the symbol file into the correct name. + symfileobj=$output_objdir/${my_outputname}S.$objext + case $host in + *cygwin* | *mingw* | *cegcc* ) + if test -f "$output_objdir/$my_outputname.def"; then + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + else + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + fi + ;; + *) + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + ;; + esac + ;; + *) + func_fatal_error "unknown suffix for '$my_dlsyms'" + ;; + esac + else + # We keep going just in case the user didn't refer to + # lt_preloaded_symbols. The linker will fail if global_symbol_pipe + # really was required. + + # Nullify the symbol file. + compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"` + fi +} + +# func_cygming_gnu_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is a GNU/binutils-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_gnu_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'` + test -n "$func_cygming_gnu_implib_tmp" +} + +# func_cygming_ms_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is an MS-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_ms_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'` + test -n "$func_cygming_ms_implib_tmp" +} + +# func_win32_libid arg +# return the library type of file 'arg' +# +# Need a lot of goo to handle *both* DLLs and import libs +# Has to be a shell function in order to 'eat' the argument +# that is supplied when $file_magic_command is called. +# Despite the name, also deal with 64 bit binaries. +func_win32_libid () +{ + $debug_cmd + + win32_libid_type=unknown + win32_fileres=`file -L $1 2>/dev/null` + case $win32_fileres in + *ar\ archive\ import\ library*) # definitely import + win32_libid_type="x86 archive import" + ;; + *ar\ archive*) # could be an import, or static + # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD. + if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | + $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then + case $nm_interface in + "MS dumpbin") + if func_cygming_ms_implib_p "$1" || + func_cygming_gnu_implib_p "$1" + then + win32_nmres=import + else + win32_nmres= + fi + ;; + *) + func_to_tool_file "$1" func_convert_file_msys_to_w32 + win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" | + $SED -n -e ' + 1,100{ + / I /{ + s|.*|import| + p + q + } + }'` + ;; + esac + case $win32_nmres in + import*) win32_libid_type="x86 archive import";; + *) win32_libid_type="x86 archive static";; + esac + fi + ;; + *DLL*) + win32_libid_type="x86 DLL" + ;; + *executable*) # but shell scripts are "executable" too... + case $win32_fileres in + *MS\ Windows\ PE\ Intel*) + win32_libid_type="x86 DLL" + ;; + esac + ;; + esac + $ECHO "$win32_libid_type" +} + +# func_cygming_dll_for_implib ARG +# +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib () +{ + $debug_cmd + + sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"` +} + +# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs +# +# The is the core of a fallback implementation of a +# platform-specific function to extract the name of the +# DLL associated with the specified import library LIBNAME. +# +# SECTION_NAME is either .idata$6 or .idata$7, depending +# on the platform and compiler that created the implib. +# +# Echos the name of the DLL associated with the +# specified import library. +func_cygming_dll_for_implib_fallback_core () +{ + $debug_cmd + + match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"` + $OBJDUMP -s --section "$1" "$2" 2>/dev/null | + $SED '/^Contents of section '"$match_literal"':/{ + # Place marker at beginning of archive member dllname section + s/.*/====MARK====/ + p + d + } + # These lines can sometimes be longer than 43 characters, but + # are always uninteresting + /:[ ]*file format pe[i]\{,1\}-/d + /^In archive [^:]*:/d + # Ensure marker is printed + /^====MARK====/p + # Remove all lines with less than 43 characters + /^.\{43\}/!d + # From remaining lines, remove first 43 characters + s/^.\{43\}//' | + $SED -n ' + # Join marker and all lines until next marker into a single line + /^====MARK====/ b para + H + $ b para + b + :para + x + s/\n//g + # Remove the marker + s/^====MARK====// + # Remove trailing dots and whitespace + s/[\. \t]*$// + # Print + /./p' | + # we now have a list, one entry per line, of the stringified + # contents of the appropriate section of all members of the + # archive that possess that section. Heuristic: eliminate + # all those that have a first or second character that is + # a '.' (that is, objdump's representation of an unprintable + # character.) This should work for all archives with less than + # 0x302f exports -- but will fail for DLLs whose name actually + # begins with a literal '.' or a single character followed by + # a '.'. + # + # Of those that remain, print the first one. + $SED -e '/^\./d;/^.\./d;q' +} + +# func_cygming_dll_for_implib_fallback ARG +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# +# This fallback implementation is for use when $DLLTOOL +# does not support the --identify-strict option. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib_fallback () +{ + $debug_cmd + + if func_cygming_gnu_implib_p "$1"; then + # binutils import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"` + elif func_cygming_ms_implib_p "$1"; then + # ms-generated import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"` + else + # unknown + sharedlib_from_linklib_result= + fi +} + + +# func_extract_an_archive dir oldlib +func_extract_an_archive () +{ + $debug_cmd + + f_ex_an_ar_dir=$1; shift + f_ex_an_ar_oldlib=$1 + if test yes = "$lock_old_archive_extraction"; then + lockfile=$f_ex_an_ar_oldlib.lock + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + fi + func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \ + 'stat=$?; rm -f "$lockfile"; exit $stat' + if test yes = "$lock_old_archive_extraction"; then + $opt_dry_run || rm -f "$lockfile" + fi + if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then + : + else + func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" + fi +} + + +# func_extract_archives gentop oldlib ... +func_extract_archives () +{ + $debug_cmd + + my_gentop=$1; shift + my_oldlibs=${1+"$@"} + my_oldobjs= + my_xlib= + my_xabs= + my_xdir= + + for my_xlib in $my_oldlibs; do + # Extract the objects. + case $my_xlib in + [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;; + *) my_xabs=`pwd`"/$my_xlib" ;; + esac + func_basename "$my_xlib" + my_xlib=$func_basename_result + my_xlib_u=$my_xlib + while :; do + case " $extracted_archives " in + *" $my_xlib_u "*) + func_arith $extracted_serial + 1 + extracted_serial=$func_arith_result + my_xlib_u=lt$extracted_serial-$my_xlib ;; + *) break ;; + esac + done + extracted_archives="$extracted_archives $my_xlib_u" + my_xdir=$my_gentop/$my_xlib_u + + func_mkdir_p "$my_xdir" + + case $host in + *-darwin*) + func_verbose "Extracting $my_xabs" + # Do not bother doing anything if just a dry run + $opt_dry_run || { + darwin_orig_dir=`pwd` + cd $my_xdir || exit $? + darwin_archive=$my_xabs + darwin_curdir=`pwd` + func_basename "$darwin_archive" + darwin_base_archive=$func_basename_result + darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true` + if test -n "$darwin_arches"; then + darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'` + darwin_arch= + func_verbose "$darwin_base_archive has multiple architectures $darwin_arches" + for darwin_arch in $darwin_arches; do + func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch" + $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive" + cd "unfat-$$/$darwin_base_archive-$darwin_arch" + func_extract_an_archive "`pwd`" "$darwin_base_archive" + cd "$darwin_curdir" + $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" + done # $darwin_arches + ## Okay now we've a bunch of thin objects, gotta fatten them up :) + darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u` + darwin_file= + darwin_files= + for darwin_file in $darwin_filelist; do + darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP` + $LIPO -create -output "$darwin_file" $darwin_files + done # $darwin_filelist + $RM -rf unfat-$$ + cd "$darwin_orig_dir" + else + cd $darwin_orig_dir + func_extract_an_archive "$my_xdir" "$my_xabs" + fi # $darwin_arches + } # !$opt_dry_run + ;; + *) + func_extract_an_archive "$my_xdir" "$my_xabs" + ;; + esac + my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP` + done + + func_extract_archives_result=$my_oldobjs +} + + +# func_emit_wrapper [arg=no] +# +# Emit a libtool wrapper script on stdout. +# Don't directly open a file because we may want to +# incorporate the script contents within a cygwin/mingw +# wrapper executable. Must ONLY be called from within +# func_mode_link because it depends on a number of variables +# set therein. +# +# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR +# variable will take. If 'yes', then the emitted script +# will assume that the directory where it is stored is +# the $objdir directory. This is a cygwin/mingw-specific +# behavior. +func_emit_wrapper () +{ + func_emit_wrapper_arg1=${1-no} + + $ECHO "\ +#! $SHELL + +# $output - temporary wrapper script for $objdir/$outputname +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# The $output program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='$sed_quote_subst' + +# Be Bourne compatible +if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command=\"$relink_command\" + +# This environment variable determines our operation mode. +if test \"\$libtool_install_magic\" = \"$magic\"; then + # install mode needs the following variables: + generated_by_libtool_version='$macro_version' + notinst_deplibs='$notinst_deplibs' +else + # When we are sourced in execute mode, \$file and \$ECHO are already set. + if test \"\$libtool_execute_magic\" != \"$magic\"; then + file=\"\$0\"" + + qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"` + $ECHO "\ + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + ECHO=\"$qECHO\" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ that is used only on +# windows platforms, and (c) all begin with the string "--lt-" +# (application programs are unlikely to have options that match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's $0 value, followed by "$@". +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=\$0 + shift + for lt_opt + do + case \"\$lt_opt\" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\` + test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=. + lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\` + cat \"\$lt_dump_D/\$lt_dump_F\" + exit 0 + ;; + --lt-*) + \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n \"\$lt_option_debug\"; then + echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\" + lt_dump_args_N=\`expr \$lt_dump_args_N + 1\` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ +" + case $host in + # Backslashes separate directories on plain windows + *-*-mingw | *-*-os2* | *-cegcc*) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} +" + ;; + + *) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir/\$program\" \${1+\"\$@\"} +" + ;; + esac + $ECHO "\ + \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from \$@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case \" \$* \" in + *\\ --lt-*) + for lt_wr_arg + do + case \$lt_wr_arg in + --lt-*) ;; + *) set x \"\$@\" \"\$lt_wr_arg\"; shift;; + esac + shift + done ;; + esac + func_exec_program_core \${1+\"\$@\"} +} + + # Parse options + func_parse_lt_options \"\$0\" \${1+\"\$@\"} + + # Find the directory that this script lives in. + thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\` + test \"x\$thisdir\" = \"x\$file\" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\` + while test -n \"\$file\"; do + destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\` + + # If there was a directory component, then change thisdir. + if test \"x\$destdir\" != \"x\$file\"; then + case \"\$destdir\" in + [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; + *) thisdir=\"\$thisdir/\$destdir\" ;; + esac + fi + + file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\` + file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1 + if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then + # special case for '.' + if test \"\$thisdir\" = \".\"; then + thisdir=\`pwd\` + fi + # remove .libs from thisdir + case \"\$thisdir\" in + *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;; + $objdir ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=\`cd \"\$thisdir\" && pwd\` + test -n \"\$absdir\" && thisdir=\"\$absdir\" +" + + if test yes = "$fast_install"; then + $ECHO "\ + program=lt-'$outputname'$exeext + progdir=\"\$thisdir/$objdir\" + + if test ! -f \"\$progdir/\$program\" || + { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\ + test \"X\$file\" != \"X\$progdir/\$program\"; }; then + + file=\"\$\$-\$program\" + + if test ! -d \"\$progdir\"; then + $MKDIR \"\$progdir\" + else + $RM \"\$progdir/\$file\" + fi" + + $ECHO "\ + + # relink executable if necessary + if test -n \"\$relink_command\"; then + if relink_command_output=\`eval \$relink_command 2>&1\`; then : + else + \$ECHO \"\$relink_command_output\" >&2 + $RM \"\$progdir/\$file\" + exit 1 + fi + fi + + $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || + { $RM \"\$progdir/\$program\"; + $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; } + $RM \"\$progdir/\$file\" + fi" + else + $ECHO "\ + program='$outputname' + progdir=\"\$thisdir/$objdir\" +" + fi + + $ECHO "\ + + if test -f \"\$progdir/\$program\"; then" + + # fixup the dll searchpath if we need to. + # + # Fix the DLL searchpath if we need to. Do this before prepending + # to shlibpath, because on Windows, both are PATH and uninstalled + # libraries must come first. + if test -n "$dllsearchpath"; then + $ECHO "\ + # Add the dll search path components to the executable PATH + PATH=$dllsearchpath:\$PATH +" + fi + + # Export our shlibpath_var if we have one. + if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + $ECHO "\ + # Add our own library path to $shlibpath_var + $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" + + # Some systems cannot cope with colon-terminated $shlibpath_var + # The second colon is a workaround for a bug in BeOS R4 sed + $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\` + + export $shlibpath_var +" + fi + + $ECHO "\ + if test \"\$libtool_execute_magic\" != \"$magic\"; then + # Run the actual program with our arguments. + func_exec_program \${1+\"\$@\"} + fi + else + # The program doesn't exist. + \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2 + \$ECHO \"This script is just a wrapper for \$program.\" 1>&2 + \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2 + exit 1 + fi +fi\ +" +} + + +# func_emit_cwrapperexe_src +# emit the source code for a wrapper executable on stdout +# Must ONLY be called from within func_mode_link because +# it depends on a number of variable set therein. +func_emit_cwrapperexe_src () +{ + cat < +#include +#ifdef _MSC_VER +# include +# include +# include +#else +# include +# include +# ifdef __CYGWIN__ +# include +# endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* declarations of non-ANSI functions */ +#if defined __MINGW32__ +# ifdef __STRICT_ANSI__ +int _putenv (const char *); +# endif +#elif defined __CYGWIN__ +# ifdef __STRICT_ANSI__ +char *realpath (const char *, char *); +int putenv (char *); +int setenv (const char *, const char *, int); +# endif +/* #elif defined other_platform || defined ... */ +#endif + +/* portability defines, excluding path handling macros */ +#if defined _MSC_VER +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +# define S_IXUSR _S_IEXEC +#elif defined __MINGW32__ +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +#elif defined __CYGWIN__ +# define HAVE_SETENV +# define FOPEN_WB "wb" +/* #elif defined other platforms ... */ +#endif + +#if defined PATH_MAX +# define LT_PATHMAX PATH_MAX +#elif defined MAXPATHLEN +# define LT_PATHMAX MAXPATHLEN +#else +# define LT_PATHMAX 1024 +#endif + +#ifndef S_IXOTH +# define S_IXOTH 0 +#endif +#ifndef S_IXGRP +# define S_IXGRP 0 +#endif + +/* path handling portability macros */ +#ifndef DIR_SEPARATOR +# define DIR_SEPARATOR '/' +# define PATH_SEPARATOR ':' +#endif + +#if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \ + defined __OS2__ +# define HAVE_DOS_BASED_FILE_SYSTEM +# define FOPEN_WB "wb" +# ifndef DIR_SEPARATOR_2 +# define DIR_SEPARATOR_2 '\\' +# endif +# ifndef PATH_SEPARATOR_2 +# define PATH_SEPARATOR_2 ';' +# endif +#endif + +#ifndef DIR_SEPARATOR_2 +# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) +#else /* DIR_SEPARATOR_2 */ +# define IS_DIR_SEPARATOR(ch) \ + (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) +#endif /* DIR_SEPARATOR_2 */ + +#ifndef PATH_SEPARATOR_2 +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) +#else /* PATH_SEPARATOR_2 */ +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) +#endif /* PATH_SEPARATOR_2 */ + +#ifndef FOPEN_WB +# define FOPEN_WB "w" +#endif +#ifndef _O_BINARY +# define _O_BINARY 0 +#endif + +#define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) +#define XFREE(stale) do { \ + if (stale) { free (stale); stale = 0; } \ +} while (0) + +#if defined LT_DEBUGWRAPPER +static int lt_debug = 1; +#else +static int lt_debug = 0; +#endif + +const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */ + +void *xmalloc (size_t num); +char *xstrdup (const char *string); +const char *base_name (const char *name); +char *find_executable (const char *wrapper); +char *chase_symlinks (const char *pathspec); +int make_executable (const char *path); +int check_executable (const char *path); +char *strendzap (char *str, const char *pat); +void lt_debugprintf (const char *file, int line, const char *fmt, ...); +void lt_fatal (const char *file, int line, const char *message, ...); +static const char *nonnull (const char *s); +static const char *nonempty (const char *s); +void lt_setenv (const char *name, const char *value); +char *lt_extend_str (const char *orig_value, const char *add, int to_end); +void lt_update_exe_path (const char *name, const char *value); +void lt_update_lib_path (const char *name, const char *value); +char **prepare_spawn (char **argv); +void lt_dump_script (FILE *f); +EOF + + cat <= 0) + && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return 1; + else + return 0; +} + +int +make_executable (const char *path) +{ + int rval = 0; + struct stat st; + + lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n", + nonempty (path)); + if ((!path) || (!*path)) + return 0; + + if (stat (path, &st) >= 0) + { + rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR); + } + return rval; +} + +/* Searches for the full path of the wrapper. Returns + newly allocated full path name if found, NULL otherwise + Does not chase symlinks, even on platforms that support them. +*/ +char * +find_executable (const char *wrapper) +{ + int has_slash = 0; + const char *p; + const char *p_next; + /* static buffer for getcwd */ + char tmp[LT_PATHMAX + 1]; + size_t tmp_len; + char *concat_name; + + lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n", + nonempty (wrapper)); + + if ((wrapper == NULL) || (*wrapper == '\0')) + return NULL; + + /* Absolute path? */ +#if defined HAVE_DOS_BASED_FILE_SYSTEM + if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':') + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + else + { +#endif + if (IS_DIR_SEPARATOR (wrapper[0])) + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } +#if defined HAVE_DOS_BASED_FILE_SYSTEM + } +#endif + + for (p = wrapper; *p; p++) + if (*p == '/') + { + has_slash = 1; + break; + } + if (!has_slash) + { + /* no slashes; search PATH */ + const char *path = getenv ("PATH"); + if (path != NULL) + { + for (p = path; *p; p = p_next) + { + const char *q; + size_t p_len; + for (q = p; *q; q++) + if (IS_PATH_SEPARATOR (*q)) + break; + p_len = (size_t) (q - p); + p_next = (*q == '\0' ? q : q + 1); + if (p_len == 0) + { + /* empty path: current directory */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = + XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + } + else + { + concat_name = + XMALLOC (char, p_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, p, p_len); + concat_name[p_len] = '/'; + strcpy (concat_name + p_len + 1, wrapper); + } + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + } + /* not found in PATH; assume curdir */ + } + /* Relative path | not found in path: prepend cwd */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + return NULL; +} + +char * +chase_symlinks (const char *pathspec) +{ +#ifndef S_ISLNK + return xstrdup (pathspec); +#else + char buf[LT_PATHMAX]; + struct stat s; + char *tmp_pathspec = xstrdup (pathspec); + char *p; + int has_symlinks = 0; + while (strlen (tmp_pathspec) && !has_symlinks) + { + lt_debugprintf (__FILE__, __LINE__, + "checking path component for symlinks: %s\n", + tmp_pathspec); + if (lstat (tmp_pathspec, &s) == 0) + { + if (S_ISLNK (s.st_mode) != 0) + { + has_symlinks = 1; + break; + } + + /* search backwards for last DIR_SEPARATOR */ + p = tmp_pathspec + strlen (tmp_pathspec) - 1; + while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + p--; + if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + { + /* no more DIR_SEPARATORS left */ + break; + } + *p = '\0'; + } + else + { + lt_fatal (__FILE__, __LINE__, + "error accessing file \"%s\": %s", + tmp_pathspec, nonnull (strerror (errno))); + } + } + XFREE (tmp_pathspec); + + if (!has_symlinks) + { + return xstrdup (pathspec); + } + + tmp_pathspec = realpath (pathspec, buf); + if (tmp_pathspec == 0) + { + lt_fatal (__FILE__, __LINE__, + "could not follow symlinks for %s", pathspec); + } + return xstrdup (tmp_pathspec); +#endif +} + +char * +strendzap (char *str, const char *pat) +{ + size_t len, patlen; + + assert (str != NULL); + assert (pat != NULL); + + len = strlen (str); + patlen = strlen (pat); + + if (patlen <= len) + { + str += len - patlen; + if (STREQ (str, pat)) + *str = '\0'; + } + return str; +} + +void +lt_debugprintf (const char *file, int line, const char *fmt, ...) +{ + va_list args; + if (lt_debug) + { + (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line); + va_start (args, fmt); + (void) vfprintf (stderr, fmt, args); + va_end (args); + } +} + +static void +lt_error_core (int exit_status, const char *file, + int line, const char *mode, + const char *message, va_list ap) +{ + fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode); + vfprintf (stderr, message, ap); + fprintf (stderr, ".\n"); + + if (exit_status >= 0) + exit (exit_status); +} + +void +lt_fatal (const char *file, int line, const char *message, ...) +{ + va_list ap; + va_start (ap, message); + lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap); + va_end (ap); +} + +static const char * +nonnull (const char *s) +{ + return s ? s : "(null)"; +} + +static const char * +nonempty (const char *s) +{ + return (s && !*s) ? "(empty)" : nonnull (s); +} + +void +lt_setenv (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_setenv) setting '%s' to '%s'\n", + nonnull (name), nonnull (value)); + { +#ifdef HAVE_SETENV + /* always make a copy, for consistency with !HAVE_SETENV */ + char *str = xstrdup (value); + setenv (name, str, 1); +#else + size_t len = strlen (name) + 1 + strlen (value) + 1; + char *str = XMALLOC (char, len); + sprintf (str, "%s=%s", name, value); + if (putenv (str) != EXIT_SUCCESS) + { + XFREE (str); + } +#endif + } +} + +char * +lt_extend_str (const char *orig_value, const char *add, int to_end) +{ + char *new_value; + if (orig_value && *orig_value) + { + size_t orig_value_len = strlen (orig_value); + size_t add_len = strlen (add); + new_value = XMALLOC (char, add_len + orig_value_len + 1); + if (to_end) + { + strcpy (new_value, orig_value); + strcpy (new_value + orig_value_len, add); + } + else + { + strcpy (new_value, add); + strcpy (new_value + add_len, orig_value); + } + } + else + { + new_value = xstrdup (add); + } + return new_value; +} + +void +lt_update_exe_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_exe_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + /* some systems can't cope with a ':'-terminated path #' */ + size_t len = strlen (new_value); + while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1])) + { + new_value[--len] = '\0'; + } + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +void +lt_update_lib_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_lib_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +EOF + case $host_os in + mingw*) + cat <<"EOF" + +/* Prepares an argument vector before calling spawn(). + Note that spawn() does not by itself call the command interpreter + (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") : + ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&v); + v.dwPlatformId == VER_PLATFORM_WIN32_NT; + }) ? "cmd.exe" : "command.com"). + Instead it simply concatenates the arguments, separated by ' ', and calls + CreateProcess(). We must quote the arguments since Win32 CreateProcess() + interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a + special way: + - Space and tab are interpreted as delimiters. They are not treated as + delimiters if they are surrounded by double quotes: "...". + - Unescaped double quotes are removed from the input. Their only effect is + that within double quotes, space and tab are treated like normal + characters. + - Backslashes not followed by double quotes are not special. + - But 2*n+1 backslashes followed by a double quote become + n backslashes followed by a double quote (n >= 0): + \" -> " + \\\" -> \" + \\\\\" -> \\" + */ +#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +char ** +prepare_spawn (char **argv) +{ + size_t argc; + char **new_argv; + size_t i; + + /* Count number of arguments. */ + for (argc = 0; argv[argc] != NULL; argc++) + ; + + /* Allocate new argument vector. */ + new_argv = XMALLOC (char *, argc + 1); + + /* Put quoted arguments into the new argument vector. */ + for (i = 0; i < argc; i++) + { + const char *string = argv[i]; + + if (string[0] == '\0') + new_argv[i] = xstrdup ("\"\""); + else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL) + { + int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL); + size_t length; + unsigned int backslashes; + const char *s; + char *quoted_string; + char *p; + + length = 0; + backslashes = 0; + if (quote_around) + length++; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + length += backslashes + 1; + length++; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + length += backslashes + 1; + + quoted_string = XMALLOC (char, length + 1); + + p = quoted_string; + backslashes = 0; + if (quote_around) + *p++ = '"'; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + { + unsigned int j; + for (j = backslashes + 1; j > 0; j--) + *p++ = '\\'; + } + *p++ = c; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + { + unsigned int j; + for (j = backslashes; j > 0; j--) + *p++ = '\\'; + *p++ = '"'; + } + *p = '\0'; + + new_argv[i] = quoted_string; + } + else + new_argv[i] = (char *) string; + } + new_argv[argc] = NULL; + + return new_argv; +} +EOF + ;; + esac + + cat <<"EOF" +void lt_dump_script (FILE* f) +{ +EOF + func_emit_wrapper yes | + $SED -n -e ' +s/^\(.\{79\}\)\(..*\)/\1\ +\2/ +h +s/\([\\"]\)/\\\1/g +s/$/\\n/ +s/\([^\n]*\).*/ fputs ("\1", f);/p +g +D' + cat <<"EOF" +} +EOF +} +# end: func_emit_cwrapperexe_src + +# func_win32_import_lib_p ARG +# True if ARG is an import lib, as indicated by $file_magic_cmd +func_win32_import_lib_p () +{ + $debug_cmd + + case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in + *import*) : ;; + *) false ;; + esac +} + +# func_suncc_cstd_abi +# !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!! +# Several compiler flags select an ABI that is incompatible with the +# Cstd library. Avoid specifying it if any are in CXXFLAGS. +func_suncc_cstd_abi () +{ + $debug_cmd + + case " $compile_command " in + *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*) + suncc_use_cstd_abi=no + ;; + *) + suncc_use_cstd_abi=yes + ;; + esac +} + +# func_mode_link arg... +func_mode_link () +{ + $debug_cmd + + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + # It is impossible to link a dll without this setting, and + # we shouldn't force the makefile maintainer to figure out + # what system we are compiling for in order to pass an extra + # flag for every libtool invocation. + # allow_undefined=no + + # FIXME: Unfortunately, there are problems with the above when trying + # to make a dll that has undefined symbols, in which case not + # even a static library is built. For now, we need to specify + # -no-undefined on the libtool link line when we can be certain + # that all symbols are satisfied, otherwise we get a static library. + allow_undefined=yes + ;; + *) + allow_undefined=yes + ;; + esac + libtool_args=$nonopt + base_compile="$nonopt $@" + compile_command=$nonopt + finalize_command=$nonopt + + compile_rpath= + finalize_rpath= + compile_shlibpath= + finalize_shlibpath= + convenience= + old_convenience= + deplibs= + old_deplibs= + compiler_flags= + linker_flags= + dllsearchpath= + lib_search_path=`pwd` + inst_prefix_dir= + new_inherited_linker_flags= + + avoid_version=no + bindir= + dlfiles= + dlprefiles= + dlself=no + export_dynamic=no + export_symbols= + export_symbols_regex= + generated= + libobjs= + ltlibs= + module=no + no_install=no + objs= + os2dllname= + non_pic_objects= + precious_files_regex= + prefer_static_libs=no + preload=false + prev= + prevarg= + release= + rpath= + xrpath= + perm_rpath= + temp_rpath= + thread_safe=no + vinfo= + vinfo_number=no + weak_libs= + single_module=$wl-single_module + func_infer_tag $base_compile + + # We need to know -static, to get the right output filenames. + for arg + do + case $arg in + -shared) + test yes != "$build_libtool_libs" \ + && func_fatal_configuration "cannot build a shared library" + build_old_libs=no + break + ;; + -all-static | -static | -static-libtool-libs) + case $arg in + -all-static) + if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then + func_warning "complete static linking is impossible in this configuration" + fi + if test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + -static) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=built + ;; + -static-libtool-libs) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + esac + build_libtool_libs=no + build_old_libs=yes + break + ;; + esac + done + + # See if our shared archives depend on static archives. + test -n "$old_archive_from_new_cmds" && build_old_libs=yes + + # Go through the arguments, transforming them on the way. + while test "$#" -gt 0; do + arg=$1 + shift + func_quote_for_eval "$arg" + qarg=$func_quote_for_eval_unquoted_result + func_append libtool_args " $func_quote_for_eval_result" + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case $prev in + output) + func_append compile_command " @OUTPUT@" + func_append finalize_command " @OUTPUT@" + ;; + esac + + case $prev in + bindir) + bindir=$arg + prev= + continue + ;; + dlfiles|dlprefiles) + $preload || { + # Add the symbol object into the linking commands. + func_append compile_command " @SYMFILE@" + func_append finalize_command " @SYMFILE@" + preload=: + } + case $arg in + *.la | *.lo) ;; # We handle these cases below. + force) + if test no = "$dlself"; then + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + self) + if test dlprefiles = "$prev"; then + dlself=yes + elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then + dlself=yes + else + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + *) + if test dlfiles = "$prev"; then + func_append dlfiles " $arg" + else + func_append dlprefiles " $arg" + fi + prev= + continue + ;; + esac + ;; + expsyms) + export_symbols=$arg + test -f "$arg" \ + || func_fatal_error "symbol file '$arg' does not exist" + prev= + continue + ;; + expsyms_regex) + export_symbols_regex=$arg + prev= + continue + ;; + framework) + case $host in + *-*-darwin*) + case "$deplibs " in + *" $qarg.ltframework "*) ;; + *) func_append deplibs " $qarg.ltframework" # this is fixed later + ;; + esac + ;; + esac + prev= + continue + ;; + inst_prefix) + inst_prefix_dir=$arg + prev= + continue + ;; + mllvm) + # Clang does not use LLVM to link, so we can simply discard any + # '-mllvm $arg' options when doing the link step. + prev= + continue + ;; + objectlist) + if test -f "$arg"; then + save_arg=$arg + moreargs= + for fil in `cat "$save_arg"` + do +# func_append moreargs " $fil" + arg=$fil + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + if test none != "$pic_object"; then + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + fi + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + done + else + func_fatal_error "link input file '$arg' does not exist" + fi + arg=$save_arg + prev= + continue + ;; + os2dllname) + os2dllname=$arg + prev= + continue + ;; + precious_regex) + precious_files_regex=$arg + prev= + continue + ;; + release) + release=-$arg + prev= + continue + ;; + rpath | xrpath) + # We need an absolute path. + case $arg in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + if test rpath = "$prev"; then + case "$rpath " in + *" $arg "*) ;; + *) func_append rpath " $arg" ;; + esac + else + case "$xrpath " in + *" $arg "*) ;; + *) func_append xrpath " $arg" ;; + esac + fi + prev= + continue + ;; + shrext) + shrext_cmds=$arg + prev= + continue + ;; + weak) + func_append weak_libs " $arg" + prev= + continue + ;; + xcclinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xcompiler) + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xlinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $wl$qarg" + prev= + func_append compile_command " $wl$qarg" + func_append finalize_command " $wl$qarg" + continue + ;; + *) + eval "$prev=\"\$arg\"" + prev= + continue + ;; + esac + fi # test -n "$prev" + + prevarg=$arg + + case $arg in + -all-static) + if test -n "$link_static_flag"; then + # See comment for -static flag below, for more details. + func_append compile_command " $link_static_flag" + func_append finalize_command " $link_static_flag" + fi + continue + ;; + + -allow-undefined) + # FIXME: remove this flag sometime in the future. + func_fatal_error "'-allow-undefined' must not be used because it is the default" + ;; + + -avoid-version) + avoid_version=yes + continue + ;; + + -bindir) + prev=bindir + continue + ;; + + -dlopen) + prev=dlfiles + continue + ;; + + -dlpreopen) + prev=dlprefiles + continue + ;; + + -export-dynamic) + export_dynamic=yes + continue + ;; + + -export-symbols | -export-symbols-regex) + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + func_fatal_error "more than one -exported-symbols argument is not allowed" + fi + if test X-export-symbols = "X$arg"; then + prev=expsyms + else + prev=expsyms_regex + fi + continue + ;; + + -framework) + prev=framework + continue + ;; + + -inst-prefix-dir) + prev=inst_prefix + continue + ;; + + # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* + # so, if we see these flags be careful not to treat them like -L + -L[A-Z][A-Z]*:*) + case $with_gcc/$host in + no/*-*-irix* | /*-*-irix*) + func_append compile_command " $arg" + func_append finalize_command " $arg" + ;; + esac + continue + ;; + + -L*) + func_stripname "-L" '' "$arg" + if test -z "$func_stripname_result"; then + if test "$#" -gt 0; then + func_fatal_error "require no space between '-L' and '$1'" + else + func_fatal_error "need path for '-L' option" + fi + fi + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + absdir=`cd "$dir" && pwd` + test -z "$absdir" && \ + func_fatal_error "cannot determine absolute directory name of '$dir'" + dir=$absdir + ;; + esac + case "$deplibs " in + *" -L$dir "* | *" $arg "*) + # Will only happen for absolute or sysroot arguments + ;; + *) + # Preserve sysroot, but never include relative directories + case $dir in + [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;; + *) func_append deplibs " -L$dir" ;; + esac + func_append lib_search_path " $dir" + ;; + esac + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$dir:"*) ;; + ::) dllsearchpath=$dir;; + *) func_append dllsearchpath ":$dir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + continue + ;; + + -l*) + if test X-lc = "X$arg" || test X-lm = "X$arg"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*) + # These systems don't actually have a C or math library (as such) + continue + ;; + *-*-os2*) + # These systems don't actually have a C library (as such) + test X-lc = "X$arg" && continue + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc due to us having libc/libc_r. + test X-lc = "X$arg" && continue + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C and math libraries are in the System framework + func_append deplibs " System.ltframework" + continue + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + test X-lc = "X$arg" && continue + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + test X-lc = "X$arg" && continue + ;; + esac + elif test X-lc_r = "X$arg"; then + case $host in + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc_r directly, use -pthread flag. + continue + ;; + esac + fi + func_append deplibs " $arg" + continue + ;; + + -mllvm) + prev=mllvm + continue + ;; + + -module) + module=yes + continue + ;; + + # Tru64 UNIX uses -model [arg] to determine the layout of C++ + # classes, name mangling, and exception handling. + # Darwin uses the -arch flag to determine output architecture. + -model|-arch|-isysroot|--sysroot) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + prev=xcompiler + continue + ;; + + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case "$new_inherited_linker_flags " in + *" $arg "*) ;; + * ) func_append new_inherited_linker_flags " $arg" ;; + esac + continue + ;; + + -multi_module) + single_module=$wl-multi_module + continue + ;; + + -no-fast-install) + fast_install=no + continue + ;; + + -no-install) + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*) + # The PATH hackery in wrapper scripts is required on Windows + # and Darwin in order for the loader to find any dlls it needs. + func_warning "'-no-install' is ignored for $host" + func_warning "assuming '-no-fast-install' instead" + fast_install=no + ;; + *) no_install=yes ;; + esac + continue + ;; + + -no-undefined) + allow_undefined=no + continue + ;; + + -objectlist) + prev=objectlist + continue + ;; + + -os2dllname) + prev=os2dllname + continue + ;; + + -o) prev=output ;; + + -precious-files-regex) + prev=precious_regex + continue + ;; + + -release) + prev=release + continue + ;; + + -rpath) + prev=rpath + continue + ;; + + -R) + prev=xrpath + continue + ;; + + -R*) + func_stripname '-R' '' "$arg" + dir=$func_stripname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + =*) + func_stripname '=' '' "$dir" + dir=$lt_sysroot$func_stripname_result + ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + continue + ;; + + -shared) + # The effects of -shared are defined in a previous loop. + continue + ;; + + -shrext) + prev=shrext + continue + ;; + + -static | -static-libtool-libs) + # The effects of -static are defined in a previous loop. + # We used to do the same as -all-static on platforms that + # didn't have a PIC flag, but the assumption that the effects + # would be equivalent was wrong. It would break on at least + # Digital Unix and AIX. + continue + ;; + + -thread-safe) + thread_safe=yes + continue + ;; + + -version-info) + prev=vinfo + continue + ;; + + -version-number) + prev=vinfo + vinfo_number=yes + continue + ;; + + -weak) + prev=weak + continue + ;; + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $func_quote_for_eval_result" + func_append compiler_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Wl,*) + func_stripname '-Wl,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $wl$func_quote_for_eval_result" + func_append compiler_flags " $wl$func_quote_for_eval_result" + func_append linker_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Xcompiler) + prev=xcompiler + continue + ;; + + -Xlinker) + prev=xlinker + continue + ;; + + -XCClinker) + prev=xcclinker + continue + ;; + + # -msg_* for osf cc + -msg_*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + # Flags to be passed through unchanged, with rationale: + # -64, -mips[0-9] enable 64-bit mode for the SGI compiler + # -r[0-9][0-9]* specify processor for the SGI compiler + # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler + # +DA*, +DD* enable 64-bit mode for the HP compiler + # -q* compiler args for the IBM compiler + # -m*, -t[45]*, -txscale* architecture-specific flags for GCC + # -F/path path to uninstalled frameworks, gcc on darwin + # -p, -pg, --coverage, -fprofile-* profiling flags for GCC + # -fstack-protector* stack protector flags for GCC + # @file GCC response files + # -tp=* Portland pgcc target processor selection + # --sysroot=* for sysroot support + # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization + # -specs=* GCC specs files + # -stdlib=* select c++ std lib with clang + # -fsanitize=* Clang/GCC memory and address sanitizer + # -fuse-ld=* Linker select flags for GCC + # -static-* direct GCC to link specific libraries statically + # -fcilkplus Cilk Plus language extension features for C/C++ + -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ + -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ + -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ + -specs=*|-fsanitize=*|-fuse-ld=*|-static-*|-fcilkplus) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + func_append compile_command " $arg" + func_append finalize_command " $arg" + func_append compiler_flags " $arg" + continue + ;; + + -Z*) + if test os2 = "`expr $host : '.*\(os2\)'`"; then + # OS/2 uses -Zxxx to specify OS/2-specific options + compiler_flags="$compiler_flags $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case $arg in + -Zlinker | -Zstack) + prev=xcompiler + ;; + esac + continue + else + # Otherwise treat like 'Some other compiler flag' below + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + fi + ;; + + # Some other compiler flag. + -* | +*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + *.$objext) + # A standard object. + func_append objs " $arg" + ;; + + *.lo) + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + test none = "$pic_object" || { + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + } + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + ;; + + *.$libext) + # An archive. + func_append deplibs " $arg" + func_append old_deplibs " $arg" + continue + ;; + + *.la) + # A libtool-controlled library. + + func_resolve_sysroot "$arg" + if test dlfiles = "$prev"; then + # This library was specified with -dlopen. + func_append dlfiles " $func_resolve_sysroot_result" + prev= + elif test dlprefiles = "$prev"; then + # The library was specified with -dlpreopen. + func_append dlprefiles " $func_resolve_sysroot_result" + prev= + else + func_append deplibs " $func_resolve_sysroot_result" + fi + continue + ;; + + # Some other compiler argument. + *) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + esac # arg + + # Now actually substitute the argument into the commands. + if test -n "$arg"; then + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + done # argument parsing loop + + test -n "$prev" && \ + func_fatal_help "the '$prevarg' option requires an argument" + + if test yes = "$export_dynamic" && test -n "$export_dynamic_flag_spec"; then + eval arg=\"$export_dynamic_flag_spec\" + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + + oldlibs= + # calculate the name of the file, without its directory + func_basename "$output" + outputname=$func_basename_result + libobjs_save=$libobjs + + if test -n "$shlibpath_var"; then + # get the directories listed in $shlibpath_var + eval shlib_search_path=\`\$ECHO \"\$$shlibpath_var\" \| \$SED \'s/:/ /g\'\` + else + shlib_search_path= + fi + eval sys_lib_search_path=\"$sys_lib_search_path_spec\" + eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" + + # Definition is injected by LT_CONFIG during libtool generation. + func_munge_path_list sys_lib_dlsearch_path "$LT_SYS_LIBRARY_PATH" + + func_dirname "$output" "/" "" + output_objdir=$func_dirname_result$objdir + func_to_tool_file "$output_objdir/" + tool_output_objdir=$func_to_tool_file_result + # Create the object directory. + func_mkdir_p "$output_objdir" + + # Determine the type of output + case $output in + "") + func_fatal_help "you must specify an output file" + ;; + *.$libext) linkmode=oldlib ;; + *.lo | *.$objext) linkmode=obj ;; + *.la) linkmode=lib ;; + *) linkmode=prog ;; # Anything else should be a program. + esac + + specialdeplibs= + + libs= + # Find all interdependent deplibs by searching for libraries + # that are linked more than once (e.g. -la -lb -la) + for deplib in $deplibs; do + if $opt_preserve_dup_deps; then + case "$libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append libs " $deplib" + done + + if test lib = "$linkmode"; then + libs="$predeps $libs $compiler_lib_search_path $postdeps" + + # Compute libraries that are listed more than once in $predeps + # $postdeps and mark them as special (i.e., whose duplicates are + # not to be eliminated). + pre_post_deps= + if $opt_duplicate_compiler_generated_deps; then + for pre_post_dep in $predeps $postdeps; do + case "$pre_post_deps " in + *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;; + esac + func_append pre_post_deps " $pre_post_dep" + done + fi + pre_post_deps= + fi + + deplibs= + newdependency_libs= + newlib_search_path= + need_relink=no # whether we're linking any uninstalled libtool libraries + notinst_deplibs= # not-installed libtool libraries + notinst_path= # paths that contain not-installed libtool libraries + + case $linkmode in + lib) + passes="conv dlpreopen link" + for file in $dlfiles $dlprefiles; do + case $file in + *.la) ;; + *) + func_fatal_help "libraries can '-dlopen' only libtool libraries: $file" + ;; + esac + done + ;; + prog) + compile_deplibs= + finalize_deplibs= + alldeplibs=false + newdlfiles= + newdlprefiles= + passes="conv scan dlopen dlpreopen link" + ;; + *) passes="conv" + ;; + esac + + for pass in $passes; do + # The preopen pass in lib mode reverses $deplibs; put it back here + # so that -L comes before libs that need it for instance... + if test lib,link = "$linkmode,$pass"; then + ## FIXME: Find the place where the list is rebuilt in the wrong + ## order, and fix it there properly + tmp_deplibs= + for deplib in $deplibs; do + tmp_deplibs="$deplib $tmp_deplibs" + done + deplibs=$tmp_deplibs + fi + + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass"; then + libs=$deplibs + deplibs= + fi + if test prog = "$linkmode"; then + case $pass in + dlopen) libs=$dlfiles ;; + dlpreopen) libs=$dlprefiles ;; + link) + libs="$deplibs %DEPLIBS%" + test "X$link_all_deplibs" != Xno && libs="$libs $dependency_libs" + ;; + esac + fi + if test lib,dlpreopen = "$linkmode,$pass"; then + # Collect and forward deplibs of preopened libtool libs + for lib in $dlprefiles; do + # Ignore non-libtool-libs + dependency_libs= + func_resolve_sysroot "$lib" + case $lib in + *.la) func_source "$func_resolve_sysroot_result" ;; + esac + + # Collect preopened libtool deplibs, except any this library + # has declared as weak libs + for deplib in $dependency_libs; do + func_basename "$deplib" + deplib_base=$func_basename_result + case " $weak_libs " in + *" $deplib_base "*) ;; + *) func_append deplibs " $deplib" ;; + esac + done + done + libs=$dlprefiles + fi + if test dlopen = "$pass"; then + # Collect dlpreopened libraries + save_deplibs=$deplibs + deplibs= + fi + + for deplib in $libs; do + lib= + found=false + case $deplib in + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append compiler_flags " $deplib" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -l*) + if test lib != "$linkmode" && test prog != "$linkmode"; then + func_warning "'-l' is ignored for archives/objects" + continue + fi + func_stripname '-l' '' "$deplib" + name=$func_stripname_result + if test lib = "$linkmode"; then + searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path" + else + searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path" + fi + for searchdir in $searchdirs; do + for search_ext in .la $std_shrext .so .a; do + # Search the libtool library + lib=$searchdir/lib$name$search_ext + if test -f "$lib"; then + if test .la = "$search_ext"; then + found=: + else + found=false + fi + break 2 + fi + done + done + if $found; then + # deplib is a libtool library + # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib, + # We need to do some special things here, and not later. + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $deplib "*) + if func_lalib_p "$lib"; then + library_names= + old_library= + func_source "$lib" + for l in $old_library $library_names; do + ll=$l + done + if test "X$ll" = "X$old_library"; then # only static version available + found=false + func_dirname "$lib" "" "." + ladir=$func_dirname_result + lib=$ladir/$old_library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + fi + ;; + *) ;; + esac + fi + else + # deplib doesn't seem to be a libtool library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + ;; # -l + *.ltframework) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -L*) + case $linkmode in + lib) + deplibs="$deplib $deplibs" + test conv = "$pass" && continue + newdependency_libs="$deplib $newdependency_libs" + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + prog) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + if test scan = "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + *) + func_warning "'-L' is ignored for archives/objects" + ;; + esac # linkmode + continue + ;; # -L + -R*) + if test link = "$pass"; then + func_stripname '-R' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # Make sure the xrpath contains only unique directories. + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + fi + deplibs="$deplib $deplibs" + continue + ;; + *.la) + func_resolve_sysroot "$deplib" + lib=$func_resolve_sysroot_result + ;; + *.$libext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + case $linkmode in + lib) + # Linking convenience modules into shared libraries is allowed, + # but linking other static libraries is non-portable. + case " $dlpreconveniencelibs " in + *" $deplib "*) ;; + *) + valid_a_lib=false + case $deplibs_check_method in + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \ + | $EGREP "$match_pattern_regex" > /dev/null; then + valid_a_lib=: + fi + ;; + pass_all) + valid_a_lib=: + ;; + esac + if $valid_a_lib; then + echo + $ECHO "*** Warning: Linking the shared library $output against the" + $ECHO "*** static library $deplib is not portable!" + deplibs="$deplib $deplibs" + else + echo + $ECHO "*** Warning: Trying to link with static lib archive $deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because the file extensions .$libext of this argument makes me believe" + echo "*** that it is just a static archive that I should not use here." + fi + ;; + esac + continue + ;; + prog) + if test link != "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + continue + ;; + esac # linkmode + ;; # *.$libext + *.lo | *.$objext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + elif test prog = "$linkmode"; then + if test dlpreopen = "$pass" || test yes != "$dlopen_support" || test no = "$build_libtool_libs"; then + # If there is no dlopen support or we're linking statically, + # we need to preload. + func_append newdlprefiles " $deplib" + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append newdlfiles " $deplib" + fi + fi + continue + ;; + %DEPLIBS%) + alldeplibs=: + continue + ;; + esac # case $deplib + + $found || test -f "$lib" \ + || func_fatal_error "cannot find the library '$lib' or unhandled argument '$deplib'" + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$lib" \ + || func_fatal_error "'$lib' is not a valid libtool archive" + + func_dirname "$lib" "" "." + ladir=$func_dirname_result + + dlname= + dlopen= + dlpreopen= + libdir= + library_names= + old_library= + inherited_linker_flags= + # If the library was installed with an old release of libtool, + # it will not redefine variables installed, or shouldnotlink + installed=yes + shouldnotlink=no + avoidtemprpath= + + + # Read the .la file + func_source "$lib" + + # Convert "-framework foo" to "foo.ltframework" + if test -n "$inherited_linker_flags"; then + tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'` + for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do + case " $new_inherited_linker_flags " in + *" $tmp_inherited_linker_flag "*) ;; + *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";; + esac + done + fi + dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass" || + { test prog != "$linkmode" && test lib != "$linkmode"; }; then + test -n "$dlopen" && func_append dlfiles " $dlopen" + test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen" + fi + + if test conv = "$pass"; then + # Only check for convenience libraries + deplibs="$lib $deplibs" + if test -z "$libdir"; then + if test -z "$old_library"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + # It is a libtool convenience library, so add in its objects. + func_append convenience " $ladir/$objdir/$old_library" + func_append old_convenience " $ladir/$objdir/$old_library" + tmp_libs= + for deplib in $dependency_libs; do + deplibs="$deplib $deplibs" + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done + elif test prog != "$linkmode" && test lib != "$linkmode"; then + func_fatal_error "'$lib' is not a convenience library" + fi + continue + fi # $pass = conv + + + # Get the name of the library we link against. + linklib= + if test -n "$old_library" && + { test yes = "$prefer_static_libs" || + test built,no = "$prefer_static_libs,$installed"; }; then + linklib=$old_library + else + for l in $old_library $library_names; do + linklib=$l + done + fi + if test -z "$linklib"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + + # This library was specified with -dlopen. + if test dlopen = "$pass"; then + test -z "$libdir" \ + && func_fatal_error "cannot -dlopen a convenience library: '$lib'" + if test -z "$dlname" || + test yes != "$dlopen_support" || + test no = "$build_libtool_libs" + then + # If there is no dlname, no dlopen support or we're linking + # statically, we need to preload. We also need to preload any + # dependent libraries so libltdl's deplib preloader doesn't + # bomb out in the load deplibs phase. + func_append dlprefiles " $lib $dependency_libs" + else + func_append newdlfiles " $lib" + fi + continue + fi # $pass = dlopen + + # We need an absolute path. + case $ladir in + [\\/]* | [A-Za-z]:[\\/]*) abs_ladir=$ladir ;; + *) + abs_ladir=`cd "$ladir" && pwd` + if test -z "$abs_ladir"; then + func_warning "cannot determine absolute directory name of '$ladir'" + func_warning "passing it literally to the linker, although it might fail" + abs_ladir=$ladir + fi + ;; + esac + func_basename "$lib" + laname=$func_basename_result + + # Find the relevant object directory and library name. + if test yes = "$installed"; then + if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then + func_warning "library '$lib' was moved." + dir=$ladir + absdir=$abs_ladir + libdir=$abs_ladir + else + dir=$lt_sysroot$libdir + absdir=$lt_sysroot$libdir + fi + test yes = "$hardcode_automatic" && avoidtemprpath=yes + else + if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then + dir=$ladir + absdir=$abs_ladir + # Remove this search path later + func_append notinst_path " $abs_ladir" + else + dir=$ladir/$objdir + absdir=$abs_ladir/$objdir + # Remove this search path later + func_append notinst_path " $abs_ladir" + fi + fi # $installed = yes + func_stripname 'lib' '.la' "$laname" + name=$func_stripname_result + + # This library was specified with -dlpreopen. + if test dlpreopen = "$pass"; then + if test -z "$libdir" && test prog = "$linkmode"; then + func_fatal_error "only libraries may -dlpreopen a convenience library: '$lib'" + fi + case $host in + # special handling for platforms with PE-DLLs. + *cygwin* | *mingw* | *cegcc* ) + # Linker will automatically link against shared library if both + # static and shared are present. Therefore, ensure we extract + # symbols from the import library if a shared library is present + # (otherwise, the dlopen module name will be incorrect). We do + # this by putting the import library name into $newdlprefiles. + # We recover the dlopen module name by 'saving' the la file + # name in a special purpose variable, and (later) extracting the + # dlname from the la file. + if test -n "$dlname"; then + func_tr_sh "$dir/$linklib" + eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname" + func_append newdlprefiles " $dir/$linklib" + else + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + fi + ;; + * ) + # Prefer using a static library (so that no silly _DYNAMIC symbols + # are required to link). + if test -n "$old_library"; then + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + # Otherwise, use the dlname, so that lt_dlopen finds it. + elif test -n "$dlname"; then + func_append newdlprefiles " $dir/$dlname" + else + func_append newdlprefiles " $dir/$linklib" + fi + ;; + esac + fi # $pass = dlpreopen + + if test -z "$libdir"; then + # Link the convenience library + if test lib = "$linkmode"; then + deplibs="$dir/$old_library $deplibs" + elif test prog,link = "$linkmode,$pass"; then + compile_deplibs="$dir/$old_library $compile_deplibs" + finalize_deplibs="$dir/$old_library $finalize_deplibs" + else + deplibs="$lib $deplibs" # used for prog,scan pass + fi + continue + fi + + + if test prog = "$linkmode" && test link != "$pass"; then + func_append newlib_search_path " $ladir" + deplibs="$lib $deplibs" + + linkalldeplibs=false + if test no != "$link_all_deplibs" || test -z "$library_names" || + test no = "$build_libtool_libs"; then + linkalldeplibs=: + fi + + tmp_libs= + for deplib in $dependency_libs; do + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + esac + # Need to link against all dependency_libs? + if $linkalldeplibs; then + deplibs="$deplib $deplibs" + else + # Need to hardcode shared library paths + # or/and link against static libraries + newdependency_libs="$deplib $newdependency_libs" + fi + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done # for deplib + continue + fi # $linkmode = prog... + + if test prog,link = "$linkmode,$pass"; then + if test -n "$library_names" && + { { test no = "$prefer_static_libs" || + test built,yes = "$prefer_static_libs,$installed"; } || + test -z "$old_library"; }; then + # We need to hardcode the library path + if test -n "$shlibpath_var" && test -z "$avoidtemprpath"; then + # Make sure the rpath contains only unique directories. + case $temp_rpath: in + *"$absdir:"*) ;; + *) func_append temp_rpath "$absdir:" ;; + esac + fi + + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi # $linkmode,$pass = prog,link... + + if $alldeplibs && + { test pass_all = "$deplibs_check_method" || + { test yes = "$build_libtool_libs" && + test -n "$library_names"; }; }; then + # We only need to search for static libraries + continue + fi + fi + + link_static=no # Whether the deplib will be linked statically + use_static_libs=$prefer_static_libs + if test built = "$use_static_libs" && test yes = "$installed"; then + use_static_libs=no + fi + if test -n "$library_names" && + { test no = "$use_static_libs" || test -z "$old_library"; }; then + case $host in + *cygwin* | *mingw* | *cegcc* | *os2*) + # No point in relinking DLLs because paths are not encoded + func_append notinst_deplibs " $lib" + need_relink=no + ;; + *) + if test no = "$installed"; then + func_append notinst_deplibs " $lib" + need_relink=yes + fi + ;; + esac + # This is a shared library + + # Warn about portability, can't link against -module's on some + # systems (darwin). Don't bleat about dlopened modules though! + dlopenmodule= + for dlpremoduletest in $dlprefiles; do + if test "X$dlpremoduletest" = "X$lib"; then + dlopenmodule=$dlpremoduletest + break + fi + done + if test -z "$dlopenmodule" && test yes = "$shouldnotlink" && test link = "$pass"; then + echo + if test prog = "$linkmode"; then + $ECHO "*** Warning: Linking the executable $output against the loadable module" + else + $ECHO "*** Warning: Linking the shared library $output against the loadable module" + fi + $ECHO "*** $linklib is not portable!" + fi + if test lib = "$linkmode" && + test yes = "$hardcode_into_libs"; then + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi + + if test -n "$old_archive_from_expsyms_cmds"; then + # figure out the soname + set dummy $library_names + shift + realname=$1 + shift + libname=`eval "\\$ECHO \"$libname_spec\""` + # use dlname if we got it. it's perfectly good, no? + if test -n "$dlname"; then + soname=$dlname + elif test -n "$soname_spec"; then + # bleh windows + case $host in + *cygwin* | mingw* | *cegcc* | *os2*) + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + esac + eval soname=\"$soname_spec\" + else + soname=$realname + fi + + # Make a new name for the extract_expsyms_cmds to use + soroot=$soname + func_basename "$soroot" + soname=$func_basename_result + func_stripname 'lib' '.dll' "$soname" + newlib=libimp-$func_stripname_result.a + + # If the library has no export list, then create one now + if test -f "$output_objdir/$soname-def"; then : + else + func_verbose "extracting exported symbol list from '$soname'" + func_execute_cmds "$extract_expsyms_cmds" 'exit $?' + fi + + # Create $newlib + if test -f "$output_objdir/$newlib"; then :; else + func_verbose "generating import library for '$soname'" + func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?' + fi + # make sure the library variables are pointing to the new library + dir=$output_objdir + linklib=$newlib + fi # test -n "$old_archive_from_expsyms_cmds" + + if test prog = "$linkmode" || test relink != "$opt_mode"; then + add_shlibpath= + add_dir= + add= + lib_linked=yes + case $hardcode_action in + immediate | unsupported) + if test no = "$hardcode_direct"; then + add=$dir/$linklib + case $host in + *-*-sco3.2v5.0.[024]*) add_dir=-L$dir ;; + *-*-sysv4*uw2*) add_dir=-L$dir ;; + *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \ + *-*-unixware7*) add_dir=-L$dir ;; + *-*-darwin* ) + # if the lib is a (non-dlopened) module then we cannot + # link against it, someone is ignoring the earlier warnings + if /usr/bin/file -L $add 2> /dev/null | + $GREP ": [^:]* bundle" >/dev/null; then + if test "X$dlopenmodule" != "X$lib"; then + $ECHO "*** Warning: lib $linklib is a module, not a shared library" + if test -z "$old_library"; then + echo + echo "*** And there doesn't seem to be a static archive available" + echo "*** The link will probably fail, sorry" + else + add=$dir/$old_library + fi + elif test -n "$old_library"; then + add=$dir/$old_library + fi + fi + esac + elif test no = "$hardcode_minus_L"; then + case $host in + *-*-sunos*) add_shlibpath=$dir ;; + esac + add_dir=-L$dir + add=-l$name + elif test no = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + relink) + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$dir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$absdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + *) lib_linked=no ;; + esac + + if test yes != "$lib_linked"; then + func_fatal_configuration "unsupported hardcode properties" + fi + + if test -n "$add_shlibpath"; then + case :$compile_shlibpath: in + *":$add_shlibpath:"*) ;; + *) func_append compile_shlibpath "$add_shlibpath:" ;; + esac + fi + if test prog = "$linkmode"; then + test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs" + test -n "$add" && compile_deplibs="$add $compile_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + if test yes != "$hardcode_direct" && + test yes != "$hardcode_minus_L" && + test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + fi + fi + fi + + if test prog = "$linkmode" || test relink = "$opt_mode"; then + add_shlibpath= + add_dir= + add= + # Finalize command for both is simple: just hardcode it. + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$libdir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$libdir + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + add=-l$name + elif test yes = "$hardcode_automatic"; then + if test -n "$inst_prefix_dir" && + test -f "$inst_prefix_dir$libdir/$linklib"; then + add=$inst_prefix_dir$libdir/$linklib + else + add=$libdir/$linklib + fi + else + # We cannot seem to hardcode it, guess we'll fake it. + add_dir=-L$libdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + fi + + if test prog = "$linkmode"; then + test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs" + test -n "$add" && finalize_deplibs="$add $finalize_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + fi + fi + elif test prog = "$linkmode"; then + # Here we assume that one of hardcode_direct or hardcode_minus_L + # is not unsupported. This is valid on all known static and + # shared platforms. + if test unsupported != "$hardcode_direct"; then + test -n "$old_library" && linklib=$old_library + compile_deplibs="$dir/$linklib $compile_deplibs" + finalize_deplibs="$dir/$linklib $finalize_deplibs" + else + compile_deplibs="-l$name -L$dir $compile_deplibs" + finalize_deplibs="-l$name -L$dir $finalize_deplibs" + fi + elif test yes = "$build_libtool_libs"; then + # Not a shared library + if test pass_all != "$deplibs_check_method"; then + # We're trying link a shared library against a static one + # but the system doesn't support it. + + # Just print a warning and add the library to dependency_libs so + # that the program can be linked against the static library. + echo + $ECHO "*** Warning: This system cannot link to static lib archive $lib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + if test yes = "$module"; then + echo "*** But as you try to build a module library, libtool will still create " + echo "*** a static module, that should work as long as the dlopening application" + echo "*** is linked with the -dlopen flag to resolve symbols at runtime." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + else + deplibs="$dir/$old_library $deplibs" + link_static=yes + fi + fi # link shared/static library? + + if test lib = "$linkmode"; then + if test -n "$dependency_libs" && + { test yes != "$hardcode_into_libs" || + test yes = "$build_old_libs" || + test yes = "$link_static"; }; then + # Extract -R from dependency_libs + temp_deplibs= + for libdir in $dependency_libs; do + case $libdir in + -R*) func_stripname '-R' '' "$libdir" + temp_xrpath=$func_stripname_result + case " $xrpath " in + *" $temp_xrpath "*) ;; + *) func_append xrpath " $temp_xrpath";; + esac;; + *) func_append temp_deplibs " $libdir";; + esac + done + dependency_libs=$temp_deplibs + fi + + func_append newlib_search_path " $absdir" + # Link against this library + test no = "$link_static" && newdependency_libs="$abs_ladir/$laname $newdependency_libs" + # ... and its dependency_libs + tmp_libs= + for deplib in $dependency_libs; do + newdependency_libs="$deplib $newdependency_libs" + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result";; + *) func_resolve_sysroot "$deplib" ;; + esac + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $func_resolve_sysroot_result "*) + func_append specialdeplibs " $func_resolve_sysroot_result" ;; + esac + fi + func_append tmp_libs " $func_resolve_sysroot_result" + done + + if test no != "$link_all_deplibs"; then + # Add the search paths of all dependency libraries + for deplib in $dependency_libs; do + path= + case $deplib in + -L*) path=$deplib ;; + *.la) + func_resolve_sysroot "$deplib" + deplib=$func_resolve_sysroot_result + func_dirname "$deplib" "" "." + dir=$func_dirname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) absdir=$dir ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + func_warning "cannot determine absolute directory name of '$dir'" + absdir=$dir + fi + ;; + esac + if $GREP "^installed=no" $deplib > /dev/null; then + case $host in + *-*-darwin*) + depdepl= + eval deplibrary_names=`$SED -n -e 's/^library_names=\(.*\)$/\1/p' $deplib` + if test -n "$deplibrary_names"; then + for tmp in $deplibrary_names; do + depdepl=$tmp + done + if test -f "$absdir/$objdir/$depdepl"; then + depdepl=$absdir/$objdir/$depdepl + darwin_install_name=`$OTOOL -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + if test -z "$darwin_install_name"; then + darwin_install_name=`$OTOOL64 -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + fi + func_append compiler_flags " $wl-dylib_file $wl$darwin_install_name:$depdepl" + func_append linker_flags " -dylib_file $darwin_install_name:$depdepl" + path= + fi + fi + ;; + *) + path=-L$absdir/$objdir + ;; + esac + else + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + test "$absdir" != "$libdir" && \ + func_warning "'$deplib' seems to be moved" + + path=-L$absdir + fi + ;; + esac + case " $deplibs " in + *" $path "*) ;; + *) deplibs="$path $deplibs" ;; + esac + done + fi # link_all_deplibs != no + fi # linkmode = lib + done # for deplib in $libs + if test link = "$pass"; then + if test prog = "$linkmode"; then + compile_deplibs="$new_inherited_linker_flags $compile_deplibs" + finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs" + else + compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + fi + fi + dependency_libs=$newdependency_libs + if test dlpreopen = "$pass"; then + # Link the dlpreopened libraries before other libraries + for deplib in $save_deplibs; do + deplibs="$deplib $deplibs" + done + fi + if test dlopen != "$pass"; then + test conv = "$pass" || { + # Make sure lib_search_path contains only unique directories. + lib_search_path= + for dir in $newlib_search_path; do + case "$lib_search_path " in + *" $dir "*) ;; + *) func_append lib_search_path " $dir" ;; + esac + done + newlib_search_path= + } + + if test prog,link = "$linkmode,$pass"; then + vars="compile_deplibs finalize_deplibs" + else + vars=deplibs + fi + for var in $vars dependency_libs; do + # Add libraries to $var in reverse order + eval tmp_libs=\"\$$var\" + new_libs= + for deplib in $tmp_libs; do + # FIXME: Pedantically, this is the right thing to do, so + # that some nasty dependency loop isn't accidentally + # broken: + #new_libs="$deplib $new_libs" + # Pragmatically, this seems to cause very few problems in + # practice: + case $deplib in + -L*) new_libs="$deplib $new_libs" ;; + -R*) ;; + *) + # And here is the reason: when a library appears more + # than once as an explicit dependence of a library, or + # is implicitly linked in more than once by the + # compiler, it is considered special, and multiple + # occurrences thereof are not removed. Compare this + # with having the same library being listed as a + # dependency of multiple other libraries: in this case, + # we know (pedantically, we assume) the library does not + # need to be listed more than once, so we keep only the + # last copy. This is not always right, but it is rare + # enough that we require users that really mean to play + # such unportable linking tricks to link the library + # using -Wl,-lname, so that libtool does not consider it + # for duplicate removal. + case " $specialdeplibs " in + *" $deplib "*) new_libs="$deplib $new_libs" ;; + *) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$deplib $new_libs" ;; + esac + ;; + esac + ;; + esac + done + tmp_libs= + for deplib in $new_libs; do + case $deplib in + -L*) + case " $tmp_libs " in + *" $deplib "*) ;; + *) func_append tmp_libs " $deplib" ;; + esac + ;; + *) func_append tmp_libs " $deplib" ;; + esac + done + eval $var=\"$tmp_libs\" + done # for var + fi + + # Add Sun CC postdeps if required: + test CXX = "$tagname" && { + case $host_os in + linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C++ 5.9 + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + + solaris*) + func_cc_basename "$CC" + case $func_cc_basename_result in + CC* | sunCC*) + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + esac + } + + # Last step: remove runtime libs from dependency_libs + # (they stay in deplibs) + tmp_libs= + for i in $dependency_libs; do + case " $predeps $postdeps $compiler_lib_search_path " in + *" $i "*) + i= + ;; + esac + if test -n "$i"; then + func_append tmp_libs " $i" + fi + done + dependency_libs=$tmp_libs + done # for pass + if test prog = "$linkmode"; then + dlfiles=$newdlfiles + fi + if test prog = "$linkmode" || test lib = "$linkmode"; then + dlprefiles=$newdlprefiles + fi + + case $linkmode in + oldlib) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for archives" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for archives" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for archives" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for archives" + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for archives" + + test -n "$release" && \ + func_warning "'-release' is ignored for archives" + + test -n "$export_symbols$export_symbols_regex" && \ + func_warning "'-export-symbols' is ignored for archives" + + # Now set the variables for building old libraries. + build_libtool_libs=no + oldlibs=$output + func_append objs "$old_deplibs" + ;; + + lib) + # Make sure we only generate libraries of the form 'libNAME.la'. + case $outputname in + lib*) + func_stripname 'lib' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + ;; + *) + test no = "$module" \ + && func_fatal_help "libtool library '$output' must begin with 'lib'" + + if test no != "$need_lib_prefix"; then + # Add the "lib" prefix for modules if required + func_stripname '' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + else + func_stripname '' '.la' "$outputname" + libname=$func_stripname_result + fi + ;; + esac + + if test -n "$objs"; then + if test pass_all != "$deplibs_check_method"; then + func_fatal_error "cannot build libtool library '$output' from non-libtool objects on this host:$objs" + else + echo + $ECHO "*** Warning: Linking the shared library $output against the non-libtool" + $ECHO "*** objects $objs is not portable!" + func_append libobjs " $objs" + fi + fi + + test no = "$dlself" \ + || func_warning "'-dlopen self' is ignored for libtool libraries" + + set dummy $rpath + shift + test 1 -lt "$#" \ + && func_warning "ignoring multiple '-rpath's for a libtool library" + + install_libdir=$1 + + oldlibs= + if test -z "$rpath"; then + if test yes = "$build_libtool_libs"; then + # Building a libtool convenience library. + # Some compilers have problems with a '.al' extension so + # convenience libraries should have the same extension an + # archive normally would. + oldlibs="$output_objdir/$libname.$libext $oldlibs" + build_libtool_libs=convenience + build_old_libs=yes + fi + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for convenience libraries" + + test -n "$release" && \ + func_warning "'-release' is ignored for convenience libraries" + else + + # Parse the version information argument. + save_ifs=$IFS; IFS=: + set dummy $vinfo 0 0 0 + shift + IFS=$save_ifs + + test -n "$7" && \ + func_fatal_help "too many parameters to '-version-info'" + + # convert absolute version numbers to libtool ages + # this retains compatibility with .la files and attempts + # to make the code below a bit more comprehensible + + case $vinfo_number in + yes) + number_major=$1 + number_minor=$2 + number_revision=$3 + # + # There are really only two kinds -- those that + # use the current revision as the major version + # and those that subtract age and use age as + # a minor version. But, then there is irix + # that has an extra 1 added just for fun + # + case $version_type in + # correct linux to gnu/linux during the next big refactor + darwin|freebsd-elf|linux|osf|windows|none) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_revision + ;; + freebsd-aout|qnx|sunos) + current=$number_major + revision=$number_minor + age=0 + ;; + irix|nonstopux) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_minor + lt_irix_increment=no + ;; + *) + func_fatal_configuration "$modename: unknown library version type '$version_type'" + ;; + esac + ;; + no) + current=$1 + revision=$2 + age=$3 + ;; + esac + + # Check that each of the things are valid numbers. + case $current in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "CURRENT '$current' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $revision in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "REVISION '$revision' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $age in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "AGE '$age' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + if test "$age" -gt "$current"; then + func_error "AGE '$age' is greater than the current interface number '$current'" + func_fatal_error "'$vinfo' is not valid version information" + fi + + # Calculate the version variables. + major= + versuffix= + verstring= + case $version_type in + none) ;; + + darwin) + # Like Linux, but with the current version available in + # verstring for coding it into the library header + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + # Darwin ld doesn't like 0 for these options... + func_arith $current + 1 + minor_current=$func_arith_result + xlcverstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + # On Darwin other compilers + case $CC in + nagfor*) + verstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + ;; + *) + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + ;; + esac + ;; + + freebsd-aout) + major=.$current + versuffix=.$current.$revision + ;; + + freebsd-elf) + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + irix | nonstopux) + if test no = "$lt_irix_increment"; then + func_arith $current - $age + else + func_arith $current - $age + 1 + fi + major=$func_arith_result + + case $version_type in + nonstopux) verstring_prefix=nonstopux ;; + *) verstring_prefix=sgi ;; + esac + verstring=$verstring_prefix$major.$revision + + # Add in all the interfaces that we are compatible with. + loop=$revision + while test 0 -ne "$loop"; do + func_arith $revision - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring_prefix$major.$iface:$verstring + done + + # Before this point, $major must not contain '.'. + major=.$major + versuffix=$major.$revision + ;; + + linux) # correct to gnu/linux during the next big refactor + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + osf) + func_arith $current - $age + major=.$func_arith_result + versuffix=.$current.$age.$revision + verstring=$current.$age.$revision + + # Add in all the interfaces that we are compatible with. + loop=$age + while test 0 -ne "$loop"; do + func_arith $current - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring:$iface.0 + done + + # Make executables depend on our current version. + func_append verstring ":$current.0" + ;; + + qnx) + major=.$current + versuffix=.$current + ;; + + sco) + major=.$current + versuffix=.$current + ;; + + sunos) + major=.$current + versuffix=.$current.$revision + ;; + + windows) + # Use '-' rather than '.', since we only want one + # extension on DOS 8.3 file systems. + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + + *) + func_fatal_configuration "unknown library version type '$version_type'" + ;; + esac + + # Clear the version info if we defaulted, and they specified a release. + if test -z "$vinfo" && test -n "$release"; then + major= + case $version_type in + darwin) + # we can't check for "0.0" in archive_cmds due to quoting + # problems, so we reset it completely + verstring= + ;; + *) + verstring=0.0 + ;; + esac + if test no = "$need_version"; then + versuffix= + else + versuffix=.0.0 + fi + fi + + # Remove version info from name if versioning should be avoided + if test yes,no = "$avoid_version,$need_version"; then + major= + versuffix= + verstring= + fi + + # Check to see if the archive will have undefined symbols. + if test yes = "$allow_undefined"; then + if test unsupported = "$allow_undefined_flag"; then + if test yes = "$build_old_libs"; then + func_warning "undefined symbols not allowed in $host shared libraries; building static only" + build_libtool_libs=no + else + func_fatal_error "can't build $host shared library unless -no-undefined is specified" + fi + fi + else + # Don't allow undefined symbols. + allow_undefined_flag=$no_undefined_flag + fi + + fi + + func_generate_dlsyms "$libname" "$libname" : + func_append libobjs " $symfileobj" + test " " = "$libobjs" && libobjs= + + if test relink != "$opt_mode"; then + # Remove our outputs, but don't remove object files since they + # may have been created when compiling PIC objects. + removelist= + tempremovelist=`$ECHO "$output_objdir/*"` + for p in $tempremovelist; do + case $p in + *.$objext | *.gcno) + ;; + $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/$libname$release.*) + if test -n "$precious_files_regex"; then + if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1 + then + continue + fi + fi + func_append removelist " $p" + ;; + *) ;; + esac + done + test -n "$removelist" && \ + func_show_eval "${RM}r \$removelist" + fi + + # Now set the variables for building old libraries. + if test yes = "$build_old_libs" && test convenience != "$build_libtool_libs"; then + func_append oldlibs " $output_objdir/$libname.$libext" + + # Transform .lo files to .o files. + oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; $lo2o" | $NL2SP` + fi + + # Eliminate all temporary directories. + #for path in $notinst_path; do + # lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"` + # deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"` + # dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"` + #done + + if test -n "$xrpath"; then + # If the user specified any rpath flags, then add them. + temp_xrpath= + for libdir in $xrpath; do + func_replace_sysroot "$libdir" + func_append temp_xrpath " -R$func_replace_sysroot_result" + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + if test yes != "$hardcode_into_libs" || test yes = "$build_old_libs"; then + dependency_libs="$temp_xrpath $dependency_libs" + fi + fi + + # Make sure dlfiles contains only unique files that won't be dlpreopened + old_dlfiles=$dlfiles + dlfiles= + for lib in $old_dlfiles; do + case " $dlprefiles $dlfiles " in + *" $lib "*) ;; + *) func_append dlfiles " $lib" ;; + esac + done + + # Make sure dlprefiles contains only unique files + old_dlprefiles=$dlprefiles + dlprefiles= + for lib in $old_dlprefiles; do + case "$dlprefiles " in + *" $lib "*) ;; + *) func_append dlprefiles " $lib" ;; + esac + done + + if test yes = "$build_libtool_libs"; then + if test -n "$rpath"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*) + # these systems don't actually have a c library (as such)! + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C library is in the System framework + func_append deplibs " System.ltframework" + ;; + *-*-netbsd*) + # Don't link with libc until the a.out ld.so is fixed. + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc due to us having libc/libc_r. + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + ;; + *) + # Add libc to deplibs on all other systems if necessary. + if test yes = "$build_libtool_need_lc"; then + func_append deplibs " -lc" + fi + ;; + esac + fi + + # Transform deplibs into only deplibs that can be linked in shared. + name_save=$name + libname_save=$libname + release_save=$release + versuffix_save=$versuffix + major_save=$major + # I'm not sure if I'm treating the release correctly. I think + # release should show up in the -l (ie -lgmp5) so we don't want to + # add it in twice. Is that correct? + release= + versuffix= + major= + newdeplibs= + droppeddeps=no + case $deplibs_check_method in + pass_all) + # Don't check for shared/static. Everything works. + # This might be a little naive. We might want to check + # whether the library exists or not. But this is on + # osf3 & osf4 and I'm not really sure... Just + # implementing what was already the behavior. + newdeplibs=$deplibs + ;; + test_compile) + # This code stresses the "libraries are programs" paradigm to its + # limits. Maybe even breaks it. We compile a program, linking it + # against the deplibs as a proxy for the library. Then we can check + # whether they linked in statically or dynamically with ldd. + $opt_dry_run || $RM conftest.c + cat > conftest.c </dev/null` + $nocaseglob + else + potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null` + fi + for potent_lib in $potential_libs; do + # Follow soft links. + if ls -lLd "$potent_lib" 2>/dev/null | + $GREP " -> " >/dev/null; then + continue + fi + # The statement above tries to avoid entering an + # endless loop below, in case of cyclic links. + # We might still enter an endless loop, since a link + # loop can be closed while we follow links, + # but so what? + potlib=$potent_lib + while test -h "$potlib" 2>/dev/null; do + potliblink=`ls -ld $potlib | $SED 's/.* -> //'` + case $potliblink in + [\\/]* | [A-Za-z]:[\\/]*) potlib=$potliblink;; + *) potlib=`$ECHO "$potlib" | $SED 's|[^/]*$||'`"$potliblink";; + esac + done + if eval $file_magic_cmd \"\$potlib\" 2>/dev/null | + $SED -e 10q | + $EGREP "$file_magic_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for file magic test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a file magic. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + for a_deplib in $deplibs; do + case $a_deplib in + -l*) + func_stripname -l '' "$a_deplib" + name=$func_stripname_result + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $a_deplib "*) + func_append newdeplibs " $a_deplib" + a_deplib= + ;; + esac + fi + if test -n "$a_deplib"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do + potential_libs=`ls $i/$libname[.-]* 2>/dev/null` + for potent_lib in $potential_libs; do + potlib=$potent_lib # see symlink-check above in file_magic test + if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \ + $EGREP "$match_pattern_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a regex pattern. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + none | unknown | *) + newdeplibs= + tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'` + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + for i in $predeps $postdeps; do + # can't use Xsed below, because $i might contain '/' + tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s|$i||"` + done + fi + case $tmp_deplibs in + *[!\ \ ]*) + echo + if test none = "$deplibs_check_method"; then + echo "*** Warning: inter-library dependencies are not supported in this platform." + else + echo "*** Warning: inter-library dependencies are not known to be supported." + fi + echo "*** All declared inter-library dependencies are being dropped." + droppeddeps=yes + ;; + esac + ;; + esac + versuffix=$versuffix_save + major=$major_save + release=$release_save + libname=$libname_save + name=$name_save + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library with the System framework + newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + if test yes = "$droppeddeps"; then + if test yes = "$module"; then + echo + echo "*** Warning: libtool could not satisfy all declared inter-library" + $ECHO "*** dependencies of module $libname. Therefore, libtool will create" + echo "*** a static module, that should work as long as the dlopening" + echo "*** application is linked with the -dlopen flag." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + else + echo "*** The inter-library dependencies that have been dropped here will be" + echo "*** automatically added whenever a program is linked with this library" + echo "*** or is declared to -dlopen it." + + if test no = "$allow_undefined"; then + echo + echo "*** Since this library must not contain undefined symbols," + echo "*** because either the platform does not support them or" + echo "*** it was explicitly requested with -no-undefined," + echo "*** libtool will only create a static version of it." + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + fi + fi + # Done checking deplibs! + deplibs=$newdeplibs + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + case $host in + *-*-darwin*) + newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + deplibs=$new_libs + + # All the library-specific variables (install_libdir is set above). + library_names= + old_library= + dlname= + + # Test again, we may have decided not to build it any more + if test yes = "$build_libtool_libs"; then + # Remove $wl instances when linking with ld. + # FIXME: should test the right _cmds variable. + case $archive_cmds in + *\$LD\ *) wl= ;; + esac + if test yes = "$hardcode_into_libs"; then + # Hardcode the library paths + hardcode_libdirs= + dep_rpath= + rpath=$finalize_rpath + test relink = "$opt_mode" || rpath=$compile_rpath$rpath + for libdir in $rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + func_replace_sysroot "$libdir" + libdir=$func_replace_sysroot_result + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append dep_rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval "dep_rpath=\"$hardcode_libdir_flag_spec\"" + fi + if test -n "$runpath_var" && test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var" + fi + test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs" + fi + + shlibpath=$finalize_shlibpath + test relink = "$opt_mode" || shlibpath=$compile_shlibpath$shlibpath + if test -n "$shlibpath"; then + eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var" + fi + + # Get the real and link names of the library. + eval shared_ext=\"$shrext_cmds\" + eval library_names=\"$library_names_spec\" + set dummy $library_names + shift + realname=$1 + shift + + if test -n "$soname_spec"; then + eval soname=\"$soname_spec\" + else + soname=$realname + fi + if test -z "$dlname"; then + dlname=$soname + fi + + lib=$output_objdir/$realname + linknames= + for link + do + func_append linknames " $link" + done + + # Use standard objects if they are pic + test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP` + test "X$libobjs" = "X " && libobjs= + + delfiles= + if test -n "$export_symbols" && test -n "$include_expsyms"; then + $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp" + export_symbols=$output_objdir/$libname.uexp + func_append delfiles " $export_symbols" + fi + + orig_export_symbols= + case $host_os in + cygwin* | mingw* | cegcc*) + if test -n "$export_symbols" && test -z "$export_symbols_regex"; then + # exporting using user supplied symfile + func_dll_def_p "$export_symbols" || { + # and it's NOT already a .def file. Must figure out + # which of the given symbols are data symbols and tag + # them as such. So, trigger use of export_symbols_cmds. + # export_symbols gets reassigned inside the "prepare + # the list of exported symbols" if statement, so the + # include_expsyms logic still works. + orig_export_symbols=$export_symbols + export_symbols= + always_export_symbols=yes + } + fi + ;; + esac + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + if test yes = "$always_export_symbols" || test -n "$export_symbols_regex"; then + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + cmds=$export_symbols_cmds + save_ifs=$IFS; IFS='~' + for cmd1 in $cmds; do + IFS=$save_ifs + # Take the normal branch if the nm_file_list_spec branch + # doesn't work or if tool conversion is not needed. + case $nm_file_list_spec~$to_tool_file_cmd in + *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*) + try_normal_branch=yes + eval cmd=\"$cmd1\" + func_len " $cmd" + len=$func_len_result + ;; + *) + try_normal_branch=no + ;; + esac + if test yes = "$try_normal_branch" \ + && { test "$len" -lt "$max_cmd_len" \ + || test "$max_cmd_len" -le -1; } + then + func_show_eval "$cmd" 'exit $?' + skipped_export=false + elif test -n "$nm_file_list_spec"; then + func_basename "$output" + output_la=$func_basename_result + save_libobjs=$libobjs + save_output=$output + output=$output_objdir/$output_la.nm + func_to_tool_file "$output" + libobjs=$nm_file_list_spec$func_to_tool_file_result + func_append delfiles " $output" + func_verbose "creating $NM input file list: $output" + for obj in $save_libobjs; do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > "$output" + eval cmd=\"$cmd1\" + func_show_eval "$cmd" 'exit $?' + output=$save_output + libobjs=$save_libobjs + skipped_export=false + else + # The command line is too long to execute in one step. + func_verbose "using reloadable object file for export list..." + skipped_export=: + # Break out early, otherwise skipped_export may be + # set to false by a later but shorter cmd. + break + fi + done + IFS=$save_ifs + if test -n "$export_symbols_regex" && test : != "$skipped_export"; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + fi + + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test : != "$skipped_export" && test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + + tmp_deplibs= + for test_deplib in $deplibs; do + case " $convenience " in + *" $test_deplib "*) ;; + *) + func_append tmp_deplibs " $test_deplib" + ;; + esac + done + deplibs=$tmp_deplibs + + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec" && + test yes = "$compiler_needs_object" && + test -z "$libobjs"; then + # extract the archives, so we have objects to list. + # TODO: could optimize this to just extract one archive. + whole_archive_flag_spec= + fi + if test -n "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + else + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + fi + + if test yes = "$thread_safe" && test -n "$thread_safe_flag_spec"; then + eval flag=\"$thread_safe_flag_spec\" + func_append linker_flags " $flag" + fi + + # Make a backup of the uninstalled library when relinking + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $? + fi + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + eval test_cmds=\"$module_expsym_cmds\" + cmds=$module_expsym_cmds + else + eval test_cmds=\"$module_cmds\" + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + eval test_cmds=\"$archive_expsym_cmds\" + cmds=$archive_expsym_cmds + else + eval test_cmds=\"$archive_cmds\" + cmds=$archive_cmds + fi + fi + + if test : != "$skipped_export" && + func_len " $test_cmds" && + len=$func_len_result && + test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + : + else + # The command line is too long to link in one step, link piecewise + # or, if using GNU ld and skipped_export is not :, use a linker + # script. + + # Save the value of $output and $libobjs because we want to + # use them later. If we have whole_archive_flag_spec, we + # want to use save_libobjs as it was before + # whole_archive_flag_spec was expanded, because we can't + # assume the linker understands whole_archive_flag_spec. + # This may have to be revisited, in case too many + # convenience libraries get linked in and end up exceeding + # the spec. + if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + fi + save_output=$output + func_basename "$output" + output_la=$func_basename_result + + # Clear the reloadable object creation command queue and + # initialize k to one. + test_cmds= + concat_cmds= + objlist= + last_robj= + k=1 + + if test -n "$save_libobjs" && test : != "$skipped_export" && test yes = "$with_gnu_ld"; then + output=$output_objdir/$output_la.lnkscript + func_verbose "creating GNU ld script: $output" + echo 'INPUT (' > $output + for obj in $save_libobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + echo ')' >> $output + func_append delfiles " $output" + func_to_tool_file "$output" + output=$func_to_tool_file_result + elif test -n "$save_libobjs" && test : != "$skipped_export" && test -n "$file_list_spec"; then + output=$output_objdir/$output_la.lnk + func_verbose "creating linker input file list: $output" + : > $output + set x $save_libobjs + shift + firstobj= + if test yes = "$compiler_needs_object"; then + firstobj="$1 " + shift + fi + for obj + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + func_append delfiles " $output" + func_to_tool_file "$output" + output=$firstobj\"$file_list_spec$func_to_tool_file_result\" + else + if test -n "$save_libobjs"; then + func_verbose "creating reloadable object files..." + output=$output_objdir/$output_la-$k.$objext + eval test_cmds=\"$reload_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + + # Loop over the list of objects to be linked. + for obj in $save_libobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + if test -z "$objlist" || + test "$len" -lt "$max_cmd_len"; then + func_append objlist " $obj" + else + # The command $test_cmds is almost too long, add a + # command to the queue. + if test 1 -eq "$k"; then + # The first file doesn't have a previous command to add. + reload_objs=$objlist + eval concat_cmds=\"$reload_cmds\" + else + # All subsequent reloadable object files will link in + # the last one created. + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\" + fi + last_robj=$output_objdir/$output_la-$k.$objext + func_arith $k + 1 + k=$func_arith_result + output=$output_objdir/$output_la-$k.$objext + objlist=" $obj" + func_len " $last_robj" + func_arith $len0 + $func_len_result + len=$func_arith_result + fi + done + # Handle the remaining objects by creating one last + # reloadable object file. All subsequent reloadable object + # files will link in the last one created. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds$reload_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + func_append delfiles " $output" + + else + output= + fi + + ${skipped_export-false} && { + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + libobjs=$output + # Append the command to create the export file. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + } + + test -n "$save_libobjs" && + func_verbose "creating a temporary reloadable object file: $output" + + # Loop through the commands generated above and execute them. + save_ifs=$IFS; IFS='~' + for cmd in $concat_cmds; do + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + if test -n "$export_symbols_regex" && ${skipped_export-false}; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + + ${skipped_export-false} && { + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + } + + libobjs=$output + # Restore the value of output. + output=$save_output + + if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + fi + # Expand the library linking commands again to reset the + # value of $libobjs for piecewise linking. + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + cmds=$module_expsym_cmds + else + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + cmds=$archive_expsym_cmds + else + cmds=$archive_cmds + fi + fi + fi + + if test -n "$delfiles"; then + # Append the command to remove temporary files to $cmds. + eval cmds=\"\$cmds~\$RM $delfiles\" + fi + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + + save_ifs=$IFS; IFS='~' + for cmd in $cmds; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $? + + if test -n "$convenience"; then + if test -z "$whole_archive_flag_spec"; then + func_show_eval '${RM}r "$gentop"' + fi + fi + + exit $EXIT_SUCCESS + fi + + # Create links to the real library. + for linkname in $linknames; do + if test "$realname" != "$linkname"; then + func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?' + fi + done + + # If -module or -export-dynamic was specified, set the dlname. + if test yes = "$module" || test yes = "$export_dynamic"; then + # On all known operating systems, these are identical. + dlname=$soname + fi + fi + ;; + + obj) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for objects" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for objects" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for objects" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for objects" + + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for objects" + + test -n "$release" && \ + func_warning "'-release' is ignored for objects" + + case $output in + *.lo) + test -n "$objs$old_deplibs" && \ + func_fatal_error "cannot build library object '$output' from non-libtool objects" + + libobj=$output + func_lo2o "$libobj" + obj=$func_lo2o_result + ;; + *) + libobj= + obj=$output + ;; + esac + + # Delete the old objects. + $opt_dry_run || $RM $obj $libobj + + # Objects from convenience libraries. This assumes + # single-version convenience libraries. Whenever we create + # different ones for PIC/non-PIC, this we'll have to duplicate + # the extraction. + reload_conv_objs= + gentop= + # if reload_cmds runs $LD directly, get rid of -Wl from + # whole_archive_flag_spec and hope we can get by with turning comma + # into space. + case $reload_cmds in + *\$LD[\ \$]*) wl= ;; + esac + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec"; then + eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\" + test -n "$wl" || tmp_whole_archive_flags=`$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'` + reload_conv_objs=$reload_objs\ $tmp_whole_archive_flags + else + gentop=$output_objdir/${obj}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + reload_conv_objs="$reload_objs $func_extract_archives_result" + fi + fi + + # If we're not building shared, we need to use non_pic_objs + test yes = "$build_libtool_libs" || libobjs=$non_pic_objects + + # Create the old-style object. + reload_objs=$objs$old_deplibs' '`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; /\.lib$/d; $lo2o" | $NL2SP`' '$reload_conv_objs + + output=$obj + func_execute_cmds "$reload_cmds" 'exit $?' + + # Exit if we aren't doing a library object file. + if test -z "$libobj"; then + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + fi + + test yes = "$build_libtool_libs" || { + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + # Create an invalid libtool object if no PIC, so that we don't + # accidentally link it into a program. + # $show "echo timestamp > $libobj" + # $opt_dry_run || eval "echo timestamp > $libobj" || exit $? + exit $EXIT_SUCCESS + } + + if test -n "$pic_flag" || test default != "$pic_mode"; then + # Only do commands if we really have different PIC objects. + reload_objs="$libobjs $reload_conv_objs" + output=$libobj + func_execute_cmds "$reload_cmds" 'exit $?' + fi + + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + ;; + + prog) + case $host in + *cygwin*) func_stripname '' '.exe' "$output" + output=$func_stripname_result.exe;; + esac + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for programs" + + test -n "$release" && \ + func_warning "'-release' is ignored for programs" + + $preload \ + && test unknown,unknown,unknown = "$dlopen_support,$dlopen_self,$dlopen_self_static" \ + && func_warning "'LT_INIT([dlopen])' not used. Assuming no dlopen support." + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library is the System framework + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + case $host in + *-*-darwin*) + # Don't allow lazy linking, it breaks C++ global constructors + # But is supposedly fixed on 10.4 or later (yay!). + if test CXX = "$tagname"; then + case ${MACOSX_DEPLOYMENT_TARGET-10.0} in + 10.[0123]) + func_append compile_command " $wl-bind_at_load" + func_append finalize_command " $wl-bind_at_load" + ;; + esac + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $compile_deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $compile_deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + compile_deplibs=$new_libs + + + func_append compile_command " $compile_deplibs" + func_append finalize_command " $finalize_deplibs" + + if test -n "$rpath$xrpath"; then + # If the user specified any rpath flags, then add them. + for libdir in $rpath $xrpath; do + # This is the magic to use -rpath. + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + fi + + # Now hardcode the library paths + rpath= + hardcode_libdirs= + for libdir in $compile_rpath $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$libdir" | $SED -e 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$libdir:"*) ;; + ::) dllsearchpath=$libdir;; + *) func_append dllsearchpath ":$libdir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + compile_rpath=$rpath + + rpath= + hardcode_libdirs= + for libdir in $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$finalize_perm_rpath " in + *" $libdir "*) ;; + *) func_append finalize_perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + finalize_rpath=$rpath + + if test -n "$libobjs" && test yes = "$build_old_libs"; then + # Transform all the library objects into standard objects. + compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + fi + + func_generate_dlsyms "$outputname" "@PROGRAM@" false + + # template prelinking step + if test -n "$prelink_cmds"; then + func_execute_cmds "$prelink_cmds" 'exit $?' + fi + + wrappers_required=: + case $host in + *cegcc* | *mingw32ce*) + # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway. + wrappers_required=false + ;; + *cygwin* | *mingw* ) + test yes = "$build_libtool_libs" || wrappers_required=false + ;; + *) + if test no = "$need_relink" || test yes != "$build_libtool_libs"; then + wrappers_required=false + fi + ;; + esac + $wrappers_required || { + # Replace the output file specification. + compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + link_command=$compile_command$compile_rpath + + # We have no uninstalled library dependencies, so finalize right now. + exit_status=0 + func_show_eval "$link_command" 'exit_status=$?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Delete the generated files. + if test -f "$output_objdir/${outputname}S.$objext"; then + func_show_eval '$RM "$output_objdir/${outputname}S.$objext"' + fi + + exit $exit_status + } + + if test -n "$compile_shlibpath$finalize_shlibpath"; then + compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" + fi + if test -n "$finalize_shlibpath"; then + finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" + fi + + compile_var= + finalize_var= + if test -n "$runpath_var"; then + if test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + compile_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + if test -n "$finalize_perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $finalize_perm_rpath; do + func_append rpath "$dir:" + done + finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + fi + + if test yes = "$no_install"; then + # We don't need to create a wrapper script. + link_command=$compile_var$compile_command$compile_rpath + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + # Delete the old output file. + $opt_dry_run || $RM $output + # Link the executable and exit + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + exit $EXIT_SUCCESS + fi + + case $hardcode_action,$fast_install in + relink,*) + # Fast installation is not supported + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + + func_warning "this platform does not like uninstalled shared libraries" + func_warning "'$output' will be relinked during installation" + ;; + *,yes) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'` + ;; + *,no) + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + ;; + *,needless) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command= + ;; + esac + + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` + + # Delete the old output files. + $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname + + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output_objdir/$outputname" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Now create the wrapper script. + func_verbose "creating $output" + + # Quote the relink command for shipping. + if test -n "$relink_command"; then + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + relink_command="(cd `pwd`; $relink_command)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + fi + + # Only actually do things if not in dry run mode. + $opt_dry_run || { + # win32 will think the script is a binary if it has + # a .exe suffix, so we strip it off here. + case $output in + *.exe) func_stripname '' '.exe' "$output" + output=$func_stripname_result ;; + esac + # test for cygwin because mv fails w/o .exe extensions + case $host in + *cygwin*) + exeext=.exe + func_stripname '' '.exe' "$outputname" + outputname=$func_stripname_result ;; + *) exeext= ;; + esac + case $host in + *cygwin* | *mingw* ) + func_dirname_and_basename "$output" "" "." + output_name=$func_basename_result + output_path=$func_dirname_result + cwrappersource=$output_path/$objdir/lt-$output_name.c + cwrapper=$output_path/$output_name.exe + $RM $cwrappersource $cwrapper + trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15 + + func_emit_cwrapperexe_src > $cwrappersource + + # The wrapper executable is built using the $host compiler, + # because it contains $host paths and files. If cross- + # compiling, it, like the target executable, must be + # executed on the $host or under an emulation environment. + $opt_dry_run || { + $LTCC $LTCFLAGS -o $cwrapper $cwrappersource + $STRIP $cwrapper + } + + # Now, create the wrapper script for func_source use: + func_ltwrapper_scriptname $cwrapper + $RM $func_ltwrapper_scriptname_result + trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15 + $opt_dry_run || { + # note: this script will not be executed, so do not chmod. + if test "x$build" = "x$host"; then + $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result + else + func_emit_wrapper no > $func_ltwrapper_scriptname_result + fi + } + ;; + * ) + $RM $output + trap "$RM $output; exit $EXIT_FAILURE" 1 2 15 + + func_emit_wrapper no > $output + chmod +x $output + ;; + esac + } + exit $EXIT_SUCCESS + ;; + esac + + # See if we need to build an old-fashioned archive. + for oldlib in $oldlibs; do + + case $build_libtool_libs in + convenience) + oldobjs="$libobjs_save $symfileobj" + addlibs=$convenience + build_libtool_libs=no + ;; + module) + oldobjs=$libobjs_save + addlibs=$old_convenience + build_libtool_libs=no + ;; + *) + oldobjs="$old_deplibs $non_pic_objects" + $preload && test -f "$symfileobj" \ + && func_append oldobjs " $symfileobj" + addlibs=$old_convenience + ;; + esac + + if test -n "$addlibs"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $addlibs + func_append oldobjs " $func_extract_archives_result" + fi + + # Do each command in the archive commands. + if test -n "$old_archive_from_new_cmds" && test yes = "$build_libtool_libs"; then + cmds=$old_archive_from_new_cmds + else + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append oldobjs " $func_extract_archives_result" + fi + + # POSIX demands no paths to be encoded in archives. We have + # to avoid creating archives with duplicate basenames if we + # might have to extract them afterwards, e.g., when creating a + # static archive out of a convenience library, or when linking + # the entirety of a libtool archive into another (currently + # not supported by libtool). + if (for obj in $oldobjs + do + func_basename "$obj" + $ECHO "$func_basename_result" + done | sort | sort -uc >/dev/null 2>&1); then + : + else + echo "copying selected object files to avoid basename conflicts..." + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + func_mkdir_p "$gentop" + save_oldobjs=$oldobjs + oldobjs= + counter=1 + for obj in $save_oldobjs + do + func_basename "$obj" + objbase=$func_basename_result + case " $oldobjs " in + " ") oldobjs=$obj ;; + *[\ /]"$objbase "*) + while :; do + # Make sure we don't pick an alternate name that also + # overlaps. + newobj=lt$counter-$objbase + func_arith $counter + 1 + counter=$func_arith_result + case " $oldobjs " in + *[\ /]"$newobj "*) ;; + *) if test ! -f "$gentop/$newobj"; then break; fi ;; + esac + done + func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj" + func_append oldobjs " $gentop/$newobj" + ;; + *) func_append oldobjs " $obj" ;; + esac + done + fi + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + eval cmds=\"$old_archive_cmds\" + + func_len " $cmds" + len=$func_len_result + if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + cmds=$old_archive_cmds + elif test -n "$archiver_list_spec"; then + func_verbose "using command file archive linking..." + for obj in $oldobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > $output_objdir/$libname.libcmd + func_to_tool_file "$output_objdir/$libname.libcmd" + oldobjs=" $archiver_list_spec$func_to_tool_file_result" + cmds=$old_archive_cmds + else + # the command line is too long to link in one step, link in parts + func_verbose "using piecewise archive linking..." + save_RANLIB=$RANLIB + RANLIB=: + objlist= + concat_cmds= + save_oldobjs=$oldobjs + oldobjs= + # Is there a better way of finding the last object in the list? + for obj in $save_oldobjs + do + last_oldobj=$obj + done + eval test_cmds=\"$old_archive_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + for obj in $save_oldobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + func_append objlist " $obj" + if test "$len" -lt "$max_cmd_len"; then + : + else + # the above command should be used before it gets too long + oldobjs=$objlist + if test "$obj" = "$last_oldobj"; then + RANLIB=$save_RANLIB + fi + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$old_archive_cmds\" + objlist= + len=$len0 + fi + done + RANLIB=$save_RANLIB + oldobjs=$objlist + if test -z "$oldobjs"; then + eval cmds=\"\$concat_cmds\" + else + eval cmds=\"\$concat_cmds~\$old_archive_cmds\" + fi + fi + fi + func_execute_cmds "$cmds" 'exit $?' + done + + test -n "$generated" && \ + func_show_eval "${RM}r$generated" + + # Now create the libtool archive. + case $output in + *.la) + old_library= + test yes = "$build_old_libs" && old_library=$libname.$libext + func_verbose "creating $output" + + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + # Quote the link command for shipping. + relink_command="(cd `pwd`; $SHELL \"$progpath\" $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + if test yes = "$hardcode_automatic"; then + relink_command= + fi + + # Only create the output if not a dry run. + $opt_dry_run || { + for installed in no yes; do + if test yes = "$installed"; then + if test -z "$install_libdir"; then + break + fi + output=$output_objdir/${outputname}i + # Replace all uninstalled libtool libraries with the installed ones + newdependency_libs= + for deplib in $dependency_libs; do + case $deplib in + *.la) + func_basename "$deplib" + name=$func_basename_result + func_resolve_sysroot "$deplib" + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name" + ;; + -L*) + func_stripname -L '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -L$func_replace_sysroot_result" + ;; + -R*) + func_stripname -R '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -R$func_replace_sysroot_result" + ;; + *) func_append newdependency_libs " $deplib" ;; + esac + done + dependency_libs=$newdependency_libs + newdlfiles= + + for lib in $dlfiles; do + case $lib in + *.la) + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name" + ;; + *) func_append newdlfiles " $lib" ;; + esac + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + *.la) + # Only pass preopened files to the pseudo-archive (for + # eventual linking with the app. that links it) if we + # didn't already link the preopened objects directly into + # the library: + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name" + ;; + esac + done + dlprefiles=$newdlprefiles + else + newdlfiles= + for lib in $dlfiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlfiles " $abs" + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlprefiles " $abs" + done + dlprefiles=$newdlprefiles + fi + $RM $output + # place dlname in correct position for cygwin + # In fact, it would be nice if we could use this code for all target + # systems that can't hard-code library paths into their executables + # and that have no shared library path variable independent of PATH, + # but it turns out we can't easily determine that from inspecting + # libtool variables, so we have to hard-code the OSs to which it + # applies here; at the moment, that means platforms that use the PE + # object format with DLL files. See the long comment at the top of + # tests/bindir.at for full details. + tdlname=$dlname + case $host,$output,$installed,$module,$dlname in + *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll) + # If a -bindir argument was supplied, place the dll there. + if test -n "$bindir"; then + func_relative_path "$install_libdir" "$bindir" + tdlname=$func_relative_path_result/$dlname + else + # Otherwise fall back on heuristic. + tdlname=../bin/$dlname + fi + ;; + esac + $ECHO > $output "\ +# $outputname - a libtool library file +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='$tdlname' + +# Names of this library. +library_names='$library_names' + +# The name of the static archive. +old_library='$old_library' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags='$new_inherited_linker_flags' + +# Libraries that this one depends upon. +dependency_libs='$dependency_libs' + +# Names of additional weak libraries provided by this library +weak_library_names='$weak_libs' + +# Version information for $libname. +current=$current +age=$age +revision=$revision + +# Is this an already installed library? +installed=$installed + +# Should we warn about portability when linking against -modules? +shouldnotlink=$module + +# Files to dlopen/dlpreopen +dlopen='$dlfiles' +dlpreopen='$dlprefiles' + +# Directory that this library needs to be installed in: +libdir='$install_libdir'" + if test no,yes = "$installed,$need_relink"; then + $ECHO >> $output "\ +relink_command=\"$relink_command\"" + fi + done + } + + # Do a symbolic link so that the libtool archive can be found in + # LD_LIBRARY_PATH before the program is installed. + func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?' + ;; + esac + exit $EXIT_SUCCESS +} + +if test link = "$opt_mode" || test relink = "$opt_mode"; then + func_mode_link ${1+"$@"} +fi + + +# func_mode_uninstall arg... +func_mode_uninstall () +{ + $debug_cmd + + RM=$nonopt + files= + rmforce=false + exit_status=0 + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + for arg + do + case $arg in + -f) func_append RM " $arg"; rmforce=: ;; + -*) func_append RM " $arg" ;; + *) func_append files " $arg" ;; + esac + done + + test -z "$RM" && \ + func_fatal_help "you must specify an RM program" + + rmdirs= + + for file in $files; do + func_dirname "$file" "" "." + dir=$func_dirname_result + if test . = "$dir"; then + odir=$objdir + else + odir=$dir/$objdir + fi + func_basename "$file" + name=$func_basename_result + test uninstall = "$opt_mode" && odir=$dir + + # Remember odir for removal later, being careful to avoid duplicates + if test clean = "$opt_mode"; then + case " $rmdirs " in + *" $odir "*) ;; + *) func_append rmdirs " $odir" ;; + esac + fi + + # Don't error if the file doesn't exist and rm -f was used. + if { test -L "$file"; } >/dev/null 2>&1 || + { test -h "$file"; } >/dev/null 2>&1 || + test -f "$file"; then + : + elif test -d "$file"; then + exit_status=1 + continue + elif $rmforce; then + continue + fi + + rmfiles=$file + + case $name in + *.la) + # Possibly a libtool archive, so verify it. + if func_lalib_p "$file"; then + func_source $dir/$name + + # Delete the libtool libraries and symlinks. + for n in $library_names; do + func_append rmfiles " $odir/$n" + done + test -n "$old_library" && func_append rmfiles " $odir/$old_library" + + case $opt_mode in + clean) + case " $library_names " in + *" $dlname "*) ;; + *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;; + esac + test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i" + ;; + uninstall) + if test -n "$library_names"; then + # Do each command in the postuninstall commands. + func_execute_cmds "$postuninstall_cmds" '$rmforce || exit_status=1' + fi + + if test -n "$old_library"; then + # Do each command in the old_postuninstall commands. + func_execute_cmds "$old_postuninstall_cmds" '$rmforce || exit_status=1' + fi + # FIXME: should reinstall the best remaining shared library. + ;; + esac + fi + ;; + + *.lo) + # Possibly a libtool object, so verify it. + if func_lalib_p "$file"; then + + # Read the .lo file + func_source $dir/$name + + # Add PIC object to the list of files to remove. + if test -n "$pic_object" && test none != "$pic_object"; then + func_append rmfiles " $dir/$pic_object" + fi + + # Add non-PIC object to the list of files to remove. + if test -n "$non_pic_object" && test none != "$non_pic_object"; then + func_append rmfiles " $dir/$non_pic_object" + fi + fi + ;; + + *) + if test clean = "$opt_mode"; then + noexename=$name + case $file in + *.exe) + func_stripname '' '.exe' "$file" + file=$func_stripname_result + func_stripname '' '.exe' "$name" + noexename=$func_stripname_result + # $file with .exe has already been added to rmfiles, + # add $file without .exe + func_append rmfiles " $file" + ;; + esac + # Do a test to see if this is a libtool program. + if func_ltwrapper_p "$file"; then + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + relink_command= + func_source $func_ltwrapper_scriptname_result + func_append rmfiles " $func_ltwrapper_scriptname_result" + else + relink_command= + func_source $dir/$noexename + fi + + # note $name still contains .exe if it was in $file originally + # as does the version of $file that was added into $rmfiles + func_append rmfiles " $odir/$name $odir/${name}S.$objext" + if test yes = "$fast_install" && test -n "$relink_command"; then + func_append rmfiles " $odir/lt-$name" + fi + if test "X$noexename" != "X$name"; then + func_append rmfiles " $odir/lt-$noexename.c" + fi + fi + fi + ;; + esac + func_show_eval "$RM $rmfiles" 'exit_status=1' + done + + # Try to remove the $objdir's in the directories where we deleted files + for dir in $rmdirs; do + if test -d "$dir"; then + func_show_eval "rmdir $dir >/dev/null 2>&1" + fi + done + + exit $exit_status +} + +if test uninstall = "$opt_mode" || test clean = "$opt_mode"; then + func_mode_uninstall ${1+"$@"} +fi + +test -z "$opt_mode" && { + help=$generic_help + func_fatal_help "you must specify a MODE" +} + +test -z "$exec_cmd" && \ + func_fatal_help "invalid operation mode '$opt_mode'" + +if test -n "$exec_cmd"; then + eval exec "$exec_cmd" + exit $EXIT_FAILURE +fi + +exit $exit_status + + +# The TAGs below are defined such that we never get into a situation +# where we disable both kinds of libraries. Given conflicting +# choices, we go for a static library, that is the most portable, +# since we can't tell whether shared libraries were disabled because +# the user asked for that or because the platform doesn't support +# them. This is particularly important on AIX, because we don't +# support having both static and shared libraries enabled at the same +# time on that platform, so we default to a shared-only configuration. +# If a disable-shared tag is given, we'll fallback to a static-only +# configuration. But we'll never go from static-only to shared-only. + +# ### BEGIN LIBTOOL TAG CONFIG: disable-shared +build_libtool_libs=no +build_old_libs=yes +# ### END LIBTOOL TAG CONFIG: disable-shared + +# ### BEGIN LIBTOOL TAG CONFIG: disable-static +build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac` +# ### END LIBTOOL TAG CONFIG: disable-static + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/build-aux/missing b/build-aux/missing new file mode 100755 index 0000000..625aeb1 --- /dev/null +++ b/build-aux/missing @@ -0,0 +1,215 @@ +#! /bin/sh +# Common wrapper for a few potentially missing GNU programs. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Originally written by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try '$0 --help' for more information" + exit 1 +fi + +case $1 in + + --is-lightweight) + # Used by our autoconf macros to check whether the available missing + # script is modern enough. + exit 0 + ;; + + --run) + # Back-compat with the calling convention used by older automake. + shift + ;; + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due +to PROGRAM being missing or too old. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + +Supported PROGRAM values: + aclocal autoconf autoheader autom4te automake makeinfo + bison yacc flex lex help2man + +Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and +'g' are ignored when checking the name. + +Send bug reports to ." + exit $? + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + exit $? + ;; + + -*) + echo 1>&2 "$0: unknown '$1' option" + echo 1>&2 "Try '$0 --help' for more information" + exit 1 + ;; + +esac + +# Run the given program, remember its exit status. +"$@"; st=$? + +# If it succeeded, we are done. +test $st -eq 0 && exit 0 + +# Also exit now if we it failed (or wasn't found), and '--version' was +# passed; such an option is passed most likely to detect whether the +# program is present and works. +case $2 in --version|--help) exit $st;; esac + +# Exit code 63 means version mismatch. This often happens when the user +# tries to use an ancient version of a tool on a file that requires a +# minimum version. +if test $st -eq 63; then + msg="probably too old" +elif test $st -eq 127; then + # Program was missing. + msg="missing on your system" +else + # Program was found and executed, but failed. Give up. + exit $st +fi + +perl_URL=https://www.perl.org/ +flex_URL=https://github.com/westes/flex +gnu_software_URL=https://www.gnu.org/software + +program_details () +{ + case $1 in + aclocal|automake) + echo "The '$1' program is part of the GNU Automake package:" + echo "<$gnu_software_URL/automake>" + echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/autoconf>" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + autoconf|autom4te|autoheader) + echo "The '$1' program is part of the GNU Autoconf package:" + echo "<$gnu_software_URL/autoconf/>" + echo "It also requires GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + esac +} + +give_advice () +{ + # Normalize program name to check for. + normalized_program=`echo "$1" | sed ' + s/^gnu-//; t + s/^gnu//; t + s/^g//; t'` + + printf '%s\n' "'$1' is $msg." + + configure_deps="'configure.ac' or m4 files included by 'configure.ac'" + case $normalized_program in + autoconf*) + echo "You should only need it if you modified 'configure.ac'," + echo "or m4 files included by it." + program_details 'autoconf' + ;; + autoheader*) + echo "You should only need it if you modified 'acconfig.h' or" + echo "$configure_deps." + program_details 'autoheader' + ;; + automake*) + echo "You should only need it if you modified 'Makefile.am' or" + echo "$configure_deps." + program_details 'automake' + ;; + aclocal*) + echo "You should only need it if you modified 'acinclude.m4' or" + echo "$configure_deps." + program_details 'aclocal' + ;; + autom4te*) + echo "You might have modified some maintainer files that require" + echo "the 'autom4te' program to be rebuilt." + program_details 'autom4te' + ;; + bison*|yacc*) + echo "You should only need it if you modified a '.y' file." + echo "You may want to install the GNU Bison package:" + echo "<$gnu_software_URL/bison/>" + ;; + lex*|flex*) + echo "You should only need it if you modified a '.l' file." + echo "You may want to install the Fast Lexical Analyzer package:" + echo "<$flex_URL>" + ;; + help2man*) + echo "You should only need it if you modified a dependency" \ + "of a man page." + echo "You may want to install the GNU Help2man package:" + echo "<$gnu_software_URL/help2man/>" + ;; + makeinfo*) + echo "You should only need it if you modified a '.texi' file, or" + echo "any other file indirectly affecting the aspect of the manual." + echo "You might want to install the Texinfo package:" + echo "<$gnu_software_URL/texinfo/>" + echo "The spurious makeinfo call might also be the consequence of" + echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" + echo "want to install GNU make:" + echo "<$gnu_software_URL/make/>" + ;; + *) + echo "You might have modified some files without having the proper" + echo "tools for further handling them. Check the 'README' file, it" + echo "often tells you about the needed prerequisites for installing" + echo "this package. You may also peek at any GNU archive site, in" + echo "case some other package contains this missing '$1' program." + ;; + esac +} + +give_advice "$1" | sed -e '1s/^/WARNING: /' \ + -e '2,$s/^/ /' >&2 + +# Propagate the correct exit status (expected to be 127 for a program +# not found, 63 for a program that failed due to version mismatch). +exit $st + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/build-aux/test-driver b/build-aux/test-driver new file mode 100755 index 0000000..b8521a4 --- /dev/null +++ b/build-aux/test-driver @@ -0,0 +1,148 @@ +#! /bin/sh +# test-driver - basic testsuite driver script. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 2011-2018 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +usage_error () +{ + echo "$0: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <$log_file 2>&1 +estatus=$? + +if test $enable_hard_errors = no && test $estatus -eq 99; then + tweaked_estatus=1 +else + tweaked_estatus=$estatus +fi + +case $tweaked_estatus:$expect_failure in + 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; + 0:*) col=$grn res=PASS recheck=no gcopy=no;; + 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; + 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; + *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; + *:*) col=$red res=FAIL recheck=yes gcopy=yes;; +esac + +# Report the test outcome and exit status in the logs, so that one can +# know whether the test passed or failed simply by looking at the '.log' +# file, without the need of also peaking into the corresponding '.trs' +# file (automake bug#11814). +echo "$res $test_name (exit status: $estatus)" >>$log_file + +# Report outcome to console. +echo "${col}${res}${std}: $test_name" + +# Register the test result, and other relevant metadata. +echo ":test-result: $res" > $trs_file +echo ":global-test-result: $res" >> $trs_file +echo ":recheck: $recheck" >> $trs_file +echo ":copy-in-global-log: $gcopy" >> $trs_file + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/cmake/Modules/FindLibGcrypt.cmake b/cmake/Modules/FindLibGcrypt.cmake new file mode 100644 index 0000000..dcf1c98 --- /dev/null +++ b/cmake/Modules/FindLibGcrypt.cmake @@ -0,0 +1,127 @@ +#.rst: +# FindLibGcrypt +# ------------- +# +# Try to find libgcrypt. +# +# This will define the following variables: +# +# ``LibGcrypt_FOUND`` +# True if libgcrypt is available. +# +# ``LibGcrypt_VERSION`` +# The version of LibGcrypt +# +# ``LibGcrypt_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if +# the target is not used for linking +# +# ``LibGcrypt_LIBRARIES`` +# This can be passed to target_link_libraries() instead of +# the ``LibGcrypt::LibGcrypt`` target +# +# If ``LibGcrypt_FOUND`` is TRUE, the following imported target +# will be available: +# +# ``LibGcrypt::LibGcrypt`` +# The libgcrypt library +# +# Since 1.9.50. + +#============================================================================= +# Copyright 2007 Charles Connell (This was based upon FindKopete.cmake) +# Copyright 2010 Joris Guisson +# Copyright 2016 Christophe Giboudeaux +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_path(LibGcrypt_INCLUDE_DIRS + NAMES gcrypt.h + PATH_SUFFIXES libgcrypt +) + +find_library(LibGcrypt_LIBRARIES + NAMES gcrypt +) + +if(MSVC) + find_library(LibGcrypt_LIBRARIES_DEBUG + NAMES gcryptd + ) + + if(NOT LibGcrypt_LIBRARIES_DEBUG) + unset(LibGcrypt_LIBRARIES CACHE) + endif() + + if(MSVC_IDE) + if(NOT (LibGcrypt_LIBRARIES_DEBUG AND LibGcrypt_LIBRARIES)) + message(STATUS + "\nCould NOT find the debug AND release version of the libgcrypt library.\n + You need to have both to use MSVC projects.\n + Please build and install both libgcrypt libraries first.\n" + ) + unset(LibGcrypt_LIBRARIES CACHE) + endif() + else() + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + if(CMAKE_BUILD_TYPE_TOLOWER MATCHES debug) + set(LibGcrypt_LIBRARIES ${LibGcrypt_LIBRARIES_DEBUG}) + endif() + endif() +endif() + +# Get version from gcrypt.h +# #define GCRYPT_VERSION "1.6.4" +if(LibGcrypt_INCLUDE_DIRS AND LibGcrypt_LIBRARIES) + file(STRINGS ${LibGcrypt_INCLUDE_DIRS}/gcrypt.h _GCRYPT_H REGEX "^#define GCRYPT_VERSION[ ]+.*$") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\1" LibGcrypt_MAJOR_VERSION "${_GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\2" LibGcrypt_MINOR_VERSION "${_GCRYPT_H}") + string(REGEX REPLACE "^.*GCRYPT_VERSION[ ]+\"([0-9]+).([0-9]+).([0-9]+).*\".*$" "\\3" LibGcrypt_PATCH_VERSION "${_GCRYPT_H}") + + set(LibGcrypt_VERSION "${LibGcrypt_MAJOR_VERSION}.${LibGcrypt_MINOR_VERSION}.${LibGcrypt_PATCH_VERSION}") + unset(_GCRYPT_H) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibGcrypt + FOUND_VAR LibGcrypt_FOUND + REQUIRED_VARS LibGcrypt_INCLUDE_DIRS LibGcrypt_LIBRARIES + VERSION_VAR LibGcrypt_VERSION +) + +if(LibGcrypt_FOUND AND NOT TARGET LibGcrypt::LibGcrypt) + add_library(LibGcrypt::LibGcrypt UNKNOWN IMPORTED) + set_target_properties(LibGcrypt::LibGcrypt PROPERTIES + IMPORTED_LOCATION "${LibGcrypt_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${LibGcrypt_INCLUDE_DIRS}") +endif() + +mark_as_advanced(LibGcrypt_INCLUDE_DIRS LibGcrypt_LIBRARIES) + +include(FeatureSummary) +set_package_properties(LibGcrypt PROPERTIES + URL "http://directory.fsf.org/wiki/Libgcrypt" + DESCRIPTION "General purpose crypto library based on the code used in GnuPG." +) diff --git a/cmake/Modules/GeneratePkgConfig.cmake b/cmake/Modules/GeneratePkgConfig.cmake new file mode 100644 index 0000000..78b470e --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig.cmake @@ -0,0 +1,175 @@ +# This module provides generate_and_install_pkg_config_file() function. +# The function takes target name and expects a fully configured project, i.e. with set version and +# description. The function extracts interface libraries, include dirs, definitions and options +# from the target and generates pkg-config file with install() command +# The function expands imported targets and generator expressions + +# save the current file dir for later use in the generate_and_install_pkg_config_file() function +set(_GeneratePkGConfigDir "${CMAKE_CURRENT_LIST_DIR}/GeneratePkgConfig") + +include(GNUInstallDirs) + +function(_get_target_property_merging_configs _var_name _target_name _propert_name) + get_property(prop_set TARGET ${_target_name} PROPERTY ${_propert_name} SET) + if (prop_set) + get_property(vals TARGET ${_target_name} PROPERTY ${_propert_name}) + else() + if (CMAKE_BUILD_TYPE) + list(APPEND configs ${CMAKE_BUILD_TYPE}) + elseif(CMAKE_CONFIGURATION_TYPES) + list(APPEND configs ${CMAKE_CONFIGURATION_TYPES}) + endif() + foreach(cfg ${configs}) + string(TOUPPER "${cfg}" UPPERCFG) + get_property(mapped_configs TARGET ${_target_name} PROPERTY "MAP_IMPORTED_CONFIG_${UPPERCFG}") + if (mapped_configs) + list(GET "${mapped_configs}" 0 target_cfg) + else() + set(target_cfg "${UPPERCFG}") + endif() + get_property(prop_set TARGET ${_target_name} PROPERTY ${_propert_name}_${target_cfg} SET) + if (prop_set) + get_property(val_for_cfg TARGET ${_target_name} PROPERTY ${_propert_name}_${target_cfg}) + list(APPEND vals "$<$:${val_for_cfg}>") + break() + endif() + endforeach() + if (NOT prop_set) + get_property(imported_cfgs TARGET ${_target_name} PROPERTY IMPORTED_CONFIGURATIONS) + # CMake docs say we can use any of the imported configs + list(GET imported_cfgs 0 imported_config) + get_property(vals TARGET ${_target_name} PROPERTY ${_propert_name}_${imported_config}) + # remove config generator expression. Only in this case! Notice we use such expression + # ourselves in the loop above + string(REPLACE "$<$:" "$<1:" vals "${vals}") + endif() + endif() + # HACK for static libraries cmake populates link dependencies as $. + # pkg-config does not support special handling for static libraries and as such we will remove + # that generator expression + string(REPLACE "$") + + configure_file("${_GeneratePkGConfigDir}/generate-pkg-config.cmake.in" + "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake" @ONLY) + + install(SCRIPT "${_generate_target_dir}/${cfg}/generate-pkg-config.cmake") + endforeach() + endif() +endfunction() diff --git a/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in b/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in new file mode 100644 index 0000000..5454b14 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/generate-pkg-config.cmake.in @@ -0,0 +1,52 @@ +include("@_variables_file_name@") + +function (cmake_list_to_pkg_config _result _list _prefix) + set(_tmp_list "${_list}") + list(REMOVE_ITEM _tmp_list "") + # remove prefix from prefixed items + string(REGEX REPLACE "(^|;)(${_prefix})" "\\1" _tmp_list "${_tmp_list}") + # append 'prefix' to each element + string(REGEX REPLACE "([^;]+)" "${_prefix}\\1" _tmp_list "${_tmp_list}") + # transform cmake list into a space delimited list + string(REPLACE ";" " " _tmp_list "${_tmp_list}") + set(${_result} "${_tmp_list}" PARENT_SCOPE) +endfunction() + +# Helper function for splitting full library paths into [dir, name] and merging repetitive dir entries +function(split_library_dirs _libraries _base_library_dir _library_dirs_var _library_names_var) + set(libdirs "${_base_library_dir}") + set(libs "") + foreach (l IN LISTS _libraries) + get_filename_component(lDir "${l}" DIRECTORY) + if (lDir) + get_filename_component(lDir "${lDir}" REALPATH) + endif() + get_filename_component(lFile "${l}" NAME_WE) + string(REPLACE "${_SHARED_LIBRARY_PREFIX}" "" lFile "${lFile}") + list(APPEND libdirs "${lDir}") + list(APPEND libs "${lFile}") + endforeach() + list(REMOVE_DUPLICATES libdirs) + list(REMOVE_AT libdirs 0) # as it is the base libdir and will be handled separately + + set(${_library_dirs_var} "${libdirs}" PARENT_SCOPE) + set(${_library_names_var} "${libs}" PARENT_SCOPE) +endfunction() + +split_library_dirs("${_TARGET_INTERFACE_LINK_LIBRARIES}" "${CMAKE_INSTALL_PREFIX}/${_INSTALL_LIBDIR}" _lib_dirs _library_names) +cmake_list_to_pkg_config(_libs "${_library_names}" "-l") +list(LENGTH _lib_dirs _additional_libdirs_count) +if (_additional_libdirs_count GREATER 0) + cmake_list_to_pkg_config(_additional_libdirs "${_lib_dirs}" "-L") + set(_interface_link_libraries "${_additional_libdirs} ${_libs}") +else() + set(_interface_link_libraries "${_libs}") +endif() + +cmake_list_to_pkg_config(_interface_definitions "${_TARGET_INTERFACE_DEFINITIONS}" "-D") +cmake_list_to_pkg_config(_interface_include_dirs "${_TARGET_INTERFACE_INCLUDE_DIRS}" "-I") +set(_interface_compile_options "${_TARGET_INTERFACE_COMPILE_OPTIONS}") +string(REPLACE ";" " " _interface_compile_options "${_interface_compile_options}") + +configure_file("@_pkg_config_file_template_filename@" "@_generate_target_dir@/@_package_name@.pc" @ONLY) +file(INSTALL "@_generate_target_dir@/@_package_name@.pc" DESTINATION "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@/pkgconfig") diff --git a/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in b/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in new file mode 100644 index 0000000..4354134 --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/pkg-config.cmake.in @@ -0,0 +1,8 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=${prefix}/@_INSTALL_LIBDIR@ + +Name: @_PROJECT_NAME@ +Description: @_PROJECT_DESCRIPTION@ +Version: @_PROJECT_VERSION@ +Libs: -L${libdir} -l@_TARGET_OUTPUT_NAME@ @_interface_link_libraries@ +Cflags: @_interface_compile_options@ @_interface_include_dirs@ @_interface_definitions@ diff --git a/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in b/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in new file mode 100644 index 0000000..e16d7bc --- /dev/null +++ b/cmake/Modules/GeneratePkgConfig/target-compile-settings.cmake.in @@ -0,0 +1,13 @@ +set(_TARGET_INTERFACE_LINK_LIBRARIES "@_interface_link_libraries@") +set(_TARGET_INTERFACE_COMPILE_OPTIONS "@_interface_compile_options@") +set(_TARGET_INTERFACE_INCLUDE_DIRS "@_interface_include_dirs@") +set(_TARGET_INTERFACE_DEFINITIONS "@_interface_definitions@") +set(_TARGET_OUTPUT_NAME "@_output_name@") + +set(_INSTALL_LIBDIR "@CMAKE_INSTALL_LIBDIR@") +set(_INSTALL_INCLUDEDIR "@CMAKE_INSTALL_INCLUDEDIR@") +set(_SHARED_LIBRARY_PREFIX "@CMAKE_SHARED_LIBRARY_PREFIX@") + +set(_PROJECT_NAME "@PROJECT_NAME@") +set(_PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@") +set(_PROJECT_VERSION "@PROJECT_VERSION@") diff --git a/cmake/Modules/LibtorrentMacros.cmake b/cmake/Modules/LibtorrentMacros.cmake new file mode 100644 index 0000000..e3500c7 --- /dev/null +++ b/cmake/Modules/LibtorrentMacros.cmake @@ -0,0 +1,80 @@ +# Various helper function and macros for building libtorrent + +include(FeatureSummary) + +# macro for issuing option() and add_feature_info() in a single call. +# Synopsis: +# feature_option( ) +macro(feature_option _name _description _default) + option(${_name} "${_description}" ${_default}) + add_feature_info(${_name} ${_name} "${_description}") +endmacro() + +# function to add a simple build option which controls compile definition(s) for a target. +# Synopsis: +# target_optional_compile_definitions( [FEATURE] +# NAME DESCRIPTION DEFAULT +# [ENABLED [enabled_compile_definitions...]] +# [DISABLED [disabled_compile_defnitions...]] +# ) +# NAME, DESCRIPTION and DEFAULT are passed to option() call +# if FEATURE is given, they are passed to add_feature_info() +# ENABLED lists compile definitions that will be set on when option is enabled, +# DISABLED lists definitions that will be set otherwise +function(target_optional_compile_definitions _target _scope) + set(options FEATURE) + set(oneValueArgs NAME DESCRIPTION DEFAULT) + set(multiValueArgs ENABLED DISABLED) + cmake_parse_arguments(TOCD ${options} "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + option(${TOCD_NAME} "${TOCD_DESCRIPTION}" ${TOCD_DEFAULT}) + if (${${TOCD_NAME}}) + target_compile_definitions(${_target} ${_scope} ${TOCD_ENABLED}) + else() + target_compile_definitions(${_target} ${_scope} ${TOCD_DISABLED}) + endif() + if(${TOCD_FEATURE}) + add_feature_info(${TOCD_NAME} ${TOCD_NAME} "${TOCD_DESCRIPTION}") + endif() +endfunction() + +# a helper macro that calls find_package() and appends the package (if found) to the +# _package_dependencies list, which can be used later to generate package config file +macro(find_public_dependency _name) + find_package(${_name} ${ARGN}) + string(TOUPPER "${_name}" _name_uppercased) + if (${_name}_FOUND OR ${_name_uppercased}_FOUND) + # Dependencies to be used below for generating Config.cmake file + # We don't need the 'REQUIRED' argument there + set(_args "${_name}") + list(APPEND _args "${ARGN}") + list(REMOVE_ITEM _args "REQUIRED") + list(REMOVE_ITEM _args "") # just in case + string(REPLACE ";" " " _args "${_args}") + list(APPEND _package_dependencies "${_args}") + endif() +endmacro() + +# function for parsing version variables that are set in version.hpp file +# the version identifiers there are defined as follows: +# #define LIBTORRENT_VERSION_MAJOR 1 +# #define LIBTORRENT_VERSION_MINOR 2 +# #define LIBTORRENT_VERSION_TINY 0 + +function(read_version _verFile _outVarMajor _outVarMinor _outVarTiny) + file(STRINGS ${_verFile} verFileContents REGEX ".+LIBTORRENT_VERSION_[A-Z]+.[0-9]+.*") +# message(STATUS "version file contents: ${verFileContents}") + # the verFileContents variable contains something like the following: + # #define LIBTORRENT_VERSION_MAJOR 1;#define LIBTORRENT_VERSION_MINOR 2;#define LIBTORRENT_VERSION_TINY 0 + set(_regex ".+_MAJOR +([0-9]+);.+_MINOR +([0-9]+);.+_TINY +([0-9]+)") + # note quotes around _regex, they are needed because the variable contains semicolons + string(REGEX MATCH "${_regex}" _tmp "${verFileContents}") + if (NOT _tmp) + message(FATAL_ERROR "Could not detect project version number from ${_verFile}") + endif() + +# message(STATUS "Matched version string: ${_tmp}") + + set(${_outVarMajor} ${CMAKE_MATCH_1} PARENT_SCOPE) + set(${_outVarMinor} ${CMAKE_MATCH_2} PARENT_SCOPE) + set(${_outVarTiny} ${CMAKE_MATCH_3} PARENT_SCOPE) +endfunction() diff --git a/cmake/Modules/ucm_flags.cmake b/cmake/Modules/ucm_flags.cmake new file mode 100644 index 0000000..7b3af2a --- /dev/null +++ b/cmake/Modules/ucm_flags.cmake @@ -0,0 +1,118 @@ +# taken from https://github.com/onqtam/ucm/blob/master/cmake/ucm.cmake + +# +# ucm.cmake - useful cmake macros +# +# Copyright (c) 2016 Viktor Kirilov +# +# Distributed under the MIT Software License +# See accompanying file LICENSE.txt or copy at +# https://opensource.org/licenses/MIT +# +# The documentation can be found at the library's page: +# https://github.com/onqtam/ucm + +include(CMakeParseArguments) + +# ucm_gather_flags +# Gathers all lists of flags for printing or manipulation +macro(ucm_gather_flags with_linker result) + set(${result} "") + # add the main flags without a config + list(APPEND ${result} CMAKE_C_FLAGS) + list(APPEND ${result} CMAKE_CXX_FLAGS) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS) + endif() + + if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") + # handle single config generators - like makefiles/ninja - when CMAKE_BUILD_TYPE is set + string(TOUPPER ${CMAKE_BUILD_TYPE} config) + list(APPEND ${result} CMAKE_C_FLAGS_${config}) + list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) + endif() + else() + # handle multi config generators (like msvc, xcode) + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${config} config) + list(APPEND ${result} CMAKE_C_FLAGS_${config}) + list(APPEND ${result} CMAKE_CXX_FLAGS_${config}) + if(${with_linker}) + list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config}) + list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config}) + endif() + endforeach() + endif() +endmacro() + +# ucm_set_runtime +# Sets the runtime (static/dynamic) for msvc/gcc +macro(ucm_set_runtime) + cmake_parse_arguments(ARG "STATIC;DYNAMIC" "" "" ${ARGN}) + + if(ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" STREQUAL "") + message(AUTHOR_WARNING "ucm_set_runtime() does not support clang yet!") + endif() + + ucm_gather_flags(0 flags_configs) + + # add/replace the flags + # note that if the user has messed with the flags directly this function might fail + # - for example if with MSVC and the user has removed the flags - here we just switch/replace them + if("${ARG_STATIC}") + foreach(flags ${flags_configs}) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(NOT ${flags} MATCHES "-static-libstdc\\+\\+") + set(${flags} "${${flags}} -static-libstdc++") + endif() + if(NOT ${flags} MATCHES "-static-libgcc") + set(${flags} "${${flags}} -static-libgcc") + endif() + elseif(MSVC) + if(${flags} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flags} "${${flags}}") + endif() + endif() + endforeach() + elseif("${ARG_DYNAMIC}") + foreach(flags ${flags_configs}) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + if(${flags} MATCHES "-static-libstdc\\+\\+") + string(REGEX REPLACE "-static-libstdc\\+\\+" "" ${flags} "${${flags}}") + endif() + if(${flags} MATCHES "-static-libgcc") + string(REGEX REPLACE "-static-libgcc" "" ${flags} "${${flags}}") + endif() + elseif(MSVC) + if(${flags} MATCHES "/MT") + string(REGEX REPLACE "/MT" "/MD" ${flags} "${${flags}}") + endif() + endif() + endforeach() + endif() +endmacro() + +# ucm_print_flags +# Prints all compiler flags for all configurations +macro(ucm_print_flags) + ucm_gather_flags(1 flags_configs) + message("") + foreach(flags ${flags_configs}) + message("${flags}: ${${flags}}") + endforeach() + message("") +endmacro() diff --git a/configure b/configure new file mode 100755 index 0000000..b218ade --- /dev/null +++ b/configure @@ -0,0 +1,23504 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for libtorrent-rasterbar 1.2.9. +# +# Report bugs to . +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 + + test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ + || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org and +$0: arvid@libtorrent.org about your system, including any +$0: error possibly output before this message. Then install +$0: a modern shell, or manually run the script under such a +$0: shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + +SHELL=${CONFIG_SHELL-/bin/sh} + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='libtorrent-rasterbar' +PACKAGE_TARNAME='libtorrent-rasterbar' +PACKAGE_VERSION='1.2.9' +PACKAGE_STRING='libtorrent-rasterbar 1.2.9' +PACKAGE_BUGREPORT='arvid@libtorrent.org' +PACKAGE_URL='http://www.libtorrent.org' + +ac_unique_file="src/torrent.cpp" +: "${AR_FLAGS=rc}" +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='am__EXEEXT_FALSE +am__EXEEXT_TRUE +LTLIBOBJS +LIBOBJS +PYTHON_CXXFLAGS +COMPILETIME_OPTIONS +DEBUGFLAGS +PYTHON_INSTALL_PARAMS +WITH_OPENSSL_FALSE +WITH_OPENSSL_TRUE +ENABLE_PYTHON_BINDING_FALSE +ENABLE_PYTHON_BINDING_TRUE +ENABLE_TESTS_FALSE +ENABLE_TESTS_TRUE +ENABLE_EXAMPLES_FALSE +ENABLE_EXAMPLES_TRUE +ENABLE_DHT_FALSE +ENABLE_DHT_TRUE +ICONV_LIBS +LTLIBICONV +LIBICONV +BOOST_PYTHON_LIB +PYTHON_EXTRA_LDFLAGS +PYTHON_EXTRA_LIBS +PYTHON_SITE_PKG +PYTHON_LIBS +PYTHON_CPPFLAGS +pkgpyexecdir +pyexecdir +pkgpythondir +pythondir +PYTHON_PLATFORM +PYTHON_EXEC_PREFIX +PYTHON_PREFIX +PYTHON_VERSION +PYTHON +OPENSSL_LDFLAGS +OPENSSL_LIBS +OPENSSL_INCLUDES +PKG_CONFIG_LIBDIR +PKG_CONFIG_PATH +PKG_CONFIG +BOOST_SYSTEM_LIB +HAVE_CXX11 +BOOST_LDFLAGS +BOOST_CPPFLAGS +PTHREAD_CFLAGS +PTHREAD_LIBS +PTHREAD_CC +ax_pthread_config +HAVE_WINDOWS_FALSE +HAVE_WINDOWS_TRUE +HAVE_ANDROID_FALSE +HAVE_ANDROID_TRUE +LT_SYS_LIBRARY_PATH +OTOOL64 +OTOOL +LIPO +NMEDIT +DSYMUTIL +MANIFEST_TOOL +RANLIB +ac_ct_AR +AR +DLLTOOL +OBJDUMP +LN_S +NM +ac_ct_DUMPBIN +DUMPBIN +LD +FGREP +EGREP +GREP +SED +LIBTOOL +MAINT +MAINTAINER_MODE_FALSE +MAINTAINER_MODE_TRUE +am__fastdepCXX_FALSE +am__fastdepCXX_TRUE +CXXDEPMODE +am__fastdepCC_FALSE +am__fastdepCC_TRUE +CCDEPMODE +am__nodep +AMDEPBACKSLASH +AMDEP_FALSE +AMDEP_TRUE +am__include +DEPDIR +am__untar +am__tar +AMTAR +am__leading_dot +SET_MAKE +AWK +mkdir_p +MKDIR_P +INSTALL_STRIP_PROGRAM +STRIP +install_sh +MAKEINFO +AUTOHEADER +AUTOMAKE +AUTOCONF +ACLOCAL +VERSION +PACKAGE +CYGPATH_W +am__isrc +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +target_os +target_vendor +target_cpu +target +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +CXXCPP +ac_ct_CXX +CXXFLAGS +CXX +CPP +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +INTERFACE_VERSION_INFO +AM_BACKSLASH +AM_DEFAULT_VERBOSITY +AM_DEFAULT_V +AM_V +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL +am__quote' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_silent_rules +enable_dependency_tracking +enable_maintainer_mode +enable_shared +enable_static +with_pic +enable_fast_install +with_aix_soname +with_gnu_ld +with_sysroot +enable_libtool_lock +with_boost +with_boost_libdir +with_boost_system +enable_largefile +enable_logging +enable_debug +enable_dht +enable_encryption +enable_export_all +enable_invariant_checks +enable_deprecated_functions +enable_examples +enable_tests +enable_python_binding +with_libiconv +with_openssl +with_boost_python +enable_rpath +with_libiconv_prefix +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP +CXX +CXXFLAGS +CCC +CXXCPP +LT_SYS_LIBRARY_PATH +PKG_CONFIG +PKG_CONFIG_PATH +PKG_CONFIG_LIBDIR +PYTHON +PYTHON_VERSION +PYTHON_INSTALL_PARAMS' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures libtorrent-rasterbar 1.2.9 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root + [DATAROOTDIR/doc/libtorrent-rasterbar] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +Program names: + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM run sed PROGRAM on installed program names + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] + --target=TARGET configure for building compilers for TARGET [HOST] +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of libtorrent-rasterbar 1.2.9:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-silent-rules less verbose build output (undo: "make V=1") + --disable-silent-rules verbose build output (undo: "make V=0") + --enable-dependency-tracking + do not reject slow dependency extractors + --disable-dependency-tracking + speeds up one-time build + --disable-maintainer-mode + disable make rules and dependencies not useful (and + sometimes confusing) to the casual installer + --enable-shared[=PKGS] build shared libraries [default=yes] + --enable-static[=PKGS] build static libraries [default=yes] + --enable-fast-install[=PKGS] + optimize for fast installation [default=yes] + --disable-libtool-lock avoid locking (might break parallel builds) + --disable-largefile omit support for large files + --enable-logging enable logging alerts [default=yes] + --enable-debug enable debug build [default=no] + --enable-dht enable dht support [default=yes] + --enable-encryption enable encryption support (requires OpenSSL to be + installed on your system, you can use --with-openssl + to set the path) [default=yes] + --enable-export-all export all symbols from libtorrent, including + non-public ones [default=no] + --enable-invariant-checks + enable invariant checks (use value "full" to turn on + extra expensive invariant checks) [default=yes if + debug is enabled, no otherwhise] + --enable-deprecated-functions + enable deprecated functions in the API [default=yes] + --enable-examples build example files [default=no] + --enable-tests build test files [default=no] + --enable-python-binding build python bindings [default=no] + --disable-rpath do not hardcode runtime library paths + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use + both] + --with-aix-soname=aix|svr4|both + shared library versioning (aka "SONAME") variant to + provide on AIX, [default=aix]. + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-sysroot[=DIR] Search for dependent libraries within DIR (or the + compiler's sysroot if not specified). + --with-boost[=ARG] use Boost library from a standard location + (ARG=yes), from the specified location (ARG=), + or disable it (ARG=no) [ARG=yes] + --with-boost-libdir=LIB_DIR + Force given directory for boost libraries. Note that + this will override library path detection, so use + this parameter only if default library detection + fails and you know exactly where your boost + libraries are located. + --with-boost-system[=special-lib] + use the System library from boost - it is possible + to specify a certain library for the linker e.g. + --with-boost-system=boost_system-gcc-mt + --with-libiconv enable linking against system libiconv [default=no] + --with-openssl=DIR root of the OpenSSL directory + --with-boost-python specify yes/no or the boost python library or suffix + to use + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-libiconv-prefix[=DIR] search for libiconv in DIR/include and DIR/lib + --without-libiconv-prefix don't search for libiconv in includedir and libdir + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + CXX C++ compiler command + CXXFLAGS C++ compiler flags + CXXCPP C++ preprocessor + LT_SYS_LIBRARY_PATH + User-defined run-time library search path. + PKG_CONFIG path to pkg-config utility + PKG_CONFIG_PATH + directories to add to pkg-config's search path + PKG_CONFIG_LIBDIR + path overriding pkg-config's built-in search path + PYTHON the Python interpreter + PYTHON_VERSION + The installed Python version to use, for example '2.3'. This + string will be appended to the Python interpreter canonical + name. + PYTHON_INSTALL_PARAMS + Set specific install parameters for python bindings. + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to . +libtorrent-rasterbar home page: . +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +libtorrent-rasterbar configure 1.2.9 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_cxx_try_compile LINENO +# ---------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_compile + +# ac_fn_cxx_try_cpp LINENO +# ------------------------ +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_cpp + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_cxx_try_link LINENO +# ------------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_link +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by libtorrent-rasterbar $as_me 1.2.9, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +ac_aux_dir= +for ac_dir in build-aux "$srcdir"/build-aux; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in build-aux \"$srcdir\"/build-aux" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + + + +# Silencing build output (automake-1.11) +# Check whether --enable-silent-rules was given. +if test "${enable_silent_rules+set}" = set; then : + enableval=$enable_silent_rules; +fi + +case $enable_silent_rules in # ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=0;; +esac +am_make=${MAKE-make} +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 +$as_echo_n "checking whether $am_make supports nested variables... " >&6; } +if ${am_cv_make_support_nested_variables+:} false; then : + $as_echo_n "(cached) " >&6 +else + if $as_echo 'TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 +$as_echo "$am_cv_make_support_nested_variables" >&6; } +if test $am_cv_make_support_nested_variables = yes; then + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AM_BACKSLASH='\' + + +# Change ar argument to 'rc' to silence the warning: +# ar: `u' modifier ignored since `D' is the default (see `U') + + +############################################################################### + + + + +INTERFACE_VERSION_INFO=10:0:0 + +############################################################################### + + +############################################################################### +# Start +############################################################################### + +$as_echo +$as_echo "Building $PACKAGE_STRING" + + +############################################################################### +# Performing some basic checks and initializing the build system +############################################################################### + +$as_echo +$as_echo "Checking for a C/C++ compiler to use:" +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +if test "x$CC" != xcc; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC and cc understand -c and -o together" >&5 +$as_echo_n "checking whether $CC and cc understand -c and -o together... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether cc understands -c and -o together" >&5 +$as_echo_n "checking whether cc understands -c and -o together... " >&6; } +fi +set dummy $CC; ac_cc=`$as_echo "$2" | + sed 's/[^a-zA-Z0-9_]/_/g;s/^[0-9]/_/'` +if eval \${ac_cv_prog_cc_${ac_cc}_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +# Make sure it works both with $CC and with simple cc. +# We do the test twice because some compilers refuse to overwrite an +# existing .o file with -o, though they will create one. +ac_try='$CC -c conftest.$ac_ext -o conftest2.$ac_objext >&5' +rm -f conftest2.* +if { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && + test -f conftest2.$ac_objext && { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; +then + eval ac_cv_prog_cc_${ac_cc}_c_o=yes + if test "x$CC" != xcc; then + # Test first that cc exists at all. + if { ac_try='cc -c conftest.$ac_ext >&5' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + ac_try='cc -c conftest.$ac_ext -o conftest2.$ac_objext >&5' + rm -f conftest2.* + if { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && + test -f conftest2.$ac_objext && { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; + then + # cc works too. + : + else + # cc exists but doesn't like -o. + eval ac_cv_prog_cc_${ac_cc}_c_o=no + fi + fi + fi +else + eval ac_cv_prog_cc_${ac_cc}_c_o=no +fi +rm -f core conftest* + +fi +if eval test \$ac_cv_prog_cc_${ac_cc}_c_o = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define NO_MINUS_C_MINUS_O 1" >>confdefs.h + +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CXX"; then + ac_cv_prog_CXX="$CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CXX=$ac_cv_prog_CXX +if test -n "$CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 +$as_echo "$CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CXX" && break + done +fi +if test -z "$CXX"; then + ac_ct_CXX=$CXX + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CXX"; then + ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CXX=$ac_cv_prog_ac_ct_CXX +if test -n "$ac_ct_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 +$as_echo "$ac_ct_CXX" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CXX" && break +done + + if test "x$ac_ct_CXX" = x; then + CXX="g++" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CXX=$ac_ct_CXX + fi +fi + + fi +fi +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 +$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } +if ${ac_cv_cxx_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_cxx_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 +$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GXX=yes +else + GXX= +fi +ac_test_CXXFLAGS=${CXXFLAGS+set} +ac_save_CXXFLAGS=$CXXFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 +$as_echo_n "checking whether $CXX accepts -g... " >&6; } +if ${ac_cv_prog_cxx_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_cxx_werror_flag=$ac_cxx_werror_flag + ac_cxx_werror_flag=yes + ac_cv_prog_cxx_g=no + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +else + CXXFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + +else + ac_cxx_werror_flag=$ac_save_cxx_werror_flag + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_prog_cxx_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 +$as_echo "$ac_cv_prog_cxx_g" >&6; } +if test "$ac_test_CXXFLAGS" = set; then + CXXFLAGS=$ac_save_CXXFLAGS +elif test $ac_cv_prog_cxx_g = yes; then + if test "$GXX" = yes; then + CXXFLAGS="-g -O2" + else + CXXFLAGS="-g" + fi +else + if test "$GXX" = yes; then + CXXFLAGS="-O2" + else + CXXFLAGS= + fi +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 +$as_echo_n "checking how to run the C++ preprocessor... " >&6; } +if test -z "$CXXCPP"; then + if ${ac_cv_prog_CXXCPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CXXCPP needs to be expanded + for CXXCPP in "$CXX -E" "/lib/cpp" + do + ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CXXCPP=$CXXCPP + +fi + CXXCPP=$ac_cv_prog_CXXCPP +else + ac_cv_prog_CXXCPP=$CXXCPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 +$as_echo "$CXXCPP" >&6; } +ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX understands -c and -o together" >&5 +$as_echo_n "checking whether $CXX understands -c and -o together... " >&6; } +if ${ac_cv_prog_cxx_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +# We test twice because some compilers refuse to overwrite an existing +# `.o' file with `-o', although they will create one. +ac_try='$CXX $CXXFLAGS -c conftest.$ac_ext -o conftest2.$ac_objext >&5' +rm -f conftest2.* +if { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && + test -f conftest2.$ac_objext && + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + ac_cv_prog_cxx_c_o=yes +else + ac_cv_prog_cxx_c_o=no +fi +rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_c_o" >&5 +$as_echo "$ac_cv_prog_cxx_c_o" >&6; } +if test $ac_cv_prog_cxx_c_o = no; then + +$as_echo "#define CXX_NO_MINUS_C_MINUS_O 1" >>confdefs.h + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +$as_echo +$as_echo "Checking system type:" +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking target system type" >&5 +$as_echo_n "checking target system type... " >&6; } +if ${ac_cv_target+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$target_alias" = x; then + ac_cv_target=$ac_cv_host +else + ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_target" >&5 +$as_echo "$ac_cv_target" >&6; } +case $ac_cv_target in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5;; +esac +target=$ac_cv_target +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_target +shift +target_cpu=$1 +target_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +target_os=$* +IFS=$ac_save_IFS +case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac + + +# The aliases save the names the user supplied, while $host etc. +# will get canonicalized. +test -n "$target_alias" && + test "$program_prefix$program_suffix$program_transform_name" = \ + NONENONEs,x,x, && + program_prefix=${target_alias}- + +$as_echo +$as_echo "Initializing Automake:" +am__api_version='1.16' + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 +$as_echo_n "checking whether build environment is sane... " >&6; } +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[\\\"\#\$\&\'\`$am_lf]*) + as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; +esac +case $srcdir in + *[\\\"\#\$\&\'\`$am_lf\ \ ]*) + as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$*" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$*" != "X $srcdir/configure conftest.file" \ + && test "$*" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + as_fn_error $? "ls -t appears to fail. Make sure there is not a broken + alias in your environment" "$LINENO" 5 + fi + if test "$2" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$2" = conftest.file + ) +then + # Ok. + : +else + as_fn_error $? "newly created file is older than distributed files! +Check your system clock" "$LINENO" 5 +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi + +rm -f conftest.file + +test "$program_prefix" != NONE && + program_transform_name="s&^&$program_prefix&;$program_transform_name" +# Use a double $ so make ignores it. +test "$program_suffix" != NONE && + program_transform_name="s&\$&$program_suffix&;$program_transform_name" +# Double any \ or $. +# By default was `s,x,x', remove it if useless. +ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' +program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` + +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 +$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;} +fi + +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi + +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +if test "$cross_compiling" != no; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 +$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } +if test -z "$MKDIR_P"; then + if ${ac_cv_path_mkdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in mkdir gmkdir; do + for ac_exec_ext in '' $ac_executable_extensions; do + as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue + case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( + 'mkdir (GNU coreutils) '* | \ + 'mkdir (coreutils) '* | \ + 'mkdir (fileutils) '4.1*) + ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext + break 3;; + esac + done + done + done +IFS=$as_save_IFS + +fi + + test -d ./--version && rmdir ./--version + if test "${ac_cv_path_mkdir+set}" = set; then + MKDIR_P="$ac_cv_path_mkdir -p" + else + # As a last resort, use the slow shell script. Don't cache a + # value for MKDIR_P within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + MKDIR_P="$ac_install_sh -d" + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 +$as_echo "$MKDIR_P" >&6; } + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AWK+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null + +DEPDIR="${am__leading_dot}deps" + +ac_config_commands="$ac_config_commands depfiles" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 +$as_echo_n "checking whether ${MAKE-make} supports the include directive... " >&6; } +cat > confinc.mk << 'END' +am__doit: + @echo this is the am__doit target >confinc.out +.PHONY: am__doit +END +am__include="#" +am__quote= +# BSD make does it like this. +echo '.include "confinc.mk" # ignored' > confmf.BSD +# Other make implementations (GNU, Solaris 10, AIX) do it like this. +echo 'include confinc.mk # ignored' > confmf.GNU +_am_result=no +for s in GNU BSD; do + { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 + (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + case $?:`cat confinc.out 2>/dev/null` in #( + '0:this is the am__doit target') : + case $s in #( + BSD) : + am__include='.include' am__quote='"' ;; #( + *) : + am__include='include' am__quote='' ;; +esac ;; #( + *) : + ;; +esac + if test "$am__include" != "#"; then + _am_result="yes ($s style)" + break + fi +done +rm -f confinc.* confmf.* +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 +$as_echo "${_am_result}" >&6; } + +# Check whether --enable-dependency-tracking was given. +if test "${enable_dependency_tracking+set}" = set; then : + enableval=$enable_dependency_tracking; +fi + +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi + if test "x$enable_dependency_tracking" != xno; then + AMDEP_TRUE= + AMDEP_FALSE='#' +else + AMDEP_TRUE='#' + AMDEP_FALSE= +fi + + +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + am__isrc=' -I$(srcdir)' + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi + + +# Define the identity of the package. + PACKAGE='libtorrent-rasterbar' + VERSION='1.2.9' + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE "$PACKAGE" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define VERSION "$VERSION" +_ACEOF + +# Some tools Automake needs. + +ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} + + +AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} + + +AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} + + +AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} + + +MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} + +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# +# +mkdir_p='$(MKDIR_P)' + +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AMTAR='$${TAR-tar}' + + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar pax cpio none' + +am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' + + + + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + +depcc="$CXX" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CXX_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CXX_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CXX_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CXX_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CXX_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CXX_dependencies_compiler_type" >&6; } +CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then + am__fastdepCXX_TRUE= + am__fastdepCXX_FALSE='#' +else + am__fastdepCXX_TRUE='#' + am__fastdepCXX_FALSE= +fi + + + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: . + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5 + fi +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable maintainer-specific portions of Makefiles" >&5 +$as_echo_n "checking whether to enable maintainer-specific portions of Makefiles... " >&6; } + # Check whether --enable-maintainer-mode was given. +if test "${enable_maintainer_mode+set}" = set; then : + enableval=$enable_maintainer_mode; USE_MAINTAINER_MODE=$enableval +else + USE_MAINTAINER_MODE=yes +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $USE_MAINTAINER_MODE" >&5 +$as_echo "$USE_MAINTAINER_MODE" >&6; } + if test $USE_MAINTAINER_MODE = yes; then + MAINTAINER_MODE_TRUE= + MAINTAINER_MODE_FALSE='#' +else + MAINTAINER_MODE_TRUE='#' + MAINTAINER_MODE_FALSE= +fi + + MAINT=$MAINTAINER_MODE_TRUE + + + +$as_echo +$as_echo "Initializing Libtool:" + +case `pwd` in + *\ * | *\ *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 +$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; +esac + + + +macro_version='2.4.6' +macro_revision='2.4.6' + + + + + + + + + + + + + +ltmain=$ac_aux_dir/ltmain.sh + +# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 +$as_echo_n "checking how to print strings... " >&6; } +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "" +} + +case $ECHO in + printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5 +$as_echo "printf" >&6; } ;; + print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 +$as_echo "print -r" >&6; } ;; + *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5 +$as_echo "cat" >&6; } ;; +esac + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 +$as_echo_n "checking for fgrep... " >&6; } +if ${ac_cv_path_FGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 + then ac_cv_path_FGREP="$GREP -F" + else + if test -z "$FGREP"; then + ac_path_FGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in fgrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_FGREP" || continue +# Check for GNU ac_path_FGREP and select it if it is found. + # Check for GNU $ac_path_FGREP +case `"$ac_path_FGREP" --version 2>&1` in +*GNU*) + ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'FGREP' >> "conftest.nl" + "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_FGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_FGREP="$ac_path_FGREP" + ac_path_FGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_FGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_FGREP"; then + as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_FGREP=$FGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 +$as_echo "$ac_cv_path_FGREP" >&6; } + FGREP="$ac_cv_path_FGREP" + + +test -z "$GREP" && GREP=grep + + + + + + + + + + + + + + + + + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 +$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; } +if ${lt_cv_path_NM+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 +$as_echo "$lt_cv_path_NM" >&6; } +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + if test -n "$ac_tool_prefix"; then + for ac_prog in dumpbin "link -dump" + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DUMPBIN"; then + ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DUMPBIN=$ac_cv_prog_DUMPBIN +if test -n "$DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 +$as_echo "$DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$DUMPBIN" && break + done +fi +if test -z "$DUMPBIN"; then + ac_ct_DUMPBIN=$DUMPBIN + for ac_prog in dumpbin "link -dump" +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DUMPBIN"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN +if test -n "$ac_ct_DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 +$as_echo "$ac_ct_DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_DUMPBIN" && break +done + + if test "x$ac_ct_DUMPBIN" = x; then + DUMPBIN=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DUMPBIN=$ac_ct_DUMPBIN + fi +fi + + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 +$as_echo_n "checking the name lister ($NM) interface... " >&6; } +if ${lt_cv_nm_interface+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: output\"" >&5) + cat conftest.out >&5 + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 +$as_echo "$lt_cv_nm_interface" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + +# find the maximum length of command line arguments +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 +$as_echo_n "checking the maximum length of command line arguments... " >&6; } +if ${lt_cv_sys_max_cmd_len+:} false; then : + $as_echo_n "(cached) " >&6 +else + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac + +fi + +if test -n "$lt_cv_sys_max_cmd_len"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 +$as_echo "$lt_cv_sys_max_cmd_len" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } +fi +max_cmd_len=$lt_cv_sys_max_cmd_len + + + + + + +: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} + +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi + + + + + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 +$as_echo_n "checking how to convert $build file names to $host format... " >&6; } +if ${lt_cv_to_host_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac + +fi + +to_host_file_cmd=$lt_cv_to_host_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 +$as_echo "$lt_cv_to_host_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 +$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; } +if ${lt_cv_to_tool_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + #assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac + +fi + +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 +$as_echo "$lt_cv_to_tool_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 +$as_echo_n "checking for $LD option to reload object files... " >&6; } +if ${lt_cv_ld_reload_flag+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_reload_flag='-r' +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 +$as_echo "$lt_cv_ld_reload_flag" >&6; } +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + if test yes != "$GCC"; then + reload_cmds=false + fi + ;; + darwin*) + if test yes = "$GCC"; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. +set dummy ${ac_tool_prefix}objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OBJDUMP"; then + ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OBJDUMP=$ac_cv_prog_OBJDUMP +if test -n "$OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 +$as_echo "$OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OBJDUMP"; then + ac_ct_OBJDUMP=$OBJDUMP + # Extract the first word of "objdump", so it can be a program name with args. +set dummy objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OBJDUMP"; then + ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OBJDUMP="objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP +if test -n "$ac_ct_OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 +$as_echo "$ac_ct_OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OBJDUMP" = x; then + OBJDUMP="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OBJDUMP=$ac_ct_OBJDUMP + fi +else + OBJDUMP="$ac_cv_prog_OBJDUMP" +fi + +test -z "$OBJDUMP" && OBJDUMP=objdump + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 +$as_echo_n "checking how to recognize dependent libraries... " >&6; } +if ${lt_cv_deplibs_check_method+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[4-9]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[45]*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[3-9]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 +$as_echo "$lt_cv_deplibs_check_method" >&6; } + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + + + + + + + + + + + + + + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. +set dummy ${ac_tool_prefix}dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DLLTOOL"; then + ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DLLTOOL=$ac_cv_prog_DLLTOOL +if test -n "$DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 +$as_echo "$DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DLLTOOL"; then + ac_ct_DLLTOOL=$DLLTOOL + # Extract the first word of "dlltool", so it can be a program name with args. +set dummy dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DLLTOOL"; then + ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DLLTOOL="dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL +if test -n "$ac_ct_DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 +$as_echo "$ac_ct_DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DLLTOOL" = x; then + DLLTOOL="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DLLTOOL=$ac_ct_DLLTOOL + fi +else + DLLTOOL="$ac_cv_prog_DLLTOOL" +fi + +test -z "$DLLTOOL" && DLLTOOL=dlltool + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 +$as_echo_n "checking how to associate runtime and link libraries... " >&6; } +if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 +$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; } +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + + + + + + + + +if test -n "$ac_tool_prefix"; then + for ac_prog in ar + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AR" && break + done +fi +if test -z "$AR"; then + ac_ct_AR=$AR + for ac_prog in ar +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_AR" && break +done + + if test "x$ac_ct_AR" = x; then + AR="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +fi + +: ${AR=ar} +: ${AR_FLAGS=cr} + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 +$as_echo_n "checking for archiver @FILE support... " >&6; } +if ${lt_cv_ar_at_file+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ar_at_file=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 +$as_echo "$lt_cv_ar_at_file" >&6; } + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +test -z "$STRIP" && STRIP=: + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +test -z "$RANLIB" && RANLIB=: + + + + + + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# Check for command to grab the raw symbol name followed by C symbol from nm. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 +$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } +if ${lt_cv_sys_global_symbol_pipe+:} false; then : + $as_echo_n "(cached) " >&6 +else + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[ABCDGISTW]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[ABCDEGRST]' + fi + ;; +irix* | nonstopux*) + symcode='[BCDEGRST]' + ;; +osf*) + symcode='[BCDEGQRST]' + ;; +solaris*) + symcode='[BDRT]' + ;; +sco3.2v5*) + symcode='[DT]' + ;; +sysv4.2uw2*) + symcode='[DT]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[ABDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[ABCDGIRSTW]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK '"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Now try to grab the symbols. + nlist=conftest.nm + $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5 + if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done + +fi + +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 +$as_echo "failed" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5 +$as_echo "ok" >&6; } +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 +$as_echo_n "checking for sysroot... " >&6; } + +# Check whether --with-sysroot was given. +if test "${with_sysroot+set}" = set; then : + withval=$with_sysroot; +else + with_sysroot=no +fi + + +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 +$as_echo "$with_sysroot" >&6; } + as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 + ;; +esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 +$as_echo "${lt_sysroot:-no}" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 +$as_echo_n "checking for a working dd... " >&6; } +if ${ac_cv_path_lt_DD+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +if test -z "$lt_DD"; then + ac_path_lt_DD_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in dd; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_lt_DD="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_lt_DD" || continue +if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi + $ac_path_lt_DD_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_lt_DD"; then + : + fi +else + ac_cv_path_lt_DD=$lt_DD +fi + +rm -f conftest.i conftest2.i conftest.out +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 +$as_echo "$ac_cv_path_lt_DD" >&6; } + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 +$as_echo_n "checking how to truncate binary pipes... " >&6; } +if ${lt_cv_truncate_bin+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 +$as_echo "$lt_cv_truncate_bin" >&6; } + + + + + + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then : + enableval=$enable_libtool_lock; +fi + +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 +$as_echo_n "checking whether the C compiler needs -belf... " >&6; } +if ${lt_cv_cc_needs_belf+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_cc_needs_belf=yes +else + lt_cv_cc_needs_belf=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 +$as_echo "$lt_cv_cc_needs_belf" >&6; } + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. +set dummy ${ac_tool_prefix}mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$MANIFEST_TOOL"; then + ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL +if test -n "$MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 +$as_echo "$MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_MANIFEST_TOOL"; then + ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL + # Extract the first word of "mt", so it can be a program name with args. +set dummy mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_MANIFEST_TOOL"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL +if test -n "$ac_ct_MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 +$as_echo "$ac_ct_MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_MANIFEST_TOOL" = x; then + MANIFEST_TOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL + fi +else + MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" +fi + +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 +$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } +if ${lt_cv_path_mainfest_tool+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&5 + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5 +$as_echo "$lt_cv_path_mainfest_tool" >&6; } +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi + + + + + + + case $host_os in + rhapsody* | darwin*) + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. +set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DSYMUTIL"; then + ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DSYMUTIL=$ac_cv_prog_DSYMUTIL +if test -n "$DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 +$as_echo "$DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DSYMUTIL"; then + ac_ct_DSYMUTIL=$DSYMUTIL + # Extract the first word of "dsymutil", so it can be a program name with args. +set dummy dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DSYMUTIL"; then + ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL +if test -n "$ac_ct_DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 +$as_echo "$ac_ct_DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DSYMUTIL" = x; then + DSYMUTIL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DSYMUTIL=$ac_ct_DSYMUTIL + fi +else + DSYMUTIL="$ac_cv_prog_DSYMUTIL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. +set dummy ${ac_tool_prefix}nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NMEDIT"; then + ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +NMEDIT=$ac_cv_prog_NMEDIT +if test -n "$NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 +$as_echo "$NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_NMEDIT"; then + ac_ct_NMEDIT=$NMEDIT + # Extract the first word of "nmedit", so it can be a program name with args. +set dummy nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_NMEDIT"; then + ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_NMEDIT="nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT +if test -n "$ac_ct_NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 +$as_echo "$ac_ct_NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_NMEDIT" = x; then + NMEDIT=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + NMEDIT=$ac_ct_NMEDIT + fi +else + NMEDIT="$ac_cv_prog_NMEDIT" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. +set dummy ${ac_tool_prefix}lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$LIPO"; then + ac_cv_prog_LIPO="$LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_LIPO="${ac_tool_prefix}lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +LIPO=$ac_cv_prog_LIPO +if test -n "$LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 +$as_echo "$LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_LIPO"; then + ac_ct_LIPO=$LIPO + # Extract the first word of "lipo", so it can be a program name with args. +set dummy lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_LIPO"; then + ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_LIPO="lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO +if test -n "$ac_ct_LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 +$as_echo "$ac_ct_LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_LIPO" = x; then + LIPO=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + LIPO=$ac_ct_LIPO + fi +else + LIPO="$ac_cv_prog_LIPO" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL"; then + ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL="${ac_tool_prefix}otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL=$ac_cv_prog_OTOOL +if test -n "$OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 +$as_echo "$OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL"; then + ac_ct_OTOOL=$OTOOL + # Extract the first word of "otool", so it can be a program name with args. +set dummy otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL"; then + ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL="otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL +if test -n "$ac_ct_OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 +$as_echo "$ac_ct_OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL" = x; then + OTOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL=$ac_ct_OTOOL + fi +else + OTOOL="$ac_cv_prog_OTOOL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL64"; then + ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL64=$ac_cv_prog_OTOOL64 +if test -n "$OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 +$as_echo "$OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL64"; then + ac_ct_OTOOL64=$OTOOL64 + # Extract the first word of "otool64", so it can be a program name with args. +set dummy otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL64"; then + ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL64="otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 +if test -n "$ac_ct_OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 +$as_echo "$ac_ct_OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL64" = x; then + OTOOL64=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL64=$ac_ct_OTOOL64 + fi +else + OTOOL64="$ac_cv_prog_OTOOL64" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 +$as_echo_n "checking for -single_module linker flag... " >&6; } +if ${lt_cv_apple_cc_single_mod+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&5 + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&5 + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 +$as_echo "$lt_cv_apple_cc_single_mod" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 +$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } +if ${lt_cv_ld_exported_symbols_list+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_ld_exported_symbols_list=yes +else + lt_cv_ld_exported_symbols_list=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 +$as_echo "$lt_cv_ld_exported_symbols_list" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 +$as_echo_n "checking for -force_load linker flag... " >&6; } +if ${lt_cv_ld_force_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 + echo "$AR cr libconftest.a conftest.o" >&5 + $AR cr libconftest.a conftest.o 2>&5 + echo "$RANLIB libconftest.a" >&5 + $RANLIB libconftest.a 2>&5 + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&5 + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&5 + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 +$as_echo "$lt_cv_ld_force_load" >&6; } + case $host_os in + rhapsody* | darwin1.[012]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[012][,.]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in dlfcn.h +do : + ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default +" +if test "x$ac_cv_header_dlfcn_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DLFCN_H 1 +_ACEOF + +fi + +done + + + + +func_stripname_cnf () +{ + case $2 in + .*) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%\\\\$2\$%%"`;; + *) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%$2\$%%"`;; + esac +} # func_stripname_cnf + + + + + +# Set options + + + + enable_dlopen=no + + + enable_win32_dll=no + + + # Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_shared=yes +fi + + + + + + + + + + # Check whether --enable-static was given. +if test "${enable_static+set}" = set; then : + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_static=yes +fi + + + + + + + + + + +# Check whether --with-pic was given. +if test "${with_pic+set}" = set; then : + withval=$with_pic; lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + pic_mode=default +fi + + + + + + + + + # Check whether --enable-fast-install was given. +if test "${enable_fast_install+set}" = set; then : + enableval=$enable_fast_install; p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_fast_install=yes +fi + + + + + + + + + shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[5-9]*,yes) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 +$as_echo_n "checking which variant of shared library versioning to provide... " >&6; } + +# Check whether --with-aix-soname was given. +if test "${with_aix_soname+set}" = set; then : + withval=$with_aix_soname; case $withval in + aix|svr4|both) + ;; + *) + as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname +else + if ${lt_cv_with_aix_soname+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_with_aix_soname=aix +fi + + with_aix_soname=$lt_cv_with_aix_soname +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 +$as_echo "$with_aix_soname" >&6; } + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + + + + + + + + + + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +test -z "$LN_S" && LN_S="ln -s" + + + + + + + + + + + + + + +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 +$as_echo_n "checking for objdir... " >&6; } +if ${lt_cv_objdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 +$as_echo "$lt_cv_objdir" >&6; } +objdir=$lt_cv_objdir + + + + + +cat >>confdefs.h <<_ACEOF +#define LT_OBJDIR "$lt_cv_objdir/" +_ACEOF + + + + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +func_cc_basename $compiler +cc_basename=$func_cc_basename_result + + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 +$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/${ac_tool_prefix}file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + + +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5 +$as_echo_n "checking for file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + MAGIC_CMD=: + fi +fi + + fi + ;; +esac + +# Use C for the default configuration in the libtool script + +lt_save_CC=$CC +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +objext=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + +lt_prog_compiler_no_builtin_flag= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; + *) + lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then + lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" +else + : +fi + +fi + + + + + + + lt_prog_compiler_wl= +lt_prog_compiler_pic= +lt_prog_compiler_static= + + + if test yes = "$GCC"; then + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_static='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + fi + lt_prog_compiler_pic='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic=-Kconform_pic + fi + ;; + + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + lt_prog_compiler_wl='-Xlinker ' + if test -n "$lt_prog_compiler_pic"; then + lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + else + lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-static' + ;; + # flang / f18. f95 an alias for gfortran or flang on Debian + flang* | f18* | f95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='--shared' + lt_prog_compiler_static='--static' + ;; + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-qpic' + lt_prog_compiler_static='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='' + ;; + *Sun\ F* | *Sun*Fortran*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Wl,' + ;; + *Intel*\ [CF]*Compiler*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + *Portland\ Group*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + lt_prog_compiler_wl='-Qoption ld ';; + *) + lt_prog_compiler_wl='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl='-Qoption ld ' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic='-Kconform_pic' + lt_prog_compiler_static='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_can_build_shared=no + ;; + + uts4*) + lt_prog_compiler_pic='-pic' + lt_prog_compiler_static='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared=no + ;; + esac + fi + +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic= + ;; + *) + lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic=$lt_prog_compiler_pic +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 +$as_echo "$lt_cv_prog_compiler_pic" >&6; } +lt_prog_compiler_pic=$lt_cv_prog_compiler_pic + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } +if ${lt_cv_prog_compiler_pic_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_pic_works"; then + case $lt_prog_compiler_pic in + "" | " "*) ;; + *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; + esac +else + lt_prog_compiler_pic= + lt_prog_compiler_can_build_shared=no +fi + +fi + + + + + + + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works=yes + fi + else + lt_cv_prog_compiler_static_works=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 +$as_echo "$lt_cv_prog_compiler_static_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_static_works"; then + : +else + lt_prog_compiler_static= +fi + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + +hard_links=nottested +if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test no = "$hard_links"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag= + always_export_symbols=no + archive_cmds= + archive_expsym_cmds= + compiler_needs_object=no + enable_shared_with_static_runtimes=no + export_dynamic_flag_spec= + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + hardcode_automatic=no + hardcode_direct=no + hardcode_direct_absolute=no + hardcode_libdir_flag_spec= + hardcode_libdir_separator= + hardcode_minus_L=no + hardcode_shlibpath_var=unsupported + inherit_rpath=no + link_all_deplibs=unknown + module_cmds= + module_expsym_cmds= + old_archive_from_new_cmds= + old_archive_from_expsyms_cmds= + thread_safe_flag_spec= + whole_archive_flag_spec= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + link_all_deplibs=no + ;; + esac + + ld_shlibs=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; + *\ \(GNU\ Binutils\)\ [3-9]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + export_dynamic_flag_spec='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + whole_archive_flag_spec= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/(^)\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + export_dynamic_flag_spec='$wl--export-all-symbols' + allow_undefined_flag=unsupported + always_export_symbols=no + enable_shared_with_static_runtimes=yes + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs=no + fi + ;; + + haiku*) + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + link_all_deplibs=yes + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + interix[3-9]*) + hardcode_direct=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + whole_archive_flag_spec= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + export_dynamic_flag_spec='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + ld_shlibs=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test no = "$ld_shlibs"; then + runpath_var= + hardcode_libdir_flag_spec= + export_dynamic_flag_spec= + whole_archive_flag_spec= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix[4-9]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds='' + hardcode_direct=yes + hardcode_direct_absolute=yes + hardcode_libdir_separator=':' + link_all_deplibs=yes + file_list_spec='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + hardcode_direct=no + hardcode_direct_absolute=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + export_dynamic_flag_spec='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' + allow_undefined_flag="-z nodefs" + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag=' $wl-bernotok' + allow_undefined_flag=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec='$convenience' + fi + archive_cmds_need_lc=yes + archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + bsdi[45]*) + export_dynamic_flag_spec=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + always_export_symbols=yes + file_list_spec='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, )='true' + enable_shared_with_static_runtimes=yes + exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + old_postinstall_cmds='chmod 644 $oldlib' + postlink_cmds='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_from_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' + enable_shared_with_static_runtimes=yes + ;; + esac + ;; + + darwin* | rhapsody*) + + + archive_cmds_need_lc=no + hardcode_direct=no + hardcode_automatic=yes + hardcode_shlibpath_var=unsupported + if test yes = "$lt_cv_ld_force_load"; then + whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec='' + fi + link_all_deplibs=yes + allow_undefined_flag=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + archive_expsym_cmds="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + module_expsym_cmds="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + + else + ld_shlibs=no + fi + + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9*) + if test yes = "$GCC"; then + archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + export_dynamic_flag_spec='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 +$as_echo_n "checking if $CC understands -b... " >&6; } +if ${lt_cv_prog_compiler__b+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler__b=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -b" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler__b=yes + fi + else + lt_cv_prog_compiler__b=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 +$as_echo "$lt_cv_prog_compiler__b" >&6; } + +if test yes = "$lt_cv_prog_compiler__b"; then + archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' +else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' +fi + + ;; + esac + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + hardcode_shlibpath_var=no + ;; + *) + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 +$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; } +if ${lt_cv_irix_exported_symbol+:} false; then : + $as_echo_n "(cached) " >&6 +else + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int foo (void) { return 0; } +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_irix_exported_symbol=yes +else + lt_cv_irix_exported_symbol=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 +$as_echo "$lt_cv_irix_exported_symbol" >&6; } + if test yes = "$lt_cv_irix_exported_symbol"; then + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + link_all_deplibs=no + else + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + inherit_rpath=yes + link_all_deplibs=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + ld_shlibs=yes + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + newsos6) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + hardcode_shlibpath_var=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + hardcode_shlibpath_var=no + hardcode_direct_absolute=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + else + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + fi + else + ld_shlibs=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + osf3*) + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + archive_cmds_need_lc='no' + hardcode_libdir_separator=: + ;; + + solaris*) + no_undefined_flag=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + whole_archive_flag_spec='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds='$CC -r -o $output$reload_objs' + hardcode_direct=no + ;; + motorola) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag='$wl-z,text' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag='$wl-z,text' + allow_undefined_flag='$wl-z,nodefs' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-R,$libdir' + hardcode_libdir_separator=':' + link_all_deplibs=yes + export_dynamic_flag_spec='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + export_dynamic_flag_spec='$wl-Blargedynsym' + ;; + esac + fi + fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 +$as_echo "$ld_shlibs" >&6; } +test no = "$ld_shlibs" && can_build_shared=no + +with_gnu_ld=$with_gnu_ld + + + + + + + + + + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $archive_cmds in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl + pic_flag=$lt_prog_compiler_pic + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag + allow_undefined_flag= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc=no + else + lt_cv_archive_cmds_need_lc=yes + fi + allow_undefined_flag=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc" >&6; } + archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[lt_foo]++; } + if (lt_freq[lt_foo] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([A-Za-z]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + + + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a(lib.so.V)' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + hardcode_libdir_flag_spec='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || + test -n "$runpath_var" || + test yes = "$hardcode_automatic"; then + + # We can hardcode non-existent directories. + if test no != "$hardcode_direct" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && + test no != "$hardcode_minus_L"; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 +$as_echo "$hardcode_action" >&6; } + +if test relink = "$hardcode_action" || + test yes = "$inherit_rpath"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + +fi + + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + lt_cv_dlopen=shl_load +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + lt_cv_dlopen=dlopen +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 +$as_echo_n "checking whether a program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 +$as_echo "$lt_cv_dlopen_self" >&6; } + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 +$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self_static+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self_static=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self_static=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 +$as_echo "$lt_cv_dlopen_self_static" >&6; } + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + + + + + + + + + + + + + + + + + +striplib= +old_striplib= +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 +$as_echo_n "checking whether stripping libraries is possible... " >&6; } +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; + esac +fi + + + + + + + + + + + + + # Report what library types will actually be built + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[4-9]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + + + + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC=$lt_save_CC + + if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 +$as_echo_n "checking how to run the C++ preprocessor... " >&6; } +if test -z "$CXXCPP"; then + if ${ac_cv_prog_CXXCPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CXXCPP needs to be expanded + for CXXCPP in "$CXX -E" "/lib/cpp" + do + ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CXXCPP=$CXXCPP + +fi + CXXCPP=$ac_cv_prog_CXXCPP +else + ac_cv_prog_CXXCPP=$CXXCPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 +$as_echo "$CXXCPP" >&6; } +ac_preproc_ok=false +for ac_cxx_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_cxx_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +else + _lt_caught_CXX_error=yes +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +archive_cmds_need_lc_CXX=no +allow_undefined_flag_CXX= +always_export_symbols_CXX=no +archive_expsym_cmds_CXX= +compiler_needs_object_CXX=no +export_dynamic_flag_spec_CXX= +hardcode_direct_CXX=no +hardcode_direct_absolute_CXX=no +hardcode_libdir_flag_spec_CXX= +hardcode_libdir_separator_CXX= +hardcode_minus_L_CXX=no +hardcode_shlibpath_var_CXX=unsupported +hardcode_automatic_CXX=no +inherit_rpath_CXX=no +module_cmds_CXX= +module_expsym_cmds_CXX= +link_all_deplibs_CXX=unknown +old_archive_cmds_CXX=$old_archive_cmds +reload_flag_CXX=$reload_flag +reload_cmds_CXX=$reload_cmds +no_undefined_flag_CXX= +whole_archive_flag_spec_CXX= +enable_shared_with_static_runtimes_CXX=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +objext_CXX=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + + # save warnings/boilerplate of simple test code + ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + + ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + compiler_CXX=$CC + func_cc_basename $compiler +cc_basename=$func_cc_basename_result + + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin' + else + lt_prog_compiler_no_builtin_flag_CXX= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + archive_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + whole_archive_flag_spec_CXX= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + ld_shlibs_CXX=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aix[4-9]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds_CXX='' + hardcode_direct_CXX=yes + hardcode_direct_absolute_CXX=yes + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + file_list_spec_CXX='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + hardcode_direct_CXX=no + hardcode_direct_absolute_CXX=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct_CXX=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L_CXX=yes + hardcode_libdir_flag_spec_CXX='-L$libdir' + hardcode_libdir_separator_CXX= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + export_dynamic_flag_spec_CXX='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + always_export_symbols_CXX=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + no_undefined_flag_CXX='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath__CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath__CXX +fi + + hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" + + archive_expsym_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + hardcode_libdir_flag_spec_CXX='$wl-R $libdir:/usr/lib:/lib' + allow_undefined_flag_CXX="-z nodefs" + archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath__CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath__CXX"; then + lt_cv_aix_libpath__CXX=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath__CXX +fi + + hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag_CXX=' $wl-bernotok' + allow_undefined_flag_CXX=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec_CXX='$convenience' + fi + archive_cmds_need_lc_CXX=yes + archive_expsym_cmds_CXX='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag_CXX=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + ld_shlibs_CXX=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec_CXX=' ' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=yes + file_list_spec_CXX='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, CXX)='true' + enable_shared_with_static_runtimes_CXX=yes + # Don't use ranlib + old_postinstall_cmds_CXX='chmod 644 $oldlib' + postlink_cmds_CXX='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec_CXX='-L$libdir' + export_dynamic_flag_spec_CXX='$wl--export-all-symbols' + allow_undefined_flag_CXX=unsupported + always_export_symbols_CXX=no + enable_shared_with_static_runtimes_CXX=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs_CXX=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + + + archive_cmds_need_lc_CXX=no + hardcode_direct_CXX=no + hardcode_automatic_CXX=yes + hardcode_shlibpath_var_CXX=unsupported + if test yes = "$lt_cv_ld_force_load"; then + whole_archive_flag_spec_CXX='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec_CXX='' + fi + link_all_deplibs_CXX=yes + allow_undefined_flag_CXX=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + archive_expsym_cmds_CXX="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + module_expsym_cmds_CXX="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + if test yes != "$lt_cv_apple_cc_single_mod"; then + archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + archive_expsym_cmds_CXX="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi + + else + ld_shlibs_CXX=no + fi + + ;; + + os2*) + hardcode_libdir_flag_spec_CXX='-L$libdir' + hardcode_minus_L_CXX=yes + allow_undefined_flag_CXX=unsupported + shrext_cmds=.dll + archive_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds_CXX='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes_CXX=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + ld_shlibs_CXX=no + ;; + + freebsd-elf*) + archive_cmds_need_lc_CXX=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + ld_shlibs_CXX=yes + ;; + + haiku*) + archive_cmds_CXX='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + link_all_deplibs_CXX=yes + ;; + + hpux9*) + hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' + hardcode_libdir_separator_CXX=: + export_dynamic_flag_spec_CXX='$wl-E' + hardcode_direct_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + archive_cmds_CXX='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + archive_cmds_CXX='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' + hardcode_libdir_separator_CXX=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + export_dynamic_flag_spec_CXX='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + ;; + *) + hardcode_direct_CXX=yes + hardcode_direct_absolute_CXX=yes + hardcode_minus_L_CXX=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + archive_cmds_CXX='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + interix[3-9]*) + hardcode_direct_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + export_dynamic_flag_spec_CXX='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds_CXX='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + link_all_deplibs_CXX=yes + ;; + esac + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + hardcode_libdir_separator_CXX=: + inherit_rpath_CXX=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + archive_cmds_need_lc_CXX=no + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [1-5].* | *pgcpp\ [1-5].*) + prelink_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + old_archive_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + archive_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + hardcode_libdir_flag_spec_CXX='$wl--rpath $wl$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + whole_archive_flag_spec_CXX='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + export_dynamic_flag_spec_CXX='$wl--export-dynamic' + archive_cmds_CXX='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds_CXX='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + hardcode_libdir_flag_spec_CXX='-R$libdir' + whole_archive_flag_spec_CXX='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object_CXX=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + ld_shlibs_CXX=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + hardcode_direct_CXX=yes + hardcode_shlibpath_var_CXX=no + hardcode_direct_absolute_CXX=yes + archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + export_dynamic_flag_spec_CXX='$wl-E' + whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + ld_shlibs_CXX=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' + hardcode_libdir_separator_CXX=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + cxx*) + case $host in + osf3*) + allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' + archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + ;; + *) + allow_undefined_flag_CXX=' -expect_unresolved \*' + archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + hardcode_libdir_flag_spec_CXX='-rpath $libdir' + ;; + esac + + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + archive_cmds_CXX='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' + hardcode_libdir_separator_CXX=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + + else + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + archive_cmds_need_lc_CXX=yes + no_undefined_flag_CXX=' -zdefs' + archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + hardcode_libdir_flag_spec_CXX='-R$libdir' + hardcode_shlibpath_var_CXX=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract' + ;; + esac + link_all_deplibs_CXX=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + no_undefined_flag_CXX=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + archive_cmds_CXX='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + fi + + hardcode_libdir_flag_spec_CXX='$wl-R $wl$libdir' + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + whole_archive_flag_spec_CXX='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag_CXX='$wl-z,text' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag_CXX='$wl-z,text' + allow_undefined_flag_CXX='$wl-z,nodefs' + archive_cmds_need_lc_CXX=no + hardcode_shlibpath_var_CXX=no + hardcode_libdir_flag_spec_CXX='$wl-R,$libdir' + hardcode_libdir_separator_CXX=':' + link_all_deplibs_CXX=yes + export_dynamic_flag_spec_CXX='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + old_archive_cmds_CXX='$CC -Tprelink_objects $oldobjs~ + '"$old_archive_cmds_CXX" + reload_cmds_CXX='$CC -Tprelink_objects $reload_objs~ + '"$reload_cmds_CXX" + ;; + *) + archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + + *) + # FIXME: insert proper C++ library support + ld_shlibs_CXX=no + ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } + test no = "$ld_shlibs_CXX" && can_build_shared=no + + GCC_CXX=$GXX + LD_CXX=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + # Dependencies to place before and after the object being linked: +predep_objects_CXX= +postdep_objects_CXX= +predeps_CXX= +postdeps_CXX= +compiler_lib_search_path_CXX= + +cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF + + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$compiler_lib_search_path_CXX"; then + compiler_lib_search_path_CXX=$prev$p + else + compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$postdeps_CXX"; then + postdeps_CXX=$prev$p + else + postdeps_CXX="${postdeps_CXX} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$predep_objects_CXX"; then + predep_objects_CXX=$p + else + predep_objects_CXX="$predep_objects_CXX $p" + fi + else + if test -z "$postdep_objects_CXX"; then + postdep_objects_CXX=$p + else + postdep_objects_CXX="$postdep_objects_CXX $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling CXX test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +case $host_os in +interix[3-9]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + predep_objects_CXX= + postdep_objects_CXX= + postdeps_CXX= + ;; +esac + + +case " $postdeps_CXX " in +*" -lc "*) archive_cmds_need_lc_CXX=no ;; +esac + compiler_lib_search_dirs_CXX= +if test -n "${compiler_lib_search_path_CXX}"; then + compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lt_prog_compiler_wl_CXX= +lt_prog_compiler_pic_CXX= +lt_prog_compiler_static_CXX= + + + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + fi + lt_prog_compiler_pic_CXX='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic_CXX='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static_CXX='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic_CXX='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + lt_prog_compiler_pic_CXX= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static_CXX= + ;; + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic_CXX=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic_CXX='-fPIC -shared' + ;; + *) + lt_prog_compiler_pic_CXX='-fPIC' + ;; + esac + else + case $host_os in + aix[4-9]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static_CXX='-Bstatic' + else + lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic_CXX='-DDLL_EXPORT' + ;; + dgux*) + case $cc_basename in + ec++*) + lt_prog_compiler_pic_CXX='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + lt_prog_compiler_pic_CXX='+Z' + fi + ;; + aCC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic_CXX='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_static_CXX='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + lt_prog_compiler_wl_CXX='--backend -Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fPIC' + lt_prog_compiler_static_CXX='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-fpic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + xlc* | xlC* | bgxl[cC]* | mpixl[cC]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-qpic' + lt_prog_compiler_static_CXX='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + lt_prog_compiler_pic_CXX='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd* | netbsdelf*-gnu) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic_CXX='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + lt_prog_compiler_wl_CXX='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + lt_prog_compiler_pic_CXX='-pic' + ;; + cxx*) + # Digital/Compaq C++ + lt_prog_compiler_wl_CXX='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + lt_prog_compiler_pic_CXX= + lt_prog_compiler_static_CXX='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + lt_prog_compiler_wl_CXX='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + lt_prog_compiler_pic_CXX='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + lt_prog_compiler_pic_CXX='-pic' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + lcc*) + # Lucid + lt_prog_compiler_pic_CXX='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + lt_prog_compiler_wl_CXX='-Wl,' + lt_prog_compiler_pic_CXX='-KPIC' + lt_prog_compiler_static_CXX='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + lt_prog_compiler_pic_CXX='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + lt_prog_compiler_can_build_shared_CXX=no + ;; + esac + fi + +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic_CXX= + ;; + *) + lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_CXX" >&6; } +lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic_CXX"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } +if ${lt_cv_prog_compiler_pic_works_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works_CXX=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works_CXX=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works_CXX" >&6; } + +if test yes = "$lt_cv_prog_compiler_pic_works_CXX"; then + case $lt_prog_compiler_pic_CXX in + "" | " "*) ;; + *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; + esac +else + lt_prog_compiler_pic_CXX= + lt_prog_compiler_can_build_shared_CXX=no +fi + +fi + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works_CXX=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works_CXX=yes + fi + else + lt_cv_prog_compiler_static_works_CXX=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_static_works_CXX" >&6; } + +if test yes = "$lt_cv_prog_compiler_static_works_CXX"; then + : +else + lt_prog_compiler_static_CXX= +fi + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o_CXX=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o_CXX=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 +$as_echo "$lt_cv_prog_compiler_c_o_CXX" >&6; } + + + + +hard_links=nottested +if test no = "$lt_cv_prog_compiler_c_o_CXX" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test no = "$hard_links"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + case $host_os in + aix[4-9]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds_CXX='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + export_symbols_cmds_CXX=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + exclude_expsyms_CXX='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms_CXX='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + ;; + esac + ;; + linux* | k*bsd*-gnu | gnu*) + link_all_deplibs_CXX=no + ;; + *) + export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 +$as_echo "$ld_shlibs_CXX" >&6; } +test no = "$ld_shlibs_CXX" && can_build_shared=no + +with_gnu_ld_CXX=$with_gnu_ld + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc_CXX" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc_CXX=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $archive_cmds_CXX in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc_CXX+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl_CXX + pic_flag=$lt_prog_compiler_pic_CXX + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag_CXX + allow_undefined_flag_CXX= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc_CXX=no + else + lt_cv_archive_cmds_need_lc_CXX=yes + fi + allow_undefined_flag_CXX=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_CXX" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc_CXX" >&6; } + archive_cmds_need_lc_CXX=$lt_cv_archive_cmds_need_lc_CXX + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + + + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a(lib.so.V)' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + hardcode_libdir_flag_spec_CXX='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_CXX\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_CXX\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action_CXX= +if test -n "$hardcode_libdir_flag_spec_CXX" || + test -n "$runpath_var_CXX" || + test yes = "$hardcode_automatic_CXX"; then + + # We can hardcode non-existent directories. + if test no != "$hardcode_direct_CXX" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, CXX)" && + test no != "$hardcode_minus_L_CXX"; then + # Linking always hardcodes the temporary library directory. + hardcode_action_CXX=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action_CXX=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action_CXX=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_CXX" >&5 +$as_echo "$hardcode_action_CXX" >&6; } + +if test relink = "$hardcode_action_CXX" || + test yes = "$inherit_rpath_CXX"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + + + + + + + + + + + + ac_config_commands="$ac_config_commands libtool" + + + + +# Only expand once: + + + +if test "$SYS" = linux; then : + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __ANDROID__ + # error Not Android + #endif + +int +main () +{ +; + ; + return 0; +} + +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + + HAVE_ANDROID="1" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +fi + if test "${HAVE_ANDROID}" = "1"; then + HAVE_ANDROID_TRUE= + HAVE_ANDROID_FALSE='#' +else + HAVE_ANDROID_TRUE='#' + HAVE_ANDROID_FALSE= +fi + + +HAVE_WINDOWS=0 +case "${host_os}" in + *mingw32* | *cygwin*) + HAVE_WINDOWS=1 + ;; +esac + if test "${HAVE_WINDOWS}" = "1"; then + HAVE_WINDOWS_TRUE= + HAVE_WINDOWS_FALSE='#' +else + HAVE_WINDOWS_TRUE='#' + HAVE_WINDOWS_FALSE= +fi + + +############################################################################### +# Checking for needed base libraries +############################################################################### + +$as_echo +$as_echo "Checking for posix thread support:" + + + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + if test "x$PTHREAD_CC" != "x"; then : + CC="$PTHREAD_CC" +fi + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS" >&5 +$as_echo_n "checking for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_join (); +int +main () +{ +return pthread_join (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ax_pthread_ok=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 +$as_echo "$ax_pthread_ok" >&6; } + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "AX_PTHREAD_ZOS_MISSING" >/dev/null 2>&1; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support." >&5 +$as_echo "$as_me: WARNING: IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support." >&2;} +fi +rm -f conftest* + + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC is Clang" >&5 +$as_echo_n "checking whether $CC is Clang... " >&6; } +if ${ax_cv_PTHREAD_CLANG+:} false; then : + $as_echo_n "(cached) " >&6 +else + ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "AX_PTHREAD_CC_IS_CLANG" >/dev/null 2>&1; then : + ax_cv_PTHREAD_CLANG=yes +fi +rm -f conftest* + + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_CLANG" >&5 +$as_echo "$ax_cv_PTHREAD_CLANG" >&6; } +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +if test "x$GCC" = "xyes"; then : + ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags" +fi + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +if test "x$ax_pthread_clang" = "xyes"; then : + ax_pthread_flags="-pthread,-lpthread -pthread" +fi + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +if test "x$ax_pthread_check_macro" = "x--"; then : + ax_pthread_check_cond=0 +else + ax_pthread_check_cond="!defined($ax_pthread_check_macro)" +fi + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5 +$as_echo_n "checking whether pthreads work without any flags... " >&6; } + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with \"$PTHREAD_CFLAGS\" and \"$PTHREAD_LIBS\"" >&5 +$as_echo_n "checking whether pthreads work with \"$PTHREAD_CFLAGS\" and \"$PTHREAD_LIBS\"... " >&6; } + ;; + + -*) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $ax_pthread_try_flag" >&5 +$as_echo_n "checking whether pthreads work with $ax_pthread_try_flag... " >&6; } + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + # Extract the first word of "pthread-config", so it can be a program name with args. +set dummy pthread-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ax_pthread_config+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ax_pthread_config"; then + ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ax_pthread_config="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_ax_pthread_config" && ac_cv_prog_ax_pthread_config="no" +fi +fi +ax_pthread_config=$ac_cv_prog_ax_pthread_config +if test -n "$ax_pthread_config"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5 +$as_echo "$ax_pthread_config" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test "x$ax_pthread_config" = "xno"; then : + continue +fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$ax_pthread_try_flag" >&5 +$as_echo_n "checking for the pthreads library -l$ax_pthread_try_flag... " >&6; } + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; } +int +main () +{ +pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */ + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ax_pthread_ok=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 +$as_echo "$ax_pthread_ok" >&6; } + if test "x$ax_pthread_ok" = "xyes"; then : + break +fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether Clang needs flag to prevent \"argument unused\" warning when linking with -pthread" >&5 +$as_echo_n "checking whether Clang needs flag to prevent \"argument unused\" warning when linking with -pthread... " >&6; } +if ${ax_cv_PTHREAD_CLANG_NO_WARN_FLAG+:} false; then : + $as_echo_n "(cached) " >&6 +else + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + if test "x$ax_pthread_try" = "xunknown"; then : + break +fi + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(void){return 0;} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_link="$ax_pthread_2step_ac_link" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(void){return 0;} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + break +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + if test "x$ax_pthread_try" = "x"; then : + ax_pthread_try=no +fi + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" >&5 +$as_echo "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" >&6; } + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5 +$as_echo_n "checking for joinable pthread attribute... " >&6; } +if ${ax_cv_PTHREAD_JOINABLE_ATTR+:} false; then : + $as_echo_n "(cached) " >&6 +else + ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +int attr = $ax_pthread_attr; return attr /* ; */ + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + done + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_JOINABLE_ATTR" >&5 +$as_echo "$ax_cv_PTHREAD_JOINABLE_ATTR" >&6; } + if test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"; then : + +cat >>confdefs.h <<_ACEOF +#define PTHREAD_CREATE_JOINABLE $ax_cv_PTHREAD_JOINABLE_ATTR +_ACEOF + + ax_pthread_joinable_attr_defined=yes + +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether more special flags are required for pthreads" >&5 +$as_echo_n "checking whether more special flags are required for pthreads... " >&6; } +if ${ax_cv_PTHREAD_SPECIAL_FLAGS+:} false; then : + $as_echo_n "(cached) " >&6 +else + ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_SPECIAL_FLAGS" >&5 +$as_echo "$ax_cv_PTHREAD_SPECIAL_FLAGS" >&6; } + if test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"; then : + PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5 +$as_echo_n "checking for PTHREAD_PRIO_INHERIT... " >&6; } +if ${ax_cv_PTHREAD_PRIO_INHERIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +int i = PTHREAD_PRIO_INHERIT; + return i; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ax_cv_PTHREAD_PRIO_INHERIT=yes +else + ax_cv_PTHREAD_PRIO_INHERIT=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5 +$as_echo "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; } + if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"; then : + +$as_echo "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h + + ax_pthread_prio_inherit_defined=yes + +fi + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + case "x/$CC" in #( + x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6) : + #handle absolute path differently from PATH based program lookup + case "x$CC" in #( + x/*) : + if as_fn_executable_p ${CC}_r; then : + PTHREAD_CC="${CC}_r" +fi ;; #( + *) : + for ac_prog in ${CC}_r +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_PTHREAD_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$PTHREAD_CC"; then + ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_PTHREAD_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +PTHREAD_CC=$ac_cv_prog_PTHREAD_CC +if test -n "$PTHREAD_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5 +$as_echo "$PTHREAD_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$PTHREAD_CC" && break +done +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + ;; +esac ;; #( + *) : + ;; +esac + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + + + + + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + +$as_echo "#define HAVE_PTHREAD 1" >>confdefs.h + + : +else + ax_pthread_ok=no + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$PTHREAD_CFLAGS $CFLAGS" +CC="$PTHREAD_CC" +CXXFLAGS="$CXXFLAGS -ftemplate-depth=512 -Wno-format-zero-length" + +$as_echo "Checking for visibility support:" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __attribute__((visibility(\"hidden\")))" >&5 +$as_echo_n "checking for __attribute__((visibility(\"hidden\")))... " >&6; } +if ${ac_cv_hidden_visibility_attribute+:} false; then : + $as_echo_n "(cached) " >&6 +else + + echo 'int __attribute__ ((visibility ("hidden"))) foo (void) { return 1; }' > visibility_conftest.c + ac_cv_hidden_visibility_attribute=no + if { ac_try='${CC-cc} -fvisibility=hidden -S visibility_conftest.c -o visibility_conftest.s 1>&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; + then + $as_echo "found" + ac_cv_hidden_visibility_attribute=yes + fi + rm -f visibility_conftest.* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_hidden_visibility_attribute" >&5 +$as_echo "$ac_cv_hidden_visibility_attribute" >&6; } + +$as_echo +$as_echo "Checking for boost libraries:" + + + +# Check whether --with-boost was given. +if test "${with_boost+set}" = set; then : + withval=$with_boost; + case $withval in #( + no) : + want_boost="no";_AX_BOOST_BASE_boost_path="" ;; #( + yes) : + want_boost="yes";_AX_BOOST_BASE_boost_path="" ;; #( + *) : + want_boost="yes";_AX_BOOST_BASE_boost_path="$withval" ;; +esac + +else + want_boost="yes" +fi + + + + +# Check whether --with-boost-libdir was given. +if test "${with_boost_libdir+set}" = set; then : + withval=$with_boost_libdir; + if test -d "$withval"; then : + _AX_BOOST_BASE_boost_lib_path="$withval" +else + as_fn_error $? "--with-boost-libdir expected directory name" "$LINENO" 5 +fi + +else + _AX_BOOST_BASE_boost_lib_path="" +fi + + +BOOST_LDFLAGS="" +BOOST_CPPFLAGS="" +if test "x$want_boost" = "xyes"; then : + + + if test "x1.58" = "x"; then : + _AX_BOOST_BASE_TONUMERICVERSION_req="1.20.0" +else + _AX_BOOST_BASE_TONUMERICVERSION_req="1.58" +fi + _AX_BOOST_BASE_TONUMERICVERSION_req_shorten=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([0-9]*\.[0-9]*\)'` + _AX_BOOST_BASE_TONUMERICVERSION_req_major=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([0-9]*\)'` + if test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_major" = "x"; then : + as_fn_error $? "You should at least specify libboost major version" "$LINENO" 5 +fi + _AX_BOOST_BASE_TONUMERICVERSION_req_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[0-9]*\.\([0-9]*\)'` + if test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_minor" = "x"; then : + _AX_BOOST_BASE_TONUMERICVERSION_req_minor="0" +fi + _AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[0-9]*\.[0-9]*\.\([0-9]*\)'` + if test "X$_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor" = "X"; then : + _AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor="0" +fi + _AX_BOOST_BASE_TONUMERICVERSION_RET=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req_major \* 100000 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_minor \* 100 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor` + WANT_BOOST_VERSION=$_AX_BOOST_BASE_TONUMERICVERSION_RET + + succeeded=no + + + + case ${host_cpu} in #( + x86_64) : + libsubdirs="lib64 libx32 lib lib64" ;; #( + mips*64*) : + libsubdirs="lib64 lib32 lib lib64" ;; #( + ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64) : + libsubdirs="lib64 lib lib64" ;; #( + *) : + libsubdirs="lib" + ;; +esac + + case ${host_cpu} in #( + i?86) : + multiarch_libsubdir="lib/i386-${host_os}" ;; #( + *) : + multiarch_libsubdir="lib/${host_cpu}-${host_os}" + ;; +esac + + if test "x$_AX_BOOST_BASE_boost_path" != "x"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for boostlib >= 1.58 ($WANT_BOOST_VERSION) includes in \"$_AX_BOOST_BASE_boost_path/include\"" >&5 +$as_echo_n "checking for boostlib >= 1.58 ($WANT_BOOST_VERSION) includes in \"$_AX_BOOST_BASE_boost_path/include\"... " >&6; } + if test -d "$_AX_BOOST_BASE_boost_path/include" && test -r "$_AX_BOOST_BASE_boost_path/include"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include" + for _AX_BOOST_BASE_boost_path_tmp in $multiarch_libsubdir $libsubdirs; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for boostlib >= 1.58 ($WANT_BOOST_VERSION) lib path in \"$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp\"" >&5 +$as_echo_n "checking for boostlib >= 1.58 ($WANT_BOOST_VERSION) lib path in \"$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp\"... " >&6; } + if test -d "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" && test -r "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" ; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp"; + break; + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + done +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + +else + + if test X"$cross_compiling" = Xyes; then + search_libsubdirs=$multiarch_libsubdir + else + search_libsubdirs="$multiarch_libsubdir $libsubdirs" + fi + for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local ; do + if test -d "$_AX_BOOST_BASE_boost_path_tmp/include/boost" && test -r "$_AX_BOOST_BASE_boost_path_tmp/include/boost" ; then + for libsubdir in $search_libsubdirs ; do + if ls "$_AX_BOOST_BASE_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path_tmp/$libsubdir" + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path_tmp/include" + break; + fi + done + +fi + + if test "x$_AX_BOOST_BASE_boost_lib_path" != "x"; then : + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_lib_path" +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for boostlib >= 1.58 ($WANT_BOOST_VERSION)" >&5 +$as_echo_n "checking for boostlib >= 1.58 ($WANT_BOOST_VERSION)... " >&6; } + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include + +int +main () +{ + +(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($WANT_BOOST_VERSION))])); + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + succeeded=yes + found_system=yes + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + if test "x$succeeded" != "xyes" ; then + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + BOOST_CPPFLAGS= + if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then + BOOST_LDFLAGS= + fi + _version=0 + if test -n "$_AX_BOOST_BASE_boost_path" ; then + if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path"; then + for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "x$V_CHECK" = "x1" ; then + _version=$_version_tmp + fi + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include/boost-$VERSION_UNDERSCORE" + done + if test -z "$BOOST_CPPFLAGS"; then + if test -d "$_AX_BOOST_BASE_boost_path/boost" && test -r "$_AX_BOOST_BASE_boost_path/boost"; then + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path" + fi + fi + if test -n "$BOOST_CPPFLAGS" && test -z "$BOOST_LDFLAGS"; then + for libsubdir in $libsubdirs ; do + if ls "$_AX_BOOST_BASE_boost_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$libsubdir" + fi + fi + else + if test "x$cross_compiling" != "xyes" ; then + for _AX_BOOST_BASE_boost_path in /usr /usr/local /opt /opt/local ; do + if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path" ; then + for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "x$V_CHECK" = "x1" ; then + _version=$_version_tmp + best_path=$_AX_BOOST_BASE_boost_path + fi + done + fi + done + + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" + if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then + for libsubdir in $libsubdirs ; do + if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$best_path/$libsubdir" + fi + fi + + if test -n "$BOOST_ROOT" ; then + for libsubdir in $libsubdirs ; do + if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then + version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` + stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` + stage_version_shorten=`expr $stage_version : '\([0-9]*\.[0-9]*\)'` + V_CHECK=`expr $stage_version_shorten \>\= $_version` + if test "x$V_CHECK" = "x1" && test -z "$_AX_BOOST_BASE_boost_lib_path" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: We will use a staged boost library from $BOOST_ROOT" >&5 +$as_echo "$as_me: We will use a staged boost library from $BOOST_ROOT" >&6;} + BOOST_CPPFLAGS="-I$BOOST_ROOT" + BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" + fi + fi + fi + fi + + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include + +int +main () +{ + +(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($WANT_BOOST_VERSION))])); + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + succeeded=yes + found_system=yes + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + fi + + if test "x$succeeded" != "xyes" ; then + if test "x$_version" = "x0" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: We could not detect the boost libraries (version 1.58 or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation." >&5 +$as_echo "$as_me: We could not detect the boost libraries (version 1.58 or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation." >&6;} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: Your boost libraries seems to old (version $_version)." >&5 +$as_echo "$as_me: Your boost libraries seems to old (version $_version)." >&6;} + fi + # execute ACTION-IF-NOT-FOUND (if present): + : + else + +$as_echo "#define HAVE_BOOST /**/" >>confdefs.h + + # execute ACTION-IF-FOUND (if present): + : + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + + +fi + + + + + ax_cxx_compile_alternatives="11 0x" ax_cxx_compile_cxx11_required=true + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + ac_success=no + + + + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5 +$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; } +if eval \${$cachevar+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_CXX="$CXX" + CXX="$CXX $switch" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + + + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + eval $cachevar=yes +else + eval $cachevar=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CXX="$ac_save_CXX" +fi +eval ac_res=\$$cachevar + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + as_fn_error $? "*** A compiler with support for C++11 language features is required." "$LINENO" 5 + fi + fi + if test x$ac_success = xno; then + HAVE_CXX11=0 + { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++11 support was found" >&5 +$as_echo "$as_me: No compiler with C++11 support was found" >&6;} + else + HAVE_CXX11=1 + +$as_echo "#define HAVE_CXX11 1" >>confdefs.h + + fi + + + + + +# Check whether --with-boost-system was given. +if test "${with_boost_system+set}" = set; then : + withval=$with_boost_system; + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_system_lib="" + else + want_boost="yes" + ax_boost_user_system_lib="$withval" + fi + +else + want_boost="yes" + +fi + + + if test "x$want_boost" = "xyes"; then + + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::System library is available" >&5 +$as_echo_n "checking whether the Boost::System library is available... " >&6; } +if ${ax_cv_boost_system+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + CXXFLAGS_SAVE=$CXXFLAGS + CXXFLAGS= + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +boost::system::error_category *a = 0; + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ax_cv_boost_system=yes +else + ax_cv_boost_system=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CXXFLAGS=$CXXFLAGS_SAVE + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_boost_system" >&5 +$as_echo "$ax_cv_boost_system" >&6; } + if test "x$ax_cv_boost_system" = "xyes"; then + + + +$as_echo "#define HAVE_BOOST_SYSTEM /**/" >>confdefs.h + + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_system_lib" = "x"; then + for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break +else + link_system="no" +fi + + done + if test "x$link_system" != "xyes"; then + for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do + ax_lib=${libextension} + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break +else + link_system="no" +fi + + done + fi + + else + for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do + as_ac_Lib=`$as_echo "ac_cv_lib_$ax_lib''_exit" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exit in -l$ax_lib" >&5 +$as_echo_n "checking for exit in -l$ax_lib... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$ax_lib $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char exit (); +int +main () +{ +return exit (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + BOOST_SYSTEM_LIB="-l$ax_lib"; link_system="yes"; break +else + link_system="no" +fi + + done + + fi + if test "x$ax_lib" = "x"; then + as_fn_error $? "Could not find a version of the Boost::System library!" "$LINENO" 5 + fi + if test "x$link_system" = "xno"; then + as_fn_error $? "Could not link against $ax_lib !" "$LINENO" 5 + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi + +if test -z "$BOOST_SYSTEM_LIB"; then : + as_fn_error $? "Boost.System library not found. Try using --with-boost-system=lib" "$LINENO" 5 +fi + +CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +LIBS="$BOOST_SYSTEM_LIB $LIBS" + +############################################################################### +# Checking for functions and other stuffs +############################################################################### + + +$as_echo +$as_echo "Checking for pkg-config:" + + + + + + + + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_PKG_CONFIG"; then + ac_pt_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $ac_pt_PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG +if test -n "$ac_pt_PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 +$as_echo "$ac_pt_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_pt_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +fi + +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=0.20 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 +$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + PKG_CONFIG="" + fi +fi + +$as_echo +$as_echo "Checking for functions and headers:" + +# Check whether --enable-largefile was given. +if test "${enable_largefile+set}" = set; then : + enableval=$enable_largefile; +fi + +if test "$enable_largefile" != no; then + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for special C compiler options needed for large files" >&5 +$as_echo_n "checking for special C compiler options needed for large files... " >&6; } +if ${ac_cv_sys_largefile_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_sys_largefile_CC=no + if test "$GCC" != yes; then + ac_save_CC=$CC + while :; do + # IRIX 6.2 and later do not support large files by default, + # so use the C compiler's -n32 option if that helps. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF + if ac_fn_c_try_compile "$LINENO"; then : + break +fi +rm -f core conftest.err conftest.$ac_objext + CC="$CC -n32" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_largefile_CC=' -n32'; break +fi +rm -f core conftest.err conftest.$ac_objext + break + done + CC=$ac_save_CC + rm -f conftest.$ac_ext + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_largefile_CC" >&5 +$as_echo "$ac_cv_sys_largefile_CC" >&6; } + if test "$ac_cv_sys_largefile_CC" != no; then + CC=$CC$ac_cv_sys_largefile_CC + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _FILE_OFFSET_BITS value needed for large files" >&5 +$as_echo_n "checking for _FILE_OFFSET_BITS value needed for large files... " >&6; } +if ${ac_cv_sys_file_offset_bits+:} false; then : + $as_echo_n "(cached) " >&6 +else + while :; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_file_offset_bits=no; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _FILE_OFFSET_BITS 64 +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_file_offset_bits=64; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cv_sys_file_offset_bits=unknown + break +done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_file_offset_bits" >&5 +$as_echo "$ac_cv_sys_file_offset_bits" >&6; } +case $ac_cv_sys_file_offset_bits in #( + no | unknown) ;; + *) +cat >>confdefs.h <<_ACEOF +#define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits +_ACEOF +;; +esac +rm -rf conftest* + if test $ac_cv_sys_file_offset_bits = unknown; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _LARGE_FILES value needed for large files" >&5 +$as_echo_n "checking for _LARGE_FILES value needed for large files... " >&6; } +if ${ac_cv_sys_large_files+:} false; then : + $as_echo_n "(cached) " >&6 +else + while :; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_large_files=no; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _LARGE_FILES 1 +#include + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) + int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_sys_large_files=1; break +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_cv_sys_large_files=unknown + break +done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sys_large_files" >&5 +$as_echo "$ac_cv_sys_large_files" >&6; } +case $ac_cv_sys_large_files in #( + no | unknown) ;; + *) +cat >>confdefs.h <<_ACEOF +#define _LARGE_FILES $ac_cv_sys_large_files +_ACEOF +;; +esac +rm -rf conftest* + fi + + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + + +for ac_func in clock_gettime +do : + ac_fn_c_check_func "$LINENO" "clock_gettime" "ac_cv_func_clock_gettime" +if test "x$ac_cv_func_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_CLOCK_GETTIME 1 +_ACEOF + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 +$as_echo_n "checking for clock_gettime in -lrt... " >&6; } +if ${ac_cv_lib_rt_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrt $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rt_clock_gettime=yes +else + ac_cv_lib_rt_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 +$as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } +if test "x$ac_cv_lib_rt_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBRT 1 +_ACEOF + + LIBS="-lrt $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lposix4" >&5 +$as_echo_n "checking for clock_gettime in -lposix4... " >&6; } +if ${ac_cv_lib_posix4_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lposix4 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_posix4_clock_gettime=yes +else + ac_cv_lib_posix4_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_posix4_clock_gettime" >&5 +$as_echo "$ac_cv_lib_posix4_clock_gettime" >&6; } +if test "x$ac_cv_lib_posix4_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPOSIX4 1 +_ACEOF + + LIBS="-lposix4 $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: clock_gettime function not found." >&5 +$as_echo "$as_me: WARNING: clock_gettime function not found." >&2;} +fi + +fi + + +fi +done + + + +COMPILETIME_OPTIONS="" + + +############################################################################### +# Setting configure options +############################################################################### + +# Check whether --enable-logging was given. +if test "${enable_logging+set}" = set; then : + enableval=$enable_logging; ARG_ENABLE_LOGGING=$enableval +else + ARG_ENABLE_LOGGING=yes + +fi + + +# Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; + ARG_ENABLE_DEBUG=$enableval + ac_arg_enable_debug=$enableval + +else + + ARG_ENABLE_DEBUG=no + ac_arg_enable_debug=no + + +fi + + +# Check whether --enable-dht was given. +if test "${enable_dht+set}" = set; then : + enableval=$enable_dht; ARG_ENABLE_DHT=$enableval +else + ARG_ENABLE_DHT=yes + +fi + + +# Check whether --enable-encryption was given. +if test "${enable_encryption+set}" = set; then : + enableval=$enable_encryption; ARG_ENABLE_ENCRYPTION=$enableval +else + ARG_ENABLE_ENCRYPTION=yes + +fi + + +# Check whether --enable-export-all was given. +if test "${enable_export_all+set}" = set; then : + enableval=$enable_export_all; ARG_ENABLE_FULL_EXPORT=$enableval +else + ARG_ENABLE_FULL_EXPORT=no + +fi + + +# Check whether --enable-invariant-checks was given. +if test "${enable_invariant_checks+set}" = set; then : + enableval=$enable_invariant_checks; ARG_ENABLE_INVARIANT=$enableval +else + + if test "x$ac_arg_enable_debug" = "xyes"; then : + ARG_ENABLE_INVARIANT=yes +else + ARG_ENABLE_INVARIANT=no +fi + + +fi + + +# Check whether --enable-deprecated-functions was given. +if test "${enable_deprecated_functions+set}" = set; then : + enableval=$enable_deprecated_functions; ARG_ENABLE_DEPRECATED=$enableval +else + ARG_ENABLE_DEPRECATED=yes + +fi + + +# Check whether --enable-examples was given. +if test "${enable_examples+set}" = set; then : + enableval=$enable_examples; ARG_ENABLE_EXAMPLES=$enableval +else + ARG_ENABLE_EXAMPLES=no + +fi + + +# Check whether --enable-tests was given. +if test "${enable_tests+set}" = set; then : + enableval=$enable_tests; ARG_ENABLE_TESTS=$enableval +else + ARG_ENABLE_TESTS=no + +fi + + +# Check whether --enable-python-binding was given. +if test "${enable_python_binding+set}" = set; then : + enableval=$enable_python_binding; ARG_ENABLE_PYTHON_BINDING=$enableval +else + ARG_ENABLE_PYTHON_BINDING=no + +fi + + + +# Check whether --with-libiconv was given. +if test "${with_libiconv+set}" = set; then : + withval=$with_libiconv; ARG_WITH_LIBICONV=$withval +else + ARG_WITH_LIBICONV=no + +fi + + +############################################################################### +# Checking configure options +############################################################################### + +$as_echo +$as_echo "Checking build options:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether deprecated functions should be enabled" >&5 +$as_echo_n "checking whether deprecated functions should be enabled... " >&6; } +case "$ARG_ENABLE_DEPRECATED" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_NO_DEPRECATE 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_NO_DEPRECATE " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DEPRECATED" >&5 +$as_echo "$ARG_ENABLE_DEPRECATED" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DEPRECATED\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether debug build should be enabled" >&5 +$as_echo_n "checking whether debug build should be enabled... " >&6; } +case "$ARG_ENABLE_DEBUG" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define TORRENT_USE_ASSERTS 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_USE_ASSERTS" + DEBUGFLAGS="-Og" + ;; #( + "no") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define NDEBUG 1" >>confdefs.h + + #COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DNDEBUG " + DEBUGFLAGS="-g0 -Os" + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DEBUG" >&5 +$as_echo "$ARG_ENABLE_DEBUG" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DEBUG\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether invariant check should be enabled" >&5 +$as_echo_n "checking whether invariant check should be enabled... " >&6; } #depends: $ac_arg_enable_debug +case "$ARG_ENABLE_INVARIANT" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define TORRENT_USE_INVARIANT_CHECKS 1" >>confdefs.h + + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_USE_INVARIANT_CHECKS 0" >>confdefs.h + + ;; #( + "full") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: full" >&5 +$as_echo "full" >&6; } + +$as_echo "#define TORRENT_USE_INVARIANT_CHECKS 1" >>confdefs.h + + +$as_echo "#define TORRENT_EXPENSIVE_INVARIANT_CHECKS 1" >>confdefs.h +, + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_INVARIANT" >&5 +$as_echo "$ARG_ENABLE_INVARIANT" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_INVARIANT\". Use either \"yes\", \"no\" or \"full\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether logging to disk should be enabled" >&5 +$as_echo_n "checking whether logging to disk should be enabled... " >&6; } +case "$ARG_ENABLE_LOGGING" in #( + "yes"|"default") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"none") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_LOGGING 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_LOGGING " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_LOGGING" >&5 +$as_echo "$ARG_ENABLE_LOGGING" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_LOGGING\". Use either \"yes\" or \"no\"" "$LINENO" 5 + ;; +esac + +$as_echo +$as_echo "Checking features to be enabled:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether encryption support should be enabled" >&5 +$as_echo_n "checking whether encryption support should be enabled... " >&6; } +case "$ARG_ENABLE_ENCRYPTION" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: encryption support: now checking for the OpenSSL library..." >&5 +$as_echo "$as_me: encryption support: now checking for the OpenSSL library..." >&6;} + + + found=false + +# Check whether --with-openssl was given. +if test "${with_openssl+set}" = set; then : + withval=$with_openssl; + case "$withval" in + "" | y | ye | yes | n | no) + as_fn_error $? "Invalid --with-openssl value" "$LINENO" 5 + ;; + *) ssldirs="$withval" + ;; + esac + +else + + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$PKG_CONFIG"; then + ac_cv_prog_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_PKG_CONFIG="${ac_tool_prefix}pkg-config" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +PKG_CONFIG=$ac_cv_prog_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_PKG_CONFIG"; then + ac_ct_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_PKG_CONFIG"; then + ac_cv_prog_ac_ct_PKG_CONFIG="$ac_ct_PKG_CONFIG" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_PKG_CONFIG="pkg-config" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_PKG_CONFIG=$ac_cv_prog_ac_ct_PKG_CONFIG +if test -n "$ac_ct_PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_PKG_CONFIG" >&5 +$as_echo "$ac_ct_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_ct_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_prog_PKG_CONFIG" +fi + + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + + +fi + + + + # note that we #include , so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for include/openssl/ssl.h in $ssldir" >&5 +$as_echo_n "checking for include/openssl/ssl.h in $ssldir... " >&6; } + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + break + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiling and linking against OpenSSL works" >&5 +$as_echo_n "checking whether compiling and linking against OpenSSL works... " >&6; } + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&5 + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +SSL_new(NULL) + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + +$as_echo "#define TORRENT_USE_OPENSSL 1" >>confdefs.h + + +$as_echo "#define TORRENT_USE_LIBCRYPTO 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_USE_OPENSSL -DTORRENT_USE_LIBCRYPTO " + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + as_fn_error $? "OpenSSL library not found. Try using --with-openssl=DIR or disabling encryption at all." "$LINENO" 5 + + +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + + + + + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_ENCRYPTION 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_ENCRYPTION " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_ENCRYPTION" >&5 +$as_echo "$ARG_ENABLE_ENCRYPTION" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_ENCRYPTION\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether dht support should be enabled" >&5 +$as_echo_n "checking whether dht support should be enabled... " >&6; } +case "$ARG_ENABLE_DHT" in #( + "yes"|"on") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + ;; #( + "no"|"off") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +$as_echo "#define TORRENT_DISABLE_DHT 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_DHT " + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_DHT" >&5 +$as_echo "$ARG_ENABLE_DHT" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_DHT\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +if test "x$ac_cv_hidden_visibility_attribute" = "xyes"; then : + + if test "x$ARG_ENABLE_FULL_EXPORT" = "xno"; then : + + CXXFLAGS="$CXXFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + CFLAGS="$CFLAGS -fvisibility=hidden" + LDFLAGS="$LDFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + +fi + +fi + +$as_echo +$as_echo "Checking for extra build files:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether example files should be built" >&5 +$as_echo_n "checking whether example files should be built... " >&6; } +case "$ARG_ENABLE_EXAMPLES" in #( + "yes") : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } ;; #( + "no") : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_EXAMPLES" >&5 +$as_echo "$ARG_ENABLE_EXAMPLES" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_EXAMPLES\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether test files should be built" >&5 +$as_echo_n "checking whether test files should be built... " >&6; } +case "$ARG_ENABLE_TESTS" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +$as_echo "#define TORRENT_EXPORT_EXTRA 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_EXPORT_EXTRA " + ;; #( + "no") : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_TESTS" >&5 +$as_echo "$ARG_ENABLE_TESTS" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_TESTS\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether python bindings should be built" >&5 +$as_echo_n "checking whether python bindings should be built... " >&6; } +case "$ARG_ENABLE_PYTHON_BINDING" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + + + + + + + if test -n "$PYTHON"; then + # If the user set $PYTHON, use it and don't search something else. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $PYTHON version is >= 2.4" >&5 +$as_echo_n "checking whether $PYTHON version is >= 2.4... " >&6; } + prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python 3.0 and a list in 2.x +minver = list(map(int, '2.4'.split('.'))) + [0, 0, 0] +minverhex = 0 +# xrange is not present in Python 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] +sys.exit(sys.hexversion < minverhex)" + if { echo "$as_me:$LINENO: $PYTHON -c "$prog"" >&5 + ($PYTHON -c "$prog") >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Python interpreter is too old" "$LINENO" 5 +fi + am_display_PYTHON=$PYTHON + else + # Otherwise, try each interpreter until we find one that satisfies + # VERSION. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a Python interpreter with version >= 2.4" >&5 +$as_echo_n "checking for a Python interpreter with version >= 2.4... " >&6; } +if ${am_cv_pathless_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + + for am_cv_pathless_PYTHON in python python2 python3 python3.9 python3.8 python3.7 python3.6 python3.5 python3.4 python3.3 python3.2 python3.1 python3.0 python2.7 python2.6 python2.5 python2.4 python2.3 python2.2 python2.1 python2.0 none; do + test "$am_cv_pathless_PYTHON" = none && break + prog="import sys +# split strings by '.' and convert to numeric. Append some zeros +# because we need at least 4 digits for the hex conversion. +# map returns an iterator in Python 3.0 and a list in 2.x +minver = list(map(int, '2.4'.split('.'))) + [0, 0, 0] +minverhex = 0 +# xrange is not present in Python 3.0 and range returns an iterator +for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i] +sys.exit(sys.hexversion < minverhex)" + if { echo "$as_me:$LINENO: $am_cv_pathless_PYTHON -c "$prog"" >&5 + ($am_cv_pathless_PYTHON -c "$prog") >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; then : + break +fi + done +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_pathless_PYTHON" >&5 +$as_echo "$am_cv_pathless_PYTHON" >&6; } + # Set $PYTHON to the absolute path of $am_cv_pathless_PYTHON. + if test "$am_cv_pathless_PYTHON" = none; then + PYTHON=: + else + # Extract the first word of "$am_cv_pathless_PYTHON", so it can be a program name with args. +set dummy $am_cv_pathless_PYTHON; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PYTHON in + [\\/]* | ?:[\\/]*) + ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PYTHON=$ac_cv_path_PYTHON +if test -n "$PYTHON"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi + am_display_PYTHON=$am_cv_pathless_PYTHON + fi + + + if test "$PYTHON" = :; then + as_fn_error $? "Python interpreter not found." "$LINENO" 5 + else + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON version" >&5 +$as_echo_n "checking for $am_display_PYTHON version... " >&6; } +if ${am_cv_python_version+:} false; then : + $as_echo_n "(cached) " >&6 +else + am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[:3])"` +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_version" >&5 +$as_echo "$am_cv_python_version" >&6; } + PYTHON_VERSION=$am_cv_python_version + + + + PYTHON_PREFIX='${prefix}' + + PYTHON_EXEC_PREFIX='${exec_prefix}' + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON platform" >&5 +$as_echo_n "checking for $am_display_PYTHON platform... " >&6; } +if ${am_cv_python_platform+:} false; then : + $as_echo_n "(cached) " >&6 +else + am_cv_python_platform=`$PYTHON -c "import sys; sys.stdout.write(sys.platform)"` +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_platform" >&5 +$as_echo "$am_cv_python_platform" >&6; } + PYTHON_PLATFORM=$am_cv_python_platform + + + # Just factor out some code duplication. + am_python_setup_sysconfig="\ +import sys +# Prefer sysconfig over distutils.sysconfig, for better compatibility +# with python 3.x. See automake bug#10227. +try: + import sysconfig +except ImportError: + can_use_sysconfig = 0 +else: + can_use_sysconfig = 1 +# Can't use sysconfig in CPython 2.7, since it's broken in virtualenvs: +# +try: + from platform import python_implementation + if python_implementation() == 'CPython' and sys.version[:3] == '2.7': + can_use_sysconfig = 0 +except ImportError: + pass" + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON script directory" >&5 +$as_echo_n "checking for $am_display_PYTHON script directory... " >&6; } +if ${am_cv_python_pythondir+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$prefix" = xNONE + then + am_py_prefix=$ac_default_prefix + else + am_py_prefix=$prefix + fi + am_cv_python_pythondir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('purelib', vars={'base':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(0, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pythondir in + $am_py_prefix*) + am__strip_prefix=`echo "$am_py_prefix" | sed 's|.|.|g'` + am_cv_python_pythondir=`echo "$am_cv_python_pythondir" | sed "s,^$am__strip_prefix,$PYTHON_PREFIX,"` + ;; + *) + case $am_py_prefix in + /usr|/System*) ;; + *) + am_cv_python_pythondir=$PYTHON_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pythondir" >&5 +$as_echo "$am_cv_python_pythondir" >&6; } + pythondir=$am_cv_python_pythondir + + + + pkgpythondir=\${pythondir}/$PACKAGE + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $am_display_PYTHON extension module directory" >&5 +$as_echo_n "checking for $am_display_PYTHON extension module directory... " >&6; } +if ${am_cv_python_pyexecdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$exec_prefix" = xNONE + then + am_py_exec_prefix=$am_py_prefix + else + am_py_exec_prefix=$exec_prefix + fi + am_cv_python_pyexecdir=`$PYTHON -c " +$am_python_setup_sysconfig +if can_use_sysconfig: + sitedir = sysconfig.get_path('platlib', vars={'platbase':'$am_py_prefix'}) +else: + from distutils import sysconfig + sitedir = sysconfig.get_python_lib(1, 0, prefix='$am_py_prefix') +sys.stdout.write(sitedir)"` + case $am_cv_python_pyexecdir in + $am_py_exec_prefix*) + am__strip_prefix=`echo "$am_py_exec_prefix" | sed 's|.|.|g'` + am_cv_python_pyexecdir=`echo "$am_cv_python_pyexecdir" | sed "s,^$am__strip_prefix,$PYTHON_EXEC_PREFIX,"` + ;; + *) + case $am_py_exec_prefix in + /usr|/System*) ;; + *) + am_cv_python_pyexecdir=$PYTHON_EXEC_PREFIX/lib/python$PYTHON_VERSION/site-packages + ;; + esac + ;; + esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_python_pyexecdir" >&5 +$as_echo "$am_cv_python_pyexecdir" >&6; } + pyexecdir=$am_cv_python_pyexecdir + + + + pkgpyexecdir=\${pyexecdir}/$PACKAGE + + + + fi + + + + # + # Allow the use of a (user set) custom python version + # + + + # Extract the first word of "python[$PYTHON_VERSION]", so it can be a program name with args. +set dummy python$PYTHON_VERSION; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PYTHON+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PYTHON in + [\\/]* | ?:[\\/]*) + ac_cv_path_PYTHON="$PYTHON" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PYTHON="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PYTHON=$ac_cv_path_PYTHON +if test -n "$PYTHON"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON" >&5 +$as_echo "$PYTHON" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + if test -z "$PYTHON"; then + as_fn_error $? "Cannot find python$PYTHON_VERSION in your system path" "$LINENO" 5 + PYTHON_VERSION="" + fi + + # + # Check for a version of Python >= 2.1.0 + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a version of Python >= '2.1.0'" >&5 +$as_echo_n "checking for a version of Python >= '2.1.0'... " >&6; } + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[0]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? " +This version of the AC_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. + +See \`config.log' for more details" "$LINENO" 5; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: skip at user request" >&5 +$as_echo "skip at user request" >&6; } + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n ">= '2.4'"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for a version of Python >= '2.4'" >&5 +$as_echo_n "checking for a version of Python >= '2.4'... " >&6; } + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[0]; \ + print (ver >= '2.4')"` + if test "$ac_supports_python_ver" = "True"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "this package requires Python >= '2.4'. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See \`\`configure --help'' for reference. +" "$LINENO" 5 + PYTHON_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for the distutils Python package" >&5 +$as_echo_n "checking for the distutils Python package... " >&6; } + ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` + if test $? -eq 0; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "cannot import Python module \"distutils\". +Please check your Python installation. The error was: +$ac_distutils_result" "$LINENO" 5 + PYTHON_VERSION="" + fi + + # + # Check for Python include path + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python include path" >&5 +$as_echo_n "checking for Python include path... " >&6; } + if test -z "$PYTHON_CPPFLAGS"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc (plat_specific=1));"` + if test -n "${python_path}"; then + if test "${plat_python_path}" != "${python_path}"; then + python_path="-I$python_path -I$plat_python_path" + else + python_path="-I$python_path" + fi + fi + PYTHON_CPPFLAGS=$python_path + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_CPPFLAGS" >&5 +$as_echo "$PYTHON_CPPFLAGS" >&6; } + + + # + # Check for Python library path + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python library path" >&5 +$as_echo_n "checking for Python library path... " >&6; } + if test -z "$PYTHON_LIBS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<>confdefs.h <<_ACEOF +#define HAVE_PYTHON "$ac_python_version" +_ACEOF + + + # First, the library directory: + ac_python_libdir=`cat<&5 +$as_echo "$PYTHON_LIBS" >&6; } + + + # + # Check for site packages + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Python site-packages path" >&5 +$as_echo_n "checking for Python site-packages path... " >&6; } + if test -z "$PYTHON_SITE_PKG"; then + PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_lib(0,0));"` + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_SITE_PKG" >&5 +$as_echo "$PYTHON_SITE_PKG" >&6; } + + + # + # libraries which must be linked in when embedding + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking python extra libraries" >&5 +$as_echo_n "checking python extra libraries... " >&6; } + if test -z "$PYTHON_EXTRA_LIBS"; then + PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \ + conf = distutils.sysconfig.get_config_var; \ + print (conf('LIBS') + ' ' + conf('SYSLIBS'))"` + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_EXTRA_LIBS" >&5 +$as_echo "$PYTHON_EXTRA_LIBS" >&6; } + + + # + # linking flags needed when embedding + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking python extra linking flags" >&5 +$as_echo_n "checking python extra linking flags... " >&6; } + if test -z "$PYTHON_EXTRA_LDFLAGS"; then + PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "import distutils.sysconfig; \ + conf = distutils.sysconfig.get_config_var; \ + print (conf('LINKFORSHARED'))"` + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PYTHON_EXTRA_LDFLAGS" >&5 +$as_echo "$PYTHON_EXTRA_LDFLAGS" >&6; } + + + # + # final check to see if everything compiles alright + # + { $as_echo "$as_me:${as_lineno-$LINENO}: checking consistency of all components of python development environment" >&5 +$as_echo_n "checking consistency of all components of python development environment... " >&6; } + # save current global flags + ac_save_LIBS="$LIBS" + ac_save_LDFLAGS="$LDFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + LIBS="$ac_save_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS $PYTHON_EXTRA_LIBS" + LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS" + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include +int +main () +{ +Py_Initialize(); + ; + return 0; +} + +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + pythonexists=yes +else + pythonexists=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + LDFLAGS="$ac_save_LDFLAGS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $pythonexists" >&5 +$as_echo "$pythonexists" >&6; } + + if test ! "x$pythonexists" = "xyes"; then + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? " + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LIBS environment variable. + Example: ./configure LIBS=\"-L/usr/non-standard-path/python/lib\" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + +See \`config.log' for more details" "$LINENO" 5; } + PYTHON_VERSION="" + fi + + # + # all done! + # + + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +ax_boost_python_save_CPPFLAGS="$CPPFLAGS" +ax_boost_python_save_LDFLAGS="$LDFLAGS" +ax_boost_python_save_LIBS="$LIBS" +if test "x$PYTHON_CPPFLAGS" != "x"; then + CPPFLAGS="$PYTHON_CPPFLAGS $CPPFLAGS" +fi + +# Versions of AX_PYTHON_DEVEL() before serial 18 provided PYTHON_LDFLAGS +# instead of PYTHON_LIBS, so this is just here for compatibility. +if test "x$PYTHON_LDFLAGS" != "x"; then + LDFLAGS="$PYTHON_LDFLAGS $LDFLAGS" +fi + +# Note: Only versions of AX_PYTHON_DEVEL() since serial 18 provide PYTHON_LIBS +# instead of PYTHON_LDFLAGS. +if test "x$PYTHON_LIBS" != "x"; then + LIBS="$PYTHON_LIBS $LIBS" +fi + +if test "x$BOOST_CPPFLAGS" != "x"; then + CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +fi +if test "x$BOOST_LDFLAGS" != "x"; then + LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the Boost::Python library is available" >&5 +$as_echo_n "checking whether the Boost::Python library is available... " >&6; } +if ${ac_cv_boost_python+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +BOOST_PYTHON_MODULE(test) { throw "Boost::Python test."; } +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + ac_cv_boost_python=yes +else + ac_cv_boost_python=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_boost_python" >&5 +$as_echo "$ac_cv_boost_python" >&6; } +if test "$ac_cv_boost_python" = "yes"; then + +$as_echo "#define HAVE_BOOST_PYTHON /**/" >>confdefs.h + + ax_python_lib=boost_python + +# Check whether --with-boost-python was given. +if test "${with_boost_python+set}" = set; then : + withval=$with_boost_python; if test "x$with_boost_python" != "xno" -a "x$with_boost_python" != "xyes"; then + ax_python_lib=$with_boost_python + ax_boost_python_lib=boost_python-$with_boost_python + fi +fi + + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/[^\/]*//'` + for ax_lib in $ax_python_lib $ax_boost_python_lib `ls $BOOSTLIBDIR/libboost_python*.so* $BOOSTLIBDIR/libboost_python*.dylib* $BOOSTLIBDIR/libboost_python*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_python.*\)\.so.*$;\1;' -e 's;^lib\(boost_python.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_python.*\)\.a.*$;\1;' ` boost_python boost_python3; do + as_ax_Lib=`$as_echo "ax_cv_lib_$ax_lib''_BOOST_PYTHON_MODULE" | $as_tr_sh` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $ax_lib is the correct library" >&5 +$as_echo_n "checking whether $ax_lib is the correct library... " >&6; } +if eval \${$as_ax_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + LIBS="-l$ax_lib $ax_boost_python_save_LIBS $PYTHON_LIBS" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +BOOST_PYTHON_MODULE(test) { throw "Boost::Python test."; } +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + eval "$as_ax_Lib=yes" +else + eval "$as_ax_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$as_ax_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + if eval test \"x\$"$as_ax_Lib"\" = x"yes"; then : + BOOST_PYTHON_LIB=-l$ax_lib break +fi + done + +fi +CPPFLAGS="$ax_boost_python_save_CPPFLAGS" +LDFLAGS="$ax_boost_python_save_LDFLAGS" +LIBS="$ax_boost_python_save_LIBS" +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + if test -z "$BOOST_PYTHON_LIB"; then : + as_fn_error $? "Boost.Python library not found. Try using --with-boost-python=lib." "$LINENO" 5 +fi + ;; #( + "no") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_ENABLE_PYTHON_BINDING" >&5 +$as_echo "$ARG_ENABLE_PYTHON_BINDING" >&6; } + as_fn_error $? "Unknown option \"$ARG_ENABLE_PYTHON_BINDING\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +$as_echo +$as_echo "Checking for external libraries:" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to link against system libiconv" >&5 +$as_echo_n "checking whether to link against system libiconv... " >&6; } + + if test "X$prefix" = "XNONE"; then + acl_final_prefix="$ac_default_prefix" + else + acl_final_prefix="$prefix" + fi + if test "X$exec_prefix" = "XNONE"; then + acl_final_exec_prefix='${prefix}' + else + acl_final_exec_prefix="$exec_prefix" + fi + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" + prefix="$acl_save_prefix" + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +# Prepare PATH_SEPARATOR. +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + # Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which + # contains only /bin. Note that ksh looks also at the FPATH variable, + # so we have to set that as well for the test. + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \ + && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \ + || PATH_SEPARATOR=';' + } +fi + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo "$ac_prog"| sed 's%\\\\%/%g'` + while echo "$ac_prog" | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${acl_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$acl_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + acl_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$acl_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${acl_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$acl_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$acl_cv_prog_gnu_ld + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shared library run path origin" >&5 +$as_echo_n "checking for shared library run path origin... " >&6; } +if ${acl_cv_rpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + + CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ + ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh + . ./conftest.sh + rm -f ./conftest.sh + acl_cv_rpath=done + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $acl_cv_rpath" >&5 +$as_echo "$acl_cv_rpath" >&6; } + wl="$acl_cv_wl" + acl_libext="$acl_cv_libext" + acl_shlibext="$acl_cv_shlibext" + acl_libname_spec="$acl_cv_libname_spec" + acl_library_names_spec="$acl_cv_library_names_spec" + acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" + acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" + acl_hardcode_direct="$acl_cv_hardcode_direct" + acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" + # Check whether --enable-rpath was given. +if test "${enable_rpath+set}" = set; then : + enableval=$enable_rpath; : +else + enable_rpath=yes +fi + + + + + acl_libdirstem=lib + acl_libdirstem2= + case "$host_os" in + solaris*) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for 64-bit host" >&5 +$as_echo_n "checking for 64-bit host... " >&6; } +if ${gl_cv_solaris_64bit+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#ifdef _LP64 +sixtyfour bits +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "sixtyfour bits" >/dev/null 2>&1; then : + gl_cv_solaris_64bit=yes +else + gl_cv_solaris_64bit=no +fi +rm -f conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gl_cv_solaris_64bit" >&5 +$as_echo "$gl_cv_solaris_64bit" >&6; } + if test $gl_cv_solaris_64bit = yes; then + acl_libdirstem=lib/64 + case "$host_cpu" in + sparc*) acl_libdirstem2=lib/sparcv9 ;; + i*86 | x86_64) acl_libdirstem2=lib/amd64 ;; + esac + fi + ;; + *) + searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` + if test -n "$searchpath"; then + acl_save_IFS="${IFS= }"; IFS=":" + for searchdir in $searchpath; do + if test -d "$searchdir"; then + case "$searchdir" in + */lib64/ | */lib64 ) acl_libdirstem=lib64 ;; + */../ | */.. ) + # Better ignore directories of this form. They are misleading. + ;; + *) searchdir=`cd "$searchdir" && pwd` + case "$searchdir" in + */lib64 ) acl_libdirstem=lib64 ;; + esac ;; + esac + fi + done + IFS="$acl_save_IFS" + fi + ;; + esac + test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" + + + + + + + + + + + + + use_additional=yes + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + +# Check whether --with-libiconv-prefix was given. +if test "${with_libiconv_prefix+set}" = set; then : + withval=$with_libiconv_prefix; + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + if test "$acl_libdirstem2" != "$acl_libdirstem" \ + && ! test -d "$withval/$acl_libdirstem"; then + additional_libdir="$withval/$acl_libdirstem2" + fi + fi + fi + +fi + + LIBICONV= + LTLIBICONV= + INCICONV= + LIBICONV_PREFIX= + HAVE_LIBICONV= + rpathdirs= + ltrpathdirs= + names_already_handled= + names_next_round='iconv ' + while test -n "$names_next_round"; do + names_this_round="$names_next_round" + names_next_round= + for name in $names_this_round; do + already_handled= + for n in $names_already_handled; do + if test "$n" = "$name"; then + already_handled=yes + break + fi + done + if test -z "$already_handled"; then + names_already_handled="$names_already_handled $name" + uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'` + eval value=\"\$HAVE_LIB$uppername\" + if test -n "$value"; then + if test "$value" = yes; then + eval value=\"\$LIB$uppername\" + test -z "$value" || LIBICONV="${LIBICONV}${LIBICONV:+ }$value" + eval value=\"\$LTLIB$uppername\" + test -z "$value" || LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }$value" + else + : + fi + else + found_dir= + found_la= + found_so= + found_a= + eval libname=\"$acl_libname_spec\" # typically: libname=lib$name + if test -n "$acl_shlibext"; then + shrext=".$acl_shlibext" # typically: shrext=.so + else + shrext= + fi + if test $use_additional = yes; then + dir="$additional_libdir" + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext"; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + fi + if test "X$found_dir" = "X"; then + for x in $LDFLAGS $LTLIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + case "$x" in + -L*) + dir=`echo "X$x" | sed -e 's/^X-L//'` + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext"; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + ;; + esac + if test "X$found_dir" != "X"; then + break + fi + done + fi + if test "X$found_dir" != "X"; then + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-L$found_dir -l$name" + if test "X$found_so" != "X"; then + if test "$enable_rpath" = no \ + || test "X$found_dir" = "X/usr/$acl_libdirstem" \ + || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + else + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $found_dir" + fi + if test "$acl_hardcode_direct" = yes; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + else + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $found_dir" + fi + else + haveit= + for x in $LDFLAGS $LIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-L$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }-L$found_dir" + fi + if test "$acl_hardcode_minus_L" != no; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_so" + else + LIBICONV="${LIBICONV}${LIBICONV:+ }-l$name" + fi + fi + fi + fi + else + if test "X$found_a" != "X"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }$found_a" + else + LIBICONV="${LIBICONV}${LIBICONV:+ }-L$found_dir -l$name" + fi + fi + additional_includedir= + case "$found_dir" in + */$acl_libdirstem | */$acl_libdirstem/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` + if test "$name" = 'iconv'; then + LIBICONV_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + */$acl_libdirstem2 | */$acl_libdirstem2/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` + if test "$name" = 'iconv'; then + LIBICONV_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + esac + if test "X$additional_includedir" != "X"; then + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + for x in $CPPFLAGS $INCICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + INCICONV="${INCICONV}${INCICONV:+ }-I$additional_includedir" + fi + fi + fi + fi + fi + if test -n "$found_la"; then + save_libdir="$libdir" + case "$found_la" in + */* | *\\*) . "$found_la" ;; + *) . "./$found_la" ;; + esac + libdir="$save_libdir" + for dep in $dependency_libs; do + case "$dep" in + -L*) + additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` + if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \ + && test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then + haveit= + if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \ + || test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + haveit= + for x in $LDFLAGS $LIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + LIBICONV="${LIBICONV}${LIBICONV:+ }-L$additional_libdir" + fi + fi + haveit= + for x in $LDFLAGS $LTLIBICONV; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-L$additional_libdir" + fi + fi + fi + fi + ;; + -R*) + dir=`echo "X$dep" | sed -e 's/^X-R//'` + if test "$enable_rpath" != no; then + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $dir" + fi + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $dir" + fi + fi + ;; + -l*) + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` + ;; + *.la) + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` + ;; + *) + LIBICONV="${LIBICONV}${LIBICONV:+ }$dep" + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }$dep" + ;; + esac + done + fi + else + LIBICONV="${LIBICONV}${LIBICONV:+ }-l$name" + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-l$name" + fi + fi + fi + done + done + if test "X$rpathdirs" != "X"; then + if test -n "$acl_hardcode_libdir_separator"; then + alldirs= + for found_dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" + done + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIBICONV="${LIBICONV}${LIBICONV:+ }$flag" + else + for found_dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$found_dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIBICONV="${LIBICONV}${LIBICONV:+ }$flag" + done + fi + fi + if test "X$ltrpathdirs" != "X"; then + for found_dir in $ltrpathdirs; do + LTLIBICONV="${LTLIBICONV}${LTLIBICONV:+ }-R$found_dir" + done + fi + + + + + + + + + + + + + am_save_CPPFLAGS="$CPPFLAGS" + + for element in $INCICONV; do + haveit= + for x in $CPPFLAGS; do + + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + eval x=\"$x\" + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" + + if test "X$x" = "X$element"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }$element" + fi + done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for iconv" >&5 +$as_echo_n "checking for iconv... " >&6; } +if ${am_cv_func_iconv+:} false; then : + $as_echo_n "(cached) " >&6 +else + + am_cv_func_iconv="no, consider installing GNU libiconv" + am_cv_lib_iconv=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include + +int +main () +{ +iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + am_cv_func_iconv=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test "$am_cv_func_iconv" != yes; then + am_save_LIBS="$LIBS" + LIBS="$LIBS $LIBICONV" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include + +int +main () +{ +iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + am_cv_lib_iconv=yes + am_cv_func_iconv=yes +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LIBS="$am_save_LIBS" + fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_func_iconv" >&5 +$as_echo "$am_cv_func_iconv" >&6; } + if test "$am_cv_func_iconv" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working iconv" >&5 +$as_echo_n "checking for working iconv... " >&6; } +if ${am_cv_func_iconv_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + + am_save_LIBS="$LIBS" + if test $am_cv_lib_iconv = yes; then + LIBS="$LIBS $LIBICONV" + fi + am_cv_func_iconv_works=no + for ac_iconv_const in '' 'const'; do + if test "$cross_compiling" = yes; then : + case "$host_os" in + aix* | hpux*) am_cv_func_iconv_works="guessing no" ;; + *) am_cv_func_iconv_works="guessing yes" ;; + esac +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include + +#ifndef ICONV_CONST +# define ICONV_CONST $ac_iconv_const +#endif + +int +main () +{ +int result = 0; + /* Test against AIX 5.1 bug: Failures are not distinguishable from successful + returns. */ + { + iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8"); + if (cd_utf8_to_88591 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */ + char buf[10]; + ICONV_CONST char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_utf8_to_88591, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res == 0) + result |= 1; + iconv_close (cd_utf8_to_88591); + } + } + /* Test against Solaris 10 bug: Failures are not distinguishable from + successful returns. */ + { + iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646"); + if (cd_ascii_to_88591 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\263"; + char buf[10]; + ICONV_CONST char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_ascii_to_88591, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res == 0) + result |= 2; + iconv_close (cd_ascii_to_88591); + } + } + /* Test against AIX 6.1..7.1 bug: Buffer overrun. */ + { + iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1"); + if (cd_88591_to_utf8 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\304"; + static char buf[2] = { (char)0xDE, (char)0xAD }; + ICONV_CONST char *inptr = input; + size_t inbytesleft = 1; + char *outptr = buf; + size_t outbytesleft = 1; + size_t res = iconv (cd_88591_to_utf8, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res != (size_t)(-1) || outptr - buf > 1 || buf[1] != (char)0xAD) + result |= 4; + iconv_close (cd_88591_to_utf8); + } + } +#if 0 /* This bug could be worked around by the caller. */ + /* Test against HP-UX 11.11 bug: Positive return value instead of 0. */ + { + iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591"); + if (cd_88591_to_utf8 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337"; + char buf[50]; + ICONV_CONST char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_88591_to_utf8, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if ((int)res > 0) + result |= 8; + iconv_close (cd_88591_to_utf8); + } + } +#endif + /* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is + provided. */ + { + /* Try standardized names. */ + iconv_t cd1 = iconv_open ("UTF-8", "EUC-JP"); + /* Try IRIX, OSF/1 names. */ + iconv_t cd2 = iconv_open ("UTF-8", "eucJP"); + /* Try AIX names. */ + iconv_t cd3 = iconv_open ("UTF-8", "IBM-eucJP"); + /* Try HP-UX names. */ + iconv_t cd4 = iconv_open ("utf8", "eucJP"); + if (cd1 == (iconv_t)(-1) && cd2 == (iconv_t)(-1) + && cd3 == (iconv_t)(-1) && cd4 == (iconv_t)(-1)) + result |= 16; + if (cd1 != (iconv_t)(-1)) + iconv_close (cd1); + if (cd2 != (iconv_t)(-1)) + iconv_close (cd2); + if (cd3 != (iconv_t)(-1)) + iconv_close (cd3); + if (cd4 != (iconv_t)(-1)) + iconv_close (cd4); + } + return result; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + am_cv_func_iconv_works=yes +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + test "$am_cv_func_iconv_works" = no || break + done + LIBS="$am_save_LIBS" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_func_iconv_works" >&5 +$as_echo "$am_cv_func_iconv_works" >&6; } + case "$am_cv_func_iconv_works" in + *no) am_func_iconv=no am_cv_lib_iconv=no ;; + *) am_func_iconv=yes ;; + esac + else + am_func_iconv=no am_cv_lib_iconv=no + fi + if test "$am_func_iconv" = yes; then + +$as_echo "#define HAVE_ICONV 1" >>confdefs.h + + fi + if test "$am_cv_lib_iconv" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to link with libiconv" >&5 +$as_echo_n "checking how to link with libiconv... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIBICONV" >&5 +$as_echo "$LIBICONV" >&6; } + else + CPPFLAGS="$am_save_CPPFLAGS" + LIBICONV= + LTLIBICONV= + fi + + + + if test "$am_cv_func_iconv" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for iconv declaration" >&5 +$as_echo_n "checking for iconv declaration... " >&6; } + if ${am_cv_proto_iconv+:} false; then : + $as_echo_n "(cached) " >&6 +else + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include +#include +extern +#ifdef __cplusplus +"C" +#endif +#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) +size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft); +#else +size_t iconv(); +#endif + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + am_cv_proto_iconv_arg1="" +else + am_cv_proto_iconv_arg1="const" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);" +fi + + am_cv_proto_iconv=`echo "$am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'` + { $as_echo "$as_me:${as_lineno-$LINENO}: result: + $am_cv_proto_iconv" >&5 +$as_echo " + $am_cv_proto_iconv" >&6; } + else + am_cv_proto_iconv_arg1="" + fi + +cat >>confdefs.h <<_ACEOF +#define ICONV_CONST $am_cv_proto_iconv_arg1 +_ACEOF + + + +case "$ARG_WITH_LIBICONV" in #( + "yes") : + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + if test "x$am_cv_func_iconv" = "xyes"; then : + + ICONV_LIBS=$LIBICONV + + LIBS="$ICONV_LIBS $LIBS" + +else + + as_fn_error $? "Iconv library not found." "$LINENO" 5 + +fi + ;; #( + "no") : + + ARG_WITH_LIBICONV="no" + ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ARG_WITH_LIBICONV" >&5 +$as_echo "$ARG_WITH_LIBICONV" >&6; } + as_fn_error $? "Unknown option \"$ARG_WITH_LIBICONV\". Use either \"yes\" or \"no\"." "$LINENO" 5 + ;; +esac + +############################################################################### +# Setting conditional variables for Makefiles +############################################################################### + + if test "x$ARG_ENABLE_DHT" = "xyes" -o "x$ARG_ENABLE_DHT" = "xlogging" ; then + ENABLE_DHT_TRUE= + ENABLE_DHT_FALSE='#' +else + ENABLE_DHT_TRUE='#' + ENABLE_DHT_FALSE= +fi + + if test "x$ARG_ENABLE_EXAMPLES" = "xyes"; then + ENABLE_EXAMPLES_TRUE= + ENABLE_EXAMPLES_FALSE='#' +else + ENABLE_EXAMPLES_TRUE='#' + ENABLE_EXAMPLES_FALSE= +fi + + if test "x$ARG_ENABLE_TESTS" = "xyes"; then + ENABLE_TESTS_TRUE= + ENABLE_TESTS_FALSE='#' +else + ENABLE_TESTS_TRUE='#' + ENABLE_TESTS_FALSE= +fi + + if test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"; then + ENABLE_PYTHON_BINDING_TRUE= + ENABLE_PYTHON_BINDING_FALSE='#' +else + ENABLE_PYTHON_BINDING_TRUE='#' + ENABLE_PYTHON_BINDING_FALSE= +fi + + + if test "x$ARG_ENABLE_ENCRYPTION" = "xyes" -o "x$ARG_ENABLE_ENCRYPTION" = "xon" ; then + WITH_OPENSSL_TRUE= + WITH_OPENSSL_FALSE='#' +else + WITH_OPENSSL_TRUE='#' + WITH_OPENSSL_FALSE= +fi + + + +############################################################################### +# Other useful stuff +############################################################################### + + +$as_echo "#define BOOST_ASIO_HAS_STD_CHRONO 1" >>confdefs.h + +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_HAS_STD_CHRONO=1 " + + +$as_echo "#define BOOST_EXCEPTION_DISABLE 1" >>confdefs.h + +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_EXCEPTION_DISABLE " + + +$as_echo "#define BOOST_ASIO_ENABLE_CANCELIO 1" >>confdefs.h + +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_ENABLE_CANCELIO " + + +if test "x$PYTHON_INSTALL_PARAMS" = "x"; then : + PYTHON_INSTALL_PARAMS='--prefix=$(DESTDIR)$(prefix)' +fi + +if test "x$enable_shared" = "xyes"; then : + +$as_echo "#define TORRENT_BUILDING_SHARED 1" >>confdefs.h + + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_LINKING_SHARED " +fi + + + + + +# Disable deprecated warnings for the Python binding builds. +PYTHON_CXXFLAGS="${PYTHON_CXXFLAGS} -Wno-deprecated-declarations" + + +# Try to guess real git revision if any, fallback to hardcoded otherwise +GIT_REVISION=`git log -1 --format=format:%h 2>/dev/null` +if test -z "$GIT_REVISION"; then : + GIT_REVISION=`sed -n -e "s/^#define LIBTORRENT_REVISION \"\([0-9a-z]*\)\"$/\1/p" $(dirname $0)/include/libtorrent/version.hpp` +fi + + +############################################################################### +# Generating Makefiles +############################################################################### + +$as_echo +$as_echo "Generating Makefiles:" + +ac_config_files="$ac_config_files Makefile src/Makefile include/libtorrent/Makefile examples/Makefile test/Makefile tools/Makefile bindings/Makefile bindings/python/Makefile bindings/python/link_flags bindings/python/compile_flags bindings/python/compile_cmd libtorrent-rasterbar.pc" + + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 +$as_echo_n "checking that generated files are newer than configure... " >&6; } + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5 +$as_echo "done" >&6; } +if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then + as_fn_error $? "conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCXX\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + if test -n "$EXEEXT"; then + am__EXEEXT_TRUE= + am__EXEEXT_FALSE='#' +else + am__EXEEXT_TRUE='#' + am__EXEEXT_FALSE= +fi + +if test -z "${MAINTAINER_MODE_TRUE}" && test -z "${MAINTAINER_MODE_FALSE}"; then + as_fn_error $? "conditional \"MAINTAINER_MODE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HAVE_ANDROID_TRUE}" && test -z "${HAVE_ANDROID_FALSE}"; then + as_fn_error $? "conditional \"HAVE_ANDROID\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HAVE_WINDOWS_TRUE}" && test -z "${HAVE_WINDOWS_FALSE}"; then + as_fn_error $? "conditional \"HAVE_WINDOWS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_DHT_TRUE}" && test -z "${ENABLE_DHT_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_DHT\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_EXAMPLES_TRUE}" && test -z "${ENABLE_EXAMPLES_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_EXAMPLES\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_TESTS_TRUE}" && test -z "${ENABLE_TESTS_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_TESTS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ENABLE_PYTHON_BINDING_TRUE}" && test -z "${ENABLE_PYTHON_BINDING_FALSE}"; then + as_fn_error $? "conditional \"ENABLE_PYTHON_BINDING\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${WITH_OPENSSL_TRUE}" && test -z "${WITH_OPENSSL_FALSE}"; then + as_fn_error $? "conditional \"WITH_OPENSSL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by libtorrent-rasterbar $as_me 1.2.9, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Configuration commands: +$config_commands + +Report bugs to . +libtorrent-rasterbar home page: ." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +libtorrent-rasterbar config.status 1.2.9 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +MKDIR_P='$MKDIR_P' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" + + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' +macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' +enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' +enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' +pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' +enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' +shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' +SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' +ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' +PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' +host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' +host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' +host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' +build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' +build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' +build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' +SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' +Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' +GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' +EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' +FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' +LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' +NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' +LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' +max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' +ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' +exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' +lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' +lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' +lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' +lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' +lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' +reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' +reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' +OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' +deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' +file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' +file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' +want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' +DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' +sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' +AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' +AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' +archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' +STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' +RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' +old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' +old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' +lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' +CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' +CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' +compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' +GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' +lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' +nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' +lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' +lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' +objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' +MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' +need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' +MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' +DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' +NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' +LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' +OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' +OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' +libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' +shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' +extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' +compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' +module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' +with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' +no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' +hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' +hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' +inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' +link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' +always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' +exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' +include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' +prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' +postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' +file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' +variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' +need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' +need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' +version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' +runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' +libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' +library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' +soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' +install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' +postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' +postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' +finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' +hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' +sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' +configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' +configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' +hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' +enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' +old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' +striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`' +predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`' +postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`' +predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`' +postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`' +LD_CXX='`$ECHO "$LD_CXX" | $SED "$delay_single_quote_subst"`' +reload_flag_CXX='`$ECHO "$reload_flag_CXX" | $SED "$delay_single_quote_subst"`' +reload_cmds_CXX='`$ECHO "$reload_cmds_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_cmds_CXX='`$ECHO "$old_archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' +compiler_CXX='`$ECHO "$compiler_CXX" | $SED "$delay_single_quote_subst"`' +GCC_CXX='`$ECHO "$GCC_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag_CXX='`$ECHO "$lt_prog_compiler_no_builtin_flag_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic_CXX='`$ECHO "$lt_prog_compiler_pic_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl_CXX='`$ECHO "$lt_prog_compiler_wl_CXX" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static_CXX='`$ECHO "$lt_prog_compiler_static_CXX" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o_CXX='`$ECHO "$lt_cv_prog_compiler_c_o_CXX" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc_CXX='`$ECHO "$archive_cmds_need_lc_CXX" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes_CXX='`$ECHO "$enable_shared_with_static_runtimes_CXX" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec_CXX='`$ECHO "$export_dynamic_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec_CXX='`$ECHO "$whole_archive_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +compiler_needs_object_CXX='`$ECHO "$compiler_needs_object_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds_CXX='`$ECHO "$old_archive_from_new_cmds_CXX" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds_CXX='`$ECHO "$old_archive_from_expsyms_cmds_CXX" | $SED "$delay_single_quote_subst"`' +archive_cmds_CXX='`$ECHO "$archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds_CXX='`$ECHO "$archive_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' +module_cmds_CXX='`$ECHO "$module_cmds_CXX" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds_CXX='`$ECHO "$module_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' +with_gnu_ld_CXX='`$ECHO "$with_gnu_ld_CXX" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag_CXX='`$ECHO "$allow_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' +no_undefined_flag_CXX='`$ECHO "$no_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec_CXX='`$ECHO "$hardcode_libdir_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator_CXX='`$ECHO "$hardcode_libdir_separator_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_direct_CXX='`$ECHO "$hardcode_direct_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute_CXX='`$ECHO "$hardcode_direct_absolute_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L_CXX='`$ECHO "$hardcode_minus_L_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var_CXX='`$ECHO "$hardcode_shlibpath_var_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_automatic_CXX='`$ECHO "$hardcode_automatic_CXX" | $SED "$delay_single_quote_subst"`' +inherit_rpath_CXX='`$ECHO "$inherit_rpath_CXX" | $SED "$delay_single_quote_subst"`' +link_all_deplibs_CXX='`$ECHO "$link_all_deplibs_CXX" | $SED "$delay_single_quote_subst"`' +always_export_symbols_CXX='`$ECHO "$always_export_symbols_CXX" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds_CXX='`$ECHO "$export_symbols_cmds_CXX" | $SED "$delay_single_quote_subst"`' +exclude_expsyms_CXX='`$ECHO "$exclude_expsyms_CXX" | $SED "$delay_single_quote_subst"`' +include_expsyms_CXX='`$ECHO "$include_expsyms_CXX" | $SED "$delay_single_quote_subst"`' +prelink_cmds_CXX='`$ECHO "$prelink_cmds_CXX" | $SED "$delay_single_quote_subst"`' +postlink_cmds_CXX='`$ECHO "$postlink_cmds_CXX" | $SED "$delay_single_quote_subst"`' +file_list_spec_CXX='`$ECHO "$file_list_spec_CXX" | $SED "$delay_single_quote_subst"`' +hardcode_action_CXX='`$ECHO "$hardcode_action_CXX" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_dirs_CXX='`$ECHO "$compiler_lib_search_dirs_CXX" | $SED "$delay_single_quote_subst"`' +predep_objects_CXX='`$ECHO "$predep_objects_CXX" | $SED "$delay_single_quote_subst"`' +postdep_objects_CXX='`$ECHO "$postdep_objects_CXX" | $SED "$delay_single_quote_subst"`' +predeps_CXX='`$ECHO "$predeps_CXX" | $SED "$delay_single_quote_subst"`' +postdeps_CXX='`$ECHO "$postdeps_CXX" | $SED "$delay_single_quote_subst"`' +compiler_lib_search_path_CXX='`$ECHO "$compiler_lib_search_path_CXX" | $SED "$delay_single_quote_subst"`' + +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in SHELL \ +ECHO \ +PATH_SEPARATOR \ +SED \ +GREP \ +EGREP \ +FGREP \ +LD \ +NM \ +LN_S \ +lt_SP2NL \ +lt_NL2SP \ +reload_flag \ +OBJDUMP \ +deplibs_check_method \ +file_magic_cmd \ +file_magic_glob \ +want_nocaseglob \ +DLLTOOL \ +sharedlib_from_linklib_cmd \ +AR \ +AR_FLAGS \ +archiver_list_spec \ +STRIP \ +RANLIB \ +CC \ +CFLAGS \ +compiler \ +lt_cv_sys_global_symbol_pipe \ +lt_cv_sys_global_symbol_to_cdecl \ +lt_cv_sys_global_symbol_to_import \ +lt_cv_sys_global_symbol_to_c_name_address \ +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ +lt_cv_nm_interface \ +nm_file_list_spec \ +lt_cv_truncate_bin \ +lt_prog_compiler_no_builtin_flag \ +lt_prog_compiler_pic \ +lt_prog_compiler_wl \ +lt_prog_compiler_static \ +lt_cv_prog_compiler_c_o \ +need_locks \ +MANIFEST_TOOL \ +DSYMUTIL \ +NMEDIT \ +LIPO \ +OTOOL \ +OTOOL64 \ +shrext_cmds \ +export_dynamic_flag_spec \ +whole_archive_flag_spec \ +compiler_needs_object \ +with_gnu_ld \ +allow_undefined_flag \ +no_undefined_flag \ +hardcode_libdir_flag_spec \ +hardcode_libdir_separator \ +exclude_expsyms \ +include_expsyms \ +file_list_spec \ +variables_saved_for_relink \ +libname_spec \ +library_names_spec \ +soname_spec \ +install_override_mode \ +finish_eval \ +old_striplib \ +striplib \ +compiler_lib_search_dirs \ +predep_objects \ +postdep_objects \ +predeps \ +postdeps \ +compiler_lib_search_path \ +LD_CXX \ +reload_flag_CXX \ +compiler_CXX \ +lt_prog_compiler_no_builtin_flag_CXX \ +lt_prog_compiler_pic_CXX \ +lt_prog_compiler_wl_CXX \ +lt_prog_compiler_static_CXX \ +lt_cv_prog_compiler_c_o_CXX \ +export_dynamic_flag_spec_CXX \ +whole_archive_flag_spec_CXX \ +compiler_needs_object_CXX \ +with_gnu_ld_CXX \ +allow_undefined_flag_CXX \ +no_undefined_flag_CXX \ +hardcode_libdir_flag_spec_CXX \ +hardcode_libdir_separator_CXX \ +exclude_expsyms_CXX \ +include_expsyms_CXX \ +file_list_spec_CXX \ +compiler_lib_search_dirs_CXX \ +predep_objects_CXX \ +postdep_objects_CXX \ +predeps_CXX \ +postdeps_CXX \ +compiler_lib_search_path_CXX; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in reload_cmds \ +old_postinstall_cmds \ +old_postuninstall_cmds \ +old_archive_cmds \ +extract_expsyms_cmds \ +old_archive_from_new_cmds \ +old_archive_from_expsyms_cmds \ +archive_cmds \ +archive_expsym_cmds \ +module_cmds \ +module_expsym_cmds \ +export_symbols_cmds \ +prelink_cmds \ +postlink_cmds \ +postinstall_cmds \ +postuninstall_cmds \ +finish_cmds \ +sys_lib_search_path_spec \ +configure_time_dlsearch_path \ +configure_time_lt_sys_library_path \ +reload_cmds_CXX \ +old_archive_cmds_CXX \ +old_archive_from_new_cmds_CXX \ +old_archive_from_expsyms_cmds_CXX \ +archive_cmds_CXX \ +archive_expsym_cmds_CXX \ +module_cmds_CXX \ +module_expsym_cmds_CXX \ +export_symbols_cmds_CXX \ +prelink_cmds_CXX \ +postlink_cmds_CXX; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +ac_aux_dir='$ac_aux_dir' + +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + + + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile' + + + + + + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; + "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; + "include/libtorrent/Makefile") CONFIG_FILES="$CONFIG_FILES include/libtorrent/Makefile" ;; + "examples/Makefile") CONFIG_FILES="$CONFIG_FILES examples/Makefile" ;; + "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; + "tools/Makefile") CONFIG_FILES="$CONFIG_FILES tools/Makefile" ;; + "bindings/Makefile") CONFIG_FILES="$CONFIG_FILES bindings/Makefile" ;; + "bindings/python/Makefile") CONFIG_FILES="$CONFIG_FILES bindings/python/Makefile" ;; + "bindings/python/link_flags") CONFIG_FILES="$CONFIG_FILES bindings/python/link_flags" ;; + "bindings/python/compile_flags") CONFIG_FILES="$CONFIG_FILES bindings/python/compile_flags" ;; + "bindings/python/compile_cmd") CONFIG_FILES="$CONFIG_FILES bindings/python/compile_cmd" ;; + "libtorrent-rasterbar.pc") CONFIG_FILES="$CONFIG_FILES libtorrent-rasterbar.pc" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac + ac_MKDIR_P=$MKDIR_P + case $MKDIR_P in + [\\/$]* | ?:[\\/]* ) ;; + */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +s&@MKDIR_P@&$ac_MKDIR_P&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "depfiles":C) test x"$AMDEP_TRUE" != x"" || { + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + # TODO: see whether this extra hack can be removed once we start + # requiring Autoconf 2.70 or later. + case $CONFIG_FILES in #( + *\'*) : + eval set x "$CONFIG_FILES" ;; #( + *) : + set x $CONFIG_FILES ;; #( + *) : + ;; +esac + shift + # Used to flag and report bootstrapping failures. + am_rc=0 + for am_mf + do + # Strip MF so we end up with the name of the file. + am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile which includes + # dependency-tracking related rules and includes. + # Grep'ing the whole file directly is not great: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ + || continue + am_dirpart=`$as_dirname -- "$am_mf" || +$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$am_mf" : 'X\(//\)[^/]' \| \ + X"$am_mf" : 'X\(//\)$' \| \ + X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$am_mf" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + am_filepart=`$as_basename -- "$am_mf" || +$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ + X"$am_mf" : 'X\(//\)$' \| \ + X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$am_mf" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + { echo "$as_me:$LINENO: cd "$am_dirpart" \ + && sed -e '/# am--include-marker/d' "$am_filepart" \ + | $MAKE -f - am--depfiles" >&5 + (cd "$am_dirpart" \ + && sed -e '/# am--include-marker/d' "$am_filepart" \ + | $MAKE -f - am--depfiles) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } || am_rc=$? + done + if test $am_rc -ne 0; then + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "Something went wrong bootstrapping makefile fragments + for automatic dependency tracking. Try re-running configure with the + '--disable-dependency-tracking' option to at least be able to build + the package (albeit without support for automatic dependency tracking). +See \`config.log' for more details" "$LINENO" 5; } + fi + { am_dirpart=; unset am_dirpart;} + { am_filepart=; unset am_filepart;} + { am_mf=; unset am_mf;} + { am_rc=; unset am_rc;} + rm -f conftest-deps.mk +} + ;; + "libtool":C) + + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# The names of the tagged configurations supported by this script. +available_tags='CXX ' + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG + +# Which release of libtool.m4 was used? +macro_version=$macro_version +macro_revision=$macro_revision + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# What type of objects to build. +pic_mode=$pic_mode + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# Shared archive member basename,for filename based shared library versioning on AIX. +shared_archive_member_spec=$shared_archive_member_spec + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# An echo program that protects backslashes. +ECHO=$lt_ECHO + +# The PATH separator for the build system. +PATH_SEPARATOR=$lt_PATH_SEPARATOR + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="\$SED -e 1s/^X//" + +# A grep program that handles long lines. +GREP=$lt_GREP + +# An ERE matcher. +EGREP=$lt_EGREP + +# A literal string matcher. +FGREP=$lt_FGREP + +# A BSD- or MS-compatible name lister. +NM=$lt_NM + +# Whether we need soft or hard links. +LN_S=$lt_LN_S + +# What is the maximum length of a command? +max_cmd_len=$max_cmd_len + +# Object file suffix (normally "o"). +objext=$ac_objext + +# Executable file suffix (normally ""). +exeext=$exeext + +# whether the shell understands "unset". +lt_unset=$lt_unset + +# turn spaces into newlines. +SP2NL=$lt_lt_SP2NL + +# turn newlines into spaces. +NL2SP=$lt_lt_NL2SP + +# convert \$build file names to \$host format. +to_host_file_cmd=$lt_cv_to_host_file_cmd + +# convert \$build files to toolchain format. +to_tool_file_cmd=$lt_cv_to_tool_file_cmd + +# An object symbol dumper. +OBJDUMP=$lt_OBJDUMP + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method = "file_magic". +file_magic_cmd=$lt_file_magic_cmd + +# How to find potential files when deplibs_check_method = "file_magic". +file_magic_glob=$lt_file_magic_glob + +# Find potential files using nocaseglob when deplibs_check_method = "file_magic". +want_nocaseglob=$lt_want_nocaseglob + +# DLL creation program. +DLLTOOL=$lt_DLLTOOL + +# Command to associate shared and link libraries. +sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd + +# The archiver. +AR=$lt_AR + +# Flags to create an archive. +AR_FLAGS=$lt_AR_FLAGS + +# How to feed a file listing to the archiver. +archiver_list_spec=$lt_archiver_list_spec + +# A symbol stripping program. +STRIP=$lt_STRIP + +# Commands used to install an old-style archive. +RANLIB=$lt_RANLIB +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Whether to use a lock for old archive extraction. +lock_old_archive_extraction=$lock_old_archive_extraction + +# A C compiler. +LTCC=$lt_CC + +# LTCC compiler flags. +LTCFLAGS=$lt_CFLAGS + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration. +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm into a list of symbols to manually relocate. +global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import + +# Transform the output of nm in a C name address pair. +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# Transform the output of nm in a C name address pair when lib prefix is needed. +global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix + +# The name lister interface. +nm_interface=$lt_lt_cv_nm_interface + +# Specify filename containing input files for \$NM. +nm_file_list_spec=$lt_nm_file_list_spec + +# The root where to search for dependent libraries,and where our libraries should be installed. +lt_sysroot=$lt_sysroot + +# Command to truncate a binary pipe. +lt_truncate_bin=$lt_lt_cv_truncate_bin + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# Used to examine libraries when file_magic_cmd begins with "file". +MAGIC_CMD=$MAGIC_CMD + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Manifest tool. +MANIFEST_TOOL=$lt_MANIFEST_TOOL + +# Tool to manipulate archived DWARF debug symbol files on Mac OS X. +DSYMUTIL=$lt_DSYMUTIL + +# Tool to change global to local symbols on Mac OS X. +NMEDIT=$lt_NMEDIT + +# Tool to manipulate fat objects and archives on Mac OS X. +LIPO=$lt_LIPO + +# ldd/readelf like tool for Mach-O binaries on Mac OS X. +OTOOL=$lt_OTOOL + +# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. +OTOOL64=$lt_OTOOL64 + +# Old archive suffix (normally "a"). +libext=$libext + +# Shared library suffix (normally ".so"). +shrext_cmds=$lt_shrext_cmds + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at link time. +variables_saved_for_relink=$lt_variables_saved_for_relink + +# Do we need the "lib" prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Library versioning type. +version_type=$version_type + +# Shared library runtime path variable. +runpath_var=$runpath_var + +# Shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Permission mode override for installation of shared libraries. +install_override_mode=$lt_install_override_mode + +# Command to use after installation of a shared archive. +postinstall_cmds=$lt_postinstall_cmds + +# Command to use after uninstallation of a shared archive. +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# As "finish_cmds", except a single script fragment to be evaled but +# not shown. +finish_eval=$lt_finish_eval + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Compile-time system search path for libraries. +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Detected run-time system search path for libraries. +sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path + +# Explicit LT_SYS_LIBRARY_PATH set during ./configure time. +configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + + +# The linker used to build libraries. +LD=$lt_LD + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds + +# A language specific compiler. +CC=$lt_compiler + +# Is the compiler the GNU compiler? +with_gcc=$GCC + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds +archive_expsym_cmds=$lt_archive_expsym_cmds + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds +module_expsym_cmds=$lt_module_expsym_cmds + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \$shlibpath_var if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# The directories searched by this compiler when creating a shared library. +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs + +# Dependencies to place before and after the objects being linked to +# create a shared library. +predep_objects=$lt_predep_objects +postdep_objects=$lt_postdep_objects +predeps=$lt_predeps +postdeps=$lt_postdeps + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path + +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + +ltmain=$ac_aux_dir/ltmain.sh + + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" + + + cat <<_LT_EOF >> "$ofile" + +# ### BEGIN LIBTOOL TAG CONFIG: CXX + +# The linker used to build libraries. +LD=$lt_LD_CXX + +# How to create reloadable object files. +reload_flag=$lt_reload_flag_CXX +reload_cmds=$lt_reload_cmds_CXX + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds_CXX + +# A language specific compiler. +CC=$lt_compiler_CXX + +# Is the compiler the GNU compiler? +with_gcc=$GCC_CXX + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic_CXX + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl_CXX + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static_CXX + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc_CXX + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object_CXX + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds_CXX +archive_expsym_cmds=$lt_archive_expsym_cmds_CXX + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds_CXX +module_expsym_cmds=$lt_module_expsym_cmds_CXX + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld_CXX + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag_CXX + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag_CXX + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct_CXX + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \$shlibpath_var if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute_CXX + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L_CXX + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic_CXX + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath_CXX + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs_CXX + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols_CXX + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds_CXX + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms_CXX + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms_CXX + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds_CXX + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds_CXX + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec_CXX + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action_CXX + +# The directories searched by this compiler when creating a shared library. +compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX + +# Dependencies to place before and after the objects being linked to +# create a shared library. +predep_objects=$lt_predep_objects_CXX +postdep_objects=$lt_postdep_objects_CXX +predeps=$lt_predeps_CXX +postdeps=$lt_postdeps_CXX + +# The library search path used internally by the compiler when linking +# a shared library. +compiler_lib_search_path=$lt_compiler_lib_search_path_CXX + +# ### END LIBTOOL TAG CONFIG: CXX +_LT_EOF + + ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + + +############################################################################### +# Generating config.report +############################################################################### + +$as_echo +$as_echo "Configure script has finished system check." +$as_echo + +cat > config.report << END + Config results: + -=-=-=-=-=-=-=-=- + +Package: + name: ${PACKAGE_NAME} + version: ${PACKAGE_VERSION} + git revision: ${GIT_REVISION} + +Build environment: + build system: ${build} + host system: ${host} + target system: ${target} + +Compiler and linker flags: + CPPFlags: ${CPPFLAGS} + CFlags: ${CFLAGS} + CXXFlags: ${CXXFLAGS} + LDFlags: ${LDFLAGS} + Libs: ${LIBS} + Defs: ${DEFS} + +Build options: + deprecated functions: ${ARG_ENABLE_DEPRECATED:-yes} + debug build: ${ARG_ENABLE_DEBUG:-no} + invariant checks: ${ARG_ENABLE_INVARIANT:-no} + logging support: ${ARG_ENABLE_LOGGING:-yes} + +Features: + encryption support: ${ARG_ENABLE_ENCRYPTION:-yes} + dht support: ${ARG_ENABLE_DHT:-yes} + +Extra builds: + examples: ${ARG_ENABLE_EXAMPLES:-no} + tests: ${ARG_ENABLE_TESTS:-no} + python bindings: ${ARG_ENABLE_PYTHON_BINDING:-no} + +Pthread library: + CFlags: ${PTHREAD_CFLAGS} + Libs: ${PTHREAD_LIBS} + +Boost libraries: + version: ${BOOST_VERSION} + CPPFlags: ${BOOST_CPPFLAGS} + LDFlags: ${BOOST_LDFLAGS} + boost.system: ${BOOST_SYSTEM_LIB} +END + +if test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"; then : + +cat >> config.report << END + boost.python: ${BOOST_PYTHON_LIB} + +Python environment: + -automake- + binary: ${PYTHON} + version: ${PYTHON_VERSION} + platform: ${PYTHON_PLATFORM} + prefix: ${PYTHON_PREFIX} + exec_prefix: ${PYTHON_EXEC_PREFIX} + pythondir: ${pythondir} + pkgpythondir: ${pkgpythondir} + pyexecdir: ${pyexecdir} + pkgpyexecdir: ${pkgpyexecdir} + + -m4- + cppflags: ${PYTHON_CPPFLAGS} + ldflags: ${PYTHON_LDFLAGS} + extra libs: ${PYTHON_EXTRA_LIBS} + +END + +fi + +cat >> config.report << END + +External libraries: + system libiconv: ${ARG_WITH_LIBICONV:-no} +END + +if test "x$ARG_WITH_LIBICONV" = "xyes"; then : + +cat >> config.report << END + +Iconv library: + Iconv Libs: ${ICONV_LIBS} +END + +fi + +if test "x$ARG_ENABLE_ENCRYPTION" = "xyes"; then : + +cat >> config.report << END + +OpenSSL library: + OpenSSL Libs: ${OPENSSL_LIBS} + OpenSSL LDFlags: ${OPENSSL_LDFLAGS} + OpenSSL Includes: ${OPENSSL_INCLUDES} +END + +fi + +cat config.report + +$as_echo +$as_echo "Type 'make' to compile $PACKAGE_STRING" +$as_echo "or type 'make V=1' for verbose compiling" +$as_echo "and then 'make install' to install it into $prefix" +$as_echo diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..0b9b0c4 --- /dev/null +++ b/configure.ac @@ -0,0 +1,668 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +# $Id$ + +AC_PREREQ([2.63]) + +AC_INIT([libtorrent-rasterbar],[1.2.9],[arvid@libtorrent.org], + [libtorrent-rasterbar],[http://www.libtorrent.org]) +AC_CONFIG_SRCDIR([src/torrent.cpp]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) + +# Silencing build output (automake-1.11) +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +# Change ar argument to 'rc' to silence the warning: +# ar: `u' modifier ignored since `D' is the default (see `U') +m4_divert_text([DEFAULTS], [: "${AR_FLAGS=rc}"]) + +############################################################################### +dnl --------------------------------------------------------------------------- +dnl interface version info +dnl --------------------------------------------------------------------------- +dnl Advanced information about versioning: +dnl * "Writing shared libraries" by Mike Hearn +dnl http://navi.cx/~mike/writing-shared-libraries.html +dnl * libtool.info chapter "Versioning" +dnl * libtool.info chapter "Updating library version information" +dnl --------------------------------------------------------------------------- +dnl Versioning: +dnl - CURRENT (Major): Increment if the interface has changes. AGE is always +dnl *changed* at the same time. +dnl - AGE (Micro): Increment if any interfaces have been added; set to 0 +dnl if any interfaces have been removed. Removal has +dnl precedence over adding, so set to 0 if both happened. +dnl It denotes upward compatibility. +dnl - REVISION (Minor): Increment any time the source changes; set to +dnl 0 if you incremented CURRENT. +dnl +dnl To summarize. Any interface *change* increment CURRENT. If that interface +dnl change does not break upward compatibility (ie it is an addition), +dnl increment AGE, Otherwise AGE is reset to 0. If CURRENT has changed, +dnl REVISION is set to 0, otherwise REVISION is incremented. +dnl --------------------------------------------------------------------------- + +m4_define([VERSION_INFO_CURRENT],[10]) +m4_define([VERSION_INFO_REVISION],[0]) +m4_define([VERSION_INFO_AGE],[0]) +INTERFACE_VERSION_INFO=VERSION_INFO_CURRENT:VERSION_INFO_REVISION:VERSION_INFO_AGE +AC_SUBST(INTERFACE_VERSION_INFO) +############################################################################### + + +############################################################################### +# Start +############################################################################### + +AS_ECHO +AS_ECHO "Building $PACKAGE_STRING" + + +############################################################################### +# Performing some basic checks and initializing the build system +############################################################################### + +AS_ECHO +AS_ECHO "Checking for a C/C++ compiler to use:" +AC_PROG_CC +AC_PROG_CPP +AC_PROG_CC_C_O +AC_PROG_CXX +AC_PROG_CXXCPP +AC_PROG_CXX_C_O + +AS_ECHO +AS_ECHO "Checking system type:" +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AS_ECHO +AS_ECHO "Initializing Automake:" +AM_INIT_AUTOMAKE([1.11 foreign]) +AM_MAINTAINER_MODE([enable]) + +AS_ECHO +AS_ECHO "Initializing Libtool:" +LT_PREREQ([2.2.6]) +LT_INIT + +AS_IF([test "$SYS" = linux],[ + AC_PREPROC_IFELSE([AC_LANG_PROGRAM( + [[#ifndef __ANDROID__ + # error Not Android + #endif + ]],[[;]]) + ],[ + HAVE_ANDROID="1" + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) +AM_CONDITIONAL(HAVE_ANDROID, test "${HAVE_ANDROID}" = "1") + +HAVE_WINDOWS=0 +case "${host_os}" in + *mingw32* | *cygwin*) + HAVE_WINDOWS=1 + ;; +esac +AM_CONDITIONAL(HAVE_WINDOWS, test "${HAVE_WINDOWS}" = "1") + +############################################################################### +# Checking for needed base libraries +############################################################################### + +AS_ECHO +AS_ECHO "Checking for posix thread support:" + +AX_PTHREAD() + +LIBS="$PTHREAD_LIBS $LIBS" +CFLAGS="$PTHREAD_CFLAGS $CFLAGS" +CC="$PTHREAD_CC" +CXXFLAGS="$CXXFLAGS -ftemplate-depth=512 -Wno-format-zero-length" + +AS_ECHO "Checking for visibility support:" +AC_CACHE_CHECK([for __attribute__((visibility("hidden")))], + ac_cv_hidden_visibility_attribute, [ + echo 'int __attribute__ ((visibility ("hidden"))) foo (void) { return 1; }' > visibility_conftest.c + ac_cv_hidden_visibility_attribute=no + if AC_TRY_COMMAND(${CC-cc} -fvisibility=hidden -S visibility_conftest.c -o visibility_conftest.s 1>&AS_MESSAGE_LOG_FD); + then + AS_ECHO "found" + ac_cv_hidden_visibility_attribute=yes + fi + rm -f visibility_conftest.* +]) + +AS_ECHO +AS_ECHO "Checking for boost libraries:" + +AX_BOOST_BASE([1.58]) + +AX_CXX_COMPILE_STDCXX_11([noext], [mandatory]) + +AX_BOOST_SYSTEM() +AS_IF([test -z "$BOOST_SYSTEM_LIB"], + [AC_MSG_ERROR(Boost.System library not found. Try using --with-boost-system=lib)]) + +CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +LIBS="$BOOST_SYSTEM_LIB $LIBS" + +############################################################################### +# Checking for functions and other stuffs +############################################################################### + + +AS_ECHO +AS_ECHO "Checking for pkg-config:" + +PKG_PROG_PKG_CONFIG([0.20]) + +AS_ECHO +AS_ECHO "Checking for functions and headers:" + +AC_SYS_LARGEFILE +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET + +AC_CHECK_FUNCS([clock_gettime], [], + [AC_CHECK_LIB([rt], [clock_gettime], [], + [AC_CHECK_LIB([posix4], [clock_gettime], [], + [AC_MSG_WARN([clock_gettime function not found.])])])] +) + + +dnl Pass some build options to .pc file +COMPILETIME_OPTIONS="" + + +############################################################################### +# Setting configure options +############################################################################### + +AC_ARG_ENABLE( + [logging], + [AS_HELP_STRING( + [--enable-logging], + [enable logging alerts [default=yes]])], + [[ARG_ENABLE_LOGGING=$enableval]], + [[ARG_ENABLE_LOGGING=yes]] +) + +AC_ARG_ENABLE( + [debug], + [AS_HELP_STRING( + [--enable-debug], + [enable debug build [default=no]])], + [ + ARG_ENABLE_DEBUG=$enableval + ac_arg_enable_debug=$enableval + ], + [ + ARG_ENABLE_DEBUG=no + ac_arg_enable_debug=no + ] +) + +AC_ARG_ENABLE( + [dht], + [AS_HELP_STRING( + [--enable-dht], + [enable dht support [default=yes]])], + [[ARG_ENABLE_DHT=$enableval]], + [[ARG_ENABLE_DHT=yes]] +) + +AC_ARG_ENABLE( + [encryption], + [AS_HELP_STRING( + [--enable-encryption], + [enable encryption support (requires OpenSSL to be installed on your system, you can use --with-openssl to set the path) [default=yes]])], + [[ARG_ENABLE_ENCRYPTION=$enableval]], + [[ARG_ENABLE_ENCRYPTION=yes]] +) + +AC_ARG_ENABLE( + [export-all], + [AS_HELP_STRING( + [--enable-export-all], + [export all symbols from libtorrent, including non-public ones [default=no]])], + [[ARG_ENABLE_FULL_EXPORT=$enableval]], + [[ARG_ENABLE_FULL_EXPORT=no]] +) + +AC_ARG_ENABLE( + [invariant-checks], + [AS_HELP_STRING( + [--enable-invariant-checks], + [enable invariant checks (use value "full" to turn on extra expensive invariant checks) @<:@default=yes if debug is enabled, no otherwhise@:>@])], + [[ARG_ENABLE_INVARIANT=$enableval]], + [ + AS_IF([test "x$ac_arg_enable_debug" = "xyes"], + [ARG_ENABLE_INVARIANT=yes], + [ARG_ENABLE_INVARIANT=no]) + ] +) + +AC_ARG_ENABLE( + [deprecated-functions], + [AS_HELP_STRING( + [--enable-deprecated-functions], + [enable deprecated functions in the API [default=yes]])], + [[ARG_ENABLE_DEPRECATED=$enableval]], + [[ARG_ENABLE_DEPRECATED=yes]] +) + +AC_ARG_ENABLE( + [examples], + [AS_HELP_STRING( + [--enable-examples], + [build example files [default=no]])], + [[ARG_ENABLE_EXAMPLES=$enableval]], + [[ARG_ENABLE_EXAMPLES=no]] +) + +AC_ARG_ENABLE( + [tests], + [AS_HELP_STRING( + [--enable-tests], + [build test files [default=no]])], + [[ARG_ENABLE_TESTS=$enableval]], + [[ARG_ENABLE_TESTS=no]] +) + +AC_ARG_ENABLE( + [python-binding], + [AS_HELP_STRING( + [--enable-python-binding], + [build python bindings [default=no]])], + [[ARG_ENABLE_PYTHON_BINDING=$enableval]], + [[ARG_ENABLE_PYTHON_BINDING=no]] +) + +AC_ARG_WITH( + [libiconv], + [AS_HELP_STRING( + [--with-libiconv], + [enable linking against system libiconv [default=no]])], + [[ARG_WITH_LIBICONV=$withval]], + [[ARG_WITH_LIBICONV=no]] +) + +############################################################################### +# Checking configure options +############################################################################### + +AS_ECHO +AS_ECHO "Checking build options:" + +AC_MSG_CHECKING([whether deprecated functions should be enabled]) +AS_CASE(["$ARG_ENABLE_DEPRECATED"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_NO_DEPRECATE],[1],[Define to exclude all deprecated functions from the API.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_NO_DEPRECATE " + ], + [AC_MSG_RESULT([$ARG_ENABLE_DEPRECATED]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DEPRECATED". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether debug build should be enabled]) +AS_CASE(["$ARG_ENABLE_DEBUG"], + ["yes"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([TORRENT_USE_ASSERTS],[1],[Define to enable debug code.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_USE_ASSERTS" + DEBUGFLAGS="-Og" + ], + ["no"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([NDEBUG],[1],[Define to disable debug code.]) + #COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DNDEBUG " + DEBUGFLAGS="-g0 -Os" + ], + [AC_MSG_RESULT([$ARG_ENABLE_DEBUG]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DEBUG". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether invariant check should be enabled]) #depends: $ac_arg_enable_debug +AS_CASE(["$ARG_ENABLE_INVARIANT"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([TORRENT_USE_INVARIANT_CHECKS],[1],[Define to enable internal invariant checks.]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_USE_INVARIANT_CHECKS],[0],[Define to disable internal invariant checks. Asserts are still enabled while debug is on.]) + ], + ["full"], [ + AC_MSG_RESULT([full]) + AC_DEFINE([TORRENT_USE_INVARIANT_CHECKS],[1],[Define to enable internal invariant checks.]) + AC_DEFINE([TORRENT_EXPENSIVE_INVARIANT_CHECKS],[1],[Define to enable extra expensive invariant checks.]), + ], + [AC_MSG_RESULT([$ARG_ENABLE_INVARIANT]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_INVARIANT". Use either "yes", "no" or "full".])] +) + +AC_MSG_CHECKING([whether logging to disk should be enabled]) +AS_CASE(["$ARG_ENABLE_LOGGING"], + ["yes"|"default"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"none"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_LOGGING],[1],[Define to disable support for logging alerts]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_LOGGING " + ], + [AC_MSG_RESULT([$ARG_ENABLE_LOGGING]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_LOGGING". Use either "yes" or "no"])] +) + +AS_ECHO +AS_ECHO "Checking features to be enabled:" + +AC_MSG_CHECKING([whether encryption support should be enabled]) +AS_CASE(["$ARG_ENABLE_ENCRYPTION"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + AC_MSG_NOTICE([encryption support: now checking for the OpenSSL library...]) + + AX_CHECK_OPENSSL([ + AC_DEFINE([TORRENT_USE_OPENSSL],[1],[Define to use OpenSSL support.]) + AC_DEFINE([TORRENT_USE_LIBCRYPTO],[1],[Define to use libcrypto from OpenSSL.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_USE_OPENSSL -DTORRENT_USE_LIBCRYPTO " + ], [ + AC_MSG_ERROR([OpenSSL library not found. Try using --with-openssl=DIR or disabling encryption at all.]) + ]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_ENCRYPTION],[1],[Define to disable any encryption support and avoid linking against OpenSSL.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_ENCRYPTION " + ], + [AC_MSG_RESULT([$ARG_ENABLE_ENCRYPTION]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_ENCRYPTION". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether dht support should be enabled]) +AS_CASE(["$ARG_ENABLE_DHT"], + ["yes"|"on"], [ + AC_MSG_RESULT([yes]) + ], + ["no"|"off"], [ + AC_MSG_RESULT([no]) + AC_DEFINE([TORRENT_DISABLE_DHT],[1],[Define to disable the DHT support.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_DISABLE_DHT " + ], + [AC_MSG_RESULT([$ARG_ENABLE_DHT]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_DHT". Use either "yes" or "no".])] +) + +AS_IF([test "x$ac_cv_hidden_visibility_attribute" = "xyes"], [ + AS_IF([test "x$ARG_ENABLE_FULL_EXPORT" = "xno"], [ + CXXFLAGS="$CXXFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + CFLAGS="$CFLAGS -fvisibility=hidden" + LDFLAGS="$LDFLAGS -fvisibility=hidden -fvisibility-inlines-hidden" + ]) +]) + +AS_ECHO +AS_ECHO "Checking for extra build files:" + +AC_MSG_CHECKING([whether example files should be built]) +AS_CASE(["$ARG_ENABLE_EXAMPLES"], + ["yes"], [AC_MSG_RESULT([yes])], + ["no"], [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([$ARG_ENABLE_EXAMPLES]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_EXAMPLES". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether test files should be built]) +AS_CASE(["$ARG_ENABLE_TESTS"], + ["yes"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([TORRENT_EXPORT_EXTRA],[1],[Define to export additional symbols for unit tests.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_EXPORT_EXTRA " + ], + ["no"], [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([$ARG_ENABLE_TESTS]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_TESTS". Use either "yes" or "no".])] +) + +AC_MSG_CHECKING([whether python bindings should be built]) +AS_CASE(["$ARG_ENABLE_PYTHON_BINDING"], + ["yes"], [ + AC_MSG_RESULT([yes]) + + AM_PATH_PYTHON([2.4], [], AC_MSG_ERROR([Python interpreter not found.])) + AX_PYTHON_DEVEL([>= '2.4']) + AX_BOOST_PYTHON() + + AS_IF([test -z "$BOOST_PYTHON_LIB"], + [AC_MSG_ERROR([Boost.Python library not found. Try using --with-boost-python=lib.])]) + ], + ["no"], [ + AC_MSG_RESULT([no]) + ], + [AC_MSG_RESULT([$ARG_ENABLE_PYTHON_BINDING]) + AC_MSG_ERROR([Unknown option "$ARG_ENABLE_PYTHON_BINDING". Use either "yes" or "no".])] +) + +AS_ECHO +AS_ECHO "Checking for external libraries:" + +AC_MSG_CHECKING([whether to link against system libiconv]) +AS_CASE(["$ARG_WITH_LIBICONV"], + ["yes"], [ + AC_MSG_RESULT([yes]) + AM_ICONV() + AS_IF([test "x$am_cv_func_iconv" = "xyes"], [ + ICONV_LIBS=$LIBICONV + AC_SUBST([ICONV_LIBS]) + LIBS="$ICONV_LIBS $LIBS" + ], [ + AC_MSG_ERROR([Iconv library not found.]) + ]) + ], + ["no"], [ + ARG_WITH_LIBICONV="no" + ], + [AC_MSG_RESULT([$ARG_WITH_LIBICONV]) + AC_MSG_ERROR([Unknown option "$ARG_WITH_LIBICONV". Use either "yes" or "no".])] +) + +############################################################################### +# Setting conditional variables for Makefiles +############################################################################### + +AM_CONDITIONAL([ENABLE_DHT], [test "x$ARG_ENABLE_DHT" = "xyes" -o "x$ARG_ENABLE_DHT" = "xlogging" ]) +AM_CONDITIONAL([ENABLE_EXAMPLES], [test "x$ARG_ENABLE_EXAMPLES" = "xyes"]) +AM_CONDITIONAL([ENABLE_TESTS], [test "x$ARG_ENABLE_TESTS" = "xyes"]) +AM_CONDITIONAL([ENABLE_PYTHON_BINDING], [test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"]) + +AM_CONDITIONAL([WITH_OPENSSL], [test "x$ARG_ENABLE_ENCRYPTION" = "xyes" -o "x$ARG_ENABLE_ENCRYPTION" = "xon" ]) + + +############################################################################### +# Other useful stuff +############################################################################### + +AC_DEFINE([BOOST_ASIO_HAS_STD_CHRONO],[1],[Define to make sure asio uses std::chrono.]) +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_HAS_STD_CHRONO=1 " + +AC_DEFINE([BOOST_EXCEPTION_DISABLE],[1],[Define to disable the boost.exception features.]) +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_EXCEPTION_DISABLE " + +AC_DEFINE([BOOST_ASIO_ENABLE_CANCELIO],[1],[Define to enable cancel support in asio on windows XP and older.]) +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_ENABLE_CANCELIO " + +dnl Use possibly specific python install params +AC_ARG_VAR([PYTHON_INSTALL_PARAMS], [Set specific install parameters for python bindings.]) +AS_IF([test "x$PYTHON_INSTALL_PARAMS" = "x"], + [PYTHON_INSTALL_PARAMS='--prefix=$(DESTDIR)$(prefix)']) + +dnl Set some defines if we are building a shared library +AS_IF([test "x$enable_shared" = "xyes"], + [AC_DEFINE([TORRENT_BUILDING_SHARED],[1],[Define to exports functions and classes with default visibility in GCC.]) + COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DTORRENT_LINKING_SHARED "]) + +AC_SUBST(DEBUGFLAGS) +AC_SUBST(PYTHON_INSTALL_PARAMS) +AC_SUBST(COMPILETIME_OPTIONS) + +# Disable deprecated warnings for the Python binding builds. +PYTHON_CXXFLAGS="${PYTHON_CXXFLAGS} -Wno-deprecated-declarations" +AC_SUBST(PYTHON_CXXFLAGS) + +# Try to guess real git revision if any, fallback to hardcoded otherwise +GIT_REVISION=`git log -1 --format=format:%h 2>/dev/null` +AS_IF([test -z "$GIT_REVISION"], + [GIT_REVISION=`sed -n -e "s/^#define LIBTORRENT_REVISION \"\([0-9a-z]*\)\"$/\1/p" $(dirname $0)/include/libtorrent/version.hpp`]) + + +############################################################################### +# Generating Makefiles +############################################################################### + +AS_ECHO +AS_ECHO "Generating Makefiles:" + +AC_CONFIG_FILES( + [Makefile] + [src/Makefile] + [include/libtorrent/Makefile] + [examples/Makefile] + [test/Makefile] + [tools/Makefile] + [bindings/Makefile] + [bindings/python/Makefile] + [bindings/python/link_flags] + [bindings/python/compile_flags] + [bindings/python/compile_cmd] + [libtorrent-rasterbar.pc] +) + +AC_OUTPUT + + +############################################################################### +# Generating config.report +############################################################################### + +AS_ECHO +AS_ECHO "Configure script has finished system check." +AS_ECHO + +cat > config.report << END + Config results: + -=-=-=-=-=-=-=-=- + +Package: + name: ${PACKAGE_NAME} + version: ${PACKAGE_VERSION} + git revision: ${GIT_REVISION} + +Build environment: + build system: ${build} + host system: ${host} + target system: ${target} + +Compiler and linker flags: + CPPFlags: ${CPPFLAGS} + CFlags: ${CFLAGS} + CXXFlags: ${CXXFLAGS} + LDFlags: ${LDFLAGS} + Libs: ${LIBS} + Defs: ${DEFS} + +Build options: + deprecated functions: ${ARG_ENABLE_DEPRECATED:-yes} + debug build: ${ARG_ENABLE_DEBUG:-no} + invariant checks: ${ARG_ENABLE_INVARIANT:-no} + logging support: ${ARG_ENABLE_LOGGING:-yes} + +Features: + encryption support: ${ARG_ENABLE_ENCRYPTION:-yes} + dht support: ${ARG_ENABLE_DHT:-yes} + +Extra builds: + examples: ${ARG_ENABLE_EXAMPLES:-no} + tests: ${ARG_ENABLE_TESTS:-no} + python bindings: ${ARG_ENABLE_PYTHON_BINDING:-no} + +Pthread library: + CFlags: ${PTHREAD_CFLAGS} + Libs: ${PTHREAD_LIBS} + +Boost libraries: + version: ${BOOST_VERSION} + CPPFlags: ${BOOST_CPPFLAGS} + LDFlags: ${BOOST_LDFLAGS} + boost.system: ${BOOST_SYSTEM_LIB} +END + +AS_IF([test "x$ARG_ENABLE_PYTHON_BINDING" = "xyes"], [ +cat >> config.report << END + boost.python: ${BOOST_PYTHON_LIB} + +Python environment: + -automake- + binary: ${PYTHON} + version: ${PYTHON_VERSION} + platform: ${PYTHON_PLATFORM} + prefix: ${PYTHON_PREFIX} + exec_prefix: ${PYTHON_EXEC_PREFIX} + pythondir: ${pythondir} + pkgpythondir: ${pkgpythondir} + pyexecdir: ${pyexecdir} + pkgpyexecdir: ${pkgpyexecdir} + + -m4- + cppflags: ${PYTHON_CPPFLAGS} + ldflags: ${PYTHON_LDFLAGS} + extra libs: ${PYTHON_EXTRA_LIBS} + +END +]) + +cat >> config.report << END + +External libraries: + system libiconv: ${ARG_WITH_LIBICONV:-no} +END + +AS_IF([test "x$ARG_WITH_LIBICONV" = "xyes"], [ +cat >> config.report << END + +Iconv library: + Iconv Libs: ${ICONV_LIBS} +END +]) + +AS_IF([test "x$ARG_ENABLE_ENCRYPTION" = "xyes"], [ +cat >> config.report << END + +OpenSSL library: + OpenSSL Libs: ${OPENSSL_LIBS} + OpenSSL LDFlags: ${OPENSSL_LDFLAGS} + OpenSSL Includes: ${OPENSSL_INCLUDES} +END +]) + +cat config.report + +AS_ECHO +AS_ECHO "Type 'make' to compile $PACKAGE_STRING" +AS_ECHO "or type 'make V=1' for verbose compiling" +AS_ECHO "and then 'make install' to install it into $prefix" +AS_ECHO diff --git a/docs/bitcoin.png b/docs/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..af2f6b52cd3c4f1feb785f3313ab2b358860aa07 GIT binary patch literal 3411 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 literal 0 HcmV?d00001 diff --git a/docs/building.html b/docs/building.html new file mode 100644 index 0000000..67920d3 --- /dev/null +++ b/docs/building.html @@ -0,0 +1,821 @@ + + + + + + +building.rst + + + + + + + +
+
+ + + + +
+ + +++ + + + + + +
Author:Arvid Norberg, arvid@libtorrent.org
Version:1.2.9
+ +
+

downloading and building

+

To download the latest version of libtorrent, clone the github repository.

+

The build systems supported "out of the box" in libtorrent are boost-build v2 +(BBv2) and cmake. If you still can't build after following these instructions, +you can usually get help in the #libtorrent IRC channel on +irc.freenode.net.

+
+

Warning

+

A common mistake when building and linking against libtorrent is +to build with one set of configuration options (#defines) and +link against it using a different set of configuration options. Since +libtorrent has some code in header files, that code will not be +compatible with the built library if they see different configurations.

+

Always make sure that the same TORRENT_* macros are defined when you +link against libtorrent as when you build it.

+

Boost-build supports propagating configuration options to dependencies. +When building using the makefiles, this is handled by setting the +configuration options in the pkg-config file. Always use pkg-config +when linking against libtorrent.

+
+
+

building from git

+

To build libtorrent from git you need to clone the libtorrent repository from +github. If you downloaded a release tarball, you can skip this section.

+
+git clone https://github.com/arvidn/libtorrent.git
+
+
+
+

building with BBv2

+

The primary reason to use boost-build is that it will automatically build the +dependent boost libraries with the correct compiler settings, in order to +ensure that the build targets are link compatible (see boost guidelines +for some details on this issue).

+

Since BBv2 will build the boost libraries for you, you need the full boost +source package. Having boost installed via some package system is usually not +enough (and even if it is enough, the necessary environment variables are +usually not set by the package installer).

+

If you want to build against an installed copy of boost, you can skip directly +to step 3 (assuming you also have boost build installed).

+
+

Step 1: Download boost

+

If you want to build against boost installed on your system, you can skip this +strep. Just make sure to have BOOST_ROOT unset for the b2 invocation.

+

You'll find boost here.

+

Extract the archive to some directory where you want it. For the sake of this +guide, let's assume you extract the package to c:\boost_1_69_0. You'll +need at least version 1.58 of the boost library in order to build libtorrent.

+
+
+

Step 2: Setup BBv2

+

If you have installed boost-build via a package manager, you can skip this +step. If not, you need to build boost build from the boost source package.

+

First you need to build b2. You do this by opening a terminal (In windows, +run cmd). Change directory to c:\boost_1_68_0\tools\build. Then run the +script called bootstrap.bat or bootstrap.sh on a Unix system. This will +build b2 and place it in a directory src/engine/bin.<architecture>. +Copy the b2.exe (or b2 on a Unix system) to a place that's in you +shell's PATH. On Linux systems a place commonly used may be +/usr/local/bin or on Windows c:\windows (you can also add directories to +the search paths by modifying the environment variable called PATH).

+

Now you have b2 installed. b2 can be considered an interpreter +that the boost-build system is implemented on. So boost-build uses b2. +So, to complete the installation you need to make two more things. You need to +set the environment variable BOOST_BUILD_PATH. This is the path that tells +b2 where it can find boost-build, your configuration file and all the +toolsets (descriptions used by boost-build to know how to use different +compilers on different platforms). Assuming the boost install path above, set +it to c:\boost_1_68_0\tools\build.

+

To set an environment variable in windows, type for example:

+
+set BOOST_BUILD_PATH=c:\boost_1_68_0\tools\build\v2
+
+

In a terminal window.

+

The last thing to do is to configure which compiler(s) to use. Create a file +user-config.jam in your home directory. Depending on your platform and which +compiler you're using, you should add a line for each compiler and compiler +version you have installed on your system that you want to be able to use with +BBv2. For example, if you're using Microsoft Visual Studio 12 (2013), just add a +line:

+
+using msvc : 14.0 ;
+
+

If you use GCC, add the line:

+
+using gcc ;
+
+

If you have more than one version of GCC installed, you can add the +command line used to invoke g++ after the version number, like this:

+
+using gcc : 6.0 : g++-6 ;
+using gcc : 7.0 : g++-7 ;
+
+

Another toolset worth mentioning is the darwin toolset (for macOS). +From Tiger (10.4) macOS comes with both GCC 3.3 and GCC 4.0. Then you can +use the following toolsets:

+
+using darwin : 3.3 : g++-3.3 ;
+using darwin : 4.0 : g++-4.0 ;
+
+

Note that the spaces around the semi-colons and colons are important!

+

Also see the official installation instructions.

+
+
+

Step 3: Building libtorrent

+

When building libtorrent, boost is either picked up from system installed +locations or from a boost source package, if the BOOST_ROOT environment +variable is set pointing to one. If you're building boost from source, set +BOOST_ROOT to your boost directory, e.g. c:\boost_1_68_0.

+

Then the only thing left is simply to invoke b2. If you want to specify +a specific toolset to use (compiler) you can just add that to the command line. +For example:

+
+b2 msvc-14.0
+b2 gcc-7.0
+b2 darwin-4.0
+
+
+

Note

+

If the environment variable BOOST_ROOT is not set, the Jamfile will +attempt to link against "installed" boost libraries. i.e. assume the headers +and libraries are available in default search paths. +In this case it's critical that you build your project with the same version +of C++ and the same build flags as the system libraries were built with.

+
+

To build different versions you can also just add the name of the build +variant. Some default build variants in BBv2 are release, debug, +profile.

+

You can build libtorrent as a DLL too, by typing link=shared, or +link=static to build a static library.

+

If you want to explicitly say how to link against the runtime library, you +can set the runtime-link feature on the command line, either to shared +or static. Most operating systems will only allow linking shared against +the runtime, but on windows you can do both. Example:

+
+b2 msvc-14.0 link=static runtime-link=static
+
+
+

Note

+

When building on windows, the path boost-build puts targets in may be too +long. If you get an error message like: "The input line is long", try to +pass --hash on the b2 command line.

+
+
+

Warning

+

If you link statically to the runtime library, you cannot build libtorrent +as a shared library (DLL), since you will get separate heaps in the library +and in the client application. It will result in crashes and possibly link +errors.

+
+
+

Note

+

Some Linux systems requires linking against librt in order to access +the POSIX clock functions. If you get an error complaining about a missing +symbol clock_gettime, you have to give need-librt=yes on the +b2 command line. This will make libtorrent link against librt.

+
+
+

Note

+

When building on Solaris, you may have to specify stdlib=sun-stlport +on the b2 command line.

+
+

The build targets are put in a directory called bin, and under it they are +sorted in directories depending on the toolset and build variant used.

+

To build the examples, just change directory to the examples directory and +invoke b2 from there. To build and run the tests, go to the test +directory and run b2.

+

Note that if you're building on windows using the msvc toolset, you cannot run it +from a cygwin terminal, you'll have to run it from a cmd terminal. The same goes for +cygwin, if you're building with gcc in cygwin you'll have to run it from a cygwin terminal. +Also, make sure the paths are correct in the different environments. In cygwin, the paths +(BOOST_BUILD_PATH and BOOST_ROOT) should be in the typical Unix-format (e.g. +/cygdrive/c/boost_1_68_0). In the windows environment, they should have the typical +windows format (c:/boost_1_68_0).

+
+

Note

+

In Jamfiles, spaces are separators. It's typically easiest to avoid spaces +in path names. If you want spaces in your paths, make sure to quote them +with double quotes (").

+
+

The Jamfile will define NDEBUG when it's building a release build. +For more build configuration flags see Build configurations.

+

When enabling linking against openssl (by setting the crypto feature to +openssl) the Jamfile will look in some default directory for the openssl +headers and libraries. On macOS, it will look for the homebrew openssl package. +On Windows, it will look in C:\OpenSSL-Win32, or C:\OpenSSL-Win64 if +compiling in 64-bit.

+

To customize the library path and include path for openssl, set the features +openssl-lib and openssl-include respectively.

+

Build features:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
boost build featurevalues
boost-link
    +
  • static - links statically against the boost +libraries.
  • +
  • shared - links dynamically against the boost +libraries.
  • +
+
openssl-libcan be used to specify the directory where libssl +and libcrypto are installed (or the windows +counterparts).
openssl-includecan be used to specify the include directory where +the openssl headers are installed.
logging
    +
  • off - logging alerts disabled. The +reason to disable logging is to keep the binary +size low where that matters.
  • +
  • on - default. logging alerts available, +still need to be enabled by the alert mask.
  • +
+
dht
    +
  • on - build with DHT support
  • +
  • off - build without DHT support.
  • +
+
asserts
    +
  • auto - asserts are on if in debug mode
  • +
  • on - asserts are on, even in release mode
  • +
  • off - asserts are disabled
  • +
  • production - assertion failures are logged +to asserts.log in the current working +directory, but won't abort the process. +The file they are logged to can be customized +by setting the global pointer extern char +const* libtorrent_assert_log to a different +filename.
  • +
  • system use the libc assert macro
  • +
+
encryption
    +
  • on - encrypted bittorrent connections +enabled. (Message Stream encryption).(default)
  • +
  • off - turns off support for encrypted +connections. The shipped public domain SHA-1 +implementation is used.
  • +
+
mutable-torrents
    +
  • on - mutable torrents are supported +(BEP 38) (default).
  • +
  • off - mutable torrents are not supported.
  • +
+
crypto
    +
  • built-in - (default) uses built-in SHA-1 +implementation. In macOS/iOS it uses +CommonCrypto SHA-1 implementation.
  • +
  • openssl - links against openssl and +libcrypto to use for SHA-1 hashing. +This also enables HTTPS-tracker support and +support for bittorrent over SSL.
  • +
  • libcrypto - links against libcrypto +to use the SHA-1 implementation. (no SSL support)
  • +
  • gcrypt - links against libgcrypt +to use the SHA-1 implementation. (no SSL support)
  • +
+
openssl-version

This can be used on windows to link against the +special OpenSSL library names used on windows +prior to OpenSSL 1.1.

+
    +
  • 1.1 - link against the normal openssl +library name. (default)
  • +
  • pre1.1 - link against the old windows names +(i.e. ssleay32 and libeay32.
  • +
+
link
    +
  • static - builds libtorrent as a static +library (.a / .lib)
  • +
  • shared - builds libtorrent as a shared +library (.so / .dll).
  • +
+
runtime-link
    +
  • static - links statically against the +run-time library (if available on your +platform).
  • +
  • shared - link dynamically against the +run-time library (default).
  • +
+
variant
    +
  • debug - builds libtorrent with debug +information and invariant checks.
  • +
  • release - builds libtorrent in release mode +without invariant checks and with optimization.
  • +
  • profile - builds libtorrent with profile +information.
  • +
+
invariant-checks

This setting only affects debug builds (where +NDEBUG is not defined). It defaults to on.

+
    +
  • on - internal invariant checks are enabled.
  • +
  • off - internal invariant checks are +disabled. The resulting executable will run +faster than a regular debug build.
  • +
  • full - turns on extra expensive invariant +checks.
  • +
+
debug-symbols
    +
  • on - default for debug builds. This setting +is useful for building release builds with +symbols.
  • +
  • off - default for release builds.
  • +
+
deprecated-functions
    +
  • on - default. Includes deprecated functions +of the API (might produce warnings during build +when deprecated functions are used).
  • +
  • off - excludes deprecated functions from the +API. Generates build errors when deprecated +functions are used.
  • +
+
iconv
    +
  • auto - use iconv for string conversions for +Linux and MinGW and other posix platforms.
  • +
  • on - force use of iconv
  • +
  • off - force not using iconv (disables locale +awareness except on windows).
  • +
+
i2p
    +
  • on - default. build with I2P support
  • +
  • off - build without I2P support
  • +
+
profile-calls
    +
  • off - default. No additional call profiling.
  • +
  • on - Enable logging of stack traces of +calls into libtorrent that are blocking. On +session shutdown, a file blocking_calls.txt +is written with stack traces of blocking calls +ordered by the number of them.
  • +
+
utp-log
    +
  • off - default. Do not print verbose uTP +log.
  • +
  • on - Print verbose uTP log, used to debug +the uTP implementation.
  • +
+
picker-debugging
    +
  • off - default. no extra invariant checks in +piece picker.
  • +
  • on - include additional invariant checks in +piece picker. Used for testing the piece picker.
  • +
+
extensions
    +
  • on - enable extensions to the bittorrent +protocol.(default)
  • +
  • off - disable bittorrent extensions.
  • +
+
streaming
    +
  • on - enable streaming functionality. i.e. +set_piece_deadline(). (default)
  • +
  • off - disable streaming functionality.
  • +
+
super-seeding
    +
  • on - enable super seeding feature. (default)
  • +
  • off - disable super seeding feature
  • +
+
share-mode
    +
  • on - enable share-mode feature. (default)
  • +
  • off - disable share-mode feature
  • +
+
predictive-pieces
    +
  • on - enable predictive piece announce +feature. i.e. +settings_pack::predictive_piece_announce +(default)
  • +
  • off - disable feature.
  • +
+
fpic
    +
  • off - default. Build without specifying +-fPIC.
  • +
  • on - Force build with -fPIC (useful for +building a static library to be linked into a +shared library).
  • +
+
+

The variant feature is implicit, which means you don't need to specify +the name of the feature, just the value.

+

When building the example client on windows, you need to build with +link=static otherwise you may get unresolved external symbols for some +boost.program-options symbols.

+

For more information, see the Boost build v2 documentation, or more +specifically the section on built-in features.

+
+
+

Step 4: Installing libtorrent

+

To install libtorrent run b2 with the install target:

+
+b2 install --prefix=/usr/local
+
+

Change the value of the --prefix argument to install it in a different location.

+
+
+
+

building with cmake

+

First of all, you need to install cmake. Additionally you need a build +system to actually schedule builds, for example ninja.

+
+

Step 1: Generating the build system

+

Create a build directory for out-of-source build inside the libtorrent root directory:

+
+mkdir build
+
+

and cd there:

+
+cd build
+
+

Run cmake in the build directory, like this:

+
+cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=11 -G Ninja ..
+
+

The CMAKE_CXX_STANDARD has to be at least 11, but you may want to raise it +to 14 or 17 if your project use a newer version of the C++ standard.

+
+

Warning

+

The detection of boost sometimes fail in subtle ways. If you have the +BOOST_ROOT environment variable set, it may find the pre-built system +libraries, but use the header files from your source package. To avoid this, +invoke cmake with BOOST_ROOT set to an empty string: +BOOST_ROOT="" cmake ....

+
+

Other build options are:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + +
BUILD_SHARED_LIBSDefaults ON. Builds libtorrent as a shared +library.
static_runtimeDefaults OFF. Link libtorrent statically +against the runtime libraries.
build_testsDefaults OFF. Also build the libtorrent +tests.
build_examplesDefaults OFF. Also build the examples in the +examples directory.
build_toolsDefaults OFF. Also build the tools in the +tools directory.
python-bindingsDefaults OFF. Also build the python bindings +in bindings/python directory.
encryptionDefaults ON. Support trackers and bittorrent +over TLS, and obfuscated bittorrent connections.
+

Options are set on the cmake command line with the -D option or later on using ccmake or cmake-gui applications. cmake run outputs a summary of all available options and their current values.

+
+
+

Step 2: Building libtorrent

+

In the terminal, run:

+
+ninja -j8
+
+

in the build directory the number after -j specifies the number of parallel jobs to build in; you may omit this option to let ninja use all your cores).

+

If you enabled test in the configuration step, to run them, run:

+
+ctest -j8
+
+
+
+
+

building with VCPKG

+

You can download and install libtorrent using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:

+
+git clone https://github.com/Microsoft/vcpkg.git
+cd vcpkg
+./bootstrap-vcpkg.sh
+./vcpkg integrate install
+./vcpkg install libtorrent
+
+

The libtorrent port in vcpkg is kept up to date by Microsoft team members and community contributors. +If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.

+
+
+

building with other build systems

+

If you're building in MS Visual Studio, you may have to set the compiler +options "force conformance in for loop scope", "treat wchar_t as built-in +type" and "Enable Run-Time Type Info" to Yes.

+
+
+

build configurations

+

By default libtorrent is built In debug mode, and will have pretty expensive +invariant checks and asserts built into it. If you want to disable such checks +(you want to do that in a release build) you can see the table below for which +defines you can use to control the build.

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
macrodescription
NDEBUGIf you define this macro, all asserts, +invariant checks and general debug code will be +removed. Since there is quite a lot of code in +in header files in libtorrent, it may be +important to define the symbol consistently +across compilation units, including the clients +files. Potential problems is different +compilation units having different views of +structs and class layouts and sizes.
TORRENT_DISABLE_LOGGINGThis macro will disable support for logging +alerts, like log_alert, torrent_log_alert and +peer_log_alert. With this build flag, you +cannot enable those alerts.
TORRENT_DISABLE_SUPERSEEDINGThis macro will disable support for super +seeding. The settings will exist, but will not +have an effect, when this macro is defined.
TORRENT_DISABLE_SHARE_MODEThis macro will disable support for share-mode. +i.e. the mode to maximize upload/download +ratio for a torrent.
TORRENT_DISABLE_MUTABLE_TORRENTSDisables mutable torrent support (BEP 38)
TORRENT_DISABLE_STREAMINGDisables set_piece_deadline() and associated +functionality.
TORRENT_DISABLE_PREDICTIVE_PIECESDisables +settings_pack::predictive_piece_announce +feature.
TORRENT_LINKING_SHAREDIf this is defined when including the +libtorrent headers, the classes and functions +will be tagged with __declspec(dllimport) +on msvc and default visibility on GCC 4 and +later. Set this in your project if you're +linking against libtorrent as a shared library. +(This is set by the Jamfile when +link=shared is set).
TORRENT_BUILDING_SHAREDIf this is defined, the functions and classes +in libtorrent are marked with +__declspec(dllexport) on msvc, or with +default visibility on GCC 4 and later. This +should be defined when building libtorrent as +a shared library. (This is set by the Jamfile +when link=shared is set).
TORRENT_DISABLE_DHTIf this is defined, the support for trackerless +torrents will be disabled.
TORRENT_DISABLE_ENCRYPTIONThis will disable any encryption support and +the dependencies of a crypto library. +Encryption support is the peer connection +encrypted supported by clients such as +uTorrent, Azureus and KTorrent. +If this is not defined, either +TORRENT_USE_LIBCRYPTO or +TORRENT_USE_LIBGCRYPT must be defined.
TORRENT_DISABLE_EXTENSIONSWhen defined, libtorrent plugin support is +disabled along with support for the extension +handshake (BEP 10).
TORRENT_USE_INVARIANT_CHECKSIf defined to non-zero, this will enable +internal invariant checks in libtorrent. +The invariant checks can sometimes +be quite expensive, they typically don't scale +very well.
TORRENT_EXPENSIVE_INVARIANT_CHECKSThis will enable extra expensive invariant +checks. Useful for finding particular bugs +or for running before releases.
TORRENT_NO_DEPRECATEThis will exclude all deprecated functions from +the header files and source files.
TORRENT_PRODUCTION_ASSERTSDefine to either 0 or 1. Enables assert logging +in release builds.
TORRENT_USE_ASSERTSDefine as 0 to disable asserts unconditionally.
TORRENT_USE_SYSTEM_ASSERTSUses the libc assert macro rather then the +custom one.
TORRENT_USE_OPENSSLLink against libssl for SSL support. Must +be combined with TORRENT_USE_LIBCRYPTO
TORRENT_USE_LIBCRYPTOLink against libcrypto for SHA-1 support +and other hashing algorithms.
TORRENT_USE_LIBGCRYPTLink against libgcrypt for SHA-1 support +and other hashing algorithms.
+

If you experience that libtorrent uses unreasonable amounts of CPU, it will +definitely help to define NDEBUG, since it will remove the invariant checks +within the library.

+
+
+

building openssl for windows

+

To build openssl for windows with Visual Studio 7.1 (2003) execute the following commands +in a command shell:

+
+perl Configure VC-WIN32 --prefix="c:/openssl
+call ms\do_nasm
+call "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\bin\vcvars32.bat"
+nmake -f ms\nt.mak
+copy inc32\openssl "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\include\"
+copy out32\libeay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib"
+copy out32\ssleay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib"
+
+

This will also install the headers and library files in the visual studio directories to +be picked up by libtorrent.

+
+
+ +
+
+
+ +
+ +
+ + diff --git a/docs/building.rst b/docs/building.rst new file mode 100644 index 0000000..b89de48 --- /dev/null +++ b/docs/building.rst @@ -0,0 +1,645 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +downloading and building +======================== + +To download the latest version of libtorrent, clone the `github repository`__. + +__ https://github.com/arvidn/libtorrent + +The build systems supported "out of the box" in libtorrent are boost-build v2 +(BBv2) and cmake. If you still can't build after following these instructions, +you can usually get help in the ``#libtorrent`` IRC channel on +``irc.freenode.net``. + +.. warning:: + + A common mistake when building and linking against libtorrent is + to build with one set of configuration options (#defines) and + link against it using a different set of configuration options. Since + libtorrent has some code in header files, that code will not be + compatible with the built library if they see different configurations. + + Always make sure that the same TORRENT_* macros are defined when you + link against libtorrent as when you build it. + + Boost-build supports propagating configuration options to dependencies. + When building using the makefiles, this is handled by setting the + configuration options in the pkg-config file. Always use pkg-config + when linking against libtorrent. + +building from git +----------------- + +To build libtorrent from git you need to clone the libtorrent repository from +github. If you downloaded a release `tarball`__, you can skip this section. + +__ https://github.com/arvidn/libtorrent/releases/latest + +:: + + git clone https://github.com/arvidn/libtorrent.git + + +building with BBv2 +------------------ + +The primary reason to use boost-build is that it will automatically build the +dependent boost libraries with the correct compiler settings, in order to +ensure that the build targets are link compatible (see `boost guidelines`__ +for some details on this issue). + +__ https://boost.org/more/separate_compilation.html + +Since BBv2 will build the boost libraries for you, you need the full boost +source package. Having boost installed via some package system is usually not +enough (and even if it is enough, the necessary environment variables are +usually not set by the package installer). + +If you want to build against an installed copy of boost, you can skip directly +to step 3 (assuming you also have boost build installed). + + +Step 1: Download boost +~~~~~~~~~~~~~~~~~~~~~~ + +If you want to build against boost installed on your system, you can skip this +strep. Just make sure to have `BOOST_ROOT` unset for the `b2` invocation. + +You'll find boost here__. + +__ https://www.boost.org/users/download/#live + +Extract the archive to some directory where you want it. For the sake of this +guide, let's assume you extract the package to ``c:\boost_1_69_0``. You'll +need at least version 1.58 of the boost library in order to build libtorrent. + + +Step 2: Setup BBv2 +~~~~~~~~~~~~~~~~~~ + +If you have installed ``boost-build`` via a package manager, you can skip this +step. If not, you need to build boost build from the boost source package. + +First you need to build ``b2``. You do this by opening a terminal (In windows, +run ``cmd``). Change directory to ``c:\boost_1_68_0\tools\build``. Then run the +script called ``bootstrap.bat`` or ``bootstrap.sh`` on a Unix system. This will +build ``b2`` and place it in a directory ``src/engine/bin.``. +Copy the ``b2.exe`` (or ``b2`` on a Unix system) to a place that's in you +shell's ``PATH``. On Linux systems a place commonly used may be +``/usr/local/bin`` or on Windows ``c:\windows`` (you can also add directories to +the search paths by modifying the environment variable called ``PATH``). + +Now you have ``b2`` installed. ``b2`` can be considered an interpreter +that the boost-build system is implemented on. So boost-build uses ``b2``. +So, to complete the installation you need to make two more things. You need to +set the environment variable ``BOOST_BUILD_PATH``. This is the path that tells +``b2`` where it can find boost-build, your configuration file and all the +toolsets (descriptions used by boost-build to know how to use different +compilers on different platforms). Assuming the boost install path above, set +it to ``c:\boost_1_68_0\tools\build``. + +To set an environment variable in windows, type for example:: + + set BOOST_BUILD_PATH=c:\boost_1_68_0\tools\build\v2 + +In a terminal window. + +The last thing to do is to configure which compiler(s) to use. Create a file +``user-config.jam`` in your home directory. Depending on your platform and which +compiler you're using, you should add a line for each compiler and compiler +version you have installed on your system that you want to be able to use with +BBv2. For example, if you're using Microsoft Visual Studio 12 (2013), just add a +line:: + + using msvc : 14.0 ; + +If you use GCC, add the line:: + + using gcc ; + +If you have more than one version of GCC installed, you can add the +command line used to invoke g++ after the version number, like this:: + + using gcc : 6.0 : g++-6 ; + using gcc : 7.0 : g++-7 ; + +Another toolset worth mentioning is the ``darwin`` toolset (for macOS). +From Tiger (10.4) macOS comes with both GCC 3.3 and GCC 4.0. Then you can +use the following toolsets:: + + using darwin : 3.3 : g++-3.3 ; + using darwin : 4.0 : g++-4.0 ; + +Note that the spaces around the semi-colons and colons are important! + +Also see the `official installation instructions`_. + +.. _`official installation instructions`: https://www.boost.org/doc/html/bbv2/installation.html + + +Step 3: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When building libtorrent, boost is either picked up from system installed +locations or from a boost source package, if the ``BOOST_ROOT`` environment +variable is set pointing to one. If you're building boost from source, set +``BOOST_ROOT`` to your boost directory, e.g. ``c:\boost_1_68_0``. + +Then the only thing left is simply to invoke ``b2``. If you want to specify +a specific toolset to use (compiler) you can just add that to the command line. +For example:: + + b2 msvc-14.0 + b2 gcc-7.0 + b2 darwin-4.0 + +.. note:: + + If the environment variable ``BOOST_ROOT`` is not set, the Jamfile will + attempt to link against "installed" boost libraries. i.e. assume the headers + and libraries are available in default search paths. + In this case it's critical that you build your project with the same version + of C++ and the same build flags as the system libraries were built with. + +To build different versions you can also just add the name of the build +variant. Some default build variants in BBv2 are ``release``, ``debug``, +``profile``. + +You can build libtorrent as a DLL too, by typing ``link=shared``, or +``link=static`` to build a static library. + +If you want to explicitly say how to link against the runtime library, you +can set the ``runtime-link`` feature on the command line, either to ``shared`` +or ``static``. Most operating systems will only allow linking shared against +the runtime, but on windows you can do both. Example:: + + b2 msvc-14.0 link=static runtime-link=static + +.. note:: + + When building on windows, the path boost-build puts targets in may be too + long. If you get an error message like: "The input line is long", try to + pass --hash on the ``b2`` command line. + +.. warning:: + + If you link statically to the runtime library, you cannot build libtorrent + as a shared library (DLL), since you will get separate heaps in the library + and in the client application. It will result in crashes and possibly link + errors. + +.. note:: + + Some Linux systems requires linking against ``librt`` in order to access + the POSIX clock functions. If you get an error complaining about a missing + symbol ``clock_gettime``, you have to give ``need-librt=yes`` on the + b2 command line. This will make libtorrent link against ``librt``. + +.. note:: + + When building on Solaris, you may have to specify ``stdlib=sun-stlport`` + on the b2 command line. + +The build targets are put in a directory called bin, and under it they are +sorted in directories depending on the toolset and build variant used. + +To build the examples, just change directory to the examples directory and +invoke ``b2`` from there. To build and run the tests, go to the test +directory and run ``b2``. + +Note that if you're building on windows using the ``msvc`` toolset, you cannot run it +from a cygwin terminal, you'll have to run it from a ``cmd`` terminal. The same goes for +cygwin, if you're building with gcc in cygwin you'll have to run it from a cygwin terminal. +Also, make sure the paths are correct in the different environments. In cygwin, the paths +(``BOOST_BUILD_PATH`` and ``BOOST_ROOT``) should be in the typical Unix-format (e.g. +``/cygdrive/c/boost_1_68_0``). In the windows environment, they should have the typical +windows format (``c:/boost_1_68_0``). + +.. note:: + In Jamfiles, spaces are separators. It's typically easiest to avoid spaces + in path names. If you want spaces in your paths, make sure to quote them + with double quotes ("). + +The ``Jamfile`` will define ``NDEBUG`` when it's building a release build. +For more build configuration flags see `Build configurations`_. + +When enabling linking against openssl (by setting the ``crypto`` feature to +``openssl``) the Jamfile will look in some default directory for the openssl +headers and libraries. On macOS, it will look for the homebrew openssl package. +On Windows, it will look in ``C:\OpenSSL-Win32``, or ``C:\OpenSSL-Win64`` if +compiling in 64-bit. + +To customize the library path and include path for openssl, set the features +``openssl-lib`` and ``openssl-include`` respectively. + +Build features: + ++--------------------------+----------------------------------------------------+ +| boost build feature | values | ++==========================+====================================================+ +| ``boost-link`` | * ``static`` - links statically against the boost | +| | libraries. | +| | * ``shared`` - links dynamically against the boost | +| | libraries. | ++--------------------------+----------------------------------------------------+ +| ``openssl-lib`` | can be used to specify the directory where libssl | +| | and libcrypto are installed (or the windows | +| | counterparts). | ++--------------------------+----------------------------------------------------+ +| ``openssl-include`` | can be used to specify the include directory where | +| | the openssl headers are installed. | ++--------------------------+----------------------------------------------------+ +| ``logging`` | * ``off`` - logging alerts disabled. The | +| | reason to disable logging is to keep the binary | +| | size low where that matters. | +| | * ``on`` - default. logging alerts available, | +| | still need to be enabled by the alert mask. | ++--------------------------+----------------------------------------------------+ +| ``dht`` | * ``on`` - build with DHT support | +| | * ``off`` - build without DHT support. | ++--------------------------+----------------------------------------------------+ +| ``asserts`` | * ``auto`` - asserts are on if in debug mode | +| | * ``on`` - asserts are on, even in release mode | +| | * ``off`` - asserts are disabled | +| | * ``production`` - assertion failures are logged | +| | to ``asserts.log`` in the current working | +| | directory, but won't abort the process. | +| | The file they are logged to can be customized | +| | by setting the global pointer ``extern char | +| | const* libtorrent_assert_log`` to a different | +| | filename. | +| | * ``system`` use the libc assert macro | ++--------------------------+----------------------------------------------------+ +| ``encryption`` | * ``on`` - encrypted bittorrent connections | +| | enabled. (Message Stream encryption).(default) | +| | * ``off`` - turns off support for encrypted | +| | connections. The shipped public domain SHA-1 | +| | implementation is used. | ++--------------------------+----------------------------------------------------+ +| ``mutable-torrents`` | * ``on`` - mutable torrents are supported | +| | (`BEP 38`_) (default). | +| | * ``off`` - mutable torrents are not supported. | ++--------------------------+----------------------------------------------------+ +| ``crypto`` | * ``built-in`` - (default) uses built-in SHA-1 | +| | implementation. In macOS/iOS it uses | +| | CommonCrypto SHA-1 implementation. | +| | * ``openssl`` - links against openssl and | +| | libcrypto to use for SHA-1 hashing. | +| | This also enables HTTPS-tracker support and | +| | support for bittorrent over SSL. | +| | * ``libcrypto`` - links against libcrypto | +| | to use the SHA-1 implementation. (no SSL support)| +| | * ``gcrypt`` - links against libgcrypt | +| | to use the SHA-1 implementation. (no SSL support)| ++--------------------------+----------------------------------------------------+ +| ``openssl-version`` | This can be used on windows to link against the | +| | special OpenSSL library names used on windows | +| | prior to OpenSSL 1.1. | +| | | +| | * ``1.1`` - link against the normal openssl | +| | library name. (default) | +| | * ``pre1.1`` - link against the old windows names | +| | (i.e. ``ssleay32`` and ``libeay32``. | ++--------------------------+----------------------------------------------------+ +| ``link`` | * ``static`` - builds libtorrent as a static | +| | library (.a / .lib) | +| | * ``shared`` - builds libtorrent as a shared | +| | library (.so / .dll). | ++--------------------------+----------------------------------------------------+ +| ``runtime-link`` | * ``static`` - links statically against the | +| | run-time library (if available on your | +| | platform). | +| | * ``shared`` - link dynamically against the | +| | run-time library (default). | ++--------------------------+----------------------------------------------------+ +| ``variant`` | * ``debug`` - builds libtorrent with debug | +| | information and invariant checks. | +| | * ``release`` - builds libtorrent in release mode | +| | without invariant checks and with optimization. | +| | * ``profile`` - builds libtorrent with profile | +| | information. | ++--------------------------+----------------------------------------------------+ +| ``invariant-checks`` | This setting only affects debug builds (where | +| | ``NDEBUG`` is not defined). It defaults to ``on``. | +| | | +| | * ``on`` - internal invariant checks are enabled. | +| | * ``off`` - internal invariant checks are | +| | disabled. The resulting executable will run | +| | faster than a regular debug build. | +| | * ``full`` - turns on extra expensive invariant | +| | checks. | ++--------------------------+----------------------------------------------------+ +| ``debug-symbols`` | * ``on`` - default for debug builds. This setting | +| | is useful for building release builds with | +| | symbols. | +| | * ``off`` - default for release builds. | ++--------------------------+----------------------------------------------------+ +| ``deprecated-functions`` | * ``on`` - default. Includes deprecated functions | +| | of the API (might produce warnings during build | +| | when deprecated functions are used). | +| | * ``off`` - excludes deprecated functions from the | +| | API. Generates build errors when deprecated | +| | functions are used. | ++--------------------------+----------------------------------------------------+ +| ``iconv`` | * ``auto`` - use iconv for string conversions for | +| | Linux and MinGW and other posix platforms. | +| | * ``on`` - force use of iconv | +| | * ``off`` - force not using iconv (disables locale | +| | awareness except on windows). | ++--------------------------+----------------------------------------------------+ +| ``i2p`` | * ``on`` - default. build with I2P support | +| | * ``off`` - build without I2P support | ++--------------------------+----------------------------------------------------+ +| ``profile-calls`` | * ``off`` - default. No additional call profiling. | +| | * ``on`` - Enable logging of stack traces of | +| | calls into libtorrent that are blocking. On | +| | session shutdown, a file ``blocking_calls.txt`` | +| | is written with stack traces of blocking calls | +| | ordered by the number of them. | ++--------------------------+----------------------------------------------------+ +| ``utp-log`` | * ``off`` - default. Do not print verbose uTP | +| | log. | +| | * ``on`` - Print verbose uTP log, used to debug | +| | the uTP implementation. | ++--------------------------+----------------------------------------------------+ +| ``picker-debugging`` | * ``off`` - default. no extra invariant checks in | +| | piece picker. | +| | * ``on`` - include additional invariant checks in | +| | piece picker. Used for testing the piece picker. | ++--------------------------+----------------------------------------------------+ +| ``extensions`` | * ``on`` - enable extensions to the bittorrent | +| | protocol.(default) | +| | * ``off`` - disable bittorrent extensions. | ++--------------------------+----------------------------------------------------+ +| ``streaming`` | * ``on`` - enable streaming functionality. i.e. | +| | ``set_piece_deadline()``. (default) | +| | * ``off`` - disable streaming functionality. | ++--------------------------+----------------------------------------------------+ +| ``super-seeding`` | * ``on`` - enable super seeding feature. (default) | +| | * ``off`` - disable super seeding feature | ++--------------------------+----------------------------------------------------+ +| ``share-mode`` | * ``on`` - enable share-mode feature. (default) | +| | * ``off`` - disable share-mode feature | ++--------------------------+----------------------------------------------------+ +| ``predictive-pieces`` | * ``on`` - enable predictive piece announce | +| | feature. i.e. | +| | settings_pack::predictive_piece_announce | +| | (default) | +| | * ``off`` - disable feature. | ++--------------------------+----------------------------------------------------+ +| ``fpic`` | * ``off`` - default. Build without specifying | +| | ``-fPIC``. | +| | * ``on`` - Force build with ``-fPIC`` (useful for | +| | building a static library to be linked into a | +| | shared library). | ++--------------------------+----------------------------------------------------+ + +The ``variant`` feature is *implicit*, which means you don't need to specify +the name of the feature, just the value. + +When building the example client on windows, you need to build with +``link=static`` otherwise you may get unresolved external symbols for some +boost.program-options symbols. + +For more information, see the `Boost build v2 documentation`__, or more +specifically `the section on built-in features`__. + +__ https://boostorg.github.io/build/manual/develop/index.html +__ https://boostorg.github.io/build/manual/develop/index.html#bbv2.overview.builtins.features + + +Step 4: Installing libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To install libtorrent run ``b2`` with the ``install`` target:: + + b2 install --prefix=/usr/local + +Change the value of the ``--prefix`` argument to install it in a different location. + +building with cmake +------------------- + +First of all, you need to install ``cmake``. Additionally you need a build +system to actually schedule builds, for example ``ninja``. + +Step 1: Generating the build system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Create a build directory for out-of-source build inside the libtorrent root directory:: + + mkdir build + +and ``cd`` there:: + + cd build + +Run ``cmake`` in the build directory, like this:: + + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=11 -G Ninja .. + +The ``CMAKE_CXX_STANDARD`` has to be at least 11, but you may want to raise it +to ``14`` or ``17`` if your project use a newer version of the C++ standard. + +.. warning:: + + The detection of boost sometimes fail in subtle ways. If you have the + ``BOOST_ROOT`` environment variable set, it may find the pre-built system + libraries, but use the header files from your source package. To avoid this, + invoke ``cmake`` with ``BOOST_ROOT`` set to an empty string: + ``BOOST_ROOT="" cmake ...``. + +Other build options are: + ++-----------------------+---------------------------------------------------+ +| ``BUILD_SHARED_LIBS`` | Defaults ``ON``. Builds libtorrent as a shared | +| | library. | ++-----------------------+---------------------------------------------------+ +| ``static_runtime`` | Defaults ``OFF``. Link libtorrent statically | +| | against the runtime libraries. | ++-----------------------+---------------------------------------------------+ +| ``build_tests`` | Defaults ``OFF``. Also build the libtorrent | +| | tests. | ++-----------------------+---------------------------------------------------+ +| ``build_examples`` | Defaults ``OFF``. Also build the examples in the | +| | examples directory. | ++-----------------------+---------------------------------------------------+ +| ``build_tools`` | Defaults ``OFF``. Also build the tools in the | +| | tools directory. | ++-----------------------+---------------------------------------------------+ +| ``python-bindings`` | Defaults ``OFF``. Also build the python bindings | +| | in bindings/python directory. | ++-----------------------+---------------------------------------------------+ +| ``encryption`` | Defaults ``ON``. Support trackers and bittorrent | +| | over TLS, and obfuscated bittorrent connections. | ++-----------------------+---------------------------------------------------+ + +Options are set on the ``cmake`` command line with the ``-D`` option or later on using ``ccmake`` or ``cmake-gui`` applications. ``cmake`` run outputs a summary of all available options and their current values. + +Step 2: Building libtorrent +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the terminal, run:: + + ninja -j8 + +in the build directory the number after ``-j`` specifies the number of parallel jobs to build in; you may omit this option to let ``ninja`` use all your cores). + +If you enabled test in the configuration step, to run them, run:: + + ctest -j8 + +building with VCPKG +------------------- + +You can download and install libtorrent using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager:: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install libtorrent + +The libtorrent port in vcpkg is kept up to date by Microsoft team members and community contributors. +If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +building with other build systems +--------------------------------- + +If you're building in MS Visual Studio, you may have to set the compiler +options "force conformance in for loop scope", "treat wchar_t as built-in +type" and "Enable Run-Time Type Info" to Yes. + +build configurations +-------------------- + +By default libtorrent is built In debug mode, and will have pretty expensive +invariant checks and asserts built into it. If you want to disable such checks +(you want to do that in a release build) you can see the table below for which +defines you can use to control the build. + ++----------------------------------------+-------------------------------------------------+ +| macro | description | ++========================================+=================================================+ +| ``NDEBUG`` | If you define this macro, all asserts, | +| | invariant checks and general debug code will be | +| | removed. Since there is quite a lot of code in | +| | in header files in libtorrent, it may be | +| | important to define the symbol consistently | +| | across compilation units, including the clients | +| | files. Potential problems is different | +| | compilation units having different views of | +| | structs and class layouts and sizes. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_LOGGING`` | This macro will disable support for logging | +| | alerts, like log_alert, torrent_log_alert and | +| | peer_log_alert. With this build flag, you | +| | cannot enable those alerts. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_SUPERSEEDING`` | This macro will disable support for super | +| | seeding. The settings will exist, but will not | +| | have an effect, when this macro is defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_SHARE_MODE`` | This macro will disable support for share-mode. | +| | i.e. the mode to maximize upload/download | +| | ratio for a torrent. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_MUTABLE_TORRENTS`` | Disables mutable torrent support (`BEP 38`_) | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_STREAMING`` | Disables set_piece_deadline() and associated | +| | functionality. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_PREDICTIVE_PIECES`` | Disables | +| | settings_pack::predictive_piece_announce | +| | feature. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_LINKING_SHARED`` | If this is defined when including the | +| | libtorrent headers, the classes and functions | +| | will be tagged with ``__declspec(dllimport)`` | +| | on msvc and default visibility on GCC 4 and | +| | later. Set this in your project if you're | +| | linking against libtorrent as a shared library. | +| | (This is set by the Jamfile when | +| | ``link=shared`` is set). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_BUILDING_SHARED`` | If this is defined, the functions and classes | +| | in libtorrent are marked with | +| | ``__declspec(dllexport)`` on msvc, or with | +| | default visibility on GCC 4 and later. This | +| | should be defined when building libtorrent as | +| | a shared library. (This is set by the Jamfile | +| | when ``link=shared`` is set). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_DHT`` | If this is defined, the support for trackerless | +| | torrents will be disabled. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_ENCRYPTION`` | This will disable any encryption support and | +| | the dependencies of a crypto library. | +| | Encryption support is the peer connection | +| | encrypted supported by clients such as | +| | uTorrent, Azureus and KTorrent. | +| | If this is not defined, either | +| | ``TORRENT_USE_LIBCRYPTO`` or | +| | ``TORRENT_USE_LIBGCRYPT`` must be defined. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_DISABLE_EXTENSIONS`` | When defined, libtorrent plugin support is | +| | disabled along with support for the extension | +| | handshake (BEP 10). | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_INVARIANT_CHECKS`` | If defined to non-zero, this will enable | +| | internal invariant checks in libtorrent. | +| | The invariant checks can sometimes | +| | be quite expensive, they typically don't scale | +| | very well. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_EXPENSIVE_INVARIANT_CHECKS`` | This will enable extra expensive invariant | +| | checks. Useful for finding particular bugs | +| | or for running before releases. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_NO_DEPRECATE`` | This will exclude all deprecated functions from | +| | the header files and source files. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_PRODUCTION_ASSERTS`` | Define to either 0 or 1. Enables assert logging | +| | in release builds. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_ASSERTS`` | Define as 0 to disable asserts unconditionally. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_SYSTEM_ASSERTS`` | Uses the libc assert macro rather then the | +| | custom one. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_OPENSSL`` | Link against ``libssl`` for SSL support. Must | +| | be combined with ``TORRENT_USE_LIBCRYPTO`` | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_LIBCRYPTO`` | Link against ``libcrypto`` for SHA-1 support | +| | and other hashing algorithms. | ++----------------------------------------+-------------------------------------------------+ +| ``TORRENT_USE_LIBGCRYPT`` | Link against ``libgcrypt`` for SHA-1 support | +| | and other hashing algorithms. | ++----------------------------------------+-------------------------------------------------+ + +.. _`BEP 38`: https://www.bittorrent.org/beps/bep_0038.html + +If you experience that libtorrent uses unreasonable amounts of CPU, it will +definitely help to define ``NDEBUG``, since it will remove the invariant checks +within the library. + +building openssl for windows +---------------------------- + +To build openssl for windows with Visual Studio 7.1 (2003) execute the following commands +in a command shell:: + + perl Configure VC-WIN32 --prefix="c:/openssl + call ms\do_nasm + call "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\bin\vcvars32.bat" + nmake -f ms\nt.mak + copy inc32\openssl "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\include\" + copy out32\libeay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + copy out32\ssleay32.lib "C:\Program Files\Microsoft Visual Studio .NET 2003\vc7\lib" + +This will also install the headers and library files in the visual studio directories to +be picked up by libtorrent. diff --git a/docs/client_test.html b/docs/client_test.html new file mode 100644 index 0000000..0a23d68 --- /dev/null +++ b/docs/client_test.html @@ -0,0 +1,111 @@ + + + + + + +client_test example program + + + + + + + +
+
+ + + + +
+

client_test example program

+ +++ + + + + + +
Author:Arvid Norberg, arvid@libtorrent.org
Version:1.2.9
+

Client test is a, more or less, complete bittorrent client. It lacks most +settings and you can't start or stop torrents once you've started it. All +the settings are hard coded. The command line arguments are:

+
+client_test <filename1.torrent> <filename2.torrent> ...
+
+

You can start any number of torrent downloads/seeds via the command line. +If one argument starts with http:// it is interpreted as a tracker +announce url, and it expects an info-hash as the next argument. The info-hash +has to be hex-encoded. For example: 2410d4554d5ed856d69f426c38791673c59f4418. +If you pass an announce url and info-hash, a torrent-less download is started. +It relies on that at least one peer on the tracker is running a libtorrent based +client and has the metadata (.torrent file). The metadata extension in +libtorrent will then download it from that peer (or from those peers if more +than one).

+

While running, the client_test sample will look something like this:

+screenshot.png +

The commands available in the client are:

+
    +
  • q quits the client (there will be a delay while the client waits +for tracker responses)
  • +
  • l toggle log. Will display the log at the bottom, informing about +tracker and peer events.
  • +
  • i toggles torrent info. Will show the peer list for each torrent.
  • +
  • d toggle download info. Will show the block list for each torrent, +showing downloaded and requested blocks.
  • +
  • p pause all torrents.
  • +
  • u resume all torrents.
  • +
  • r force tracker reannounce for all torrents.
  • +
  • f toggle show file progress. Displays a list of all files and the +download progress for each file.
  • +
+

The list at the bottom (shown if you press d) shows which blocks has +been requested from which peer. The green background means that it has been +downloaded. It shows that fast peers will prefer to request whole pieces +instead of downloading parts of pieces. It may make it easier to determine +which peer that sent the corrupt data if a piece fails the hash test.

+ +
+
+
+ +
+ +
+ + diff --git a/docs/client_test.rst b/docs/client_test.rst new file mode 100644 index 0000000..2e12305 --- /dev/null +++ b/docs/client_test.rst @@ -0,0 +1,47 @@ +=========================== +client_test example program +=========================== + +.. include:: header.rst + +Client test is a, more or less, complete bittorrent client. It lacks most +settings and you can't start or stop torrents once you've started it. All +the settings are hard coded. The command line arguments are:: + + client_test ... + +You can start any number of torrent downloads/seeds via the command line. +If one argument starts with ``http://`` it is interpreted as a tracker +announce url, and it expects an info-hash as the next argument. The info-hash +has to be hex-encoded. For example: ``2410d4554d5ed856d69f426c38791673c59f4418``. +If you pass an announce url and info-hash, a torrent-less download is started. +It relies on that at least one peer on the tracker is running a libtorrent based +client and has the metadata (.torrent file). The metadata extension in +libtorrent will then download it from that peer (or from those peers if more +than one). + +While running, the ``client_test`` sample will look something like this: + +.. image:: screenshot.png + +The commands available in the client are: + +* ``q`` quits the client (there will be a delay while the client waits + for tracker responses) +* ``l`` toggle log. Will display the log at the bottom, informing about + tracker and peer events. +* ``i`` toggles torrent info. Will show the peer list for each torrent. +* ``d`` toggle download info. Will show the block list for each torrent, + showing downloaded and requested blocks. +* ``p`` pause all torrents. +* ``u`` resume all torrents. +* ``r`` force tracker reannounce for all torrents. +* ``f`` toggle show file progress. Displays a list of all files and the + download progress for each file. + +The list at the bottom (shown if you press ``d``) shows which blocks has +been requested from which peer. The green background means that it has been +downloaded. It shows that fast peers will prefer to request whole pieces +instead of downloading parts of pieces. It may make it easier to determine +which peer that sent the corrupt data if a piece fails the hash test. + diff --git a/docs/complete_bit_prefixes.png b/docs/complete_bit_prefixes.png new file mode 100644 index 0000000000000000000000000000000000000000..3bc73874d9f321c8e4848f6084c41fbc1a3d0371 GIT binary patch literal 7795 zcmZ8`2{@GB`}bfbW5f&w5#}+JwR}-oiZKjE%9^NTYbq@YktGI08SBiDt+Fc>g_5OY z%QCVgk`h@Oq!=3eGR^<-eSg3A{a^2OJSK$=+N2pXJ4ikoEFA-JoX5MSJhO;X7dIi;A{{Q_#1&u!0u0Q z{`WgUM#dKu1p?`g?fDJa7Lf<(`N%`3@*?aOA|rX`kc~+Hne@O{S8DDEFE0=0 zMFPBs5H^K<7t%kHH$t~Fak`=viH1a?(UB(L6B+5z80o=_+?L;jmj^x`ygXlCV27h59YFy?v*?iH#eJLaWU)1RP62f*1wua&IUI8e!#Z5d1c}<0>%kOnZgxg{`U`) z?_Ae3?(J$cDlT)LCjwJ!iC*8Lp(Z9yc*&UV*!ux_hW@R+jb)BYVx%Y)+7$nemI_n8 ztd$C@Jz~qf|DNco4AU;=ovh7iW!7S)n{H)kGK)1|Cg|3-w-MzT39Pr8G{&u~iJ!#c zP(`O#U5fx)3mbGTfGy$5qtxJ8R9K2Bo|$(7#d1{OwuQ-&Qv#klpsDCA=O{L zwug-ziU7{<3la!{`{sd+ZfB$48B~j689jx_L%-qtx$#ATgce&m4ai$#!w1vQ4GgWF|F ziFY2o2AD(!g<5)FMkqdDoo_BWG7m_c6`Q$=Q-v$KUq-v2V-ii}!4H|}qvQwdAlIIb z(x&+0G1o7Go}?oR_{eb7p!{Nbw4{-NUCi4ji81CIvUGp^=R;nDV?lZ(pM$a@CzmEs z{ghYvJI zt#cXqTo3ab!+b0FT{6~Z{FnvA$BN>DSIdSaaup5mD~PohlU%pNo%Mv z>GSd69Jf|?w;vXsO$8#z-&wQK^rZN=iT_a8MY*kc0O{d<3_t8ih@`dMNEh=i^C~`m z)gcl1rn2(DKhGOydm*Xox=EW@ONJS-9ihS2l)F%lwYRdw74$Uw?a&ZU3y>t8ZDZXi z?%6dMWK6TB9t$`9M=+k_5cZkLkB5Y#UXHZtf#2+N2p0o$H+)S6Jc7^KR`PxctfmZLlmYs@^c^4xL`22UbpAG+rY!M zQieHY!NODg-iOpvN}aMOgz1jr>$hJeACKjT>>qtK0WHYwA?rg9W}{;Ox(Xc5ps4O| zRY}rZE^KzAFmvrQbA1zr7D+=y#0y+LXb{3~evML_aQfbod#Lm9r9yWVu7j7KQ|gSK zb?aJC^*eQ!fg*ol4youVle43xA!B}GR!0@Cl%Vumh{K+XJ%*>M|5`RVx&+~#XPy~| zNpMchzDz+M#AHLCFiZj*t>}Lz2QXNe{pomH*|>#w6&cti5KfxOrAky3j)X8p+ZQ*g zuSSQXuoPJe@EU`TA?C$nBz10xsZrlMg!K^0T^JW;sFEGfg2q1_oAcP+Y zZUO{|H{Q2f5T?jw{1!xjx@v;D5`;KO=+E3W6a21IC?QEgp2F96BkS6|EWYREjpU_Y z(XK6dz#=~y>gKk55BcKR-R%1&clc%m_am&Q3Ph@aHS^s$;!@a}kt2WdZTVu;7FION zd#fYgy}pW&@+oiiQVIV|bP?c$I)AZ7VPT5uCPJL24jxetI?qa%C`Tpo5$T5HCAUgN zT+~kfHk$JuoZW(jqLQwXBBf3tSHneV8NVPTeIgqK0G12lPai_5JBhp8`z$rt7C_+F z{(N;n>0l=*pOyt~6Y&SqCP1JdIEHIY#zgV>9&ptYxNF=D3SChR&0<-Kd=XUSsxJN* zXhb4R4IPx3S2i=26gJEr1EEL%oKmue`mJwwyu95}cq`c88`#VO>rT&Do_`HHIXccp z?jQO`TXyRw7{X%Td^q+TD7!<>W1gvOlB*Z6v{o~|M5@BXmWfglKM9^1Y#pixO;q0f za!ye*i^Z)P9Pg}-3|hG0Sne%=QwfLu3a_t?cFyBIZkCWR!%sbY2$b~?f=hbwD$b6z zf5>AQ-*{^&vf~5Oj#Q87xV8Qogk@ATDx}bE{YkOXi)`Sckt&>F`-WemnI;?XJ9
    #R#X0fJ7b{AkTJn== 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 + + + + + +contributing.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +
    +

    Table of contents

    + +
    +
    +

    contributing to libtorrent

    +

    There are several ways to contribute to libtorrent at various levels. Any help is +much appreciated. If you're interested in something libtorrent related that's not +enumerated on this page, please contact arvid@libtorrent.org or the mailing list.

    +
      +
    1. +
      Testing
      +

      This is not just limited to finding bugs and ways to reproduce crashes, but also +sub-optimal behavior is certain scenarios and finding ways to reproduce those. Please +report any issue to the bug tracker at github.

      +

      New features that need testing are streaming (set_piece_deadline()), the different +choking algorithms (especially the new BitTyrant choker), the disk cache options (such +as explicit_cache).

      +
      +
      +
    2. +
    +
      +
    1. +
      Documentation
      +

      Finding typos or outdated sections in the documentation. Contributing documentation +based on your own experience and experimentation with the library or with BitTorrent +in general. Non-reference documentation is very much welcome as well, higher level +descriptions on how to configure libtorrent for various situations for instance. +The reference documentation for libtorrent is generated from the header files.

      +

      Each heading in the online documentation has a short-cut link to file a new issue +against the documentation.

      +

      For updates, please submit a pull request. All documentation is in +restructured text (rst). All documentation is spell checked with hunspell +which can be invoked via make spell-check in the docs directory. If +words are missing, please add them to docs/hunspell/libtorrent.dic

      +
      +
      +
    2. +
    3. +
      Code
      +

      Contributing code for new features or bug-fixes is highly welcome. If you're interested +in adding a feature but not sure where to start, please contact the mailing list or +#libtorrent @ irc.freenode.net. For proposed fixes or updates, please +submit a pull request.

      +

      New features might be better support for integrating with other services, new choking +algorithms, seeding policies, ports to new platforms etc.

      +
      +
      +
    4. +
    +

    For an overview of the internals of libtorrent, see the hacking page.

    +

    For outstanding things to do, see the todo list or the sonarqube analysis of master.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..2388adb --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,60 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +contributing to libtorrent +========================== + +There are several ways to contribute to libtorrent at various levels. Any help is +much appreciated. If you're interested in something libtorrent related that's not +enumerated on this page, please contact arvid@libtorrent.org or the `mailing list`_. + +.. _`mailing list`: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +1. Testing + This is not just limited to finding bugs and ways to reproduce crashes, but also + sub-optimal behavior is certain scenarios and finding ways to reproduce those. Please + report any issue to the bug tracker at `github`_. + + New features that need testing are streaming (``set_piece_deadline()``), the different + choking algorithms (especially the new BitTyrant choker), the disk cache options (such + as ``explicit_cache``). + +.. _`github`: https://github.com/arvidn/libtorrent/issues + +2. Documentation + Finding typos or outdated sections in the documentation. Contributing documentation + based on your own experience and experimentation with the library or with BitTorrent + in general. Non-reference documentation is very much welcome as well, higher level + descriptions on how to configure libtorrent for various situations for instance. + The reference documentation for libtorrent is generated from the header files. + + Each heading in the online documentation has a short-cut link to file a new issue + against the documentation. + + For updates, please submit a `pull request`_. All documentation is in + restructured text (rst_). All documentation is spell checked with hunspell + which can be invoked via ``make spell-check`` in the docs directory. If + words are missing, please add them to ``docs/hunspell/libtorrent.dic`` + +3. Code + Contributing code for new features or bug-fixes is highly welcome. If you're interested + in adding a feature but not sure where to start, please contact the `mailing list`_ or + ``#libtorrent`` @ ``irc.freenode.net``. For proposed fixes or updates, please + submit a `pull request`_. + + New features might be better support for integrating with other services, new choking + algorithms, seeding policies, ports to new platforms etc. + +For an overview of the internals of libtorrent, see the hacking_ page. + +For outstanding things to do, see the `todo list`_ or the `sonarqube analysis`_ of master. + +.. _hacking: hacking.html +.. _`pull request`: https://github.com/arvidn/libtorrent +.. _`todo list`: todo.html +.. _`sonarqube analysis`: https://sonarcloud.io/dashboard?id=libtorrent +.. _rst: https://docutils.sourceforge.io/rst.html + diff --git a/docs/cwnd.png b/docs/cwnd.png new file mode 100644 index 0000000000000000000000000000000000000000..9f850caa7a84b54f896447625f57a8e54ff3d04e GIT binary patch literal 18998 zcmeIa2UHZ@wk}##R6`SlCP@&XC1;QzCC) z&jbL#7UO;T2LS+w{iGw}>9GGy47J|_04QQ>Dh@4$B0HzUSE!=XAD+y?7Sp%@YYcWk@0MG~k z2p^vh0K@^nntYs%aGWr(77m=io`AJ>Ab)alt*Be?D`Us^hRtreRM30WUQk&K|o z8i0`2P@>MMFYn#~C2CsAxXtbF}HfI^{Q-LWAQ6n=AIzd*l(=2ie{sKW&Sh=8%a z&Y{4JiA>L%cclp=$AgP8_2(tV!@0f7?*2@Wyw5e%r~K)=q%Q8mHkG<87646A`pt^b zuY*vt9|Zu@xkS=PbE^grxMylEfAr?sRCD13J4I%_ z%@>a$6HN~T>W*JWN4Eo-8E-uB1eJ9b1)IcC?e}6hJT-2egilXWIG%80o!_jc#bK=axx8z-1}Q<55oX0h~u2ylx&)xRXLC{+AqbJC&v1M9`Lyy59lq$ zGEv9ZjPvPoK_KIvEb252uoHnE5(9YHfj$xdY4jnVD~b!ld7S8d)ujQ%q(oQM%6AK% z0C)i)gtkWjAo1-Mp0B@;oxi~2*VXDk2fv} znD9T}O~z)Q&s@pL)$X>x{8am{6s;_C&2x(j(hP$UkpA53Tid?mcG=%A2~((*OUo8* zQVq;!0i|(RU-FyW+o9tbuWc}4?YAit?txprd_6b@oJ$VGNPcL^)wVxIoi;N9Kxr=>87iW~!X zMF5;5JMj>_g@|&lPniVc`PI+fNL%Qe3|>l`F9~?S?-lK%5X2Gz=+Irba9MYMxOaPb zhL~9KAlX6#@&)hh@6K*H?_2DD zduhIi&D-Ap7~R?vpQguZYI-GCYkB$|8L-B%bNlqKLyN3$XFb{0I@%SE-I0FfeO^7q zz^0ew%Y*3qJ2rrbu08O6d=XDTMMOv%3M?O@o!jwOe8+GjYjur;b60l<8X$mRCm+Re zB6&;lO%BQr)+cNkbo$7V6Rp;d2ZugIVm3sU`&}geR2`nZ$>)sp)l3|=D!b{EPNQ)E->^2UNPpiGrYJ@#j3G4 z+@A;3y$<3=!YvQF%Q$^k&_#BifYo{6K;O|Fq6u2lNq8bfwj#YS{8xMDVtvp!f(-g`xAKU~E z$p>8HB+8v+s*w! zpyc)bk)U=u0d|@L7dd3QEK@$i^|;bm7MAYU z7~Lu95W<~O8S3J^wqu{&zM2WP45OK)vs!FTpXKrd%oHnU5+8)C1%-Dv5||*5zAxG9 z(~TL}=iZwcZM5v8GO$_|P!?F=WwQ2J0n*$8nYLcbr3YUbI4GbuO-l|U-R#r5!LWfw z0)c#PJ0x&p8{Bb~DFzOFxIK8ZP{}5eZOu-3*%&enmk+vX#5lcLIOubI7(Z?6R#;g7 znbBwI%!wzmi5wJT1??zdP<|>w4ysskV9yvNxo3vlt+qhkM^~I)>2q56UA$X1wPV-2 zrJs90$?zUPvf)e4vjcF6=C|YTnW$AvusVHOMicj5F?6PsFdloc&}(e$(q;YjMbE)d zP<(7wO|j^kM|Oq=xB5LGGrQ&IFk=D1#2mT8z)mA@Nh}@hT)eaD62M4pW%ikc)rUvT z8K)~5ee%k-?rEHXM-;S|^NJPF?i(NF(eXM;yo0%G{VW${#V|d1cYpt2c(%n!S5AkU zDV2P$yx*U9l8=1$HDuU3S~0&Jyfyf|c7928&H78_^I<&IK@MHIaaI>mh6FebAkzt@ zPClAO#fr|)C#O0U&Q)XvZwqM>v{S44q^6fzY3K4l4o%?dZ8zbtwdtk({d2jt$kcS5LU(Pm#Yp+D9n-^| z-rp0cH|L?<^luy1oa(w>^}q6;DO}xl09l*u4_~>ll`L8zG@#wzyO8oVb~Ie0U+T=> z)8j|Lx+h^3Mwkr4q2=u_dM_-bi)Gfos&0&e-Ns{xUs$GxeN*`bu5a~@CTnTg5BLuI z{b(e(JZX0h(|Xx`ckFJwB*{tv-Ay3>B%=1SM6e0(!@`R9xyeDUdc+Tk+g2Q!+v9OO zTzi$(Wenb4v1pAvSl9)MD^LW@keT7d0C=9DHT#6{_NgtB{lre%z-wE#%XwGae5jh< zJ4_2W$fLDMU*P%O2Qo)+u)6SXNaZZl@KRL*XZwD6b+5r(*6I4{@FJn=w?*0gALKgZ zmM5B@SD<%$kJPj})arn@6gX({K(<~ope=NLwNLYYB=Cg4VEE(}K}h0jebIGwcx#6U z0|(iq-GXUa?MOugBS>E?ETo5&I*|~OV>nyhv_X^O*;Ybnva(FAoTjUnQwyOCb~4)P zpl62+A%NLMV8sAW@N(&cZI#}h?N2g8-otV)zcMS@&)Oia9N&BcuK;qGODtxj9@%vn z*aD4{X1&K=a5-u>Ni+F4T{-jtl4bn$w)e3lYSq3df?j=88hsib@#gJw@Falp^)hhk zd;uQJx_=jFKV&V7@_}X8qUj)vlKotcUtyXS4{s2L;*+kG>t{27E>E(xlLa}kDVtnk zRy?DCE#Ju%T6msZ9?B_pTF6Vl%m^%!_>}~YFez}+W<|>PIU%lz>bXE9lMCDtm#2&- zUk3`gLn>kcWg?oq51Aa=k6F4f+T9|De(Xo>aO(LMzNcb=`>D51Q+JTkr4k9maH+ft zUQoRJp?ypRhiZ%vmp++2(LiP>?KF;eqSK}k5E4f}YSQho6eA7KG2ngDFLhyY)bY!Z z>3FBMKa{bO+i(;V9G&`PlSxDsj&hP0+Y(#KXL;}iKJuL4tqQ>GAW~TDsrXYTsF54e zyeafx%|{Aa=EzuD6oq+(ie51L$S+rtPC_gJf^eWMlGm4!U4MNW9C%PY7n2o@w?__r zeea`OU?kuw1zvVcovnmeZfY7M^dM`EFI>`h@9flz<|2#xxRJhW7^H954K}3%uHM(I z6n{K$ZDO3>7jv46yoXpN+|FQwv=AEN>@o~Gm0K8asj?bscxtK1)KRc04={Vt++~(6 zf)08`y^jew6rQgth5pum{^>mCA?9eySsb*AYNuojQw~z?RtPq*j zYj7psoY`l4nKJZi8yA6gSZ_ls^qi0}?)u+QLr#ta$vf_0j{(#(D-7G#8-rfkCB;y= z;x1eutqp$!%&V?vCeY1Ni0I(3GF+lH&Pd%m?yn|`FW7yi+7$U>+arlCdRjpl-LSgR zkNMles9?wwU^oNlebqE|9<#;CPjE?S_f9F)O}37ma*C7uHT}NVLwo_l9Ft;~uylYf zGW+UQ5Xf(Y6dn*qC-i%UtzzeYIpC~LTk$g z4@Xe>o*n4IF|e#=0#9AK{(aWYoYALhKaxJ$SIXullWG^)#Yw&)n+Iq&eZVU=$zFt1 z(t(LkrBBd{-N{_EBG(3&;nNgTP(zUio}Vdw{3YqvX~7H*%5G8gn4oAA_Y!2<*ZKK( zM;TFs6l@|fHxab3+{{VNQ(&E8&63LrKE0JZ{n!y~nkiFDfk%4%T|*m~ea^wQ{a>B| zGQ}?;zn{BAP|qX#k#o(dj@!ib43IhH^a#L4gSVJE{PrRwJFN57Op`$FpgLcpgH*DB z_UxJGM%%DJugYWgNY!#fs7Y?bcMr>5mtQ}>2NZwrlSVzg<(a4g?36QbAf;9WZ1b|0 zK|9~S+bxIw4&?&fMWxV_?{4nEFo=EC&Zy|#ns#uU*(XbZowolV@?PP|XXTgl{-%hF z4Kg#{el`XuPP#Vx=qb=x1*F71EB*wP&aZT+3k7oGz@XuvZ@5%x2MKgqa{dO;xC4td z5@?4(*0cBu_cvK|e&P*RfJnQZ?P0zri0Jth@welCbL`-vT=Va4CMKWvS@-QLkO_;YB5RDY4B^OT0ZV3`66Sd>| z9@vd@Bo)|ucMu+FddyDSdk3a1(?Oh6gdjF!>Z_HQRB zeuXu|K56%u1Gr~kykdv89`c?Tg6S;xp)ZVzVQ)j90~4n^zou*Rpu(v0J($;hWGYr$ z_!&@nM$id4y1Ij;dJk9P7#LIuL~g%*5P8)5_8ZI_>i(505q1*>%dEIzH+?u&u;va> z+~mzg?yY+TnbvVD=pn^P?6e@;zA4So+{6RQy1c( z1l$OI1jwl6@z6pHk@t6noDtsXtsGEKUQdNHGIRr&$Zv!)I3X6TMLonHT9)k!;dUy! zv7a~y`Q?36z5*0q{0*5hHf3fBmXO)I_A&Z629S_*-;}()c7t8ahvFgd);+*XmgE|+ zA`AxEzyjPf^V1@DYU&H1eYc)D@o{EflQ}#zNirL44sfioPtNdu5Tlf=x^1TS` zq-}8!MMs`DKgEXYPSKi!Q%fFi1#_@P8Y3Df-tu7&o0b^qcyYYm7LBd)`fEK)7NhD$9`zF+QP zgMl36;%dsuNEOQPl(1Fum`xHR&=Qy4`m!=pmj((~v#caY+6Tgm1UujooJ;M>k75#{ z0jX#}@qMfb%JE|0s#exkT7R8&?MoR{wc!qMm*;!s^y}h*oZ`-)&t)ymvOwaB@oCDtBa;!p_#K7 zI>-lc9dpmq$p`KV8X-Q{pTHw|{p?sQWiPW&QMDc09X2<&)#9n%%pmJNS#-MlO3f4x zo{9xp+cUN$&e;*jm*as1Loi3XYl}R8CJ)#a&x3_29FWRv=_O$JDKKn}Z9ZnrbJYklxlSx@}9q@);+jPrSa@#*2d%k^oe)or!lo(fNp- z@=SXR#Klf&x4}=J)sm_tijsUyk)rFq(DI>F9&*Ts2m2AP^8-9ONF1)@jlC0y~gB~rK*1K(B-_4lac zPUOVILnkbZ?(Ps>e$pO;OBI?s$>XPed{<66V9#bvpgp*Xt#A^sWH>$^P%tt?>iSR2 z#{4J%mMi%kTbXC;Q$JMlEh{1-K`_5BD>m_PfJ;a3xT@(*V)X zbVj}i965yay%c$L`p)qE4qiy*$H9`zQNYB9U0~PF&>X-O4kRHuX2xKXE3RZk(E!Y& zJ94HD0>c>b(@(Jgo@*&RUjQ- zWKzt7!NWVTc+%~uB~m_c5vYzj4XbM$2}}kmYq!C?J5}2)v87!XY+}Hvw=?^MR}g3^ z1V+U@SzE&MIcTm6x8iK@RPE|dddOs9oy=_`Zc3>oGE@L)rxQu)BuR8W%4q`+wEZA_&H4Qk3p}VC}#ko*y8nAtZAMEtSn^g2b*eAwzJ-sha7GSvXkAfCjwu1 zp8?x%;YLJ%oP->t$7M-t%?AZCSy^S#BhP?Lj_l*tLcy-!2eTaH5TCWcpuV^-?pXAY zf-P#_ZHKPNlDsD>p%;bXg)2jhRCSp)iwSE+#U0pEFS7-5un10uOJqH`Xye0spr=RO z^KKg=U!(>!aBi$jRK9&;km-FXs3Lg=QuzvH6bKaydsNK@z>q) z(x^pr(+~0IP(~c&Ak!*_Mox9(eU6MWQ7JJ$n=^i9QF4@4CiKIm0s#^?X#@O8(yQ^&r?ZiQ`X>mmqx#rk|Ih?Zr%P zkNu*nJ*JW^(&#(?N3U-ZC`po60MCoka7n{{0>!Zv^FPJFM63fmzg>k%F>BmWl$69l zduobp$SyNY7ryiEvk$(28QQWm7bEqi*?m7^!?6Zc}aV}%f9A9t?Q<5oc9}z2qZPddU?QpygidK$Dp(F4C zYV?d<6fTe#Q`8A@EMtO6xc|aIaOmI?0x+jt1fB#|0GS!LCKhfs+wif|0w{J!-}n|# z@Oam$pNY+P8HLUsc*H|F(kue^J z=ol3j4&UG=hgk4p^BLhLr@o5BE6zOs@Qa=HxcmZ+ckRF}PwPrxSkZ()o1+{-zH~p= zK^RhJ?HzOHSOtrIPyHsH*GUHwV!xfVX=S47tG(1moNKWh&C!S5iFRO)MO3=tfof}H z%(&V%qVpkvBE$>{WEw~^H*=7Ol1QB5qbbZj-}GqLfuwfr^V^2Km@~JME%%utohmQC zpw>jv5irZNO%PhXH2DlNbp}Q~LoAUq*20i!2>$q2$Y)Csj`#j+UP#kO8@v+A7k>k| zYv#>EUR3Sas7qz#1`jK%wom(kdG_LGF(4T61klA8KRuOLlM6XyEgw1E*%`j8!J?#3 z;+fl#s?QUL26FxE)69x4X1CrsQTXF|6kv71@N*O>t~dQbB4Yn8Tna6~r**|CkIz+| zV{9g_sPWu>v6S8Kexz;xcjb!2{9*-x#4N5hAJh<%%0Sxr9=T8Bx@X zracm_wo4ar<@*y1*4e>*p{0Y9w{mknh0Q4)XKgL zh{0H_;Z?^JLy!`x&k3?sKO9&Q3R`Clix$%A(i(j4;_)I-r(fJireFl zs!79_0PH-rsSQufH_%0rFkj0!3NKxL2x$=LML}0Mt zeQ;nG##I`I5DVm>gUp_C1*OZkzw#yhVPoP`6{OMtDnNe*dx7z+yvgH7QBpD6&22eb zZyl6Jv$Nx{yg(#S{xc91pT#0M<2c|r{Z`6O9!8&Fc6+}+lB!r1NhJM{PB7x9# z)#P7SO*ZJ44ubb_`_qL16h?3VR~@R1F!^Y)|E270GwBr%;O$G+sn-~>)SU@MkC%}ZQ@vFE*fxqqo;Hfz&+>}qR7Oi4|f$eAQqX14U zhf-K3i5`;C?)!uth01VDc~$|Z-HqES6i|H?{Iu{PMykvU;r_y2EU(-R3E(PN@!4-)FYG>z|gh`3n9g6q@7&?T5Si1XVE&;wo zlpo99VVRUwCmlGI4s1gJ>kFH$Q2~4bJ^T<;xuR+t0ysoMu;W zHxQUU4}Bx3Kf^+OhW~1sLLwyjNF4;(z)s5u!BnkS9-b%t4O$;NWKEsQ()u@iqYEJw zQz#$!e-1`0kYkG3fhzqaEm{C`!6E*CpWNNpiS%3yf1A%1?9PHD+rL%cDvXe9?I!#A zq)P4$FU^E0e9R`QeZuJH!0@$|n4p}+Lx_)j0uRr>Jj8kn$Ah zmuWd$XgjrCvsHOxmB7wp*lD$YI;4VL5X(diP&?S2s4s&j%x;!@a}#6GCU?w_y!m5f z|D@r8F+lsL2nYIsJj{(*3T*0579R!1HWPSiE;Fb_*epxO*@hi)gy%WsxjD!sE~g9+ zBlb!L`NCeyJg}sBW5*+Q+ARqj7_FhVGynRAmGPWCPxpDEcf0Wx?H+dPjNs@ais^^M zavT)#(^>S7yRDL4nY9Jz;vxZi67q_Oa^Nim;SLDmq6cr1kgu&F^uhqtLkISKBKZeD z{^(K}9U!xt838bIy0XO*mSD3+D^1`;zzsAddGtYmO^!+q; zGUp#6`<1Z+`d3saWbMa!l`ncq(r^c+16v@E<36Al+uG_9+xLeAvNM6Af-gv@!Oc7Q z#{g_kzcWBL=)t0$C4kLf<=vRvcfortS8?Dh4%{CJbo1IF;f*=tZ&?aTa)j`f{BTE| zGW>4G^ljA@67rFV?uy<@{uqMy2{6;%qzdXl9wL8)<+^Efog0*Sd{=g--``JpwWov6vk`I z5oA&3wE;iA$nCz&l2*WUz1i>kL}ss*cP?M1pZN-Y10gf`vNC2_n^#EDJ5 zk8^=pI8a*e4{V`$2bbqBvzJ(4(EU#tuRfANhb_$Of~g}0L=9Qd1n|@4I*2FBPcOBmZ}d)SQ9?)|M@ z9tSOd>ZXO)MD5X>5V{9Z#m#iwjtcvaUKiU+o*Vdv`@E{%v(uJq2g*afiNF~X#i>tdldJ!Ku+X^#wcJD3%97}Nc|_z~@i z&r3D^z5W_7{E#JCXG_CI|6t~Vr!S@4uow&D!v(!@I5{;E@3VW=0Aa`Q<5RqEE#~44 z9$q{8%QpoSCVBqHFl%~OA1{Gf^qX=~Chvc;zo8G3RnGO#UY6ep9g4y9c9S5cD*kxc zSw|XJ_#ZRzz2uA3zZJqOf*9!7nR5-W8Cv83O|Jx*0@mKlCbbrl9eA93@TR#A_`=@9E zWX@VfpVj*TT+K%}Fu?oxM03)n*kH-JO_(q>={viJXMYD;hjcz?_-pj{yU}$c*_4GU zUa@)oTI3;mi0h=_26XF|tdx1_ZtLr(kEaYsh;w6eV$)Ii+tOdXImx~ksaVU4QHN{! zz{$|J0egg+x}xS-z;g`XWnJATAgGJOTXJ#OZ4rR<{`|rdq)Et~t5kR1uS&c>@2hRr z|D%+ISb`fA^(4U6u;_}A>*Lz@)^$~9qtl28I@d3E@}@?83EO+jPMjM8aCOd$j1$hvu+N-1D?fe@^5mm zqJ=X==q>;TJ8pkQPb&W5f{sV`KQaCf7U`fUrp@sQ#6!qNPXxFP!IDgjx%4oedvjbO z2|M)vXEgrvZ2uKf^LQV_3rZNG7-Ka`5Z;m=1N`4R#HPglm5FPI6G=NTDPU+|%b(7M zZph&+nKoE;*Fg?$^3M$2h=@9jp9+{K>b^nWBgAZ;E1sh<+0inq8yc3AN%kpw)YzM~2mjn<86by|A{vCClKL8l(hf zu~S$##TC?q-O|4AiYPDKfj6bK5`*F`nKyeaYQs8+9ce?>)Nno)s1@3j+A8yHcFNZO zy~O|BC@|G*5Bt5TSuoTZ83wK{-!0W8AQuP`VXd_QB|mzb0rhrd&+RV>Tq@ox;NqUxw6GlNkW!K*cLs@QXMc>lCwlG`D?CEKRxbTMzw za-J8@K4Q36R2%d5SEDu>)1}mq{z$ikz(2YJvO!!ILfSC@6-1!mzZI^li`8yIK3jX8lH%rLEsG`m^T;|?9Yh%GG_r%VmR|R0 zYP$$2lSpEJ=epOAKnc%J`$p6O(GrSys=XT)WZs-aX4Cp%x$q_s*lJlRNaxGfV5>+FrX~c@blv{Y zUuXP(8gjj8J|g|kWFi3rV)nYp2*Q%k!czw9ilR@tl@!J`#v{X0EJn5h4umj4aB{{`azWf2&r;E9vlR0dj1$l=%6dl+(x zTOn%ft#vq$?*K9%GOi>1|AwN9AckpppBt8ic*p=Uk#tWB6KG39Ewu;4TOPjS!PEuB zD(54Xe)m?dt=1mhC>5*F`o(%Y@K3KLMywaD^1AKJ!e0iS8PY-raUoG{`Xj}2u3djD z{97F4l#Okn0S-IPmKfrg1?z>H=lHA72#9&Beh4d;j>hXCXEBuU*v>rhLMaRVRwvfV zZ zgI5?fppFfTL03Pwts1mXp6vP-C&&Ji2q5&v_H&OJzttAb^`=|)#(F-=_n3MF zf`;*`NXo!$^k`@R*P(!8zCRO-W{Bve18|^KD&8`6$h<;HXYF(E;U%I*Gd;v|HS>*g z8morC^A8RJ1qi}Pp6Qi>1RM}UKNce%a)A-1GQXIp_y!=IuB5cmm3i91`w9_VnR9nR z_MaMieY$#kt6gxgEyHOyFCP*p7}yw!lf3?TVPb$g-}5%jDFV27YW%f{wo9mL6P83& zz|wtcOw(Ob#U$N(PXd#1k{@5>`eErmrmJJGKY-=BNKD@@1o?yac1s(^yQjjk@ppy#>Mzm9X3 z=BZd4S4?x?&|2Q(6ziHTA`DAn$1QUotBsN5?i5!fCdYfS`!^#v$nTzGkjj_wVP;pPv^252 zJg;7we{sCeB*A?9_vcNt2iWC~rj`I)D)Sx@G}DyT1zFgayO0WAPRaxid!#ovKf&(* z^gmSVL_3WCNMK8-mWi6W$8IV{_4RX$TVx@ ztlCedqP)hd4;ZN_@A`~u;(=P$q8J_pwU(RyMVEWHe4lt^mgaKP^Kbd2@=*47@Qt<> z+H$y|KU9Iv>NGf@@-o5SPavC%Rv#*tPfITO5%d1p z@NtoGPRLt`LLssZ0{jl-5{jjTTOm zCXPZ?hR6lo5GRu>9nV+!aN)dG{QkYPr{SG)MgC2J8lDomW^c0s*~LJw3rJ4!H+}k?mf#jPS?c`C_OMQK2Zh^JV>xYGw*Xd zAJZE0+mBrd#96-~Ln@_?Glg{>p_)4h`DBSiPKCe}e@-zeQun@5$MeB4s!lHIpCz0Q zFMs$6snpwNX9wN)I{Vz!UMQKxIT;5sSxTURhbm(BCFIQjKtFvVP-o0*ft;}mJa=ivW!scnu*6{nq@d2)u6wZ+sN0IYV|r?!yzN{- zjsX(pwHVwyHfWT!jafZ1+*~5-!<%Zuso(iN%C7G!L~U)nVu#J(J5kJi^qEB&{dXVT z>Axd^{~00pNTzT9nhWtG%3}Qf}&B z56*ow`ZU3$Jh-Mv=~A86&U{5pnP)x>mlK0~Uzoy#X48zWud}aMfB5qr4vl&Sk5s)Q zv+Xj6#DYcn@%bO_qit87>7MoAEh^v+d-H>@SY8c!;yF;6JtR+I!i~6I{lwt1r7N~Q z{LS~O_nOA9vEeV_c+I|~B-eaI18(blJurKCsS$fyq;E>y2*f)LnLOESnKeDr&IB?!5}#<)cj4xn6$hIU1krd|VW5KRjHY`*G;T5B|$A zuS9x7RXlR~iOYYJA-|t%_M9R!#D$(arnssw)k{sk8pF5E#o97) zMz&O#^GJ~Mk6S-Do8)_;m#X?T7xQ;ZRx&DHYNMb1Ia;I|eeI-^6Gt+%JhdVOpi);# z=&=`=@2Y?0Plg%YB3d#Wf|0mYwL9Rxg6$%}tdv4zat69F?qCKqeX^wVDNey7=t;v&$mWpNDRzlsDL&%|%NR7Mk0b zYE9yb)vRTR<;jii<-_WRzDb|jN~_>-HI@Lg_LQQVj$l{QhpA_{I_$J=t$Xs<)=sfm zo@UdQz;R|p+wXFPj2>@$hkUN)oRjCNJf16+&Q4Ygk4yTbubR`x#mxBCMkwD1fllDu zOw}81i?#Kb%Wge+5q1$~KR)TLYWV&WO!D6z*^{LJl0a|2=!0Lpv&v z-mQ3xlL@bjZObPdFH2kfcl!Zb`7qa>n;xopkyi7sRoe_CZ|I*gig&WBerleHm~x#e z3R`oO4@)sKDw&XdR8e0wdF=5H64+)QzcTZD$w&WduR@QOKv8>rz0QLd#XssrKYE*` zFTbw0e%LARd}eItE(6b^r5~k`15*xMa-7tC>c#r>MIIi%NI^MJ7pa!w}y=F1z~DCMQar+D>j58PH@Vv!(K$K09AvPI$urj};Y zO(}1(KXe-EWz1U+Qe)&TNEu)u;7}hGF@5yE}ZeBJt_VbM{x) zeq1w;*q1K_8ZPOKev1#kc|NyWJ#FiQkGI;K8e)0xrg(?o&YE z(ZeFvJb=@?FWY&%{89C;o(VG@MBIGlP>X+S($c;INM8S0Wzv*fN3dL-iAldBBN#OC z;O9ai?dnRyJ}90+q1Zc)$;Up$?xsbB+rV`o(v*oaz+NCnN4#w0%`L_1<#RJ^t#{(_vlEzbGBd#)9}pA|K4JF(o% zP5zo&-(Q^hCU&?cdf_6&E0!eBCI2bQpC}78aH06-rO}zO4=QP+HJ)dlTS3@7TKdnM ztvZ=J%&|;cQbsLGkG*m^c-Ih#<)a9BD|P%Tf1W+~sD0WJ%vo9+-GD1zWc+`hJ^&xf{HS&G~Fh!xpO2BUWl}Slq<)*3?Gm~@0(o*Qb`|mG(6%j1n z;{B=Gxme^E`PFsrdAuH;DtgYjJI1&YXK;5bE2H8QRtu|^d%1UpN@8ELP*2PG{u1QY zoR%KvgahfY7pT5Gdw#bkdXDYLEZg0$pF4XMO8ZNGJgs(tC0`RE({r=CL@w7&#skb! zSI2_39efq-RPpmJ!4v;vBG@C?(`G+L!}bvFY&%l zj}vD3^pQbdMC0XW|1;<3mE$ri+|t3IqbIcQp8qU5#tt5C9m3X@k|ItEJoV}Mg5oM7 z+8XTo=tCqF|7o=wPVLG)`QUX6U&Zp#i*n!0e!lwd?6oIw&np=hB!*QEL%HcEjq z)sMD8C$nqE?4JQoZsjF3>z_&l3}U0d3|?_NBb_bl7e-%_i0qFXCD1Y*WY9~OzvTK_ zmtNkkvvpg)0@n@BTL+evt^oPsYIG{ekgU8BQgFutvA#dcb~Q0eMAK4IR-%}Vh_d}y zZscnj=ncIOoZRgfas|LmS9SS?I_+aDaQL>!M_w{CCeUJ{p6%H!^TW_?X19e^+)FX+ zMGLiNV5~$RX}qLPt7A7N@ak9!B=wRljoG@f&s!m5|j^8BTAD<;VcAC=$5eqFg2c2X=E2 zKqf_ZiP262Q5~dg_}?80)cqa+^Ej9I*9fHXQtldj!Z zZIkCZ0f3LoAYX%(cfzPk9tWi-W_F$CLv}1bF&jH$^FulhoGd@pO4avlKJp26 zva$`6<#qcId9|@b|RTJCF=Z6n)K#C*sRk_&?`8< z4d1@{Nq{nm?;m~Jp*ynMOw+HWH-|=uv3t|8)20gtMj}-;W z;nZgEQ_~l~FHRdBez0Enq$amz>Ie39mCX`kW@>qbCca-|%5trWgBk&WMMmrK>Besfp{_ zLXj{cD^AUls!~`9!hL&-msL9TNt+3V{N?}dW)iC$jK{n+| z2TQ0h;*U`lv0@Ai46OT|)_{eC(j_z*@n45YYvc%rR93!5d<9strezZjtkzl3|KRm# z3=umi8QIQZ_6KG>w0f5xf`}mqsi~=QMcN<`sQ$;V;X`BqCSUS481Hc;!LFbIQu5o-?p>;PTlW)#_j$dID z2lYlA`!~KE#F*{1MU{>f!GQ1Zkx)to+@@$~5PZp&smkZI2u)0rkE1(^`tMc98kMBt`01tQ%#*&;+Uldl_SUNS47n?G@*5 z?mZ;^6CGKzbE_i~OHI^b_mKRa{69twjZlFbGotAI7|SeX7L6Q>@0I$(xzp_43C@CF zhOF4oqjn3jXO5owaD#gPJZx!x{3y_16iZ_66EZ!m;pgXP)a56DibHAfa{mhq2B%QQ z>*?ECSOn+gQP*~yiz0ZmQC@UCPy>7KJ-tILC`fs)5kBh+d}P#MH57h9B+nMNY~AL@ zk&o`d@{Gu!7+>ZS;QqZSt0JH8k!e&bWl`%kFg}Z zy}bq8`{T!t`yL`1xuWteE}U_3ah-qef3%u%(I=sO=)6K9A|e6{3VQ61zKN%ijJG6Z z#7FlsE32%Gk&VRJaT7+1kYSDKuQlsvy&9%~Kp=FQc}SGB3TLp;!NKBqQG=!|-7<}- zC$U6Cn~)?r*{s`UJ6d@xEOH2=bSR^;Y3pH!DE=7yV7<)~c; zTJ7=|0(Md2Yck(!+th{ONkhE!2?08(=MzQQqWJbFu5l)G{NhjyFxPoF z9&Io6y&Ly~#^bd4<6M%zCnM}cr&$`<*@(nFub;nvMOD?_oFWZpMFZA=)bdBz7^8rI z07CQEuU9M935Bc}LR$g+*x1;N$;v-ZPgU>ZmzE3=_%gBB4XS%D9{BnAWPl9-VQJ{- zCfdJfdq8L_0nSm2jx@|O@EU#k6YuzNod5U*gWSs4H0)fLzHLd1Frv1W3k3y5CBI|= za(}U<$(23;o|^w`EhK8sM`rGK!UWvDy*7dWUC-a=Q}L($60e$ zk&uuAF8sHOYHQ=y*4{y(P@k7yEl=0!J(KKpouMOpeS24l69X4sibh%TKqPKc z;EQ+v4o@lM8i+>M6g^iI%`GiO6eui;?aFowtm(5h+01(F0nLU2QxjrFT8R7Vn~W+c zBR3M;?W%K{ew9J0 zso$qxtMV3%y^7R-(K6MbMjXQ?AG+i{J^8&ma;0dc{Qbo$;j_n8bakhu^MsC}Q&zbx zpXv)iya>yZEv%!tqQV3a*DTqgMfsr;=kOo>A%Zwc%Tfy1~lys{DmxXRZMdyoIi10>4fJ~+g)6;!hH`5my;rFww`g01alaZ(Pt9dyBW!TL-+_CRt?42@9 zseqZ(7K-8UP;3f8i5F!=a<^SNZZ1yQNc3(A(gD!pI>hH^iuC{i_ z%#rLIA%`lbQNui?OX-L5gyCU@JfB_k>!$#=!BIWAB8!G#r&l6|(-jE3JfwCRpvro_ zpw*J1y&r>6J|wMomcoQ(}C-XQFy9GeCY``3Jwm!Zi%+nx&MBWZlMt* zqrC}u91i4a^XBC}Da}?OFFQaaXHbAnRzJ*5l#q(Mfy{jmh}x&Ucr9oO+DbFiXS!oU z`hap_GL6#&aBuNHw+o$@{g{CFA_j|HbS4MB+`ErZeY#xtoJn2OMWA_}jjVrHbGFie z6l|X#m_Sjt4olh?*me!&t9C+s13hfi$Rg%$J(BM zeo(SfpwzlKn(_So=>A^`RSF@6N`Lsked3h`gyF_j4qqnCtyta5YdwHnaA}abdI=B87?mJT85KZ*{oM$zY=66LbslR={KSC{g9gTC8#fQVVRZ2pC6I3}Cb& z@e&7lMGbOM^=V(Rc)`{Q!>;WOmPH{@k(wJ8$tPu$7u40Q`uw7|icckqLD7eYcH-2Q zPC(iS_UtApQc^UdVBE2PTF8+sJD7P!xI?bAL7q2H^ zf_q_M0jZZo;tD7zSSWE8=I3RcoY-q>YJhXEaLo`dHeerUJ zup!{UBP)yRT-o+(ze`I9yjLcMTA&P(Q2~p6N%`(ZI`sHhPx~t?$}_uk3agpdoPr62 z$-wo9%gSnZNEPKxTvPVBd=r~8pV0o8t|ApW{{Cp{#xM}^($*&4RrHMb4@Tb@ONgD` zjURa2-rvu!t%*+O0W}g3h?|?6@#th+m2ni8q=RVRV+5D8VgO-&H%@4?0LXmf<0{a| zNT6+T-e^yakH0{wT!!Jdv5=+Bno@f zxj9Hdh_Jf?X<-aV3VeU$6aS;_PfYvQclA6{lBY&Oz&WRUL(gH$rfSsxa>k(S@h(q~ zO{Z9SXXs0abdIp4O!FMHZ!p$c8D@kj33H0{%o#|aK?*}lg7{J|>CVuG@pre-NCw`f z{$edkJ`!d*F1~?%OaJ>fsQdnF-PPB+z_{~sSKtf-0YJ~taL-E=9VIl!BaeQOW;fuE zFUn`p(Tjh57Yi#ZN?hrCPe=JjfbXCSZFmvHz}+S|TQ2W^d}#W3YGCejg1mX?a@{Td zZ@j1RFi*gSlk6{KL~<_0X}2WZd>5#fk4m#$;T;{>E9#jpuWsmbg-h z&X9#);vdEPz1HOr$(^Ho|9_ch7Q`I`)9Kx}Ovfi7pPxuZ(+fdhETbkAiR&a|Cz=Jz z>DN)w%1fi}cJ=vDEPWAsaKzgY1tqK#Jwe+7JenyU>`m%5)$F>kIE~LI?~a5O3DncU zh==rU!a-@TY!d|psF}rn2RI{qDn=Yax`8lj-4Oddhf!*2!oIPxe*M{M^oop+@__X! z0G1rq1vW6?7hKmDreG3;d!~5WaDwbOSzocFVGu3wx$8fC_)W3wPIYQ5`8hRhlC8^+ z=5y+A;)FqJ#hVk>JXPFX-|OS!!G4Iwu)q6D&E%r2cutfjXmKREa-rIbvt$2?jA5iD z^xtSt-LtsU;&)x@(>uS4+S-}U+zFA2roZO;7me!lU zKSuRzfWjT9>w(Vkzi@>U8J7M&C=;k!*M0zjjPGotBVEY5Q~Kf(p;?ya!P8))73V{3 zh(rx0LrIhWgX{hIhAN23Il;xn1!&|Z4(KT{npm0={)pkskvr~e{p;t3sUIDS3Cr9y zu;OG>)y+)Q2(5!aI{L`CZyCb}N_f;QNi$gv)q)w7@y8>Su}n^;ap68ALhD#$8m~Kq z>zne4K6%M}c?=jO9o{sHXqqy_2O(iyVt4n|T~{^F37(5e9+Xwv3FLYqZlmkZZP(Z_ z5^pPAzpirL8=yKG^8^LN`==7w$UONH(JnRT&OUL(9|%@5V3((^35P5z(roQ!b7anF@9IZG0V3Fo=!NL<%o2|)QIm#`1Hp_X~$G@u#1CfKo(a! znghcv=xw#sqp|%M(Ebv{UP*^`4m<5(c@a;^zTxE=n#@$!PagxXcij*T?X!R^;nq8^HoA`h zh63X=1^8!tGfCGe`L2}?k@GAwZm>dbhu~qw2>Sx9q?eah$}r6W7~<&I&x7Si@l%BC zXJN&+5Q-1&A)oFZ%D zi>$KZP?)7P*#x1%vEJG~W5<1jdKb@RMJ9YScAS)1oMGV3S^KsrKL+<&+RI>(^@#7cgE0bn@Q077u^zvL2d&&TJ|18u`A}~9qPn(P`jN-d(p6Rr_ZCE# zD!+Y-Aq?|&bf~srf`GBqyqmIJob$FF{}F$flR|3od?{Y$=bfD!OSHrtF;-JDFV7A$Y3OOR8&NzqM}N#_Jm@&G;6D3ci*V%F*~`nWINZpaJ;n1Uu#F!ke~Fgs$Kp6XJY0H!a2)6j$}tD4+ZumA`z6_tQw?3P zou=$3OliFLE*^7(C0k>Z6Qi`vMb69O{%Y2A@DdeW_WZ+iLBOIR`G-JaYh;t`*YCl|R zTlqQ?W|=e&Vvd|6ab{+aBOB`UklSv*H(5NjWqcZWy~I_1T%caGfc0Pa;$*v{kbj?W|mV+{Ho&J8!u;}rL)pDd`6oM^)0 z(eiO*M#rZ-dJO;Tl~TksrI_!aEHb}iHgyRi!qC@G{(rl{-oGu9 zSiFPA(;OMcB>W{D6r-LqZ%2|C6djzMZQR@vv$Dt$L!`!(_HUkicB5GUY{-;L5llYh z_;uGyR5jZPXeXkXu+$ncH+pHU6w1FY?&_HBk0#Sc5V5=O>S@apBO@h?`|b{2?QzTJ z-R&}I=3#)`_P9Sb3fx@xt2{jD3`xy zLr4fRW~!b?9zv?#c0t4&1!m{QIQ)`GYJ>XFuCtOP1-P6$$@(;*UVnH4VGxdclsCCi zU(9?^)@$_klS4b#L(a|qLE6xj2v_>o%Avw^GG3yG{LYk2SK62R~PD3cVx$Cqo(0F>gJ^C};?0vS8?dPcHRAOQgh7f?_Y>In|4v100S?=f<~n-|9a-$>4C#M_WlC}G(z6)YPgZZB3GT71VXUlr zx>F(=aW6r^uL2%R^@Jw#q*6H4CehJh`%s$tP(N+W2DCSS<9mwo{IVd2 z0|6OkXIAJndthRT-$o65qqK3zGfnV;7;K7Hh#47Jd`cNSAdq=LO3_;*wG<|)H17BD zm6gC;0T1o<$~PAZ0Kx#8ANU+A02P;1o2aX+1D*fdy>EA#k-MtGsw6ed@PYz(fu&_4 z-5P|O>a#(xj|BmHX^;R3_RK6W%6$6eXsXN=Tz7OZ_RGsGAbo9b!@}yRB9;| zMO4^pat^_9;4~Kou^~2tMi{kU)SC{Q#VCR5!c@^Lhkmn1w#ruf@nrEaX2FKEuX_?e zKU`ExNaG5ElJ35u?u}0V>z;SrIvhFBg;tXTARBML!E;p4)xW;Wf%72dH#g={OCMBc_lrfgC-94iNu zRnvF}LFrT{$B=?TqV-!{Chj84XnZVdBk1pCbvDzoV^Rx}$XC=RyiN|6@&(NACYt+ShyD2} z{c|4=7J$71usZ-{H`z^*yLR0zLAIJW8j31Gg4x*e+V!i>B|ZB3K%ms{|GG2s{@Qso zaR*!$O~6eWFf`ec4}=i3q)=PE>4ULky08#wOwLv}-d=jqzZ9{vL^VErBcELkL=Gth7qgUA7?%Jv8HaphD&g9l%_QWib z+*kPA*YsJC!l~6zY!k@ANmBS_ye3t*W- zJfKlo_rK_JUa1Gl8M|*Ewlq{#G1(1kbLIk%Y*_r#IJk0rt~6%ph|{~t_*hwwNvkcG zgqq^5DH#-T2#`s*{7!6{*hfwb4eLXDc0TwdqN?7b5F`t`ueU-tNJlx1Mw6_g3Sl2! z9-bsVxuIG%E^L};`-vbU{aMj4rVOwiwCilz=q#ucobw7{NsY#4zY1;YTeJ-)cSOV6 z2B1`pbB+T~w;ij*K6KGjE2@}WK^eK>srGSJjpqJvAiToA5Q^S$9JotNJBo@F=alZh zHAf-%0r4dagL}`(GsH1TGt}yDPSqG0_hD=v{efXOAAdEDXW4O&Cs~K8rx6B8#cJno zC+A3=tx?)#XrvVt;IWuzEXL<^I645WVwLff_czZpiR2BUGhmnlQysOIr z+%GQp=G;!pwZJ?I7_D)of3e|8A{=>nE8Fh(X1dSYm>wF4iZd~MurkvdQGE?co{Gjv0bN8`eATy8r&IqHV{1o8M zM8M{JFFHW>v*L(!#O_W+`4D%9Y@bPnkV24pkJq3ckqG$*`0gqU%W5blW*oD#*O%Ef zX(lPxqmh7ll3!dl9>4-Ak9_EQ)P2p3hLjTs?3KThvzRv?smsq9?+2adFGBZ53@>>I zW@?Tx&R&I9KkN=LKLUgq&O0)9j=a(22T}{aiks~xS^M~QF z)7RM$JCO-Y{zFyn4t+fyX(6lztgh`8yuwhi!fX#-Y&TGCqrIzc62pkuy1t6CQwBT- zMbzT@l|-+^W{*QMSdEICsJwj7nG|ZNQ)A$nvj;>_u4?}%JfPetFg5gD@<9|k5i zp@YqN5jK{E*-*5ZbV(-cE=F!wL1ddMd$p1ARANEDA1@m=R;J(Fp`qh*SSnZ4 zVDW`tgy#NaW+nw#0*#Sx<)S^Z7H;QF+-g1*rCNTPNG93TDh-8~@6}!mn|b-RvkrCH z0^M;+HgFKnkAgt4j$R7|Y44^zP`VOLn3Ung=uhQO+WmILV0Dt8{xrT;$gdh7Z!Rat zFyz?E3oN`)n?rq0lwl;fX3evF(tkWHTVXzeLzs%2hl`hwWC>7(_W@Do$SsU1v%q?La7ZW3Y6MEXSZ||1Q37X;W$YLC+{fm7qRihWD6xIA z;ONzi?^F=9yB_(?OU9Q&-ef2zI}lP-jN(ujp+H_y7Gzq@H%31YuEiJg<1nXzSYDRG z9#b`mUK$ZQPKjgeiXklA+C6AAdGA^MXR`vA;NQ}t&_Ck-``8NebTG!YWHFIZY4fAI z(7CownNlUo!|l|83tkxMogJlBq62duf8-WzR=g(J`FaGV(7F4cf2q11WUv9Fr#!&(0E&mgFn- zmP0C0c+#OH8<1zTgp^Lf()Q9y*5aEz?7ld#g$?s`_ejaUNmr0(16&U=cvnSkW5qbhWeR z^ONvhP%Of$`6rG(x$w>;d!i~8u@4JR99eMK#8E|A-f7!k`0z}F*Q~p?kos8v$e@j{ z3;TqV3Gd__p5#HLo4Oh~gmk$KpHYPz>Ln2dkGi+Dh}Yd=MD&GaP6~gh^^*7k)ZYhA zQ6zSUAFhbGOo7M-nYTse(L)=lmKB#15ZH5uW+Vz>gL0_(p3ZFj>pArR^v~vwQj^9nuKSpW zSf0IPvDun?RfG$uPAP>|wlXpZc9&Vviso$izp!d+F`FA|leSE9T4{5D+2ROdg9oq@ zZq8VtfvA=YN=fAQpM}f3lJ$~X376$%Bh;X99R~-y9SM>vJGRB|po&i!Q=$OuBsW^rG-eW%Yu}&zDmFpuH?m@WFCIq+i6)W zY8j*rjWjm#l*%vy#7=buCVi6DAJM~=Mae9ZCsYwl(JEe(+YqZXK}GZg9-w__v7yw2 z6-P6Jn_+IJrP-vsr7uhB3!)b92ftjzQW9zJ8?>s?6l@o($Dpcb_YH zQm*%0su%!hgRBY(X1|Z*XoDe*tC3oUlPf%Mh`8f3Mv;7g%oxNF+Mi@K*I|-)i=7&c z_$}Soc2BZA?N|G--z`L<&Iog>B3sVVR8f&y?l)rn0Fk5P1G7%sGcI;wQ(-bYC5*%q zt#vP5(|1d{irif*^+ab3xb z`1!*sMrH4Y)6jOd2&$4;ejO4ag|slB8!bwS`lCa59isQWEQ0lvm!cEhS z?yGVLlR}86j`(|ms@{Fb%Y(6ss%XEfk%PtKW~^;F6jU=;jv3#t8M9cNP0Ye>NKqp` zJOU}roO0%ByCa@3j^%Ra9n7nvd}m3^{mobm=XZp9fNm1=+i4Md<^Y(chgi#dD}geg zNea9rsip&=!*1vOjUV#351G>H(f*vt$qyMnx|r|!&`6}s3%W)N6AfWYK4BWrqM!)q zz4x+cd5;GZm4&`WpD$K@UT2*{4|8bX|9SixJ!RadVdEXF^`KV_#q}Ej&wym4GPXV|DK0FJE3B zA#y;hKBUl*59_Gi`HDn!u{5c=%Ptv_Y(15B2R*m-G9_DESPY12AgD!x zAuz^~8q%2Mk9(ydC?5O5V`DOCg_+v7XgTD(y({Ch~(zFj~=cToW!*iJS-UW4kRVW%@Q zz1Kw7y5Tj#R}0HT>1hvd^Iu>1EnW4DN<*zcC2`{I6}k`u^#xk?MwEB! zevtgp70G%|;OEmI4{eLmt~xm>hb6j@=R&Og77~`)nxH(w?7O9U=*AhdUX-O!_U;~< zBJ&DEUvb(yJ;I$Q`DI4SMRfptht`1G*oT+0G4=G$cQraA9?kXj-mpyPU)k?1dZ7yQ z)VLtr^mQKSHd`HV*dKb?nSdSNSHB_oR8y*aquylHI?%}{lSO6Lek(^R2CIhZp#sEN}OSnYi zA@6l*WF0Edl0RVLXP;&!ClABg9WYeNXAk-oJ2v6k*PpvS=ITA8oJmX&gSc#l%5ET~ z|2-i6aNKfed}=)$st~^(i+!o9edhE{X4Er@$ib^sv_EOyyadyIcQ7K^+z$eW%>UKJ zDoM$TuqbPah}QEsYp#YUS=ON) z4TYd!y?c&mZ%CNAA1{wjve3vBoL)}ls+SlM8*D)s+D|kvr8ew!-(u=yRAbn3iNH(O zo>>DRPPAbJh)!7<>%%VTwkZZ$ZriQpUp`0S;ok5-<_QEDs>;h9ItRo6u2scwVaBe7 zXcB!bn6r6SQ!-LyvZJ$ucF`%_$`u)HyHTVyXpG!Btt~;WqUeTBVlZwU9ARly_TV~GTDBCKc6wWt!8D7r^bNceVx-@xO z`8j&5W?kjZwpvY3ue+B71JUBQlwaNrqmq^^_T6H7e}3%SS}1w75v2Hvo~z4_ZvLV_ zb7xrx;PQxy64{nM=YAlbUdTpL>kT1zL6$frk>zZCo)V{w*24T{fodZ#2Fo`mY?isE|I8L0V?eD#!27f9+oksxN*;Xe`TYT`7nj6mHux}{=f0h@``2A%-8#5b{QLI zcHI6AmIGmtVU%*2_TLk0E_F5REAy;`diK`}QdEy(tgS}O8*zo*fs~45#Y96NPGn+f zyBtOT`p_-O(t^E#?yL_D@Ys{Yb9{_aLG_!m2 z6s1tKxVQ*F!~iu2m~;n7&}fQZy^etcOiH z#mW#&bBP3i%hs367k(?~5xwb9(H9bpYXJS2qB%MnjHu6_BW zT`e91fbIX$!yw5;z>OV*}u0g9QZKXWl-j8Tk&*D+8tK zqL=jvI zE9oGgvP^{on{s?*dAP27Qab^UTC$rcROlCdrh4*ZDSDYzMh0UeB&JUkwo2G%^O`6X z!=H&kAz00v#bqpUv_zW~$}n5MYokO#SIMIKY~a2{3WdO?ZP6I%Nn=8&NGx}YM|X6q z>QA^rV|Zjnre42I3-Tbjbs|bler8+a`z5DjVO2n(Fc!vX;G@wFL7NeLYq<6+|N9M4 zRWv?PwignpDU9tm(U&#B@svmAu)~1GaUTrB9a$?Crg=nML}a zmz=&ImG7eLVZ|O~p<}G8ozr$VD0)*WI_k+|w&-lUYGTKg8p1>jRY&M$y8s0uHWah- zQQYv^TsjuDK@X#4N_8`jC3OaGRAIQJOJ7UrU_N|-cU`%Rv_~!3Mi9HyxH7WT5opCi zY+hF}L-m?w|4|W(73!sUaedjaVlZUQ|8XcK^xP`{qb?mY3bOCn%2VsXk|1 z)mg&}k>*3EZaa;lCpfrTzpiiQ?mk@~t_GIJj6Yq{x}?TAJHOMQ&pxbay6`NxVEmW| zS`zTK%CM3$({D*JVwN_n$JN&sk?@N5l8_b5--9lF6|TYg*`=R@eedqknjQA1tlZo1 z@=q6c;&_ROs85gfoSsM1}^tNi{fuj`;)M(;1!W#6ag`kEG7*^jGT z3mLkg1PmL$x9|Ka(O^B8Zn#_BS>b#^sZW+gj# z@qb@X)0l)&7P;dSb^30wvUK(R1i&~esso@HY=&qahG^}b_NG%>c1IJy8D){me0v^* zYlz}*kG0kE!ye*u3;Y5ZVi4^Sy8x5TS2Fj?Ni(I4&T1VM*GK9!qG`Q|hu3>Dg zI=Ob25Fgb=H7^`{uljXDb92^sd1b6gaT6}YC-LZi_d)DQ>2q%o*h$nUhOC_7VqXyjk@45)JC7mWYfi>=De zRn*k3T1*_XKf#DAT^CSGK25LQF)hz8G{Pf{@cTeiVVY%>9^Jk;z8PYzHy1kKNM(4~ zXB6zkrTW1p3b2zvEe1zcL?=Y+;F^~d^U`}cXbnVhk`S-OH=fN5@PaB$C7z20I)4Oa zxTp%j#!H^2&+{(}zqi2I$nQ2CHR5y1*cWHq0IO@>{$L2RH(H(@bD_#6=|LdNo}0F7 zihFr${H+61Tg%8`VLa>ke%?Mi+tA&yIFk82=Z=?qiG{}i>?`G&0`+$8tG`yKuZt6!%o7I!fGpO z_1_8!dIko>j&K@EyK@0s-EGcczdDkUkrI5YL?6Ki&X0LkjYU6&*7~Xuwv0{=rw%CrYI( zeQIj&W93NX^p0oi>Ut+ENiJhq^MWZ!nYS9-+U~l%ef;n+6_7=^iHEB<#dScfR}_Yg z9$ld@ZSk1w1NE9mDw%_y-$YgyJ7KyEM1Ym+T zn$N7%9jdK=TdL`@XPPQ}nO^)72vigR^eM+pbi8no_u7uSZbLqGXe2F7KiRwmaQV+( z#o*RDmt)#lg6;gci#LW%&dx?56L$Hha)ZBEvcN0`q#UVP#-V)kiQpWE;A*XZV5F%W?! zRV0Bu-OBo4b92#*%`KVPBUV}agWgQg*;Wi6`K8D2hw<=6#b^Kj<-^8XBRxYqiF~BJ#I>jQu=+ycI&-HSk8j=+-Gibun8$yTQ-;dS>lq&H6t}$H0^~P zqXeMPv20oeq!)k~Ov}(v@$+ZNsi`S|``qYppd#qHF8s@XItk&VG$=-$SAMSz{mY!R zjlt|JhHKhQWQg?JyUx*@>WqE?UtXBT046tDh}%}+?t|Xo-sK*RtSYASR?r=DJ>RPh zqnz`urjYpQF!50`Kl@HgA3A_l4Jh6i@X%(ze;+dYv>|Z*0w}(KgamM{&$fE>(JpSE^ig>D&fEkY)IjY&*udNg_3c5X4E$GIcxidbjK+>xX%M$|TDE&@7)n3_s& zpfA#2;M2LqZNO>Bo>i%RaJ^H{v1yy8k;h}{tT%lefQ|W+@&K_-2RIdLU!7iE`iK)O z`T_zQKrq9cTv$>P0SwMG^2C(^bq2uc2S`D`{PGQ7S!HcEuRZmSXY?k4*j7g z;b&$RJd2>Z(cSC&f*gmHiA>``9xm1TOYrK#<8{=)tA|bY_}mc3z&VQrjsS-#mxEPq zVp?L3SiIhZZ1T0*xsNHm=CfbdxQxsxpnY8rn_nzR%PRq09-xK*6lMUOB7DH|chJAs z^EL~$-;QJDUYtT9S88dJ`#|MbpE-4QG?_92DgPql7CR(5r9_mj<7vjpVX#AoFKb+0 zmWMUg50Z(eAp!VZz>J(g;^8Sg*R%0ry!$@DP{YGLCn5*M(|VkQHA>jfGj$VCorx2w zsd-%|`{}CGa$uSUXOR3}=(LLs9O|4}Go7xSyuM)U&toLZk*1`Nw)w`xds2`qP@M_= zQ8hN0CUGNS;DOMNVqA|Xr=6m+f{iy(Z^SH8@n!9MRONq@g_4anPxl@rQScqOo3=iA-6zAEl)3#)tO0^KOwLm_sa%8f8e*lqI+C5? z^UGDD^V~b&zQ5MEDoiYWBe=4>T~ecDNGGlY_RfT{8^H%~ZeWPBj_% zrPq_zy33v@?h6M0R+|~G17W0U(7fx5)X^pc<>bkfhg7`}M({bSRbFtP7G$Q6Rjnzz z&dOC*CT3wy(cwxdQ{r>~L20z8O_3iHI&z-nPBs{GxOOJw3GZ*T$}Jk?HzeW8!QyRZ z)iF^4bi24BmE0DltyeqQQ1*+j4SdN;jF@3gI>Cf{&*_n&mgIv?1fKla88+7WNBKgxIyl5*yMw(M`UxTn;YQ59 z^1qUd-x`h|>B>ppZrKP5uxNCsoP2JIbUhA-&7pssAi0KfFe?@UTMS% znn9B?bp4ij z2t(j~l|L{L$lenF4!sw$l;S*`ZJlp*(U55M`_U@l+m?~RQBi*6 zT#)Vt5w<Yc+z%}GM2S**-mVYxPpbT5m zMJD76h%a2yRj%p4-vAR&rH%R$&=TwK6@L9uvYa5-sFc|w?aFI2o?%R72V1a_$XuTD zFAZA$pXU8@tujOVe~M_X{KnT&mZUaKtMeZgz^b2*JOH)2-!UMFAN#6c45|iTv8=*E zIzS;Q68H@M`t>Uy6#N||87+sHKQlt0DINl61ysENE6bpYsB9q`>n8{hpBu8+q9;CV z`s11{`~Lg$Fm~Y4dADQ&2sj9EBHcSSfVe~Lf5uCRg9-?o0PY562(z z*2IXJ4)hZ0LK}lXKsIXgU;?OL|05^W0ShQyhp1|tqYOHf(qkfk8VZY^lWo{^!q%_7D9 zvNEE+4&IUnF7#A0R`Al-Z&-C^jR~EyWB*fA2{Neq+Ps!%s+u9}-jF%9tOnx-F|o2hGd8LJRQ*YSsvi}XnvoF0{cw^DAY@3KcNo48B+8x| zZu2}kK0jy9)!4i9i&iKCbfN~;Mtv8*r!UlAx-k`lGg1~!bw!<1lfC!)F*V$aedn4D z4l_<;pS)oGagv{jJ-K2#%0x~)d8aZ(=FE)}c6E71{xG!}u$7sqJIFRI*$jUh9D810 z>9&ZM`~|bkJJVR;d~|pkmlyzZm#2En?b+#Y$!j}&_)zxsE71aYBqG9)5V_^Y)a4}) z;52p1Uq<$%wFv=lXs_LScqx0rdhJOs)m)4XDwPv1q@c8t(nvQ*2}4MS2!eD;2na}sbPOpXNJ{5` zNHZXv^Uiqhee1pZ?pq5MYccV!6Z`D*eZM_Q*XjNEb=?PHD*C&AvzcN7lT~8+AIH~w z%AXVTOCQYSQh;T8P1*BLB*uE5-=A82ZKwW*r_4kjgG2iUyEoLK*nbCfb`zlvO>l2|SdXaV z3x388aMJ=l>j{Jxa$jGcOY2FsFU-}x8ec2ik_0)Ep^{H$`b7z3ln-S`bYvp*C(3*b z)9#t^r}UN^dlBu4YbSeL!I@c0Mruxyr+S2lLF)CQb&q+JCBtCk2_7dKSV+g%CM!Sn1dHeTXKmFW5yVVp#ZC6x@rdjC z`4LT7VL*%KBN0{hzmJz>&g9O%F26DOw&M56H+ABO=vz4WI5-V(Y_CUoyrm)IJJHXC zJFb@Mpq@KL={Z<-Cpj58pYCNs-)hLkkH9%o2Ulw5gfxX~%RT%@HN)juyG2#a?R^%X ziZ@nv+xnP(9Rj(Gdwwd8PiaSr@k$GS1@P9I{2=TXZGf(m53<)fQorfmUa@}htk%Nr z$T-J9^|6CiH1JuXvS6yJ{a;6N1zvu^&>d1AdGd`Q;s=i%<}>`atm~ZRe;>-Ar3}ke zW<}Z|l|St=q#zdxoj|%~q0+wO%8Z_x?vtsVqHLT`vr#Vet@_8V+#JFk-HlHe5utx+ z@&@&0?u%z#*Bj{*)*s&UuF`ONHn2OY&b0Bh&f&*>TZWKNzP6M6U(a`*gpB-=6U-Lp zwcMq4ejGz}6&nugkZC*|O@s0P!3sjaCex=6Q>>ccQz8fh?e zdFgxhsrq12zy_bCoxMj*6x1Z(6TrU`s83;@J*2^n8s;fp78@ql)`0Qnx0BL6PViS* zgsm72yhJ|mCg~q>gI%<+GItXoGj^o1N(y$QKmcqi5Z!03FzutKpZaPHkV`a@J`g4G zWcq;NdiipyQH?W&->l`msQs{xiHSNhDIgnwSOw^TWDa{NxZ@NB(%J4MSl2)IlwKV7 z7kI~yM$Ya~?AZ~|()nZwgxvplp24Og;cDD!Bh)fjGG$^2?kam{2!MNg z|4h-16a+Q0%PLLVhtjexr=OJu9;351OA2I%Um3b)BdpbaFjEp70_MQ@%nV4tWtVy< zB|GU3*cO$GjE5?F)gOSD$P*sjrKSDQ(2&;9&=AkahaH1@q)!(7mV1()Ae`ce5=WG?4>j#OIh9pZv~JdNV15I`bu zmv0X|YB_R2_j)n{5L%AdUi%9V!Ln%J;NaTz8a#ONfz&>Jm+J?DU(nC(Ay;>)<0DH- zO4JFG#tDjRYu^F&-27f1;u1KZVr{biVOJ<{ZlH1_KpUDRhdaw z05@U5XWHx|0!V?YVYgDT%3=O8Z~LrS=ED5k~E$?%gt6y~H?+07sggcf`%o;5I! zQNPwj=wZA?uMk+2(|yt-0L&nM>Y~Cz>v3mU!0=FJC1dnoK?fuQ!GMwR@lK$zJM>2d zT3g-$9r=-{2WN*5Cgx>C|CaYtfI|6cSJcuHFfM6W;y4bL(XEIMb+LQ7e_MD#>|q5& zx4mngQP^>g*bn6)WM=*NDnm=-Mm>7}+ET$YY&76=q{m*E;hv8u zG!+(qP~D3-7y`hRKTp@XpkAA!EWW!l3Sb~&LZ0`Rf-@r|?GY>U7#_|`?$c298GOGi z0hT;%$;u+KL=(k5Q^S{+>**o2*gxXtrY;1!zc&5y7s>})Fo!GQBp^|tpszpqzCZ20 zJlIjXfaWQ)f`3pI=-|PYwOQYRr}@mxENJXys&mi<%ex_oABy@JYSj0frz&}_X1-vl z0c=aiv1&+!W0oZS+ozpXg_a;U0X?a(P?-<|G@kyq3_W&rV`Fo}52{;1G@M-V9z^5L z)$NbCDZ!4`GXSCjq=b#5R0AfkGiWvq0%{P7i)Uj~=Ub_xp8Y!5{kj0Wjc-{GoPqtf z*IF2KvP}e{6bwHel5eJW`qsVP4X&4g*amPmicEgLcp8Ih50h%f7`;yQ0L=02LZIT` zoI@%y{BM8O$?;!7PB1@VgO}{kDM%RaIebW8x)68+&|av? zXo!H9cT%s&^z+yyI0)8%pPm#6NC#|FB#6MK$|vY^-Wsq}#seXBx@KK<8ptxs%Jw%o ziBdmiPsRs$ux1x9TZ80%Y)KiRl zrTbX?TM+!_?cDNydu7(UbMm(Wc3JTcgvCxBuM1j_5N&Zm0sPB8&%bHFbp6-lWa_P% zt}Z%WmshizApgJ1LEVJ^c|PZ73t%T_0SE>V8-cF^j3yoKh-C!VJ2k*50nYF|L<@MF zu{L*jfFVlKBXlD_gi&)Mrhg5Pv8-r0EM6q`_ZY%1^~E&7c!)xSfiX(`xe2ZABZp$2 zjvXFw1CotP_|c!5n8f$MS9)V903-~wbh-TMua^J>04}A^8kjP^%vP#-I=xNeP?tS9 zGSmKd)*`kD%;x=;9siZrxSKmWMa|8b044xduK-cz-);_WRUa2pvtt~;nW;bLp)C=W~ zdLl&2l|Ay)PnN@wR^>HxE+QuYdysbV}gfQ!Oq0wiLX|tq}@OjzgkKl49KJufldPo8#0=VEuj~)R5!a1~K ze47mf7dujbRn#K{nlH#HIA$H0K?X4TIVjd2v&W}_49vuq4PfT|nx38j2tmLcy1BUl z(?a~Rkv|I+ zLE}XYNZ8}Ao{I4GuGL&=l8DpsyW*6->`-Tar&c9~8Y!_8aosv(qb3ViyaWEUN}~EJ zh}cD(XZVzi^oUWxdoy(s9X>~0G(Pi914ptm_Wy4{03+zXz87PB)-57DkfN%fpjv8a zoQfLL^}kgTkW!_}UHWIH*IKeXj$(gYZ2uOVooUceIp7HaEtPZ=CLuLz@5AAV@A{~) z6ui7gamP(5YE^e_B>e^|-^E`l*s;G?qoyoz^twEDBLY)W3zNX3M+Me9)v%6ZGpXD^_0#5S=Z9+m+y<8R zV!%d&0?Dlb0$}E0&_@DwFXIeOdRc;ZLPlmh;^z5j-Ab&PxByoMS}uhFB?x)FrGA15 z0vOEzHEs1!5={E00)ED4{La|P12!>z)*z*Z0xSZ}2_*si9}fWZ&+>|jjjM~te_q3_ z{BK{CRfWlNtyUV9V!XW&NK^LG1Uxx#i_t(rkJ1);UUsrG4H87NKn3znEuy7G3Un`{ zR^L6_#YWIEUT$dl?k^1dNacHVGHD1hW3*Aayv1NaRbg=h+=gVa$|#PQZ#J=EJ;`0% zo$ZpUJB-1!*K%d%d4*NFPAVM9kr9(lwqPfQLe2vCUTrN}E0@f{T*UhB0}Ph$FQdgz z-Shv(6e=JuBoZ<3K%thQN-yaMJNEazLVk3x)37sLZGZ819R=wELk-A^0k$Lu2*pnV z0GtveBzgJxW^1f4!I`N&VrIxgNCCEF>~8|#2}k`KKn~F?L6CT4W`}CI{Ak5AQy714 z&R8h35KmFf_P6OX4CU#KR;=}-+p7Z;81{%PZu*uZnBoLFg(3)Be*bN`!KyP)*5mpdI_-4$@+V%>%Z(N3<=4{| zFcDFbo{aX?u)gJm_C7^c2lkQ?hUcZE5AO?!m4bnM1`1dv-y%A zpHDTeBuLnmGH{W5tf0|9#F*V(m|SzRF~?GqlD$!oA?I>lx4amUiFU4UIqJXMoo=oX-?O9FHw`fZlYZb%=<6r}my} zfjm~%C&RJ(drXr%tFZJ44}DZ=UKk?S5HEsoYmbVzd2a47mzn%^{x#O^iA6!F>ig1G z6PdG5+o^+^!TBz}*P;@dEbl3?@6q)=>NYe-mxV~5qzy4dp+jzk>E%psA4>anF5iB3 z!1cZQ-ESjuuoRds!CmsSCa}>VrPVJUNQ0k_{rP5{wKng=$@UiJ@l;=CO2~3rKsawm zIhTPT;WWjF=neyU5OM=Tb94A~ZQ}N3jp0R0%S!&OV*tXEMR)^)oszsR9d|CDDnlhtVAX^-M7mO-T8nLq2 zy+EG2uLfj^Uz%~U<#mlFHQBk|m${X6nPvU)Fzp?gXHZY5-xApg3DzUn$VOl2;dVul zt)wqe$C<4EmFs!?G9d6!husWi#!jd6yZG*gE?;XFF!I_9-9POo2_}_0t&I&5&Zog# z9nL*~Ai3yejDn3Au_% zI~}rfe+=T}+*iCkTCj!RtfqY2x{*(Fv(E*2jXXnA-Pm3HRiAep&ZW4y{8akrY9db- zAlF50>&%#BAODh!Yze;`nym~Zk9^SftFUa#WU^nQHz9+YZUc0+^0j5Mx1EG(CFu)w zvr9-L+S_Y_Z#J1BBCPwE2l=-tWdO;y+SJcP!*KGQOt`c1Id-B z@K$>7kx>VRmcY1{$9;0VP6-$frz_ajozo_ppuFjHV}kIE`i#kKZ!05PlEQKO*j*=i z$8ImLtWLIL`KWqG!wQ;>p)~Qa;`Me3o6p=D9Zaa>9ggIfDvekST8N2P>#P|%PVRfQ zLoO%|U%7tfMs7`5w{dG#4d+o|)RjCsF35JL4w_vf!zicsHhX`SY8XM_a^mEP08(aJ z$`{=}(_Jk-s}C=33I(EAeiU#QYQNsx@okkfoamLbr7c7R$UJj?Oiljs`Oqq6c_k(0 z4Dtf9+C_EB?KSUukXjvsKCw=o95g^argM8h5_!4BXF-;*^-}_VwM>J#(Bh*=+q<@k z9oT-brQH*B);0CnI_+j2b{}UNH01Cz3uOIhY4Blup;gmjx0cswWrpYURNUHU1K}bm z4|%a|Hq{7u|6|>;6iRiaMMbq?#9GBD}@&;DO@9L^3BzN-OAIEQj zCGTe=?Eby;>gykyPkOf~Vflet3-)~kp1HDt%FT>{8be=Q#65Gf?&+qhDa%9~X_R3h zTGWZCll%G(YBKz0~`Bf$n50rEuOGv7EcF(&$N^e4ujKBwf2jo7Rk zOzUDxjCnckDEyed_4z|jjNmniUghBboc6Afl-3nhreOme`fAY5sueF14uJvmWXq6U( z+%=5~|^*4zA0KVO9)%-tnSh(GzgY&an!+)CdJv;M(BL39^9 zEZdz-w35amEGu9WLBW@^6zuQUm~@N03F$0PErg#OzwW^wH0aEBkAGxSi=R5B9gM+r zfp`547e3>;$rSO-zSrrK4}PT)wqFoh6-?pRNi)sLsFhkNGc8Uc9peSZZ|=V~G>@D%ry;)D6dtXtIWqI# z_4Gfmis4#8-ts|@OgygkYV7VqSJ&1eqnkVFuIl3+a>{4A#hm60bR%wQn%oGB>pcDv zDYJR35syxj;F82Ie+e05b=}Qw=cj&F%U6~!V}jd`T=AlmA&o|sBXVV*qe&n{%`&?XBcpnE4E^vW zIkk^)Xd?G=!`{kCPepZns)p2koWuH^z94<7I}CtqCo$IH-i+OG5MSeyoxv`9^1>7L zqJO0oiT7Fv3)A|kp&91LEoF{?u<^|bZb3WdHXVGVL!heu#IEchzN3uWql3mg7(2~3 zWkBL|wzM$oUF5oTRHn-2bLJqP+jcv38d*+R~Wdy4@0jjR?X` z-dO1w{7`gP#w=-pm_DqYx9tGJ@|W}YPWOF@t{xbMeJJ9FW(LeEY3-2k`Nkg2!4(ZY z{)DcegcV~R?~80PmZnOs-oMx15)NRujXGp)wis@5KeO=+5Gu^GGZNf~M{ZmmKCreN72Yn(b@0 zBK#Yuv40J*!G_fY`S)L89n{Bo$Ja!@Bt6L=bwtrH1(k$|AFfc7d=*!8rZljDAJ&sX zF^6FUf;kbQgMF3ddxSMInA*4K#mOGIbaN(FKDZ0!*to17tEvYZg7^iVx2^{#@~VpO zE9CGfCk^7lWM`sPCO@$9`#IhxrJU$o^dsQFaky9dHg?iQ2rFB>`^5LW)xG=EL?P2H zoJr{=q09pQ4*x+kj6+XUoX?2P(v~e}4u9l^V^g8b1ml}-jB2MBk+{Rbl!Vm>isx-B zJCU$+l^W>X{T900{gBS1kxoH5W&=U@cJzjq2dRmjT!^Tw=bN&ESW)dOtQ|6#6;hp^ z)Oi8qZ|TF-6ejB)g{wjYIg>{qBo{X|#f-!I5&3-uB^DZ$Z)LH6vNQ!sUte?J`F0~h z=;fNVhOT?uJMS}i3}HiVWrZ{fKE+7)Hh<#Jz>WB}dYKK+&F`*4$XnB#Gm-f`^wlU^ zzr%#HhIf77d@u;Z*eSQPy(3qTp4QMcSlzvJGpdJMO44(4e+3bxxu>&8Qhog}N6K{- zQFpkRK3^M1z5ht4Pt3l$)Lx0j_j(E8sqK0n@KpY()5R`!zVC2CE@CtAO&js|+ewz@ z3A0!uz9k#fvewM^{=Gb~GHQP`blE!GJ?<;%^OPT#j;@!eq*h_5_=|CEj*JId8)I2n z6k9)h>T@?RC}R07-FZ!Bwe#J^>Vns(9pe;1!TWVis#bYgY*UWwm(?LHJp^AHN_~fc z)HjY~fv@VKJHE!XqA9e?O9i2K}#z@@RP>3Y9u;^PGAG zSp?kOIAgOsztlB<*6f&#L9B&^51q|;4fgGayako00Gt#?NaJw5;M@^0HZJ);Q01NZHC?D()g%9-)bIHu5uz{8!=lK4Kj{W-|od!lh(Cjc(z8`p5+C~ zPpEOQ5k*`&I+^#?L0(rw>4v1vzgvXY(wkq3T0Q%&*L70S8Is}<(~jtpxTR&B$0_}s z)7AzET>kK4x zc&uXrZ+YZMR~hj<1a2qqVSO*oInt1sZ`+S_jV+2=ODX~v^O?>xY(Y-s-ucyS| zd<_yNY7b9@VcTCH43c!s^%wNq8Af#e2<&V(T@cO3oxsrgaM#F>^7fF=I_l(P8ONT` zVX|}luo@yZo}f~%V?62IM^{xWLp1T>Y=I@qWSUgd$bXyeV92w{Hd`lq4uXe?q{)L4 z^2!lH0tntRg*Wi3-My|xq8+Ao<1PD8z?JVv(GE~A0`NIJkJy7KODWp-pCS@bfZe)(2s;&NelD1*2#|$ST!yb#7_2alZ1W5 z+$QN+fw`1K>H8DA<0$_^e<;g#6V@lTaW}VKvNLK0%;f5hpa>t?m zD?c?Ae^guGA`s!PgZ;19JO&MFDWUF-9_c<02@@7^Ab|?}`i*Xps&c$IKN+e!E3ir8 zK{~_p#>csjzq}>j*U#$mr?l}Bl{9B^P$QxPbHTQ&xBgpTffiX?tb+HxCG5_Z&-$Cu zW?GWoI${xP{gJ~ylTTPgCETeqFQTGV`kgFzx2G1^e@IB8SzMFU>`{9z^pJE}TsI3z zE_t+bhj4QnJgBd?raE3@BK-WpvC}@ERJ6LzK2o@?ND1%Fg9@uYFaJY(>u(LV@shBQ z*nc0+G$||`HQV>`?t!41FONwU+i#J_fVKXCVhCL#p@dnYs*LmmWkXSm)S(9Cxf zqR+>zg^V7Xq_{{`C1ln&-l1YJ4Sz%_pX7N~-wpX;w-d9V0cWXpe5&FLOz7_(>(uM* z3hRTV8uJCg?AL73k<{?djlB=V-AF5PK1;;Q6_yv~a#0l0gH_gg8$2qE_*7qanq*1h z`7jAoeukBg5B_#OlJvX?5;jq~Qk#fgvT4Yd7B9S(nICgl*g&1;6PKDdE!{$e)+_4h zOu?7E*a`yXm|I3#GQNx!$2kR%b@PV5%*|fNdRZi!$>R-Y^}FL5HB2l z1{1tDv>_+qbX-3hAsE>30mI~r@-=ReInSr}9zMi4kuX1{@TUH9{Z&{wuWpH+)35m# zI|jlBZ{Ng3;dN7ND>0Gh(PfL{D;$j~9Sn(f2kv|)8{-zedpHYp&X)w<;i1{V2vw@elH z#5#L80?B9|@K}|{J zX=#0;7aKIo+{!Cd<`=%G9KY7bP8|z6!xt<};Zk9muU9{wZbeYjawmmw>yA8AMgEr=TYA(d+Rc_;MG?9_7_G_EglH|XwC?^M%nF9MT z`3XWTE;)qxke#@_n5P%Lo%7?Ya(rI`9$beY^F46+hxz4+%GuZyXBNCun*7B1?s@5_ z`d_Ff#yz^l4PmaT;0Bp7N&a2N5jjFS2?B+)HKusuX-twBEo!1}1X2!qF}V5>n1PA) z^%Is?&N?v*Us6`@dBj9bjxx&_$F;i6&-Weu4N%Et3Q&80lj|4p)@MU&j5In*xs+CV zpb2?9_EvXc1Vb@nX0H$m@=0}&bIh?r2{(EKZ*NMNF3|Zj$sEY&hDQ-hlumrD^;XxBcb0up=Yx)9q+z$%Y~h1{?SI{9xEkzP(iy{ z)B5cpQ~ZmnR;}ZWks!CPHwn4twj$O#A(xJDr`^LhX)m^(9$LK1b|bI5`1VNnA+LG$ zCeGFD2!TX!qlrcmXWeC<6bIHC;Z$d(0mBm%o`57?a9n~!h z?w`fOxPKVwvTV=JaPMDng|K4jvba9X;_NVK@rEKz+Mi$JDu!AdxwXZy-rEHk7Ne2` zvmloxemmOS-0o1D#zZFXJzqGVd9M>y2;}eg%{MT$mvtEhi61M}%U&=BI2<#&cktLv z<==ii;Ez-89Ty|N3+W8^?}A;m7FxN?FdJ~^pGP<>+`n&qA}qpM6|@{w=+-w9m+P|| z^?{Hse1ywRnb}UJImf7rGJT3UPt`29W`|PgVe!+#+SWyyHjTkeSsBJLt9WEb(zkE1 zRxI)hS0GU%`Z?X49Ao`YcC|Mj*xxGE0_}DXCKGIDV(MvnhtDt$83(#ZFUoZ%GGvzNcKKi+G;y*Z*-;LBTlr38s*PDXOZNm7On{SbGKIUe8^ro zgpZ)|fwMB*$VQ|Pf4O}>K|^AQsYay?W5FICj*4vD8IOoT&FtRlL;E^KZhkXf&R%AN z6P1mu)6;>S`q4<-p~**zwySm=q{I$Cv$o8Bz`5PmvZ@H_`t}zKg=q%XBhbFDEB7>= z7@TLr$OHp+iOSS;zf;j2Eczt|Z=A#d6WUD6u~o=1B11uq)i! zHsGT^n~%?$&sjO|E@#M*grbjl!=Bb7-Sy{Y_p!$G!atS7%H0@-aEQxM>$?cerORtX zDYAGXYlGTbo;wJMxq8hr#sur@WiERtU7Vn?`~sQEM{Cj87>fGTc>H%*gsBK;IzldH zT-NM4vN#PGDh%-3Z4Zxpb_9ZnRWcX|w5#_)F~_$Q-3 zQmqX{rcQOVUVzGEoS7Qq;<;%)nYK4Y5c1sC8GK&e2v4`00O6fXr-q49W;>Tw-vk;b zQ28c>OZy9;J-&C;@DmWH$kql`lD=;o0e#_+pRPbW*pQp8vo|)=^R=)#u1xUr<^44o zGkfhaJ~0vAXI)@bGPr2mX?^!3EJx$MiL$PMfF-Cp1vG_~Z8@2SncQ(R%Ld-GMnk3s zV&-7Ew718hg9k6?E^-S(%l!I7JHO%Rpr>2i+U4ud=1x0`I$s~0`Zs;nKv*(lY@Dkl ztkgDYu4^G^$Cpmt5yv`=+*@X3+SHo%0eg0TO2`fUA2QyF#y7)$y!x_vA*Ve>O>KQW z#O#~B+{Z&cn8=k?k5U0CyinMZ(NnV5C73gafjMPh6fzOPC@QbNVc<_hOgy%{Ji@r|Wv2qe{v26%@!fXkTq_Ot)VOT4O7F&W2{+31#G7CZ9q;N&vPOd*c z5Ae~~e;9HrGv+{UUo%kweAjx6PH2IG*c&sS{HK6F!Ze0_dzd<+; zsI=z^8rg#f^<|>^Y6581wX{HSzQq0`G|ik|}?g;h?j{ ze()=?tdcE$=$})X7Z3-8JwPb}G=kZ%i)K=2Zz(>Fq0jR~ulKNz9etxi2?u5sU z`y1A2{!;}TmpinZ)S|h-X z1q};)zcxQLo1D1g1uO720NGJ6EFY+BL3P5Oc(IEwlTIJ&>r+7mBOv4eoBG91KY^S6 z6`U}tauLwoqP3a355#-cf`W8CxR7&&U{LfX<;h1Npot%sOC4MX3~3;F6A}^vGE1!R zF4Pl49XMP;+;y%OC|{!|o!Se1bB(E(vq}!Xvn8TBM%I>_u!X#h$Xy`C0hOdsC87W@ z<9WP7F*R9}ZN?aMJT^{XBX`C)M5(WTv9KZ653G#f zZUIf7TiYR!=}o->+Ch|`C*;}_$bsSt`JdDmf;wBEh#k-fqvV>Po?^DRYZk)#XAEdU z+mjW|@F+S^nGxs{pf4zvMnvk|Fu`2_cB8JdXe>7 zU=;#2ILnUDz!yf_2l6#4e~B@Mc7&4;5P@b}9dfg?0Lnpu%7!%|$Y9WcfL7V=WP8%v z$EOyI;!{;-gq;)G5KVh~E}%}f1*VDz&QoA#<2DJXiB&cn+25Js18JbCC=jXjz?m5n zLD4!%;CKQj>^h^j1{S({7|6eMMg=pgOaZrdtHK9p?Vb$ zI9Wv?umqwZP*@RkCh*9B=mT_1P{XINE(!Y+P&SC(+p8m}p%SYx zDXIV|O5Syd#58~KJ5dAbYz_*ka(Js4dD~cd+e*FgvIT$8M1+Ke?t>o@Q9WTXDb&w{ r`$7^@LPCWc@`3-?2VC849PM8H|35I{f8Gy1fc98LOSwwXGVFf=plCrf literal 0 HcmV?d00001 diff --git a/docs/delays.png b/docs/delays.png new file mode 100644 index 0000000000000000000000000000000000000000..66db05e98bac6c83bcfc27448a7376d774492013 GIT binary patch literal 10875 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 gesbs>QD*ylh literal 0 HcmV?d00001 diff --git a/docs/delays_thumb.png b/docs/delays_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..84739446cfa90df6736fc1fe7f7aa367062351dd GIT binary patch literal 10938 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 + + + + + + +Mainline DHT extensions + + + + + +
    +
    + + libtorrent logo + +
    +

    Mainline DHT extensions

    + +++ + + + +
    Version:2.0.0
    +

    libtorrent implements a few extensions to the Mainline DHT protocol.

    +
    +

    get_peers response

    +

    libtorrent always responds with nodes to a get_peers request. If it has +peers for the specified info-hash, it will return values as well. This is +because just because some peer announced to us, doesn't mean that we are +among the 8 closest nodes of the info hash. libtorrent also keeps traversing +nodes using get_peers until it has found the 8 closest ones, and then announces +to those nodes.

    +
    +
    +

    forward compatibility

    +

    In order to support future DHT messages, any message which is not recognized +but has either an info_hash or target argument is interpreted as +find node for that target. i.e. it returns nodes. This allows future messages +to be properly forwarded by clients that don't understand them instead of +being blocked.

    +
    +
    +

    client identification

    +

    In each DHT packet, an extra key is inserted named "v". This is a string +describing the client and version used. This can help a lot when debugging +and finding errors in client implementations. The string is encoded as four +characters, two characters describing the client and two characters interpreted +as a binary number describing the client version.

    +

    Currently known clients:

    + ++++ + + + + + + + + + + + + + + +
    uTorrentUT
    libtorrentLT
    MooPoliceMP
    GetRightGR
    +
    +
    +

    IPv6 support

    +

    This extension is superseded by BEP 32.

    +

    The DHT messages that don't support IPv6 are the nodes replies. +They encode all the contacts as 6 bytes packed together in sequence in a +string. The problem is that IPv6 endpoints cannot be encoded as 6 bytes, but +needs 18 bytes. The extension libtorrent applies is to add another key, called +nodes2.

    +

    nodes2 may be present in replies that contains a nodes key. It is encoded +as a list of strings. Each string represents one contact and is encoded as 20 +bytes node-id and then a variable length encoded IP address (6 bytes in IPv4 case +and 18 bytes in IPv6 case).

    +

    As an optimization, libtorrent does not include the extra key in case there are +only IPv4 nodes present.

    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/dht_extensions.rst b/docs/dht_extensions.rst new file mode 100644 index 0000000..db4d94b --- /dev/null +++ b/docs/dht_extensions.rst @@ -0,0 +1,68 @@ +Mainline DHT extensions +======================= + +.. include:: header.rst + +libtorrent implements a few extensions to the Mainline DHT protocol. + +get_peers response +------------------ + +libtorrent always responds with ``nodes`` to a get_peers request. If it has +peers for the specified info-hash, it will return ``values`` as well. This is +because just because some peer announced to us, doesn't mean that we are +among the 8 closest nodes of the info hash. libtorrent also keeps traversing +nodes using get_peers until it has found the 8 closest ones, and then announces +to those nodes. + +forward compatibility +--------------------- + +In order to support future DHT messages, any message which is not recognized +but has either an ``info_hash`` or ``target`` argument is interpreted as +find node for that target. i.e. it returns nodes. This allows future messages +to be properly forwarded by clients that don't understand them instead of +being blocked. + +client identification +--------------------- + +In each DHT packet, an extra key is inserted named "v". This is a string +describing the client and version used. This can help a lot when debugging +and finding errors in client implementations. The string is encoded as four +characters, two characters describing the client and two characters interpreted +as a binary number describing the client version. + +Currently known clients: + ++---------------+--------+ +| uTorrent | ``UT`` | ++---------------+--------+ +| libtorrent | ``LT`` | ++---------------+--------+ +| MooPolice | ``MP`` | ++---------------+--------+ +| GetRight | ``GR`` | ++---------------+--------+ + +IPv6 support +------------ + +**This extension is superseded by** `BEP 32`_. + +.. _`BEP 32`: https://www.bittorrent.org/beps/bep_0032.html + +The DHT messages that don't support IPv6 are the ``nodes`` replies. +They encode all the contacts as 6 bytes packed together in sequence in a +string. The problem is that IPv6 endpoints cannot be encoded as 6 bytes, but +needs 18 bytes. The extension libtorrent applies is to add another key, called +``nodes2``. + +``nodes2`` may be present in replies that contains a ``nodes`` key. It is encoded +as a list of strings. Each string represents one contact and is encoded as 20 +bytes node-id and then a variable length encoded IP address (6 bytes in IPv4 case +and 18 bytes in IPv6 case). + +As an optimization, libtorrent does not include the extra key in case there are +only IPv4 nodes present. + diff --git a/docs/dht_rss.html b/docs/dht_rss.html new file mode 100644 index 0000000..3db6dca --- /dev/null +++ b/docs/dht_rss.html @@ -0,0 +1,405 @@ + + + + + + + +BitTorrent extension for DHT RSS feeds + + + + + +
    +
    + + libtorrent logo + +
    +

    BitTorrent extension for DHT RSS feeds

    + +++ + + + +
    Version:2.0.0
    + +

    This proposal has been superseded by the dht_put feature. This may +still be implemented on top of that.

    +

    This is a proposal for an extension to the BitTorrent DHT to allow +for decentralized RSS feed like functionality.

    +

    The intention is to allow the creation of repositories of torrents +where only a single identity has the authority to add new content. For +this repository to be robust against network failures and resilient +to attacks at the source.

    +

    The target ID under which the repository is stored in the DHT, is the +SHA-1 hash of a feed name and the 512 bit public key. This private key +in this pair MUST be used to sign every item stored in the repository. +Every message that contain signed items MUST also include this key, to +allow the receiver to verify the key itself against the target ID as well +as the validity of the signatures of the items. Every recipient of a +message with feed items in it MUST verify both the validity of the public +key against the target ID it is stored under, as well as the validity of +the signatures of each individual item.

    +

    As with normal DHT announces, the write-token mechanism is used to +prevent IP spoof attacks.

    +
    +

    terminology

    +

    In this document, a storage node refers to the node in the DHT to which +an item is being announce. A subscribing node refers to a node which +makes look ups in the DHT to find the storage nodes, to request items +from them.

    +
    +
    +

    linked lists

    +

    Items are chained together in a general singly linked list. A linked +list does not necessarily contain RSS items, and no RSS related items +are mandatory. However, RSS items will be used as examples in this BEP:

    +
    +key = SHA1(name + key)
    ++---------+
    +| head    |           key = SHA1(bencode(item))
    +| +---------+         +---------+
    +| | next    |-------->| item    |          key = SHA1(bencode(item))
    +| | key     |         | +---------+        +---------+
    +| | name    |         | | next    |------->| item    |
    +| | seq     |         | | key     |        | +---------+
    +| | ...     |         | | ...     |        | | next    |--->0
    +| +---------+         | +---------+        | | key     |
    +| sig     |           | sig     |          | | ...     |
    ++---------+           +---------+          | +---------+
    +                                           | sig     |
    +                                           +---------+
    +
    +

    The next pointer is at least 20 byte ID in the DHT key space pointing to where the next +item in the list is announced. The list is terminated with an ID of all zeros.

    +

    The ID an items is announced to is determined by the SHA1 hash of the bencoded representation +of the item itself. This contains all fields in the item, except the signature. +The only mandatory fields in an item are next, key and sig.

    +

    The key field MUST match the public key of the list head node. The sig field +MUST be the signature of the bencoded representation of item or head (whichever +is included in the message).

    +

    All subscribers MUST verify that the item is announced under the correct DHT key +and MUST verify the signature is valid and MUST verify the public key is the same +as the list-head. If a node fails any of these checks, it must be ignored and the +chain of items considered terminated.

    +

    Each item holds a bencoded dictionary with arbitrary keys, except two mandatory keys: +next and key. The signature sig is transferred outside of this dictionary +and is the signature of all of it. An implementation should store any arbitrary keys that +are announced to an item, within reasonable restriction such as nesting, size and numeric +range of integers.

    +
    +
    +

    skip lists

    +

    The next key stored in the list head and the items is a string of at least length +20 bytes, it may be any length divisible by 20. Each 20 bytes are the ID of the next +item in the list, the item 2 hops away, 4 hops away, 8 hops away, and so on. For +simplicity, only the first ID (1 hop) in the next field is illustrated above.

    +

    A publisher of an item SHOULD include as many IDs in the next field as the remaining +size of the list warrants, within reason.

    +

    These skip lists allow for parallelized lookups of items and also makes it more efficient +to search for specific items. It also mitigates breaking lists missing some items.

    +

    Figure of the skip list in the first list item:

    +
    +n      Item0  Item1  Item2  Item3  Item4  Item5  Item6  Item7  Item8  Item9  Item10
    +0        O----->
    +20       O------------>
    +40       O-------------------------->
    +60       O------------------------------------------------------>
    +
    +

    n refers to the byte offset into the next field.

    +
    +
    +

    list-head

    +

    The list head item is special in that it can be updated, without changing its +DHT key. This is required to prepend new items to the linked list. To authenticate +that only the original publisher can update the head, the whole linked list head +is signed. In order to avoid a malicious node to overwrite the list head with an old +version, the sequence number seq must be monotonically increasing for each update, +and a node hosting the list node MUST not downgrade a list head from a higher sequence +number to a lower one, only upgrade.

    +

    The list head's DHT key (which it is announced to) MUST be the SHA1 hash of the name +(n) and key fields concatenated.

    +

    Any node MUST reject any list head which is announced under any other ID.

    +
    +
    +

    messages

    +

    These are the messages to deal with linked lists.

    +

    The id field in these messages has the same semantics as the standard DHT messages, +i.e. the node ID of the node sending the message, to maintain the structure of the DHT +network.

    +

    The token field also has the same semantics as the standard DHT message get_peers +and announce_peer, when requesting an item and to write an item respectively.

    +

    nodes and nodes6 has the same semantics as in its get_peers response.

    +
    +

    requesting items

    +

    This message can be used to request both a list head and a list item. When requesting +a list head, the n (name) field MUST be specified. When requesting a list item the +n field is not required.

    +
    +{
    +   "a":
    +   {
    +      "id": <20 byte ID of sending node>,
    +      "key": <64 byte public curve25519 key for this list>,
    +      "n": <list name>
    +      "target": <target-id for 'head' or 'item'>
    +   },
    +   "q": "get_item",
    +   "t": <transaction-id>,
    +   "y": "q",
    +}
    +
    +

    When requesting a list-head the target MUST always be SHA-1(feed_name + public_key). +target is the target node ID the item was written to.

    +

    The n field is the name of the list. If specified, It MUST be UTF-8 encoded string +and it MUST match the name of the feed in the receiving node.

    +
    +
    +

    request item response

    +

    This is the format of a response of a list head:

    +
    +{
    +   "r":
    +   {
    +      "head":
    +      {
    +         "key": <64 byte public curve25519 key for this list>,
    +         "next": <20 bytes item ID>,
    +         "n": <name of the linked list>,
    +         "seq": <monotonically increasing sequence number>
    +      },
    +      "sig": <curve25519 signature of 'head' entry (in bencoded form)>,
    +      "id": <20 byte id of sending node>,
    +      "token": <write-token>,
    +      "nodes": <n * compact IPv4-port pair>,
    +      "nodes6": <n * compact IPv6-port pair>
    +   },
    +   "t": <transaction-id>,
    +   "y": "r",
    +}
    +
    +

    This is the format of a response of a list item:

    +
    +{
    +   "r":
    +   {
    +      "item":
    +      {
    +         "key": <64 byte public curve25519 key for this list>,
    +         "next": <20 bytes item ID>,
    +         ...
    +      },
    +      "sig": <curve25519 signature of 'item' entry (in bencoded form)>,
    +      "id": <20 byte id of sending node>,
    +      "token": <write-token>,
    +      "nodes": <n * compact IPv4-port pair>,
    +      "nodes6": <n * compact IPv6-port pair>
    +   },
    +   "t": <transaction-id>,
    +   "y": "r",
    +}
    +
    +

    A client receiving a get_item response MUST verify the signature in the sig +field against the bencoded representation of the item field, using the key as +the public key. The key MUST match the public key of the feed.

    +

    The item dictionary MAY contain arbitrary keys, and all keys MUST be stored for +items.

    +
    +
    +

    announcing items

    +

    The message format for announcing a list head:

    +
    +{
    +   "a":
    +   {
    +      "head":
    +      {
    +         "key": <64 byte public curve25519 key for this list>,
    +         "next": <20 bytes item ID>,
    +         "n": <name of the linked list>,
    +         "seq": <monotonically increasing sequence number>
    +      },
    +      "sig": <curve25519 signature of 'head' entry (in bencoded form)>,
    +      "id": <20 byte node-id of origin node>,
    +      "target": <target-id as derived from public key and name>,
    +      "token": <write-token as obtained by previous request>
    +   },
    +   "y": "q",
    +   "q": "announce_item",
    +   "t": <transaction-id>
    +}
    +
    +

    The message format for announcing a list item:

    +
    +{
    +   "a":
    +   {
    +      "item":
    +      {
    +         "key": <64 byte public curve25519 key for this list>,
    +         "next": <20 bytes item ID>,
    +         ...
    +      },
    +      "sig": <curve25519 signature of 'item' entry (in bencoded form)>,
    +      "id": <20 byte node-id of origin node>,
    +      "target": <target-id as derived from item dict>,
    +      "token": <write-token as obtained by previous request>
    +   },
    +   "y": "q",
    +   "q": "announce_item",
    +   "t": <transaction-id>
    +}
    +
    +

    A storage node MAY reject items and heads whose bencoded representation is +greater than 1024 bytes.

    +
    +
    +
    +

    re-announcing

    +

    In order to keep feeds alive, subscriber nodes SHOULD help out in announcing +items they have downloaded to the DHT.

    +

    Every subscriber node SHOULD store items in long term storage, across sessions, +in order to keep items alive for as long as possible, with as few sources as possible.

    +

    Subscribers to a feed SHOULD also announce items that they know of, to the feed. +Since a feed may have many subscribers and many items, subscribers should re-announce +items according to the following algorithm.

    +
    +1. pick one random item (i) from the local repository (except
    +   items already announced this round)
    +2. If all items in the local repository have been announced
    +  2.1 terminate
    +3. look up item i in the DHT
    +4. If fewer than 8 nodes returned the item
    +  4.1 announce i to the DHT
    +  4.2 goto 1
    +
    +

    This ensures a balanced load on the DHT while still keeping items alive

    +
    +
    +

    timeouts

    +

    Items SHOULD be announced to the DHT every 30 minutes. A storage node MAY time +out an item after 60 minutes of no one announcing it.

    +

    A storing node MAY extend the timeout when it receives a request for it. Since +items are immutable, the data doesn't go stale. Therefore it doesn't matter if +the storing node no longer is in the set of the 8 closest nodes.

    +
    +
    +

    RSS feeds

    +

    For RSS feeds, following keys are mandatory in the list item's item dictionary.

    +
    +
    ih
    +
    The torrent's info hash
    +
    size
    +
    The size (in bytes) of all files the torrent
    +
    n
    +
    name of the torrent
    +
    +
    +

    example

    +

    This is an example of an announce_item message:

    +
    +{
    +   "a":
    +   {
    +      "item":
    +      {
    +         "key": "6bc1de5443d1a7c536cdf69433ac4a7163d3c63e2f9c92d
    +            78f6011cf63dbcd5b638bbc2119cdad0c57e4c61bc69ba5e2c08
    +            b918c2db8d1848cf514bd9958d307",
    +         "info-hash": "7ea94c240691311dc0916a2a91eb7c3db2c6f3e4",
    +         "size": 24315329,
    +         "n": "my stuff",
    +         "next": "c68f29156404e8e0aas8761ef5236bcagf7f8f2e"
    +      }
    +      "sig": <signature>
    +      "id": "b46989156404e8e0acdb751ef553b210ef77822e",
    +      "target": "b4692ef0005639e86d7165bf378474107bf3a762"
    +      "token": "23ba"
    +   },
    +   "y": "q",
    +   "q": "announce_item",
    +"t": "a421"
    +}
    +
    +

    Strings are printed in hex for printability, but actual encoding is binary.

    +

    Note that target is in fact SHA1 hash of the same data the signature sig +is the signature of, i.e.:

    +
    +d9:info-hash20:7ea94c240691311dc0916a2a91eb7c3db2c6f3e43:key64:6bc1de5443d1
    +a7c536cdf69433ac4a7163d3c63e2f9c92d78f6011cf63dbcd5b638bbc2119cdad0c57e4c61
    +bc69ba5e2c08b918c2db8d1848cf514bd9958d3071:n8:my stuff4:next20:c68f29156404
    +e8e0aas8761ef5236bcagf7f8f2e4:sizei24315329ee
    +
    +

    (note that binary data is printed as hex)

    +
    +
    +
    +

    RSS feed URI scheme

    +

    The proposed URI scheme for DHT feeds is:

    +
    +magnet:?xt=btfd:<base16-curve25519-public-key> &dn= <feed name>
    +
    +

    Note that a difference from regular torrent magnet links is the btfd +versus btih used in regular magnet links to torrents.

    +

    The feed name is mandatory since it is used in the request and when +calculating the target ID.

    +
    +
    +

    rationale

    +

    The reason to use curve25519 instead of, for instance, RSA is compactness. According to +https://cr.yp.to/, curve25519 is free from patent claims and there are open implementations +in both C and Java.

    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/dht_rss.rst b/docs/dht_rss.rst new file mode 100644 index 0000000..6601c9c --- /dev/null +++ b/docs/dht_rss.rst @@ -0,0 +1,396 @@ +====================================== +BitTorrent extension for DHT RSS feeds +====================================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This proposal has been superseded by the dht_put_ feature. This may +still be implemented on top of that. + +.. _dht_put: dht_store.html + +This is a proposal for an extension to the BitTorrent DHT to allow +for decentralized RSS feed like functionality. + +The intention is to allow the creation of repositories of torrents +where only a single identity has the authority to add new content. For +this repository to be robust against network failures and resilient +to attacks at the source. + +The target ID under which the repository is stored in the DHT, is the +SHA-1 hash of a feed name and the 512 bit public key. This private key +in this pair MUST be used to sign every item stored in the repository. +Every message that contain signed items MUST also include this key, to +allow the receiver to verify the key itself against the target ID as well +as the validity of the signatures of the items. Every recipient of a +message with feed items in it MUST verify both the validity of the public +key against the target ID it is stored under, as well as the validity of +the signatures of each individual item. + +As with normal DHT announces, the write-token mechanism is used to +prevent IP spoof attacks. + +terminology +----------- + +In this document, a *storage node* refers to the node in the DHT to which +an item is being announce. A *subscribing node* refers to a node which +makes look ups in the DHT to find the storage nodes, to request items +from them. + +linked lists +------------ + +Items are chained together in a general singly linked list. A linked +list does not necessarily contain RSS items, and no RSS related items +are mandatory. However, RSS items will be used as examples in this BEP:: + + key = SHA1(name + key) + +---------+ + | head | key = SHA1(bencode(item)) + | +---------+ +---------+ + | | next |-------->| item | key = SHA1(bencode(item)) + | | key | | +---------+ +---------+ + | | name | | | next |------->| item | + | | seq | | | key | | +---------+ + | | ... | | | ... | | | next |--->0 + | +---------+ | +---------+ | | key | + | sig | | sig | | | ... | + +---------+ +---------+ | +---------+ + | sig | + +---------+ + +The ``next`` pointer is at least 20 byte ID in the DHT key space pointing to where the next +item in the list is announced. The list is terminated with an ID of all zeros. + +The ID an items is announced to is determined by the SHA1 hash of the bencoded representation +of the item itself. This contains all fields in the item, except the signature. +The only mandatory fields in an item are ``next``, ``key`` and ``sig``. + +The ``key`` field MUST match the public key of the list head node. The ``sig`` field +MUST be the signature of the bencoded representation of ``item`` or ``head`` (whichever +is included in the message). + +All subscribers MUST verify that the item is announced under the correct DHT key +and MUST verify the signature is valid and MUST verify the public key is the same +as the list-head. If a node fails any of these checks, it must be ignored and the +chain of items considered terminated. + +Each item holds a bencoded dictionary with arbitrary keys, except two mandatory keys: +``next`` and ``key``. The signature ``sig`` is transferred outside of this dictionary +and is the signature of all of it. An implementation should store any arbitrary keys that +are announced to an item, within reasonable restriction such as nesting, size and numeric +range of integers. + +skip lists +---------- + +The ``next`` key stored in the list head and the items is a string of at least length +20 bytes, it may be any length divisible by 20. Each 20 bytes are the ID of the next +item in the list, the item 2 hops away, 4 hops away, 8 hops away, and so on. For +simplicity, only the first ID (1 hop) in the ``next`` field is illustrated above. + +A publisher of an item SHOULD include as many IDs in the ``next`` field as the remaining +size of the list warrants, within reason. + +These skip lists allow for parallelized lookups of items and also makes it more efficient +to search for specific items. It also mitigates breaking lists missing some items. + +Figure of the skip list in the first list item:: + + n Item0 Item1 Item2 Item3 Item4 Item5 Item6 Item7 Item8 Item9 Item10 + 0 O-----> + 20 O------------> + 40 O--------------------------> + 60 O------------------------------------------------------> + +*n* refers to the byte offset into the ``next`` field. + +list-head +--------- + +The list head item is special in that it can be updated, without changing its +DHT key. This is required to prepend new items to the linked list. To authenticate +that only the original publisher can update the head, the whole linked list head +is signed. In order to avoid a malicious node to overwrite the list head with an old +version, the sequence number ``seq`` must be monotonically increasing for each update, +and a node hosting the list node MUST not downgrade a list head from a higher sequence +number to a lower one, only upgrade. + +The list head's DHT key (which it is announced to) MUST be the SHA1 hash of the name +(``n``) and ``key`` fields concatenated. + +Any node MUST reject any list head which is announced under any other ID. + +messages +-------- + +These are the messages to deal with linked lists. + +The ``id`` field in these messages has the same semantics as the standard DHT messages, +i.e. the node ID of the node sending the message, to maintain the structure of the DHT +network. + +The ``token`` field also has the same semantics as the standard DHT message ``get_peers`` +and ``announce_peer``, when requesting an item and to write an item respectively. + +``nodes`` and ``nodes6`` has the same semantics as in its ``get_peers`` response. + +requesting items +................ + +This message can be used to request both a list head and a list item. When requesting +a list head, the ``n`` (name) field MUST be specified. When requesting a list item the +``n`` field is not required. + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte ID of sending node>*, + "key": *<64 byte public curve25519 key for this list>*, + "n": ** + "target": ** + }, + "q": "get_item", + "t": **, + "y": "q", + } + +When requesting a list-head the ``target`` MUST always be SHA-1(*feed_name* + *public_key*). +``target`` is the target node ID the item was written to. + +The ``n`` field is the name of the list. If specified, It MUST be UTF-8 encoded string +and it MUST match the name of the feed in the receiving node. + +request item response +..................... + +This is the format of a response of a list head: + +.. parsed-literal:: + + { + "r": + { + "head": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + "n": **, + "seq": ** + }, + "sig": **, + "id": *<20 byte id of sending node>*, + "token": **, + "nodes": **, + "nodes6": ** + }, + "t": **, + "y": "r", + } + +This is the format of a response of a list item: + +.. parsed-literal:: + + { + "r": + { + "item": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + ... + }, + "sig": **, + "id": *<20 byte id of sending node>*, + "token": **, + "nodes": **, + "nodes6": ** + }, + "t": **, + "y": "r", + } + +A client receiving a ``get_item`` response MUST verify the signature in the ``sig`` +field against the bencoded representation of the ``item`` field, using the ``key`` as +the public key. The ``key`` MUST match the public key of the feed. + +The ``item`` dictionary MAY contain arbitrary keys, and all keys MUST be stored for +items. + +announcing items +................ + +The message format for announcing a list head: + +.. parsed-literal:: + + { + "a": + { + "head": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + "n": **, + "seq": ** + }, + "sig": **, + "id": *<20 byte node-id of origin node>*, + "target": **, + "token": ** + }, + "y": "q", + "q": "announce_item", + "t": ** + } + +The message format for announcing a list item: + +.. parsed-literal:: + + { + "a": + { + "item": + { + "key": *<64 byte public curve25519 key for this list>*, + "next": *<20 bytes item ID>*, + ... + }, + "sig": **, + "id": *<20 byte node-id of origin node>*, + "target": **, + "token": ** + }, + "y": "q", + "q": "announce_item", + "t": ** + } + +A storage node MAY reject items and heads whose bencoded representation is +greater than 1024 bytes. + +re-announcing +------------- + +In order to keep feeds alive, subscriber nodes SHOULD help out in announcing +items they have downloaded to the DHT. + +Every subscriber node SHOULD store items in long term storage, across sessions, +in order to keep items alive for as long as possible, with as few sources as possible. + +Subscribers to a feed SHOULD also announce items that they know of, to the feed. +Since a feed may have many subscribers and many items, subscribers should re-announce +items according to the following algorithm. + +.. parsed-literal:: + + 1. pick one random item (*i*) from the local repository (except + items already announced this round) + 2. If all items in the local repository have been announced + 2.1 terminate + 3. look up item *i* in the DHT + 4. If fewer than 8 nodes returned the item + 4.1 announce *i* to the DHT + 4.2 goto 1 + +This ensures a balanced load on the DHT while still keeping items alive + +timeouts +-------- + +Items SHOULD be announced to the DHT every 30 minutes. A storage node MAY time +out an item after 60 minutes of no one announcing it. + +A storing node MAY extend the timeout when it receives a request for it. Since +items are immutable, the data doesn't go stale. Therefore it doesn't matter if +the storing node no longer is in the set of the 8 closest nodes. + +RSS feeds +--------- + +For RSS feeds, following keys are mandatory in the list item's ``item`` dictionary. + +ih + The torrent's info hash + +size + The size (in bytes) of all files the torrent + +n + name of the torrent + +example +....... + +This is an example of an ``announce_item`` message: + +.. parsed-literal:: + + { + "a": + { + "item": + { + "key": "6bc1de5443d1a7c536cdf69433ac4a7163d3c63e2f9c92d + 78f6011cf63dbcd5b638bbc2119cdad0c57e4c61bc69ba5e2c08 + b918c2db8d1848cf514bd9958d307", + "info-hash": "7ea94c240691311dc0916a2a91eb7c3db2c6f3e4", + "size": 24315329, + "n": "my stuff", + "next": "c68f29156404e8e0aas8761ef5236bcagf7f8f2e" + } + "sig": ** + "id": "b46989156404e8e0acdb751ef553b210ef77822e", + "target": "b4692ef0005639e86d7165bf378474107bf3a762" + "token": "23ba" + }, + "y": "q", + "q": "announce_item", + "t": "a421" + } + +Strings are printed in hex for printability, but actual encoding is binary. + +Note that ``target`` is in fact SHA1 hash of the same data the signature ``sig`` +is the signature of, i.e.:: + + d9:info-hash20:7ea94c240691311dc0916a2a91eb7c3db2c6f3e43:key64:6bc1de5443d1 + a7c536cdf69433ac4a7163d3c63e2f9c92d78f6011cf63dbcd5b638bbc2119cdad0c57e4c61 + bc69ba5e2c08b918c2db8d1848cf514bd9958d3071:n8:my stuff4:next20:c68f29156404 + e8e0aas8761ef5236bcagf7f8f2e4:sizei24315329ee + +(note that binary data is printed as hex) + +RSS feed URI scheme +-------------------- + +The proposed URI scheme for DHT feeds is: + +.. parsed-literal:: + + magnet:?xt=btfd:** &dn= ** + +Note that a difference from regular torrent magnet links is the **btfd** +versus **btih** used in regular magnet links to torrents. + +The *feed name* is mandatory since it is used in the request and when +calculating the target ID. + +rationale +--------- + +The reason to use curve25519_ instead of, for instance, RSA is compactness. According to +https://cr.yp.to/, curve25519 is free from patent claims and there are open implementations +in both C and Java. + +.. _curve25519: https://cr.yp.to/ecdh.html + diff --git a/docs/dht_sec.html b/docs/dht_sec.html new file mode 100644 index 0000000..ba75f6f --- /dev/null +++ b/docs/dht_sec.html @@ -0,0 +1,294 @@ + + + + + + +dht_sec.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +
    +

    BitTorrent DHT security extension

    +

    The purpose of this extension is to make it harder to launch a few +specific attacks against the BitTorrent DHT and also to make it harder +to snoop the network.

    +

    Specifically the attack this extension intends to make harder is launching +8 or more DHT nodes which node-IDs selected close to a specific target +info-hash, in order to become the main nodes hosting peers for it. Currently +this is very easy to do and lets the attacker not only see all the traffic +related to this specific info-hash but also block access to it by other +peers.

    +

    The proposed guard against this is to enforce restrictions on which node-ID +a node can choose, based on its external IP address.

    +
    +
    +

    considerations

    +

    One straight forward scheme to tie the node ID to an IP would be to hash +the IP and force the node ID to share the prefix of that hash. One main +draw back of this approach is that an entities control over the DHT key +space grows linearly with its control over the IP address space.

    +

    In order to successfully launch an attack, you just need to find 8 IPs +whose hash will be closest to the target info-hash. Given the current +size of the DHT, that is quite likely to be possible by anyone in control +of a /8 IP block.

    +

    The size of the DHT is approximately 8.4 million nodes. This is estimated +by observing that a typical routing table typically has about 20 of its +top routing table buckets full. That means the key space is dense enough +to contain 8 nodes for every combination of the 20 top bits of node IDs.

    +
    +2^20 * 8 = 8388608
    +

    By controlling that many IP addresses, an attacker could snoop any info-hash. +By controlling 8 times that many IP addresses, an attacker could actually +take over any info-hash.

    +

    With IPv4, snooping would require a /8 IP block, giving access to 16.7 million +IPs.

    +

    Another problem with hashing the IP is that multiple users behind a NAT are +forced to run their DHT nodes on the same node ID.

    +
    +
    +

    Node ID restriction

    +

    In order to avoid the number node IDs controlled to grow linearly by the number +of IPs, as well as allowing more than one node ID per external IP, the node +ID can be restricted at each class level of the IP.

    +

    Another important property of the restriction put on node IDs is that the +distribution of the IDs remain uniform. This is why CRC32C (Castagnoli) was +chosen as the hash function.

    +

    The expression to calculate a valid ID prefix (from an IPv4 address) is:

    +
    +crc32c((ip & 0x030f3fff) | (r << 29))
    +
    +

    And for an IPv6 address (ip is the high 64 bits of the address):

    +
    +crc32c((ip & 0x0103070f1f3f7fff) | (r << 61))
    +
    +

    r is a random number in the range [0, 7]. The resulting integer, +representing the masked IP address is supposed to be big-endian before +hashed. The "|" operator means bit-wise OR.

    +

    The details of implementing this is to evaluate the expression, store the +result in a big-endian 64 bit integer and hash those 8 bytes with CRC32C.

    +

    The first (most significant) 21 bits of the node ID used in the DHT MUST +match the first 21 bits of the resulting hash. The last byte of the hash MUST +match the random number (r) used to generate the hash.

    +ip_id_v4.png +ip_id_v6.png +

    Example code code for calculating a valid node ID:

    +
    +uint8_t* ip; // our external IPv4 or IPv6 address (network byte order)
    +int num_octets; // the number of octets to consider in ip (4 or 8)
    +uint8_t node_id[20]; // resulting node ID
    +
    +uint8_t v4_mask[] = { 0x03, 0x0f, 0x3f, 0xff };
    +uint8_t v6_mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
    +uint8_t* mask = num_octets == 4 ? v4_mask : v6_mask;
    +
    +for (int i = 0; i < num_octets; ++i)
    +        ip[i] &= mask[i];
    +
    +uint32_t rand = std::rand() & 0xff;
    +uint8_t r = rand & 0x7;
    +ip[0] |= r << 5;
    +
    +uint32_t crc = 0;
    +crc = crc32c(crc, ip, num_octets);
    +
    +// only take the top 21 bits from crc
    +node_id[0] = (crc >> 24) & 0xff;
    +node_id[1] = (crc >> 16) & 0xff;
    +node_id[2] = ((crc >> 8) & 0xf8) | (std::rand() & 0x7);
    +for (int i = 3; i < 19; ++i) node_id[i] = std::rand();
    +node_id[19] = rand;
    +
    +

    test vectors:

    +
    +IP           rand  example node ID
    +============ ===== ==========================================
    +124.31.75.21   1   5fbfbf f10c5d6a4ec8a88e4c6ab4c28b95eee4 01
    +21.75.31.124  86   5a3ce9 c14e7a08645677bbd1cfe7d8f956d532 56
    +65.23.51.170  22   a5d432 20bc8f112a3d426c84764f8c2a1150e6 16
    +84.124.73.14  65   1b0321 dd1bb1fe518101ceef99462b947a01ff 41
    +43.213.53.83  90   e56f6c bf5b7c4be0237986d5243b87aa6d5130 5a
    +
    +

    The bold parts of the node ID are the important parts. The rest are +random numbers. The last bold number of each row has only its most significant +bit pulled from the CRC32C function. The lower 3 bits are random.

    +
    +
    +

    bootstrapping

    +

    In order to set ones initial node ID, the external IP needs to be known. This +is not a trivial problem. With this extension, all DHT responses SHOULD include +a top-level field called ip, containing a compact binary representation of +the requester's IP and port. That is big-endian IP followed by 2 bytes of big-endian +port.

    +

    The IP portion is the same byte sequence used to verify the node ID.

    +

    It is important that the ip field is in the top level dictionary. Nodes that +enforce the node-ID will respond with an error message ("y": "e", "e": { ... }), +whereas a node that supports this extension but without enforcing it will respond +with a normal reply ("y": "r", "r": { ... }).

    +

    A DHT node which receives an ip result in a request SHOULD consider restarting +its DHT node with a new node ID, taking this IP into account. Since a single node +can not be trusted, there should be some mechanism to determine whether or +not the node has a correct understanding of its external IP or not. This could +be done by voting, or only restart the DHT once at least a certain number of +nodes, from separate searches, tells you your node ID is incorrect.

    +
    +
    +

    rationale

    +

    The choice of using CRC32C instead of a more traditional cryptographic hash +function is justified primarily of these reasons:

    +
      +
    1. it is a fast function
    2. +
    3. produces well distributed results
    4. +
    5. there is no need for the hash function to be one-way (the input set is +so small that any hash function could be reversed).
    6. +
    7. CRC32C (Castagnoli) is supported in hardware by SSE 4.2, which can +significantly speed up computation
    8. +
    +

    There are primarily two tests run on SHA-1 and CRC32C to establish the +distribution of results. The first one is the number of bits in the output +set that contain every possible combination of bits. The CRC32C function +has a longer such prefix in its output than SHA-1. This means nodes will still +have well uniformly distributed IDs, even when IP addresses in use are not +uniformly distributed.

    +

    The following graph illustrate a few different hash functions with regard +to this property.

    +complete_bit_prefixes.png +

    This test takes into account IP addresses that are not globally routable, i.e. +reserved for local networks, multicast and other things. It also takes into +account that some /8 blocks are not in use by end-users and extremely unlikely +to ever run a DHT node. This makes the results likely to be very similar to +what we would see in the wild.

    +

    These results indicate that CRC32C provides the best uniformity in the results +in terms of bit prefixes where all possibilities are represented, and that +no more than 21 bits should be used from the result. If more than 21 bits +were to be used, there would be certain node IDs that would be impossible to +have, which would make routing sub-optimal.

    +

    The second test is more of a sanity test for the uniform distribution property. +The target space (32 bit integer) is divided up into 1000 buckets. Every valid +IP and r input is run through the algorithm and the result is put in the +bucket it falls in. The expectation is that each bucket has roughly an equal +number of results falling into it. The following graph shows the resulting +histogram, comparing SHA-1 and CRC32C.

    +hash_distribution.png +

    The source code for these tests can be found here.

    +

    The reason to use CRC32C instead of the CRC32 implemented by zlib is that +Intel CPUs have hardware support for the CRC32C calculations. The input +being exactly 4 bytes is also deliberate, to make it fit in a single +instruction.

    +
    +
    +

    enforcement

    +

    Once enforced, write tokens from peers whose node ID does not match its external +IP should be considered dropped. In other words, a peer that uses a non-matching +ID MUST never be used to store information on, regardless of which request. In the +original DHT specification only announce_peer stores data in the network, +but any future extension which stores data in the network SHOULD use the same +restriction.

    +

    Any peer on a local network address is exempt from this node ID verification. +This includes the following IP blocks:

    +
    +
    10.0.0.0/8
    +
    reserved for local networks
    +
    172.16.0.0/12
    +
    reserved for local networks
    +
    192.168.0.0/16
    +
    reserved for local networks
    +
    169.254.0.0/16
    +
    reserved for self-assigned IPs
    +
    127.0.0.0/8
    +
    reserved for loopback
    +
    +
    +
    +

    backwards compatibility and transition

    +

    During some transition period, this restriction should not be enforced, and +peers whose node ID does not match this formula relative to their external IP +should not be blocked.

    +

    Requests from peers whose node ID does not match their external IP should +always be serviced, even after the transition period. The attack this protects +from is storing data on an attacker's node, not servicing an attackers request.

    +
    +
    +

    forward compatibility

    +

    If the total size of the DHT grows to the point where the inherent size limit +in this proposal is too small, the modulus constants can be updated in a new +proposal, and another transition period where both sets of modulus constants +are accepted.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/dht_sec.rst b/docs/dht_sec.rst new file mode 100644 index 0000000..ca47be9 --- /dev/null +++ b/docs/dht_sec.rst @@ -0,0 +1,251 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +BitTorrent DHT security extension +--------------------------------- + +The purpose of this extension is to make it harder to launch a few +specific attacks against the BitTorrent DHT and also to make it harder +to snoop the network. + +Specifically the attack this extension intends to make harder is launching +8 or more DHT nodes which node-IDs selected close to a specific target +info-hash, in order to become the main nodes hosting peers for it. Currently +this is very easy to do and lets the attacker not only see all the traffic +related to this specific info-hash but also block access to it by other +peers. + +The proposed guard against this is to enforce restrictions on which node-ID +a node can choose, based on its external IP address. + +considerations +-------------- + +One straight forward scheme to tie the node ID to an IP would be to hash +the IP and force the node ID to share the prefix of that hash. One main +draw back of this approach is that an entities control over the DHT key +space grows linearly with its control over the IP address space. + +In order to successfully launch an attack, you just need to find 8 IPs +whose hash will be *closest* to the target info-hash. Given the current +size of the DHT, that is quite likely to be possible by anyone in control +of a /8 IP block. + +The size of the DHT is approximately 8.4 million nodes. This is estimated +by observing that a typical routing table typically has about 20 of its +top routing table buckets full. That means the key space is dense enough +to contain 8 nodes for every combination of the 20 top bits of node IDs. + + ``2^20 * 8 = 8388608`` + +By controlling that many IP addresses, an attacker could snoop any info-hash. +By controlling 8 times that many IP addresses, an attacker could actually +take over any info-hash. + +With IPv4, snooping would require a /8 IP block, giving access to 16.7 million +IPs. + +Another problem with hashing the IP is that multiple users behind a NAT are +forced to run their DHT nodes on the same node ID. + +Node ID restriction +------------------- + +In order to avoid the number node IDs controlled to grow linearly by the number +of IPs, as well as allowing more than one node ID per external IP, the node +ID can be restricted at each class level of the IP. + +Another important property of the restriction put on node IDs is that the +distribution of the IDs remain uniform. This is why CRC32C (Castagnoli) was +chosen as the hash function. + +The expression to calculate a valid ID prefix (from an IPv4 address) is:: + + crc32c((ip & 0x030f3fff) | (r << 29)) + +And for an IPv6 address (``ip`` is the high 64 bits of the address):: + + crc32c((ip & 0x0103070f1f3f7fff) | (r << 61)) + +``r`` is a random number in the range [0, 7]. The resulting integer, +representing the masked IP address is supposed to be big-endian before +hashed. The "|" operator means bit-wise OR. + +The details of implementing this is to evaluate the expression, store the +result in a big-endian 64 bit integer and hash those 8 bytes with CRC32C. + +The first (most significant) 21 bits of the node ID used in the DHT MUST +match the first 21 bits of the resulting hash. The last byte of the hash MUST +match the random number (``r``) used to generate the hash. + +.. image:: ip_id_v4.png +.. image:: ip_id_v6.png + +Example code code for calculating a valid node ID:: + + uint8_t* ip; // our external IPv4 or IPv6 address (network byte order) + int num_octets; // the number of octets to consider in ip (4 or 8) + uint8_t node_id[20]; // resulting node ID + + uint8_t v4_mask[] = { 0x03, 0x0f, 0x3f, 0xff }; + uint8_t v6_mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + uint8_t* mask = num_octets == 4 ? v4_mask : v6_mask; + + for (int i = 0; i < num_octets; ++i) + ip[i] &= mask[i]; + + uint32_t rand = std::rand() & 0xff; + uint8_t r = rand & 0x7; + ip[0] |= r << 5; + + uint32_t crc = 0; + crc = crc32c(crc, ip, num_octets); + + // only take the top 21 bits from crc + node_id[0] = (crc >> 24) & 0xff; + node_id[1] = (crc >> 16) & 0xff; + node_id[2] = ((crc >> 8) & 0xf8) | (std::rand() & 0x7); + for (int i = 3; i < 19; ++i) node_id[i] = std::rand(); + node_id[19] = rand; + +test vectors: + +.. parsed-literal:: + + IP rand example node ID + ============ ===== ========================================== + 124.31.75.21 1 **5fbfbf** f10c5d6a4ec8a88e4c6ab4c28b95eee4 **01** + 21.75.31.124 86 **5a3ce9** c14e7a08645677bbd1cfe7d8f956d532 **56** + 65.23.51.170 22 **a5d432** 20bc8f112a3d426c84764f8c2a1150e6 **16** + 84.124.73.14 65 **1b0321** dd1bb1fe518101ceef99462b947a01ff **41** + 43.213.53.83 90 **e56f6c** bf5b7c4be0237986d5243b87aa6d5130 **5a** + +The bold parts of the node ID are the important parts. The rest are +random numbers. The last bold number of each row has only its most significant +bit pulled from the CRC32C function. The lower 3 bits are random. + +bootstrapping +------------- + +In order to set ones initial node ID, the external IP needs to be known. This +is not a trivial problem. With this extension, *all* DHT responses SHOULD include +a *top-level* field called ``ip``, containing a compact binary representation of +the requester's IP and port. That is big-endian IP followed by 2 bytes of big-endian +port. + +The IP portion is the same byte sequence used to verify the node ID. + +It is important that the ``ip`` field is in the top level dictionary. Nodes that +enforce the node-ID will respond with an error message ("y": "e", "e": { ... }), +whereas a node that supports this extension but without enforcing it will respond +with a normal reply ("y": "r", "r": { ... }). + +A DHT node which receives an ``ip`` result in a request SHOULD consider restarting +its DHT node with a new node ID, taking this IP into account. Since a single node +can not be trusted, there should be some mechanism to determine whether or +not the node has a correct understanding of its external IP or not. This could +be done by voting, or only restart the DHT once at least a certain number of +nodes, from separate searches, tells you your node ID is incorrect. + +rationale +--------- + +The choice of using CRC32C instead of a more traditional cryptographic hash +function is justified primarily of these reasons: + +1. it is a fast function +2. produces well distributed results +3. there is no need for the hash function to be one-way (the input set is + so small that any hash function could be reversed). +4. CRC32C (Castagnoli) is supported in hardware by SSE 4.2, which can + significantly speed up computation + +There are primarily two tests run on SHA-1 and CRC32C to establish the +distribution of results. The first one is the number of bits in the output +set that contain every possible combination of bits. The CRC32C function +has a longer such prefix in its output than SHA-1. This means nodes will still +have well uniformly distributed IDs, even when IP addresses in use are not +uniformly distributed. + +The following graph illustrate a few different hash functions with regard +to this property. + +.. image:: complete_bit_prefixes.png + +This test takes into account IP addresses that are not globally routable, i.e. +reserved for local networks, multicast and other things. It also takes into +account that some /8 blocks are not in use by end-users and extremely unlikely +to ever run a DHT node. This makes the results likely to be very similar to +what we would see in the wild. + +These results indicate that CRC32C provides the best uniformity in the results +in terms of bit prefixes where all possibilities are represented, and that +no more than 21 bits should be used from the result. If more than 21 bits +were to be used, there would be certain node IDs that would be impossible to +have, which would make routing sub-optimal. + +The second test is more of a sanity test for the uniform distribution property. +The target space (32 bit integer) is divided up into 1000 buckets. Every valid +IP and ``r`` input is run through the algorithm and the result is put in the +bucket it falls in. The expectation is that each bucket has roughly an equal +number of results falling into it. The following graph shows the resulting +histogram, comparing SHA-1 and CRC32C. + +.. image:: hash_distribution.png + +The source code for these tests can be found here_. + +.. _here: https://github.com/arvidn/hash_complete_prefix + +The reason to use CRC32C instead of the CRC32 implemented by zlib is that +Intel CPUs have hardware support for the CRC32C calculations. The input +being exactly 4 bytes is also deliberate, to make it fit in a single +instruction. + +enforcement +----------- + +Once enforced, write tokens from peers whose node ID does not match its external +IP should be considered dropped. In other words, a peer that uses a non-matching +ID MUST never be used to store information on, regardless of which request. In the +original DHT specification only ``announce_peer`` stores data in the network, +but any future extension which stores data in the network SHOULD use the same +restriction. + +Any peer on a local network address is exempt from this node ID verification. +This includes the following IP blocks: + +10.0.0.0/8 + reserved for local networks +172.16.0.0/12 + reserved for local networks +192.168.0.0/16 + reserved for local networks +169.254.0.0/16 + reserved for self-assigned IPs +127.0.0.0/8 + reserved for loopback + + +backwards compatibility and transition +-------------------------------------- + +During some transition period, this restriction should not be enforced, and +peers whose node ID does not match this formula relative to their external IP +should not be blocked. + +Requests from peers whose node ID does not match their external IP should +always be serviced, even after the transition period. The attack this protects +from is storing data on an attacker's node, not servicing an attackers request. + +forward compatibility +--------------------- + +If the total size of the DHT grows to the point where the inherent size limit +in this proposal is too small, the modulus constants can be updated in a new +proposal, and another transition period where both sets of modulus constants +are accepted. + diff --git a/docs/dht_store.html b/docs/dht_store.html new file mode 100644 index 0000000..8527cd8 --- /dev/null +++ b/docs/dht_store.html @@ -0,0 +1,508 @@ + + + + + + + +BitTorrent extension for arbitrary DHT store + + + + + +
    +
    + + libtorrent logo + +
    +

    BitTorrent extension for arbitrary DHT store

    + +++ + + + +
    Version:2.0.0
    + +

    This is a proposal for an extension to the BitTorrent DHT to allow +storing and retrieving of arbitrary data.

    +

    It supports both storing immutable items, where the key is +the SHA-1 hash of the data itself, and mutable items, where +the key is the public key of the key pair used to sign the data.

    +

    There are two new proposed messages, put and get.

    +
    +

    terminology

    +

    In this document, a storage node refers to the node in the DHT to which +an item is being announced and stored on. A requesting node refers to +a node which makes look-ups in the DHT to find the storage nodes, to +request items from them, and possibly re-announce those items to keep them +alive.

    +
    +
    +

    messages

    +

    The proposed new messages get and put are similar to the existing +get_peers and announce_peer.

    +

    Responses to get should always include nodes and nodes6. Those +fields have the same semantics as in its get_peers response. It should also +include a write token, token, with the same semantics as int get_peers. +The write token MAY be tied specifically to the key which get requested. +i.e. the token can only be used to store values under that one key. It may +also be tied to the node ID and IP address of the requesting node.

    +

    The id field in these messages has the same semantics as the standard DHT +messages, i.e. the node ID of the node sending the message, to maintain the +structure of the DHT network.

    +

    The token field also has the same semantics as the standard DHT message +get_peers and announce_peer, when requesting an item and to write an +item respectively.

    +

    The k field is the 32 byte ed25519 public key, which the signature can be +authenticated with. When looking up a mutable item, the target field MUST be +the SHA-1 hash of this key concatenated with the salt, if present.

    +

    The distinction between storing mutable and immutable items is the inclusion of +a public key, a sequence number, signature and an optional salt (k, seq, +sig and salt).

    +

    get requests for mutable items and immutable items cannot be distinguished +from each other. An implementation can either store mutable and immutable items +in the same hash table internally, or in separate ones and potentially do two +lookups for get requests.

    +

    The v field is the value to be stored. It is allowed to be any bencoded +type (list, dict, string or integer). When it's being hashed (for verifying its +signature or to calculate its key), its flattened, bencoded, form is used. It is +important to use the verbatim bencoded representation as it appeared in the +message. decoding and then re-encoding bencoded structures is not necessarily an +identity operation.

    +

    Storing nodes MAY reject put requests where the bencoded form of v is +longer than 1000 bytes. In other words, it's not safe to assume storing more +than 1000 bytes will succeed.

    +
    +
    +

    immutable items

    +

    Immutable items are stored under their SHA-1 hash, and since they cannot be +modified, there is no need to authenticate the origin of them. This makes +immutable items simple.

    +

    A node making a lookup SHOULD verify the data it receives from the network, to +verify that its hash matches the target that was looked up.

    +
    +

    put message

    +

    Request:

    +
    +{
    +        "a":
    +        {
    +                "id": <20 byte id of sending node (string)>,
    +                "v": <any bencoded type, whose encoded size <= 1000>
    +        },
    +        "t": <transaction-id (string)>,
    +        "y": "q",
    +        "q": "put"
    +}
    +
    +

    Response:

    +
    +{
    +        "r": { "id": <20 byte id of sending node (string)> },
    +        "t": <transaction-id (string)>,
    +        "y": "r",
    +}
    +
    +
    +
    +

    get message

    +

    Request:

    +
    +{
    +        "a":
    +        {
    +                "id": <20 byte id of sending node (string)>,
    +                "target": <SHA-1 hash of item (string)>,
    +        },
    +        "t": <transaction-id (string)>,
    +        "y": "q",
    +        "q": "get"
    +}
    +
    +

    Response:

    +
    +{
    +        "r":
    +        {
    +                "id": <20 byte id of sending node (string)>,
    +                "token": <write token (string)>,
    +                "v": <any bencoded type whose SHA-1 hash matches 'target'>,
    +                "nodes": <IPv4 nodes close to 'target'>,
    +                "nodes6": <IPv6 nodes close to 'target'>
    +        },
    +        "t": <transaction-id>,
    +        "y": "r",
    +}
    +
    +
    +
    +
    +

    mutable items

    +

    Mutable items can be updated, without changing their DHT keys. To authenticate +that only the original publisher can update an item, it is signed by a private +key generated by the original publisher. The target ID mutable items are stored +under is the SHA-1 hash of the public key (as it appears in the put +message).

    +

    In order to avoid a malicious node to overwrite the list head with an old +version, the sequence number seq must be monotonically increasing for each +update, and a node hosting the list node MUST not downgrade a list head from a +higher sequence number to a lower one, only upgrade. The sequence number SHOULD +not exceed MAX_INT64, (i.e. 0x7fffffffffffffff. A client MAY reject any +message with a sequence number exceeding this. A client MAY also reject any +message with a negative sequence number.

    +

    The signature is a 64 byte ed25519 signature of the bencoded sequence number +concatenated with the v key. e.g. something like this:

    +
    +3:seqi4e1:v12:Hello world!
    +
    +

    If the salt key is present and non-empty, the salt string must be included +in what's signed. Note that if salt is specified and an empty string, it is +as if it was not specified and nothing in addition to the sequence number and +the data is signed. The salt string may not be longer than 64 bytes.

    +

    When a salt is included in what is signed, the key salt with the value of +the key is prepended in its bencoded form. For example, if salt is "foobar", +the buffer to be signed is:

    +
    +4:salt6:foobar3:seqi4e1:v12:Hello world!
    +
    +
    +

    put message

    +

    Request:

    +
    +{
    +        "a":
    +        {
    +                "cas": <optional expected seq-nr (int)>,
    +                "id": <20 byte id of sending node (string)>,
    +                "k": <ed25519 public key (32 bytes string)>,
    +                "salt": <optional salt to be appended to "k" when hashing (string)>
    +                "seq": <monotonically increasing sequence number (integer)>,
    +                "sig": <ed25519 signature (64 bytes string)>,
    +                "token": <write-token (string)>,
    +                "v": <any bencoded type, whose encoded size < 1000>
    +        },
    +        "t": <transaction-id (string)>,
    +        "y": "q",
    +        "q": "put"
    +}
    +
    +

    Storing nodes receiving a put request where seq is lower than or equal +to what's already stored on the node, MUST reject the request. If the sequence +number is equal, and the value is also the same, the node SHOULD reset its +timeout counter.

    +

    If the sequence number in the put message is lower than the sequence number +associated with the currently stored value, the storing node MAY return an error +message with code 302 (see error codes below).

    +

    Note that this request does not contain a target hash. The target hash under +which this blob is stored is implied by the k argument. The key is the SHA-1 +hash of the key (k).

    +

    In order to support a single key being used to store separate items in the DHT, +an optional salt can be specified in the put request of mutable items. +If the salt entry is not present, it can be assumed to be an empty string, and +its semantics should be identical as specifying a salt key with an empty string. +The salt can be any binary string (but probably most conveniently a hash of +something). This string is appended to the key, as specified in the k field, +when calculating the key to store the blob under (i.e. the key get requests +specify to retrieve this data).

    +

    This lets a single entity, with a single key, publish any number of unrelated +items, with a single key that readers can verify. This is useful if the +publisher doesn't know ahead of time how many different items are to be +published. It can distribute a single public key for users to authenticate the +published blobs.

    +

    Note that the salt is not returned in the response to a get request. This +is intentional. When issuing a get request for an item is expected to +know what the salt is (because it is part of what the target ID that is being +looked up is derived from). There is no need to repeat it back for bystanders +to see.

    +
    +
    +

    CAS

    +

    CAS is short for compare and swap, it has similar semantics as CAS CPU +instructions. It is used to avoid race conditions when multiple nodes are +writing to the same slot in the DHT.

    +

    The cas field is optional. If present it specifies the sequence number of +the data blob being overwritten by the put. When present, the storing node +MUST compare this number to the current sequence number it has stored under +this key. Only if the cas matches the stored sequence number is the put +performed. If it mismatches, the store fails and an error is returned. +See errors below.

    +

    The cas field only applies to mutable puts. If there is no current +value, the cas field SHOULD be ignored.

    +

    When sending a put request to a node that did not return any data for the +get, the cas field SHOULD NOT be included.

    +
    +
    +

    response

    +

    Response:

    +
    +{
    +        "r": { "id": <20 byte id of sending node (string)> },
    +        "t": <transaction-id (string)>,
    +        "y": "r",
    +}
    +
    +
    +
    +

    errors

    +

    If the store fails for any reason an error message is returned instead of the +message template above, i.e. one where "y" is "e" and "e" is a tuple of +[error-code, message]). Failures include cas mismatches and the sequence +number is outdated.

    +

    The error message (as specified by BEP5) looks like this:

    +
    +{
    +        "e": [ <error-code (integer)>, <error-string (string)> ],
    +        "t": <transaction-id (string)>,
    +        "y": "e",
    +}
    +
    +

    In addition to the error codes defined in BEP5, this specification defines +some additional error codes.

    + ++++ + + + + + + + + + + + + + + + + + + + + + + +
    error-codedescription
    205message (v field) +too big.
    206invalid signature
    207salt (salt field) +too big.
    301the CAS hash mismatched, +re-read value and try +again.
    302sequence number less than +current.
    +

    An implementation MUST emit 301 errors if the cas mismatches. This is a +critical feature in synchronization of multiple agents sharing an immutable +item.

    +
    +
    +

    get message

    +

    Request:

    +
    +{
    +        "a":
    +        {
    +                "id": <20 byte id of sending node (string)>,
    +                "target:" <20 byte SHA-1 hash of public key and salt (string)>
    +        },
    +        "t": <transaction-id (string)>,
    +        "y": "q",
    +        "q": "get"
    +}
    +
    +

    Response:

    +
    +{
    +        "r":
    +        {
    +                "id": <20 byte id of sending node (string)>,
    +                "k": <ed25519 public key (32 bytes string)>,
    +                "nodes": <IPv4 nodes close to 'target'>,
    +                "nodes6": <IPv6 nodes close to 'target'>,
    +                "seq": <monotonically increasing sequence number (integer)>,
    +                "sig": <ed25519 signature (64 bytes string)>,
    +                "token": <write-token (string)>,
    +                "v": <any bencoded type, whose encoded size <= 1000>
    +        },
    +        "t": <transaction-id (string)>,
    +        "y": "r",
    +}
    +
    +
    +
    +
    +

    signature verification

    +

    In order to make it maximally difficult to attack the bencoding parser, signing +and verification of the value and sequence number should be done as follows:

    +
      +
    1. encode value and sequence number separately
    2. +
    3. concatenate ("4:salt" length-of-salt ":" salt) "3:seqi" seq +"e1:v" len ":" and the encoded value. +sequence number 1 of value "Hello World!" would be converted to: +"3:seqi1e1:v12:Hello World!". In this way it is not possible to convince a +node that part of the length is actually part of the sequence number even if +the parser contains certain bugs. Furthermore it is not possible to have a +verification failure if a bencoding serializer alters the order of entries in +the dictionary. The salt is in parenthesis because it is optional. It is only +prepended if a non-empty salt is specified in the put request.
    4. +
    5. sign or verify the concatenated string
    6. +
    +

    On the storage node, the signature MUST be verified before accepting the store +command. The data MUST be stored under the SHA-1 hash of the public key (as it +appears in the bencoded dict) and the salt (if present).

    +

    On the requesting nodes, the key they get back from a get request MUST be +verified to hash to the target ID the lookup was made for, as well as verifying +the signature. If any of these fail, the response SHOULD be considered invalid.

    +
    +
    +

    expiration

    +

    Without re-announcement, these items MAY expire in 2 hours. In order +to keep items alive, they SHOULD be re-announced once an hour.

    +

    Any node that's interested in keeping a blob in the DHT alive may announce it. +It would simply repeat the signature for a mutable put without having the +private key.

    +
    +
    +

    test vectors

    +
    +

    test 1 (mutable)

    +

    value:

    +
    +12:Hello World!
    +
    +

    buffer being signed:

    +
    +3:seqi1e1:v12:Hello World!
    +
    +

    public key:

    +
    +77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
    +
    +

    private key:

    +
    +e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
    +b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
    +
    +

    target ID:

    +
    +4a533d47ec9c7d95b1ad75f576cffc641853b750
    +
    +

    signature:

    +
    +305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff
    +1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01
    +
    +
    +
    +

    test 2 (mutable with salt)

    +

    value:

    +
    +12:Hello World!
    +
    +

    salt:

    +
    +foobar
    +
    +

    buffer being signed:

    +
    +4:salt6:foobar3:seqi1e1:v12:Hello World!
    +
    +

    public key:

    +
    +77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548
    +
    +

    private key:

    +
    +e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d
    +b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d
    +
    +

    target ID:

    +
    +411eba73b6f087ca51a3795d9c8c938d365e32c1
    +
    +

    signature:

    +
    +6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d
    +df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08
    +
    +
    +
    +

    test 3 (immutable)

    +

    value:

    +
    +12:Hello World!
    +
    +

    target ID:

    +
    +e5f96f6f38320f0f33959cb4d3d656452117aadb
    +
    +
    +
    +
    +

    resources

    +

    Libraries that implement ed25519 DSA:

    + +
    + +
    +
    +
    + + +
    + + diff --git a/docs/dht_store.rst b/docs/dht_store.rst new file mode 100644 index 0000000..195c4ca --- /dev/null +++ b/docs/dht_store.rst @@ -0,0 +1,480 @@ +============================================ +BitTorrent extension for arbitrary DHT store +============================================ + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +This is a proposal for an extension to the BitTorrent DHT to allow +storing and retrieving of arbitrary data. + +It supports both storing *immutable* items, where the key is +the SHA-1 hash of the data itself, and *mutable* items, where +the key is the public key of the key pair used to sign the data. + +There are two new proposed messages, ``put`` and ``get``. + +terminology +----------- + +In this document, a *storage node* refers to the node in the DHT to which +an item is being announced and stored on. A *requesting node* refers to +a node which makes look-ups in the DHT to find the storage nodes, to +request items from them, and possibly re-announce those items to keep them +alive. + +messages +-------- + +The proposed new messages ``get`` and ``put`` are similar to the existing +``get_peers`` and ``announce_peer``. + +Responses to ``get`` should always include ``nodes`` and ``nodes6``. Those +fields have the same semantics as in its ``get_peers`` response. It should also +include a write token, ``token``, with the same semantics as int ``get_peers``. +The write token MAY be tied specifically to the key which ``get`` requested. +i.e. the ``token`` can only be used to store values under that one key. It may +also be tied to the node ID and IP address of the requesting node. + +The ``id`` field in these messages has the same semantics as the standard DHT +messages, i.e. the node ID of the node sending the message, to maintain the +structure of the DHT network. + +The ``token`` field also has the same semantics as the standard DHT message +``get_peers`` and ``announce_peer``, when requesting an item and to write an +item respectively. + +The ``k`` field is the 32 byte ed25519 public key, which the signature can be +authenticated with. When looking up a mutable item, the ``target`` field MUST be +the SHA-1 hash of this key concatenated with the ``salt``, if present. + +The distinction between storing mutable and immutable items is the inclusion of +a public key, a sequence number, signature and an optional salt (``k``, ``seq``, +``sig`` and ``salt``). + +``get`` requests for mutable items and immutable items cannot be distinguished +from each other. An implementation can either store mutable and immutable items +in the same hash table internally, or in separate ones and potentially do two +lookups for ``get`` requests. + +The ``v`` field is the *value* to be stored. It is allowed to be any bencoded +type (list, dict, string or integer). When it's being hashed (for verifying its +signature or to calculate its key), its flattened, bencoded, form is used. It is +important to use the verbatim bencoded representation as it appeared in the +message. decoding and then re-encoding bencoded structures is not necessarily an +identity operation. + +Storing nodes MAY reject ``put`` requests where the bencoded form of ``v`` is +longer than 1000 bytes. In other words, it's not safe to assume storing more +than 1000 bytes will succeed. + +immutable items +--------------- + +Immutable items are stored under their SHA-1 hash, and since they cannot be +modified, there is no need to authenticate the origin of them. This makes +immutable items simple. + +A node making a lookup SHOULD verify the data it receives from the network, to +verify that its hash matches the target that was looked up. + +put message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "v": ** + }, + "t": **, + "y": "q", + "q": "put" + } + +Response: + +.. parsed-literal:: + + { + "r": { "id": *<20 byte id of sending node (string)>* }, + "t": **, + "y": "r", + } + +get message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "target": **, + }, + "t": **, + "y": "q", + "q": "get" + } + +Response: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "token": **, + "v": **, + "nodes": **, + "nodes6": ** + }, + "t": **, + "y": "r", + } + + +mutable items +------------- + +Mutable items can be updated, without changing their DHT keys. To authenticate +that only the original publisher can update an item, it is signed by a private +key generated by the original publisher. The target ID mutable items are stored +under is the SHA-1 hash of the public key (as it appears in the ``put`` +message). + +In order to avoid a malicious node to overwrite the list head with an old +version, the sequence number ``seq`` must be monotonically increasing for each +update, and a node hosting the list node MUST not downgrade a list head from a +higher sequence number to a lower one, only upgrade. The sequence number SHOULD +not exceed ``MAX_INT64``, (i.e. ``0x7fffffffffffffff``. A client MAY reject any +message with a sequence number exceeding this. A client MAY also reject any +message with a negative sequence number. + +The signature is a 64 byte ed25519 signature of the bencoded sequence number +concatenated with the ``v`` key. e.g. something like this:: + + 3:seqi4e1:v12:Hello world! + +If the ``salt`` key is present and non-empty, the salt string must be included +in what's signed. Note that if ``salt`` is specified and an empty string, it is +as if it was not specified and nothing in addition to the sequence number and +the data is signed. The salt string may not be longer than 64 bytes. + +When a salt is included in what is signed, the key ``salt`` with the value of +the key is prepended in its bencoded form. For example, if ``salt`` is "foobar", +the buffer to be signed is:: + + 4:salt6:foobar3:seqi4e1:v12:Hello world! + +put message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "cas": **, + "id": *<20 byte id of sending node (string)>*, + "k": **, + "salt": ** + "seq": **, + "sig": **, + "token": **, + "v": ** + }, + "t": **, + "y": "q", + "q": "put" + } + +Storing nodes receiving a ``put`` request where ``seq`` is lower than or equal +to what's already stored on the node, MUST reject the request. If the sequence +number is equal, and the value is also the same, the node SHOULD reset its +timeout counter. + +If the sequence number in the ``put`` message is lower than the sequence number +associated with the currently stored value, the storing node MAY return an error +message with code 302 (see error codes below). + +Note that this request does not contain a target hash. The target hash under +which this blob is stored is implied by the ``k`` argument. The key is the SHA-1 +hash of the key (``k``). + +In order to support a single key being used to store separate items in the DHT, +an optional ``salt`` can be specified in the ``put`` request of mutable items. +If the salt entry is not present, it can be assumed to be an empty string, and +its semantics should be identical as specifying a salt key with an empty string. +The salt can be any binary string (but probably most conveniently a hash of +something). This string is appended to the key, as specified in the ``k`` field, +when calculating the key to store the blob under (i.e. the key ``get`` requests +specify to retrieve this data). + +This lets a single entity, with a single key, publish any number of unrelated +items, with a single key that readers can verify. This is useful if the +publisher doesn't know ahead of time how many different items are to be +published. It can distribute a single public key for users to authenticate the +published blobs. + +Note that the salt is not returned in the response to a ``get`` request. This +is intentional. When issuing a ``get`` request for an item is expected to +know what the salt is (because it is part of what the target ID that is being +looked up is derived from). There is no need to repeat it back for bystanders +to see. + +CAS +... + +CAS is short for *compare and swap*, it has similar semantics as CAS CPU +instructions. It is used to avoid race conditions when multiple nodes are +writing to the same slot in the DHT. + +The ``cas`` field is optional. If present it specifies the sequence number of +the data blob being overwritten by the put. When present, the storing node +MUST compare this number to the current sequence number it has stored under +this key. Only if the ``cas`` matches the stored sequence number is the put +performed. If it mismatches, the store fails and an error is returned. +See errors_ below. + +The ``cas`` field only applies to mutable puts. If there is no current +value, the ``cas`` field SHOULD be ignored. + +When sending a ``put`` request to a node that did not return any data for the +``get``, the ``cas`` field SHOULD NOT be included. + +response +........ + +Response: + +.. parsed-literal:: + + { + "r": { "id": *<20 byte id of sending node (string)>* }, + "t": **, + "y": "r", + } + +errors +...... + +If the store fails for any reason an error message is returned instead of the +message template above, i.e. one where "y" is "e" and "e" is a tuple of +[error-code, message]). Failures include ``cas`` mismatches and the sequence +number is outdated. + +The error message (as specified by BEP5_) looks like this: + +.. _BEP5: https://www.bittorrent.org/beps/bep_0005.html + +.. parsed-literal:: + + { + "e": [ **, ** ], + "t": **, + "y": "e", + } + +In addition to the error codes defined in BEP5_, this specification defines +some additional error codes. + ++------------+-----------------------------+ +| error-code | description | ++============+=============================+ +| 205 | message (``v`` field) | +| | too big. | ++------------+-----------------------------+ +| 206 | invalid signature | ++------------+-----------------------------+ +| 207 | salt (``salt`` field) | +| | too big. | ++------------+-----------------------------+ +| 301 | the CAS hash mismatched, | +| | re-read value and try | +| | again. | ++------------+-----------------------------+ +| 302 | sequence number less than | +| | current. | ++------------+-----------------------------+ + +An implementation MUST emit 301 errors if the cas mismatches. This is a +critical feature in synchronization of multiple agents sharing an immutable +item. + +get message +........... + +Request: + +.. parsed-literal:: + + { + "a": + { + "id": *<20 byte id of sending node (string)>*, + "target:" *<20 byte SHA-1 hash of public key and salt (string)>* + }, + "t": **, + "y": "q", + "q": "get" + } + +Response: + +.. parsed-literal:: + + { + "r": + { + "id": *<20 byte id of sending node (string)>*, + "k": **, + "nodes": **, + "nodes6": **, + "seq": **, + "sig": **, + "token": **, + "v": ** + }, + "t": **, + "y": "r", + } + +signature verification +---------------------- + +In order to make it maximally difficult to attack the bencoding parser, signing +and verification of the value and sequence number should be done as follows: + +1. encode value and sequence number separately +2. concatenate ("4:salt" *length-of-salt* ":" *salt*) "3:seqi" *seq* + "e1:v" *len* ":" and the encoded value. + sequence number 1 of value "Hello World!" would be converted to: + "3:seqi1e1:v12:Hello World!". In this way it is not possible to convince a + node that part of the length is actually part of the sequence number even if + the parser contains certain bugs. Furthermore it is not possible to have a + verification failure if a bencoding serializer alters the order of entries in + the dictionary. The salt is in parenthesis because it is optional. It is only + prepended if a non-empty salt is specified in the ``put`` request. +3. sign or verify the concatenated string + +On the storage node, the signature MUST be verified before accepting the store +command. The data MUST be stored under the SHA-1 hash of the public key (as it +appears in the bencoded dict) and the salt (if present). + +On the requesting nodes, the key they get back from a ``get`` request MUST be +verified to hash to the target ID the lookup was made for, as well as verifying +the signature. If any of these fail, the response SHOULD be considered invalid. + +expiration +---------- + +Without re-announcement, these items MAY expire in 2 hours. In order +to keep items alive, they SHOULD be re-announced once an hour. + +Any node that's interested in keeping a blob in the DHT alive may announce it. +It would simply repeat the signature for a mutable put without having the +private key. + +test vectors +------------ + +test 1 (mutable) +................ + +value:: + + 12:Hello World! + +buffer being signed:: + + 3:seqi1e1:v12:Hello World! + +public key:: + + 77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548 + +private key:: + + e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d + b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d + +**target ID**:: + + 4a533d47ec9c7d95b1ad75f576cffc641853b750 + +**signature**:: + + 305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff + 1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01 + +test 2 (mutable with salt) +.......................... + +value:: + + 12:Hello World! + +salt:: + + foobar + +buffer being signed:: + + 4:salt6:foobar3:seqi1e1:v12:Hello World! + +public key:: + + 77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548 + +private key:: + + e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d + b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d + +**target ID**:: + + 411eba73b6f087ca51a3795d9c8c938d365e32c1 + +**signature**:: + + 6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d + df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08 + +test 3 (immutable) +.................. + +value:: + + 12:Hello World! + +**target ID**:: + + e5f96f6f38320f0f33959cb4d3d656452117aadb + +resources +--------- + +Libraries that implement ed25519 DSA: + +* NaCl_ +* libsodium_ +* `nightcracker's ed25519`_ + +.. _NaCl: https://nacl.cr.yp.to/ +.. _libsodium: https://github.com/jedisct1/libsodium +.. _`nightcracker's ed25519`: https://github.com/nightcracker/ed25519 + diff --git a/docs/disk_cache.diagram b/docs/disk_cache.diagram new file mode 100644 index 0000000..da666fb --- /dev/null +++ b/docs/disk_cache.diagram @@ -0,0 +1,12 @@ + <---- "recently" "frequently" ---> + "used" "used" + + L1 ghost L1 L2 L2 ghost + AAAAAAAAFFFFFFFF FFFFFFFFAAAAAAAA + + cache size + <---------------> + + 2x cache size + <-------------------------------> + diff --git a/docs/disk_cache.png b/docs/disk_cache.png new file mode 100644 index 0000000000000000000000000000000000000000..6f865cc79a58cbe69c765606bfc6e57bb840e6a2 GIT binary patch literal 3689 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 literal 0 HcmV?d00001 diff --git a/docs/examples.html b/docs/examples.html new file mode 100644 index 0000000..5bd0c2d --- /dev/null +++ b/docs/examples.html @@ -0,0 +1,564 @@ + + + + + + + +examples.rst + + + + + +
    +
    + + libtorrent logo + +
    + + +++ + + + +
    Version:2.0.0
    +
    +

    Table of contents

    + +
    +
    +

    examples

    +

    Except for the example programs in this manual, there's also a bigger example +of a (little bit) more complete client, client_test. There are separate +instructions for how to use it here if you'd like to try it.

    +
    +

    simple client

    +

    This is a simple client. It doesn't have much output to keep it simple:

    +
    +#include <cstdlib>
    +#include "libtorrent/entry.hpp"
    +#include "libtorrent/bencode.hpp"
    +#include "libtorrent/session.hpp"
    +#include "libtorrent/torrent_info.hpp"
    +
    +#include <iostream>
    +
    +int main(int argc, char* argv[]) try
    +{
    +  if (argc != 2) {
    +    std::cerr << "usage: ./simple_client torrent-file\n"
    +      "to stop the client, press return.\n";
    +    return 1;
    +  }
    +
    +  lt::session s;
    +  lt::add_torrent_params p;
    +  p.save_path = "./";
    +  p.ti = std::make_shared<lt::torrent_info>(argv[1]);
    +  s.add_torrent(p);
    +
    +  // wait for the user to end
    +  char a;
    +  int ret = std::scanf("%c\n", &a);
    +  (void)ret; // ignore
    +  return 0;
    +}
    +catch (std::exception const& e) {
    +  std::cerr << "ERROR: " << e.what() << "\n";
    +}
    +
    +
    +
    +

    make_torrent

    +

    Shows how to create a torrent from a directory tree:

    +
    +#include "libtorrent/entry.hpp"
    +#include "libtorrent/bencode.hpp"
    +#include "libtorrent/torrent_info.hpp"
    +#include "libtorrent/create_torrent.hpp"
    +
    +#include <functional>
    +#include <cstdio>
    +#include <sstream>
    +#include <fstream>
    +#include <iostream>
    +
    +#ifdef TORRENT_WINDOWS
    +#include <direct.h> // for _getcwd
    +#endif
    +
    +using namespace std::placeholders;
    +
    +std::vector<char> load_file(std::string const& filename)
    +{
    +  std::fstream in;
    +  in.exceptions(std::ifstream::failbit);
    +  in.open(filename.c_str(), std::ios_base::in | std::ios_base::binary);
    +  in.seekg(0, std::ios_base::end);
    +  size_t const size = size_t(in.tellg());
    +  in.seekg(0, std::ios_base::beg);
    +  std::vector<char> ret(size);
    +  in.read(ret.data(), ret.size());
    +  return ret;
    +}
    +
    +std::string branch_path(std::string const& f)
    +{
    +  if (f.empty()) return f;
    +
    +#ifdef TORRENT_WINDOWS
    +  if (f == "\\\\") return "";
    +#endif
    +  if (f == "/") return "";
    +
    +  int len = int(f.size());
    +  // if the last character is / or \ ignore it
    +  if (f[len-1] == '/' || f[len-1] == '\\') --len;
    +  while (len > 0) {
    +    --len;
    +    if (f[len] == '/' || f[len] == '\\')
    +      break;
    +  }
    +
    +  if (f[len] == '/' || f[len] == '\\') ++len;
    +  return std::string(f.c_str(), len);
    +}
    +
    +// do not include files and folders whose
    +// name starts with a .
    +bool file_filter(std::string const& f)
    +{
    +  if (f.empty()) return false;
    +
    +  char const* first = f.c_str();
    +  char const* sep = strrchr(first, '/');
    +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
    +  char const* altsep = strrchr(first, '\\');
    +  if (sep == nullptr || altsep > sep) sep = altsep;
    +#endif
    +  // if there is no parent path, just set 'sep'
    +  // to point to the filename.
    +  // if there is a parent path, skip the '/' character
    +  if (sep == nullptr) sep = first;
    +  else ++sep;
    +
    +  // return false if the first character of the filename is a .
    +  if (sep[0] == '.') return false;
    +
    +  std::cerr << f << "\n";
    +  return true;
    +}
    +
    +void print_usage()
    +{
    +  std::cerr << R"(usage: make_torrent FILE [OPTIONS]
    +
    +Generates a torrent file from the specified file
    +or directory and writes it to standard out
    +
    +
    +OPTIONS:
    +-w url        adds a web seed to the torrent with
    +              the specified url
    +-t url        adds the specified tracker to the
    +              torrent. For multiple trackers, specify more
    +              -t options. Specify a dash character "-" as a tracker to indicate
    +              the following trackers should be in a higher tier.
    +-c comment    sets the comment to the specified string
    +-C creator    sets the created-by field to the specified string
    +-s bytes      specifies a piece size for the torrent
    +              This has to be a power of 2, minimum 16kiB
    +-l            Don't follow symlinks, instead encode them as
    +              links in the torrent file
    +-o file       specifies the output filename of the torrent file
    +              If this is not specified, the torrent file is
    +              printed to the standard out, except on windows
    +              where the filename defaults to a.torrent
    +-r file       add root certificate to the torrent, to verify
    +              the HTTPS tracker
    +-S info-hash  add a similar torrent by info-hash. The similar
    +              torrent is expected to share some files with this one
    +-L collection add a collection name to this torrent. Other torrents
    +              in the same collection is expected to share files
    +              with this one.
    +-2            Only generate V2 metadata
    +)";
    +}
    +
    +int main(int argc_, char const* argv_[]) try
    +{
    +  lt::span<char const*> args(argv_, size_t(argc_));
    +  std::string creator_str = "libtorrent";
    +  std::string comment_str;
    +
    +  if (args.size() < 2) {
    +    print_usage();
    +    return 1;
    +  }
    +
    +  std::vector<std::string> web_seeds;
    +  std::vector<std::string> trackers;
    +  std::vector<std::string> collections;
    +  std::vector<lt::sha1_hash> similar;
    +  int piece_size = 0;
    +  lt::create_flags_t flags = {};
    +  std::string root_cert;
    +
    +  std::string outfile;
    +#ifdef TORRENT_WINDOWS
    +  // don't ever write binary data to the console on windows
    +  // it will just be interpreted as text and corrupted
    +  outfile = "a.torrent";
    +#endif
    +
    +  std::string full_path = args[1];
    +  args = args.subspan(2);
    +
    +  for (; !args.empty(); args = args.subspan(1)) {
    +    if (args[0][0] != '-') {
    +      print_usage();
    +      return 1;
    +    }
    +
    +    char const flag = args[0][1];
    +
    +    switch (flag)
    +    {
    +      case 'l':
    +        flags |= lt::create_torrent::symlinks;
    +        continue;
    +      case '2':
    +        flags |= lt::create_torrent::v2_only;
    +        continue;
    +    }
    +
    +    if (args.size() < 2) {
    +      print_usage();
    +      return 1;
    +    }
    +
    +    switch (flag)
    +    {
    +      case 'w': web_seeds.push_back(args[1]); break;
    +      case 't': trackers.push_back(args[1]); break;
    +      case 's': piece_size = atoi(args[1]); break;
    +      case 'o': outfile = args[1]; break;
    +      case 'C': creator_str = args[1]; break;
    +      case 'c': comment_str = args[1]; break;
    +      case 'r': root_cert = args[1]; break;
    +      case 'L': collections.push_back(args[1]); break;
    +      case 'S': {
    +        if (strlen(args[1]) != 40) {
    +          std::cerr << "invalid info-hash for -S. "
    +            "Expected 40 hex characters\n";
    +          print_usage();
    +          return 1;
    +        }
    +        std::stringstream hash(args[1]);
    +        lt::sha1_hash ih;
    +        hash >> ih;
    +        if (hash.fail()) {
    +          std::cerr << "invalid info-hash for -S\n";
    +          print_usage();
    +          return 1;
    +        }
    +        similar.push_back(ih);
    +        break;
    +      }
    +      default:
    +        print_usage();
    +        return 1;
    +    }
    +    args = args.subspan(1);
    +  }
    +
    +  lt::file_storage fs;
    +#ifdef TORRENT_WINDOWS
    +  if (full_path[1] != ':')
    +#else
    +  if (full_path[0] != '/')
    +#endif
    +  {
    +    char cwd[2048];
    +#ifdef TORRENT_WINDOWS
    +#define getcwd_ _getcwd
    +#else
    +#define getcwd_ getcwd
    +#endif
    +
    +    char const* ret = getcwd_(cwd, sizeof(cwd));
    +    if (ret == nullptr) {
    +      std::cerr << "failed to get current working directory: "
    +        << strerror(errno) << "\n";
    +      return 1;
    +    }
    +
    +#undef getcwd_
    +#ifdef TORRENT_WINDOWS
    +    full_path = cwd + ("\\" + full_path);
    +#else
    +    full_path = cwd + ("/" + full_path);
    +#endif
    +  }
    +
    +  lt::add_files(fs, full_path, file_filter, flags);
    +  if (fs.num_files() == 0) {
    +    std::cerr << "no files specified.\n";
    +    return 1;
    +  }
    +
    +  lt::create_torrent t(fs, piece_size, flags);
    +  int tier = 0;
    +  for (std::string const& tr : trackers) {
    +    if (tr == "-") ++tier;
    +    else t.add_tracker(tr, tier);
    +  }
    +
    +  for (std::string const& ws : web_seeds)
    +    t.add_url_seed(ws);
    +
    +  for (std::string const& c : collections)
    +    t.add_collection(c);
    +
    +  for (lt::sha1_hash const& s : similar)
    +    t.add_similar_torrent(s);
    +
    +  auto const num = t.num_pieces();
    +  lt::set_piece_hashes(t, branch_path(full_path)
    +    , [num] (lt::piece_index_t const p) {
    +      std::cerr << "\r" << p << "/" << num;
    +    });
    +
    +  std::cerr << "\n";
    +  t.set_creator(creator_str.c_str());
    +  if (!comment_str.empty()) {
    +    t.set_comment(comment_str.c_str());
    +  }
    +
    +  if (!root_cert.empty()) {
    +    std::vector<char> const pem = load_file(root_cert);
    +    t.set_root_cert(std::string(&pem[0], pem.size()));
    +  }
    +
    +  // create the torrent and print it to stdout
    +  std::vector<char> torrent;
    +  lt::bencode(back_inserter(torrent), t.generate());
    +  if (!outfile.empty()) {
    +    std::fstream out;
    +    out.exceptions(std::ifstream::failbit);
    +    out.open(outfile.c_str(), std::ios_base::out | std::ios_base::binary);
    +    out.write(torrent.data(), torrent.size());
    +  }
    +  else {
    +    std::cout.write(torrent.data(), torrent.size());
    +  }
    +
    +  return 0;
    +}
    +catch (std::exception& e) {
    +  std::cerr << "ERROR: " << e.what() << "\n";
    +  return 1;
    +}
    +
    +
    +
    +

    dump_torrent

    +

    This is an example of a program that will take a torrent-file as a parameter and +print information about it to std out:

    +
    +#include <cstdio> // for snprintf
    +#include <cinttypes> // for PRId64 et.al.
    +#include <fstream>
    +#include <iostream>
    +
    +#include "libtorrent/entry.hpp"
    +#include "libtorrent/bencode.hpp"
    +#include "libtorrent/torrent_info.hpp"
    +#include "libtorrent/bdecode.hpp"
    +#include "libtorrent/magnet_uri.hpp"
    +#include "libtorrent/span.hpp"
    +
    +void print_usage()
    +{
    +  std::cerr << R"(usage: dump_torrent torrent-file [options]
    +    OPTIONS:
    +    --items-limit <count>    set the upper limit of the number of bencode items
    +                             in the torrent file.
    +    --depth-limit <count>    set the recursion limit in the bdecoder
    +    --show-padfiles          show pad files in file list
    +    --max-pieces <count>     set the upper limit on the number of pieces to
    +                             load in the torrent.
    +    --max-size <size in MiB> reject files larger than this size limit
    +)";
    +  std::exit(1);
    +}
    +
    +int main(int argc, char const* argv[]) try
    +{
    +  lt::span<char const*> args(argv, argc);
    +
    +  // strip executable name
    +  args = args.subspan(1);
    +
    +  lt::load_torrent_limits cfg;
    +  bool show_pad = false;
    +
    +  if (args.empty()) print_usage();
    +
    +  char const* filename = args[0];
    +  args = args.subspan(1);
    +
    +  using namespace lt::literals;
    +
    +  while (!args.empty())
    +  {
    +    if (args[0] == "--items-limit"_sv && args.size() > 1)
    +    {
    +      cfg.max_decode_tokens = atoi(args[1]);
    +      args = args.subspan(2);
    +    }
    +    else if (args[0] == "--depth-limit"_sv && args.size() > 1)
    +    {
    +      cfg.max_decode_depth = atoi(args[1]);
    +      args = args.subspan(2);
    +    }
    +    else if (args[0] == "--max-pieces"_sv && args.size() > 1)
    +    {
    +      cfg.max_pieces = atoi(args[1]);
    +      args = args.subspan(2);
    +    }
    +    else if (args[0] == "--max-size"_sv && args.size() > 1)
    +    {
    +      cfg.max_buffer_size = atoi(args[1]) * 1024 * 1024;
    +      args = args.subspan(2);
    +    }
    +    else if (args[0] == "--show-padfiles"_sv)
    +    {
    +      show_pad = true;
    +      args = args.subspan(1);
    +    }
    +    else
    +    {
    +      std::cerr << "unknown option: " << args[0] << "\n";
    +      print_usage();
    +    }
    +  }
    +
    +  lt::torrent_info const t(filename, cfg);
    +
    +  // print info about torrent
    +  if (!t.nodes().empty())
    +  {
    +    std::printf("nodes:\n");
    +    for (auto const& i : t.nodes())
    +      std::printf("%s: %d\n", i.first.c_str(), i.second);
    +  }
    +
    +  if (!t.trackers().empty())
    +  {
    +    puts("trackers:\n");
    +    for (auto const& i : t.trackers())
    +      std::printf("%2d: %s\n", i.tier, i.url.c_str());
    +  }
    +
    +  std::stringstream ih;
    +  ih << t.info_hashes().v1;
    +  if (t.info_hashes().has_v2())
    +    ih << ", " << t.info_hashes().v2;
    +  std::printf("number of pieces: %d\n"
    +    "piece length: %d\n"
    +    "info hash: %s\n"
    +    "comment: %s\n"
    +    "created by: %s\n"
    +    "magnet link: %s\n"
    +    "name: %s\n"
    +    "number of files: %d\n"
    +    "files:\n"
    +    , t.num_pieces()
    +    , t.piece_length()
    +    , ih.str().c_str()
    +    , t.comment().c_str()
    +    , t.creator().c_str()
    +    , make_magnet_uri(t).c_str()
    +    , t.name().c_str()
    +    , t.num_files());
    +  lt::file_storage const& st = t.files();
    +  for (auto const i : st.file_range())
    +  {
    +    auto const first = st.map_file(i, 0, 0).piece;
    +    auto const last = st.map_file(i, std::max(std::int64_t(st.file_size(i)) - 1, std::int64_t(0)), 0).piece;
    +    auto const flags = st.file_flags(i);
    +    if ((flags & lt::file_storage::flag_pad_file) && !show_pad) continue;
    +    std::stringstream file_root;
    +    if (!st.root(i).is_all_zeros())
    +      file_root << st.root(i);
    +    std::printf(" %8" PRIx64 " %11" PRId64 " %c%c%c%c [ %5d, %5d ] %7u %s %s %s%s\n"
    +      , st.file_offset(i)
    +      , st.file_size(i)
    +      , ((flags & lt::file_storage::flag_pad_file)?'p':'-')
    +      , ((flags & lt::file_storage::flag_executable)?'x':'-')
    +      , ((flags & lt::file_storage::flag_hidden)?'h':'-')
    +      , ((flags & lt::file_storage::flag_symlink)?'l':'-')
    +      , static_cast<int>(first)
    +      , static_cast<int>(last)
    +      , std::uint32_t(st.mtime(i))
    +      , file_root.str().c_str()
    +      , st.file_path(i).c_str()
    +      , (flags & lt::file_storage::flag_symlink) ? "-> " : ""
    +      , (flags & lt::file_storage::flag_symlink) ? st.symlink(i).c_str() : "");
    +  }
    +  std::printf("web seeds:\n");
    +  for (auto const& ws : t.web_seeds())
    +  {
    +    std::printf("%s %s\n"
    +      , ws.type == lt::web_seed_entry::url_seed ? "BEP19" : "BEP17"
    +      , ws.url.c_str());
    +  }
    +
    +  return 0;
    +}
    +catch (std::exception const& e)
    +{
    +  std::cerr << "ERROR: " << e.what() << "\n";
    +}
    +
    +
    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..5baf4bf --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,46 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +examples +======== + +Except for the example programs in this manual, there's also a bigger example +of a (little bit) more complete client, ``client_test``. There are separate +instructions for how to use it here__ if you'd like to try it. + +__ client_test.html + +simple client +------------- + +This is a simple client. It doesn't have much output to keep it simple: + +.. include:: ../examples/simple_client.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +make_torrent +------------ + +Shows how to create a torrent from a directory tree: + +.. include:: ../examples/make_torrent.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +dump_torrent +------------ + +This is an example of a program that will take a torrent-file as a parameter and +print information about it to std out: + +.. include:: ../examples/dump_torrent.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + diff --git a/docs/extension_protocol.html b/docs/extension_protocol.html new file mode 100644 index 0000000..6842616 --- /dev/null +++ b/docs/extension_protocol.html @@ -0,0 +1,471 @@ + + + + + + + +extension_protocol.rst + + + + + + +
    +
    + + libtorrent logo + +
    + + +++ + + + +
    Author:Arvid Norberg, arvid@libtorrent.org +Ludvig Strigeus, ludde@utorrent.com
    +
    +

    extension protocol for bittorrent

    +

    The intention of this protocol is to provide a simple and thin transport +for extensions to the bittorrent protocol. Supporting this protocol makes +it easy to add new extensions without interfering with the standard +bittorrent protocol or clients that don't support this extension or the +one you want to add.

    +

    To advertise to other clients that you support, one bit from the reserved +bytes is used.

    +

    The bit selected for the extension protocol is bit 20 from the right (counting +starts at 0). So (reserved_byte[5] & 0x10) is the expression to use for checking +if the client supports extended messaging.

    +

    Once support for the protocol is established, the client is supposed to +support 1 new message:

    + ++++ + + + + + + + + + + +
    nameid
    extended20
    +

    This message is sent as any other bittorrent message, with a 4 byte length +prefix and a single byte identifying the message (the single byte being 20 +in this case). At the start of the payload of the message, is a single byte +message identifier. This identifier can refer to different extension messages +and only one ID is specified, 0. If the ID is 0, the message is a handshake +message which is described below. The layout of a general extended message +follows (including the message headers used by the bittorrent protocol):

    + ++++ + + + + + + + + + + + + + + + + +
    sizedescription
    uint32_tlength prefix. Specifies the number of bytes for the +entire message. (big-endian)
    uint8_tbittorrent message ID, = 20
    uint8_textended message ID. 0 = handshake, >0 = extended +message as specified by the handshake.
    +
    +

    handshake message

    +

    The payload of the handshake message is a bencoded dictionary. All items +in the dictionary are optional. Any unknown names should be ignored +by the client. All parts of the dictionary are case sensitive. +This is the defined item in the dictionary:

    + ++++ + + + + + + + + + + +
    namedescription
    m

    Dictionary of supported extension messages which maps +names of extensions to an extended message ID for each +extension message. The only requirement on these IDs +is that no extension message share the same one. Setting +an extension number to zero means that the extension is +not supported/disabled. The client should ignore any +extension names it doesn't recognize.

    +

    The extension message IDs are the IDs used to send the +extension messages to the peer sending this handshake. +i.e. The IDs are local to this particular peer.

    +
    +

    Here are some other items that an implementation may choose to support:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    namedescription
    pLocal TCP listen port. Allows each side to learn about +the TCP port number of the other side. Note that there is +no need for the receiving side of the connection to send +this extension message, since its port number is already +known.
    vClient name and version (as a utf-8 string). +This is a much more reliable way of identifying the +client than relying on the peer id encoding.
    youripA string containing the compact representation of the ip +address this peer sees you as. i.e. this is the +receiver's external ip address (no port is included). +This may be either an IPv4 (4 bytes) or an IPv6 +(16 bytes) address.
    ipv6If this peer has an IPv6 interface, this is the compact +representation of that address (16 bytes). The client may +prefer to connect back via the IPv6 address.
    ipv4If this peer has an IPv4 interface, this is the compact +representation of that address (4 bytes). The client may +prefer to connect back via this interface.
    reqqAn integer, the number of outstanding request messages +this client supports without dropping any. The default in +in libtorrent is 250.
    +

    The handshake dictionary could also include extended handshake +information, such as support for encrypted headers or anything +imaginable.

    +

    An example of what the payload of a handshake message could look like:

    + ++++ + + + + + + + + + + + + + + + +
    Dictionary
    m ++++ + + + + + + + + + + + + +
    Dictionary
    LT_metadata1
    ut_pex2
    +
    p6881
    v"uTorrent 1.2"
    +

    and in the encoded form:

    +

    d1:md11:LT_metadatai1e6:ut_pexi2ee1:pi6881e1:v12:uTorrent 1.2e

    +

    To make sure the extension names do not collide by mistake, they should be +prefixed with the two (or one) character code that is used to identify the +client that introduced the extension. This applies for both the names of +extension messages, and for any additional information put inside the +top-level dictionary. All one and two byte identifiers are invalid to use +unless defined by this specification.

    +

    This message should be sent immediately after the standard bittorrent handshake +to any peer that supports this extension protocol. It is valid to send the +handshake message more than once during the lifetime of a connection, +the sending client should not be disconnected. An implementation may choose +to ignore the subsequent handshake messages (or parts of them).

    +

    Subsequent handshake messages can be used to enable/disable extensions +without restarting the connection. If a peer supports changing extensions +at run time, it should note that the m dictionary is additive. +It's enough that it contains the actual CHANGES to the extension list. +To disable the support for LT_metadata at run-time, without affecting +any other extensions, this message should be sent: +d11:LT_metadatai0ee. +As specified above, the value 0 is used to turn off an extension.

    +

    The extension IDs must be stored for every peer, because every peer may have +different IDs for the same extension.

    +

    This specification, deliberately, does not specify any extensions such as +peer-exchange or metadata exchange. This protocol is merely a transport +for the actual extensions to the bittorrent protocol and the extensions +named in the example above (such as p) are just examples of possible +extensions.

    +
    +
    +

    rationale

    +

    The reason why the extension messages' IDs would be defined in the handshake +is to avoid having a global registry of message IDs. Instead the names of the +extension messages requires unique names, which is much easier to do without +a global registry. The convention is to use a two letter prefix on the +extension message names, the prefix would identify the client first +implementing the extension message. e.g. LT_metadata is implemented by +libtorrent, and hence it has the LT prefix.

    +

    If the client supporting the extensions can decide which numbers the messages +it receives will have, it means they are constants within that client. i.e. +they can be used in switch statements. It's easy for the other end to +store an array with the ID's we expect for each message and use that for +lookups each time it sends an extension message.

    +

    The reason for having a dictionary instead of having an array (using +implicitly assigned index numbers to the extensions) is that if a client +want to disable some extensions, the ID numbers would change, and it wouldn't +be able to use constants (and hence, not use them in a switch). If the +messages IDs would map directly to bittorrent message IDs, It would also make +it possible to map extensions in the handshake to existing extensions with +fixed message IDs.

    +

    The reasoning behind having a single byte as extended message identifier is +to follow the bittorrent spec. with its single byte message identifiers. +It is also considered to be enough. It won't limit the total number of +extensions, only the number of extensions used simultaneously.

    +

    The reason for using single byte identifiers for the standardized handshake +identifiers is 1) The mainline DHT uses single byte identifiers. 2) Saves +bandwidth. The only advantage of longer messages is that it makes the +protocol more readable for a human, but the BT protocol wasn't designed to +be a human readable protocol, so why bother.

    +
    +
    +
    +

    extensions

    +

    These extensions all operates within the extension protocol. The name of the +extension is the name used in the extension-list packets, and the payload is +the data in the extended message (not counting the length-prefix, message-id +nor extension-id).

    +

    Note that since this protocol relies on one of the reserved bits in the +handshake, it may be incompatible with future versions of the mainline +bittorrent client.

    +

    These are the extensions that are currently implemented.

    +
    +

    metadata from peers

    +

    Extension name: "LT_metadata"

    +
    +

    Note

    +

    This extension is deprecated in favor of the more widely supported +ut_metadata extension, see BEP 9.

    +
    +

    The point with this extension is that you don't have to distribute the +metadata (.torrent-file) separately. The metadata can be distributed +through the bittorrent swarm. The only thing you need to download such +a torrent is the tracker url and the info-hash of the torrent.

    +

    It works by assuming that the initial seeder has the metadata and that the +metadata will propagate through the network as more peers join.

    +

    There are three kinds of messages in the metadata extension. These packets are +put as payload to the extension message. The three packets are:

    +
    +
      +
    • request metadata
    • +
    • metadata
    • +
    • don't have metadata
    • +
    +
    +

    request metadata:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    uint8_tmsg_typeDetermines the kind of message this is +0 means request metadata
    uint8_tstartThe start of the metadata block that +is requested. It is given in 256ths +of the total size of the metadata, +since the requesting client don't know +the size of the metadata.
    uint8_tsizeThe size of the metadata block that is +requested. This is also given in +256ths of the total size of the +metadata. The size is given as size-1. +That means that if this field is set +0, the request wants one 256:th of the +metadata.
    +

    metadata:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    uint8_tmsg_type1 means metadata
    int32_ttotal_sizeThe total size of the metadata, given +in number of bytes.
    int32_toffsetThe offset of where the metadata block +in this message belongs in the final +metadata. This is given in bytes.
    uint8_t[]metadataThe actual metadata block. The size of +this part is given implicit by the +length prefix in the bittorrent +protocol packet.
    +

    Don't have metadata:

    + +++++ + + + + + + + + + + + + +
    sizenamedescription
    uint8_tmsg_type2 means "I don't have metadata". +This message is sent as a reply to a +metadata request if the the client +doesn't have any metadata.
    +
    +
    +

    dont_have

    +

    Extension name: "lt_donthave"

    +

    The dont_have extension message is used to tell peers that the client no +longer has a specific piece. The extension message should be advertised in the +m dictionary as lt_donthave. The message format mimics the regular +HAVE bittorrent message.

    +

    Just like all extension messages, the first 2 bytes in the message itself are 20 +(the bittorrent extension message) and the message ID assigned to this +extension in the m dictionary in the handshake.

    + +++++ + + + + + + + + + + + + +
    sizenamedescription
    uint32_tpieceindex of the piece the peer no longer +has.
    +

    The length of this message (including the extension message prefix) is 6 bytes, +i.e. one byte longer than the normal HAVE message, because of the extension +message wrapping.

    +
    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/extension_protocol.rst b/docs/extension_protocol.rst new file mode 100644 index 0000000..cd6a637 --- /dev/null +++ b/docs/extension_protocol.rst @@ -0,0 +1,324 @@ +:Author: Arvid Norberg, arvid@libtorrent.org + Ludvig Strigeus, ludde@utorrent.com + +extension protocol for bittorrent +================================= + +The intention of this protocol is to provide a simple and thin transport +for extensions to the bittorrent protocol. Supporting this protocol makes +it easy to add new extensions without interfering with the standard +bittorrent protocol or clients that don't support this extension or the +one you want to add. + +To advertise to other clients that you support, one bit from the reserved +bytes is used. + +The bit selected for the extension protocol is bit 20 from the right (counting +starts at 0). So (reserved_byte[5] & 0x10) is the expression to use for checking +if the client supports extended messaging. + +Once support for the protocol is established, the client is supposed to +support 1 new message: + ++------------------------+----+ +|name | id | ++========================+====+ +|``extended`` | 20 | ++------------------------+----+ + +This message is sent as any other bittorrent message, with a 4 byte length +prefix and a single byte identifying the message (the single byte being 20 +in this case). At the start of the payload of the message, is a single byte +message identifier. This identifier can refer to different extension messages +and only one ID is specified, 0. If the ID is 0, the message is a handshake +message which is described below. The layout of a general ``extended`` message +follows (including the message headers used by the bittorrent protocol): + ++----------+---------------------------------------------------------+ +| size | description | ++==========+=========================================================+ +| uint32_t | length prefix. Specifies the number of bytes for the | +| | entire message. (big-endian) | ++----------+---------------------------------------------------------+ +| uint8_t | bittorrent message ID, = 20 | ++----------+---------------------------------------------------------+ +| uint8_t | extended message ID. 0 = handshake, >0 = extended | +| | message as specified by the handshake. | ++----------+---------------------------------------------------------+ + + +handshake message +----------------- + +The payload of the handshake message is a bencoded dictionary. All items +in the dictionary are optional. Any unknown names should be ignored +by the client. All parts of the dictionary are case sensitive. +This is the defined item in the dictionary: + ++-------+-----------------------------------------------------------+ +| name | description | ++=======+===========================================================+ +| m | Dictionary of supported extension messages which maps | +| | names of extensions to an extended message ID for each | +| | extension message. The only requirement on these IDs | +| | is that no extension message share the same one. Setting | +| | an extension number to zero means that the extension is | +| | not supported/disabled. The client should ignore any | +| | extension names it doesn't recognize. | +| | | +| | The extension message IDs are the IDs used to send the | +| | extension messages to the peer sending this handshake. | +| | i.e. The IDs are local to this particular peer. | ++-------+-----------------------------------------------------------+ + + +Here are some other items that an implementation may choose to support: + ++--------+-----------------------------------------------------------+ +| name | description | ++========+===========================================================+ +| p | Local TCP listen port. Allows each side to learn about | +| | the TCP port number of the other side. Note that there is | +| | no need for the receiving side of the connection to send | +| | this extension message, since its port number is already | +| | known. | ++--------+-----------------------------------------------------------+ +| v | Client name and version (as a utf-8 string). | +| | This is a much more reliable way of identifying the | +| | client than relying on the peer id encoding. | ++--------+-----------------------------------------------------------+ +| yourip | A string containing the compact representation of the ip | +| | address this peer sees you as. i.e. this is the | +| | receiver's external ip address (no port is included). | +| | This may be either an IPv4 (4 bytes) or an IPv6 | +| | (16 bytes) address. | ++--------+-----------------------------------------------------------+ +| ipv6 | If this peer has an IPv6 interface, this is the compact | +| | representation of that address (16 bytes). The client may | +| | prefer to connect back via the IPv6 address. | ++--------+-----------------------------------------------------------+ +| ipv4 | If this peer has an IPv4 interface, this is the compact | +| | representation of that address (4 bytes). The client may | +| | prefer to connect back via this interface. | ++--------+-----------------------------------------------------------+ +| reqq | An integer, the number of outstanding request messages | +| | this client supports without dropping any. The default in | +| | in libtorrent is 250. | ++--------+-----------------------------------------------------------+ + +The handshake dictionary could also include extended handshake +information, such as support for encrypted headers or anything +imaginable. + +An example of what the payload of a handshake message could look like: + ++------------------------------------------------------+ +| Dictionary | ++===================+==================================+ +| ``m`` | +--------------------------+ | +| | | Dictionary | | +| | +======================+===+ | +| | | ``LT_metadata`` | 1 | | +| | +----------------------+---+ | +| | | ``ut_pex`` | 2 | | +| | +----------------------+---+ | +| | | ++-------------------+----------------------------------+ +| ``p`` | 6881 | ++-------------------+----------------------------------+ +| ``v`` | "uTorrent 1.2" | ++-------------------+----------------------------------+ + +and in the encoded form: + +``d1:md11:LT_metadatai1e6:ut_pexi2ee1:pi6881e1:v12:uTorrent 1.2e`` + +To make sure the extension names do not collide by mistake, they should be +prefixed with the two (or one) character code that is used to identify the +client that introduced the extension. This applies for both the names of +extension messages, and for any additional information put inside the +top-level dictionary. All one and two byte identifiers are invalid to use +unless defined by this specification. + +This message should be sent immediately after the standard bittorrent handshake +to any peer that supports this extension protocol. It is valid to send the +handshake message more than once during the lifetime of a connection, +the sending client should not be disconnected. An implementation may choose +to ignore the subsequent handshake messages (or parts of them). + +Subsequent handshake messages can be used to enable/disable extensions +without restarting the connection. If a peer supports changing extensions +at run time, it should note that the ``m`` dictionary is additive. +It's enough that it contains the actual *CHANGES* to the extension list. +To disable the support for ``LT_metadata`` at run-time, without affecting +any other extensions, this message should be sent: +``d11:LT_metadatai0ee``. +As specified above, the value 0 is used to turn off an extension. + +The extension IDs must be stored for every peer, because every peer may have +different IDs for the same extension. + +This specification, deliberately, does not specify any extensions such as +peer-exchange or metadata exchange. This protocol is merely a transport +for the actual extensions to the bittorrent protocol and the extensions +named in the example above (such as ``p``) are just examples of possible +extensions. + +rationale +--------- + +The reason why the extension messages' IDs would be defined in the handshake +is to avoid having a global registry of message IDs. Instead the names of the +extension messages requires unique names, which is much easier to do without +a global registry. The convention is to use a two letter prefix on the +extension message names, the prefix would identify the client first +implementing the extension message. e.g. ``LT_metadata`` is implemented by +libtorrent, and hence it has the ``LT`` prefix. + +If the client supporting the extensions can decide which numbers the messages +it receives will have, it means they are constants within that client. i.e. +they can be used in ``switch`` statements. It's easy for the other end to +store an array with the ID's we expect for each message and use that for +lookups each time it sends an extension message. + +The reason for having a dictionary instead of having an array (using +implicitly assigned index numbers to the extensions) is that if a client +want to disable some extensions, the ID numbers would change, and it wouldn't +be able to use constants (and hence, not use them in a ``switch``). If the +messages IDs would map directly to bittorrent message IDs, It would also make +it possible to map extensions in the handshake to existing extensions with +fixed message IDs. + +The reasoning behind having a single byte as extended message identifier is +to follow the bittorrent spec. with its single byte message identifiers. +It is also considered to be enough. It won't limit the total number of +extensions, only the number of extensions used simultaneously. + +The reason for using single byte identifiers for the standardized handshake +identifiers is 1) The mainline DHT uses single byte identifiers. 2) Saves +bandwidth. The only advantage of longer messages is that it makes the +protocol more readable for a human, but the BT protocol wasn't designed to +be a human readable protocol, so why bother. + + +extensions +========== + +These extensions all operates within the `extension protocol`_. The name of the +extension is the name used in the extension-list packets, and the payload is +the data in the extended message (not counting the length-prefix, message-id +nor extension-id). + +.. _`extension protocol`: extension_protocol.html + +Note that since this protocol relies on one of the reserved bits in the +handshake, it may be incompatible with future versions of the mainline +bittorrent client. + +These are the extensions that are currently implemented. + +metadata from peers +------------------- + +Extension name: "LT_metadata" + +.. note:: + This extension is deprecated in favor of the more widely supported + ``ut_metadata`` extension, see `BEP 9`_. + +The point with this extension is that you don't have to distribute the +metadata (.torrent-file) separately. The metadata can be distributed +through the bittorrent swarm. The only thing you need to download such +a torrent is the tracker url and the info-hash of the torrent. + +It works by assuming that the initial seeder has the metadata and that the +metadata will propagate through the network as more peers join. + +There are three kinds of messages in the metadata extension. These packets are +put as payload to the extension message. The three packets are: + + * request metadata + * metadata + * don't have metadata + +request metadata: + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | Determines the kind of message this is | +| | | 0 means *request metadata* | ++-----------+---------------+----------------------------------------+ +| uint8_t | start | The start of the metadata block that | +| | | is requested. It is given in 256ths | +| | | of the total size of the metadata, | +| | | since the requesting client don't know | +| | | the size of the metadata. | ++-----------+---------------+----------------------------------------+ +| uint8_t | size | The size of the metadata block that is | +| | | requested. This is also given in | +| | | 256ths of the total size of the | +| | | metadata. The size is given as size-1. | +| | | That means that if this field is set | +| | | 0, the request wants one 256:th of the | +| | | metadata. | ++-----------+---------------+----------------------------------------+ + +metadata: + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | 1 means *metadata* | ++-----------+---------------+----------------------------------------+ +| int32_t | total_size | The total size of the metadata, given | +| | | in number of bytes. | ++-----------+---------------+----------------------------------------+ +| int32_t | offset | The offset of where the metadata block | +| | | in this message belongs in the final | +| | | metadata. This is given in bytes. | ++-----------+---------------+----------------------------------------+ +| uint8_t[] | metadata | The actual metadata block. The size of | +| | | this part is given implicit by the | +| | | length prefix in the bittorrent | +| | | protocol packet. | ++-----------+---------------+----------------------------------------+ + +Don't have metadata: + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint8_t | msg_type | 2 means "I don't have metadata". | +| | | This message is sent as a reply to a | +| | | metadata request if the the client | +| | | doesn't have any metadata. | ++-----------+---------------+----------------------------------------+ + +.. _`BEP 9`: https://www.bittorrent.org/beps/bep_0009.html + +dont_have +--------- + +Extension name: "lt_donthave" + +The ``dont_have`` extension message is used to tell peers that the client no +longer has a specific piece. The extension message should be advertised in the +``m`` dictionary as ``lt_donthave``. The message format mimics the regular +``HAVE`` bittorrent message. + +Just like all extension messages, the first 2 bytes in the message itself are 20 +(the bittorrent extension message) and the message ID assigned to this +extension in the ``m`` dictionary in the handshake. + ++-----------+---------------+----------------------------------------+ +| size | name | description | ++===========+===============+========================================+ +| uint32_t | piece | index of the piece the peer no longer | +| | | has. | ++-----------+---------------+----------------------------------------+ + +The length of this message (including the extension message prefix) is 6 bytes, +i.e. one byte longer than the normal ``HAVE`` message, because of the extension +message wrapping. + diff --git a/docs/features.html b/docs/features.html new file mode 100644 index 0000000..03ad053 --- /dev/null +++ b/docs/features.html @@ -0,0 +1,363 @@ + + + + + + +libtorrent manual + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +
    +

    introduction

    +

    libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a simple bittorrent client demonstrating the use of +the library.

    +
    +
    +

    features

    +

    libtorrent is an ongoing project under active development. Its +current state supports and includes the following features:

    +
    +

    extensions

    +
      +
    • plugin interface for implementing custom bittorrent extensions +without having to modify libtorrent
    • +
    • supports trackerless torrents (using the Mainline kademlia DHT protocol) with +some DHT extensions. BEP 5.
    • +
    • supports the bittorrent extension protocol. See extensions. BEP 10.
    • +
    • supports the uTorrent metadata transfer protocol BEP 9 (i.e. magnet links).
    • +
    • supports the uTorrent peer exchange protocol (PEX).
    • +
    • supports local peer discovery (multicast for peers on the same local network)
    • +
    • multi-tracker extension support (supports both strict BEP 12 and the +uTorrent interpretation).
    • +
    • tracker scrapes
    • +
    • supports lt_trackers extension, to exchange trackers between peers
    • +
    • HTTP seeding, as specified in BEP 17 and BEP 19.
    • +
    • supports the UDP-tracker protocol. (BEP 15).
    • +
    • supports the no_peer_id=1 extension that will ease the load off trackers.
    • +
    • supports the compact=1 tracker parameter.
    • +
    • super seeding/initial seeding (BEP 16).
    • +
    • private torrents (BEP 27).
    • +
    • upload-only extension (BEP 21).
    • +
    • support for IPv6, including BEP 7 and BEP 24.
    • +
    • support for merkle hash tree torrents. This makes the size of torrent files +scale well with the size of the content.
    • +
    • share-mode. This is a special mode torrents can be put in to optimize share +ratio rather than downloading the torrent.
    • +
    • supports the Magnet URI extension - Select specific file indices for +download. BEP 53.
    • +
    +
    +
    +

    disk management

    +
      +
    • can use multiple disk I/O threads to not have the disk block network or +client interaction.
    • +
    • supports verifying the SHA-1 hash of pieces in multiple threads, to take +advantage of multi core machines.
    • +
    • supports files > 2 gigabytes.
    • +
    • fast resume support, a way to avoid the costly piece check at the +start of a resumed torrent. Saves the storage state, piece_picker state +as well as all local peers in a fast-resume file.
    • +
    • has an adjustable read and write disk cache for improved disk throughput.
    • +
    • queues torrents for file check, instead of checking all of them in parallel.
    • +
    • does not have any requirements on the piece order in a torrent that it +resumes. This means it can resume a torrent downloaded by any client.
    • +
    • seed mode, where the files on disk are assumed to be complete, and each +piece's hash is verified the first time it is requested.
    • +
    • implements an ARC disk cache, tuned for performing well under bittorrent work +loads
    • +
    +
    +
    +

    network

    +
      +
    • a high quality uTP implementation (BEP 29). A transport protocol with +delay based congestion control. See separate article.
    • +
    • adjusts the length of the request queue depending on download rate.
    • +
    • serves multiple torrents on a single port and in a single thread
    • +
    • piece picking on block-level (as opposed to piece-level). +This means it can download parts of the same piece from different peers. +It will also prefer to download whole pieces from single peers if the +download speed is high enough from that particular peer.
    • +
    • supports http proxies and basic proxy authentication
    • +
    • supports gzip tracker-responses
    • +
    • can limit the upload and download bandwidth usage and the maximum number of +unchoked peers
    • +
    • possibility to limit the number of connections.
    • +
    • delays have messages if there's no other outgoing traffic to the peer, and +doesn't send have messages to peers that already has the piece. This saves +bandwidth.
    • +
    • selective downloading. The ability to select which parts of a torrent you +want to download.
    • +
    • ip filter to disallow ip addresses and ip ranges from connecting and +being connected.
    • +
    • NAT-PMP and UPnP support (automatic port mapping on routers that supports it)
    • +
    • implements automatic upload slots, to optimize download rate without spreading +upload capacity too thin. The number of upload slots is adjusted based on the +peers' download capacity to work even for connections that are orders of +magnitude faster than others.
    • +
    +
    +
    +
    +

    highlighted features

    +
    +

    disk caching

    +

    All disk I/O in libtorrent is done asynchronously to the network thread, by the +disk io threads. When a block is read, the disk io thread reads all subsequent +blocks from that piece into the read cache, assuming that the peer requesting +the block will also request more blocks from the same piece. This decreases the +number of system calls for reading data. It also decreases delay from seeking.

    +

    Similarly, for write requests, blocks are cached and flushed to disk once one full +piece is complete or the piece is the least recently updated one when more cache +space is needed. The cache dynamically allocates space between the write and read +cache. The write cache is strictly prioritized over the read cache.

    +

    The cache blocks that are in used, are locked into physical memory to avoid it +being paged out to disk. Allowing the disk cache to be paged out to disk means +that it would become extremely inefficient to flush it, since it would have to be +read back into physical memory only to be flushed back out to disk again.

    +

    In order to conserve memory, and system calls, iovec file operations are +used to flush multiple cache blocks in a single call.

    +

    On low-memory systems, the disk cache can be disabled altogether or set to smaller +limit, to save memory.

    +

    The disk caching algorithm is configurable between LRU and largest contiguous. +The largest contiguous algorithm is the default and flushes the largest contiguous +block of buffers, instead of flushing all blocks belonging to the piece which was +written to least recently.

    +
    +
    +

    network buffers

    +

    On CPUs with small L2 caches, copying memory can be expensive operations. It is important +to keep copying to a minimum on such machines. This mostly applies to embedded systems.

    +

    In order to minimize the number of times received data is copied, the receive buffer +for payload data is received directly into a page aligned disk buffer. If the connection +is encrypted, the buffer is decrypted in-place. The buffer is then moved into the disk +cache without being copied. Once all the blocks for a piece have been received, or the +cache needs to be flushed, all the blocks are passed directly to writev() to flush +them in a single system call. This means a single copy into user space memory, and a single +copy back into kernel memory, as illustrated by this figure:

    +write_disk_buffers.png +

    When seeding and uploading in general, unnecessary copying is avoided by caching blocks +in aligned buffers, that are copied once into the peer's send buffer. The peer's send buffer +is not guaranteed to be aligned, even though it is most of the time. The send buffer is +then encrypted with the peer specific key and chained onto the iovec for sending. +This means there is one user space copy in order to allow unaligned peer requests and +peer-specific encryption. This is illustrated by the following figure:

    +read_disk_buffers.png +
    +
    +

    piece picker

    +

    The piece picker is a central component in a bittorrent implementation. The piece picker +in libtorrent is optimized for quickly finding the rarest pieces. It keeps a list of all +available pieces sorted by rarity, and pieces with the same rarity, shuffled. The rarest +first mode is the dominant piece picker mode. Other modes are supported as well, and +used by peers in specific situations.

    +

    The piece picker allows to combine the availability of a piece with a priority. Together +they determine the sort order of the piece list. Pieces with priority 0 will never be +picked, which is used for the selective download feature.

    +

    In order to have as few partially finished pieces as possible, peers have an affinity +towards picking blocks from the same pieces as other peers in the same speed category. +The speed category is a coarse categorization of peers based on their download rate. This +makes slow peers pick blocks from the same piece, and fast peers pick from the same piece, +and hence decreasing the likelihood of slow peers blocking the completion of pieces.

    +

    The piece picker can also be set to download pieces in sequential order.

    +
    +
    +

    share mode

    +

    The share mode feature in libtorrent is intended for users who are only interested in +helping out swarms, not downloading the torrents.

    +

    It works by predicting the demand for pieces, and only download pieces if there is enough +demand. New pieces will only be downloaded once the share ratio has hit a certain target.

    +

    This feature is especially useful when combined with RSS, so that a client can be set up +to provide additional bandwidth to an entire feed.

    +
    +
    +

    merkle hash tree torrents

    +merkle_tree.png +

    Merkle hash tree torrents is an extension that lets a torrent file only contain the +root hash of the hash tree forming the piece hashes. The main benefit of this feature +is that regardless of how many pieces there is in a torrent, the .torrent file will +always be the same size. It will only grow with the number of files (since it still +has to contain the file names).

    +

    With regular torrents, clients have to request multiple blocks for pieces, typically +from different peers, before the data can be verified against the piece hash. The +larger the pieces are, the longer it will take to download a complete piece and verify +it. Before the piece is verified, it cannot be shared with the swarm, which means the +larger piece sizes, the slower turnaround data has when it is downloaded by peers. +Since on average the data has to sit around, waiting, in client buffers before it has +been verified and can be uploaded again.

    +

    Another problem with large piece sizes is that it is harder for a client to pinpoint +the malicious or buggy peer when a piece fails, and it will take longer to re-download +it and take more tries before the piece succeeds the larger the pieces are.

    +

    The piece size in regular torrents is a trade-off between the size of the .torrent file +itself and the piece size. Often, for files that are 4 GB, the piece size is 2 or 4 MB, +just to avoid making the .torrent file too big.

    +

    Merkle torrents solves these problems by removing the trade-off between .torrent size and +piece size. With merkle torrents, the piece size can be the minimum block size (16 kB), +which lets peers verify every block of data received from peers, immediately. This +gives a minimum turnaround time and completely removes the problem of identifying malicious +peers.

    +

    The root hash is built by hashing all the piece hashes pair-wise, until they all collapse +down to the root.

    +
    +
    +

    customizable file storage

    +storage.png +

    libtorrent's storage implementation is customizable. That means a special purpose bittorrent +client can replace the default way to store files on disk.

    +

    When implementing a bittorrent cache, it doesn't matter how the data is stored on disk, as +long as it can be retrieved and seeded. In that case a new storage class can be implemented +(inheriting from the storage_interface class) that avoids the unnecessary step of mapping +slots to files and offsets. The storage can ignore the file boundaries and just store the +entire torrent in a single file (which will end up being all the files concatenated). The main +advantage of this, other than a slight CPU performance gain, is that all file operations would +be page (and sector) aligned. This enables efficient unbuffered I/O, and can potentially +lead to more efficient read caching (using the built in disk cache rather than relying on the +operating system's disk cache).

    +

    The storage interface supports operating systems where you can ask for sparse regions +(such as Windows and Solaris). The advantage of this is that when checking files, the regions +that are known to be sparse can be skipped, which can reduce the time to check a torrent +significantly.

    +
    +
    +

    easy to use API

    +

    One of the design goals of the libtorrent API is to make common operations simple, but still +have it possible to do complicated and advanced operations. This is best illustrated by example +code to implement a simple bittorrent client:

    +
    +#include <iostream>
    +#include "libtorrent/session.hpp"
    +
    +// usage a.out [torrent-file]
    +int main(int argc, char* argv[]) try
    +{
    +        lt::session s;
    +        lt::add_torrent_params p;
    +        p.save_path = "./";
    +        p.ti = new torrent_info(argv[1]);
    +        s.add_torrent(p);
    +
    +        // wait for the user to end
    +        char a;
    +        std::cin.unsetf(std::ios_base::skipws);
    +        std::cin >> a;
    +        return 0;
    +}
    +catch (std::exception const& e)
    +{
    +        std::cerr << ec.what() << std::endl;
    +        return 1;
    +}
    +
    +

    This client doesn't give the user any status information or progress about the torrent, but +it is fully functional.

    +

    libtorrent also comes with python bindings for easy access for python developers.

    +
    +
    +
    +

    portability

    +

    libtorrent runs on most major operating systems, including Windows, +macOS, Linux, BSD and Solaris. +It uses Boost.Asio, Boost.Optional, Boost.System, Boost.Multiprecision, +Boost.Intrusive, Boost.Pool, Boost.Python (for bindings), Boost.CRC and various +other boost libraries. At least version 1.49 of boost is required.

    +

    Since libtorrent uses Boost.Asio it will take full advantage of high performance +network APIs on the most popular platforms. I/O completion ports on windows, +epoll on Linux and kqueue on macOS and BSD.

    +

    libtorrent does not build with the following compilers:

    +
      +
    • GCC 2.95.4
    • +
    • Visual Studio 6, 7.0, 7.1
    • +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 0000000..8f615f7 --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,336 @@ +================= +libtorrent manual +================= + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +introduction +============ + +libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a simple bittorrent client demonstrating the use of +the library. + +features +======== + +libtorrent is an ongoing project under active development. Its +current state supports and includes the following features: + +extensions +---------- + +* plugin interface for implementing custom bittorrent extensions + without having to modify libtorrent +* supports trackerless torrents (using the Mainline kademlia DHT protocol) with + some `DHT extensions`_. `BEP 5`_. +* supports the bittorrent `extension protocol`_. See extensions_. `BEP 10`_. +* supports the uTorrent metadata transfer protocol `BEP 9`_ (i.e. magnet links). +* supports the uTorrent peer exchange protocol (PEX). +* supports local peer discovery (multicast for peers on the same local network) +* multi-tracker extension support (supports both strict `BEP 12`_ and the + uTorrent interpretation). +* tracker scrapes +* supports lt_trackers extension, to exchange trackers between peers +* `HTTP seeding`_, as specified in `BEP 17`_ and `BEP 19`_. +* supports the UDP-tracker protocol. (`BEP 15`_). +* supports the ``no_peer_id=1`` extension that will ease the load off trackers. +* supports the ``compact=1`` tracker parameter. +* super seeding/initial seeding (`BEP 16`_). +* private torrents (`BEP 27`_). +* upload-only extension (`BEP 21`_). +* support for IPv6, including `BEP 7`_ and `BEP 24`_. +* support for merkle hash tree torrents. This makes the size of torrent files + scale well with the size of the content. +* share-mode. This is a special mode torrents can be put in to optimize share + ratio rather than downloading the torrent. +* supports the Magnet URI extension - Select specific file indices for + download. `BEP 53`_. + +.. _article: utp.html +.. _extensions: manual-ref.html#extensions +.. _`http seeding`: manual-ref.html#http-seeding + +disk management +--------------- + +* can use multiple disk I/O threads to not have the disk block network or + client interaction. +* supports verifying the SHA-1 hash of pieces in multiple threads, to take + advantage of multi core machines. +* supports files > 2 gigabytes. +* fast resume support, a way to avoid the costly piece check at the + start of a resumed torrent. Saves the storage state, piece_picker state + as well as all local peers in a fast-resume file. +* has an adjustable read and write disk cache for improved disk throughput. +* queues torrents for file check, instead of checking all of them in parallel. +* does not have any requirements on the piece order in a torrent that it + resumes. This means it can resume a torrent downloaded by any client. +* seed mode, where the files on disk are assumed to be complete, and each + piece's hash is verified the first time it is requested. +* implements an ARC disk cache, tuned for performing well under bittorrent work + loads + +network +------- + +* a high quality uTP implementation (`BEP 29`_). A transport protocol with + delay based congestion control. See separate article_. +* adjusts the length of the request queue depending on download rate. +* serves multiple torrents on a single port and in a single thread +* piece picking on block-level (as opposed to piece-level). + This means it can download parts of the same piece from different peers. + It will also prefer to download whole pieces from single peers if the + download speed is high enough from that particular peer. +* supports http proxies and basic proxy authentication +* supports gzip tracker-responses +* can limit the upload and download bandwidth usage and the maximum number of + unchoked peers +* possibility to limit the number of connections. +* delays have messages if there's no other outgoing traffic to the peer, and + doesn't send have messages to peers that already has the piece. This saves + bandwidth. +* selective downloading. The ability to select which parts of a torrent you + want to download. +* ip filter to disallow ip addresses and ip ranges from connecting and + being connected. +* NAT-PMP and UPnP support (automatic port mapping on routers that supports it) +* implements automatic upload slots, to optimize download rate without spreading + upload capacity too thin. The number of upload slots is adjusted based on the + peers' download capacity to work even for connections that are orders of + magnitude faster than others. + + +.. _`DHT extensions`: dht_extensions.html +.. _`BEP 5`: https://www.bittorrent.org/beps/bep_0005.html +.. _`BEP 7`: https://www.bittorrent.org/beps/bep_0007.html +.. _`BEP 9`: https://www.bittorrent.org/beps/bep_0009.html +.. _`BEP 10`: https://www.bittorrent.org/beps/bep_0010.html +.. _`BEP 12`: https://www.bittorrent.org/beps/bep_0012.html +.. _`BEP 15`: https://www.bittorrent.org/beps/bep_0015.html +.. _`BEP 16`: https://www.bittorrent.org/beps/bep_0016.html +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html +.. _`BEP 21`: https://www.bittorrent.org/beps/bep_0021.html +.. _`BEP 24`: https://www.bittorrent.org/beps/bep_0024.html +.. _`BEP 27`: https://www.bittorrent.org/beps/bep_0027.html +.. _`BEP 29`: https://www.bittorrent.org/beps/bep_0029.html +.. _`BEP 53`: https://www.bittorrent.org/beps/bep_0053.html +.. _`extension protocol`: extension_protocol.html + +highlighted features +==================== + +disk caching +------------ + +All disk I/O in libtorrent is done asynchronously to the network thread, by the +disk io threads. When a block is read, the disk io thread reads all subsequent +blocks from that piece into the read cache, assuming that the peer requesting +the block will also request more blocks from the same piece. This decreases the +number of system calls for reading data. It also decreases delay from seeking. + +Similarly, for write requests, blocks are cached and flushed to disk once one full +piece is complete or the piece is the least recently updated one when more cache +space is needed. The cache dynamically allocates space between the write and read +cache. The write cache is strictly prioritized over the read cache. + +The cache blocks that are in used, are locked into physical memory to avoid it +being paged out to disk. Allowing the disk cache to be paged out to disk means +that it would become extremely inefficient to flush it, since it would have to be +read back into physical memory only to be flushed back out to disk again. + +In order to conserve memory, and system calls, iovec file operations are +used to flush multiple cache blocks in a single call. + +On low-memory systems, the disk cache can be disabled altogether or set to smaller +limit, to save memory. + +The disk caching algorithm is configurable between *LRU* and *largest contiguous*. +The largest contiguous algorithm is the default and flushes the largest contiguous +block of buffers, instead of flushing all blocks belonging to the piece which was +written to least recently. + +network buffers +--------------- + +On CPUs with small L2 caches, copying memory can be expensive operations. It is important +to keep copying to a minimum on such machines. This mostly applies to embedded systems. + +In order to minimize the number of times received data is copied, the receive buffer +for payload data is received directly into a page aligned disk buffer. If the connection +is encrypted, the buffer is decrypted in-place. The buffer is then moved into the disk +cache without being copied. Once all the blocks for a piece have been received, or the +cache needs to be flushed, all the blocks are passed directly to ``writev()`` to flush +them in a single system call. This means a single copy into user space memory, and a single +copy back into kernel memory, as illustrated by this figure: + +.. image:: write_disk_buffers.png + :width: 100% + +When seeding and uploading in general, unnecessary copying is avoided by caching blocks +in aligned buffers, that are copied once into the peer's send buffer. The peer's send buffer +is not guaranteed to be aligned, even though it is most of the time. The send buffer is +then encrypted with the peer specific key and chained onto the ``iovec`` for sending. +This means there is one user space copy in order to allow unaligned peer requests and +peer-specific encryption. This is illustrated by the following figure: + +.. image:: read_disk_buffers.png + :width: 100% + + +piece picker +------------ + +The piece picker is a central component in a bittorrent implementation. The piece picker +in libtorrent is optimized for quickly finding the rarest pieces. It keeps a list of all +available pieces sorted by rarity, and pieces with the same rarity, shuffled. The rarest +first mode is the dominant piece picker mode. Other modes are supported as well, and +used by peers in specific situations. + +The piece picker allows to combine the availability of a piece with a priority. Together +they determine the sort order of the piece list. Pieces with priority 0 will never be +picked, which is used for the selective download feature. + +In order to have as few partially finished pieces as possible, peers have an affinity +towards picking blocks from the same pieces as other peers in the same speed category. +The speed category is a coarse categorization of peers based on their download rate. This +makes slow peers pick blocks from the same piece, and fast peers pick from the same piece, +and hence decreasing the likelihood of slow peers blocking the completion of pieces. + +The piece picker can also be set to download pieces in sequential order. + +share mode +---------- + +The share mode feature in libtorrent is intended for users who are only interested in +helping out swarms, not downloading the torrents. + +It works by predicting the demand for pieces, and only download pieces if there is enough +demand. New pieces will only be downloaded once the share ratio has hit a certain target. + +This feature is especially useful when combined with RSS, so that a client can be set up +to provide additional bandwidth to an entire feed. + +merkle hash tree torrents +------------------------- + +.. image:: merkle_tree.png + :align: right + +Merkle hash tree torrents is an extension that lets a torrent file only contain the +root hash of the hash tree forming the piece hashes. The main benefit of this feature +is that regardless of how many pieces there is in a torrent, the .torrent file will +always be the same size. It will only grow with the number of files (since it still +has to contain the file names). + +With regular torrents, clients have to request multiple blocks for pieces, typically +from different peers, before the data can be verified against the piece hash. The +larger the pieces are, the longer it will take to download a complete piece and verify +it. Before the piece is verified, it cannot be shared with the swarm, which means the +larger piece sizes, the slower turnaround data has when it is downloaded by peers. +Since on average the data has to sit around, waiting, in client buffers before it has +been verified and can be uploaded again. + +Another problem with large piece sizes is that it is harder for a client to pinpoint +the malicious or buggy peer when a piece fails, and it will take longer to re-download +it and take more tries before the piece succeeds the larger the pieces are. + +The piece size in regular torrents is a trade-off between the size of the .torrent file +itself and the piece size. Often, for files that are 4 GB, the piece size is 2 or 4 MB, +just to avoid making the .torrent file too big. + +Merkle torrents solves these problems by removing the trade-off between .torrent size and +piece size. With merkle torrents, the piece size can be the minimum block size (16 kB), +which lets peers verify every block of data received from peers, immediately. This +gives a minimum turnaround time and completely removes the problem of identifying malicious +peers. + +The root hash is built by hashing all the piece hashes pair-wise, until they all collapse +down to the root. + +customizable file storage +------------------------- + +.. image:: storage.png + :align: right + +libtorrent's storage implementation is customizable. That means a special purpose bittorrent +client can replace the default way to store files on disk. + +When implementing a bittorrent cache, it doesn't matter how the data is stored on disk, as +long as it can be retrieved and seeded. In that case a new storage class can be implemented +(inheriting from the ``storage_interface`` class) that avoids the unnecessary step of mapping +slots to files and offsets. The storage can ignore the file boundaries and just store the +entire torrent in a single file (which will end up being all the files concatenated). The main +advantage of this, other than a slight CPU performance gain, is that all file operations would +be page (and sector) aligned. This enables efficient unbuffered I/O, and can potentially +lead to more efficient read caching (using the built in disk cache rather than relying on the +operating system's disk cache). + +The storage interface supports operating systems where you can ask for sparse regions +(such as Windows and Solaris). The advantage of this is that when checking files, the regions +that are known to be sparse can be skipped, which can reduce the time to check a torrent +significantly. + +easy to use API +--------------- + +One of the design goals of the libtorrent API is to make common operations simple, but still +have it possible to do complicated and advanced operations. This is best illustrated by example +code to implement a simple bittorrent client:: + + #include + #include "libtorrent/session.hpp" + + // usage a.out [torrent-file] + int main(int argc, char* argv[]) try + { + lt::session s; + lt::add_torrent_params p; + p.save_path = "./"; + p.ti = new torrent_info(argv[1]); + s.add_torrent(p); + + // wait for the user to end + char a; + std::cin.unsetf(std::ios_base::skipws); + std::cin >> a; + return 0; + } + catch (std::exception const& e) + { + std::cerr << ec.what() << std::endl; + return 1; + } + +This client doesn't give the user any status information or progress about the torrent, but +it is fully functional. + +libtorrent also comes with python bindings for easy access for python developers. + + +portability +=========== + +libtorrent runs on most major operating systems, including Windows, +macOS, Linux, BSD and Solaris. +It uses Boost.Asio, Boost.Optional, Boost.System, Boost.Multiprecision, +Boost.Intrusive, Boost.Pool, Boost.Python (for bindings), Boost.CRC and various +other boost libraries. At least version 1.49 of boost is required. + +Since libtorrent uses Boost.Asio it will take full advantage of high performance +network APIs on the most popular platforms. I/O completion ports on windows, +epoll on Linux and kqueue on macOS and BSD. + +libtorrent does not build with the following compilers: + +* GCC 2.95.4 +* Visual Studio 6, 7.0, 7.1 + diff --git a/docs/fuzzing.html b/docs/fuzzing.html new file mode 100644 index 0000000..cab3d12 --- /dev/null +++ b/docs/fuzzing.html @@ -0,0 +1,154 @@ + + + + + + +fuzzing libtorrent + + + + + + + +
    +
    + + + + +
    +

    fuzzing libtorrent

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +
    +

    Table of contents

    + +
    +
    +

    overview

    +

    Libtorrent comes with a set of fuzzers. They are not included in the distribution +tar ball, instead download the repository snapshot or clone the repository.

    +

    The fuzzers can be found in the fuzzers subdirectory and come with a Jamfile +to build them, and a run.sh bash script to run them.

    +
    +

    building

    +

    The fuzzers use clang's libFuzzer, which means they can only be built with clang. +Clang must be configured in your user-config.jam, for example:

    +
    +using clang : 7 : clang++-7 ;
    +
    +

    When building, you most likely want to stage the resulting binaries into a +well known location. Invoke b2 like this:

    +
    +b2 clang stage -j$(nproc)
    +
    +

    This will build and stage all fuzzers into the fuzzers/fuzzers directory.

    +
    +
    +

    corpus

    +

    Fuzzers work best if they have a relevant seed corpus of example inputs. You +can either generate one using fuzzers/tools/generate_initial_corpus.py or download +the corpus.zip from the github releases page.

    +

    To run the script to generate initial corpus, run it with fuzzers as the +current working directory, like this:

    +
    +python tools/generate_initial_corpus.py
    +
    +

    The corpus should be placed in the fuzzers directory, which should also be the +current working directory when invoking the fuzzer binaries.

    +
    +
    +

    running fuzzers

    +

    The run.sh script will run all fuzzers in parallel for 48 hours. It can easily +be tweaked and mostly serve as an example of how to invoke them.

    +
    +
    +

    large and small fuzzers

    +

    Since APIs can have different complexity, fuzz targets will also explore +code of varying complexity. Some fuzzers cover a very small amount of code +(e.g. parse_int) where other fuzz targets cover very large amount of code and +can potentially go very deep into call stacks (e.g. torrent_info).

    +

    Small fuzz targets can fairly quickly exhaust all possible code paths and have +quite limited utility after that, other than as regression tests. When putting +a lot of CPU into long running fuzzing, it is better spent on large fuzz targets.

    +

    For this reason, there's another alias in the Jamfile to only build and stage +large fuzz targets. Call b2 like this:

    +
    +b2 clang stage-large -j$(nproc)
    +
    +
    +
    +

    fast+slow

    +

    When building an initial corpus, it can be useful to quickly build a corpus with +a large code coverage. To speed up this process, you can build the fuzzers +without sanitizers, asserts and invariant checks. This won't find as many errors, +but build a good corpus which can then be run against a fully instrumented +fuzzer.

    +

    To build the fuzzers in this "fast" mode, there's a build variant build_coverage. +Invoke b2 like this:

    +
    +b2 clang stage build_coverage -j$(nproc)
    +
    +

    For more details on "fast + slow" see Paul Dreik's talk.

    +
    +
    +

    sharing corpora

    +

    Before sharing your fuzz corpus, it should be minimized. There is a script +called minimize.sh which moves corpus to prev-corpus and copies over +a minimized set of inputs to a new corpus directory.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/fuzzing.rst b/docs/fuzzing.rst new file mode 100644 index 0000000..4c08d8d --- /dev/null +++ b/docs/fuzzing.rst @@ -0,0 +1,102 @@ +================== +fuzzing libtorrent +================== + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 1 + :backlinks: none + +overview +======== + +Libtorrent comes with a set of fuzzers. They are not included in the distribution +tar ball, instead download the `repository snapshot`_ or clone the repository_. + +The fuzzers can be found in the `fuzzers` subdirectory and come with a `Jamfile` +to build them, and a `run.sh` bash script to run them. + +.. _`repository snapshot`: https://github.com/arvidn/libtorrent/releases +.. _repository: https://github.com/arvidn/libtorrent + +building +-------- + +The fuzzers use clang's libFuzzer, which means they can only be built with clang. +Clang must be configured in your `user-config.jam`, for example:: + + using clang : 7 : clang++-7 ; + +When building, you most likely want to stage the resulting binaries into a +well known location. Invoke `b2` like this:: + + b2 clang stage -j$(nproc) + +This will build and stage all fuzzers into the `fuzzers/fuzzers` directory. + +corpus +------ + +Fuzzers work best if they have a relevant seed corpus of example inputs. You +can either generate one using `fuzzers/tools/generate_initial_corpus.py` or download +the `corpus.zip` from the github `releases page`_. + +To run the script to generate initial corpus, run it with `fuzzers` as the +current working directory, like this:: + + python tools/generate_initial_corpus.py + +The corpus should be placed in the `fuzzers` directory, which should also be the +current working directory when invoking the fuzzer binaries. + +.. _`releases page`: https://github.com/arvidn/libtorrent/releases + +running fuzzers +--------------- + +The `run.sh` script will run all fuzzers in parallel for 48 hours. It can easily +be tweaked and mostly serve as an example of how to invoke them. + +large and small fuzzers +----------------------- + +Since APIs can have different complexity, fuzz targets will also explore +code of varying complexity. Some fuzzers cover a very small amount of code +(e.g. `parse_int`) where other fuzz targets cover very large amount of code and +can potentially go very deep into call stacks (e.g. `torrent_info`). + +Small fuzz targets can fairly quickly exhaust all possible code paths and have +quite limited utility after that, other than as regression tests. When putting +a lot of CPU into long running fuzzing, it is better spent on large fuzz targets. + +For this reason, there's another alias in the `Jamfile` to only build and stage +large fuzz targets. Call `b2` like this:: + + b2 clang stage-large -j$(nproc) + +fast+slow +--------- + +When building an initial corpus, it can be useful to quickly build a corpus with +a large code coverage. To speed up this process, you can build the fuzzers +without sanitizers, asserts and invariant checks. This won't find as many errors, +but build a good corpus which can then be run against a fully instrumented +fuzzer. + +To build the fuzzers in this "fast" mode, there's a build variant `build_coverage`. +Invoke `b2` like this:: + + b2 clang stage build_coverage -j$(nproc) + +For more details on "fast + slow" see `Paul Dreik's talk`_. + +.. _`Paul Dreik's talk`: https://youtu.be/e_Oc9SkCo5s?t=1679 + +sharing corpora +--------------- + +Before sharing your fuzz corpus, it should be minimized. There is a script +called `minimize.sh` which moves `corpus` to `prev-corpus` and copies over +a minimized set of inputs to a new `corpus` directory. + diff --git a/docs/hacking.diagram b/docs/hacking.diagram new file mode 100644 index 0000000..d9604ef --- /dev/null +++ b/docs/hacking.diagram @@ -0,0 +1,30 @@ ++--------------+ "pimpl" +----------------+ +| "session" +--------------------------->| "session_impl" | ++--------------+ +------+-----+---+ + "m_torrents[]" | | ++---------------------+ | | +| "torrent_handle" +--------+ | | ++---------------------+ "weak" | +--------------+ | + | | | "m_connections[]" + | | +-------------+ +--+ + | | | | | + "m_picker" v v | v v "peers we are connected to" + +----------------+ +----------++ +-------------------+ + | "piece_picker" |<---+-+ "torrent" ++ +--+ "peer_connection" ++ + +----------------+ | ++----------+| | ++------------------+| + "m_torrent_file" | +-----------+ | +-------------------+ + +-------------------+ | | + | "torrent_info" |<---+ | "m_socket" + +-------------------+ | | +----------------------------+ + | +->| "socket_type (variant)" | + "m_peer_list" v | | "(TCP/uTP/SSL/socks5/...)" | + +--------------+ | +----------------------------+ + | "peer_list" | | + +------------+-+ | "m_peer_info" + "list of all" | "m_peers[]" | "contains contact information" + "peers we" | | "for peers we're not necessarily" + "know of" | v "connected to" + | +----------------+ + +---->| "torrent_peer" ++ + ++---------------+| + +----------------+ diff --git a/docs/hacking.html b/docs/hacking.html new file mode 100644 index 0000000..1c3e09e --- /dev/null +++ b/docs/hacking.html @@ -0,0 +1,241 @@ + + + + + + +libtorrent hacking + + + + + + + +
    +
    + + + + +
    +

    libtorrent hacking

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +

    This describe some of the internals of libtorrent. If you're looking for +something to contribute, please take a look at the todo list.

    +
    +

    terminology

    +

    This section describes some of the terminology used throughout the +libtorrent source. Having a good understanding of some of these keywords +helps understanding what's going on.

    +

    A piece is a part of the data of a torrent that has a SHA-1 hash in +the .torrent file. Pieces are almost always a power of two in size, but not +necessarily. Each piece is split up in blocks, which is a 16 kiB. A block +never spans two pieces. If a piece is smaller than 16 kiB or not divisible +by 16 kiB, there are blocks smaller than that.

    +

    16 kiB is a de-facto standard of the largest transfer unit in the bittorrent +protocol. Clients typically reject any request for larger pieces than this.

    +

    The piece picker is the part of a bittorrent client that is responsible for +the logic to determine which requests to send to peers. It doesn't actually +pick full pieces, but blocks (from pieces).

    +

    The file layout of a torrent is represented by file storage objects. This +class contains a list of all files in the torrent (in a well defined order), +the size of the pieces and implicitly the total size of the whole torrent and +number of pieces. The file storage determines the mapping from pieces +to files. This representation may be quite complex in order to keep it extremely +compact. This is useful to load very large torrents without exploding in memory +usage.

    +

    A torrent object represents all the state of swarm download. This includes +a piece picker, a list of peer connections, file storage (torrent file). One +important distinction is between a connected peer (peer_connection) and a peer +we just know about, and may have been connected to, and may connect to in the +future (torrent_peer). The list of (not connected) peers may grow very large +if not limited (through tracker responses, DHT and peer exchange). This list +is typically limited to a few thousand peers.

    +

    The peer_list maintains a potentially large list of known peers for a swarm +(not necessarily connected).

    +
    +
    +

    structure

    +

    This is the high level structure of libtorrent. Bold types are part of the public +interface:

    +hacking.png +
    +

    session_impl

    +

    This is the session state object, containing all session global information, such as:

    +
    +
      +
    • the list of all torrents m_torrent.
    • +
    • the list of all peer connections m_connections.
    • +
    • the global rate limits m_settings.
    • +
    • the DHT state m_dht.
    • +
    • the port mapping state, m_upnp and m_natpmp.
    • +
    +
    +
    +
    +

    session

    +

    This is the public interface to the session. It implements pimpl (pointer to implementation) +in order to hide the internal representation of the session_impl object from the user and +make binary compatibility simpler to maintain.

    +
    +
    +

    torrent_handle

    +

    This is the public interface to a torrent. It holds a weak reference to the internal +torrent object and manipulates it by sending messages to the network thread.

    +
    +
    +

    torrent

    +
    +
    +

    peer_connection

    +
    +
    +

    peer_list

    +
    +
    +

    piece_picker

    +
    +
    +

    torrent_info

    +
    +
    +
    +

    threads

    +

    libtorrent starts 3 to 5 threads.

    +
    +
      +
    • The first thread is the main thread that will sit +idle in a select() call most of the time. This thread runs the main loop +that will send and receive data on all connections. In reality it's typically +not actually in select(), but in kqueue(), epoll_wait() or poll, +depending on operating system.
    • +
    • The second thread is the disk I/O thread. All disk read and write operations +are passed to this thread and messages are passed back to the main thread when +the operation completes.
    • +
    • The third thread is the SHA-1 hash thread. By default there's only one hash thread, +but on multi-core machines downloading at very high rates, libtorrent can be configured +to start any number of hashing threads, to take full use of multi core systems. +(see settings_pack::aio_threads).
    • +
    • The fourth and fifth threads are spawned by asio on systems that don't support +asynchronous host name resolution, in order to simulate non-blocking getaddrinfo().
    • +
    +
    +
    +
    +

    disk cache

    +

    The disk cache implements ARC, Adaptive Replacement Cache. This consists of a number of LRUs:

    +
      +
    1. LRU L1 (recently used)
    2. +
    3. LRU L1 ghost (recently evicted)
    4. +
    5. LRU L2 (frequently used)
    6. +
    7. LRU L2 ghost (recently evicted)
    8. +
    9. volatile read blocks
    10. +
    11. write cache (blocks waiting to be flushed to disk)
    12. +
    +disk_cache.png +

    These LRUs are stored in block_cache in an array m_lru.

    +

    The cache algorithm works like this:

    +
    +if (L1->is_hit(piece)) {
    +        L1->erase(piece);
    +        L2->push_back(piece);
    +} else if (L2->is_hit(piece)) {
    +        L2->erase(piece);
    +        L2->push_back(page);
    +} else if (L1->size() == cache_size) {
    +        L1->pop_front();
    +        L1->push_back(piece);
    +} else {
    +        if (L1->size() + L2->size() == 2*chache_size) {
    +                L2->pop_front();
    +        }
    +        L1->push_back(piece);
    +}
    +
    +

    It's a bit more complicated since within L1 and L2 in this pseudo code +have to separate the ghost entries and the in-cache entries.

    +

    Note that the most recently used and more frequently used pieces are at +the back of the lists. Iterating over a list gives you low priority pieces +first.

    +

    In libtorrent pieces are cached, not individual blocks, a single peer would +typically trigger many cache hits when downloading a piece. Since ARC is +sensitive to extra cache hits (a piece is moved to L2 the second time it's +hit) libtorrent only move the cache entry on cache hits when it's hit by +another peer than the last peer that hit it.

    +

    Another difference compared to the ARC paper is that libtorrent caches pieces, +which aren't necessarily fully allocated. This means the real cache size is +specified in number of blocks, not pieces, so there's not clear number of pieces +to keep in the ghost lists. There's an m_num_arc_pieces member in block_cache +that defines the arc cache size, in pieces, rather than blocks.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/hacking.png b/docs/hacking.png new file mode 100644 index 0000000000000000000000000000000000000000..dc31245d76982124b87bcdb0bafe69054ef57e14 GIT binary patch literal 15692 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 JS5o?S{vVh;j+g)d literal 0 HcmV?d00001 diff --git a/docs/hash_distribution.png b/docs/hash_distribution.png new file mode 100644 index 0000000000000000000000000000000000000000..7bc1be79e5adb6fd1c53182f67b2e971573404e7 GIT binary patch literal 9035 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 literal 0 HcmV?d00001 diff --git a/docs/header.rst b/docs/header.rst new file mode 100644 index 0000000..e8c19b2 --- /dev/null +++ b/docs/header.rst @@ -0,0 +1,3 @@ +:Author: Arvid Norberg, arvid@libtorrent.org +:Version: 1.2.9 + diff --git a/docs/img/bg.png b/docs/img/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..6d62292a13109a5b99abc5344317729d369d249e GIT binary patch literal 2773 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 BK-vHR literal 0 HcmV?d00001 diff --git a/docs/img/blue_top.png b/docs/img/blue_top.png new file mode 100644 index 0000000000000000000000000000000000000000..bcad3a2448089d4d9e2cafa4b3dc9f8fe5da7eb8 GIT binary patch literal 2953 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 literal 0 HcmV?d00001 diff --git a/docs/img/dotline.gif b/docs/img/dotline.gif new file mode 100644 index 0000000000000000000000000000000000000000..6bd546f861a448a670ebbe75a63283f9e4a1450e GIT binary patch literal 181 zcmV;m080NyNk%w1VG01q0FeLy00030|Nq(9*#H0lEC2ui015!e0007xj3cL(d+nlv zjxH-}#xHx$Y@R5A$XJq*`KcQEt}Yy=Hr?GZe(D#Wllf?-BCsY*@{B_mQ;B=-qO-}d z2elrhzilG`Nvh(hRS2#L7qQ2+otB$8KN literal 0 HcmV?d00001 diff --git a/docs/img/minus.gif b/docs/img/minus.gif new file mode 100644 index 0000000000000000000000000000000000000000..d5fb96e8c15fcad8e2e09d2c756bf50ad886452f GIT binary patch literal 262 zcmZ?wbhEHbH(18O7_V3@nckkXEJ9cc_wr%Uyty{Ki*|>4z zx^?T;u3fuo)v6UMRxDYvWYMBUbLY;Di;DwWMSVgCWF*Kh3~bs58ZtaYI9XemnVk;u lu(P!~C*9=X=3I2l*u_L?st>zKjE$Q3IrS7BA!bJgYXA<*TwwqJ literal 0 HcmV?d00001 diff --git a/docs/img/orange.png b/docs/img/orange.png new file mode 100644 index 0000000000000000000000000000000000000000..40a5ea3864d1f4c71d8d584c0260735f18c7de1c GIT binary patch literal 24833 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 literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..b3196fc --- /dev/null +++ b/docs/index.html @@ -0,0 +1,184 @@ + + + + + + +index.rst + + + + + + +
    +
    + + + + +
    + + + +

    libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a simple bittorrent client demonstrating the use of +the library.

    +
    +screenshot_thumb.png +

    client_test example program

    +
    +

    The main goals of libtorrent are:

    +
      +
    • to be CPU efficient
    • +
    • to be memory efficient
    • +
    • to be very easy to use
    • +
    +
    +

    Getting started

    +

    The tutorial is an introduction to using libtorrent (C++). Also see the +reference documentation.

    +
    +
    +

    Contribute

    +

    If your organization uses libtorrent, please consider supporting its development. +See the contribute page for other ways to help out.

    + +
    +bitcoin:373ZDeQgQSQNuxdinNAPnQ63CRNn4iEXzg +
    + + + + + + + + +
    + + + + + + + + +
    +
    +
    +
    +

    Support

    +

    Please direct questions to the mailing list, general libtorrent discussion.

    +

    You can usually find me as hydri in #libtorrent on irc.freenode.net.

    +
    +
    +

    license

    +

    libtorrent is released under the BSD-license.

    +

    This means that you can use the library in your project without having to +release its source code. The only requirement is that you give credit +to the author of the library by including the libtorrent license in your +software or documentation.

    +

    It is however greatly appreciated if additional features are contributed +back to the open source project. Patches can be emailed to the mailing +list or posted to the bug tracker.

    +
    +
    +

    Acknowledgements

    +

    Written by Arvid Norberg. Copyright © 2003-2018

    +

    Contributions by Steven Siloti, Alden Torres, Magnus Jonsson, Daniel Wallin and Cory Nelson

    +

    Thanks to Reimond Retz for bug fixes, suggestions and testing

    +

    See github for full list of contributors.

    +

    Thanks to Umeå University for providing development and test hardware.

    +

    Project is hosted by github.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..5278c9c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,208 @@ +.. raw:: html + +
    + +* download_ +* features_ +* tutorial_ +* examples_ +* overview_ +* documentation_ +* `libtorrent 1.2`_ +* contributing_ +* building_ +* troubleshooting_ +* `tuning`_ +* fuzzing_ +* `mailing list`_ (archive_) +* `projects using libtorrent`_ +* `report bugs`_ +* `github page`_ +* `blog`_ + +-------- + +Extensions + +* `uTP`_ +* `extensions protocol`_ +* `plugin interface`_ +* `streaming`_ +* `DHT extensions`_ +* `DHT security extension`_ +* `DHT store extension`_ +* `UDP tracker protocol`_ +* `HTTP seed`_ +* multi-tracker_ + +-------- + +Bindings + +* python_ +* Java_ +* go_ +* node_ + +-------- + +* `Introduction, slides`_ + +.. raw:: html + +
    +
    + +.. _download: https://github.com/arvidn/libtorrent/releases +.. _features: features.html +.. _tutorial: tutorial.html +.. _contributing: contributing.html +.. _building: building.html +.. _examples: examples.html +.. _overview: manual-ref.html +.. _documentation: reference.html +.. _`libtorrent 1.2`: upgrade_to_1.2-ref.html +.. _troubleshooting: troubleshooting.html +.. _`tuning`: tuning-ref.html +.. _fuzzing: fuzzing.html +.. _`uTP`: utp.html +.. _`extensions protocol`: extension_protocol.html +.. _`plugin interface`: reference-Plugins.html +.. _`streaming`: streaming.html +.. _`DHT extensions`: dht_extensions.html +.. _`DHT security extension`: dht_sec.html +.. _`DHT store extension`: dht_store.html +.. _`UDP tracker protocol`: udp_tracker_protocol.html +.. _`HTTP seed`: http://www.getright.com/seedtorrent.html +.. _multi-tracker: https://www.bittorrent.org/beps/bep_0012.html +.. _mailing list: https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss +.. _archive: https://sourceforge.net/p/libtorrent/mailman/libtorrent-discuss/ +.. _`projects using libtorrent`: projects.html +.. _`report bugs`: https://github.com/arvidn/libtorrent/issues +.. _`github page`: https://github.com/arvidn/libtorrent +.. _blog: https://blog.libtorrent.org + +.. _Java: https://github.com/frostwire/frostwire-jlibtorrent/ +.. _python: python_binding.html +.. _go: https://github.com/steeve/libtorrent-go +.. _node: https://github.com/fanatid/node-libtorrent + +.. _`Introduction, slides`: bittorrent.pdf + +libtorrent is a feature complete C++ bittorrent implementation focusing +on efficiency and scalability. It runs on embedded devices as well as +desktops. It boasts a well documented library interface that is easy to +use. It comes with a `simple bittorrent client`__ demonstrating the use of +the library. + +.. figure:: screenshot_thumb.png + :target: client_test.html + :figclass: align-right + + ``client_test`` example program + +__ client_test.html + +The main goals of libtorrent are: + +* to be CPU efficient +* to be memory efficient +* to be very easy to use + +Getting started +=============== + +The tutorial_ is an introduction to using libtorrent (C++). Also see the +`reference documentation`_. + +.. _`reference documentation`: reference.html + +Contribute +========== + +If your organization uses libtorrent, please consider supporting its development. +See the contribute_ page for other ways to help out. + +.. raw:: html + + +
    + bitcoin:373ZDeQgQSQNuxdinNAPnQ63CRNn4iEXzg +
    + + + + + + + + +
    + + + + + + + + +
    +
    +
    + + +Support +======= + +Please direct questions to the `mailing list`__, general libtorrent discussion. + +__ https://lists.sourceforge.net/lists/listinfo/libtorrent-discuss + +You can usually find me as hydri in ``#libtorrent`` on ``irc.freenode.net``. + +license +======= + +libtorrent is released under the BSD-license_. + +.. _BSD-license: https://opensource.org/licenses/bsd-license.php + +This means that you can use the library in your project without having to +release its source code. The only requirement is that you give credit +to the author of the library by including the libtorrent license in your +software or documentation. + +It is however greatly appreciated if additional features are contributed +back to the open source project. Patches can be emailed to the mailing +list or posted to the `bug tracker`_. + +.. _`bug tracker`: https://github.com/arvidn/libtorrent/issues + +Acknowledgements +================ + +Written by Arvid Norberg. Copyright |copy| 2003-2018 + +Contributions by Steven Siloti, Alden Torres, Magnus Jonsson, Daniel Wallin and Cory Nelson + +Thanks to Reimond Retz for bug fixes, suggestions and testing + +See github__ for full list of contributors. + +__ https://github.com/arvidn/libtorrent/graphs/contributors + +Thanks to `Umeå University`__ for providing development and test hardware. + +__ http://www.cs.umu.se + +Project is hosted by github__. + +__ https://www.github.com/arvidn/libtorrent + +.. |copy| unicode:: 0xA9 .. copyright sign + +.. raw:: html + +
    + diff --git a/docs/ip_id_v4.png b/docs/ip_id_v4.png new file mode 100644 index 0000000000000000000000000000000000000000..55d02390e9b2965af7d83816bb50bea2e778236f GIT binary patch literal 5825 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 D8#_6^ literal 0 HcmV?d00001 diff --git a/docs/manual-ref.html b/docs/manual-ref.html new file mode 100644 index 0000000..d0f2f98 --- /dev/null +++ b/docs/manual-ref.html @@ -0,0 +1,3194 @@ + + + + + + +manual-ref.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +
    +

    libtorrent API Documentation

    +
    +

    Table of contents

    + +
    +
    +

    overview

    +

    The interface of libtorrent consists of a few classes. The main class is +the session, it contains the main loop that serves all torrents.

    +

    The basic usage is as follows:

    + +

    Each class and function is described in this manual, you may want to have a +look at the tutorial as well.

    +

    For a description on how to create torrent files, see create_torrent.

    +
    +
    +

    forward declarations

    +

    Forward declaring types from the libtorrent namespace is discouraged as it may +break in future releases. Instead include libtorrent/fwd.hpp for forward +declarations of all public types in libtorrent.

    +
    +
    +

    trouble shooting

    +

    A common problem developers are facing is torrents stopping without explanation. +Here is a description on which conditions libtorrent will stop your torrents, +how to find out about it and what to do about it.

    +

    Make sure to keep track of the paused state, the error state and the upload +mode of your torrents. By default, torrents are auto-managed, which means +libtorrent will pause, resume, scrape them and take them out +of upload-mode automatically.

    +

    Whenever a torrent encounters a fatal error, it will be stopped, and the +torrent_status::error will describe the error that caused it. If a torrent +is auto managed, it is scraped periodically and paused or resumed based on +the number of downloaders per seed. This will effectively seed torrents that +are in the greatest need of seeds.

    +

    If a torrent hits a disk write error, it will be put into upload mode. This +means it will not download anything, but only upload. The assumption is that +the write error is caused by a full disk or write permission errors. If the +torrent is auto-managed, it will periodically be taken out of the upload +mode, trying to write things to the disk again. This means torrent will recover +from certain disk errors if the problem is resolved. If the torrent is not +auto managed, you have to call set_upload_mode() to turn +downloading back on again.

    +

    For a more detailed guide on how to trouble shoot performance issues, see +troubleshooting

    +
    +
    +

    ABI considerations

    +

    libtorrent maintains a stable ABI for versions with the same major and minor versions.

    +

    e.g. libtorrent-1.2.0 is ABI compatible with libtorrent-1.2.1 but not with libtorrent-1.1

    +
    +
    +

    network primitives

    +

    There are a few typedefs in the libtorrent namespace which pulls +in network types from the boost::asio namespace. These are:

    +
    +using address = boost::asio::ip::address;
    +using address_v4 = boost::asio::ip::address_v4;
    +using address_v6 = boost::asio::ip::address_v6;
    +using boost::asio::ip::tcp;
    +using boost::asio::ip::udp;
    +
    +

    These are declared in the <libtorrent/socket.hpp> header.

    +

    The using statements will give easy access to:

    +
    +tcp::endpoint
    +udp::endpoint
    +
    +

    Which are the endpoint types used in libtorrent. An endpoint is an address +with an associated port.

    +

    For documentation on these types, please refer to the asio documentation.

    +
    +
    +

    exceptions

    +

    Many functions in libtorrent have two versions, one that throws exceptions on +errors and one that takes an error_code reference which is filled with the +error code on errors.

    +

    On exceptions, libtorrent will throw boost::system::system_error exceptions +carrying an error_code describing the underlying error.

    +
    +

    translating error codes

    +

    The error_code::message() function will typically return a localized error string, +for system errors. That is, errors that belong to the generic or system category.

    +

    Errors that belong to the libtorrent error category are not localized however, they +are only available in English. In order to translate libtorrent errors, compare the +error category of the error_code object against lt::libtorrent_category(), +and if matches, you know the error code refers to the list above. You can provide +your own mapping from error code to string, which is localized. In this case, you +cannot rely on error_code::message() to generate your strings.

    +

    The numeric values of the errors are part of the API and will stay the same, although +new error codes may be appended at the end.

    +

    Here's a simple example of how to translate error codes:

    +
    +std::string error_code_to_string(boost::system::error_code const& ec)
    +{
    +        if (ec.category() != lt::libtorrent_category())
    +        {
    +                return ec.message();
    +        }
    +        // the error is a libtorrent error
    +
    +        int code = ec.value();
    +        static const char const* swedish[] =
    +        {
    +                "inget fel",
    +                "en fil i torrenten kolliderar med en fil fran en annan torrent",
    +                "hash check misslyckades",
    +                "torrentfilen ar inte en dictionary",
    +                "'info'-nyckeln saknas eller ar korrupt i torrentfilen",
    +                "'info'-faltet ar inte en dictionary",
    +                "'piece length' faltet saknas eller ar korrupt i torrentfilen",
    +                "torrentfilen saknar namnfaltet",
    +                "ogiltigt namn i torrentfilen (kan vara en attack)",
    +                // ... more strings here
    +        };
    +
    +        // use the default error string in case we don't have it
    +        // in our translated list
    +        if (code < 0 || code >= sizeof(swedish)/sizeof(swedish[0]))
    +                return ec.message();
    +
    +        return swedish[code];
    +}
    +
    +
    +
    + +
    +

    queuing

    +

    libtorrent supports queuing. Queuing is a mechanism to automatically pause and +resume torrents based on certain criteria. The criteria depends on the overall +state the torrent is in (checking, downloading or seeding).

    +

    To opt-out of the queuing logic, make sure your torrents are added with the +torrent_flags::auto_managed bit cleared from add_torrent_params::flags. +Or call torrent_handle::unset_flags() and pass in torrent_flags::auto_managed on +the torrent handle.

    +

    The overall purpose of the queuing logic is to improve performance under arbitrary +torrent downloading and seeding load. For example, if you want to download 100 +torrents on a limited home connection, you improve performance by downloading +them one at a time (or maybe two at a time), over downloading them all in +parallel. The benefits are:

    +
      +
    • the average completion time of a torrent is half of what it would be if all +downloaded in parallel.
    • +
    • The amount of upload capacity is more likely to reach the reciprocation rate +of your peers, and is likely to improve your return on investment (download +to upload ratio)
    • +
    • your disk I/O load is likely to be more local which may improve I/O +performance and decrease fragmentation.
    • +
    +

    There are fundamentally 3 separate queues:

    +
      +
    • checking torrents
    • +
    • downloading torrents
    • +
    • seeding torrents
    • +
    +

    Every torrent that is not seeding has a queue number associated with it, this is +its place in line to be started. See torrent_status::queue_position.

    +

    On top of the limits of each queue, there is an over arching limit, set in +settings_pack::active_limit. The auto manager will never start more than this +number of torrents (with one exception described below). Non-auto-managed +torrents are exempt from this logic, and not counted.

    +

    At a regular interval, torrents are checked if there needs to be any +re-ordering of which torrents are active and which are queued. This interval +can be controlled via settings_pack::auto_manage_interval.

    +

    For queuing to work, resume data needs to be saved and restored for all +torrents. See torrent_handle::save_resume_data().

    +
    +

    queue position

    +

    The torrents in the front of the queue are started and the rest are ordered by +their queue position. Any newly added torrent is placed at the end of the queue. +Once a torrent is removed or turns into a seed, its queue position is -1 and all +torrents that used to be after it in the queue, decreases their position in +order to fill the gap.

    +

    The queue positions are always contiguous, in a sequence without any gaps.

    +

    Lower queue position means closer to the front of the queue, and will be +started sooner than torrents with higher queue positions.

    +

    To query a torrent for its position in the queue, or change its position, see: +torrent_handle::queue_position(), torrent_handle::queue_position_up(), +torrent_handle::queue_position_down(), torrent_handle::queue_position_top() +and torrent_handle::queue_position_bottom().

    +
    +
    +

    checking queue

    +

    The checking queue affects torrents in the torrent_status::checking or +torrent_status::allocating state that are auto-managed.

    +

    The checking queue will make sure that (of the torrents in its queue) no more than +settings_pack::active_checking_limit torrents are started at any given time. +Once a torrent completes checking and moves into a different state, the next in +line will be started for checking.

    +

    Any torrent added force-started or force-stopped (i.e. the auto managed flag is +not set), will not be subject to this limit and they will all check +independently and in parallel.

    +

    Once a torrent completes the checking of its files, or resume data, it will +be put in the queue for downloading and potentially start downloading immediately. +In order to add a torrent and check its files without starting the download, it +can be added in stop_when_ready mode. +See add_torrent_params::flag_stop_when_ready. This flag will stop the torrent +once it is ready to start downloading.

    +

    This is conceptually the same as waiting for the torrent_checked_alert and +then call:

    +
    +h.set_flags(torrent_flags::paused, torrent_flags::paused | torrent_flags::auto_managed);
    +
    +

    With the important distinction that it entirely avoids the brief window where +the torrent is in downloading state.

    +
    +
    +

    downloading queue

    +

    Similarly to the checking queue, the downloading queue will make sure that no +more than settings_pack::active_downloads torrents are in the downloading +state at any given time.

    +

    The torrent_status::queue_position is used again here to determine who is next +in line to be started once a downloading torrent completes or is stopped/removed.

    +
    +
    +

    seeding queue

    +

    The seeding queue does not use torrent_status::queue_position to determine which +torrent to seed. Instead, it estimates the demand for the torrent to be +seeded. A torrent with few other seeds and many downloaders is assumed to have a +higher demand of more seeds than one with many seeds and few downloaders.

    +

    It limits the number of started seeds to settings_pack::active_seeds.

    +

    On top of this basic bias, seed priority can be controller by specifying a +seed ratio (the upload to download ratio), a seed-time ratio (the download +time to seeding time ratio) and a seed-time (the absolute time to be seeding a +torrent). Until all those targets are hit, the torrent will be prioritized for +seeding.

    +

    Among torrents that have met their seed target, torrents where we don't know of +any other seed take strict priority.

    +

    In order to avoid flapping, torrents that were started less than 30 minutes ago +also have priority to keep seeding.

    +

    Finally, for torrents where none of the above apply, they are prioritized based +on the download to seed ratio.

    +

    The relevant settings to control these limits are +settings_pack::share_ratio_limit, settings_pack::seed_time_ratio_limit and +settings_pack::seed_time_limit.

    +
    +
    +

    queuing options

    +

    In addition to simply starting and stopping torrents, the queuing mechanism can +have more fine grained control of the resources used by torrents.

    +
    +

    half-started torrents

    +

    In addition to the downloading and seeding limits, there are limits on actions +torrents perform. The downloading and seeding limits control whether peers are +allowed at all, and if peers are not allowed, torrents are stopped and don't do +anything. If peers are allowed, torrents may:

    +
      +
    1. announce to trackers
    2. +
    3. announce to the DHT
    4. +
    5. announce to local peer discovery (local service discovery)
    6. +
    +

    Each of those actions are associated with a cost and hence may need a separate +limit. These limits are controlled by settings_pack::active_tracker_limit, +settings_pack::active_dht_limit and settings_pack::active_lsd_limit +respectively.

    +

    Specifically, announcing to a tracker is typically cheaper than +announcing to the DHT. settings_pack::active_dht_limit will limit the number of +torrents that are allowed to announce to the DHT. The highest priority ones +will, and the lower priority ones won't. The will still be considered started +though, and any incoming peers will still be accepted.

    +

    If you do not wish to impose such limits (basically, if you do not wish to have +half-started torrents) make sure to set these limits to -1 (infinite).

    +
    +
    +

    prefer seeds

    +

    In the case where active_downloads + active_seeds > active_limit, +there's an ambiguity whether the downloads should be satisfied first or the +seeds. To disambiguate this case, the settings_pack::auto_manage_prefer_seeds +determines whether seeds are preferred or not.

    +
    +
    +

    inactive torrents

    +

    Torrents that are not transferring any bytes (downloading or uploading) have a +relatively low cost to be started. It's possible to exempt such torrents from +the download and seed queues by setting settings_pack::dont_count_slow_torrents +to true.

    +

    Since it sometimes may take a few minutes for a newly started torrent to find +peers and be unchoked, or find peers that are interested in requesting data, +torrents are not considered inactive immediately. There must be an extended +period of no transfers before it is considered inactive and exempt from the +queuing limits.

    +
    +
    +
    +
    +

    fast resume

    +

    The fast resume mechanism is a way to remember which pieces are downloaded +and where they are put between sessions. You can generate fast resume data by +calling save_resume_data() on torrent_handle. You can +then save this data to disk and use it when resuming the torrent. libtorrent +will not check the piece hashes then, and rely on the information given in the +fast-resume data. The fast-resume data also contains information about which +blocks, in the unfinished pieces, were downloaded, so it will not have to +start from scratch on the partially downloaded pieces.

    +

    To use the fast-resume data you pass it to read_resume_data(), which will return +an add_torrent_params object. Fields of this object can then be altered before +passing it to async_add_torrent() or add_torrent(). +The session will then skip the time consuming checks. It may have to do +the checking anyway, if the fast-resume data is corrupt or doesn't fit the +storage for that torrent.

    +
    +

    file format

    +

    The file format is a bencoded dictionary containing the following fields:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    file-formatstring: "libtorrent resume file"
    info-hashstring, the info hash of the torrent this data is saved for.
    piecesA string with piece flags, one character per piece. +Bit 1 means we have that piece. +Bit 2 means we have verified that this piece is correct. +This only applies when the torrent is in seed_mode.
    total_uploadedinteger. The number of bytes that have been uploaded in +total for this torrent.
    total_downloadedinteger. The number of bytes that have been downloaded in +total for this torrent.
    active_timeinteger. The number of seconds this torrent has been active. +i.e. not paused.
    seeding_timeinteger. The number of seconds this torrent has been active +and seeding.
    last_uploadinteger. The number of seconds since epoch when we last +uploaded payload to a peer on this torrent.
    last_downloadinteger. The number of seconds since epoch when we last +downloaded payload from a peer on this torrent.
    upload_rate_limitinteger. In case this torrent has a per-torrent upload rate +limit, this is that limit. In bytes per second.
    download_rate_limitinteger. The download rate limit for this torrent in case +one is set, in bytes per second.
    max_connectionsinteger. The max number of peer connections this torrent +may have, if a limit is set.
    max_uploadsinteger. The max number of unchoked peers this torrent may +have, if a limit is set.
    file_prioritylist of integers. One entry per file in the torrent. Each +entry is the priority of the file with the same index.
    piece_prioritystring of bytes. Each byte is interpreted as an integer and +is the priority of that piece.
    seed_modeinteger. 1 if the torrent is in seed mode, 0 otherwise.
    upload_modeinteger. 1 if the torrent_flags::upload_mode is set.
    share_modeinteger. 1 if the torrent_flags::share_mode is set.
    apply_ip_filterinteger. 1 if the torrent_flags::apply_ip_filter is set.
    pausedinteger. 1 if the torrent is paused, 0 otherwise.
    auto_managedinteger. 1 if the torrent is auto managed, otherwise 0.
    super_seedinginteger. 1 if the torrent_flags::super_seeding is set.
    sequential_downloadinteger. 1 if the torrent is in sequential download mode, +0 otherwise.
    stop_when_readyinteger. 1 if the torrent_flags::stop_when_ready is set.
    disable_dhtinteger. 1 if the torrent_flags::disable_dht is set.
    disable_lsdinteger. 1 if the torrent_flags::disable_lsd is set.
    disable_pexinteger. 1 if the torrent_flags::disable_pex is set.
    trackerslist of lists of strings. The top level list lists all +tracker tiers. Each second level list is one tier of +trackers.
    mapped_fileslist of strings. If any file in the torrent has been +renamed, this entry contains a list of all the filenames. +In the same order as in the torrent file.
    url-listlist of strings. List of url-seed URLs used by this torrent. +The URLs are expected to be properly encoded and not contain +any illegal url characters.
    httpseedslist of strings. List of HTTP seed URLs used by this torrent. +The URLs are expected to be properly encoded and not contain +any illegal url characters.
    merkle treestring. In case this torrent is a merkle torrent, this is a +string containing the entire merkle tree, all nodes, +including the root and all leaves. The tree is not +necessarily complete, but complete enough to be able to send +any piece that we have, indicated by the have bitmask.
    save_pathstring. The save path where this torrent was saved. This is +especially useful when moving torrents with move_storage() +since this will be updated.
    peersstring. This string contains IPv4 and port pairs of peers we +were connected to last session. The endpoints are in compact +representation. 4 bytes IPv4 address followed by 2 bytes +port. Hence, the length of this string should be divisible +by 6.
    banned_peersstring. This string has the same format as peers but +instead represent IPv4 peers that we have banned.
    peers6string. This string contains IPv6 and port pairs of peers we +were connected to last session. The endpoints are in compact +representation. 16 bytes IPv6 address followed by 2 bytes +port. The length of this string should be divisible by 18.
    banned_peers6string. This string has the same format as peers6 but +instead represent IPv6 peers that we have banned.
    infoIf this field is present, it should be the info-dictionary +of the torrent this resume data is for. Its SHA-1 hash must +match the one in the info-hash field. When present, +the torrent is loaded from here, meaning the torrent can be +added purely from resume data (no need to load the .torrent +file separately). This may have performance advantages.
    unfinished

    list of dictionaries. Each dictionary represents an +piece, and has the following layout:

    + ++++ + + + + + + + + + + + +
    pieceinteger, the index of the piece this entry +refers to.
    bitmaskstring, a binary bitmask representing the +blocks that have been downloaded in this +piece.
    adler32The adler32 checksum of the data in the +blocks specified by bitmask.
    +
    allocationThe allocation mode for the storage. Can be either +allocate or sparse.
    +
    +
    +
    +

    storage allocation

    +

    There are two modes in which storage (files on disk) are allocated in libtorrent.

    +
      +
    1. The traditional full allocation mode, where the entire files are filled up +with zeros before anything is downloaded. Files are allocated on demand, the +first time anything is written to them. The main benefit of this mode is that +it avoids creating heavily fragmented files.
    2. +
    3. The sparse allocation, sparse files are used, and pieces are downloaded +directly to where they belong. This is the recommended (and default) mode.
    4. +
    +
    +

    sparse allocation

    +

    On filesystems that supports sparse files, this allocation mode will only use +as much space as has been downloaded.

    +

    The main drawback of this mode is that it may create heavily fragmented files.

    +
    +
      +
    • It does not require an allocation pass on startup.
    • +
    +
    +
    +
    +

    full allocation

    +

    When a torrent is started in full allocation mode, the disk-io thread +will make sure that the entire storage is allocated, and fill any gaps with zeros. +It will of course still check for existing pieces and fast resume data. The main +drawbacks of this mode are:

    +
    +
      +
    • It may take longer to start the torrent, since it will need to fill the files +with zeros. This delay is linear to the size of the download.
    • +
    • The download may occupy unnecessary disk space between download sessions.
    • +
    • Disk caches usually perform poorly with random access to large files +and may slow down the download some.
    • +
    +
    +

    The benefits of this mode are:

    +
    +
      +
    • Downloaded pieces are written directly to their final place in the files and +the total number of disk operations will be fewer and may also play nicer to +the filesystem file allocation, and reduce fragmentation.
    • +
    • No risk of a download failing because of a full disk during download, once +all files have been created.
    • +
    +
    +
    +
    +
    +

    HTTP seeding

    +

    There are two kinds of HTTP seeding. One with that assumes a smart (and polite) +client and one that assumes a smart server. These are specified in BEP 19 +and BEP 17 respectively.

    +

    libtorrent supports both. In the libtorrent source code and API, BEP 19 URLs +are typically referred to as url seeds and BEP 17 URLs are typically referred +to as HTTP seeds.

    +

    The libtorrent implementation of BEP 19 assumes that, if the URL ends with a +slash ('/'), the filename should be appended to it in order to request pieces +from that file. The way this works is that if the torrent is a single-file +torrent, only that filename is appended. If the torrent is a multi-file +torrent, the torrent's name '/' the file name is appended. This is the same +directory structure that libtorrent will download torrents into.

    +

    There is limited support for HTTP redirects. In case some files are redirected +to different hosts, the files must be piece aligned or padded to be piece +aligned.

    +
    +
    +

    piece picker

    +

    The piece picker in libtorrent has the following features:

    +
      +
    • rarest first
    • +
    • sequential download
    • +
    • random pick
    • +
    • reverse order picking
    • +
    • parole mode
    • +
    • prioritize partial pieces
    • +
    • prefer whole pieces
    • +
    • piece affinity by speed category
    • +
    • piece priorities
    • +
    +
    +

    internal representation

    +

    It is optimized by, at all times, keeping a list of pieces ordered by rarity, +randomly shuffled within each rarity class. This list is organized as a single +vector of contiguous memory in RAM, for optimal memory locality and to eliminate +heap allocations and frees when updating rarity of pieces.

    +

    Expensive events, like a peer joining or leaving, are evaluated lazily, since +it's cheaper to rebuild the whole list rather than updating every single piece +in it. This means as long as no blocks are picked, peers joining and leaving is +no more costly than a single peer joining or leaving. Of course the special +cases of peers that have all or no pieces are optimized to not require +rebuilding the list.

    +
    +
    +

    picker strategy

    +

    The normal mode of the picker is of course rarest first, meaning pieces that +few peers have are preferred to be downloaded over pieces that more peers have. +This is a fundamental algorithm that is the basis of the performance of +bittorrent. However, the user may set the piece picker into sequential download +mode. This mode simply picks pieces sequentially, always preferring lower piece +indices.

    +

    When a torrent starts out, picking the rarest pieces means increased risk that +pieces won't be completed early (since there are only a few peers they can be +downloaded from), leading to a delay of having any piece to offer to other +peers. This lack of pieces to trade, delays the client from getting started +into the normal tit-for-tat mode of bittorrent, and will result in a long +ramp-up time. The heuristic to mitigate this problem is to, for the first few +pieces, pick random pieces rather than rare pieces. The threshold for when to +leave this initial picker mode is determined by +settings_pack::initial_picker_threshold.

    +
    +
    +

    reverse order

    +

    An orthogonal setting is reverse order, which is used for snubbed peers. +Snubbed peers are peers that appear very slow, and might have timed out a piece +request. The idea behind this is to make all snubbed peers more likely to be +able to do download blocks from the same piece, concentrating slow peers on as +few pieces as possible. The reverse order means that the most common pieces are +picked, instead of the rarest pieces (or in the case of sequential download, +the last pieces, instead of the first).

    +
    +
    +

    parole mode

    +

    Peers that have participated in a piece that failed the hash check, may be put +in parole mode. This means we prefer downloading a full piece from this +peer, in order to distinguish which peer is sending corrupt data. Whether to do +this is or not is controlled by settings_pack::use_parole_mode.

    +

    In parole mode, the piece picker prefers picking one whole piece at a time for +a given peer, avoiding picking any blocks from a piece any other peer has +contributed to (since that would defeat the purpose of parole mode).

    +
    +
    +

    prioritize partial pieces

    +

    This setting determines if partially downloaded or requested pieces should +always be preferred over other pieces. The benefit of doing this is that the +number of partial pieces is minimized (and hence the turn-around time for +downloading a block until it can be uploaded to others is minimized). It also +puts less stress on the disk cache, since fewer partial pieces need to be kept +in the cache. Whether or not to enable this is controlled by +setting_pack::prioritize_partial_pieces.

    +

    The main benefit of not prioritizing partial pieces is that the rarest first +algorithm gets to have more influence on which pieces are picked. The picker is +more likely to truly pick the rarest piece, and hence improving the performance +of the swarm.

    +

    This setting is turned on automatically whenever the number of partial pieces +in the piece picker exceeds the number of peers we're connected to times 1.5. +This is in order to keep the waste of partial pieces to a minimum, but still +prefer rarest pieces.

    +
    +
    +

    prefer whole pieces

    +

    The prefer whole pieces setting makes the piece picker prefer picking entire +pieces at a time. This is used by web connections (both http seeding +standards), in order to be able to coalesce the small bittorrent requests to +larger HTTP requests. This significantly improves performance when downloading +over HTTP.

    +

    It is also used by peers that are downloading faster than a certain threshold. +The main advantage is that these peers will better utilize the other peer's +disk cache, by requesting all blocks in a single piece, from the same peer.

    +

    This threshold is controlled by the settings_pack::whole_pieces_threshold +setting.

    +

    TODO: piece priorities

    +
    +
    +
    +

    Multi-homed hosts

    +

    The settings_pack::listen_interfaces setting is used to specify which interfaces/IP addresses +to listen on, and accept incoming connections via.

    +

    Each item in listen_interfaces is an IP address or a device name, followed +by a listen port number. Each item (called listen_socket_t) will have the +following objects associated with it:

    +
      +
    • a listen socket accepting incoming TCP connections
    • +
    • a UDP socket: +1. to accept incoming uTP connections +2. to run a DHT instance on +3. to announce to UDP trackers from +4. a SOCKS5 UDP tunnel (if applicable)
    • +
    • a listen address and netmask, describing the network the sockets are bound to
    • +
    • a Local service discovery object, broadcasting to the specified subnet
    • +
    • a NAT-PMP/PCP port mapper (if applicable), to map ports on the gateway +for the specified subnet.
    • +
    • a UPnP port mapper (if applicable), to map ports on any
    • +
    • InternetGatewayDevice found on the specified local subnet.
    • +
    +

    A listen_socket_t item may be specified to only be a local network (with +the l suffix). Such listen socket will only be used to talk to peers and +trackers within the same local network. The netmask defining the network is +queried from the operating system by enumerating network interfaces.

    +

    An item that's considered to be "local network" will not be used to announce to +trackers outside of that network. For example, 10.0.0.2:6881l is marked as "local +network" and it will only be used as the source address announcing to a tracker +if the tracker is also within the same local network (e.g. 10.0.0.0/8).

    +

    The NAT-PMP/PCP and UPnP port mapper objects are only created for networks that +are expected to be externally available (i.e. not "local network"). If there are +multiple subnets connected to the internet, they will have separate port mappings.

    +
    +

    expanding device names

    +

    If a device name is specified, libtorrent will expand it to the IP addresses +associated with that device, but also retain the device name in order to attempt +to bind the listen sockets to that specific device.

    +
    +
    +

    expanding unspecified addresses

    +

    If an IP address is the unspecified address (i.e. 0.0.0.0 or ::), +libtorrent will expand it to specific IP addresses. This expansion will +enumerate all addresses it can find for the corresponding address family. +The expanded IP addresses are considered "local network" if any of the following +conditions are met:

    +
      +
    • the IP address is in a known link-local range
    • +
    • the IP address is in a known loopback range
    • +
    • the item the IP address was expanded from was marked local (l)
    • +
    • the network interface has the loopback flag set
    • +
    • NONE of the following conditions are met: +1. the IP address is in a globally reachable IP address range +2. the network interface has the point-to-point flag set +3. the routing table contains a route for at least one global internet address +(e.g. a default route) for the address family of the expanded IP that points to +the network interface of the expanded IP.
    • +
    +
    +
    +

    routing

    +

    A listen_socket_t item is considered able to route to a destination address +if any of these hold:

    +
      +
    • the destination address falls inside its subnet (i.e. interface address masked +by netmask is the same as the destination address masked by the netmask).
    • +
    • the listen_socket_t does not have the "local network" flag set, and the +address family matches the destination address.
    • +
    +

    The ability to route to an address is used when determining whether to announce +to a tracker from a listen_socket_t and whether to open a SOCKS5 UDP tunnel +for a listen_socket_t.

    +

    Note that the actual IP stack routing table is not considered for this purpose. +This mechanism is to determine which IP addresses should be announced to trackers.

    +
    +
    +

    tracker announces

    +

    Trackers are announced to from all network interfaces listening for incoming +connections. However, interfaces that cannot be used to reach the tracker, such +as loopback, are not used as the source address for announces. A +listen_socket_t item that can route to at least one of the tracker IP +addresses will be used as the source address for an announce. Each such item +will also have an announce_endpoint item associated with it, in the tracker +list.

    +

    If a tracker can be reached on a loopback address, then the loopback interface +will be used to announce to that tracker. But under normal circumstances, +loopback will not be used for announcing to trackers.

    +

    For more details, see BEP 7.

    +
    +
    +

    SOCKS5 UDP tunnels

    +

    When using a SOCKS5 proxy, each interface that can route to one of the SOCKS5 +proxy's addresses will be used to open a UDP tunnel, via that proxy. For +example, if a client has both IPv4 and IPv6 connectivity, but the socks5 proxy +only resolves to IPv4, only the IPv4 address will have a UDP tunnel. In that case, +the IPv6 connection will not be used, since it cannot use the proxy.

    +
    +
    +
    +

    rate based choking

    +

    libtorrent supports a choking algorithm that automatically determines the number +of upload slots (unchoke slots) based on the upload rate to peers. It is +controlled by the settings_pack::choking_algorithm setting. The +settings_pack::unchoke_slots_limit is ignored in this mode.

    +

    The algorithm is designed to stay stable, and not oscillate the number of upload +slots.

    +

    The initial rate threshold is set to settings_pack::rate_choker_initial_threshold.

    +

    It sorts all peers by on the rate at which we are uploading to them.

    +
      +
    1. Compare the fastest peer against the initial threshold.
    2. +
    3. Increment the threshold by 2 kiB/s.
    4. +
    5. The next fastest peer is compared against the threshold. +If the peer rate is higher than the threshold. goto 2
    6. +
    7. Terminate. The number of peers visited is the number of unchoke slots, but +never less than 2.
    8. +
    +

    In other words, the more upload slots you have, the higher rate does the slowest +unchoked peer upload at in order to open another slot.

    +
    +
    +

    predictive piece announce

    +

    In order to improve performance, libtorrent supports a feature called +predictive piece announce. When enabled, it will make libtorrent announce +that we have pieces to peers, before we truly have them. The most important +case is to announce a piece as soon as it has been downloaded and passed the +hash check, but not yet been written to disk. In this case, there is a risk the +piece will fail to be written to disk, in which case we won't have the piece +anymore, even though we announced it to peers.

    +

    The other case is when we're very close to completing the download of a piece +and assume it will pass the hash check, we can announce it to peers to make it +available one round-trip sooner than otherwise. This lets libtorrent start +uploading the piece to interested peers immediately when the piece complete, +instead of waiting one round-trip for the peers to request it.

    +

    This makes for the implementation slightly more complicated, since piece will +have more states and more complicated transitions. For instance, a piece could +be:

    +
      +
    1. hashed but not fully written to disk
    2. +
    3. fully written to disk but not hashed
    4. +
    5. not fully downloaded
    6. +
    7. downloaded and hash checked
    8. +
    +

    Once a piece is fully downloaded, the hash check could complete before any of +the write operations or it could complete after all write operations are +complete.

    +
    +
    +

    peer classes

    +

    The peer classes feature in libtorrent allows a client to define custom groups +of peers and rate limit them individually. Each such group is called a peer +class. There are a few default peer classes that are always created:

    +
      +
    • global - all peers belong to this class, except peers on the local network
    • +
    • local peers - all peers on the local network belongs to this class TCP peers
    • +
    • tcp class - all peers connected over TCP belong to this class
    • +
    +

    The TCP peers class is used by the uTP/TCP balancing logic, if it's enabled, to +throttle TCP peers. The global and local classes are used to adjust the global +rate limits.

    +

    When the rate limits are adjusted for a specific torrent, a class is created +implicitly for that torrent.

    +

    The default peer class IDs are defined as enums in the session class:

    +
    +enum {
    +        global_peer_class_id,
    +        tcp_peer_class_id,
    +        local_peer_class_id
    +};
    +
    +

    The default peer classes are automatically created on session startup, and +configured to apply to each respective type of connection. There's nothing +preventing a client from reconfiguring the peer class ip- and type filters +to disable or customize which peers they apply to. See set_peer_class_filter() +and set_peer_class_type_filter().

    +

    A peer class can be considered a more general form of labels that some +clients have. Peer classes however are not just applied to torrents, but +ultimately the peers.

    +

    Peer classes can be created with the create_peer_class() call (on the session +object), and deleted with the delete_peer_class() call.

    +

    Peer classes are configured with the set_peer_class() get_peer_class() calls.

    +

    Custom peer classes can be assigned based on the peer's IP address or the type +of transport protocol used. See set_peer_class_filter() and +set_peer_class_type_filter() for more information.

    +
    +

    peer class examples

    +

    Here are a few examples of common peer class operations.

    +

    To make the global rate limit apply to local peers as well, update the IP-filter +based peer class assignment:

    +
    +std::uint32_t const mask = 1 << lt::session::global_peer_class_id;
    +ip_filter f;
    +
    +// for every IPv4 address, assign the global peer class
    +f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask);
    +
    +// for every IPv6 address, assign the global peer class
    +f.add_rule(make_address("::")
    +        , make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
    +        , mask);
    +ses.set_peer_class_filter(f);
    +
    +

    To make uTP sockets exempt from rate limiting:

    +
    +peer_class_type_filter flt = ses.get_peer_class_type_filter();
    +// filter out the global and local peer class for uTP sockets, if these
    +// classes are set by the IP filter
    +flt.disallow(peer_class_type_filter::utp_socket, session::global_peer_class_id);
    +flt.disallow(peer_class_type_filter::utp_socket, session::local_peer_class_id);
    +
    +// this filter should not add the global or local peer class to utp sockets
    +flt.remove(peer_class_type_filter::utp_socket, session::global_peer_class_id);
    +flt.remove(peer_class_type_filter::utp_socket, session::local_peer_class_id);
    +
    +ses.set_peer_class_type_filter(flt);
    +
    +

    To make all peers on the internal network not subject to throttling:

    +
    +std::uint32_t const mask = 1 << lt::session::global_peer_class_id;
    +ip_filter f;
    +
    +// for every IPv4 address, assign the global peer class
    +f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask);
    +
    +// for every address on the local metwork, set the mask to 0
    +f.add_rule(make_address("10.0.0.0"), make_address("10.255.255.255"), 0);
    +ses.set_peer_class_filter(f);
    +
    +
    +
    +
    +

    SSL torrents

    +

    Torrents may have an SSL root (CA) certificate embedded in them. Such torrents +are called SSL torrents. An SSL torrent talks to all bittorrent peers over +SSL. The protocols are layered like this:

    +utp_stack.png +

    During the SSL handshake, both peers need to authenticate by providing a +certificate that is signed by the CA certificate found in the .torrent file. +These peer certificates are expected to be provided to peers through some other +means than bittorrent. Typically by a peer generating a certificate request +which is sent to the publisher of the torrent, and the publisher returning a +signed certificate.

    +

    In libtorrent, set_ssl_certificate() in torrent_handle is used to tell +libtorrent where to find the peer certificate and the private key for it. When +an SSL torrent is loaded, the torrent_need_cert_alert is posted to remind the +user to provide a certificate.

    +

    A peer connecting to an SSL torrent MUST provide the SNI TLS extension +(server name indication). The server name is the hex encoded info-hash of the +torrent to connect to. This is required for the client accepting the connection +to know which certificate to present.

    +

    SSL connections are accepted on a separate socket from normal bittorrent +connections. To enable support for SSL torrents, add a listen interface to the +settings_pack::listen_interfaces setting with the s suffix. For example:

    +
    +0.0.0.0:6881,0.0.0.0:6882s
    +
    +

    That will listen for normal bittorrent connections on port 6881 and for SSL +torrent connections on port 6882.

    +

    This feature is only available if libtorrent is build with openssl support +(TORRENT_USE_OPENSSL) and requires at least OpenSSL version 1.0, since it +needs SNI support.

    +

    Peer certificates must have at least one SubjectAltName field of type +DNSName. At least one of the fields must exactly match the name of the +torrent. This is a byte-by-byte comparison, the UTF-8 encoding must be +identical (i.e. there's no unicode normalization going on). This is the +recommended way of verifying certificates for HTTPS servers according to RFC +2818. Note the difference that for torrents only DNSName fields are taken +into account (not IP address fields). The most specific (i.e. last) Common +Name field is also taken into account if no SubjectAltName did not match.

    +

    If any of these fields contain a single asterisk ("*"), the certificate is +considered covering any torrent, allowing it to be reused for any torrent.

    +

    The purpose of matching the torrent name with the fields in the peer +certificate is to allow a publisher to have a single root certificate for all +torrents it distributes, and issue separate peer certificates for each torrent. +A peer receiving a certificate will not necessarily be able to access all +torrents published by this root certificate (only if it has a "star cert").

    +
    +

    testing

    +

    To test incoming SSL connections to an SSL torrent, one can use the following +openssl command:

    +
    +openssl s_client -cert <peer-certificate>.pem -key <peer-private-key>.pem -CAfile \
    +   <torrent-cert>.pem -debug -connect 127.0.0.1:4433 -tls1 -servername <info-hash>
    +
    +

    To create a root certificate, the Distinguished Name (DN) is not taken into +account by bittorrent peers. You still need to specify something, but from +libtorrent's point of view, it doesn't matter what it is. libtorrent only makes +sure the peer certificates are signed by the correct root certificate.

    +

    One way to create the certificates is to use the CA.sh script that comes +with openssl, like this (don't forget to enter a common Name for the +certificate):

    +
    +CA.sh -newca
    +CA.sh -newreq
    +CA.sh -sign
    +
    +

    The torrent certificate is located in ./demoCA/private/demoCA/cacert.pem, +this is the pem file to include in the .torrent file.

    +

    The peer's certificate is located in ./newcert.pem and the certificate's +private key in ./newkey.pem.

    +
    +
    +
    +

    session statistics

    +

    libtorrent provides a mechanism to query performance and statistics counters +from its internals.

    +

    The statistics consists of two fundamental types. counters and gauges. A +counter is a monotonically increasing value, incremented every time some event +occurs. For example, every time the network thread wakes up because a socket +became readable will increment a counter. Another example is every time a +socket receives n bytes, a counter is incremented by n.

    +

    Counters are the most flexible of metrics. It allows the program to sample +the counter at any interval, and calculate average rates of increments to the +counter. Some events may be rare and need to be sampled over a longer period in +order to get useful rates, where other events may be more frequent and evenly +distributed that sampling it frequently yields useful values. Counters also +provides accurate overall counts. For example, converting samples of a download +rate into a total transfer count is not accurate and takes more samples. +Converting an increasing counter into a rate is easy and flexible.

    +

    Gauges measure the instantaneous state of some kind. This is used for metrics +that are not counting events or flows, but states that can fluctuate. For +example, the number of torrents that are currently being downloaded.

    +

    It's important to know whether a value is a counter or a gauge in order to +interpret it correctly. In order to query libtorrent for which counters and +gauges are available, call session_stats_metrics(). This will return metadata +about the values available for inspection in libtorrent. It will include +whether a value is a counter or a gauge. The key information it includes is the +index used to extract the actual measurements for a specific counter or gauge.

    +

    In order to take a sample, call post_session_stats() in the session object. +This will result in a session_stats_alert being posted. In this alert object, +there is an array of values, these values make up the sample. The value index +in the stats metric indicates which index the metric's value is stored in.

    +

    The mapping between metric and value is not stable across versions of +libtorrent. Always query the metrics first, to find out the index at which the +value is stored, before interpreting the values array in the +session_stats_alert. The mapping will not change during the runtime of your +process though, it's tied to a specific libtorrent version. You only have to +query the mapping once on startup (or every time libtorrent.so is loaded, +if it's done dynamically).

    +

    The available stats metrics are:

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_peerscounter
    peer.disconnected_peerscounter
    +

    error_peers is the total number of peer disconnects +caused by an error (not initiated by this client) and +disconnected initiated by this client (disconnected_peers).

    + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.eof_peerscounter
    peer.connreset_peerscounter
    peer.connrefused_peerscounter
    peer.connaborted_peerscounter
    peer.notconnected_peerscounter
    peer.perm_peerscounter
    peer.buffer_peerscounter
    peer.unreachable_peerscounter
    peer.broken_pipe_peerscounter
    peer.addrinuse_peerscounter
    peer.no_access_peerscounter
    peer.invalid_arg_peerscounter
    peer.aborted_peerscounter
    +

    these counters break down the peer errors into more specific +categories. These errors are what the underlying transport +reported (i.e. TCP or uTP)

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.piece_requestscounter
    peer.max_piece_requestscounter
    peer.invalid_piece_requestscounter
    peer.choked_piece_requestscounter
    peer.cancelled_piece_requestscounter
    peer.piece_rejectscounter
    +

    the total number of incoming piece requests we've received followed +by the number of rejected piece requests for various reasons. +max_piece_requests mean we already had too many outstanding requests +from this peer, so we rejected it. cancelled_piece_requests are ones +where the other end explicitly asked for the piece to be rejected.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_incoming_peerscounter
    peer.error_outgoing_peerscounter
    +

    these counters break down the peer errors into +whether they happen on incoming or outgoing peers.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_rc4_peerscounter
    peer.error_encrypted_peerscounter
    +

    these counters break down the peer errors into +whether they happen on encrypted peers (just +encrypted handshake) and rc4 peers (full stream +encryption). These can indicate whether encrypted +peers are more or less likely to fail

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_tcp_peerscounter
    peer.error_utp_peerscounter
    +

    these counters break down the peer errors into +whether they happen on uTP peers or TCP peers. +these may indicate whether one protocol is +more error prone

    + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.connect_timeoutscounter
    peer.uninteresting_peerscounter
    peer.timeout_peerscounter
    peer.no_memory_peerscounter
    peer.too_many_peerscounter
    peer.transport_timeout_peerscounter
    peer.num_banned_peerscounter
    peer.banned_for_hash_failurecounter
    peer.connection_attemptscounter
    peer.connection_attempt_loopscounter
    peer.boost_connection_attemptscounter
    peer.missed_connection_attemptscounter
    peer.no_peer_connection_attemptscounter
    peer.incoming_connectionscounter
    +

    these counters break down the reasons to +disconnect peers.

    + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.num_tcp_peersgauge
    peer.num_socks5_peersgauge
    peer.num_http_proxy_peersgauge
    peer.num_utp_peersgauge
    peer.num_i2p_peersgauge
    peer.num_ssl_peersgauge
    peer.num_ssl_socks5_peersgauge
    peer.num_ssl_http_proxy_peersgauge
    peer.num_ssl_utp_peersgauge
    peer.num_peers_half_opengauge
    peer.num_peers_connectedgauge
    peer.num_peers_up_interestedgauge
    peer.num_peers_down_interestedgauge
    peer.num_peers_up_unchoked_allgauge
    peer.num_peers_up_unchoked_optimisticgauge
    peer.num_peers_up_unchokedgauge
    peer.num_peers_down_unchokedgauge
    peer.num_peers_up_requestsgauge
    peer.num_peers_down_requestsgauge
    peer.num_peers_end_gamegauge
    peer.num_peers_up_diskgauge
    peer.num_peers_down_diskgauge
    +

    the number of peer connections for each kind of socket. +num_peers_half_open counts half-open (connecting) peers, no other +count includes those peers. +num_peers_up_unchoked_all is the total number of unchoked peers, +whereas num_peers_up_unchoked only are unchoked peers that count +against the limit (i.e. excluding peers that are unchoked because the +limit doesn't apply to them). num_peers_up_unchoked_optimistic is +the number of optimistically unchoked peers.

    + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    net.on_read_countercounter
    net.on_write_countercounter
    net.on_tick_countercounter
    net.on_lsd_countercounter
    net.on_lsd_peer_countercounter
    net.on_udp_countercounter
    net.on_accept_countercounter
    net.on_disk_queue_countercounter
    net.on_disk_countercounter
    +

    These counters count the number of times the +network thread wakes up for each respective +reason. If these counters are very large, it +may indicate a performance issue, causing the +network thread to wake up too ofte, wasting CPU. +mitigate it by increasing buffers and limits +for the specific trigger that wakes up the +thread.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    net.sent_payload_bytescounter
    net.sent_bytescounter
    net.sent_ip_overhead_bytescounter
    net.sent_tracker_bytescounter
    net.recv_payload_bytescounter
    net.recv_bytescounter
    net.recv_ip_overhead_bytescounter
    net.recv_tracker_bytescounter
    +

    total number of bytes sent and received by the session

    + + ++++ + + + + + + + + + + + + + +
    nametype
    net.limiter_up_queuegauge
    net.limiter_down_queuegauge
    +

    the number of sockets currently waiting for upload and download +bandwidth from the rate limiter.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    net.limiter_up_bytesgauge
    net.limiter_down_bytesgauge
    +

    the number of upload and download bytes waiting to be handed out from +the rate limiter.

    + ++++ + + + + + + + + + + +
    nametype
    net.recv_failed_bytescounter
    +

    the number of bytes downloaded that had to be discarded because they +failed the hash check

    + ++++ + + + + + + + + + + +
    nametype
    net.recv_redundant_bytescounter
    +

    the number of downloaded bytes that were discarded because they +were downloaded multiple times (from different peers)

    + ++++ + + + + + + + + + + +
    nametype
    net.has_incoming_connectionsgauge
    +

    is false by default and set to true when +the first incoming connection is established +this is used to know if the client is behind +NAT or not.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_checking_torrentsgauge
    ses.num_stopped_torrentsgauge
    ses.num_upload_only_torrentsgauge
    ses.num_downloading_torrentsgauge
    ses.num_seeding_torrentsgauge
    ses.num_queued_seeding_torrentsgauge
    ses.num_queued_download_torrentsgauge
    ses.num_error_torrentsgauge
    +

    these gauges count the number of torrents in +different states. Each torrent only belongs to +one of these states. For torrents that could +belong to multiple of these, the most prominent +in picked. For instance, a torrent with an error +counts as an error-torrent, regardless of its other +state.

    + ++++ + + + + + + + + + + +
    nametype
    ses.non_filter_torrentsgauge
    +

    the number of torrents that don't have the +IP filter applied to them.

    + + + + ++++ + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_piece_passedcounter
    ses.num_piece_failedcounter
    ses.num_have_piecescounter
    ses.num_total_pieces_addedcounter
    +

    these count the number of times a piece has passed the +hash check, the number of times a piece was successfully +written to disk and the number of total possible pieces +added by adding torrents. e.g. when adding a torrent with +1000 piece, num_total_pieces_added is incremented by 1000.

    + ++++ + + + + + + + + + + +
    nametype
    ses.num_unchoke_slotsgauge
    +

    the number of allowed unchoked peers

    + ++++ + + + + + + + + + + +
    nametype
    ses.num_outstanding_acceptgauge
    +

    the number of listen sockets that are currently accepting incoming +connections

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_incoming_chokecounter
    ses.num_incoming_unchokecounter
    ses.num_incoming_interestedcounter
    ses.num_incoming_not_interestedcounter
    ses.num_incoming_havecounter
    ses.num_incoming_bitfieldcounter
    ses.num_incoming_requestcounter
    ses.num_incoming_piececounter
    ses.num_incoming_cancelcounter
    ses.num_incoming_dht_portcounter
    ses.num_incoming_suggestcounter
    ses.num_incoming_have_allcounter
    ses.num_incoming_have_nonecounter
    ses.num_incoming_rejectcounter
    ses.num_incoming_allowed_fastcounter
    ses.num_incoming_ext_handshakecounter
    ses.num_incoming_pexcounter
    ses.num_incoming_metadatacounter
    ses.num_incoming_extendedcounter
    ses.num_outgoing_chokecounter
    ses.num_outgoing_unchokecounter
    ses.num_outgoing_interestedcounter
    ses.num_outgoing_not_interestedcounter
    ses.num_outgoing_havecounter
    ses.num_outgoing_bitfieldcounter
    ses.num_outgoing_requestcounter
    ses.num_outgoing_piececounter
    ses.num_outgoing_cancelcounter
    ses.num_outgoing_dht_portcounter
    ses.num_outgoing_suggestcounter
    ses.num_outgoing_have_allcounter
    ses.num_outgoing_have_nonecounter
    ses.num_outgoing_rejectcounter
    ses.num_outgoing_allowed_fastcounter
    ses.num_outgoing_ext_handshakecounter
    ses.num_outgoing_pexcounter
    ses.num_outgoing_metadatacounter
    ses.num_outgoing_extendedcounter
    +

    bittorrent message counters. These counters are incremented +every time a message of the corresponding type is received from +or sent to a bittorrent peer.

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.waste_piece_timed_outcounter
    ses.waste_piece_cancelledcounter
    ses.waste_piece_unknowncounter
    ses.waste_piece_seedcounter
    ses.waste_piece_end_gamecounter
    ses.waste_piece_closingcounter
    +

    the number of wasted downloaded bytes by reason of the bytes being +wasted.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    picker.piece_picker_partial_loopscounter
    picker.piece_picker_suggest_loopscounter
    picker.piece_picker_sequential_loopscounter
    picker.piece_picker_reverse_rare_loopscounter
    picker.piece_picker_rare_loopscounter
    picker.piece_picker_rand_start_loopscounter
    picker.piece_picker_rand_loopscounter
    picker.piece_picker_busy_loopscounter
    +

    the number of pieces considered while picking pieces

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    picker.reject_piece_pickscounter
    picker.unchoke_piece_pickscounter
    picker.incoming_redundant_piece_pickscounter
    picker.incoming_piece_pickscounter
    picker.end_game_piece_pickscounter
    picker.snubbed_piece_pickscounter
    picker.interesting_piece_pickscounter
    picker.hash_fail_piece_pickscounter
    +

    This breaks down the piece picks into the event that +triggered it

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.write_cache_blocksgauge
    disk.read_cache_blocksgauge
    +

    These gauges indicate how many blocks are currently in use as dirty +disk blocks (write_cache_blocks) and read cache blocks, +respectively. deprecates cache_status::read_cache_size. +The sum of these gauges deprecates cache_status::cache_size.

    + ++++ + + + + + + + + + + +
    nametype
    disk.request_latencygauge
    +

    the number of microseconds it takes from receiving a request from a +peer until we're sending the response back on the socket.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.pinned_blocksgauge
    disk.disk_blocks_in_usegauge
    +

    disk_blocks_in_use indicates how many disk blocks are currently in +use, either as dirty blocks waiting to be written or blocks kept around +in the hope that a peer will request it or in a peer send buffer. This +gauge deprecates cache_status::total_used_buffers.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.queued_disk_jobsgauge
    disk.num_running_disk_jobsgauge
    disk.num_read_jobsgauge
    disk.num_write_jobsgauge
    disk.num_jobsgauge
    disk.blocked_disk_jobsgauge
    disk.num_writing_threadsgauge
    disk.num_running_threadsgauge
    +

    queued_disk_jobs is the number of disk jobs currently queued, +waiting to be executed by a disk thread. Deprecates +cache_status::job_queue_length.

    + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.queued_write_bytesgauge
    disk.arc_mru_sizegauge
    disk.arc_mru_ghost_sizegauge
    disk.arc_mfu_sizegauge
    disk.arc_mfu_ghost_sizegauge
    disk.arc_write_sizegauge
    disk.arc_volatile_sizegauge
    +

    the number of bytes we have sent to the disk I/O +thread for writing. Every time we hear back from +the disk I/O thread with a completed write job, this +is updated to the number of bytes the disk I/O thread +is actually waiting for to be written (as opposed to +bytes just hanging out in the cache)

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.num_blocks_writtencounter
    disk.num_blocks_readcounter
    +

    the number of blocks written and read from disk in total. A block is 16 +kiB. num_blocks_written and num_blocks_read deprecates +cache_status::blocks_written and cache_status::blocks_read respectively.

    + ++++ + + + + + + + + + + +
    nametype
    disk.num_blocks_hashedcounter
    +

    the total number of blocks run through SHA-1 hashing

    + ++++ + + + + + + + + + + +
    nametype
    disk.num_blocks_cache_hitscounter
    +

    the number of blocks read from the disk cache +Deprecates cache_info::blocks_read_hit.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.num_write_opscounter
    disk.num_read_opscounter
    +

    the number of disk I/O operation for reads and writes. One disk +operation may transfer more then one block. +These counters deprecates cache_status::writes and +cache_status::reads.

    + ++++ + + + + + + + + + + +
    nametype
    disk.num_read_backcounter
    +

    the number of blocks that had to be read back from disk in order to +hash a piece (when verifying against the piece hash)

    + + + + ++++ + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.disk_read_timecounter
    disk.disk_write_timecounter
    disk.disk_hash_timecounter
    disk.disk_job_timecounter
    +

    cumulative time spent in various disk jobs, as well +as total for all disk jobs. Measured in microseconds

    + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.num_fenced_readgauge
    disk.num_fenced_writegauge
    disk.num_fenced_hashgauge
    disk.num_fenced_move_storagegauge
    disk.num_fenced_release_filesgauge
    disk.num_fenced_delete_filesgauge
    disk.num_fenced_check_fastresumegauge
    disk.num_fenced_save_resume_datagauge
    disk.num_fenced_rename_filegauge
    disk.num_fenced_stop_torrentgauge
    disk.num_fenced_flush_piecegauge
    disk.num_fenced_flush_hashedgauge
    disk.num_fenced_flush_storagegauge
    disk.num_fenced_trim_cachegauge
    disk.num_fenced_file_prioritygauge
    disk.num_fenced_load_torrentgauge
    disk.num_fenced_clear_piecegauge
    disk.num_fenced_tick_storagegauge
    +

    for each kind of disk job, a counter of how many jobs of that kind +are currently blocked by a disk fence

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_nodesgauge
    +

    The number of nodes in the DHT routing table

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_node_cachegauge
    +

    The number of replacement nodes in the DHT routing table

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_torrentsgauge
    +

    the number of torrents currently tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_peersgauge
    +

    the number of peers currently tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_immutable_datagauge
    +

    the number of immutable data items tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_mutable_datagauge
    +

    the number of mutable data items tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_allocated_observersgauge
    +

    the number of RPC observers currently allocated

    + + ++++ + + + + + + + + + + + + + +
    nametype
    dht.dht_messages_incounter
    dht.dht_messages_outcounter
    +

    the total number of DHT messages sent and received

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_messages_in_droppedcounter
    +

    the number of incoming DHT requests that were dropped. There are a few +different reasons why incoming DHT packets may be dropped:

    +
      +
    1. there wasn't enough send quota to respond to them.
    2. +
    3. the Denial of service logic kicked in, blocking the peer
    4. +
    5. ignore_dark_internet is enabled, and the packet came from a +non-public IP address
    6. +
    7. the bencoding of the message was invalid
    8. +
    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_messages_out_droppedcounter
    +

    the number of outgoing messages that failed to be +sent

    + + ++++ + + + + + + + + + + + + + +
    nametype
    dht.dht_bytes_incounter
    dht.dht_bytes_outcounter
    +

    the total number of bytes sent and received by the DHT

    + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    dht.dht_ping_incounter
    dht.dht_ping_outcounter
    dht.dht_find_node_incounter
    dht.dht_find_node_outcounter
    dht.dht_get_peers_incounter
    dht.dht_get_peers_outcounter
    dht.dht_announce_peer_incounter
    dht.dht_announce_peer_outcounter
    dht.dht_get_incounter
    dht.dht_get_outcounter
    dht.dht_put_incounter
    dht.dht_put_outcounter
    dht.dht_sample_infohashes_incounter
    dht.dht_sample_infohashes_outcounter
    +

    the number of DHT messages we've sent and received +by kind.

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    dht.dht_invalid_announcecounter
    dht.dht_invalid_get_peerscounter
    dht.dht_invalid_find_nodecounter
    dht.dht_invalid_putcounter
    dht.dht_invalid_getcounter
    dht.dht_invalid_sample_infohashescounter
    +

    the number of failed incoming DHT requests by kind of request

    + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    utp.utp_packet_losscounter
    utp.utp_timeoutcounter
    utp.utp_packets_incounter
    utp.utp_packets_outcounter
    utp.utp_fast_retransmitcounter
    utp.utp_packet_resendcounter
    utp.utp_samples_above_targetcounter
    utp.utp_samples_below_targetcounter
    utp.utp_payload_pkts_incounter
    utp.utp_payload_pkts_outcounter
    utp.utp_invalid_pkts_incounter
    utp.utp_redundant_pkts_incounter
    +

    uTP counters. Each counter represents the number of time each event +has occurred.

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    utp.num_utp_idlegauge
    utp.num_utp_syn_sentgauge
    utp.num_utp_connectedgauge
    utp.num_utp_fin_sentgauge
    utp.num_utp_close_waitgauge
    utp.num_utp_deletedgauge
    +

    the number of uTP sockets in each respective state

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    sock_bufs.socket_send_size3counter
    sock_bufs.socket_send_size4counter
    sock_bufs.socket_send_size5counter
    sock_bufs.socket_send_size6counter
    sock_bufs.socket_send_size7counter
    sock_bufs.socket_send_size8counter
    sock_bufs.socket_send_size9counter
    sock_bufs.socket_send_size10counter
    sock_bufs.socket_send_size11counter
    sock_bufs.socket_send_size12counter
    sock_bufs.socket_send_size13counter
    sock_bufs.socket_send_size14counter
    sock_bufs.socket_send_size15counter
    sock_bufs.socket_send_size16counter
    sock_bufs.socket_send_size17counter
    sock_bufs.socket_send_size18counter
    sock_bufs.socket_send_size19counter
    sock_bufs.socket_send_size20counter
    sock_bufs.socket_recv_size3counter
    sock_bufs.socket_recv_size4counter
    sock_bufs.socket_recv_size5counter
    sock_bufs.socket_recv_size6counter
    sock_bufs.socket_recv_size7counter
    sock_bufs.socket_recv_size8counter
    sock_bufs.socket_recv_size9counter
    sock_bufs.socket_recv_size10counter
    sock_bufs.socket_recv_size11counter
    sock_bufs.socket_recv_size12counter
    sock_bufs.socket_recv_size13counter
    sock_bufs.socket_recv_size14counter
    sock_bufs.socket_recv_size15counter
    sock_bufs.socket_recv_size16counter
    sock_bufs.socket_recv_size17counter
    sock_bufs.socket_recv_size18counter
    sock_bufs.socket_recv_size19counter
    sock_bufs.socket_recv_size20counter
    +

    the buffer sizes accepted by +socket send and receive calls respectively. +The larger the buffers are, the more efficient, +because it reqire fewer system calls per byte. +The size is 1 << n, where n is the number +at the end of the counter name. i.e. +8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, +16384, 32768, 65536, 131072, 262144, 524288, 1048576 +bytes

    + ++++ + + + + + + + + + + +
    nametype
    tracker.num_queued_tracker_announcesgauge
    +

    if the outstanding tracker announce limit is reached, tracker +announces are queued, to be issued when an announce slot opens up. +this measure the number of tracker announces currently in the +queue

    +
    +
    +

    glossary

    +

    The libtorrent documentation use words that are bittorrent terms of art. This +section defines some of these words. For an overview of what bittorrent is and +how it works, see these slides. For an introduction to the bittorrent DHT, see +this presentation.

    +
    +
    announce
    +
    The act of telling a tracker or the DHT network about the existence of +oneself and how other peers can connect, by specifying port one is listening +on.
    +
    block
    +
    A subset of a piece. Almost always 16 kiB of payload, unless the piece size is +smaller. This is the granularity file payload is requested from peers on the +network.
    +
    DHT
    +
    The distributed hash table is a cross-swarm, world-wide network of bittorrent +peers. It's loosely connected, implementing the Kademlia protocol. Its purpose +is to act as a tracker. Peers can announce their presence to nodes on the DHT +and other peers can discover them to join the swarm.
    +
    HTTP tracker
    +
    A tracker that uses the HTTP protocol for announces.
    +
    info dictionary
    +
    The subset of a torrent file that describes piece hashes and file names. This +is the only mandatory part necessary to join the swarm (network of peers) for +the torrent.
    +
    info hash
    +
    The hash of the info dictionary. This uniquely identifies a torrent and is +used by the protocol to ensure peers talking to each other agree on which swarm +they are participating in. Sometimes spelled info-hash.
    +
    leecher
    +
    A peer that is still interested in downloading more pieces for the torrent. +It is not a seed.
    +
    magnet link
    +
    A URI containing the info hash for a torrent, allowing peers to join its +swarm. May optionally contain a display name, trackers and web seeds. +Typically magnet links rely on peers joining the swarm via the DHT.
    +
    metadata
    +
    Synonymous to a torrent file
    +
    peer
    +
    A computer running bittorrent client software that participates in the network +for a particular torrent/set of files.
    +
    piece
    +
    The smallest number of bytes that can be validated when downloading (no +longer the case in bittorrent V2). The smallest part of the files that can be +advertised to other peers. The size of a piece is determined by the info +dictionary inside the torrent file.
    +
    seed
    +
    A computer running bittorrent client software that has the complete files for +a specific torrent, able to share any piece for that file with other peers in +the network
    +
    swarm
    +
    The network of peers participating in sharing and downloading of a specific torrent.
    +
    torrent
    +
    May refer to a torrent file or the swarm (network of peers) created around +the torrent file.
    +
    torrent file
    +
    A file ending in .torrent describing the content of a set of files (but not +containing the content). Importantly, it contains hashes of all files, split +up into pieces. It may optionally contain references to trackers and nodes on +the DHT network to aid peers in joining the network of peers sharing +these files.
    +
    tracker
    +
    A server peers can announce to and receive other peers back belonging to the +same swarm. Trackers are used to introduce peers to each other, within a swarm. +When announcing, the info hash of the torrent is included. Trackers can +introduce peers to any info-hash that's specified, given other peers also use +the same tracker. Some trackers restrict which info hashes they support based +on a white list.
    +
    UDP tracker
    +
    A tracker that uses a UDP based protocol for announces.
    +
    web seed
    +
    A web server that is acting a seed, providing access to all pieces of all +files over HTTP. This is an extension that client software may or may not +support.
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/manual-ref.rst b/docs/manual-ref.rst new file mode 100644 index 0000000..6323efd --- /dev/null +++ b/docs/manual-ref.rst @@ -0,0 +1,1330 @@ + + +.. include:: header.rst + +============================ +libtorrent API Documentation +============================ + +.. contents:: Table of contents + :depth: 1 + :backlinks: none + +overview +======== + +The interface of libtorrent consists of a few classes. The main class is +the ``session``, it contains the main loop that serves all torrents. + +The basic usage is as follows: + +* construct a `session`__ +* load `session`__ state from settings file (see `load_state()`__) +* start extensions (see `add_extension()`__). +* start DHT, LSD, UPnP, NAT-PMP etc (see start_dht(), start_lsd(), start_upnp() + and start_natpmp()). +* parse .torrent-files and add them to the `session`__ (see `torrent_info`__, + `async_add_torrent()`__ and `add_torrent()`__) +* main loop (see `session`__) + + * poll for alerts (see `wait_for_alert()`__, `pop_alerts()`__) + * handle updates to torrents, (see `state_update_alert`__). + * handle other alerts, (see `alert`__). + * query the `session`__ for information (see session::status()). + * add and remove torrents from the `session`__ (`remove_torrent()`__) + +* save resume data for all torrent_handles (optional, see + `save_resume_data()`__) +* save `session`__ state (see `save_state()`__) +* destruct `session`__ object + +Each class and function is described in this manual, you may want to have a +look at the tutorial_ as well. + +.. _tutorial: tutorial.html + +For a description on how to create torrent files, see `create_torrent`__. + +.. _make_torrent: make_torrent.html + +forward declarations +==================== + +Forward declaring types from the libtorrent namespace is discouraged as it may +break in future releases. Instead include ``libtorrent/fwd.hpp`` for forward +declarations of all public types in libtorrent. + +trouble shooting +================ + +A common problem developers are facing is torrents stopping without explanation. +Here is a description on which conditions libtorrent will stop your torrents, +how to find out about it and what to do about it. + +Make sure to keep track of the paused state, the error state and the upload +mode of your torrents. By default, torrents are auto-managed, which means +libtorrent will pause, resume, scrape them and take them out +of upload-mode automatically. + +Whenever a torrent encounters a fatal error, it will be stopped, and the +``torrent_status::error`` will describe the error that caused it. If a torrent +is auto managed, it is scraped periodically and paused or resumed based on +the number of downloaders per seed. This will effectively seed torrents that +are in the greatest need of seeds. + +If a torrent hits a disk write error, it will be put into upload mode. This +means it will not download anything, but only upload. The assumption is that +the write error is caused by a full disk or write permission errors. If the +torrent is auto-managed, it will periodically be taken out of the upload +mode, trying to write things to the disk again. This means torrent will recover +from certain disk errors if the problem is resolved. If the torrent is not +auto managed, you have to call set_upload_mode() to turn +downloading back on again. + +For a more detailed guide on how to trouble shoot performance issues, see +troubleshooting_ + +.. _troubleshooting: troubleshooting.html + +ABI considerations +================== + +libtorrent maintains a stable ABI for versions with the same major and minor versions. + +e.g. libtorrent-1.2.0 is ABI compatible with libtorrent-1.2.1 but not with libtorrent-1.1 + +network primitives +================== + +There are a few typedefs in the ``libtorrent`` namespace which pulls +in network types from the ``boost::asio`` namespace. These are:: + + using address = boost::asio::ip::address; + using address_v4 = boost::asio::ip::address_v4; + using address_v6 = boost::asio::ip::address_v6; + using boost::asio::ip::tcp; + using boost::asio::ip::udp; + +These are declared in the ```` header. + +The ``using`` statements will give easy access to:: + + tcp::endpoint + udp::endpoint + +Which are the endpoint types used in libtorrent. An endpoint is an address +with an associated port. + +For documentation on these types, please refer to the `asio documentation`_. + +.. _`asio documentation`: https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio.html + +exceptions +========== + +Many functions in libtorrent have two versions, one that throws exceptions on +errors and one that takes an ``error_code`` reference which is filled with the +error code on errors. + +On exceptions, libtorrent will throw ``boost::system::system_error`` exceptions +carrying an ``error_code`` describing the underlying error. + +translating error codes +----------------------- + +The error_code::message() function will typically return a localized error string, +for system errors. That is, errors that belong to the generic or system category. + +Errors that belong to the libtorrent error category are not localized however, they +are only available in English. In order to translate libtorrent errors, compare the +error category of the ``error_code`` object against ``lt::libtorrent_category()``, +and if matches, you know the error code refers to the list above. You can provide +your own mapping from error code to string, which is localized. In this case, you +cannot rely on ``error_code::message()`` to generate your strings. + +The numeric values of the errors are part of the API and will stay the same, although +new error codes may be appended at the end. + +Here's a simple example of how to translate error codes: + +.. code:: c++ + + std::string error_code_to_string(boost::system::error_code const& ec) + { + if (ec.category() != lt::libtorrent_category()) + { + return ec.message(); + } + // the error is a libtorrent error + + int code = ec.value(); + static const char const* swedish[] = + { + "inget fel", + "en fil i torrenten kolliderar med en fil fran en annan torrent", + "hash check misslyckades", + "torrentfilen ar inte en dictionary", + "'info'-nyckeln saknas eller ar korrupt i torrentfilen", + "'info'-faltet ar inte en dictionary", + "'piece length' faltet saknas eller ar korrupt i torrentfilen", + "torrentfilen saknar namnfaltet", + "ogiltigt namn i torrentfilen (kan vara en attack)", + // ... more strings here + }; + + // use the default error string in case we don't have it + // in our translated list + if (code < 0 || code >= sizeof(swedish)/sizeof(swedish[0])) + return ec.message(); + + return swedish[code]; + } + +magnet links +============ + +Magnet links are URIs that includes an info-hash, a display name and optionally +a tracker url. The idea behind magnet links is that an end user can click on a +link in a browser and have it handled by a bittorrent application, to start a +download, without any .torrent file. + +The format of the magnet URI is: + +**magnet:?xt=urn:btih:** *Base16 encoded info-hash* [ **&dn=** *name of download* ] [ **&tr=** *tracker URL* ]* + +In order to download *just* the metadata (.torrent file) from a magnet link, set +the torrent_flags::upload_mode flag in `add_torrent_params`__ before adding the it. + +In this case, when the metadata is received from the swarm, the torrent will +still be running, but it will disconnect the majority of peers (since connections +to peers that already have the metadata are redundant). It will keep seeding the +*metadata* only. + +Note that this doesn't prevent empty files from being created, if the torrent +contains any. If you need to prevent that, you can either +set ``file_priority`` to a long list of zeros (since the number of files is not known +in advance), or set ``save_path`` to an invalid path. + +queuing +======= + +libtorrent supports *queuing*. Queuing is a mechanism to automatically pause and +resume torrents based on certain criteria. The criteria depends on the overall +state the torrent is in (checking, downloading or seeding). + +To opt-out of the queuing logic, make sure your torrents are added with the +torrent_flags::auto_managed bit *cleared* from ``add_torrent_params::flags``. +Or call `torrent_handle::unset_flags()`__ and pass in torrent_flags::auto_managed on +the torrent handle. + +The overall purpose of the queuing logic is to improve performance under arbitrary +torrent downloading and seeding load. For example, if you want to download 100 +torrents on a limited home connection, you improve performance by downloading +them one at a time (or maybe two at a time), over downloading them all in +parallel. The benefits are: + +* the average completion time of a torrent is half of what it would be if all + downloaded in parallel. +* The amount of upload capacity is more likely to reach the *reciprocation rate* + of your peers, and is likely to improve your *return on investment* (download + to upload ratio) +* your disk I/O load is likely to be more local which may improve I/O + performance and decrease fragmentation. + +There are fundamentally 3 separate queues: + +* checking torrents +* downloading torrents +* seeding torrents + +Every torrent that is not seeding has a queue number associated with it, this is +its place in line to be started. See `torrent_status::queue_position`__. + +On top of the limits of each queue, there is an over arching limit, set in +`settings_pack::active_limit`__. The auto manager will never start more than this +number of torrents (with one exception described below). Non-auto-managed +torrents are exempt from this logic, and not counted. + +At a regular interval, torrents are checked if there needs to be any +re-ordering of which torrents are active and which are queued. This interval +can be controlled via `settings_pack::auto_manage_interval`__. + +For queuing to work, resume data needs to be saved and restored for all +torrents. See `torrent_handle::save_resume_data()`__. + +queue position +-------------- + +The torrents in the front of the queue are started and the rest are ordered by +their queue position. Any newly added torrent is placed at the end of the queue. +Once a torrent is removed or turns into a seed, its queue position is -1 and all +torrents that used to be after it in the queue, decreases their position in +order to fill the gap. + +The queue positions are always contiguous, in a sequence without any gaps. + +Lower queue position means closer to the front of the queue, and will be +started sooner than torrents with higher queue positions. + +To query a torrent for its position in the queue, or change its position, see: +`torrent_handle::queue_position()`__, `torrent_handle::queue_position_up()`__, +`torrent_handle::queue_position_down()`__, `torrent_handle::queue_position_top()`__ +and `torrent_handle::queue_position_bottom()`__. + +checking queue +-------------- + +The checking queue affects torrents in the torrent_status::checking or +`torrent_status::allocating`__ state that are auto-managed. + +The checking queue will make sure that (of the torrents in its queue) no more than +settings_pack::active_checking_limit torrents are started at any given time. +Once a torrent completes checking and moves into a different state, the next in +line will be started for checking. + +Any torrent added force-started or force-stopped (i.e. the auto managed flag is +*not* set), will not be subject to this limit and they will all check +independently and in parallel. + +Once a torrent completes the checking of its files, or resume data, it will +be put in the queue for downloading and potentially start downloading immediately. +In order to add a torrent and check its files without starting the download, it +can be added in ``stop_when_ready`` mode. +See add_torrent_params::flag_stop_when_ready. This flag will stop the torrent +once it is ready to start downloading. + +This is conceptually the same as waiting for the ``torrent_checked_alert`` and +then call:: + + h.set_flags(torrent_flags::paused, torrent_flags::paused | torrent_flags::auto_managed); + +With the important distinction that it entirely avoids the brief window where +the torrent is in downloading state. + +downloading queue +----------------- + +Similarly to the checking queue, the downloading queue will make sure that no +more than `settings_pack::active_downloads`__ torrents are in the downloading +state at any given time. + +The `torrent_status::queue_position`__ is used again here to determine who is next +in line to be started once a downloading torrent completes or is stopped/removed. + +seeding queue +------------- + +The seeding queue does not use `torrent_status::queue_position`__ to determine which +torrent to seed. Instead, it estimates the *demand* for the torrent to be +seeded. A torrent with few other seeds and many downloaders is assumed to have a +higher demand of more seeds than one with many seeds and few downloaders. + +It limits the number of started seeds to `settings_pack::active_seeds`__. + +On top of this basic bias, *seed priority* can be controller by specifying a +seed ratio (the upload to download ratio), a seed-time ratio (the download +time to seeding time ratio) and a seed-time (the absolute time to be seeding a +torrent). Until all those targets are hit, the torrent will be prioritized for +seeding. + +Among torrents that have met their seed target, torrents where we don't know of +any other seed take strict priority. + +In order to avoid flapping, torrents that were started less than 30 minutes ago +also have priority to keep seeding. + +Finally, for torrents where none of the above apply, they are prioritized based +on the download to seed ratio. + +The relevant settings to control these limits are +`settings_pack::share_ratio_limit`__, `settings_pack::seed_time_ratio_limit`__ and +`settings_pack::seed_time_limit`__. + +queuing options +--------------- + +In addition to simply starting and stopping torrents, the queuing mechanism can +have more fine grained control of the resources used by torrents. + +half-started torrents +..................... + +In addition to the downloading and seeding limits, there are limits on *actions* +torrents perform. The downloading and seeding limits control whether peers are +allowed at all, and if peers are not allowed, torrents are stopped and don't do +anything. If peers are allowed, torrents may: + +1. announce to trackers +2. announce to the DHT +3. announce to local peer discovery (local service discovery) + +Each of those actions are associated with a cost and hence may need a separate +limit. These limits are controlled by `settings_pack::active_tracker_limit`__, +`settings_pack::active_dht_limit`__ and `settings_pack::active_lsd_limit`__ +respectively. + +Specifically, announcing to a tracker is typically cheaper than +announcing to the DHT. `settings_pack::active_dht_limit`__ will limit the number of +torrents that are allowed to announce to the DHT. The highest priority ones +will, and the lower priority ones won't. The will still be considered started +though, and any incoming peers will still be accepted. + +If you do not wish to impose such limits (basically, if you do not wish to have +half-started torrents) make sure to set these limits to -1 (infinite). + +prefer seeds +............ + +In the case where ``active_downloads`` + ``active_seeds`` > ``active_limit``, +there's an ambiguity whether the downloads should be satisfied first or the +seeds. To disambiguate this case, the `settings_pack::auto_manage_prefer_seeds`__ +determines whether seeds are preferred or not. + +inactive torrents +................. + +Torrents that are not transferring any bytes (downloading or uploading) have a +relatively low cost to be started. It's possible to exempt such torrents from +the download and seed queues by setting `settings_pack::dont_count_slow_torrents`__ +to true. + +Since it sometimes may take a few minutes for a newly started torrent to find +peers and be unchoked, or find peers that are interested in requesting data, +torrents are not considered inactive immediately. There must be an extended +period of no transfers before it is considered inactive and exempt from the +queuing limits. + +fast resume +=========== + +The fast resume mechanism is a way to remember which pieces are downloaded +and where they are put between sessions. You can generate fast resume data by +calling `save_resume_data()`__ on `torrent_handle`__. You can +then save this data to disk and use it when resuming the torrent. libtorrent +will not check the piece hashes then, and rely on the information given in the +fast-resume data. The fast-resume data also contains information about which +blocks, in the unfinished pieces, were downloaded, so it will not have to +start from scratch on the partially downloaded pieces. + +To use the fast-resume data you pass it to `read_resume_data()`__, which will return +an `add_torrent_params`__ object. Fields of this object can then be altered before +passing it to `async_add_torrent()`__ or `add_torrent()`__. +The `session`__ will then skip the time consuming checks. It may have to do +the checking anyway, if the fast-resume data is corrupt or doesn't fit the +storage for that torrent. + +file format +----------- + +The file format is a bencoded dictionary containing the following fields: + ++--------------------------+--------------------------------------------------------------+ +| ``file-format`` | string: "libtorrent resume file" | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``info-hash`` | string, the info hash of the torrent this data is saved for. | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``pieces`` | A string with piece flags, one character per piece. | +| | Bit 1 means we have that piece. | +| | Bit 2 means we have verified that this piece is correct. | +| | This only applies when the torrent is in seed_mode. | ++--------------------------+--------------------------------------------------------------+ +| ``total_uploaded`` | integer. The number of bytes that have been uploaded in | +| | total for this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``total_downloaded`` | integer. The number of bytes that have been downloaded in | +| | total for this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``active_time`` | integer. The number of seconds this torrent has been active. | +| | i.e. not paused. | ++--------------------------+--------------------------------------------------------------+ +| ``seeding_time`` | integer. The number of seconds this torrent has been active | +| | and seeding. | ++--------------------------+--------------------------------------------------------------+ +| ``last_upload`` | integer. The number of seconds since epoch when we last | +| | uploaded payload to a peer on this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``last_download`` | integer. The number of seconds since epoch when we last | +| | downloaded payload from a peer on this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``upload_rate_limit`` | integer. In case this torrent has a per-torrent upload rate | +| | limit, this is that limit. In bytes per second. | ++--------------------------+--------------------------------------------------------------+ +| ``download_rate_limit`` | integer. The download rate limit for this torrent in case | +| | one is set, in bytes per second. | ++--------------------------+--------------------------------------------------------------+ +| ``max_connections`` | integer. The max number of peer connections this torrent | +| | may have, if a limit is set. | ++--------------------------+--------------------------------------------------------------+ +| ``max_uploads`` | integer. The max number of unchoked peers this torrent may | +| | have, if a limit is set. | ++--------------------------+--------------------------------------------------------------+ +| ``file_priority`` | list of integers. One entry per file in the torrent. Each | +| | entry is the priority of the file with the same index. | ++--------------------------+--------------------------------------------------------------+ +| ``piece_priority`` | string of bytes. Each byte is interpreted as an integer and | +| | is the priority of that piece. | ++--------------------------+--------------------------------------------------------------+ +| ``seed_mode`` | integer. 1 if the torrent is in seed mode, 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``upload_mode`` | integer. 1 if the torrent_flags::upload_mode is set. | ++--------------------------+--------------------------------------------------------------+ +| ``share_mode`` | integer. 1 if the torrent_flags::share_mode is set. | ++--------------------------+--------------------------------------------------------------+ +| ``apply_ip_filter`` | integer. 1 if the torrent_flags::apply_ip_filter is set. | ++--------------------------+--------------------------------------------------------------+ +| ``paused`` | integer. 1 if the torrent is paused, 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``auto_managed`` | integer. 1 if the torrent is auto managed, otherwise 0. | ++--------------------------+--------------------------------------------------------------+ +| ``super_seeding`` | integer. 1 if the torrent_flags::super_seeding is set. | ++--------------------------+--------------------------------------------------------------+ +| ``sequential_download`` | integer. 1 if the torrent is in sequential download mode, | +| | 0 otherwise. | ++--------------------------+--------------------------------------------------------------+ +| ``stop_when_ready`` | integer. 1 if the torrent_flags::stop_when_ready is set. | ++--------------------------+--------------------------------------------------------------+ +| ``disable_dht`` | integer. 1 if the torrent_flags::disable_dht is set. | ++--------------------------+--------------------------------------------------------------+ +| ``disable_lsd`` | integer. 1 if the torrent_flags::disable_lsd is set. | ++--------------------------+--------------------------------------------------------------+ +| ``disable_pex`` | integer. 1 if the torrent_flags::disable_pex is set. | ++--------------------------+--------------------------------------------------------------+ +| ``trackers`` | list of lists of strings. The top level list lists all | +| | tracker tiers. Each second level list is one tier of | +| | trackers. | ++--------------------------+--------------------------------------------------------------+ +| ``mapped_files`` | list of strings. If any file in the torrent has been | +| | renamed, this entry contains a list of all the filenames. | +| | In the same order as in the torrent file. | ++--------------------------+--------------------------------------------------------------+ +| ``url-list`` | list of strings. List of url-seed URLs used by this torrent. | +| | The URLs are expected to be properly encoded and not contain | +| | any illegal url characters. | ++--------------------------+--------------------------------------------------------------+ +| ``httpseeds`` | list of strings. List of HTTP seed URLs used by this torrent.| +| | The URLs are expected to be properly encoded and not contain | +| | any illegal url characters. | ++--------------------------+--------------------------------------------------------------+ +| ``merkle tree`` | string. In case this torrent is a merkle torrent, this is a | +| | string containing the entire merkle tree, all nodes, | +| | including the root and all leaves. The tree is not | +| | necessarily complete, but complete enough to be able to send | +| | any piece that we have, indicated by the have bitmask. | ++--------------------------+--------------------------------------------------------------+ +| ``save_path`` | string. The save path where this torrent was saved. This is | +| | especially useful when moving torrents with move_storage() | +| | since this will be updated. | ++--------------------------+--------------------------------------------------------------+ +| ``peers`` | string. This string contains IPv4 and port pairs of peers we | +| | were connected to last session. The endpoints are in compact | +| | representation. 4 bytes IPv4 address followed by 2 bytes | +| | port. Hence, the length of this string should be divisible | +| | by 6. | ++--------------------------+--------------------------------------------------------------+ +| ``banned_peers`` | string. This string has the same format as ``peers`` but | +| | instead represent IPv4 peers that we have banned. | ++--------------------------+--------------------------------------------------------------+ +| ``peers6`` | string. This string contains IPv6 and port pairs of peers we | +| | were connected to last session. The endpoints are in compact | +| | representation. 16 bytes IPv6 address followed by 2 bytes | +| | port. The length of this string should be divisible by 18. | ++--------------------------+--------------------------------------------------------------+ +| ``banned_peers6`` | string. This string has the same format as ``peers6`` but | +| | instead represent IPv6 peers that we have banned. | ++--------------------------+--------------------------------------------------------------+ +| ``info`` | If this field is present, it should be the info-dictionary | +| | of the torrent this resume data is for. Its SHA-1 hash must | +| | match the one in the ``info-hash`` field. When present, | +| | the torrent is loaded from here, meaning the torrent can be | +| | added purely from resume data (no need to load the .torrent | +| | file separately). This may have performance advantages. | ++--------------------------+--------------------------------------------------------------+ +| ``unfinished`` | list of dictionaries. Each dictionary represents an | +| | piece, and has the following layout: | +| | | +| | +-------------+--------------------------------------------+ | +| | | ``piece`` | integer, the index of the piece this entry | | +| | | | refers to. | | +| | +-------------+--------------------------------------------+ | +| | | ``bitmask`` | string, a binary bitmask representing the | | +| | | | blocks that have been downloaded in this | | +| | | | piece. | | +| | +-------------+--------------------------------------------+ | +| | | ``adler32`` | The adler32 checksum of the data in the | | +| | | | blocks specified by ``bitmask``. | | +| | | | | | +| | +-------------+--------------------------------------------+ | +| | | ++--------------------------+--------------------------------------------------------------+ +| ``allocation`` | The allocation mode for the storage. Can be either | +| | ``allocate`` or ``sparse``. | ++--------------------------+--------------------------------------------------------------+ + +storage allocation +================== + +There are two modes in which storage (files on disk) are allocated in libtorrent. + +1. The traditional *full allocation* mode, where the entire files are filled up + with zeros before anything is downloaded. Files are allocated on demand, the + first time anything is written to them. The main benefit of this mode is that + it avoids creating heavily fragmented files. + +2. The *sparse allocation*, sparse files are used, and pieces are downloaded + directly to where they belong. This is the recommended (and default) mode. + +sparse allocation +----------------- + +On filesystems that supports sparse files, this allocation mode will only use +as much space as has been downloaded. + +The main drawback of this mode is that it may create heavily fragmented files. + + * It does not require an allocation pass on startup. + +full allocation +--------------- + +When a torrent is started in full allocation mode, the disk-io thread +will make sure that the entire storage is allocated, and fill any gaps with zeros. +It will of course still check for existing pieces and fast resume data. The main +drawbacks of this mode are: + + * It may take longer to start the torrent, since it will need to fill the files + with zeros. This delay is linear to the size of the download. + + * The download may occupy unnecessary disk space between download sessions. + + * Disk caches usually perform poorly with random access to large files + and may slow down the download some. + +The benefits of this mode are: + + * Downloaded pieces are written directly to their final place in the files and + the total number of disk operations will be fewer and may also play nicer to + the filesystem file allocation, and reduce fragmentation. + + * No risk of a download failing because of a full disk during download, once + all files have been created. + +HTTP seeding +============ + +There are two kinds of HTTP seeding. One with that assumes a smart (and polite) +client and one that assumes a smart server. These are specified in `BEP 19`_ +and `BEP 17`_ respectively. + +libtorrent supports both. In the libtorrent source code and API, BEP 19 URLs +are typically referred to as *url seeds* and BEP 17 URLs are typically referred +to as *HTTP seeds*. + +The libtorrent implementation of `BEP 19`_ assumes that, if the URL ends with a +slash ('/'), the filename should be appended to it in order to request pieces +from that file. The way this works is that if the torrent is a single-file +torrent, only that filename is appended. If the torrent is a multi-file +torrent, the torrent's name '/' the file name is appended. This is the same +directory structure that libtorrent will download torrents into. + +There is limited support for HTTP redirects. In case some files are redirected +to *different hosts*, the files must be piece aligned or padded to be piece +aligned. + +.. _`BEP 17`: https://www.bittorrent.org/beps/bep_0017.html +.. _`BEP 19`: https://www.bittorrent.org/beps/bep_0019.html + +piece picker +============ + +The piece picker in libtorrent has the following features: + +* rarest first +* sequential download +* random pick +* reverse order picking +* parole mode +* prioritize partial pieces +* prefer whole pieces +* piece affinity by speed category +* piece priorities + +internal representation +----------------------- + +It is optimized by, at all times, keeping a list of pieces ordered by rarity, +randomly shuffled within each rarity class. This list is organized as a single +vector of contiguous memory in RAM, for optimal memory locality and to eliminate +heap allocations and frees when updating rarity of pieces. + +Expensive events, like a peer joining or leaving, are evaluated lazily, since +it's cheaper to rebuild the whole list rather than updating every single piece +in it. This means as long as no blocks are picked, peers joining and leaving is +no more costly than a single peer joining or leaving. Of course the special +cases of peers that have all or no pieces are optimized to not require +rebuilding the list. + +picker strategy +--------------- + +The normal mode of the picker is of course *rarest first*, meaning pieces that +few peers have are preferred to be downloaded over pieces that more peers have. +This is a fundamental algorithm that is the basis of the performance of +bittorrent. However, the user may set the piece picker into sequential download +mode. This mode simply picks pieces sequentially, always preferring lower piece +indices. + +When a torrent starts out, picking the rarest pieces means increased risk that +pieces won't be completed early (since there are only a few peers they can be +downloaded from), leading to a delay of having any piece to offer to other +peers. This lack of pieces to trade, delays the client from getting started +into the normal tit-for-tat mode of bittorrent, and will result in a long +ramp-up time. The heuristic to mitigate this problem is to, for the first few +pieces, pick random pieces rather than rare pieces. The threshold for when to +leave this initial picker mode is determined by +`settings_pack::initial_picker_threshold`__. + +reverse order +------------- + +An orthogonal setting is *reverse order*, which is used for *snubbed* peers. +Snubbed peers are peers that appear very slow, and might have timed out a piece +request. The idea behind this is to make all snubbed peers more likely to be +able to do download blocks from the same piece, concentrating slow peers on as +few pieces as possible. The reverse order means that the most common pieces are +picked, instead of the rarest pieces (or in the case of sequential download, +the last pieces, instead of the first). + +parole mode +----------- + +Peers that have participated in a piece that failed the hash check, may be put +in *parole mode*. This means we prefer downloading a full piece from this +peer, in order to distinguish which peer is sending corrupt data. Whether to do +this is or not is controlled by `settings_pack::use_parole_mode`__. + +In parole mode, the piece picker prefers picking one whole piece at a time for +a given peer, avoiding picking any blocks from a piece any other peer has +contributed to (since that would defeat the purpose of parole mode). + +prioritize partial pieces +------------------------- + +This setting determines if partially downloaded or requested pieces should +always be preferred over other pieces. The benefit of doing this is that the +number of partial pieces is minimized (and hence the turn-around time for +downloading a block until it can be uploaded to others is minimized). It also +puts less stress on the disk cache, since fewer partial pieces need to be kept +in the cache. Whether or not to enable this is controlled by +setting_pack::prioritize_partial_pieces. + +The main benefit of not prioritizing partial pieces is that the rarest first +algorithm gets to have more influence on which pieces are picked. The picker is +more likely to truly pick the rarest piece, and hence improving the performance +of the swarm. + +This setting is turned on automatically whenever the number of partial pieces +in the piece picker exceeds the number of peers we're connected to times 1.5. +This is in order to keep the waste of partial pieces to a minimum, but still +prefer rarest pieces. + +prefer whole pieces +------------------- + +The *prefer whole pieces* setting makes the piece picker prefer picking entire +pieces at a time. This is used by web connections (both http seeding +standards), in order to be able to coalesce the small bittorrent requests to +larger HTTP requests. This significantly improves performance when downloading +over HTTP. + +It is also used by peers that are downloading faster than a certain threshold. +The main advantage is that these peers will better utilize the other peer's +disk cache, by requesting all blocks in a single piece, from the same peer. + +This threshold is controlled by the `settings_pack::whole_pieces_threshold`__ +setting. + +*TODO: piece priorities* + +Multi-homed hosts +================= + +The `settings_pack::listen_interfaces`__ setting is used to specify which interfaces/IP addresses +to listen on, and accept incoming connections via. + +Each item in ``listen_interfaces`` is an IP address or a device name, followed +by a listen port number. Each item (called ``listen_socket_t``) will have the +following objects associated with it: + +* a listen socket accepting incoming TCP connections +* a UDP socket: + 1. to accept incoming uTP connections + 2. to run a DHT instance on + 3. to announce to UDP trackers from + 4. a SOCKS5 UDP tunnel (if applicable) +* a listen address and netmask, describing the network the sockets are bound to +* a Local service discovery object, broadcasting to the specified subnet +* a NAT-PMP/PCP port mapper (if applicable), to map ports on the gateway + for the specified subnet. +* a UPnP port mapper (if applicable), to map ports on any +* ``InternetGatewayDevice`` found on the specified local subnet. + +A ``listen_socket_t`` item may be specified to only be a local network (with +the ``l`` suffix). Such listen socket will only be used to talk to peers and +trackers within the same local network. The netmask defining the network is +queried from the operating system by enumerating network interfaces. + +An item that's considered to be "local network" will not be used to announce to +trackers outside of that network. For example, ``10.0.0.2:6881l`` is marked as "local +network" and it will only be used as the source address announcing to a tracker +if the tracker is also within the same local network (e.g. ``10.0.0.0/8``). + +The NAT-PMP/PCP and UPnP port mapper objects are only created for networks that +are expected to be externally available (i.e. not "local network"). If there are +multiple subnets connected to the internet, they will have separate port mappings. + +expanding device names +---------------------- + +If a device name is specified, libtorrent will expand it to the IP addresses +associated with that device, but also retain the device name in order to attempt +to bind the listen sockets to that specific device. + +expanding unspecified addresses +------------------------------- + +If an IP address is the *unspecified* address (i.e. ``0.0.0.0`` or ``::``), +libtorrent will expand it to specific IP addresses. This expansion will +enumerate all addresses it can find for the corresponding address family. +The expanded IP addresses are considered "local network" if any of the following +conditions are met: + +* the IP address is in a known link-local range +* the IP address is in a known loopback range +* the item the IP address was expanded from was marked local (``l``) +* the network interface has the ``loopback`` flag set +* NONE of the following conditions are met: + 1. the IP address is in a globally reachable IP address range + 2. the network interface has the ``point-to-point`` flag set + 3. the routing table contains a route for at least one global internet address + (e.g. a default route) for the address family of the expanded IP that points to + the network interface of the expanded IP. + +routing +------- + +A ``listen_socket_t`` item is considered able to route to a destination address +if any of these hold: + +* the destination address falls inside its subnet (i.e. interface address masked + by netmask is the same as the destination address masked by the netmask). +* the ``listen_socket_t`` does not have the "local network" flag set, and the + address family matches the destination address. + +The ability to route to an address is used when determining whether to announce +to a tracker from a ``listen_socket_t`` and whether to open a SOCKS5 UDP tunnel +for a ``listen_socket_t``. + +Note that the actual IP stack routing table is not considered for this purpose. +This mechanism is to determine which IP addresses should be announced to trackers. + +tracker announces +----------------- + +Trackers are announced to from all network interfaces listening for incoming +connections. However, interfaces that cannot be used to reach the tracker, such +as loopback, are not used as the source address for announces. A +``listen_socket_t`` item that can route to at least one of the tracker IP +addresses will be used as the source address for an announce. Each such item +will also have an `announce_endpoint`__ item associated with it, in the tracker +list. + +If a tracker can be reached on a loopback address, then the loopback interface +*will* be used to announce to that tracker. But under normal circumstances, +loopback will not be used for announcing to trackers. + +For more details, see `BEP 7`_. + +.. _`BEP 7`: https://www.bittorrent.org/beps/bep_0007.html + +SOCKS5 UDP tunnels +------------------ + +When using a SOCKS5 proxy, each interface that can route to one of the SOCKS5 +proxy's addresses will be used to open a UDP tunnel, via that proxy. For +example, if a client has both IPv4 and IPv6 connectivity, but the socks5 proxy +only resolves to IPv4, only the IPv4 address will have a UDP tunnel. In that case, +the IPv6 connection will not be used, since it cannot use the proxy. + +rate based choking +================== + +libtorrent supports a choking algorithm that automatically determines the number +of upload slots (unchoke slots) based on the upload rate to peers. It is +controlled by the `settings_pack::choking_algorithm`__ setting. The +`settings_pack::unchoke_slots_limit`__ is ignored in this mode. + +The algorithm is designed to stay stable, and not oscillate the number of upload +slots. + +The initial rate threshold is set to `settings_pack::rate_choker_initial_threshold`__. + +It sorts all peers by on the rate at which we are uploading to them. + +1. Compare the fastest peer against the initial threshold. +2. Increment the threshold by 2 kiB/s. +3. The next fastest peer is compared against the threshold. + If the peer rate is higher than the threshold. goto 2 +4. Terminate. The number of peers visited is the number of unchoke slots, but + never less than 2. + +In other words, the more upload slots you have, the higher rate does the slowest +unchoked peer upload at in order to open another slot. + +predictive piece announce +========================= + +In order to improve performance, libtorrent supports a feature called +``predictive piece announce``. When enabled, it will make libtorrent announce +that we have pieces to peers, before we truly have them. The most important +case is to announce a piece as soon as it has been downloaded and passed the +hash check, but not yet been written to disk. In this case, there is a risk the +piece will fail to be written to disk, in which case we won't have the piece +anymore, even though we announced it to peers. + +The other case is when we're very close to completing the download of a piece +and assume it will pass the hash check, we can announce it to peers to make it +available one round-trip sooner than otherwise. This lets libtorrent start +uploading the piece to interested peers immediately when the piece complete, +instead of waiting one round-trip for the peers to request it. + +This makes for the implementation slightly more complicated, since piece will +have more states and more complicated transitions. For instance, a piece could +be: + +1. hashed but not fully written to disk +2. fully written to disk but not hashed +3. not fully downloaded +4. downloaded and hash checked + +Once a piece is fully downloaded, the hash check could complete before any of +the write operations or it could complete after all write operations are +complete. + +peer classes +============ + +The peer classes feature in libtorrent allows a client to define custom groups +of peers and rate limit them individually. Each such group is called a *peer +class*. There are a few default peer classes that are always created: + +* global - all peers belong to this class, except peers on the local network +* local peers - all peers on the local network belongs to this class TCP peers +* tcp class - all peers connected over TCP belong to this class + +The TCP peers class is used by the uTP/TCP balancing logic, if it's enabled, to +throttle TCP peers. The global and local classes are used to adjust the global +rate limits. + +When the rate limits are adjusted for a specific torrent, a class is created +implicitly for that torrent. + +The default peer class IDs are defined as enums in the ``session`` class: + +.. code:: c++ + + enum { + global_peer_class_id, + tcp_peer_class_id, + local_peer_class_id + }; + +The default peer classes are automatically created on `session`__ startup, and +configured to apply to each respective type of connection. There's nothing +preventing a client from reconfiguring the peer class ip- and type filters +to disable or customize which peers they apply to. See `set_peer_class_filter()`__ +and `set_peer_class_type_filter()`__. + +A peer class can be considered a more general form of *labels* that some +clients have. Peer classes however are not just applied to torrents, but +ultimately the peers. + +Peer classes can be created with the `create_peer_class()`__ call (on the `session`__ +object), and deleted with the `delete_peer_class()`__ call. + +Peer classes are configured with the `set_peer_class()`__ `get_peer_class()`__ calls. + +Custom peer classes can be assigned based on the peer's IP address or the type +of transport protocol used. See `set_peer_class_filter()`__ and +`set_peer_class_type_filter()`__ for more information. + +peer class examples +------------------- + +Here are a few examples of common peer class operations. + +To make the global rate limit apply to local peers as well, update the IP-filter +based peer class assignment: + +.. code:: c++ + + std::uint32_t const mask = 1 << lt::session::global_peer_class_id; + ip_filter f; + + // for every IPv4 address, assign the global peer class + f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask); + + // for every IPv6 address, assign the global peer class + f.add_rule(make_address("::") + , make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + , mask); + ses.set_peer_class_filter(f); + +To make uTP sockets exempt from rate limiting: + +.. code:: c++ + + peer_class_type_filter flt = ses.get_peer_class_type_filter(); + // filter out the global and local peer class for uTP sockets, if these + // classes are set by the IP filter + flt.disallow(peer_class_type_filter::utp_socket, session::global_peer_class_id); + flt.disallow(peer_class_type_filter::utp_socket, session::local_peer_class_id); + + // this filter should not add the global or local peer class to utp sockets + flt.remove(peer_class_type_filter::utp_socket, session::global_peer_class_id); + flt.remove(peer_class_type_filter::utp_socket, session::local_peer_class_id); + + ses.set_peer_class_type_filter(flt); + +To make all peers on the internal network not subject to throttling: + +.. code:: c++ + + std::uint32_t const mask = 1 << lt::session::global_peer_class_id; + ip_filter f; + + // for every IPv4 address, assign the global peer class + f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask); + + // for every address on the local metwork, set the mask to 0 + f.add_rule(make_address("10.0.0.0"), make_address("10.255.255.255"), 0); + ses.set_peer_class_filter(f); + +SSL torrents +============ + +Torrents may have an SSL root (CA) certificate embedded in them. Such torrents +are called *SSL torrents*. An SSL torrent talks to all bittorrent peers over +SSL. The protocols are layered like this: + +.. image:: utp_stack.png + +During the SSL handshake, both peers need to authenticate by providing a +certificate that is signed by the CA certificate found in the .torrent file. +These peer certificates are expected to be provided to peers through some other +means than bittorrent. Typically by a peer generating a certificate request +which is sent to the publisher of the torrent, and the publisher returning a +signed certificate. + +In libtorrent, `set_ssl_certificate()`__ in `torrent_handle`__ is used to tell +libtorrent where to find the peer certificate and the private key for it. When +an SSL torrent is loaded, the `torrent_need_cert_alert`__ is posted to remind the +user to provide a certificate. + +A peer connecting to an SSL torrent MUST provide the *SNI* TLS extension +(server name indication). The server name is the hex encoded info-hash of the +torrent to connect to. This is required for the client accepting the connection +to know which certificate to present. + +SSL connections are accepted on a separate socket from normal bittorrent +connections. To enable support for SSL torrents, add a listen interface to the +`settings_pack::listen_interfaces`__ setting with the ``s`` suffix. For example:: + + 0.0.0.0:6881,0.0.0.0:6882s + +That will listen for normal bittorrent connections on port 6881 and for SSL +torrent connections on port 6882. + +This feature is only available if libtorrent is build with openssl support +(``TORRENT_USE_OPENSSL``) and requires at least OpenSSL version 1.0, since it +needs SNI support. + +Peer certificates must have at least one *SubjectAltName* field of type +DNSName. At least one of the fields must *exactly* match the name of the +torrent. This is a byte-by-byte comparison, the UTF-8 encoding must be +identical (i.e. there's no unicode normalization going on). This is the +recommended way of verifying certificates for HTTPS servers according to `RFC +2818`_. Note the difference that for torrents only *DNSName* fields are taken +into account (not IP address fields). The most specific (i.e. last) *Common +Name* field is also taken into account if no *SubjectAltName* did not match. + +If any of these fields contain a single asterisk ("*"), the certificate is +considered covering any torrent, allowing it to be reused for any torrent. + +The purpose of matching the torrent name with the fields in the peer +certificate is to allow a publisher to have a single root certificate for all +torrents it distributes, and issue separate peer certificates for each torrent. +A peer receiving a certificate will not necessarily be able to access all +torrents published by this root certificate (only if it has a "star cert"). + +.. _`RFC 2818`: https://www.ietf.org/rfc/rfc2818.txt + +testing +------- + +To test incoming SSL connections to an SSL torrent, one can use the following +*openssl* command:: + + openssl s_client -cert .pem -key .pem -CAfile \ + .pem -debug -connect 127.0.0.1:4433 -tls1 -servername + +To create a root certificate, the Distinguished Name (*DN*) is not taken into +account by bittorrent peers. You still need to specify something, but from +libtorrent's point of view, it doesn't matter what it is. libtorrent only makes +sure the peer certificates are signed by the correct root certificate. + +One way to create the certificates is to use the ``CA.sh`` script that comes +with openssl, like this (don't forget to enter a common Name for the +certificate):: + + CA.sh -newca + CA.sh -newreq + CA.sh -sign + +The torrent certificate is located in ``./demoCA/private/demoCA/cacert.pem``, +this is the pem file to include in the .torrent file. + +The peer's certificate is located in ``./newcert.pem`` and the certificate's +private key in ``./newkey.pem``. + +session statistics +================== + +libtorrent provides a mechanism to query performance and statistics counters +from its internals. + +The statistics consists of two fundamental types. *counters* and *gauges*. A +counter is a monotonically increasing value, incremented every time some event +occurs. For example, every time the network thread wakes up because a socket +became readable will increment a counter. Another example is every time a +socket receives *n* bytes, a counter is incremented by *n*. + +*Counters* are the most flexible of metrics. It allows the program to sample +the counter at any interval, and calculate average rates of increments to the +counter. Some events may be rare and need to be sampled over a longer period in +order to get useful rates, where other events may be more frequent and evenly +distributed that sampling it frequently yields useful values. Counters also +provides accurate overall counts. For example, converting samples of a download +rate into a total transfer count is not accurate and takes more samples. +Converting an increasing counter into a rate is easy and flexible. + +*Gauges* measure the instantaneous state of some kind. This is used for metrics +that are not counting events or flows, but states that can fluctuate. For +example, the number of torrents that are currently being downloaded. + +It's important to know whether a value is a counter or a gauge in order to +interpret it correctly. In order to query libtorrent for which counters and +gauges are available, call `session_stats_metrics()`__. This will return metadata +about the values available for inspection in libtorrent. It will include +whether a value is a counter or a gauge. The key information it includes is the +index used to extract the actual measurements for a specific counter or gauge. + +In order to take a sample, call `post_session_stats()`__ in the `session`__ object. +This will result in a `session_stats_alert`__ being posted. In this `alert`__ object, +there is an array of values, these values make up the sample. The value index +in the stats metric indicates which index the metric's value is stored in. + +The mapping between metric and value is not stable across versions of +libtorrent. Always query the metrics first, to find out the index at which the +value is stored, before interpreting the values array in the +`session_stats_alert`__. The mapping will *not* change during the runtime of your +process though, it's tied to a specific libtorrent version. You only have to +query the mapping once on startup (or every time ``libtorrent.so`` is loaded, +if it's done dynamically). + +The available stats metrics are: + +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Plugins.html#load_state() +__ reference-Core.html#add_extension() +__ reference-Session.html#session +__ reference-Core.html#torrent_info +__ reference-Session.html#async_add_torrent() +__ reference-Session.html#add_torrent() +__ reference-Session.html#session +__ reference-Session.html#wait_for_alert() +__ reference-Session.html#pop_alerts() +__ reference-Alerts.html#state_update_alert +__ reference-Alerts.html#alert +__ reference-Session.html#session +__ reference-Session.html#session +__ reference-Session.html#remove_torrent() +__ reference-Core.html#save_resume_data() +__ reference-Session.html#session +__ reference-Plugins.html#save_state() +__ reference-Session.html#session +__ reference-Create_Torrents.html#create_torrent +__ reference-Core.html#add_torrent_params +__ reference-Core.html#unset_flags() +__ reference-Core.html#queue_position +__ reference-Settings.html#active_limit +__ reference-Settings.html#auto_manage_interval +__ reference-Core.html#save_resume_data() +__ reference-Core.html#queue_position() +__ reference-Core.html#queue_position_up() +__ reference-Core.html#queue_position_down() +__ reference-Core.html#queue_position_top() +__ reference-Core.html#queue_position_bottom() +__ reference-Core.html#allocating +__ reference-Settings.html#active_downloads +__ reference-Core.html#queue_position +__ reference-Core.html#queue_position +__ reference-Settings.html#active_seeds +__ reference-Settings.html#share_ratio_limit +__ reference-Settings.html#seed_time_ratio_limit +__ reference-Settings.html#seed_time_limit +__ reference-Settings.html#active_tracker_limit +__ reference-Settings.html#active_dht_limit +__ reference-Settings.html#active_lsd_limit +__ reference-Settings.html#active_dht_limit +__ reference-Settings.html#auto_manage_prefer_seeds +__ reference-Settings.html#dont_count_slow_torrents +__ reference-Core.html#save_resume_data() +__ reference-Core.html#torrent_handle +__ reference-Core.html#read_resume_data() +__ reference-Core.html#add_torrent_params +__ reference-Session.html#async_add_torrent() +__ reference-Session.html#add_torrent() +__ reference-Session.html#session +__ reference-Settings.html#initial_picker_threshold +__ reference-Settings.html#use_parole_mode +__ reference-Settings.html#whole_pieces_threshold +__ reference-Settings.html#listen_interfaces +__ reference-Core.html#announce_endpoint +__ reference-Settings.html#choking_algorithm +__ reference-Settings.html#unchoke_slots_limit +__ reference-Settings.html#rate_choker_initial_threshold +__ reference-Session.html#session +__ reference-Session.html#set_peer_class_filter() +__ reference-Session.html#set_peer_class_type_filter() +__ reference-Session.html#create_peer_class() +__ reference-Session.html#session +__ reference-Session.html#delete_peer_class() +__ reference-Session.html#set_peer_class() +__ reference-Session.html#get_peer_class() +__ reference-Session.html#set_peer_class_filter() +__ reference-Session.html#set_peer_class_type_filter() +__ reference-Core.html#set_ssl_certificate() +__ reference-Core.html#torrent_handle +__ reference-Alerts.html#torrent_need_cert_alert +__ reference-Settings.html#listen_interfaces +__ reference-Session.html#session_stats_metrics() +__ reference-Session.html#post_session_stats() +__ reference-Session.html#session +__ reference-Alerts.html#session_stats_alert +__ reference-Alerts.html#alert +__ reference-Alerts.html#session_stats_alert + +.. include:: stats_counters.rst + +glossary +======== + +The libtorrent documentation use words that are bittorrent terms of art. This +section defines some of these words. For an overview of what bittorrent is and +how it works, see these slides_. For an introduction to the bittorrent DHT, see +`this presentation`_. + +.. _slides: bittorrent.pdf +.. _`this presentation`: https://vimeo.com/56044595 + +announce + The act of telling a tracker or the DHT network about the existence of + oneself and how other peers can connect, by specifying port one is listening + on. + +block + A subset of a piece. Almost always 16 kiB of payload, unless the piece size is + smaller. This is the granularity file payload is requested from peers on the + network. + +DHT + The distributed hash table is a cross-swarm, world-wide network of bittorrent + peers. It's loosely connected, implementing the Kademlia protocol. Its purpose + is to act as a tracker. Peers can announce their presence to nodes on the DHT + and other peers can discover them to join the swarm. + +HTTP tracker + A tracker that uses the HTTP protocol for announces. + +info dictionary + The subset of a torrent file that describes piece hashes and file names. This + is the only mandatory part necessary to join the swarm (network of peers) for + the torrent. + +info hash + The hash of the info dictionary. This uniquely identifies a torrent and is + used by the protocol to ensure peers talking to each other agree on which swarm + they are participating in. Sometimes spelled info-hash. + +leecher + A peer that is still interested in downloading more pieces for the torrent. + It is not a seed. + +magnet link + A URI containing the info hash for a torrent, allowing peers to join its + swarm. May optionally contain a display name, trackers and web seeds. + Typically magnet links rely on peers joining the swarm via the DHT. + +metadata + Synonymous to a torrent file + +peer + A computer running bittorrent client software that participates in the network + for a particular torrent/set of files. + +piece + The smallest number of bytes that can be validated when downloading (no + longer the case in bittorrent V2). The smallest part of the files that can be + advertised to other peers. The size of a piece is determined by the info + dictionary inside the torrent file. + +seed + A computer running bittorrent client software that has the complete files for + a specific torrent, able to share any piece for that file with other peers in + the network + +swarm + The network of peers participating in sharing and downloading of a specific torrent. + +torrent + May refer to a torrent file or the swarm (network of peers) created around + the torrent file. + +torrent file + A file ending in .torrent describing the content of a set of files (but not + containing the content). Importantly, it contains hashes of all files, split + up into pieces. It may optionally contain references to trackers and nodes on + the DHT network to aid peers in joining the network of peers sharing + these files. + +tracker + A server peers can announce to and receive other peers back belonging to the + same swarm. Trackers are used to introduce peers to each other, within a swarm. + When announcing, the info hash of the torrent is included. Trackers can + introduce peers to any info-hash that's specified, given other peers also use + the same tracker. Some trackers restrict which info hashes they support based + on a white list. + +UDP tracker + A tracker that uses a UDP based protocol for announces. + +web seed + A web server that is acting a seed, providing access to all pieces of all + files over HTTP. This is an extension that client software may or may not + support. + + + diff --git a/docs/merkle_tree.png b/docs/merkle_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..303b68f90004933b3ef604d4092d4f9af7252cfb GIT binary patch literal 18792 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 literal 0 HcmV?d00001 diff --git a/docs/our_delay_base.png b/docs/our_delay_base.png new file mode 100644 index 0000000000000000000000000000000000000000..d14ad9ed86ad21a5fed783be6a3beecd1f9c1f96 GIT binary patch literal 34999 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 + + + + + + +projects using libtorrent + + + + + +

    +
    + + libtorrent logo + +
    +

    projects using libtorrent

    + +

    These are some of the public projects that use libtorrent. If you want your +project listed here, let me know.

    +
    +

    deluge

    +

    deluge Torrent is a more full-featured yet still lightweight bittorrent +client. It has the ability to automatically resume partial downloads and +background to the system tray.

    +
    +
    +

    qBittorrent

    +

    qBittorrent is a free and open-source cross-platform bittorrent client written in Qt, that +is available for Linux, macOS and Windows and is released under GPLv2 license.

    +

    It comes with a powerful and easy-to-use graphical interface, as well as an embedded Web interface. +It has a range of features such as an RSS downloader, scheduling rate limits, torrent queueing, +automatic resuming, background downloading, and system tray icon with a password-protected lock.

    +

    Originally written by Christophe Dumez, currently maintained by sledgehammer999.

    +
    +
    +

    Tonidoplug

    +

    Tonidoplug is a tiny, low-power, low-cost home server and +NAS device powered by Tonido software that allows you to access +your applications, files, music and media from anywhere.

    +
    +
    +

    Folx

    +

    Folx is a torrent client and download manager for macOS. +The Free version of Folx has all the basic functionality of the torrent +client, which allows users to download and create torrent files. +Folx PRO (available for a small fee) features the possibility to search +for torrent files just from Folx interface. So there is no need to +browse through multiple torrent trackers searching for particular file.

    +
    +
    +

    Miro

    +

    Miro is a free application for channels of internet video (also known as +video podcasts and video RSS). Miro is designed to be easy to use and to give +you an elegant fullscreen viewing experience.

    +
    +
    +

    MooPolice

    +

    MooPolice is a windows bittorrent client with a unique look.

    +
    +
    +

    LeechCraft

    +

    LeechCraft LeechCraft is a free open source cross-platform extensible +software, which primary goal is support of file sharing networks and protocols +like HTTP and FTP.

    +
    +
    +

    Free download manager

    +

    FDM is a powerful, easy-to-use and absolutely free download accelerator and +manager. Moreover, FDM is 100% safe, open-source software distributed under +GPL License.

    +
    +
    +

    btg

    +

    btg is a Unix bittorrent client which is run as a daemon. It has multiple user +interfaces which connects to the daemon. One GUI (gtkmm), one terminal +interface (ncurses) and one web interface (accessible through a web browser). +Written by Michael Wojciechowski and Johan Strom.

    +
    +
    +

    electric sheep

    +

    electric sheep is a screensaver which collectively generates animations and +lets the users vote which one to live on.

    +
    +
    +

    Tvitty

    +

    Tvitty is a bittorrent client for Vista Media Center, which allows +searching and downloading of torrents directly on your TV.

    +
    +
    +

    hrktorrent

    +

    hrktorrent hrktorrent is a light console torrent client written in C++.

    +
    +
    +

    halite BitTorrent

    +

    Halite is a windows bittorrent client controllable via an XML-RPC +interface.

    +
    +
    +

    Arctic Torrent

    +

    Arctic Torrent is a light-weight +bittorrent client for windows. +Written by Cory Nelson.

    +
    +
    +

    Bubba

    +

    Bubba is a mini-sized server, designed to fit your home better than +an always running PC. Boasting Torrent downloader, DAAP streaming, +Web, E-mail, printer and FTP server etc.

    +
    +
    +

    Flush

    +

    Flush is a GTK-based BitTorrent client.

    +
    +
    +

    Lince

    +

    Lince is a bittorrent client using libtorrent to handle bittorrent protocol +and gtkmm for the interface, it has been designed to be a light and full +featured client.

    +
    +
    +

    BitSlug

    +

    BitSlug is a macOS cocoa client.

    +
    +
    +

    DelCo

    +

    DelCo is a research project at Tampere university of technology, Finland.

    +
    +
    +

    Torrent2Exe

    +

    Torrent2Exe is a small BitTorrent client. Its basic idea is to +let users download a custom-built EXE program with the torrent file +integrated into it.

    +
    +
    +

    ZyXEL NSA-220

    +

    ZyXEL NSA220 makes it easy to store, protect and share files between users +on your home network. The built-in DLNA server works with many set top boxes +to allow you to play back music, watch video files, or view photos on your +home theater system, while the built in download manager can automatically +download video and audio podcasts as well as allow you to download bittorrent +files without needing to leave your computer on.

    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/projects.rst b/docs/projects.rst new file mode 100644 index 0000000..40fd42f --- /dev/null +++ b/docs/projects.rst @@ -0,0 +1,198 @@ +projects using libtorrent +========================= + +These are some of the public projects that use libtorrent. If you want your +project listed here, let me_ know. + +.. _me: mailto:arvid@libtorrent.org + + +deluge +------ + +`deluge Torrent`_ is a more full-featured yet still lightweight bittorrent +client. It has the ability to automatically resume partial downloads and +background to the system tray. + +.. _`deluge Torrent`: http://deluge-torrent.org/ + +qBittorrent +----------- + +qBittorrent_ is a free and open-source cross-platform bittorrent client written in Qt, that +is available for Linux, macOS and Windows and is released under GPLv2 license. + +It comes with a powerful and easy-to-use graphical interface, as well as an embedded Web interface. +It has a range of features such as an RSS downloader, scheduling rate limits, torrent queueing, +automatic resuming, background downloading, and system tray icon with a password-protected lock. + +Originally written by Christophe Dumez, currently maintained by sledgehammer999. + +.. _qBittorrent: http://www.qbittorrent.org/ + +Tonidoplug +---------- + +Tonidoplug_ is a tiny, low-power, low-cost home server and +NAS device powered by Tonido software that allows you to access +your applications, files, music and media from anywhere. + +.. _Tonidoplug: http://www.tonidoplug.com/ + +Folx +---- + +Folx_ is a torrent client and download manager for macOS. +The Free version of Folx has all the basic functionality of the torrent +client, which allows users to download and create torrent files. +Folx PRO (available for a small fee) features the possibility to search +for torrent files just from Folx interface. So there is no need to +browse through multiple torrent trackers searching for particular file. + +.. _folx: http://www.mac-downloader.com/ + +Miro +---- + +Miro_ is a free application for channels of internet video (also known as +video podcasts and video RSS). Miro is designed to be easy to use and to give +you an elegant fullscreen viewing experience. + +.. _Miro: http://getmiro.com + +MooPolice +--------- + +MooPolice_ is a windows bittorrent client with a unique look. + +.. _MooPolice: http://www.moopolice.de + + +LeechCraft +---------- + +LeechCraft_ LeechCraft is a free open source cross-platform extensible +software, which primary goal is support of file sharing networks and protocols +like HTTP and FTP. + +.. _LeechCraft: http://leechcraft.org/ + +Free download manager +--------------------- + +FDM_ is a powerful, easy-to-use and absolutely free download accelerator and +manager. Moreover, FDM is 100% safe, open-source software distributed under +GPL License. + +.. _FDM: http://www.freedownloadmanager.org/ + +btg +--- + +btg_ is a Unix bittorrent client which is run as a daemon. It has multiple user +interfaces which connects to the daemon. One GUI (gtkmm), one terminal +interface (ncurses) and one web interface (accessible through a web browser). +Written by Michael Wojciechowski and Johan Strom. + +.. _btg: https://sourceforge.net/projects/btg/ + +electric sheep +-------------- + +`electric sheep`_ is a screensaver which collectively generates animations and +lets the users vote which one to live on. + +.. _`electric sheep`: http://electricsheep.org + +Tvitty +------ + +Tvitty_ is a bittorrent client for Vista Media Center, which allows +searching and downloading of torrents directly on your TV. + +.. _Tvitty: https://tvitty.soft112.com/ + +hrktorrent +---------- + +hrktorrent_ hrktorrent is a light console torrent client written in C++. + +.. _hrktorrent: http://50hz.ws/hrktorrent/ + +halite BitTorrent +----------------- + +Halite_ is a windows bittorrent client controllable via an XML-RPC +interface. + +.. _Halite: http://www.binarynotions.com/halite-bittorrent-client + +Arctic Torrent +-------------- + +`Arctic Torrent`_ is a light-weight +bittorrent client for windows. +Written by Cory Nelson. + +.. _`Arctic Torrent`: https://www.softpedia.com/get/Internet/File-Sharing/Arctic-Torrent.shtml + +Bubba +----- + +Bubba_ is a mini-sized server, designed to fit your home better than +an always running PC. Boasting Torrent downloader, DAAP streaming, +Web, E-mail, printer and FTP server etc. + +.. _Bubba: https://excitostore.com/ + +Flush +----- + +Flush_ is a GTK-based BitTorrent client. + +.. _Flush: https://sourceforge.net/projects/flush/ + +Lince +----- + +Lince_ is a bittorrent client using libtorrent to handle bittorrent protocol +and gtkmm for the interface, it has been designed to be a light and full +featured client. + +.. _Lince: http://lincetorrent.sourceforge.net/ + +BitSlug +------- + +BitSlug_ is a macOS cocoa client. + +.. _BitSlug: http://bitslug.sourceforge.net/ + +DelCo +----- + +DelCo_ is a research project at Tampere university of technology, Finland. + +.. _DelCo: http://delco.cs.tut.fi/ + +Torrent2Exe +----------- + +Torrent2Exe_ is a small BitTorrent client. Its basic idea is to +let users download a custom-built EXE program with the torrent file +integrated into it. + +.. _Torrent2Exe: http://torrent2exe.com + +ZyXEL NSA-220 +------------- + +ZyXEL_ NSA220 makes it easy to store, protect and share files between users +on your home network. The built-in DLNA server works with many set top boxes +to allow you to play back music, watch video files, or view photos on your +home theater system, while the built in download manager can automatically +download video and audio podcasts as well as allow you to download bittorrent +files without needing to leave your computer on. + +.. _ZyXEL: https://www.zyxel.com/uk/en/products_services/nsa_220_plus.shtml + diff --git a/docs/python_binding.html b/docs/python_binding.html new file mode 100644 index 0000000..fa5d1be --- /dev/null +++ b/docs/python_binding.html @@ -0,0 +1,227 @@ + + + + + + +libtorrent python binding + + + + + + + +
    +
    + + + + +
    +

    libtorrent python binding

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +
    +

    building

    +

    Building the libtorrent python bindings will produce a shared library (DLL) +which is a python module that can be imported in a python program.

    +
    +

    building using boost build (windows)

    +

    Download and install Visual C++ 2015 Build Tools

    +

    Download Boost libraries Extract it to c:/Libraries/boost_1_73_0 and create these environmental vars:

    +
      +
    1. BOOST_BUILD_PATH: "c:/Libraries/boost_1_73_0/tools/build/"
    2. +
    3. BOOST_ROOT: "c:/Libraries/boost_1_73_0/"
    4. +
    +

    Navigate to BOOST_ROOT, execute "bootstrap.bat" and add to the path "c:/Libraries/boost_1_73_0/"

    +

    Move the file user-config.jam from %BOOST_BUILD_PATH%/example/ to %BOOST_BUILD_PATH%/user-config.jam and add this at the end:

    +
    +using msvc : 14.0 : : /std:c++11 ;
    +using python : 3.5 : C:/Users/<UserName>/AppData/Local/Programs/Python/Python35 : C:/Users/<UserName>/AppData/Local/Programs/Python/Python35/include : C:/Users/<UserName>/AppData/Local/Programs/Python/Python35/libs ;
    +
    +

    (change the python path for yours)

    +
    +
    Navigate to bindings/python and execute::
    +
    python setup.py build --bjam
    +
    +

    Note: If you are using 64 bits python you should edit setup.py and add this to the b2 command: +address-model=64

    +

    This will create the file libtorrent.pyd inside build/lib/ that contains the binding.

    +
    +
    +

    building using boost build (others)

    +

    To set up your build environment, you need to add some settings to your +$BOOST_BUILD_PATH/user-config.jam.

    +

    A similar line to this line should be in the file (could be another python version):

    +
    +#using python : 2.3 ;
    +
    +

    Uncomment it and change it with the version of python you have installed or want to use. If +you've installed python in a non-standard location, you have to add the prefix +path used when you installed python as a second option. Like this:

    +
    +using python : 2.6 : /usr/bin/python2.6 : /usr/include/python2.6 : /usr/lib/python2.6 ;
    +
    +

    The bindings require at least python version 2.2.

    +

    For more information on how to install and set up boost-build, see the +building libtorrent section.

    +

    Once you have boost-build set up, you cd to the bindings/python +directory and invoke b2 with the appropriate settings. For the available +build variants, see libtorrent build options.

    +

    For example:

    +
    +$ b2 stage_module stage_dependencies
    +
    +

    This will produce a libtorrent python module in the current directory (file +name extension depends on operating system). The libraries the python module depends +on will be copied into ./dependencies.

    +
    +
    +
    +

    using libtorrent in python

    +

    The python interface is nearly identical to the C++ interface. Please refer to +the library reference. The main differences are:

    +
    +
    asio::tcp::endpoint
    +
    The endpoint type is represented as a tuple of a string (as the address) and an int for +the port number. E.g. ("127.0.0.1", 6881) represents the localhost port 6881.
    +
    lt::time_duration
    +
    The time duration is represented as a number of seconds in a regular integer.
    +
    +

    The following functions takes a reference to a container that is filled with +entries by the function. The python equivalent of these functions instead returns +a list of entries.

    +
      +
    • torrent_handle::get_peer_info
    • +
    • torrent_handle::file_progress
    • +
    • torrent_handle::get_download_queue
    • +
    • torrent_handle::piece_availability
    • +
    +

    create_torrent::add_node() takes two arguments, one string and one integer, +instead of a pair. The string is the address and the integer is the port.

    +

    session::apply_settings() accepts a dictionary with keys matching the names +of settings in settings_pack. +When calling apply_settings, the dictionary does not need to have every settings set, +keys that are not present are not updated.

    +

    To get a python dictionary of the settings, call session::get_settings.

    +

    Retrieving session statistics in Python is more convenient than that in C++. The +statistics are stored as an array in session_stats_alert, which will be +posted after calling post_session_stats() in the session object. In +order to interpret the statistics array, in C++ it is required to call +session_stats_metrics() to get the indices of these metrics, while in Python +it can be done using session_stats_alert.values["NAME_OF_METRIC"], where +NAME_OF_METRIC is the name of a metric.

    +
    +
    +

    set_alert_notify

    +

    The set_alert_notify() function is not compatible with python. Since it +requires locking the GIL from within the libtorrent thread, to call the callback, +it can cause a deadlock with the main thread.

    +

    Instead, use the python-specific set_alert_fd() which takes a file descriptor +that will have 1 byte written to it to notify the client that there are new +alerts to be popped.

    +

    The file descriptor should be set to non-blocking mode. If writing to the +file/sending to the socket blocks, libtorrent's internal thread will stall.

    +

    This can be used with socket.socketpair(), for example. The file descriptor +is what fileno() returns on a socket.

    +
    +
    +

    Example

    +

    For an example python program, see client.py in the bindings/python +directory.

    +

    A very simple example usage of the module would be something like this:

    +
    +import libtorrent as lt
    +import time
    +import sys
    +
    +ses = lt.session({'listen_interfaces': '0.0.0.0:6881'})
    +
    +info = lt.torrent_info(sys.argv[1])
    +h = ses.add_torrent({'ti': info, 'save_path': '.'})
    +s = h.status()
    +print('starting', s.name)
    +
    +while (not s.is_seeding):
    +    s = h.status()
    +
    +    print('\r%.2f%% complete (down: %.1f kB/s up: %.1f kB/s peers: %d) %s' % (
    +        s.progress * 100, s.download_rate / 1000, s.upload_rate / 1000,
    +        s.num_peers, s.state), end=' ')
    +
    +    alerts = ses.pop_alerts()
    +    for a in alerts:
    +        if a.category() & lt.alert.category_t.error_notification:
    +            print(a)
    +
    +    sys.stdout.flush()
    +
    +    time.sleep(1)
    +
    +print(h.status().name, 'complete')
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/python_binding.rst b/docs/python_binding.rst new file mode 100644 index 0000000..8a586ce --- /dev/null +++ b/docs/python_binding.rst @@ -0,0 +1,157 @@ +========================= +libtorrent python binding +========================= + +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +building +======== + +Building the libtorrent python bindings will produce a shared library (DLL) +which is a python module that can be imported in a python program. + +building using boost build (windows) +------------------------------------ + +Download and install `Visual C++ 2015 Build Tools`__ + +.. __: http://landinghub.visualstudio.com/visual-cpp-build-tools + +Download `Boost libraries`__ Extract it to c:/Libraries/boost_1_73_0 and create these environmental vars: + +.. __: http://www.boost.org/users/history/ + +1. BOOST_BUILD_PATH: "c:/Libraries/boost_1_73_0/tools/build/" +2. BOOST_ROOT: "c:/Libraries/boost_1_73_0/" + +Navigate to ``BOOST_ROOT``, execute "bootstrap.bat" and add to the path "c:/Libraries/boost_1_73_0/" + +Move the file ``user-config.jam`` from ``%BOOST_BUILD_PATH%/example/`` to ``%BOOST_BUILD_PATH%/user-config.jam`` and add this at the end: + +:: + + using msvc : 14.0 : : /std:c++11 ; + using python : 3.5 : C:/Users//AppData/Local/Programs/Python/Python35 : C:/Users//AppData/Local/Programs/Python/Python35/include : C:/Users//AppData/Local/Programs/Python/Python35/libs ; + +(change the python path for yours) + +Navigate to bindings/python and execute:: + python setup.py build --bjam + +Note: If you are using 64 bits python you should edit setup.py and add this to the b2 command: +``address-model=64`` + +This will create the file libtorrent.pyd inside build/lib/ that contains the binding. + +building using boost build (others) +----------------------------------- +To set up your build environment, you need to add some settings to your +``$BOOST_BUILD_PATH/user-config.jam``. + +A similar line to this line should be in the file (could be another python version):: + + #using python : 2.3 ; + +Uncomment it and change it with the version of python you have installed or want to use. If +you've installed python in a non-standard location, you have to add the prefix +path used when you installed python as a second option. Like this:: + + using python : 2.6 : /usr/bin/python2.6 : /usr/include/python2.6 : /usr/lib/python2.6 ; + +The bindings require *at least* python version 2.2. + +For more information on how to install and set up boost-build, see the +`building libtorrent`__ section. + +.. __: building.html#step-2-setup-bbv2 + +Once you have boost-build set up, you cd to the ``bindings/python`` +directory and invoke ``b2`` with the appropriate settings. For the available +build variants, see `libtorrent build options`_. + +.. _`libtorrent build options`: building.html#step-3-building-libtorrent + +For example:: + + $ b2 stage_module stage_dependencies + +This will produce a ``libtorrent`` python module in the current directory (file +name extension depends on operating system). The libraries the python module depends +on will be copied into ``./dependencies``. + +using libtorrent in python +========================== + +The python interface is nearly identical to the C++ interface. Please refer to +the `library reference`_. The main differences are: + +asio::tcp::endpoint + The endpoint type is represented as a tuple of a string (as the address) and an int for + the port number. E.g. ``("127.0.0.1", 6881)`` represents the localhost port 6881. + +lt::time_duration + The time duration is represented as a number of seconds in a regular integer. + +The following functions takes a reference to a container that is filled with +entries by the function. The python equivalent of these functions instead returns +a list of entries. + +* torrent_handle::get_peer_info +* torrent_handle::file_progress +* torrent_handle::get_download_queue +* torrent_handle::piece_availability + +``create_torrent::add_node()`` takes two arguments, one string and one integer, +instead of a pair. The string is the address and the integer is the port. + +``session::apply_settings()`` accepts a dictionary with keys matching the names +of settings in settings_pack. +When calling ``apply_settings``, the dictionary does not need to have every settings set, +keys that are not present are not updated. + +To get a python dictionary of the settings, call ``session::get_settings``. + +.. _`library reference`: reference.html + +Retrieving session statistics in Python is more convenient than that in C++. The +statistics are stored as an array in ``session_stats_alert``, which will be +posted after calling ``post_session_stats()`` in the ``session`` object. In +order to interpret the statistics array, in C++ it is required to call +``session_stats_metrics()`` to get the indices of these metrics, while in Python +it can be done using ``session_stats_alert.values["NAME_OF_METRIC"]``, where +``NAME_OF_METRIC`` is the name of a metric. + +set_alert_notify +================ + +The ``set_alert_notify()`` function is not compatible with python. Since it +requires locking the GIL from within the libtorrent thread, to call the callback, +it can cause a deadlock with the main thread. + +Instead, use the python-specific ``set_alert_fd()`` which takes a file descriptor +that will have 1 byte written to it to notify the client that there are new +alerts to be popped. + +The file descriptor should be set to non-blocking mode. If writing to the +file/sending to the socket blocks, libtorrent's internal thread will stall. + +This can be used with ``socket.socketpair()``, for example. The file descriptor +is what ``fileno()`` returns on a socket. + +Example +======= + +For an example python program, see ``client.py`` in the ``bindings/python`` +directory. + +A very simple example usage of the module would be something like this: + +.. include:: ../bindings/python/simple_client.py + :code: python + :tab-width: 2 + :start-after: from __future__ import print_function + diff --git a/docs/read_disk_buffers.diagram b/docs/read_disk_buffers.diagram new file mode 100644 index 0000000..1d04a25 --- /dev/null +++ b/docs/read_disk_buffers.diagram @@ -0,0 +1,16 @@ + + "copy into peer's" "encrypt in place" ++------------------+ "send buffer" +---------------+ "(no copy)" +---------------+ +| "receive buffer" +----------------->| "send buffer" +------------------>| "encrypted" | +| | | | | "send buffer" | ++------------------+ +---------------+ +---------------+ + ^ | + | "read() from file" "write() to socket" | + | "(copy)" "user space" "(copy)" | +- - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - + | "kernel space" | + | v ++-------+-------------+ +-----------------+ +| "kernel page cache" | | "socket kernel" | +| | | "buffer" | ++---------------------+ +-----------------+ diff --git a/docs/read_disk_buffers.png b/docs/read_disk_buffers.png new file mode 100644 index 0000000000000000000000000000000000000000..1ee3623c3239ca6785d9c4d1de82d256c77049ea GIT binary patch literal 8371 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 ACIA2c literal 0 HcmV?d00001 diff --git a/docs/reference-Alerts.html b/docs/reference-Alerts.html new file mode 100644 index 0000000..b6b71ce --- /dev/null +++ b/docs/reference-Alerts.html @@ -0,0 +1,3474 @@ + + + + + + +reference-Alerts.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Alerts

    +
    +

    Table of contents

    + +
    +

    The pop_alerts() function on session is the main interface for retrieving +alerts (warnings, messages and errors from libtorrent). If no alerts have +been posted by libtorrent pop_alerts() will return an empty list.

    +

    By default, only errors are reported. settings_pack::alert_mask can be +used to specify which kinds of events should be reported. The alert mask is +a combination of the alert_category_t flags in the alert class.

    +

    Every alert belongs to one or more category. There is a cost associated with +posting alerts. Only alerts that belong to an enabled category are +posted. Setting the alert bitmask to 0 will disable all alerts (except those +that are non-discardable). Alerts that are responses to API calls such as +save_resume_data() and post_session_stats() are non-discardable and will be +posted even if their category is disabled.

    +

    There are other alert base classes that some alerts derive from, all the +alerts that are generated for a specific torrent are derived from +torrent_alert, and tracker events derive from tracker_alert.

    +

    Alerts returned by pop_alerts() are only valid until the next call to +pop_alerts(). You may not copy an alert object to access it after the next +call to pop_alerts(). Internal members of alerts also become invalid once +pop_alerts() is called again.

    +[report issue]
    +

    alert

    +

    Declared in "libtorrent/alert.hpp"

    +

    The alert class is the base class that specific messages are derived from. +alert types are not copyable, and cannot be constructed by the client. The +pointers returned by libtorrent are short lived (the details are described +under session_handle::pop_alerts())

    +
    +class alert
    +{
    +   time_point timestamp () const;
    +   virtual int type () const noexcept = 0;
    +   virtual char const* what () const noexcept = 0;
    +   virtual std::string message () const = 0;
    +   virtual alert_category_t category () const noexcept = 0;
    +
    +   static constexpr alert_category_t error_notification  = 0_bit;
    +   static constexpr alert_category_t peer_notification  = 1_bit;
    +   static constexpr alert_category_t port_mapping_notification  = 2_bit;
    +   static constexpr alert_category_t storage_notification  = 3_bit;
    +   static constexpr alert_category_t tracker_notification  = 4_bit;
    +   static constexpr alert_category_t connect_notification  = 5_bit;
    +   static constexpr alert_category_t status_notification  = 6_bit;
    +   static constexpr alert_category_t ip_block_notification  = 8_bit;
    +   static constexpr alert_category_t performance_warning  = 9_bit;
    +   static constexpr alert_category_t dht_notification  = 10_bit;
    +   static constexpr alert_category_t stats_notification  = 11_bit;
    +   static constexpr alert_category_t session_log_notification  = 13_bit;
    +   static constexpr alert_category_t torrent_log_notification  = 14_bit;
    +   static constexpr alert_category_t peer_log_notification  = 15_bit;
    +   static constexpr alert_category_t incoming_request_notification  = 16_bit;
    +   static constexpr alert_category_t dht_log_notification  = 17_bit;
    +   static constexpr alert_category_t dht_operation_notification  = 18_bit;
    +   static constexpr alert_category_t port_mapping_log_notification  = 19_bit;
    +   static constexpr alert_category_t picker_log_notification  = 20_bit;
    +   static constexpr alert_category_t file_progress_notification  = 21_bit;
    +   static constexpr alert_category_t piece_progress_notification  = 22_bit;
    +   static constexpr alert_category_t upload_notification  = 23_bit;
    +   static constexpr alert_category_t block_progress_notification  = 24_bit;
    +   static constexpr alert_category_t all_categories  = alert_category_t::all();
    +};
    +
    +[report issue]
    +

    timestamp()

    +
    +time_point timestamp () const;
    +
    +

    a timestamp is automatically created in the constructor

    +[report issue]
    +
    +

    type()

    +
    +virtual int type () const noexcept = 0;
    +
    +

    returns an integer that is unique to this alert type. It can be +compared against a specific alert by querying a static constant called alert_type +in the alert. It can be used to determine the run-time type of an alert* in +order to cast to that alert type and access specific members.

    +

    e.g:

    +
    +std::vector<alert*> alerts;
    +ses.pop_alerts(&alerts);
    +for (alert* a : alerts) {
    +        switch (a->type()) {
    +
    +                case read_piece_alert::alert_type:
    +                {
    +                        auto* p = static_cast<read_piece_alert*>(a);
    +                        if (p->ec) {
    +                                // read_piece failed
    +                                break;
    +                        }
    +                        // use p
    +                        break;
    +                }
    +                case file_renamed_alert::alert_type:
    +                {
    +                        // etc...
    +                }
    +        }
    +}
    +
    +[report issue]
    +
    +

    what()

    +
    +virtual char const* what () const noexcept = 0;
    +
    +

    returns a string literal describing the type of the alert. It does +not include any information that might be bundled with the alert.

    +[report issue]
    +
    +

    message()

    +
    +virtual std::string message () const = 0;
    +
    +

    generate a string describing the alert and the information bundled +with it. This is mainly intended for debug and development use. It is not suitable +to use this for applications that may be localized. Instead, handle each alert +type individually and extract and render the information from the alert depending +on the locale.

    +[report issue]
    +
    +

    category()

    +
    +virtual alert_category_t category () const noexcept = 0;
    +
    +

    returns a bitmask specifying which categories this alert belong to.

    +[report issue]
    +
    +
    +

    dht_routing_bucket

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    struct to hold information about a single DHT routing table bucket

    +
    +struct dht_routing_bucket
    +{
    +   int num_nodes;
    +   int num_replacements;
    +   int last_active;
    +};
    +
    + +[report issue]
    +
    num_nodes num_replacements
    +
    the total number of nodes and replacement nodes +in the routing table
    +
    +[report issue]
    +
    last_active
    +
    number of seconds since last activity
    +
    +[report issue]
    +
    +

    torrent_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is a base class for alerts that are associated with a +specific torrent. It contains a handle to the torrent.

    +
    +struct torrent_alert : alert
    +{
    +   std::string message () const override;
    +   char const* torrent_name () const;
    +
    +   torrent_handle handle;
    +};
    +
    +[report issue]
    +

    message()

    +
    +std::string message () const override;
    +
    +

    returns the message associated with this alert

    +[report issue]
    +
    handle
    +
    The torrent_handle pointing to the torrent this +alert is associated with.
    +
    +[report issue]
    +
    +
    +

    peer_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The peer alert is a base class for alerts that refer to a specific peer. It includes all +the information to identify the peer. i.e. ip and peer-id.

    +
    +struct peer_alert : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   aux::noexcept_movable<tcp::endpoint> endpoint;
    +   peer_id pid;
    +};
    +
    +[report issue]
    +
    endpoint
    +
    The peer's IP address and port.
    +
    +[report issue]
    +
    pid
    +
    the peer ID, if known.
    +
    +[report issue]
    +
    +

    tracker_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is a base class used for alerts that are associated with a +specific tracker. It derives from torrent_alert since a tracker +is also associated with a specific torrent.

    +
    +struct tracker_alert : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* tracker_url () const;
    +
    +   aux::noexcept_movable<tcp::endpoint> local_endpoint;
    +};
    +
    +[report issue]
    +

    tracker_url()

    +
    +char const* tracker_url () const;
    +
    +

    returns a 0-terminated string of the tracker's URL

    +[report issue]
    +
    local_endpoint
    +
    endpoint of the listen interface being announced
    +
    +[report issue]
    +
    +
    +

    torrent_removed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The torrent_removed_alert is posted whenever a torrent is removed. Since +the torrent handle in its base class will always be invalid (since the torrent +is already removed) it has the info hash as a member, to identify it. +It's posted when the status_notification bit is set in the alert_mask.

    +

    Even though the handle member doesn't point to an existing torrent anymore, +it is still useful for comparing to other handles, which may also no +longer point to existing torrents, but to the same non-existing torrents.

    +

    The torrent_handle acts as a weak_ptr, even though its object no +longer exists, it can still compare equal to another weak pointer which +points to the same non-existent object.

    +
    +struct torrent_removed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    read_piece_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the asynchronous read operation initiated by +a call to torrent_handle::read_piece() is completed. If the read failed, the torrent +is paused and an error state is set and the buffer member of the alert +is 0. If successful, buffer points to a buffer containing all the data +of the piece. piece is the piece index that was read. size is the +number of bytes that was read.

    +

    If the operation fails, error will indicate what went wrong.

    +
    +struct read_piece_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   error_code const error;
    +   boost::shared_array<char> const buffer;
    +   piece_index_t const piece;
    +   int const size;
    +};
    +
    +[report issue]
    +
    +

    file_completed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted whenever an individual file completes its download. i.e. +All pieces overlapping this file have passed their hash check.

    +
    +struct file_completed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   file_index_t const index;
    +};
    +
    +[report issue]
    +
    index
    +
    refers to the index of the file that completed.
    +
    +[report issue]
    +
    +

    file_renamed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation succeeds.

    +
    +struct file_renamed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* new_name () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   file_index_t const index;
    +};
    +
    +[report issue]
    +
    index
    +
    refers to the index of the file that was renamed,
    +
    +[report issue]
    +
    +

    file_rename_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation failed.

    +
    +struct file_rename_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   file_index_t const index;
    +   error_code const error;
    +};
    +
    + +[report issue]
    +
    index error
    +
    refers to the index of the file that was supposed to be renamed, +error is the error code returned from the filesystem.
    +
    +[report issue]
    +
    +

    performance_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a limit is reached that might have a negative impact on +upload or download rate performance.

    +
    +struct performance_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   enum performance_warning_t
    +   {
    +      outstanding_disk_buffer_limit_reached,
    +      outstanding_request_limit_reached,
    +      upload_limit_too_low,
    +      download_limit_too_low,
    +      send_buffer_watermark_too_low,
    +      too_many_optimistic_unchoke_slots,
    +      too_high_disk_queue_limit,
    +      aio_limit_reached,
    +      deprecated_bittyrant_with_no_uplimit,
    +      too_few_outgoing_ports,
    +      too_few_file_descriptors,
    +      num_warnings,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::performance_warning;
    +   performance_warning_t const warning_code;
    +};
    +
    +[report issue]
    +

    enum performance_warning_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    outstanding_disk_buffer_limit_reached0This warning means that the number of bytes queued to be written to disk +exceeds the max disk byte queue setting (settings_pack::max_queued_disk_bytes). +This might restrict the download rate, by not queuing up enough write jobs +to the disk I/O thread. When this alert is posted, peer connections are +temporarily stopped from downloading, until the queued disk bytes have fallen +below the limit again. Unless your max_queued_disk_bytes setting is already +high, you might want to increase it to get better performance.
    outstanding_request_limit_reached1This is posted when libtorrent would like to send more requests to a peer, +but it's limited by settings_pack::max_out_request_queue. The queue length +libtorrent is trying to achieve is determined by the download rate and the +assumed round-trip-time (settings_pack::request_queue_time). The assumed +round-trip-time is not limited to just the network RTT, but also the remote disk +access time and message handling time. It defaults to 3 seconds. The target number +of outstanding requests is set to fill the bandwidth-delay product (assumed RTT +times download rate divided by number of bytes per request). When this alert +is posted, there is a risk that the number of outstanding requests is too low +and limits the download rate. You might want to increase the max_out_request_queue +setting.
    upload_limit_too_low2This warning is posted when the amount of TCP/IP overhead is greater than the +upload rate limit. When this happens, the TCP/IP overhead is caused by a much +faster download rate, triggering TCP ACK packets. These packets eat into the +rate limit specified to libtorrent. When the overhead traffic is greater than +the rate limit, libtorrent will not be able to send any actual payload, such +as piece requests. This means the download rate will suffer, and new requests +can be sent again. There will be an equilibrium where the download rate, on +average, is about 20 times the upload rate limit. If you want to maximize the +download rate, increase the upload rate limit above 5% of your download capacity.
    download_limit_too_low3This is the same warning as upload_limit_too_low but referring to the download +limit instead of upload. This suggests that your download rate limit is much lower +than your upload capacity. Your upload rate will suffer. To maximize upload rate, +make sure your download rate limit is above 5% of your upload capacity.
    send_buffer_watermark_too_low4

    We're stalled on the disk. We want to write to the socket, and we can write +but our send buffer is empty, waiting to be refilled from the disk. +This either means the disk is slower than the network connection +or that our send buffer watermark is too small, because we can +send it all before the disk gets back to us. +The number of bytes that we keep outstanding, requested from the disk, is calculated +as follows:

    +
    +min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark))
    +
    +

    If you receive this alert, you might want to either increase your send_buffer_watermark +or send_buffer_watermark_factor.

    +
    too_many_optimistic_unchoke_slots5If the half (or more) of all upload slots are set as optimistic unchoke slots, this +warning is issued. You probably want more regular (rate based) unchoke slots.
    too_high_disk_queue_limit6If the disk write queue ever grows larger than half of the cache size, this warning +is posted. The disk write queue eats into the total disk cache and leaves very little +left for the actual cache. This causes the disk cache to oscillate in evicting large +portions of the cache before allowing peers to download any more, onto the disk write +queue. Either lower max_queued_disk_bytes or increase cache_size.
    aio_limit_reached7 
    deprecated_bittyrant_with_no_uplimit8 
    too_few_outgoing_ports9This is generated if outgoing peer connections are failing because of address in use +errors, indicating that settings_pack::outgoing_ports is set and is too small of +a range. Consider not using the outgoing_ports setting at all, or widen the range to +include more ports.
    too_few_file_descriptors10 
    num_warnings11 
    +[report issue]
    +
    +
    +

    state_changed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Generated whenever a torrent changes its state.

    +
    +struct state_changed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   torrent_status::state_t const state;
    +   torrent_status::state_t const prev_state;
    +};
    +
    +[report issue]
    +
    state
    +
    the new state of the torrent.
    +
    +[report issue]
    +
    prev_state
    +
    the previous state.
    +
    +[report issue]
    +
    +

    tracker_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated on tracker time outs, premature disconnects, +invalid response or a HTTP response other than "200 OK". From the alert +you can get the handle to the torrent the tracker belongs to.

    +

    The times_in_row member says how many times in a row this tracker has +failed. status_code is the code returned from the HTTP server. 401 +means the tracker needs authentication, 404 means not found etc. If the +tracker timed out, the code will be set to 0.

    +
    +struct tracker_error_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* error_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker | alert_category::error;
    +   int const times_in_row;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    error_message()

    +
    +char const* error_message () const;
    +
    +

    the message associated with this error

    +[report issue]
    +
    +
    +

    tracker_warning_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is triggered if the tracker reply contains a warning field. +Usually this means that the tracker announce was successful, but the +tracker has a message to the client.

    +
    +struct tracker_warning_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* warning_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker | alert_category::error;
    +};
    +
    +[report issue]
    +

    warning_message()

    +
    +char const* warning_message () const;
    +
    +

    the message associated with this warning

    +[report issue]
    +
    +
    +

    scrape_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a scrape request succeeds.

    +
    +struct scrape_reply_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker;
    +   int const incomplete;
    +   int const complete;
    +};
    +
    + +[report issue]
    +
    incomplete complete
    +
    the data returned in the scrape response. These numbers +may be -1 if the response was malformed.
    +
    +[report issue]
    +
    +

    scrape_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    If a scrape request fails, this alert is generated. This might be due +to the tracker timing out, refusing connection or returning an http response +code indicating an error.

    +
    +struct scrape_failed_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* error_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker | alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    error_message()

    +
    +char const* error_message () const;
    +
    +

    if the error indicates there is an associated message, this returns +that message. Otherwise and empty string.

    +[report issue]
    +
    error
    +
    the error itself. This may indicate that the tracker sent an error +message (error::tracker_failure), in which case it can be +retrieved by calling error_message().
    +
    +[report issue]
    +
    +
    +

    tracker_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is only for informational purpose. It is generated when a tracker announce +succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or +the DHT.

    +
    +struct tracker_reply_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker;
    +   int const num_peers;
    +};
    +
    +[report issue]
    +
    num_peers
    +
    tells how many peers the tracker returned in this response. This is +not expected to be greater than the num_want settings. These are not necessarily +all new peers, some of them may already be connected.
    +
    +[report issue]
    +
    +

    dht_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated each time the DHT receives peers from a node. num_peers +is the number of peers we received in this packet. Typically these packets are +received from multiple DHT nodes, and so the alerts are typically generated +a few at a time.

    +
    +struct dht_reply_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht | alert_category::tracker;
    +   int const num_peers;
    +};
    +
    +[report issue]
    +
    +

    tracker_announce_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated each time a tracker announce is sent (or attempted to be sent). +There are no extra data members in this alert. The url can be found in the base class +however.

    +
    +struct tracker_announce_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker;
    +   int const event;
    +};
    +
    +[report issue]
    +
    event
    +

    specifies what event was sent to the tracker. It is defined as:

    +
      +
    1. None
    2. +
    3. Completed
    4. +
    5. Started
    6. +
    7. Stopped
    8. +
    +
    +
    +[report issue]
    +
    +

    hash_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a finished piece fails its hash check. You can get the handle +to the torrent which got the failed piece and the index of the piece itself from the alert.

    +
    +struct hash_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    peer_ban_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is banned because it has sent too many corrupt pieces +to us. ip is the endpoint to the peer that was banned.

    +
    +struct peer_ban_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    peer_unsnubbed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is un-snubbed. Essentially when it was snubbed for stalling +sending data, and now it started sending data again.

    +
    +struct peer_unsnubbed_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    peer_snubbed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is snubbed, when it stops sending data when we request +it.

    +
    +struct peer_snubbed_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    peer_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer +will be disconnected, but you get its ip address from the alert, to identify it.

    +
    +struct peer_error_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   operation_t op;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    op
    +
    a 0-terminated string of the low-level operation that failed, or nullptr if +there was no low level disk operation.
    +
    +[report issue]
    +
    error
    +
    tells you what error caused this alert.
    +
    +[report issue]
    +
    +

    peer_connect_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted every time an outgoing peer connect attempts succeeds.

    +
    +struct peer_connect_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::connect;
    +   int const socket_type;
    +};
    +
    +[report issue]
    +
    +

    peer_disconnected_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is disconnected for any reason (other than the ones +covered by peer_error_alert ).

    +
    +struct peer_disconnected_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::connect;
    +   int const socket_type;
    +   operation_t const op;
    +   error_code const error;
    +   close_reason_t const reason;
    +};
    +
    +[report issue]
    +
    socket_type
    +
    the kind of socket this peer was connected over
    +
    +[report issue]
    +
    op
    +
    the operation or level where the error occurred. Specified as an +value from the operation_t enum. Defined in operations.hpp.
    +
    +[report issue]
    +
    error
    +
    tells you what error caused peer to disconnect.
    +
    +[report issue]
    +
    reason
    +
    the reason the peer disconnected (if specified)
    +
    +[report issue]
    +
    +

    invalid_request_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is a debug alert that is generated by an incoming invalid piece request. +ip is the address of the peer and the request is the actual incoming +request from the peer. See peer_request for more info.

    +
    +struct invalid_request_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   peer_request const request;
    +   bool const we_have;
    +   bool const peer_interested;
    +   bool const withheld;
    +};
    +
    +[report issue]
    +
    request
    +
    the request we received from the peer
    +
    +[report issue]
    +
    we_have
    +
    true if we have this piece
    +
    +[report issue]
    +
    peer_interested
    +
    true if the peer indicated that it was interested to download before +sending the request
    +
    +[report issue]
    +
    withheld
    +
    if this is true, the peer is not allowed to download this piece because +of super-seeding rules.
    +
    +[report issue]
    +
    +

    torrent_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a torrent switches from being a downloader to a seed. +It will only be generated once per torrent. It contains a torrent_handle to the +torrent in question.

    +
    +struct torrent_finished_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    piece_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted every time a piece completes downloading +and passes the hash check. This alert derives from torrent_alert +which contains the torrent_handle to the torrent the piece belongs to. +Note that being downloaded and passing the hash check may happen before +the piece is also fully flushed to disk. So torrent_handle::have_piece() +may still return false

    +
    +struct piece_finished_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    piece_index
    +
    the index of the piece that finished
    +
    +[report issue]
    +
    +

    request_dropped_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer rejects or ignores a piece request.

    +
    +struct request_dropped_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    block_timeout_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request times out.

    +
    +struct block_timeout_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    block_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request receives a response.

    +
    +struct block_finished_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    block_downloading_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request is sent to a peer.

    +
    +struct block_downloading_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    unwanted_block_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block is received that was not requested or +whose request timed out.

    +
    +struct unwanted_block_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    storage_moved_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The storage_moved_alert is generated when all the disk IO has +completed and the files have been moved, as an effect of a call to +torrent_handle::move_storage. This is useful to synchronize with the +actual disk. The storage_path() member return the new path of the +storage.

    +
    +struct storage_moved_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* storage_path () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +};
    +
    +[report issue]
    +

    storage_path()

    +
    +char const* storage_path () const;
    +
    +

    the path the torrent was moved to

    +[report issue]
    +
    +
    +

    storage_moved_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The storage_moved_failed_alert is generated when an attempt to move the storage, +via torrent_handle::move_storage(), fails.

    +
    +struct storage_moved_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* file_path () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   error_code const error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +

    file_path()

    +
    +char const* file_path () const;
    +
    +

    If the error happened for a specific file, this returns its path.

    +[report issue]
    +
    op
    +
    this indicates what underlying operation caused the error
    +
    +[report issue]
    +
    +
    +

    torrent_deleted_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a request to delete the files of a torrent complete.

    +

    The info_hash is the info-hash of the torrent that was just deleted. Most of +the time the torrent_handle in the torrent_alert will be invalid by the time +this alert arrives, since the torrent is being deleted. The info_hash member +is hence the main way of identifying which torrent just completed the delete.

    +

    This alert is posted in the storage_notification category, and that bit +needs to be set in the alert_mask.

    +
    +struct torrent_deleted_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    torrent_delete_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a request to delete the files of a torrent fails. +Just removing a torrent from the session cannot fail

    +
    +struct torrent_delete_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage
    +   | alert_category::error;
    +   error_code const error;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    error
    +
    tells you why it failed.
    +
    +[report issue]
    +
    info_hash
    +
    the info hash of the torrent whose files failed to be deleted
    +
    +[report issue]
    +
    +

    save_resume_data_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::save_resume_data request. +It is generated once the disk IO thread is done writing the state for this torrent.

    +
    +struct save_resume_data_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   add_torrent_params params;
    +};
    +
    +[report issue]
    +
    params
    +
    the params structure is populated with the fields to be passed to +add_torrent() or async_add_torrent() to resume the torrent. To +save the state to disk, you may pass it on to write_resume_data().
    +
    +[report issue]
    +
    +

    save_resume_data_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated instead of save_resume_data_alert if there was an error +generating the resume data. error describes what went wrong.

    +
    +struct save_resume_data_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage
    +   | alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    error
    +
    the error code from the resume_data failure
    +
    +[report issue]
    +
    +

    torrent_paused_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::pause request. It is +generated once all disk IO is complete and the files in the torrent have been closed. +This is useful for synchronizing with the disk.

    +
    +struct torrent_paused_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    torrent_resumed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::resume() request. It is +generated when a torrent goes from a paused state to an active state.

    +
    +struct torrent_resumed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    torrent_checked_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when a torrent completes checking. i.e. when it transitions +out of the checking files state into a state where it is ready to start downloading

    +
    +struct torrent_checked_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    url_seed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a HTTP seed name lookup fails.

    +
    +struct url_seed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* server_url () const;
    +   char const* error_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer | alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    server_url()

    +
    +char const* server_url () const;
    +
    +

    the URL the error is associated with

    +[report issue]
    +
    +

    error_message()

    +
    +char const* error_message () const;
    +
    +

    in case the web server sent an error message, this function returns +it.

    +[report issue]
    +
    error
    +
    the error the web seed encountered. If this is not set, the server +sent an error message, call error_message().
    +
    +[report issue]
    +
    +
    +

    file_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    If the storage fails to read or write files that it needs access to, this alert is +generated and the torrent is paused.

    +
    +struct file_error_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* filename () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status
    +   | alert_category::storage;
    +   error_code const error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +

    filename()

    +
    +char const* filename () const;
    +
    +

    the file that experienced the error

    +[report issue]
    +
    error
    +
    the error code describing the error.
    +
    +[report issue]
    +
    op
    +
    indicates which underlying operation caused the error
    +
    +[report issue]
    +
    +
    +

    metadata_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when the metadata has been completely received and the info-hash +failed to match it. i.e. the metadata that was received was corrupt. libtorrent will +automatically retry to fetch it in this case. This is only relevant when running a +torrent-less download, with the metadata extension provided by libtorrent.

    +
    +struct metadata_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    error
    +
    indicates what failed when parsing the metadata. This error is +what's returned from lazy_bdecode().
    +
    +[report issue]
    +
    +

    metadata_received_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when the metadata has been completely received and the torrent +can start downloading. It is not generated on torrents that are started with metadata, but +only those that needs to download it from peers (when utilizing the libtorrent extension).

    +

    There are no additional data members in this alert.

    +

    Typically, when receiving this alert, you would want to save the torrent file in order +to load it back up again when the session is restarted. Here's an example snippet of +code to do that:

    +
    +torrent_handle h = alert->handle();
    +if (h.is_valid()) {
    +        std::shared_ptr<torrent_info const> ti = h.torrent_file();
    +        create_torrent ct(*ti);
    +        entry te = ct.generate();
    +        std::vector<char> buffer;
    +        bencode(std::back_inserter(buffer), te);
    +        FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+");
    +        if (f) {
    +                fwrite(&buffer[0], 1, buffer.size(), f);
    +                fclose(f);
    +        }
    +}
    +
    +
    +struct metadata_received_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    udp_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when there is an error on a UDP socket. The +UDP sockets are used for all uTP, DHT and UDP tracker traffic. They are +global to the session.

    +
    +struct udp_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +   operation_t operation;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    endpoint
    +
    the source address associated with the error (if any)
    +
    +[report issue]
    +
    operation
    +
    the operation that failed
    +
    +[report issue]
    +
    error
    +
    the error code describing the error
    +
    +[report issue]
    +
    +

    external_ip_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Whenever libtorrent learns about the machines external IP, this alert is +generated. The external IP address can be acquired from the tracker (if it +supports that) or from peers that supports the extension protocol. +The address can be accessed through the external_address member.

    +
    +struct external_ip_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   aux::noexcept_movable<address> external_address;
    +};
    +
    +[report issue]
    +
    external_address
    +
    the IP address that is believed to be our external IP
    +
    +[report issue]
    +
    +

    listen_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when none of the ports, given in the port range, to +session can be opened for listening. The listen_interface member is the +interface that failed, error is the error code describing the failure.

    +

    In the case an endpoint was created before generating the alert, it is +represented by address and port. The combinations of socket type +and operation in which such address and port are not valid are: +accept - i2p +accept - socks5 +enum_if - tcp

    +

    libtorrent may sometimes try to listen on port 0, if all other ports failed. +Port 0 asks the operating system to pick a port that's free). If that fails +you may see a listen_failed_alert with port 0 even if you didn't ask to +listen on it.

    +
    +struct listen_failed_alert final : alert
    +{
    +   listen_failed_alert (aux::stack_allocator& alloc, string_view iface
    +      , tcp::endpoint const& ep, operation_t op, error_code const& ec
    +      , lt::socket_type_t t);
    +   listen_failed_alert (aux::stack_allocator& alloc, string_view iface
    +      , udp::endpoint const& ep, operation_t op, error_code const& ec
    +      , lt::socket_type_t t);
    +   listen_failed_alert (aux::stack_allocator& alloc, string_view iface
    +      , operation_t op, error_code const& ec, lt::socket_type_t t);
    +   std::string message () const override;
    +   char const* listen_interface () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status | alert_category::error;
    +   error_code const error;
    +   operation_t op;
    +   lt::socket_type_t const socket_type;
    +   aux::noexcept_movable<lt::address> address;
    +   int const port;
    +};
    +
    +[report issue]
    +

    listen_interface()

    +
    +char const* listen_interface () const;
    +
    +

    the network device libtorrent attempted to listen on, or the IP address

    +[report issue]
    +
    error
    +
    the error the system returned
    +
    +[report issue]
    +
    op
    +
    the underlying operation that failed
    +
    +[report issue]
    +
    socket_type
    +
    the type of listen socket this alert refers to.
    +
    +[report issue]
    +
    address
    +
    the address libtorrent attempted to listen on +see alert documentation for validity of this value
    +
    +[report issue]
    +
    port
    +
    the port libtorrent attempted to listen on +see alert documentation for validity of this value
    +
    +[report issue]
    +
    +
    +

    listen_succeeded_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the listen port succeeds to be opened on a +particular interface. address and port is the endpoint that +successfully was opened for listening.

    +
    +struct listen_succeeded_alert final : alert
    +{
    +   listen_succeeded_alert (aux::stack_allocator& alloc
    +      , tcp::endpoint const& ep
    +      , lt::socket_type_t t);
    +   listen_succeeded_alert (aux::stack_allocator& alloc
    +      , udp::endpoint const& ep
    +      , lt::socket_type_t t);
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   aux::noexcept_movable<lt::address> address;
    +   int const port;
    +   lt::socket_type_t const socket_type;
    +};
    +
    +[report issue]
    +
    address
    +
    the address libtorrent ended up listening on. This address +refers to the local interface.
    +
    +[report issue]
    +
    port
    +
    the port libtorrent ended up listening on.
    +
    +[report issue]
    +
    socket_type
    +
    the type of listen socket this alert refers to.
    +
    +[report issue]
    +
    +

    portmap_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a NAT router was successfully found but some +part of the port mapping request failed. It contains a text message that +may help the user figure out what is wrong. This alert is not generated in +case it appears the client is not running on a NAT:ed network or if it +appears there is no NAT router that can be remote controlled to add port +mappings.

    +
    +struct portmap_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::port_mapping
    +   | alert_category::error;
    +   port_mapping_t const mapping;
    +   portmap_transport map_transport;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    mapping
    +
    refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().
    +
    +[report issue]
    +
    map_transport
    +
    UPnP or NAT-PMP
    +
    +[report issue]
    +
    error
    +
    tells you what failed.
    +
    +[report issue]
    +
    +

    portmap_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a NAT router was successfully found and +a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP +capable router, this is typically generated once when mapping the TCP +port and, if DHT is enabled, when the UDP port is mapped.

    +
    +struct portmap_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::port_mapping;
    +   port_mapping_t const mapping;
    +   int const external_port;
    +   portmap_protocol const map_protocol;
    +   portmap_transport const map_transport;
    +};
    +
    +[report issue]
    +
    mapping
    +
    refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().
    +
    +[report issue]
    +
    external_port
    +
    the external port allocated for the mapping.
    +
    +[report issue]
    +
    +

    portmap_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated to log informational events related to either +UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP +and 1 = UPnP). Displaying these messages to an end user is only useful +for debugging the UPnP or NAT-PMP implementation. This alert is only +posted if the alert_category::port_mapping_log flag is enabled in +the alert mask.

    +
    +struct portmap_log_alert final : alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::port_mapping_log;
    +   portmap_transport const map_transport;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    the message associated with this log line

    +[report issue]
    +
    +
    +

    fastresume_rejected_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a fast resume file has been passed to +add_torrent() but the files on disk did not match the fast resume file. +The error_code explains the reason why the resume file was rejected.

    +
    +struct fastresume_rejected_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* file_path () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status
    +   | alert_category::error;
    +   error_code error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +

    file_path()

    +
    +char const* file_path () const;
    +
    +

    If the error happened to a specific file, this returns the path to it.

    +[report issue]
    +
    op
    +
    the underlying operation that failed
    +
    +[report issue]
    +
    +
    +

    peer_blocked_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when an incoming peer connection, or a peer that's about to be added +to our peer list, is blocked for some reason. This could be any of:

    +
      +
    • the IP filter
    • +
    • i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm)
    • +
    • the port filter
    • +
    • the peer has a low port and no_connect_privileged_ports is enabled
    • +
    • the protocol of the peer is blocked (uTP/TCP blocking)
    • +
    +
    +struct peer_blocked_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   enum reason_t
    +   {
    +      ip_filter,
    +      port_filter,
    +      i2p_mixed,
    +      privileged_ports,
    +      utp_disabled,
    +      tcp_disabled,
    +      invalid_local_interface,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::ip_block;
    +   int const reason;
    +};
    +
    +[report issue]
    +

    enum reason_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    ip_filter0 
    port_filter1 
    i2p_mixed2 
    privileged_ports3 
    utp_disabled4 
    tcp_disabled5 
    invalid_local_interface6 
    +[report issue]
    +
    reason
    +
    the reason for the peer being blocked. Is one of the values from the +reason_t enum.
    +
    +[report issue]
    +
    +
    +

    dht_announce_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a DHT node announces to an info-hash on our +DHT node. It belongs to the dht_notification category.

    +
    +struct dht_announce_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   aux::noexcept_movable<address> ip;
    +   int port;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    dht_get_peers_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a DHT node sends a get_peers message to +our DHT node. It belongs to the dht_notification category.

    +
    +struct dht_get_peers_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted approximately once every second, and it contains +byte counters of most statistics that's tracked for torrents. Each active +torrent posts these alerts regularly. +This alert has been superseded by calling post_torrent_updates() +regularly on the session object. This alert will be removed

    +
    +struct stats_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   enum stats_channel
    +   {
    +      upload_payload,
    +      upload_protocol,
    +      download_payload,
    +      download_protocol,
    +      upload_ip_protocol,
    +      deprecated1,
    +      deprecated2,
    +      download_ip_protocol,
    +      deprecated3,
    +      deprecated4,
    +      num_channels,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +   std::array<int, num_channels> const transferred;
    +   int const interval;
    +};
    +
    +[report issue]
    +

    enum stats_channel

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    upload_payload0 
    upload_protocol1 
    download_payload2 
    download_protocol3 
    upload_ip_protocol4 
    deprecated15 
    deprecated26 
    download_ip_protocol7 
    deprecated38 
    deprecated49 
    num_channels10 
    +[report issue]
    +
    transferred
    +
    an array of samples. The enum describes what each sample is a +measurement of. All of these are raw, and not smoothing is performed.
    +
    +[report issue]
    +
    interval
    +
    the number of milliseconds during which these stats were collected. +This is typically just above 1000, but if CPU is limited, it may be +higher than that.
    +
    +[report issue]
    +
    +
    +

    cache_flushed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the disk cache has been flushed for a specific +torrent as a result of a call to torrent_handle::flush_cache(). This +alert belongs to the storage_notification category, which must be +enabled to let this alert through. The alert is also posted when removing +a torrent from the session, once the outstanding cache flush is complete +and the torrent does no longer have any files open.

    +
    +struct cache_flushed_alert final : torrent_alert
    +{
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +};
    +
    +[report issue]
    +
    +

    lsd_peer_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when we receive a local service discovery message +from a peer for a torrent we're currently participating in.

    +
    +struct lsd_peer_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    trackerid_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted whenever a tracker responds with a trackerid. +The tracker ID is like a cookie. libtorrent will store the tracker ID +for this tracker and repeat it in subsequent announces.

    +
    +struct trackerid_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* tracker_id () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +

    tracker_id()

    +
    +char const* tracker_id () const;
    +
    +

    The tracker ID returned by the tracker

    +[report issue]
    +
    +
    +

    dht_bootstrap_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the initial DHT bootstrap is done.

    +
    +struct dht_bootstrap_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +};
    +
    +[report issue]
    +
    +

    torrent_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted whenever a torrent is transitioned into the error state.

    +
    +struct torrent_error_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* filename () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error | alert_category::status;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    filename()

    +
    +char const* filename () const;
    +
    +

    the filename (or object) the error occurred on.

    +[report issue]
    +
    error
    +
    specifies which error the torrent encountered.
    +
    +[report issue]
    +
    +
    +

    torrent_need_cert_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is always posted for SSL torrents. This is a reminder to the client that +the torrent won't work unless torrent_handle::set_ssl_certificate() is called with +a valid certificate. Valid certificates MUST be signed by the SSL certificate +in the .torrent file.

    +
    +struct torrent_need_cert_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    incoming_connection_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The incoming connection alert is posted every time we successfully accept +an incoming connection, through any mean. The most straight-forward ways +of accepting incoming connections are through the TCP listen socket and +the UDP listen socket for uTP sockets. However, connections may also be +accepted through a Socks5 or i2p listen socket, or via an SSL listen +socket.

    +
    +struct incoming_connection_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   int const socket_type;
    +   aux::noexcept_movable<tcp::endpoint> endpoint;
    +};
    +
    +[report issue]
    +
    socket_type
    +

    tells you what kind of socket the connection was accepted +as:

    +
      +
    1. none (no socket instantiated)
    2. +
    3. TCP
    4. +
    5. Socks5
    6. +
    7. HTTP
    8. +
    9. uTP
    10. +
    11. i2p
    12. +
    13. SSL/TCP
    14. +
    15. SSL/Socks5
    16. +
    17. HTTPS (SSL/HTTP)
    18. +
    19. SSL/uTP
    20. +
    +
    +
    +[report issue]
    +
    endpoint
    +
    is the IP address and port the connection came from.
    +
    +[report issue]
    +
    +

    add_torrent_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is always posted when a torrent was attempted to be added +and contains the return status of the add operation. The torrent handle of the new +torrent can be found as the handle member in the base class. If adding +the torrent failed, error contains the error code.

    +
    +struct add_torrent_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   add_torrent_params params;
    +   error_code error;
    +};
    +
    +[report issue]
    +
    params
    +
    a copy of the parameters used when adding the torrent, it can be used +to identify which invocation to async_add_torrent() caused this alert.
    +
    +[report issue]
    +
    error
    +
    set to the error, if one occurred while adding the torrent.
    +
    +[report issue]
    +
    +

    state_update_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is only posted when requested by the user, by calling +session::post_torrent_updates() on the session. It contains the torrent +status of all torrents that changed since last time this message was +posted. Its category is status_notification, but it's not subject to +filtering, since it's only manually posted anyway.

    +
    +struct state_update_alert final : alert
    +{
    +   state_update_alert (aux::stack_allocator& alloc
    +      , std::vector<torrent_status> st);
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   std::vector<torrent_status> status;
    +};
    +
    +[report issue]
    +
    status
    +
    contains the torrent status of all torrents that changed since last +time this message was posted. Note that you can map a torrent status +to a specific torrent via its handle member. The receiving end is +suggested to have all torrents sorted by the torrent_handle or hashed +by it, for efficient updates.
    +
    +[report issue]
    +
    +

    session_stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The session_stats_alert is posted when the user requests session statistics by +calling post_session_stats() on the session object. Its category is +status_notification, but it is not subject to filtering, since it's only +manually posted anyway.

    +

    the message() member function returns a string representation of the values that +properly match the line returned in session_stats_header_alert::message().

    +

    this specific output is parsed by tools/parse_session_stats.py +if this is changed, that parser should also be changed

    +
    +struct session_stats_alert final : alert
    +{
    +   std::string message () const override;
    +   span<std::int64_t const> counters () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +};
    +
    +[report issue]
    +

    counters()

    +
    +span<std::int64_t const> counters () const;
    +
    +

    An array are a mix of counters and gauges, which meanings can be +queries via the session_stats_metrics() function on the session. The +mapping from a specific metric to an index into this array is constant +for a specific version of libtorrent, but may differ for other +versions. The intended usage is to request the mapping, i.e. call +session_stats_metrics(), once on startup, and then use that mapping to +interpret these values throughout the process' runtime.

    +

    For more information, see the session statistics section.

    +[report issue]
    +
    +
    +

    dht_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted when something fails in the DHT. This is not necessarily a fatal +error, but it could prevent proper operation

    +
    +struct dht_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error | alert_category::dht;
    +   error_code error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +
    error
    +
    the error code
    +
    +[report issue]
    +
    op
    +
    the operation that failed
    +
    +[report issue]
    +
    +

    dht_immutable_item_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up immutable items in the DHT.

    +
    +struct dht_immutable_item_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash target;
    +   entry item;
    +};
    +
    +[report issue]
    +
    target
    +
    the target hash of the immutable item. This must +match the SHA-1 hash of the bencoded form of item.
    +
    +[report issue]
    +
    item
    +
    the data for this item
    +
    +[report issue]
    +
    +

    dht_mutable_item_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up mutable items in the DHT.

    +
    +struct dht_mutable_item_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   std::array<char, 32> key;
    +   std::array<char, 64> signature;
    +   std::int64_t seq;
    +   std::string salt;
    +   entry item;
    +   bool authoritative;
    +};
    +
    +[report issue]
    +
    key
    +
    the public key that was looked up
    +
    +[report issue]
    +
    signature
    +
    the signature of the data. This is not the signature of the +plain encoded form of the item, but it includes the sequence number +and possibly the hash as well. See the dht_store document for more +information. This is primarily useful for echoing back in a store +request.
    +
    +[report issue]
    +
    seq
    +
    the sequence number of this item
    +
    +[report issue]
    +
    salt
    +
    the salt, if any, used to lookup and store this item. If no +salt was used, this is an empty string
    +
    +[report issue]
    +
    item
    +
    the data for this item
    +
    +[report issue]
    +
    authoritative
    +
    the last response for mutable data is authoritative.
    +
    +[report issue]
    +
    +

    dht_put_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this is posted when a DHT put operation completes. This is useful if the +client is waiting for a put to complete before shutting down for instance.

    +
    +struct dht_put_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash target;
    +   std::array<char, 32> public_key;
    +   std::array<char, 64> signature;
    +   std::string salt;
    +   std::int64_t seq;
    +   int num_success;
    +};
    +
    +[report issue]
    +
    target
    +
    the target hash the item was stored under if this was an immutable +item.
    +
    + + + +[report issue]
    +
    public_key signature salt seq
    +
    if a mutable item was stored, these are the public key, signature, +salt and sequence number the item was stored under.
    +
    +[report issue]
    +
    num_success
    +
    DHT put operation usually writes item to k nodes, maybe the node +is stale so no response, or the node doesn't support 'put', or the +token for write is out of date, etc. num_success is the number of +successful responses we got from the puts.
    +
    +[report issue]
    +
    +

    i2p_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is used to report errors in the i2p SAM connection

    +
    +struct i2p_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code error;
    +};
    +
    +[report issue]
    +
    error
    +
    the error that occurred in the i2p SAM connection
    +
    +[report issue]
    +
    +

    dht_outgoing_get_peers_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when we send a get_peers request +It belongs to the dht_notification category.

    +
    +struct dht_outgoing_get_peers_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash info_hash;
    +   sha1_hash obfuscated_info_hash;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +};
    +
    +[report issue]
    +
    info_hash
    +
    the info_hash of the torrent we're looking for peers for.
    +
    +[report issue]
    +
    obfuscated_info_hash
    +
    if this was an obfuscated lookup, this is the info-hash target +actually sent to the node.
    +
    +[report issue]
    +
    endpoint
    +
    the endpoint we're sending this query to
    +
    +[report issue]
    +
    +

    log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by some session wide event. Its main purpose is +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert_category::session_log bit. +Furthermore, it's by default disabled as a build configuration.

    +
    +struct log_alert final : alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::session_log;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    returns the log message

    +[report issue]
    +
    +
    +

    torrent_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by torrent wide events. It's meant to be used for +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert_category::torrent_log bit. By +default it is disabled as a build configuration.

    +
    +struct torrent_log_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::torrent_log;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    returns the log message

    +[report issue]
    +
    +
    +

    peer_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by events specific to a peer. It's meant to be used +for trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert_category::peer_log bit. By +default it is disabled as a build configuration.

    +
    +struct peer_log_alert final : peer_alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   enum direction_t
    +   {
    +      incoming_message,
    +      outgoing_message,
    +      incoming,
    +      outgoing,
    +      info,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer_log;
    +   char const* event_type;
    +   direction_t direction;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    returns the log message

    +[report issue]
    +
    +

    enum direction_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    incoming_message0 
    outgoing_message1 
    incoming2 
    outgoing3 
    info4 
    +[report issue]
    +
    event_type
    +
    string literal indicating the kind of event. For messages, this is the +message name.
    +
    +[report issue]
    +
    +
    +

    lsd_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted if the local service discovery socket fails to start properly. +it's categorized as error_notification.

    +
    +struct lsd_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code error;
    +};
    +
    +[report issue]
    +
    error
    +
    The error code
    +
    +[report issue]
    +
    +

    dht_lookup

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    holds statistics about a current dht_lookup operation. +a DHT lookup is the traversal of nodes, looking up a +set of target nodes in the DHT for retrieving and possibly +storing information in the DHT

    +
    +struct dht_lookup
    +{
    +   char const* type;
    +   int outstanding_requests;
    +   int timeouts;
    +   int responses;
    +   int branch_factor;
    +   int nodes_left;
    +   int last_sent;
    +   int first_timeout;
    +   sha1_hash target;
    +};
    +
    +[report issue]
    +
    type
    +
    string literal indicating which kind of lookup this is
    +
    +[report issue]
    +
    outstanding_requests
    +
    the number of outstanding request to individual nodes +this lookup has right now
    +
    +[report issue]
    +
    timeouts
    +
    the total number of requests that have timed out so far +for this lookup
    +
    +[report issue]
    +
    responses
    +
    the total number of responses we have received for this +lookup so far for this lookup
    +
    +[report issue]
    +
    branch_factor
    +
    the branch factor for this lookup. This is the number of +nodes we keep outstanding requests to in parallel by default. +when nodes time out we may increase this.
    +
    +[report issue]
    +
    nodes_left
    +
    the number of nodes left that could be queries for this +lookup. Many of these are likely to be part of the trail +while performing the lookup and would never end up actually +being queried.
    +
    +[report issue]
    +
    last_sent
    +
    the number of seconds ago the +last message was sent that's still +outstanding
    +
    +[report issue]
    +
    first_timeout
    +
    the number of outstanding requests +that have exceeded the short timeout +and are considered timed out in the +sense that they increased the branch +factor
    +
    +[report issue]
    +
    target
    +
    the node-id or info-hash target for this lookup
    +
    +[report issue]
    +
    +

    dht_stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    contains current DHT state. Posted in response to session::post_dht_stats().

    +
    +struct dht_stats_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +   std::vector<dht_lookup> active_requests;
    +   std::vector<dht_routing_bucket> routing_table;
    +};
    +
    +[report issue]
    +
    active_requests
    +
    a vector of the currently running DHT lookups.
    +
    +[report issue]
    +
    routing_table
    +
    contains information about every bucket in the DHT routing +table.
    +
    +[report issue]
    +
    +

    incoming_request_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted every time an incoming request from a peer is accepted and queued +up for being serviced. This alert is only posted if +the alert_category::incoming_request flag is enabled in the alert +mask.

    +
    +struct incoming_request_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::incoming_request;
    +   peer_request req;
    +};
    +
    +[report issue]
    +
    req
    +
    the request this peer sent to us
    +
    +[report issue]
    +
    +

    dht_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    debug logging of the DHT when dht_log_notification is set in the alert +mask.

    +
    +struct dht_log_alert final : alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   enum dht_module_t
    +   {
    +      tracker,
    +      node,
    +      routing_table,
    +      rpc_manager,
    +      traversal,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_log;
    +   dht_module_t module;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    the log message

    +[report issue]
    +
    +

    enum dht_module_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tracker0 
    node1 
    routing_table2 
    rpc_manager3 
    traversal4 
    +[report issue]
    +
    module
    +
    the module, or part, of the DHT that produced this log message.
    +
    +[report issue]
    +
    +
    +

    dht_pkt_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted every time a DHT message is sent or received. It is +only posted if the alert_category::dht_log alert category is +enabled. It contains a verbatim copy of the message.

    +
    +struct dht_pkt_alert final : alert
    +{
    +   std::string message () const override;
    +   span<char const> pkt_buf () const;
    +
    +   enum direction_t
    +   {
    +      incoming,
    +      outgoing,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_log;
    +   direction_t direction;
    +   aux::noexcept_movable<udp::endpoint> node;
    +};
    +
    +[report issue]
    +

    pkt_buf()

    +
    +span<char const> pkt_buf () const;
    +
    +

    returns a pointer to the packet buffer and size of the packet, +respectively. This buffer is only valid for as long as the alert itself +is valid, which is owned by libtorrent and reclaimed whenever +pop_alerts() is called on the session.

    +[report issue]
    +
    +

    enum direction_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    incoming0 
    outgoing1 
    +[report issue]
    +
    direction
    +
    whether this is an incoming or outgoing packet.
    +
    +[report issue]
    +
    node
    +
    the DHT node we received this packet from, or sent this packet to +(depending on direction).
    +
    +[report issue]
    +
    +
    +

    dht_get_peers_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Posted when we receive a response to a DHT get_peers request.

    +
    +struct dht_get_peers_reply_alert final : alert
    +{
    +   std::string message () const override;
    +   int num_peers () const;
    +   std::vector<tcp::endpoint> peers () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_operation;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    dht_direct_response_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted exactly once for every call to session_handle::dht_direct_request. +If the request failed, response() will return a default constructed bdecode_node.

    +
    +struct dht_direct_response_alert final : alert
    +{
    +   dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr);
    +   std::string message () const override;
    +   bdecode_node response () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   void const* userdata;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +};
    +
    + +[report issue]
    +

    dht_direct_response_alert() message()

    +
    +dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr);
    +std::string message () const override;
    +
    +

    for when there was a timeout so we don't have a response

    +[report issue]
    +
    +
    +

    picker_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this is posted when one or more blocks are picked by the piece picker, +assuming the verbose piece picker logging is enabled (see +picker_log_notification).

    +
    +struct picker_log_alert final : peer_alert
    +{
    +   std::string message () const override;
    +   std::vector<piece_block> blocks () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::picker_log;
    +   static constexpr picker_flags_t partial_ratio  = 0_bit;
    +   static constexpr picker_flags_t prioritize_partials  = 1_bit;
    +   static constexpr picker_flags_t rarest_first_partials  = 2_bit;
    +   static constexpr picker_flags_t rarest_first  = 3_bit;
    +   static constexpr picker_flags_t reverse_rarest_first  = 4_bit;
    +   static constexpr picker_flags_t suggested_pieces  = 5_bit;
    +   static constexpr picker_flags_t prio_sequential_pieces  = 6_bit;
    +   static constexpr picker_flags_t sequential_pieces  = 7_bit;
    +   static constexpr picker_flags_t reverse_pieces  = 8_bit;
    +   static constexpr picker_flags_t time_critical  = 9_bit;
    +   static constexpr picker_flags_t random_pieces  = 10_bit;
    +   static constexpr picker_flags_t prefer_contiguous  = 11_bit;
    +   static constexpr picker_flags_t reverse_sequential  = 12_bit;
    +   static constexpr picker_flags_t backup1  = 13_bit;
    +   static constexpr picker_flags_t backup2  = 14_bit;
    +   static constexpr picker_flags_t end_game  = 15_bit;
    +   static constexpr picker_flags_t extent_affinity  = 16_bit;
    +   picker_flags_t const picker_flags;
    +};
    +
    +[report issue]
    +
    picker_flags
    +
    this is a bitmask of which features were enabled for this particular +pick. The bits are defined in the picker_flags_t enum.
    +
    +[report issue]
    +
    +

    session_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted when the session encounters a serious error, +potentially fatal

    +
    +struct session_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    error
    +
    The error code, if one is associated with this error
    +
    +[report issue]
    +
    +

    dht_live_nodes_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted in response to a call to session::dht_live_nodes(). It contains the +live nodes from the DHT routing table of one of the DHT nodes running +locally.

    +
    +struct dht_live_nodes_alert final : alert
    +{
    +   std::string message () const override;
    +   int num_nodes () const;
    +   std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash node_id;
    +};
    +
    + +[report issue]
    +

    num_nodes() nodes()

    +
    +int num_nodes () const;
    +std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +

    the number of nodes in the routing table and the actual nodes.

    +[report issue]
    +
    node_id
    +
    the local DHT node's node-ID this routing table belongs to
    +
    +[report issue]
    +
    +
    +

    session_stats_header_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The session_stats_header alert is posted the first time +post_session_stats() is called

    +

    the message() member function returns a string representation of the +header that properly match the stats values string returned in +session_stats_alert::message().

    +

    this specific output is parsed by tools/parse_session_stats.py +if this is changed, that parser should also be changed

    +
    +struct session_stats_header_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +};
    +
    +[report issue]
    +
    +

    dht_sample_infohashes_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted as a response to a call to session::dht_sample_infohashes() with +the information from the DHT response message.

    +
    +struct dht_sample_infohashes_alert final : alert
    +{
    +   std::string message () const override;
    +   int num_samples () const;
    +   std::vector<sha1_hash> samples () const;
    +   int num_nodes () const;
    +   std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_operation;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +   time_duration const interval;
    +   int const num_infohashes;
    +};
    +
    + +[report issue]
    +

    samples() num_samples()

    +
    +int num_samples () const;
    +std::vector<sha1_hash> samples () const;
    +
    +

    returns the number of info-hashes returned by the node, as well as the +actual info-hashes. num_samples() is more efficient than +samples().size().

    +[report issue]
    +
    +

    num_nodes()

    +
    +int num_nodes () const;
    +
    +

    The total number of nodes returned by nodes().

    +[report issue]
    +
    +

    nodes()

    +
    +std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +

    This is the set of more DHT nodes returned by the request.

    +

    The information is included so that indexing nodes can perform a key +space traversal with a single RPC per node by adjusting the target +value for each RPC.

    +[report issue]
    +
    endpoint
    +
    the node the request was sent to (and this response was received from)
    +
    +[report issue]
    +
    interval
    +
    the interval to wait before making another request to this node
    +
    +[report issue]
    +
    num_infohashes
    +
    This field indicates how many info-hash keys are currently in the node's storage. +If the value is larger than the number of returned samples it indicates that the +indexer may obtain additional samples after waiting out the interval.
    +
    +[report issue]
    +
    +
    +

    block_uploaded_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when a block intended to be sent to a peer is placed in the +send buffer. Note that if the connection is closed before the send buffer is sent, +the alert may be posted without the bytes having been sent to the peer. +It belongs to the upload_notification category.

    +
    +struct block_uploaded_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    alerts_dropped_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted to indicate to the client that some alerts were +dropped. Dropped meaning that the alert failed to be delivered to the +client. The most common cause of such failure is that the internal alert +queue grew too big (controlled by alert_queue_size).

    +
    +struct alerts_dropped_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   std::bitset<num_alert_types> dropped_alerts;
    +};
    +
    +[report issue]
    +
    dropped_alerts
    +
    a bitmask indicating which alerts were dropped. Each bit represents the +alert type ID, where bit 0 represents whether any alert of type 0 has +been dropped, and so on.
    +
    +[report issue]
    +
    +

    socks5_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is +configured. It's enabled with the error_notification alert category.

    +
    +struct socks5_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code error;
    +   operation_t op;
    +   aux::noexcept_movable<tcp::endpoint> ip;
    +};
    +
    +[report issue]
    +
    error
    +
    the error
    +
    +[report issue]
    +
    op
    +
    the operation that failed
    +
    +[report issue]
    +
    ip
    +
    the endpoint configured as the proxy
    +
    +[report issue]
    +
    +

    alert_cast()

    +

    Declared in "libtorrent/alert.hpp"

    +
    +template <class T> T const* alert_cast (alert const* a);
    +template <class T> T* alert_cast (alert* a);
    +
    +

    When you get an alert, you can use alert_cast<> to attempt to cast the +pointer to a specific alert type, in order to query it for more +information.

    +
    +

    Note

    +

    alert_cast<> can only cast to an exact alert type, not a base class

    +
    +[report issue]
    +
    +

    operation_name()

    +

    Declared in "libtorrent/operations.hpp"

    +
    +char const* operation_name (operation_t op);
    +
    +

    maps an operation id (from peer_error_alert and peer_disconnected_alert) +to its name. See operation_t for the constants

    +[report issue]
    +
    +

    enum operation_t

    +

    Declared in "libtorrent/operations.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    unknown0the error was unexpected and it is unknown which operation caused it
    bittorrent1this is used when the bittorrent logic +determines to disconnect
    iocontrol2a call to iocontrol failed
    getpeername3a call to getpeername() failed (querying the remote IP of a +connection)
    getname4a call to getname failed (querying the local IP of a +connection)
    alloc_recvbuf5an attempt to allocate a receive buffer failed
    alloc_sndbuf6an attempt to allocate a send buffer failed
    file_write7writing to a file failed
    file_read8reading from a file failed
    file9a non-read and non-write file operation failed
    sock_write10a socket write operation failed
    sock_read11a socket read operation failed
    sock_open12a call to open(), to create a socket socket failed
    sock_bind13a call to bind() on a socket failed
    available14an attempt to query the number of bytes available to read from a socket +failed
    encryption15a call related to bittorrent protocol encryption failed
    connect16an attempt to connect a socket failed
    ssl_handshake17establishing an SSL connection failed
    get_interface18a connection failed to satisfy the bind interface setting
    sock_listen19a call to listen() on a socket
    sock_bind_to_device20a call to the ioctl to bind a socket to a specific network device or +adapter
    sock_accept21a call to accept() on a socket
    parse_address22convert a string into a valid network address
    enum_if23enumeration network devices or adapters
    file_stat24invoking stat() on a file
    file_copy25copying a file
    file_fallocate26allocating storage for a file
    file_hard_link27creating a hard link
    file_remove28removing a file
    file_rename29renaming a file
    file_open30opening a file
    mkdir31creating a directory
    check_resume32check fast resume data against files on disk
    exception33an unknown exception
    alloc_cache_piece34allocate space for a piece in the cache
    partfile_move35move a part-file
    partfile_read36read from a part file
    partfile_write37write to a part-file
    hostname_lookup38a hostname lookup
    symlink39create or read a symlink
    handshake40handshake with a peer or server
    sock_option41set socket option
    enum_route42enumeration network routes
    +[report issue]
    +
    +

    enum socket_type_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp0 
    tcp_ssl1 
    udp2 
    i2p3 
    socks54 
    utp_ssl5 
    +[report issue]
    +
    +

    alert_category_t

    +

    Declared in "libtorrent/alert.hpp"

    +
    +
    error
    +

    Enables alerts that report an error. This includes:

    +
      +
    • tracker errors
    • +
    • tracker warnings
    • +
    • file errors
    • +
    • resume data failures
    • +
    • web seed errors
    • +
    • .torrent files errors
    • +
    • listen socket errors
    • +
    • port mapping errors
    • +
    +
    +
    +
    +
    peer
    +
    Enables alerts when peers send invalid requests, get banned or +snubbed.
    +
    +
    +
    port_mapping
    +
    Enables alerts for port mapping events. For NAT-PMP and UPnP.
    +
    +
    +
    storage
    +
    Enables alerts for events related to the storage. File errors and +synchronization events for moving the storage, renaming files etc.
    +
    +
    +
    tracker
    +
    Enables all tracker events. Includes announcing to trackers, +receiving responses, warnings and errors.
    +
    +
    +
    connect
    +
    Low level alerts for when peers are connected and disconnected.
    +
    +
    +
    status
    +
    Enables alerts for when a torrent or the session changes state.
    +
    +
    +
    ip_block
    +
    Alerts when a peer is blocked by the ip blocker or port blocker.
    +
    +
    +
    performance_warning
    +
    Alerts when some limit is reached that might limit the download +or upload rate.
    +
    +
    +
    dht
    +
    Alerts on events in the DHT node. For incoming searches or +bootstrapping being done etc.
    +
    +
    +
    stats
    +
    If you enable these alerts, you will receive a stats_alert +approximately once every second, for every active torrent. +These alerts contain all statistics counters for the interval since +the lasts stats alert.
    +
    +
    +
    session_log
    +
    Enables debug logging alerts. These are available unless libtorrent +was built with logging disabled (TORRENT_DISABLE_LOGGING). The +alerts being posted are log_alert and are session wide.
    +
    +
    +
    torrent_log
    +
    Enables debug logging alerts for torrents. These are available +unless libtorrent was built with logging disabled +(TORRENT_DISABLE_LOGGING). The alerts being posted are +torrent_log_alert and are torrent wide debug events.
    +
    +
    +
    peer_log
    +
    Enables debug logging alerts for peers. These are available unless +libtorrent was built with logging disabled +(TORRENT_DISABLE_LOGGING). The alerts being posted are +peer_log_alert and low-level peer events and messages.
    +
    +
    +
    incoming_request
    +
    enables the incoming_request_alert.
    +
    +
    +
    dht_log
    +
    enables dht_log_alert, debug logging for the DHT
    +
    +
    +
    dht_operation
    +
    enable events from pure dht operations not related to torrents
    +
    +
    +
    port_mapping_log
    +
    enables port mapping log events. This log is useful +for debugging the UPnP or NAT-PMP implementation
    +
    +
    +
    picker_log
    +
    enables verbose logging from the piece picker.
    +
    +
    +
    file_progress
    +
    alerts when files complete downloading
    +
    +
    +
    piece_progress
    +
    alerts when pieces complete downloading or fail hash check
    +
    +
    +
    upload
    +
    alerts when we upload blocks to other peers
    +
    +
    +
    block_progress
    +
    alerts on individual blocks being requested, downloading, finished, +rejected, time-out and cancelled. This is likely to post alerts at a +high rate.
    +
    +
    +
    all
    +

    The full bitmask, representing all available categories.

    +

    since the enum is signed, make sure this isn't +interpreted as -1. For instance, boost.python +does that and fails when assigning it to an +unsigned parameter.

    +
    +
    +[report issue]
    +
    +

    int

    +

    Declared in "libtorrent/alert_types.hpp"

    +
    +
    user_alert_id
    +
    user defined alerts should use IDs greater than this
    +
    +
    +
    num_alert_types
    +
    this constant represents "max_alert_index" + 1
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Bdecoding.html b/docs/reference-Bdecoding.html new file mode 100644 index 0000000..c6d9ef4 --- /dev/null +++ b/docs/reference-Bdecoding.html @@ -0,0 +1,397 @@ + + + + + + +reference-Bdecoding.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Bdecoding

    +
    +

    Table of contents

    + +
    +[report issue]
    +

    bdecode_node

    +

    Declared in "libtorrent/bdecode.hpp"

    +

    Sometimes it's important to get a non-owning reference to the root node ( +to be able to copy it as a reference for instance). For that, use the +non_owning() member function.

    +

    There are 5 different types of nodes, see type_t.

    +
    +struct bdecode_node
    +{
    +   bdecode_node () = default;
    +   bdecode_node (bdecode_node&&) noexcept;
    +   bdecode_node& operator= (bdecode_node&&) = default;
    +   bdecode_node& operator= (bdecode_node const&);
    +   bdecode_node (bdecode_node const&);
    +   type_t type () const noexcept;
    +   explicit operator bool () const noexcept;
    +   bdecode_node non_owning () const;
    +   span<char const> data_section () const noexcept;
    +   int list_size () const;
    +   std::int64_t list_int_value_at (int i
    +      , std::int64_t default_val = 0) const;
    +   bdecode_node list_at (int i) const;
    +   string_view list_string_value_at (int i
    +      , string_view default_val = string_view()) const;
    +   std::pair<string_view, bdecode_node> dict_at (int i) const;
    +   string_view dict_find_string_value (string_view key
    +      , string_view default_value = string_view()) const;
    +   bdecode_node dict_find_int (string_view key) const;
    +   bdecode_node dict_find (string_view key) const;
    +   bdecode_node dict_find_list (string_view key) const;
    +   int dict_size () const;
    +   std::int64_t dict_find_int_value (string_view key
    +      , std::int64_t default_val = 0) const;
    +   bdecode_node dict_find_dict (string_view key) const;
    +   bdecode_node dict_find_string (string_view key) const;
    +   std::int64_t int_value () const;
    +   char const* string_ptr () const;
    +   int string_length () const;
    +   string_view string_value () const;
    +   void clear ();
    +   void swap (bdecode_node& n);
    +   void reserve (int tokens);
    +   void switch_underlying_buffer (char const* buf) noexcept;
    +   bool has_soft_error (span<char> error) const;
    +
    +   enum type_t
    +   {
    +      none_t,
    +      dict_t,
    +      list_t,
    +      string_t,
    +      int_t,
    +   };
    +};
    +
    +[report issue]
    +

    bdecode_node()

    +
    +bdecode_node () = default;
    +
    +

    creates a default constructed node, it will have the type none_t.

    + +[report issue]
    +
    +

    bdecode_node() operator=()

    +
    +bdecode_node (bdecode_node&&) noexcept;
    +bdecode_node& operator= (bdecode_node&&) = default;
    +bdecode_node& operator= (bdecode_node const&);
    +bdecode_node (bdecode_node const&);
    +
    +

    For owning nodes, the copy will create a copy of the tree, but the +underlying buffer remains the same.

    +[report issue]
    +
    +

    type()

    +
    +type_t type () const noexcept;
    +
    +

    the type of this node. See type_t.

    +[report issue]
    +
    +

    bool()

    +
    +explicit operator bool () const noexcept;
    +
    +

    returns true if type() != none_t.

    +[report issue]
    +
    +

    non_owning()

    +
    +bdecode_node non_owning () const;
    +
    +

    return a non-owning reference to this node. This is useful to refer to +the root node without copying it in assignments.

    +[report issue]
    +
    +

    data_section()

    +
    +span<char const> data_section () const noexcept;
    +
    +

    returns the buffer and length of the section in the original bencoded +buffer where this node is defined. For a dictionary for instance, this +starts with d and ends with e, and has all the content of the +dictionary in between.

    + + + +[report issue]
    +
    +

    list_at() list_string_value_at() list_int_value_at() list_size()

    +
    +int list_size () const;
    +std::int64_t list_int_value_at (int i
    +      , std::int64_t default_val = 0) const;
    +bdecode_node list_at (int i) const;
    +string_view list_string_value_at (int i
    +      , string_view default_val = string_view()) const;
    +
    +

    functions with the list_ prefix operate on lists. These functions are +only valid if type() == list_t. list_at() returns the item +in the list at index i. i may not be greater than or equal to the +size of the list. size() returns the size of the list.

    + + + + + + + + +[report issue]
    +
    +

    dict_find_dict() dict_find_list() dict_at() dict_find_string() dict_find() dict_size() dict_find_int() dict_find_int_value() dict_find_string_value()

    +
    +std::pair<string_view, bdecode_node> dict_at (int i) const;
    +string_view dict_find_string_value (string_view key
    +      , string_view default_value = string_view()) const;
    +bdecode_node dict_find_int (string_view key) const;
    +bdecode_node dict_find (string_view key) const;
    +bdecode_node dict_find_list (string_view key) const;
    +int dict_size () const;
    +std::int64_t dict_find_int_value (string_view key
    +      , std::int64_t default_val = 0) const;
    +bdecode_node dict_find_dict (string_view key) const;
    +bdecode_node dict_find_string (string_view key) const;
    +
    +

    Functions with the dict_ prefix operates on dictionaries. They are +only valid if type() == dict_t. In case a key you're looking up +contains a 0 byte, you cannot use the 0-terminated string overloads, +but have to use std::string instead. dict_find_list will return a +valid bdecode_node if the key is found _and_ it is a list. Otherwise +it will return a default-constructed bdecode_node.

    +

    Functions with the _value suffix return the value of the node +directly, rather than the nodes. In case the node is not found, or it has +a different type, a default value is returned (which can be specified).

    +[report issue]
    +
    +

    int_value()

    +
    +std::int64_t int_value () const;
    +
    +

    this function is only valid if type() == int_t. It returns the +value of the integer.

    + + +[report issue]
    +
    +

    string_length() string_ptr() string_value()

    +
    +char const* string_ptr () const;
    +int string_length () const;
    +string_view string_value () const;
    +
    +

    these functions are only valid if type() == string_t. They return +the string values. Note that string_ptr() is not 0-terminated. +string_length() returns the number of bytes in the string.

    +[report issue]
    +
    +

    clear()

    +
    +void clear ();
    +
    +

    resets the bdecoded_node to a default constructed state. If this is +an owning node, the tree is freed and all child nodes are invalidated.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (bdecode_node& n);
    +
    +

    Swap contents.

    +[report issue]
    +
    +

    reserve()

    +
    +void reserve (int tokens);
    +
    +

    preallocate memory for the specified numbers of tokens. This is +useful if you know approximately how many tokens are in the file +you are about to parse. Doing so will save realloc operations +while parsing. You should only call this on the root node, before +passing it in to bdecode().

    +[report issue]
    +
    +

    switch_underlying_buffer()

    +
    +void switch_underlying_buffer (char const* buf) noexcept;
    +
    +

    this buffer MUST be identical to the one originally parsed. This +operation is only defined on owning root nodes, i.e. the one passed in to +decode().

    +[report issue]
    +
    +

    has_soft_error()

    +
    +bool has_soft_error (span<char> error) const;
    +
    +

    returns true if there is a non-fatal error in the bencoding of this node +or its children

    +[report issue]
    +
    +

    enum type_t

    +

    Declared in "libtorrent/bdecode.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none_t0uninitialized or default constructed. This is also used +to indicate that a node was not found in some cases.
    dict_t1a dictionary node. The dict_find_ functions are valid.
    list_t2a list node. The list_ functions are valid.
    string_t3a string node, the string_ functions are valid.
    int_t4an integer node. The int_ functions are valid.
    +[report issue]
    +
    +
    +

    print_entry()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +std::string print_entry (bdecode_node const& e
    +   , bool single_line = false, int indent = 0);
    +
    +

    print the bencoded structure in a human-readable format to a string +that's returned.

    +[report issue]
    +
    +

    bdecode()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +int bdecode (char const* start, char const* end, bdecode_node& ret
    +   , error_code& ec, int* error_pos = nullptr, int depth_limit = 100
    +   , int token_limit = 2000000);
    +bdecode_node bdecode (span<char const> buffer
    +   , error_code& ec, int* error_pos = nullptr, int depth_limit = 100
    +   , int token_limit = 2000000);
    +bdecode_node bdecode (span<char const> buffer
    +   , int depth_limit = 100, int token_limit = 2000000);
    +
    +

    This function decodes/parses bdecoded data (for example a .torrent file). +The data structure is returned in the ret argument. the buffer to parse +is specified by the start of the buffer as well as the end, i.e. one +byte past the end. If the buffer fails to parse, the function returns a +non-zero value and fills in ec with the error code. The optional +argument error_pos, if set to non-nullptr, will be set to the byte offset +into the buffer where the parse failure occurred.

    +

    depth_limit specifies the max number of nested lists or dictionaries are +allowed in the data structure. (This affects the stack usage of the +function, be careful not to set it too high).

    +

    token_limit is the max number of tokens allowed to be parsed from the +buffer. This is simply a sanity check to not have unbounded memory usage.

    +

    The resulting bdecode_node is an owning node. That means it will +be holding the whole parsed tree. When iterating lists and dictionaries, +those bdecode_node objects will simply have references to the root or +owning bdecode_node. If the root node is destructed, all other nodes +that refer to anything in that tree become invalid.

    +

    However, the underlying buffer passed in to this function (start, end) +must also remain valid while the bdecoded tree is used. The parsed tree +produced by this function does not copy any data out of the buffer, but +simply produces references back into it.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Bencoding.html b/docs/reference-Bencoding.html new file mode 100644 index 0000000..6e960fb --- /dev/null +++ b/docs/reference-Bencoding.html @@ -0,0 +1,348 @@ + + + + + + +reference-Bencoding.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Bencoding

    +
    +

    Table of contents

    + +
    +

    Bencoding is a common representation in bittorrent used for dictionary, +list, int and string hierarchies. It's used to encode .torrent files and +some messages in the network protocol. libtorrent also uses it to store +settings, resume data and other session state.

    +

    Strings in bencoded structures do not necessarily represent text. +Strings are raw byte buffers of a certain length. If a string is meant to be +interpreted as text, it is required to be UTF-8 encoded. See BEP 3.

    +

    The function for decoding bencoded data bdecode(), returning a bdecode_node. +This function builds a tree that points back into the original buffer. The +returned bdecode_node will not be valid once the buffer it was parsed out of +is discarded.

    +

    It's possible to construct an entry from a bdecode_node, if a structure needs +to be altered and re-encoded.

    +[report issue]
    +

    entry

    +

    Declared in "libtorrent/entry.hpp"

    +

    The entry class represents one node in a bencoded hierarchy. It works as a +variant type, it can be either a list, a dictionary (std::map), an integer +or a string.

    +
    +class entry
    +{
    +   data_type type () const;
    +   entry& operator= (list_type) &;
    +   entry (list_type); // NOLINT;
    +   entry (dictionary_type); // NOLINT;
    +   entry& operator= (integer_type) &;
    +   entry& operator= (preformatted_type) &;
    +   list_type& list ();
    +   preformatted_type& preformatted ();
    +   integer_type& integer ();
    +   string_type& string ();
    +   const string_type& string () const;
    +   const dictionary_type& dict () const;
    +   const preformatted_type& preformatted () const;
    +   const integer_type& integer () const;
    +   dictionary_type& dict ();
    +   const list_type& list () const;
    +   void swap (entry& e);
    +   entry& operator[] (string_view key);
    +   const entry& operator[] (string_view key) const;
    +   entry const* find_key (string_view key) const;
    +   entry* find_key (string_view key);
    +   std::string to_string (bool single_line = false) const;
    +
    +   enum data_type
    +   {
    +      int_t,
    +      string_t,
    +      list_t,
    +      dictionary_t,
    +      undefined_t,
    +      preformatted_t,
    +   };
    +
    +   mutable std::uint8_t m_type_queried:1;
    +};
    +
    +[report issue]
    +

    type()

    +
    +data_type type () const;
    +
    +

    returns the concrete type of the entry

    + +[report issue]
    +
    +

    operator=() entry()

    +
    +entry& operator= (list_type) &;
    +entry (list_type); // NOLINT;
    +entry (dictionary_type); // NOLINT;
    +entry& operator= (integer_type) &;
    +entry& operator= (preformatted_type) &;
    +
    +

    constructors directly from a specific type. +The content of the argument is copied into the +newly constructed entry

    + + + + +[report issue]
    +
    +

    string() dict() list() integer() preformatted()

    +
    +list_type& list ();
    +preformatted_type& preformatted ();
    +integer_type& integer ();
    +string_type& string ();
    +const string_type& string () const;
    +const dictionary_type& dict () const;
    +const preformatted_type& preformatted () const;
    +const integer_type& integer () const;
    +dictionary_type& dict ();
    +const list_type& list () const;
    +
    +

    The integer(), string(), list() and dict() functions +are accessors that return the respective type. If the entry object +isn't of the type you request, the accessor will throw +system_error. You can ask an entry for its type through the +type() function.

    +

    If you want to create an entry you give it the type you want it to +have in its constructor, and then use one of the non-const accessors +to get a reference which you then can assign the value you want it to +have.

    +

    The typical code to get info from a torrent file will then look like +this:

    +
    +entry torrent_file;
    +// ...
    +
    +// throws if this is not a dictionary
    +entry::dictionary_type const& dict = torrent_file.dict();
    +entry::dictionary_type::const_iterator i;
    +i = dict.find("announce");
    +if (i != dict.end())
    +{
    +        std::string tracker_url = i->second.string();
    +        std::cout << tracker_url << "\n";
    +}
    +
    +

    The following code is equivalent, but a little bit shorter:

    +
    +entry torrent_file;
    +// ...
    +
    +// throws if this is not a dictionary
    +if (entry* i = torrent_file.find_key("announce"))
    +{
    +        std::string tracker_url = i->string();
    +        std::cout << tracker_url << "\n";
    +}
    +
    +

    To make it easier to extract information from a torrent file, the +class torrent_info exists.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (entry& e);
    +
    +

    swaps the content of this with e.

    +[report issue]
    +
    +

    operator[]()

    +
    +entry& operator[] (string_view key);
    +const entry& operator[] (string_view key) const;
    +
    +

    All of these functions requires the entry to be a dictionary, if it +isn't they will throw system_error.

    +

    The non-const versions of the operator[] will return a reference +to either the existing element at the given key or, if there is no +element with the given key, a reference to a newly inserted element at +that key.

    +

    The const version of operator[] will only return a reference to an +existing element at the given key. If the key is not found, it will +throw system_error.

    +[report issue]
    +
    +

    find_key()

    +
    +entry const* find_key (string_view key) const;
    +entry* find_key (string_view key);
    +
    +

    These functions requires the entry to be a dictionary, if it isn't +they will throw system_error.

    +

    They will look for an element at the given key in the dictionary, if +the element cannot be found, they will return nullptr. If an element +with the given key is found, the return a pointer to it.

    +[report issue]
    +
    +

    to_string()

    +
    +std::string to_string (bool single_line = false) const;
    +
    +

    returns a pretty-printed string representation +of the bencoded structure, with JSON-style syntax

    +[report issue]
    +
    +

    enum data_type

    +

    Declared in "libtorrent/entry.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    int_t0 
    string_t1 
    list_t2 
    dictionary_t3 
    undefined_t4 
    preformatted_t5 
    +[report issue]
    +
    m_type_queried
    +
    in debug mode this is set to false by bdecode to indicate that the +program has not yet queried the type of this entry, and should not +assume that it has a certain type. This is asserted in the accessor +functions. This does not apply if exceptions are used.
    +
    +[report issue]
    +
    +
    +

    bencode()

    +

    Declared in "libtorrent/bencode.hpp"

    +
    +template<class OutIt> int bencode (OutIt out, const entry& e);
    +
    +

    This function will encode data to bencoded form.

    +

    The entry class is the internal representation of the bencoded data +and it can be used to retrieve information, an entry can also be build by +the program and given to bencode() to encode it into the OutIt +iterator.

    +

    OutIt is an OutputIterator. It's a template and usually +instantiated as ostream_iterator or back_insert_iterator. This +function assumes the value_type of the iterator is a char. +In order to encode entry e into a buffer, do:

    +
    +std::vector<char> buffer;
    +bencode(std::back_inserter(buf), e);
    +
    +[report issue]
    +
    +

    operator<<()

    +

    Declared in "libtorrent/entry.hpp"

    +
    +inline std::ostream& operator<< (std::ostream& os, const entry& e);
    +
    +

    prints the bencoded structure to the ostream as a JSON-style structure.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Core.html b/docs/reference-Core.html new file mode 100644 index 0000000..161c542 --- /dev/null +++ b/docs/reference-Core.html @@ -0,0 +1,4513 @@ + + + + + + +reference-Core.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Core

    + +[report issue]
    +

    peer_class_info

    +

    Declared in "libtorrent/peer_class.hpp"

    +

    holds settings for a peer class. Used in set_peer_class() and +get_peer_class() calls.

    +
    +struct peer_class_info
    +{
    +   bool ignore_unchoke_slots;
    +   int connection_limit_factor;
    +   std::string label;
    +   int upload_limit;
    +   int download_limit;
    +   int upload_priority;
    +   int download_priority;
    +};
    +
    +[report issue]
    +
    ignore_unchoke_slots
    +
    ignore_unchoke_slots determines whether peers should always +unchoke a peer, regardless of the choking algorithm, or if it should +honor the unchoke slot limits. It's used for local peers by default. +If any of the peer classes a peer belongs to has this set to true, +that peer will be unchoked at all times.
    +
    +[report issue]
    +
    connection_limit_factor
    +
    adjusts the connection limit (global and per torrent) that applies to +this peer class. By default, local peers are allowed to exceed the +normal connection limit for instance. This is specified as a percent +factor. 100 makes the peer class apply normally to the limit. 200 +means as long as there are fewer connections than twice the limit, we +accept this peer. This factor applies both to the global connection +limit and the per-torrent limit. Note that if not used carefully one +peer class can potentially completely starve out all other over time.
    +
    +[report issue]
    +
    label
    +
    not used by libtorrent. It's intended as a potentially user-facing +identifier of this peer class.
    +
    + +[report issue]
    +
    upload_limit download_limit
    +
    transfer rates limits for the whole peer class. They are specified in +bytes per second and apply to the sum of all peers that are members of +this class.
    +
    + +[report issue]
    +
    upload_priority download_priority
    +
    relative priorities used by the bandwidth allocator in the rate +limiter. If no rate limits are in use, the priority is not used +either. Priorities start at 1 (0 is not a valid priority) and may not +exceed 255.
    +
    +[report issue]
    +
    +

    peer_connection_handle

    +

    Declared in "libtorrent/peer_connection_handle.hpp"

    +

    the peer_connection_handle class provides a handle to the internal peer +connection object, to be used by plugins. This is a low level interface that +may not be stable across libtorrent versions

    +
    +struct peer_connection_handle
    +{
    +   explicit peer_connection_handle (std::weak_ptr<peer_connection> impl);
    +   connection_type type () const;
    +   void add_extension (std::shared_ptr<peer_plugin>);
    +   peer_plugin const* find_plugin (string_view type) const;
    +   bool is_seed () const;
    +   bool upload_only () const;
    +   bool has_piece (piece_index_t i) const;
    +   peer_id const& pid () const;
    +   bool is_interesting () const;
    +   bool is_choked () const;
    +   bool has_peer_choked () const;
    +   bool is_peer_interested () const;
    +   void choke_this_peer ();
    +   void maybe_unchoke_this_peer ();
    +   void get_peer_info (peer_info& p) const;
    +   torrent_handle associated_torrent () const;
    +   tcp::endpoint local_endpoint () const;
    +   tcp::endpoint const& remote () const;
    +   bool is_connecting () const;
    +   void disconnect (error_code const& ec, operation_t op
    +      , disconnect_severity_t = peer_connection_interface::normal);
    +   bool is_outgoing () const;
    +   bool is_disconnecting () const;
    +   bool on_local_network () const;
    +   bool ignore_unchoke_slots () const;
    +   bool failed () const;
    +   bool should_log (peer_log_alert::direction_t direction) const;
    +   void peer_log (peer_log_alert::direction_t direction
    +      , char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5);
    +   bool can_disconnect (error_code const& ec) const;
    +   bool has_metadata () const;
    +   bool in_handshake () const;
    +   void send_buffer (char const* begin, int size);
    +   std::time_t last_seen_complete () const;
    +   time_point time_of_last_unchoke () const;
    +   bool operator== (peer_connection_handle const& o) const;
    +   bool operator!= (peer_connection_handle const& o) const;
    +   bool operator< (peer_connection_handle const& o) const;
    +   std::shared_ptr<peer_connection> native_handle () const;
    +};
    +
    +[report issue]
    +
    +

    bt_peer_connection_handle

    +

    Declared in "libtorrent/peer_connection_handle.hpp"

    +

    The bt_peer_connection_handle provides a handle to the internal bittorrent +peer connection object to plugins. It's low level and may not be a stable API +across libtorrent versions.

    +
    +struct bt_peer_connection_handle : peer_connection_handle
    +{
    +   explicit bt_peer_connection_handle (peer_connection_handle pc);
    +   bool support_extensions () const;
    +   bool packet_finished () const;
    +   bool supports_encryption () const;
    +   void switch_recv_crypto (std::shared_ptr<crypto_plugin> crypto);
    +   void switch_send_crypto (std::shared_ptr<crypto_plugin> crypto);
    +   std::shared_ptr<bt_peer_connection> native_handle () const;
    +};
    +
    +[report issue]
    +
    +

    torrent_status

    +

    Declared in "libtorrent/torrent_status.hpp"

    +

    holds a snapshot of the status of a torrent, as queried by +torrent_handle::status().

    +
    +struct torrent_status
    +{
    +   bool operator== (torrent_status const& st) const;
    +   time_duration next_announce = seconds (0);
    +
    +   enum state_t
    +   {
    +      checking_files,
    +      downloading_metadata,
    +      downloading,
    +      finished,
    +      seeding,
    +      allocating,
    +      checking_resume_data,
    +   };
    +
    +   torrent_handle handle;
    +   error_code errc;
    +   file_index_t error_file  = torrent_status::error_file_none;
    +   static constexpr file_index_t error_file_none {-1};
    +   static constexpr file_index_t error_file_ssl_ctx {-3};
    +   static constexpr file_index_t error_file_metadata {-4};
    +   static constexpr file_index_t error_file_exception {-5};
    +   static constexpr file_index_t error_file_partfile {-6};
    +   std::string save_path;
    +   std::string name;
    +   std::weak_ptr<const torrent_info> torrent_file;
    +   std::string current_tracker;
    +   std::int64_t total_download  = 0;
    +   std::int64_t total_upload  = 0;
    +   std::int64_t total_payload_download  = 0;
    +   std::int64_t total_payload_upload  = 0;
    +   std::int64_t total_failed_bytes  = 0;
    +   std::int64_t total_redundant_bytes  = 0;
    +   typed_bitfield<piece_index_t> pieces;
    +   typed_bitfield<piece_index_t> verified_pieces;
    +   std::int64_t total_done  = 0;
    +   std::int64_t total  = 0;
    +   std::int64_t total_wanted_done  = 0;
    +   std::int64_t total_wanted  = 0;
    +   std::int64_t all_time_upload  = 0;
    +   std::int64_t all_time_download  = 0;
    +   std::time_t added_time  = 0;
    +   std::time_t completed_time  = 0;
    +   std::time_t last_seen_complete  = 0;
    +   storage_mode_t storage_mode  = storage_mode_sparse;
    +   float progress  = 0.f;
    +   int progress_ppm  = 0;
    +   queue_position_t queue_position {};
    +   int download_rate  = 0;
    +   int upload_rate  = 0;
    +   int download_payload_rate  = 0;
    +   int upload_payload_rate  = 0;
    +   int num_seeds  = 0;
    +   int num_peers  = 0;
    +   int num_complete  = -1;
    +   int num_incomplete  = -1;
    +   int list_seeds  = 0;
    +   int list_peers  = 0;
    +   int connect_candidates  = 0;
    +   int num_pieces  = 0;
    +   int distributed_full_copies  = 0;
    +   int distributed_fraction  = 0;
    +   float distributed_copies  = 0.f;
    +   int block_size  = 0;
    +   int num_uploads  = 0;
    +   int num_connections  = 0;
    +   int uploads_limit  = 0;
    +   int connections_limit  = 0;
    +   int up_bandwidth_queue  = 0;
    +   int down_bandwidth_queue  = 0;
    +   int seed_rank  = 0;
    +   state_t state  = checking_resume_data;
    +   bool need_save_resume  = false;
    +   bool is_seeding  = false;
    +   bool is_finished  = false;
    +   bool has_metadata  = false;
    +   bool has_incoming  = false;
    +   bool moving_storage  = false;
    +   bool announcing_to_trackers  = false;
    +   bool announcing_to_lsd  = false;
    +   bool announcing_to_dht  = false;
    +   sha1_hash info_hash;
    +   time_point last_upload;
    +   time_point last_download;
    +   seconds active_duration;
    +   seconds finished_duration;
    +   seconds seeding_duration;
    +   torrent_flags_t flags {};
    +};
    +
    +[report issue]
    +

    operator==()

    +
    +bool operator== (torrent_status const& st) const;
    +
    +

    compares if the torrent status objects come from the same torrent. i.e. +only the torrent_handle field is compared.

    +[report issue]
    +
    +

    seconds()

    +
    +time_duration next_announce = seconds (0);
    +
    +

    the time until the torrent will announce itself to the tracker.

    +[report issue]
    +
    +

    enum state_t

    +

    Declared in "libtorrent/torrent_status.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    checking_files1The torrent has not started its download yet, and is +currently checking existing files.
    downloading_metadata2The torrent is trying to download metadata from peers. +This implies the ut_metadata extension is in use.
    downloading3The torrent is being downloaded. This is the state +most torrents will be in most of the time. The progress +meter will tell how much of the files that has been +downloaded.
    finished4In this state the torrent has finished downloading but +still doesn't have the entire torrent. i.e. some pieces +are filtered and won't get downloaded.
    seeding5In this state the torrent has finished downloading and +is a pure seeder.
    allocating6If the torrent was started in full allocation mode, this +indicates that the (disk) storage for the torrent is +allocated.
    checking_resume_data7The torrent is currently checking the fast resume data and +comparing it to the files on disk. This is typically +completed in a fraction of a second, but if you add a +large number of torrents at once, they will queue up.
    +[report issue]
    +
    handle
    +
    a handle to the torrent whose status the object represents.
    +
    +[report issue]
    +
    errc
    +
    may be set to an error code describing why the torrent was paused, in +case it was paused by an error. If the torrent is not paused or if it's +paused but not because of an error, this error_code is not set. +if the error is attributed specifically to a file, error_file is set to +the index of that file in the .torrent file.
    +
    +[report issue]
    +
    error_file
    +
    if the torrent is stopped because of an disk I/O error, this field +contains the index of the file in the torrent that encountered the +error. If the error did not originate in a file in the torrent, there +are a few special values this can be set to: error_file_none, +error_file_ssl_ctx, error_file_exception, error_file_partfile or +error_file_metadata;
    +
    +[report issue]
    +
    error_file_none
    +
    special values for error_file to describe which file or component +encountered the error (errc). +the error did not occur on a file
    +
    +[report issue]
    +
    error_file_ssl_ctx
    +
    the error occurred setting up the SSL context
    +
    +[report issue]
    +
    error_file_metadata
    +
    the error occurred while loading the .torrent file via the user +supplied load function
    +
    +[report issue]
    +
    error_file_exception
    +
    there was a serious error reported in this torrent. The error code +or a torrent log alert may provide more information.
    +
    +[report issue]
    +
    error_file_partfile
    +
    the error occurred with the partfile
    +
    +[report issue]
    +
    save_path
    +
    the path to the directory where this torrent's files are stored. +It's typically the path as was given to async_add_torrent() or +add_torrent() when this torrent was started. This field is only +included if the torrent status is queried with +torrent_handle::query_save_path.
    +
    +[report issue]
    +
    name
    +
    the name of the torrent. Typically this is derived from the +.torrent file. In case the torrent was started without metadata, +and hasn't completely received it yet, it returns the name given +to it when added to the session. See session::add_torrent. +This field is only included if the torrent status is queried +with torrent_handle::query_name.
    +
    +[report issue]
    +
    torrent_file
    +
    set to point to the torrent_info object for this torrent. It's +only included if the torrent status is queried with +torrent_handle::query_torrent_file.
    +
    +[report issue]
    +
    current_tracker
    +
    the URL of the last working tracker. If no tracker request has +been successful yet, it's set to an empty string.
    +
    + +[report issue]
    +
    total_download total_upload
    +
    the number of bytes downloaded and uploaded to all peers, accumulated, +this session only. The session is considered to restart when a +torrent is paused and restarted again. When a torrent is paused, these +counters are reset to 0. If you want complete, persistent, stats, see +all_time_upload and all_time_download.
    +
    + +[report issue]
    +
    total_payload_download total_payload_upload
    +
    counts the amount of bytes send and received this session, but only +the actual payload data (i.e the interesting data), these counters +ignore any protocol overhead. The session is considered to restart +when a torrent is paused and restarted again. When a torrent is +paused, these counters are reset to 0.
    +
    +[report issue]
    +
    total_failed_bytes
    +
    the number of bytes that has been downloaded and that has failed the +piece hash test. In other words, this is just how much crap that has +been downloaded since the torrent was last started. If a torrent is +paused and then restarted again, this counter will be reset.
    +
    +[report issue]
    +
    total_redundant_bytes
    +
    the number of bytes that has been downloaded even though that data +already was downloaded. The reason for this is that in some situations +the same data can be downloaded by mistake. When libtorrent sends +requests to a peer, and the peer doesn't send a response within a +certain timeout, libtorrent will re-request that block. Another +situation when libtorrent may re-request blocks is when the requests +it sends out are not replied in FIFO-order (it will re-request blocks +that are skipped by an out of order block). This is supposed to be as +low as possible. This only counts bytes since the torrent was last +started. If a torrent is paused and then restarted again, this counter +will be reset.
    +
    +[report issue]
    +
    pieces
    +
    a bitmask that represents which pieces we have (set to true) and the +pieces we don't have. It's a pointer and may be set to 0 if the +torrent isn't downloading or seeding.
    +
    +[report issue]
    +
    verified_pieces
    +
    a bitmask representing which pieces has had their hash checked. This +only applies to torrents in seed mode. If the torrent is not in seed +mode, this bitmask may be empty.
    +
    +[report issue]
    +
    total_done
    +
    the total number of bytes of the file(s) that we have. All this does +not necessarily has to be downloaded during this session (that's +total_payload_download).
    +
    +[report issue]
    +
    total
    +
    the total number of bytes to download for this torrent. This +may be less than the size of the torrent in case there are +pad files. This number only counts bytes that will actually +be requested from peers.
    +
    +[report issue]
    +
    total_wanted_done
    +
    the number of bytes we have downloaded, only counting the pieces that +we actually want to download. i.e. excluding any pieces that we have +but have priority 0 (i.e. not wanted).
    +
    +[report issue]
    +
    total_wanted
    +
    The total number of bytes we want to download. This may be smaller +than the total torrent size in case any pieces are prioritized to 0, +i.e. not wanted
    +
    + +[report issue]
    +
    all_time_upload all_time_download
    +
    are accumulated upload and download payload byte counters. They are +saved in and restored from resume data to keep totals across sessions.
    +
    +[report issue]
    +
    added_time
    +
    the posix-time when this torrent was added. i.e. what time(nullptr) +returned at the time.
    +
    +[report issue]
    +
    completed_time
    +
    the posix-time when this torrent was finished. If the torrent is not +yet finished, this is 0.
    +
    +[report issue]
    +
    last_seen_complete
    +
    the time when we, or one of our peers, last saw a complete copy of +this torrent.
    +
    +[report issue]
    +
    storage_mode
    +
    The allocation mode for the torrent. See storage_mode_t for the +options. For more information, see storage allocation.
    +
    +[report issue]
    +
    progress
    +
    a value in the range [0, 1], that represents the progress of the +torrent's current task. It may be checking files or downloading.
    +
    +[report issue]
    +
    progress_ppm
    +

    progress parts per million (progress * 1000000) when disabling +floating point operations, this is the only option to query progress

    +

    reflects the same value as progress, but instead in a range [0, +1000000] (ppm = parts per million). When floating point operations are +disabled, this is the only alternative to the floating point value in +progress.

    +
    +
    +[report issue]
    +
    queue_position
    +
    the position this torrent has in the download +queue. If the torrent is a seed or finished, this is -1.
    +
    + +[report issue]
    +
    download_rate upload_rate
    +
    the total rates for all peers for this torrent. These will usually +have better precision than summing the rates from all peers. The rates +are given as the number of bytes per second.
    +
    + +[report issue]
    +
    download_payload_rate upload_payload_rate
    +
    the total transfer rate of payload only, not counting protocol +chatter. This might be slightly smaller than the other rates, but if +projected over a long time (e.g. when calculating ETA:s) the +difference may be noticeable.
    +
    +[report issue]
    +
    num_seeds
    +
    the number of peers that are seeding that this client is +currently connected to.
    +
    +[report issue]
    +
    num_peers
    +
    the number of peers this torrent currently is connected to. Peer +connections that are in the half-open state (is attempting to connect) +or are queued for later connection attempt do not count. Although they +are visible in the peer list when you call get_peer_info().
    +
    + +[report issue]
    +
    num_complete num_incomplete
    +
    if the tracker sends scrape info in its announce reply, these fields +will be set to the total number of peers that have the whole file and +the total number of peers that are still downloading. set to -1 if the +tracker did not send any scrape data in its announce reply.
    +
    + +[report issue]
    +
    list_seeds list_peers
    +
    the number of seeds in our peer list and the total number of peers +(including seeds). We are not necessarily connected to all the peers +in our peer list. This is the number of peers we know of in total, +including banned peers and peers that we have failed to connect to.
    +
    +[report issue]
    +
    connect_candidates
    +
    the number of peers in this torrent's peer list that is a candidate to +be connected to. i.e. It has fewer connect attempts than the max fail +count, it is not a seed if we are a seed, it is not banned etc. If +this is 0, it means we don't know of any more peers that we can try.
    +
    +[report issue]
    +
    num_pieces
    +
    the number of pieces that has been downloaded. It is equivalent to: +std::accumulate(pieces->begin(), pieces->end()). So you don't have +to count yourself. This can be used to see if anything has updated +since last time if you want to keep a graph of the pieces up to date.
    +
    +[report issue]
    +
    distributed_full_copies
    +
    the number of distributed copies of the torrent. Note that one copy +may be spread out among many peers. It tells how many copies there are +currently of the rarest piece(s) among the peers this client is +connected to.
    +
    +[report issue]
    +
    distributed_fraction
    +

    tells the share of pieces that have more copies than the rarest +piece(s). Divide this number by 1000 to get the fraction.

    +

    For example, if distributed_full_copies is 2 and +distributed_fraction is 500, it means that the rarest pieces have +only 2 copies among the peers this torrent is connected to, and that +50% of all the pieces have more than two copies.

    +

    If we are a seed, the piece picker is deallocated as an optimization, +and piece availability is no longer tracked. In this case the +distributed copies members are set to -1.

    +
    +
    +[report issue]
    +
    distributed_copies
    +

    the number of distributed copies of the file. note that one copy may +be spread out among many peers. This is a floating point +representation of the distributed copies.

    +
    +
    the integer part tells how many copies
    +
    there are of the rarest piece(s)
    +
    the fractional part tells the fraction of pieces that
    +
    have more copies than the rarest piece(s).
    +
    +
    +
    +[report issue]
    +
    block_size
    +
    the size of a block, in bytes. A block is a sub piece, it is the +number of bytes that each piece request asks for and the number of +bytes that each bit in the partial_piece_info's bitset represents, +see get_download_queue(). This is typically 16 kB, but it may be +smaller, if the pieces are smaller.
    +
    +[report issue]
    +
    num_uploads
    +
    the number of unchoked peers in this torrent.
    +
    +[report issue]
    +
    num_connections
    +
    the number of peer connections this torrent has, including half-open +connections that hasn't completed the bittorrent handshake yet. This +is always >= num_peers.
    +
    +[report issue]
    +
    uploads_limit
    +
    the set limit of upload slots (unchoked peers) for this torrent.
    +
    +[report issue]
    +
    connections_limit
    +
    the set limit of number of connections for this torrent.
    +
    + +[report issue]
    +
    up_bandwidth_queue down_bandwidth_queue
    +
    the number of peers in this torrent that are waiting for more +bandwidth quota from the torrent rate limiter. This can determine if +the rate you get from this torrent is bound by the torrents limit or +not. If there is no limit set on this torrent, the peers might still +be waiting for bandwidth quota from the global limiter, but then they +are counted in the session_status object.
    +
    +[report issue]
    +
    seed_rank
    +
    A rank of how important it is to seed the torrent, it is used to +determine which torrents to seed and which to queue. It is based on +the peer to seed ratio from the tracker scrape. For more information, +see queuing. Higher value means more important to seed
    +
    +[report issue]
    +
    state
    +
    the main state the torrent is in. See torrent_status::state_t.
    +
    +[report issue]
    +
    need_save_resume
    +
    true if this torrent has unsaved changes +to its download state and statistics since the last resume data +was saved.
    +
    +[report issue]
    +
    is_seeding
    +
    true if all pieces have been downloaded.
    +
    +[report issue]
    +
    is_finished
    +
    true if all pieces that have a priority > 0 are downloaded. There is +only a distinction between finished and seeding if some pieces or +files have been set to priority 0, i.e. are not downloaded.
    +
    +[report issue]
    +
    has_metadata
    +
    true if this torrent has metadata (either it was started from a +.torrent file or the metadata has been downloaded). The only scenario +where this can be false is when the torrent was started torrent-less +(i.e. with just an info-hash and tracker ip, a magnet link for +instance).
    +
    +[report issue]
    +
    has_incoming
    +
    true if there has ever been an incoming connection attempt to this +torrent.
    +
    +[report issue]
    +
    moving_storage
    +
    this is true if this torrent's storage is currently being moved from +one location to another. This may potentially be a long operation +if a large file ends up being copied from one drive to another.
    +
    + + +[report issue]
    +
    announcing_to_trackers announcing_to_lsd announcing_to_dht
    +
    these are set to true if this torrent is allowed to announce to the +respective peer source. Whether they are true or false is determined by +the queue logic/auto manager. Torrents that are not auto managed will +always be allowed to announce to all peer sources.
    +
    +[report issue]
    +
    info_hash
    +
    the info-hash for this torrent
    +
    + +[report issue]
    +
    last_upload last_download
    +
    the timestamps of the last time this torrent uploaded or downloaded +payload to any peer.
    +
    + + +[report issue]
    +
    active_duration finished_duration seeding_duration
    +
    these are cumulative counters of for how long the torrent has been in +different states. active means not paused and added to session. Whether +it has found any peers or not is not relevant. +finished means all selected files/pieces were downloaded and available +to other peers (this is always a subset of active time). +seeding means all files/pieces were downloaded and available to +peers. Being available to peers does not imply there are other peers +asking for the payload.
    +
    +[report issue]
    +
    flags
    +
    reflects several of the torrent's flags. For more +information, see torrent_handle::flags().
    +
    +[report issue]
    +
    +
    +

    announce_endpoint

    +

    Declared in "libtorrent/announce_entry.hpp"

    +

    announces are sent to each tracker using every listen socket +this class holds information about one listen socket for one tracker

    +
    +struct announce_endpoint
    +{
    +   void reset ();
    +   void failed (int backoff_ratio, seconds32 retry_interval = seconds32(0));
    +   bool can_announce (time_point now, bool is_seed, std::uint8_t fail_limit) const;
    +   bool is_working () const;
    +
    +   std::string message;
    +   error_code last_error;
    +   tcp::endpoint local_endpoint;
    +   int scrape_incomplete  = -1;
    +   int scrape_complete  = -1;
    +   int scrape_downloaded  = -1;
    +   std::uint8_t fails : 7;
    +   bool updating : 1;
    +   bool start_sent : 1;
    +   bool complete_sent : 1;
    +   bool enabled : 1;
    +};
    +
    +[report issue]
    +

    reset()

    +
    +void reset ();
    +
    +

    reset announce counters and clears the started sent flag. +The announce_endpoint will look like we've never talked to +the tracker.

    +[report issue]
    +
    +

    failed()

    +
    +void failed (int backoff_ratio, seconds32 retry_interval = seconds32(0));
    +
    +

    updates the failure counter and time-outs for re-trying. +This is called when the tracker announce fails.

    +[report issue]
    +
    +

    can_announce()

    +
    +bool can_announce (time_point now, bool is_seed, std::uint8_t fail_limit) const;
    +
    +

    returns true if we can announce to this tracker now. +The current time is passed in as now. The is_seed +argument is necessary because once we become a seed, we +need to announce right away, even if the re-announce timer +hasn't expired yet.

    +[report issue]
    +
    +

    is_working()

    +
    +bool is_working () const;
    +
    +

    returns true if the last time we tried to announce to this +tracker succeeded, or if we haven't tried yet.

    +[report issue]
    +
    message
    +
    if this tracker has returned an error or warning message +that message is stored here
    +
    +[report issue]
    +
    last_error
    +
    if this tracker failed the last time it was contacted +this error code specifies what error occurred
    +
    +[report issue]
    +
    local_endpoint
    +
    the local endpoint of the listen interface associated with this endpoint
    +
    + + +[report issue]
    +
    scrape_incomplete scrape_complete scrape_downloaded
    +
    if this tracker has returned scrape data, these fields are filled in +with valid numbers. Otherwise they are set to -1. incomplete counts +the number of current downloaders. complete counts the number of +current peers completed the download, or "seeds". downloaded is the +cumulative number of completed downloads.
    +
    +[report issue]
    +
    fails
    +
    the number of times in a row we have failed to announce to this +tracker.
    +
    +[report issue]
    +
    updating
    +
    true while we're waiting for a response from the tracker.
    +
    +[report issue]
    +
    start_sent
    +
    set to true when we get a valid response from an announce +with event=started. If it is set, we won't send start in the subsequent +announces.
    +
    +[report issue]
    +
    complete_sent
    +
    set to true when we send a event=completed.
    +
    +[report issue]
    +
    enabled
    +
    set to false to not announce from this endpoint
    +
    +[report issue]
    +
    +
    +

    announce_entry

    +

    Declared in "libtorrent/announce_entry.hpp"

    +

    this class holds information about one bittorrent tracker, as it +relates to a specific torrent.

    +
    +struct announce_entry
    +{
    +   explicit announce_entry (string_view u);
    +   announce_entry (announce_entry const&);
    +   announce_entry ();
    +   announce_entry& operator= (announce_entry const&);
    +   ~announce_entry ();
    +   void reset ();
    +   void trim ();
    +
    +   enum tracker_source
    +   {
    +      source_torrent,
    +      source_client,
    +      source_magnet_link,
    +      source_tex,
    +   };
    +
    +   std::string url;
    +   std::string trackerid;
    +   std::vector<announce_endpoint> endpoints;
    +   std::uint8_t tier  = 0;
    +   std::uint8_t fail_limit  = 0;
    +   std::uint8_t source:4;
    +   bool verified:1;
    +};
    +
    + + +[report issue]
    +

    ~announce_entry() operator=() announce_entry()

    +
    +explicit announce_entry (string_view u);
    +announce_entry (announce_entry const&);
    +announce_entry ();
    +announce_entry& operator= (announce_entry const&);
    +~announce_entry ();
    +
    +

    constructs a tracker announce entry with u as the URL.

    +[report issue]
    +
    +

    reset()

    +
    +void reset ();
    +
    +

    reset announce counters and clears the started sent flag. +The announce_entry will look like we've never talked to +the tracker.

    +[report issue]
    +
    +

    trim()

    +
    +void trim ();
    +
    +

    trims whitespace characters from the beginning of the URL.

    +[report issue]
    +
    +

    enum tracker_source

    +

    Declared in "libtorrent/announce_entry.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    source_torrent1the tracker was part of the .torrent file
    source_client2the tracker was added programmatically via the add_tracker() function
    source_magnet_link4the tracker was part of a magnet link
    source_tex8the tracker was received from the swarm via tracker exchange
    +[report issue]
    +
    url
    +
    tracker URL as it appeared in the torrent file
    +
    +[report issue]
    +
    trackerid
    +
    the current &trackerid= argument passed to the tracker. +this is optional and is normally empty (in which case no +trackerid is sent).
    +
    +[report issue]
    +
    endpoints
    +
    each local listen socket (endpoint) will announce to the tracker. This +list contains state per endpoint.
    +
    +[report issue]
    +
    tier
    +
    the tier this tracker belongs to
    +
    +[report issue]
    +
    fail_limit
    +
    the max number of failures to announce to this tracker in +a row, before this tracker is not used anymore. 0 means unlimited
    +
    +[report issue]
    +
    source
    +
    a bitmask specifying which sources we got this tracker from.
    +
    +[report issue]
    +
    verified
    +
    set to true the first time we receive a valid response +from this tracker.
    +
    +[report issue]
    +
    +
    +

    open_file_state

    +

    Declared in "libtorrent/disk_interface.hpp"

    +

    this contains information about a file that's currently open by the +libtorrent disk I/O subsystem. It's associated with a single torrent.

    +
    +struct open_file_state
    +{
    +   file_index_t file_index;
    +   file_open_mode_t open_mode;
    +   time_point last_use;
    +};
    +
    +[report issue]
    +
    file_index
    +
    the index of the file this entry refers to into the file_storage +file list of this torrent. This starts indexing at 0.
    +
    +[report issue]
    +
    open_mode
    +

    open_mode is a bitmask of the file flags this file is currently +opened with. These are the flags used in the file::open() function. +For possible flags, see file_open_mode_t.

    +

    Note that the read/write mode is not a bitmask. The two least significant bits are used +to represent the read/write mode. Those bits can be masked out using the rw_mask constant.

    +
    +
    +[report issue]
    +
    last_use
    +
    a (high precision) timestamp of when the file was last used.
    +
    +[report issue]
    +
    +

    peer_class_type_filter

    +

    Declared in "libtorrent/peer_class_type_filter.hpp"

    +

    peer_class_type_filter is a simple container for rules for adding and subtracting +peer-classes from peers. It is applied after the peer class filter is applied (which +is based on the peer's IP address).

    +
    +struct peer_class_type_filter
    +{
    +   void remove (socket_type_t const st, peer_class_t const peer_class);
    +   void add (socket_type_t const st, peer_class_t const peer_class);
    +   void disallow (socket_type_t const st, peer_class_t const peer_class);
    +   void allow (socket_type_t const st, peer_class_t const peer_class);
    +   std::uint32_t apply (socket_type_t const st, std::uint32_t peer_class_mask);
    +
    +   enum socket_type_t
    +   {
    +      tcp_socket,
    +      utp_socket,
    +      ssl_tcp_socket,
    +      ssl_utp_socket,
    +      i2p_socket,
    +      num_socket_types,
    +   };
    +};
    +
    + +[report issue]
    +

    add() remove()

    +
    +void remove (socket_type_t const st, peer_class_t const peer_class);
    +void add (socket_type_t const st, peer_class_t const peer_class);
    +
    +

    add() and remove() adds and removes a peer class to be added +to new peers based on socket type.

    + +[report issue]
    +
    +

    disallow() allow()

    +
    +void disallow (socket_type_t const st, peer_class_t const peer_class);
    +void allow (socket_type_t const st, peer_class_t const peer_class);
    +
    +

    disallow() and allow() adds and removes a peer class to be +removed from new peers based on socket type.

    +

    The peer_class argument cannot be greater than 31. The bitmasks representing +peer classes in the peer_class_type_filter are 32 bits.

    +[report issue]
    +
    +

    apply()

    +
    +std::uint32_t apply (socket_type_t const st, std::uint32_t peer_class_mask);
    +
    +

    takes a bitmask of peer classes and returns a new bitmask of +peer classes after the rules have been applied, based on the socket type argument +(st).

    +[report issue]
    +
    +

    enum socket_type_t

    +

    Declared in "libtorrent/peer_class_type_filter.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp_socket0these match the socket types from socket_type.hpp +shifted one down
    utp_socket1 
    ssl_tcp_socket2 
    ssl_utp_socket3 
    i2p_socket4 
    num_socket_types5 
    +[report issue]
    +
    +
    +

    block_info

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    holds the state of a block in a piece. Who we requested +it from and how far along we are at downloading it.

    +
    +struct block_info
    +{
    +   void set_peer (tcp::endpoint const& ep);
    +   tcp::endpoint peer () const;
    +
    +   enum block_state_t
    +   {
    +      none,
    +      requested,
    +      writing,
    +      finished,
    +   };
    +
    +   unsigned bytes_progress:15;
    +   unsigned block_size:15;
    +   unsigned state:2;
    +   unsigned num_peers:14;
    +};
    +
    + +[report issue]
    +

    peer() set_peer()

    +
    +void set_peer (tcp::endpoint const& ep);
    +tcp::endpoint peer () const;
    +
    +

    The peer is the ip address of the peer this block was downloaded from.

    +[report issue]
    +
    +

    enum block_state_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0This block has not been downloaded or requested form any peer.
    requested1The block has been requested, but not completely downloaded yet.
    writing2The block has been downloaded and is currently queued for being +written to disk.
    finished3The block has been written to disk.
    +[report issue]
    +
    bytes_progress
    +
    the number of bytes that have been received for this block
    +
    +[report issue]
    +
    block_size
    +
    the total number of bytes in this block.
    +
    +[report issue]
    +
    state
    +
    the state this block is in (see block_state_t)
    +
    +[report issue]
    +
    num_peers
    +
    the number of peers that is currently requesting this block. Typically +this is 0 or 1, but at the end of the torrent blocks may be requested +by more peers in parallel to speed things up.
    +
    +[report issue]
    +
    +
    +

    partial_piece_info

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    This class holds information about pieces that have outstanding requests +or outstanding writes

    +
    +struct partial_piece_info
    +{
    +   piece_index_t piece_index;
    +   int blocks_in_piece;
    +   int finished;
    +   int writing;
    +   int requested;
    +   block_info* blocks;
    +};
    +
    +[report issue]
    +
    piece_index
    +
    the index of the piece in question. blocks_in_piece is the number +of blocks in this particular piece. This number will be the same for +most pieces, but +the last piece may have fewer blocks than the standard pieces.
    +
    +[report issue]
    +
    blocks_in_piece
    +
    the number of blocks in this piece
    +
    +[report issue]
    +
    finished
    +
    the number of blocks that are in the finished state
    +
    +[report issue]
    +
    writing
    +
    the number of blocks that are in the writing state
    +
    +[report issue]
    +
    requested
    +
    the number of blocks that are in the requested state
    +
    +[report issue]
    +
    blocks
    +

    this is an array of blocks_in_piece number of +items. One for each block in the piece.

    +
    +

    Warning

    +

    This is a pointer that points to an array +that's owned by the session object. The next time +get_download_queue() is called, it will be invalidated.

    +
    +
    +
    +[report issue]
    +
    +

    torrent_handle

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    You will usually have to store your torrent handles somewhere, since it's +the object through which you retrieve information about the torrent and +aborts the torrent.

    +
    +

    Warning

    +

    Any member function that returns a value or fills in a value has to be +made synchronously. This means it has to wait for the main thread to +complete the query before it can return. This might potentially be +expensive if done from within a GUI thread that needs to stay +responsive. Try to avoid querying for information you don't need, and +try to do it in as few calls as possible. You can get most of the +interesting information about a torrent from the +torrent_handle::status() call.

    +
    +

    The default constructor will initialize the handle to an invalid state. +Which means you cannot perform any operation on it, unless you first +assign it a valid handle. If you try to perform any operation on an +uninitialized handle, it will throw invalid_handle.

    +
    +

    Warning

    +

    All operations on a torrent_handle may throw system_error +exception, in case the handle is no longer referring to a torrent. +There is one exception is_valid() will never throw. Since the torrents +are processed by a background thread, there is no guarantee that a +handle will remain valid between two calls.

    +
    +
    +struct torrent_handle
    +{
    +   torrent_handle () noexcept = default;
    +   void add_piece (piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const;
    +   void read_piece (piece_index_t piece) const;
    +   bool have_piece (piece_index_t piece) const;
    +   void get_peer_info (std::vector<peer_info>& v) const;
    +   torrent_status status (status_flags_t flags = status_flags_t::all()) const;
    +   void get_download_queue (std::vector<partial_piece_info>& queue) const;
    +   void set_piece_deadline (piece_index_t index, int deadline, deadline_flags_t flags = {}) const;
    +   void reset_piece_deadline (piece_index_t index) const;
    +   void clear_piece_deadlines () const;
    +   void file_progress (std::vector<std::int64_t>& progress, int flags = 0) const;
    +   std::vector<open_file_state> file_status () const;
    +   void clear_error () const;
    +   void replace_trackers (std::vector<announce_entry> const&) const;
    +   std::vector<announce_entry> trackers () const;
    +   void add_tracker (announce_entry const&) const;
    +   std::set<std::string> url_seeds () const;
    +   void add_url_seed (std::string const& url) const;
    +   void remove_url_seed (std::string const& url) const;
    +   void add_http_seed (std::string const& url) const;
    +   void remove_http_seed (std::string const& url) const;
    +   std::set<std::string> http_seeds () const;
    +   void add_extension (
    +      std::function<std::shared_ptr<torrent_plugin>(torrent_handle const&, void*)> const& ext
    +      , void* userdata = nullptr);
    +   bool set_metadata (span<char const> metadata) const;
    +   bool is_valid () const;
    +   void resume () const;
    +   void pause (pause_flags_t flags = {}) const;
    +   void unset_flags (torrent_flags_t flags) const;
    +   void set_flags (torrent_flags_t flags) const;
    +   torrent_flags_t flags () const;
    +   void set_flags (torrent_flags_t flags, torrent_flags_t mask) const;
    +   void flush_cache () const;
    +   void force_recheck () const;
    +   void save_resume_data (resume_data_flags_t flags = {}) const;
    +   bool need_save_resume_data () const;
    +   queue_position_t queue_position () const;
    +   void queue_position_down () const;
    +   void queue_position_up () const;
    +   void queue_position_top () const;
    +   void queue_position_bottom () const;
    +   void queue_position_set (queue_position_t p) const;
    +   void set_ssl_certificate (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params
    +      , std::string const& passphrase = "");
    +   void set_ssl_certificate_buffer (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params);
    +   storage_interface* get_storage_impl () const;
    +   std::shared_ptr<const torrent_info> torrent_file () const;
    +   void piece_availability (std::vector<int>& avail) const;
    +   download_priority_t piece_priority (piece_index_t index) const;
    +   void prioritize_pieces (std::vector<std::pair<piece_index_t, download_priority_t>> const& pieces) const;
    +   std::vector<download_priority_t> get_piece_priorities () const;
    +   void piece_priority (piece_index_t index, download_priority_t priority) const;
    +   void prioritize_pieces (std::vector<download_priority_t> const& pieces) const;
    +   std::vector<download_priority_t> get_file_priorities () const;
    +   void file_priority (file_index_t index, download_priority_t priority) const;
    +   download_priority_t file_priority (file_index_t index) const;
    +   void prioritize_files (std::vector<download_priority_t> const& files) const;
    +   void force_dht_announce () const;
    +   void force_reannounce (int seconds = 0, int tracker_index = -1, reannounce_flags_t = {}) const;
    +   void scrape_tracker (int idx = -1) const;
    +   void set_download_limit (int limit) const;
    +   int download_limit () const;
    +   void set_upload_limit (int limit) const;
    +   int upload_limit () const;
    +   void connect_peer (tcp::endpoint const& adr, peer_source_flags_t source = {}
    +      , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const;
    +   void clear_peers ();
    +   void set_max_uploads (int max_uploads) const;
    +   int max_uploads () const;
    +   void set_max_connections (int max_connections) const;
    +   int max_connections () const;
    +   void move_storage (std::string const& save_path
    +      , move_flags_t flags = move_flags_t::always_replace_files
    +      ) const;
    +   void rename_file (file_index_t index, std::string const& new_name) const;
    +   sha1_hash info_hash () const;
    +   bool operator== (const torrent_handle& h) const;
    +   bool operator!= (const torrent_handle& h) const;
    +   bool operator< (const torrent_handle& h) const;
    +   std::uint32_t id () const;
    +   std::shared_ptr<torrent> native_handle () const;
    +
    +   enum file_progress_flags_t
    +   {
    +      piece_granularity,
    +   };
    +
    +   static constexpr add_piece_flags_t overwrite_existing  = 0_bit;
    +   static constexpr status_flags_t query_distributed_copies  = 0_bit;
    +   static constexpr status_flags_t query_accurate_download_counters  = 1_bit;
    +   static constexpr status_flags_t query_last_seen_complete  = 2_bit;
    +   static constexpr status_flags_t query_pieces  = 3_bit;
    +   static constexpr status_flags_t query_verified_pieces  = 4_bit;
    +   static constexpr status_flags_t query_torrent_file  = 5_bit;
    +   static constexpr status_flags_t query_name  = 6_bit;
    +   static constexpr status_flags_t query_save_path  = 7_bit;
    +   static constexpr deadline_flags_t alert_when_available  = 0_bit;
    +   static constexpr pause_flags_t graceful_pause  = 0_bit;
    +   static constexpr pause_flags_t clear_disk_cache  = 1_bit;
    +   static constexpr resume_data_flags_t flush_disk_cache  = 0_bit;
    +   static constexpr resume_data_flags_t save_info_dict  = 1_bit;
    +   static constexpr resume_data_flags_t only_if_modified  = 2_bit;
    +   static constexpr reannounce_flags_t ignore_min_interval  = 0_bit;
    +};
    +
    +[report issue]
    +

    torrent_handle()

    +
    +torrent_handle () noexcept = default;
    +
    +

    constructs a torrent handle that does not refer to a torrent. +i.e. is_valid() will return false.

    +[report issue]
    +
    +

    add_piece()

    +
    +void add_piece (piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const;
    +
    +

    This function will write data to the storage as piece piece, +as if it had been downloaded from a peer. data is expected to +point to a buffer of as many bytes as the size of the specified piece. +The data in the buffer is copied and passed on to the disk IO thread +to be written at a later point.

    +

    By default, data that's already been downloaded is not overwritten by +this buffer. If you trust this data to be correct (and pass the piece +hash check) you may pass the overwrite_existing flag. This will +instruct libtorrent to overwrite any data that may already have been +downloaded with this data.

    +

    Since the data is written asynchronously, you may know that is passed +or failed the hash check by waiting for piece_finished_alert or +hash_failed_alert.

    +

    Adding pieces while the torrent is being checked (i.e. in +torrent_status::checking_files state) is not supported.

    +[report issue]
    +
    +

    read_piece()

    +
    +void read_piece (piece_index_t piece) const;
    +
    +

    This function starts an asynchronous read operation of the specified +piece from this torrent. You must have completed the download of the +specified piece before calling this function.

    +

    When the read operation is completed, it is passed back through an +alert, read_piece_alert. Since this alert is a response to an explicit +call, it will always be posted, regardless of the alert mask.

    +

    Note that if you read multiple pieces, the read operations are not +guaranteed to finish in the same order as you initiated them.

    +[report issue]
    +
    +

    have_piece()

    +
    +bool have_piece (piece_index_t piece) const;
    +
    +

    Returns true if this piece has been completely downloaded and written +to disk, and false otherwise.

    +[report issue]
    +
    +

    get_peer_info()

    +
    +void get_peer_info (std::vector<peer_info>& v) const;
    +
    +

    takes a reference to a vector that will be cleared and filled with one +entry for each peer connected to this torrent, given the handle is +valid. If the torrent_handle is invalid, it will throw +system_error exception. Each entry in the vector contains +information about that particular peer. See peer_info.

    +[report issue]
    +
    +

    status()

    +
    +torrent_status status (status_flags_t flags = status_flags_t::all()) const;
    +
    +

    status() will return a structure with information about the status +of this torrent. If the torrent_handle is invalid, it will throw +system_error exception. See torrent_status. The flags +argument filters what information is returned in the torrent_status. +Some information in there is relatively expensive to calculate, and if +you're not interested in it (and see performance issues), you can +filter them out.

    +

    By default everything is included. The flags you can use to decide +what to include are defined in this class.

    +[report issue]
    +
    +

    get_download_queue()

    +
    +void get_download_queue (std::vector<partial_piece_info>& queue) const;
    +
    +

    get_download_queue() takes a non-const reference to a vector which +it will fill with information about pieces that are partially +downloaded or not downloaded at all but partially requested. See +partial_piece_info for the fields in the returned vector.

    + + +[report issue]
    +
    +

    reset_piece_deadline() clear_piece_deadlines() set_piece_deadline()

    +
    +void set_piece_deadline (piece_index_t index, int deadline, deadline_flags_t flags = {}) const;
    +void reset_piece_deadline (piece_index_t index) const;
    +void clear_piece_deadlines () const;
    +
    +

    This function sets or resets the deadline associated with a specific +piece index (index). libtorrent will attempt to download this +entire piece before the deadline expires. This is not necessarily +possible, but pieces with a more recent deadline will always be +prioritized over pieces with a deadline further ahead in time. The +deadline (and flags) of a piece can be changed by calling this +function again.

    +

    If the piece is already downloaded when this call is made, nothing +happens, unless the alert_when_available flag is set, in which case it +will have the same effect as calling read_piece() for index.

    +

    deadline is the number of milliseconds until this piece should be +completed.

    +

    reset_piece_deadline removes the deadline from the piece. If it +hasn't already been downloaded, it will no longer be considered a +priority.

    +

    clear_piece_deadlines() removes deadlines on all pieces in +the torrent. As if reset_piece_deadline() was called on all pieces.

    +[report issue]
    +
    +

    file_progress()

    +
    +void file_progress (std::vector<std::int64_t>& progress, int flags = 0) const;
    +
    +

    This function fills in the supplied vector with the number of +bytes downloaded of each file in this torrent. The progress values are +ordered the same as the files in the torrent_info. This operation is +not very cheap. Its complexity is O(n + mj). Where n is the number +of files, m is the number of downloading pieces and j is the +number of blocks in a piece.

    +

    The flags parameter can be used to specify the granularity of the +file progress. If left at the default value of 0, the progress will be +as accurate as possible, but also more expensive to calculate. If +torrent_handle::piece_granularity is specified, the progress will +be specified in piece granularity. i.e. only pieces that have been +fully downloaded and passed the hash check count. When specifying +piece granularity, the operation is a lot cheaper, since libtorrent +already keeps track of this internally and no calculation is required.

    +[report issue]
    +
    +

    file_status()

    +
    +std::vector<open_file_state> file_status () const;
    +
    +

    This function returns a vector with status about files +that are open for this torrent. Any file that is not open +will not be reported in the vector, i.e. it's possible that +the vector is empty when returning, if none of the files in the +torrent are currently open.

    +

    See open_file_state

    +[report issue]
    +
    +

    clear_error()

    +
    +void clear_error () const;
    +
    +

    If the torrent is in an error state (i.e. torrent_status::error is +non-empty), this will clear the error and start the torrent again.

    + + +[report issue]
    +
    +

    add_tracker() replace_trackers() trackers()

    +
    +void replace_trackers (std::vector<announce_entry> const&) const;
    +std::vector<announce_entry> trackers () const;
    +void add_tracker (announce_entry const&) const;
    +
    +

    trackers() will return the list of trackers for this torrent. The +announce entry contains both a string url which specify the +announce url for the tracker as well as an int tier, which is +specifies the order in which this tracker is tried. If you want +libtorrent to use another list of trackers for this torrent, you can +use replace_trackers() which takes a list of the same form as the +one returned from trackers() and will replace it. If you want an +immediate effect, you have to call force_reannounce(). See +announce_entry.

    +

    add_tracker() will look if the specified tracker is already in the +set. If it is, it doesn't do anything. If it's not in the current set +of trackers, it will insert it in the tier specified in the +announce_entry.

    +

    The updated set of trackers will be saved in the resume data, and when +a torrent is started with resume data, the trackers from the resume +data will replace the original ones.

    + + +[report issue]
    +
    +

    url_seeds() remove_url_seed() add_url_seed()

    +
    +std::set<std::string> url_seeds () const;
    +void add_url_seed (std::string const& url) const;
    +void remove_url_seed (std::string const& url) const;
    +
    +

    add_url_seed() adds another url to the torrent's list of url +seeds. If the given url already exists in that list, the call has no +effect. The torrent will connect to the server and try to download +pieces from it, unless it's paused, queued, checking or seeding. +remove_url_seed() removes the given url if it exists already. +url_seeds() return a set of the url seeds currently in this +torrent. Note that URLs that fails may be removed automatically from +the list.

    +

    See http seeding for more information.

    + + +[report issue]
    +
    +

    add_http_seed() remove_http_seed() http_seeds()

    +
    +void add_http_seed (std::string const& url) const;
    +void remove_http_seed (std::string const& url) const;
    +std::set<std::string> http_seeds () const;
    +
    +

    These functions are identical as the *_url_seed() variants, but +they operate on BEP 17 web seeds instead of BEP 19.

    +

    See http seeding for more information.

    +[report issue]
    +
    +

    add_extension()

    +
    +void add_extension (
    +      std::function<std::shared_ptr<torrent_plugin>(torrent_handle const&, void*)> const& ext
    +      , void* userdata = nullptr);
    +
    +

    add the specified extension to this torrent. The ext argument is +a function that will be called from within libtorrent's context +passing in the internal torrent object and the specified userdata +pointer. The function is expected to return a shared pointer to +a torrent_plugin instance.

    +[report issue]
    +
    +

    set_metadata()

    +
    +bool set_metadata (span<char const> metadata) const;
    +
    +

    set_metadata expects the info section of metadata. i.e. The +buffer passed in will be hashed and verified against the info-hash. If +it fails, a metadata_failed_alert will be generated. If it passes, +a metadata_received_alert is generated. The function returns true +if the metadata is successfully set on the torrent, and false +otherwise. If the torrent already has metadata, this function will not +affect the torrent, and false will be returned.

    +[report issue]
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    Returns true if this handle refers to a valid torrent and false if it +hasn't been initialized or if the torrent it refers to has been +aborted. Note that a handle may become invalid after it has been added +to the session. Usually this is because the storage for the torrent is +somehow invalid or if the filenames are not allowed (and hence cannot +be opened/created) on your filesystem. If such an error occurs, a +file_error_alert is generated and all handles that refers to that +torrent will become invalid.

    + +[report issue]
    +
    +

    pause() resume()

    +
    +void resume () const;
    +void pause (pause_flags_t flags = {}) const;
    +
    +

    pause(), and resume() will disconnect all peers and reconnect +all peers respectively. When a torrent is paused, it will however +remember all share ratios to all peers and remember all potential (not +connected) peers. Torrents may be paused automatically if there is a +file error (e.g. disk full) or something similar. See +file_error_alert.

    +

    To know if a torrent is paused or not, call +torrent_handle::status() and inspect torrent_status::paused.

    +
    +

    Note

    +

    Torrents that are auto-managed may be automatically resumed again. It +does not make sense to pause an auto-managed torrent without making it +not auto-managed first. Torrents are auto-managed by default when added +to the session. For more information, see queuing.

    +
    + + +[report issue]
    +
    +

    flags() set_flags() unset_flags()

    +
    +void unset_flags (torrent_flags_t flags) const;
    +void set_flags (torrent_flags_t flags) const;
    +torrent_flags_t flags () const;
    +void set_flags (torrent_flags_t flags, torrent_flags_t mask) const;
    +
    +

    sets and gets the torrent state flags. See torrent_flags_t. +The set_flags overload that take a mask will affect all +flags part of the mask, and set their values to what the +flags argument is set to. This allows clearing and +setting flags in a single function call. +The set_flags overload that just takes flags, sets all +the specified flags and leave any other flags unchanged. +unset_flags clears the specified flags, while leaving +any other flags unchanged.

    +

    The seed_mode flag is special, it can only be cleared once the +torrent has been added, and it can only be set as part of the +add_torrent_params flags, when adding the torrent.

    +[report issue]
    +
    +

    flush_cache()

    +
    +void flush_cache () const;
    +
    +

    Instructs libtorrent to flush all the disk caches for this torrent and +close all file handles. This is done asynchronously and you will be +notified that it's complete through cache_flushed_alert.

    +

    Note that by the time you get the alert, libtorrent may have cached +more data for the torrent, but you are guaranteed that whatever cached +data libtorrent had by the time you called +torrent_handle::flush_cache() has been written to disk.

    +[report issue]
    +
    +

    force_recheck()

    +
    +void force_recheck () const;
    +
    +

    force_recheck puts the torrent back in a state where it assumes to +have no resume data. All peers will be disconnected and the torrent +will stop announcing to the tracker. The torrent will be added to the +checking queue, and will be checked (all the files will be read and +compared to the piece hashes). Once the check is complete, the torrent +will start connecting to peers again, as normal. +The torrent will be placed last in queue, i.e. its queue position +will be the highest of all torrents in the session.

    +[report issue]
    +
    +

    save_resume_data()

    +
    +void save_resume_data (resume_data_flags_t flags = {}) const;
    +
    +

    save_resume_data() asks libtorrent to generate fast-resume data for +this torrent.

    +

    This operation is asynchronous, save_resume_data will return +immediately. The resume data is delivered when it's done through an +save_resume_data_alert.

    +

    The fast resume data will be empty in the following cases:

    +
    +
      +
    1. The torrent handle is invalid.
    2. +
    3. The torrent hasn't received valid metadata and was started without +metadata (see libtorrent's metadata from peers extension)
    4. +
    +
    +

    Note that by the time you receive the fast resume data, it may already +be invalid if the torrent is still downloading! The recommended +practice is to first pause the session, then generate the fast resume +data, and then close it down. Make sure to not remove_torrent() before +you receive the save_resume_data_alert though. There's no need to +pause when saving intermittent resume data.

    +
    +

    Warning

    +

    If you pause every torrent individually instead of pausing the +session, every torrent will have its paused state saved in the +resume data!

    +
    +
    +

    Note

    +

    It is typically a good idea to save resume data whenever a torrent +is completed or paused. In those cases you don't need to pause the +torrent or the session, since the torrent will do no more writing to +its files. If you save resume data for torrents when they are +paused, you can accelerate the shutdown process by not saving resume +data again for paused torrents. Completed torrents should have their +resume data saved when they complete and on exit, since their +statistics might be updated.

    +
    +In full allocation mode the resume data is never invalidated by +subsequent writes to the files, since pieces won't move around. This +means that you don't need to pause before writing resume data in full +or sparse mode. If you don't, however, any data written to disk after +you saved resume data and before the session closed is lost.
    +
    +

    It also means that if the resume data is out dated, libtorrent will +not re-check the files, but assume that it is fairly recent. The +assumption is that it's better to loose a little bit than to re-check +the entire file.

    +

    It is still a good idea to save resume data periodically during +download as well as when closing down.

    +

    Example code to pause and save resume data for all torrents and wait +for the alerts:

    +
    +extern int outstanding_resume_data; // global counter of outstanding resume data
    +std::vector<torrent_handle> handles = ses.get_torrents();
    +ses.pause();
    +for (torrent_handle const& h : handles)
    +{
    +        if (!h.is_valid()) continue;
    +        torrent_status s = h.status();
    +        if (!s.has_metadata || !s.need_save_resume_data()) continue;
    +
    +        h.save_resume_data();
    +        ++outstanding_resume_data;
    +}
    +
    +while (outstanding_resume_data > 0)
    +{
    +        alert const* a = ses.wait_for_alert(seconds(10));
    +
    +        // if we don't get an alert within 10 seconds, abort
    +        if (a == nullptr) break;
    +
    +        std::vector<alert*> alerts;
    +        ses.pop_alerts(&alerts);
    +
    +        for (alert* i : alerts)
    +        {
    +                if (alert_cast<save_resume_data_failed_alert>(i))
    +                {
    +                        process_alert(i);
    +                        --outstanding_resume_data;
    +                        continue;
    +                }
    +
    +                save_resume_data_alert const* rd = alert_cast<save_resume_data_alert>(i);
    +                if (rd == nullptr)
    +                {
    +                        process_alert(i);
    +                        continue;
    +                }
    +
    +                torrent_handle h = rd->handle;
    +                torrent_status st = h.status(torrent_handle::query_save_path
    +                        | torrent_handle::query_name);
    +                std::ofstream out((st.save_path
    +                        + "/" + st.name + ".fastresume").c_str()
    +                        , std::ios_base::binary);
    +                std::vector<char> buf = write_resume_data_buf(rd->params);
    +                out.write(buf.data(), buf.size());
    +                --outstanding_resume_data;
    +        }
    +}
    +
    +
    +

    Note

    +

    Note how outstanding_resume_data is a global counter in this +example. This is deliberate, otherwise there is a race condition for +torrents that was just asked to save their resume data, they posted +the alert, but it has not been received yet. Those torrents would +report that they don't need to save resume data again, and skipped by +the initial loop, and thwart the counter otherwise.

    +
    +[report issue]
    +
    +

    need_save_resume_data()

    +
    +bool need_save_resume_data () const;
    +
    +

    This function returns true if any whole chunk has been downloaded +since the torrent was first loaded or since the last time the resume +data was saved. When saving resume data periodically, it makes sense +to skip any torrent which hasn't downloaded anything since the last +time.

    +
    +

    Note

    +

    A torrent's resume data is considered saved as soon as the +save_resume_data_alert is posted. It is important to make sure this +alert is received and handled in order for this function to be +meaningful.

    +
    + + + + +[report issue]
    +
    +

    queue_position_top() queue_position_bottom() queue_position() queue_position_down() queue_position_up()

    +
    +queue_position_t queue_position () const;
    +void queue_position_down () const;
    +void queue_position_up () const;
    +void queue_position_top () const;
    +void queue_position_bottom () const;
    +
    +

    Every torrent that is added is assigned a queue position exactly one +greater than the greatest queue position of all existing torrents. +Torrents that are being seeded have -1 as their queue position, since +they're no longer in line to be downloaded.

    +

    When a torrent is removed or turns into a seed, all torrents with +greater queue positions have their positions decreased to fill in the +space in the sequence.

    +

    queue_position() returns the torrent's position in the download +queue. The torrents with the smallest numbers are the ones that are +being downloaded. The smaller number, the closer the torrent is to the +front of the line to be started.

    +

    The queue position is also available in the torrent_status.

    +

    The queue_position_*() functions adjust the torrents position in +the queue. Up means closer to the front and down means closer to the +back of the queue. Top and bottom refers to the front and the back of +the queue respectively.

    +[report issue]
    +
    +

    queue_position_set()

    +
    +void queue_position_set (queue_position_t p) const;
    +
    +

    updates the position in the queue for this torrent. The relative order +of all other torrents remain intact but their numerical queue position +shifts to make space for this torrent's new position

    + +[report issue]
    +
    +

    set_ssl_certificate() set_ssl_certificate_buffer()

    +
    +void set_ssl_certificate (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params
    +      , std::string const& passphrase = "");
    +void set_ssl_certificate_buffer (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params);
    +
    +

    For SSL torrents, use this to specify a path to a .pem file to use as +this client's certificate. The certificate must be signed by the +certificate in the .torrent file to be valid.

    +

    The set_ssl_certificate_buffer() overload takes the actual certificate, +private key and DH params as strings, rather than paths to files.

    +

    cert is a path to the (signed) certificate in .pem format +corresponding to this torrent.

    +

    private_key is a path to the private key for the specified +certificate. This must be in .pem format.

    +

    dh_params is a path to the Diffie-Hellman parameter file, which +needs to be in .pem format. You can generate this file using the +openssl command like this: openssl dhparam -outform PEM -out +dhparams.pem 512.

    +

    passphrase may be specified if the private key is encrypted and +requires a passphrase to be decrypted.

    +

    Note that when a torrent first starts up, and it needs a certificate, +it will suspend connecting to any peers until it has one. It's +typically desirable to resume the torrent after setting the SSL +certificate.

    +

    If you receive a torrent_need_cert_alert, you need to call this to +provide a valid cert. If you don't have a cert you won't be allowed to +connect to any peers.

    +[report issue]
    +
    +

    get_storage_impl()

    +
    +storage_interface* get_storage_impl () const;
    +
    +

    Returns the storage implementation for this torrent. This depends on the +storage constructor function that was passed to add_torrent.

    +[report issue]
    +
    +

    torrent_file()

    +
    +std::shared_ptr<const torrent_info> torrent_file () const;
    +
    +

    Returns a pointer to the torrent_info object associated with this +torrent. The torrent_info object may be a copy of the internal object. +If the torrent doesn't have metadata, the pointer will not be +initialized (i.e. a nullptr). The torrent may be in a state +without metadata only if it was started without a .torrent file, e.g. +by being added by magnet link

    +[report issue]
    +
    +

    piece_availability()

    +
    +void piece_availability (std::vector<int>& avail) const;
    +
    +

    Fills the specified std::vector<int> with the availability for +each piece in this torrent. libtorrent does not keep track of +availability for seeds, so if the torrent is seeding the availability +for all pieces is reported as 0.

    +

    The piece availability is the number of peers that we are connected +that has advertised having a particular piece. This is the information +that libtorrent uses in order to prefer picking rare pieces.

    + + +[report issue]
    +
    +

    prioritize_pieces() get_piece_priorities() piece_priority()

    +
    +download_priority_t piece_priority (piece_index_t index) const;
    +void prioritize_pieces (std::vector<std::pair<piece_index_t, download_priority_t>> const& pieces) const;
    +std::vector<download_priority_t> get_piece_priorities () const;
    +void piece_priority (piece_index_t index, download_priority_t priority) const;
    +void prioritize_pieces (std::vector<download_priority_t> const& pieces) const;
    +
    +

    These functions are used to set and get the priority of individual +pieces. By default all pieces have priority 4. That means that the +random rarest first algorithm is effectively active for all pieces. +You may however change the priority of individual pieces. There are 8 +priority levels. 0 means not to download the piece at all. Otherwise, +lower priority values means less likely to be picked. Piece priority +takes precedence over piece availability. Every piece with priority 7 +will be attempted to be picked before a priority 6 piece and so on.

    +

    The default priority of pieces is 4.

    +

    Piece priorities can not be changed for torrents that have not +downloaded the metadata yet. Magnet links won't have metadata +immediately. see the metadata_received_alert.

    +

    piece_priority sets or gets the priority for an individual piece, +specified by index.

    +

    prioritize_pieces takes a vector of integers, one integer per +piece in the torrent. All the piece priorities will be updated with +the priorities in the vector. +The second overload of prioritize_pieces that takes a vector of pairs +will update the priorities of only select pieces, and leave all other +unaffected. Each pair is (piece, priority). That is, the first item is +the piece index and the second item is the priority of that piece. +Invalid entries, where the piece index or priority is out of range, are +not allowed.

    +

    get_piece_priorities returns a vector with one element for each piece +in the torrent. Each element is the current priority of that piece.

    +

    It's possible to cancel the effect of file priorities by setting the +priorities for the affected pieces. Care has to be taken when mixing +usage of file- and piece priorities.

    + + +[report issue]
    +
    +

    file_priority() prioritize_files() get_file_priorities()

    +
    +std::vector<download_priority_t> get_file_priorities () const;
    +void file_priority (file_index_t index, download_priority_t priority) const;
    +download_priority_t file_priority (file_index_t index) const;
    +void prioritize_files (std::vector<download_priority_t> const& files) const;
    +
    +

    index must be in the range [0, number_of_files).

    +

    file_priority() queries or sets the priority of file index.

    +

    prioritize_files() takes a vector that has at as many elements as +there are files in the torrent. Each entry is the priority of that +file. The function sets the priorities of all the pieces in the +torrent based on the vector.

    +

    get_file_priorities() returns a vector with the priorities of all +files.

    +

    The priority values are the same as for piece_priority(). See +download_priority_t.

    +

    Whenever a file priority is changed, all other piece priorities are +reset to match the file priorities. In order to maintain special +priorities for particular pieces, piece_priority() has to be called +again for those pieces.

    +

    You cannot set the file priorities on a torrent that does not yet have +metadata or a torrent that is a seed. file_priority(int, int) and +prioritize_files() are both no-ops for such torrents.

    +

    Since changing file priorities may involve disk operations (of moving +files in- and out of the part file), the internal accounting of file +priorities happen asynchronously. i.e. setting file priorities and then +immediately querying them may not yield the same priorities just set. +However, the piece priorities are updated immediately.

    +

    when combining file- and piece priorities, the resume file will record +both. When loading the resume data, the file priorities will be applied +first, then the piece priorities.

    + +[report issue]
    +
    +

    force_reannounce() force_dht_announce()

    +
    +void force_dht_announce () const;
    +void force_reannounce (int seconds = 0, int tracker_index = -1, reannounce_flags_t = {}) const;
    +
    +

    force_reannounce() will force this torrent to do another tracker +request, to receive new peers. The seconds argument specifies how +many seconds from now to issue the tracker announces.

    +

    If the tracker's min_interval has not passed since the last +announce, the forced announce will be scheduled to happen immediately +as the min_interval expires. This is to honor trackers minimum +re-announce interval settings.

    +

    The tracker_index argument specifies which tracker to re-announce. +If set to -1 (which is the default), all trackers are re-announce.

    +

    The flags argument can be used to affect the re-announce. See +ignore_min_interval.

    +

    force_dht_announce will announce the torrent to the DHT +immediately.

    +[report issue]
    +
    +

    scrape_tracker()

    +
    +void scrape_tracker (int idx = -1) const;
    +
    +

    scrape_tracker() will send a scrape request to a tracker. By +default (idx = -1) it will scrape the last working tracker. If +idx is >= 0, the tracker with the specified index will scraped.

    +

    A scrape request queries the tracker for statistics such as total +number of incomplete peers, complete peers, number of downloads etc.

    +

    This request will specifically update the num_complete and +num_incomplete fields in the torrent_status struct once it +completes. When it completes, it will generate a scrape_reply_alert. +If it fails, it will generate a scrape_failed_alert.

    + + + +[report issue]
    +
    +

    set_download_limit() upload_limit() download_limit() set_upload_limit()

    +
    +void set_download_limit (int limit) const;
    +int download_limit () const;
    +void set_upload_limit (int limit) const;
    +int upload_limit () const;
    +
    +

    set_upload_limit will limit the upload bandwidth used by this +particular torrent to the limit you set. It is given as the number of +bytes per second the torrent is allowed to upload. +set_download_limit works the same way but for download bandwidth +instead of upload bandwidth. Note that setting a higher limit on a +torrent then the global limit +(settings_pack::upload_rate_limit) will not override the global +rate limit. The torrent can never upload more than the global rate +limit.

    +

    upload_limit and download_limit will return the current limit +setting, for upload and download, respectively.

    +

    Local peers are not rate limited by default. see peer classes.

    +[report issue]
    +
    +

    connect_peer()

    +
    +void connect_peer (tcp::endpoint const& adr, peer_source_flags_t source = {}
    +      , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const;
    +
    +

    connect_peer() is a way to manually connect to peers that one +believe is a part of the torrent. If the peer does not respond, or is +not a member of this torrent, it will simply be disconnected. No harm +can be done by using this other than an unnecessary connection attempt +is made. If the torrent is uninitialized or in queued or checking +mode, this will throw system_error. The second (optional) +argument will be bitwise ORed into the source mask of this peer. +Typically this is one of the source flags in peer_info. i.e. +tracker, pex, dht etc.

    +

    For possible values of flags, see pex_flags_t.

    +[report issue]
    +
    +

    clear_peers()

    +
    +void clear_peers ();
    +
    +

    This will disconnect all peers and clear the peer list for this +torrent. New peers will have to be acquired before resuming, from +trackers, DHT or local service discovery, for example.

    + +[report issue]
    +
    +

    set_max_uploads() max_uploads()

    +
    +void set_max_uploads (int max_uploads) const;
    +int max_uploads () const;
    +
    +

    set_max_uploads() sets the maximum number of peers that's unchoked +at the same time on this torrent. If you set this to -1, there will be +no limit. This defaults to infinite. The primary setting controlling +this is the global unchoke slots limit, set by unchoke_slots_limit in +settings_pack.

    +

    max_uploads() returns the current settings.

    + +[report issue]
    +
    +

    max_connections() set_max_connections()

    +
    +void set_max_connections (int max_connections) const;
    +int max_connections () const;
    +
    +

    set_max_connections() sets the maximum number of connection this +torrent will open. If all connections are used up, incoming +connections may be refused or poor connections may be closed. This +must be at least 2. The default is unlimited number of connections. If +-1 is given to the function, it means unlimited. There is also a +global limit of the number of connections, set by +connections_limit in settings_pack.

    +

    max_connections() returns the current settings.

    +[report issue]
    +
    +

    move_storage()

    +
    +void move_storage (std::string const& save_path
    +      , move_flags_t flags = move_flags_t::always_replace_files
    +      ) const;
    +
    +

    Moves the file(s) that this torrent are currently seeding from or +downloading to. If the given save_path is not located on the same +drive as the original save path, the files will be copied to the new +drive and removed from their original location. This will block all +other disk IO, and other torrents download and upload rates may drop +while copying the file.

    +

    Since disk IO is performed in a separate thread, this operation is +also asynchronous. Once the operation completes, the +storage_moved_alert is generated, with the new path as the +message. If the move fails for some reason, +storage_moved_failed_alert is generated instead, containing the +error message.

    +

    The flags argument determines the behavior of the copying/moving +of the files in the torrent. see move_flags_t.

    +

    always_replace_files is the default and replaces any file that +exist in both the source directory and the target directory.

    +

    fail_if_exist first check to see that none of the copy operations +would cause an overwrite. If it would, it will fail. Otherwise it will +proceed as if it was in always_replace_files mode. Note that there +is an inherent race condition here. If the files in the target +directory appear after the check but before the copy or move +completes, they will be overwritten. When failing because of files +already existing in the target path, the error of +move_storage_failed_alert is set to +boost::system::errc::file_exists.

    +

    The intention is that a client may use this as a probe, and if it +fails, ask the user which mode to use. The client may then re-issue +the move_storage call with one of the other modes.

    +

    dont_replace always keeps the existing file in the target +directory, if there is one. The source files will still be removed in +that case. Note that it won't automatically re-check files. If an +incomplete torrent is moved into a directory with the complete files, +pause, move, force-recheck and resume. Without the re-checking, the +torrent will keep downloading and files in the new download directory +will be overwritten.

    +

    Files that have been renamed to have absolute paths are not moved by +this function. Keep in mind that files that don't belong to the +torrent but are stored in the torrent's directory may be moved as +well. This goes for files that have been renamed to absolute paths +that still end up inside the save path.

    +[report issue]
    +
    +

    rename_file()

    +
    +void rename_file (file_index_t index, std::string const& new_name) const;
    +
    +

    Renames the file with the given index asynchronously. The rename +operation is complete when either a file_renamed_alert or +file_rename_failed_alert is posted.

    +[report issue]
    +
    +

    info_hash()

    +
    +sha1_hash info_hash () const;
    +
    +

    info_hash() returns the info-hash of the torrent. If this handle +is to a torrent that hasn't loaded yet (for instance by being added) +by a URL, the returned value is undefined.

    + + +[report issue]
    +
    +

    operator==() operator!=() operator<()

    +
    +bool operator== (const torrent_handle& h) const;
    +bool operator!= (const torrent_handle& h) const;
    +bool operator< (const torrent_handle& h) const;
    +
    +

    comparison operators. The order of the torrents is unspecified +but stable.

    +[report issue]
    +
    +

    id()

    +
    +std::uint32_t id () const;
    +
    +

    returns a unique identifier for this torrent. It's not a dense index. +It's not preserved across sessions.

    +[report issue]
    +
    +

    native_handle()

    +
    +std::shared_ptr<torrent> native_handle () const;
    +
    +

    This function is intended only for use by plugins and the alert +dispatch function. This type does not have a stable ABI and should +be relied on as little as possible. Accessing the handle returned by +this function is not thread safe outside of libtorrent's internal +thread (which is used to invoke plugin callbacks). +The torrent class is not only eligible for changing ABI across +minor versions of libtorrent, its layout is also dependent on build +configuration. This adds additional requirements on a client to be +built with the exact same build configuration as libtorrent itself. +i.e. the TORRENT_ macros must match between libtorrent and the +client builds.

    +[report issue]
    +
    +

    enum file_progress_flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    piece_granularity1only calculate file progress at piece granularity. This makes +the file_progress() call cheaper and also only takes bytes that +have passed the hash check into account, so progress cannot +regress in this mode.
    +[report issue]
    +
    overwrite_existing
    +
    instruct libtorrent to overwrite any data that may already have been +downloaded with the data of the new piece being added.
    +
    +[report issue]
    +
    query_distributed_copies
    +
    calculates distributed_copies, distributed_full_copies and +distributed_fraction.
    +
    +[report issue]
    +
    query_accurate_download_counters
    +
    includes partial downloaded blocks in total_done and +total_wanted_done.
    +
    +[report issue]
    +
    query_last_seen_complete
    +
    includes last_seen_complete.
    +
    +[report issue]
    +
    query_pieces
    +
    populate the pieces field in torrent_status.
    +
    +[report issue]
    +
    query_verified_pieces
    +
    includes verified_pieces (only applies to torrents in seed +mode).
    +
    +[report issue]
    +
    query_torrent_file
    +
    includes torrent_file, which is all the static information from +the .torrent file.
    +
    +[report issue]
    +
    query_name
    +
    includes name, the name of the torrent. This is either derived +from the .torrent file, or from the &dn= magnet link argument +or possibly some other source. If the name of the torrent is not +known, this is an empty string.
    +
    +[report issue]
    +
    query_save_path
    +
    includes save_path, the path to the directory the files of the +torrent are saved to.
    +
    +[report issue]
    +
    alert_when_available
    +
    used to ask libtorrent to send an alert once the piece has been +downloaded, by passing alert_when_available. When set, the +read_piece_alert alert will be delivered, with the piece data, when +it's downloaded.
    +
    + +[report issue]
    +
    graceful_pause clear_disk_cache
    +
    will delay the disconnect of peers that we're still downloading +outstanding requests from. The torrent will not accept any more +requests and will disconnect all idle peers. As soon as a peer is done +transferring the blocks that were requested from it, it is +disconnected. This is a graceful shut down of the torrent in the sense +that no downloaded bytes are wasted.
    +
    +[report issue]
    +
    flush_disk_cache
    +
    the disk cache will be flushed before creating the resume data. +This avoids a problem with file timestamps in the resume data in +case the cache hasn't been flushed yet.
    +
    +[report issue]
    +
    save_info_dict
    +
    the resume data will contain the metadata from the torrent file as +well. This is default for any torrent that's added without a +torrent file (such as a magnet link or a URL).
    +
    +[report issue]
    +
    only_if_modified
    +
    if nothing significant has changed in the torrent since the last +time resume data was saved, fail this attempt. Significant changes +primarily include more data having been downloaded, file or piece +priorities having changed etc. If the resume data doesn't need +saving, a save_resume_data_failed_alert is posted with the error +resume_data_not_modified.
    +
    +[report issue]
    +
    ignore_min_interval
    +
    by default, force-reannounce will still honor the min-interval +published by the tracker. If this flag is set, it will be ignored +and the tracker is announced immediately.
    +
    +[report issue]
    +
    +
    +

    cache_status

    +

    Declared in "libtorrent/disk_io_thread.hpp"

    +

    this struct holds a number of statistics counters +relevant for the disk io thread and disk cache.

    +
    +struct cache_status
    +{
    +   cache_status ();
    +
    +   std::vector<cached_piece_info> pieces;
    +};
    +
    +[report issue]
    +

    cache_status()

    +
    +cache_status ();
    +
    +

    initializes all counters to 0

    +[report issue]
    +
    +
    +

    web_seed_entry

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    the web_seed_entry holds information about a web seed (also known +as URL seed or HTTP seed). It is essentially a URL with some state +associated with it. For more information, see BEP 17 and BEP 19.

    +
    +struct web_seed_entry
    +{
    +   bool operator== (web_seed_entry const& e) const;
    +   bool operator< (web_seed_entry const& e) const;
    +
    +   enum type_t
    +   {
    +      url_seed,
    +      http_seed,
    +   };
    +
    +   std::string url;
    +   std::string auth;
    +   headers_t extra_headers;
    +   std::uint8_t type;
    +};
    +
    +[report issue]
    +

    operator==()

    +
    +bool operator== (web_seed_entry const& e) const;
    +
    +

    URL and type comparison

    +[report issue]
    +
    +

    operator<()

    +
    +bool operator< (web_seed_entry const& e) const;
    +
    +

    URL and type less-than comparison

    +[report issue]
    +
    +

    enum type_t

    +

    Declared in "libtorrent/torrent_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    url_seed0 
    http_seed1 
    +[report issue]
    +
    url
    +
    The URL of the web seed
    +
    +[report issue]
    +
    auth
    +
    Optional authentication. If this is set, it's passed +in as HTTP basic auth to the web seed. The format is: +username:password.
    +
    +[report issue]
    +
    extra_headers
    +
    Any extra HTTP headers that need to be passed to the web seed
    +
    +[report issue]
    +
    type
    +
    The type of web seed (see type_t)
    +
    +[report issue]
    +
    +
    +

    load_torrent_limits

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    this object holds configuration options for limits to use when loading +torrents. They are meant to prevent loading potentially malicious torrents +that cause excessive memory allocations.

    +
    +struct load_torrent_limits
    +{
    +   int max_buffer_size  = 10000000;
    +   int max_pieces  = 0x200000;
    +   int max_decode_depth  = 100;
    +   int max_decode_tokens  = 3000000;
    +};
    +
    +[report issue]
    +
    max_buffer_size
    +
    the max size of a .torrent file to load into RAM
    +
    +[report issue]
    +
    max_pieces
    +
    the max number of pieces allowed in the torrent
    +
    +[report issue]
    +
    max_decode_depth
    +
    the max recursion depth in the bdecoded structure
    +
    +[report issue]
    +
    max_decode_tokens
    +
    the max number of bdecode tokens
    +
    +[report issue]
    +
    +

    torrent_info

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    the torrent_info class holds the information found in a .torrent file.

    +
    +class torrent_info
    +{
    +   torrent_info (std::string const& filename, load_torrent_limits const& cfg);
    +   explicit torrent_info (bdecode_node const& torrent_file);
    +   torrent_info (torrent_info const& t);
    +   explicit torrent_info (span<char const> buffer, from_span_t);
    +   torrent_info (span<char const> buffer, load_torrent_limits const& cfg, from_span_t);
    +   explicit torrent_info (sha1_hash const& info_hash);
    +   torrent_info (char const* buffer, int size, error_code& ec);
    +   torrent_info (std::string const& filename, error_code& ec);
    +   torrent_info (bdecode_node const& torrent_file, error_code& ec);
    +   torrent_info (char const* buffer, int size);
    +   explicit torrent_info (std::string const& filename);
    +   torrent_info (bdecode_node const& torrent_file, load_torrent_limits const& cfg);
    +   torrent_info (span<char const> buffer, error_code& ec, from_span_t);
    +   ~torrent_info ();
    +   file_storage const& files () const;
    +   file_storage const& orig_files () const;
    +   void rename_file (file_index_t index, std::string const& new_filename);
    +   void remap_files (file_storage const& f);
    +   void add_tracker (std::string const& url, int tier
    +      , announce_entry::tracker_source source);
    +   void add_tracker (std::string const& url, int tier = 0);
    +   std::vector<announce_entry> const& trackers () const;
    +   std::vector<std::string> collections () const;
    +   std::vector<sha1_hash> similar_torrents () const;
    +   std::vector<web_seed_entry> const& web_seeds () const;
    +   void add_url_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +   void add_http_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +   void set_web_seeds (std::vector<web_seed_entry> seeds);
    +   std::int64_t total_size () const;
    +   int piece_length () const;
    +   int num_pieces () const;
    +   piece_index_t end_piece () const;
    +   piece_index_t last_piece () const;
    +   index_range<piece_index_t> piece_range () const;
    +   const sha1_hash& info_hash () const;
    +   int num_files () const;
    +   std::vector<file_slice> map_block (piece_index_t const piece
    +      , std::int64_t offset, int size) const;
    +   peer_request map_file (file_index_t const file, std::int64_t offset, int size) const;
    +   string_view ssl_cert () const;
    +   bool is_valid () const;
    +   bool priv () const;
    +   bool is_i2p () const;
    +   int piece_size (piece_index_t index) const;
    +   char const* hash_for_piece_ptr (piece_index_t const index) const;
    +   sha1_hash hash_for_piece (piece_index_t index) const;
    +   bool is_loaded () const;
    +   std::vector<sha1_hash> const& merkle_tree () const;
    +   void set_merkle_tree (std::vector<sha1_hash>& h);
    +   const std::string& name () const;
    +   std::time_t creation_date () const;
    +   const std::string& creator () const;
    +   const std::string& comment () const;
    +   std::vector<std::pair<std::string, int>> const& nodes () const;
    +   void add_node (std::pair<std::string, int> const& node);
    +   bool parse_info_section (bdecode_node const& e, error_code& ec);
    +   bool parse_info_section (bdecode_node const& e, error_code& ec, int piece_limit);
    +   bdecode_node info (char const* key) const;
    +   void swap (torrent_info& ti);
    +   boost::shared_array<char> metadata () const;
    +   int metadata_size () const;
    +   bool is_merkle_torrent () const;
    +};
    +
    +[report issue]
    +

    torrent_info()

    +
    +torrent_info (std::string const& filename, load_torrent_limits const& cfg);
    +explicit torrent_info (bdecode_node const& torrent_file);
    +torrent_info (torrent_info const& t);
    +explicit torrent_info (span<char const> buffer, from_span_t);
    +torrent_info (span<char const> buffer, load_torrent_limits const& cfg, from_span_t);
    +explicit torrent_info (sha1_hash const& info_hash);
    +torrent_info (char const* buffer, int size, error_code& ec);
    +torrent_info (std::string const& filename, error_code& ec);
    +torrent_info (bdecode_node const& torrent_file, error_code& ec);
    +torrent_info (char const* buffer, int size);
    +explicit torrent_info (std::string const& filename);
    +torrent_info (bdecode_node const& torrent_file, load_torrent_limits const& cfg);
    +torrent_info (span<char const> buffer, error_code& ec, from_span_t);
    +
    +

    The constructor that takes an info-hash will initialize the info-hash +to the given value, but leave all other fields empty. This is used +internally when downloading torrents without the metadata. The +metadata will be created by libtorrent as soon as it has been +downloaded from the swarm.

    +

    The constructor that takes a bdecode_node will create a torrent_info +object from the information found in the given torrent_file. The +bdecode_node represents a tree node in an bencoded file. To load an +ordinary .torrent file into a bdecode_node, use bdecode().

    +

    The version that takes a buffer pointer and a size will decode it as a +.torrent file and initialize the torrent_info object for you.

    +

    The version that takes a filename will simply load the torrent file +and decode it inside the constructor, for convenience. This might not +be the most suitable for applications that want to be able to report +detailed errors on what might go wrong.

    +

    There is an upper limit on the size of the torrent file that will be +loaded by the overload taking a filename. If it's important that even +very large torrent files are loaded, use one of the other overloads.

    +

    The overloads that takes an error_code const& never throws if an +error occur, they will simply set the error code to describe what went +wrong and not fully initialize the torrent_info object. The overloads +that do not take the extra error_code parameter will always throw if +an error occurs. These overloads are not available when building +without exception support.

    +

    The overload that takes a span also needs an extra parameter of +type from_span_t to disambiguate the std::string overload for +string literals. There is an object in the libtorrent namespace of this +type called from_span.

    +[report issue]
    +
    +

    ~torrent_info()

    +
    +~torrent_info ();
    +
    +

    frees all storage associated with this torrent_info object

    + +[report issue]
    +
    +

    files() orig_files()

    +
    +file_storage const& files () const;
    +file_storage const& orig_files () const;
    +
    +

    The file_storage object contains the information on how to map the +pieces to files. It is separated from the torrent_info object because +when creating torrents a storage object needs to be created without +having a torrent file. When renaming files in a storage, the storage +needs to make its own copy of the file_storage in order to make its +mapping differ from the one in the torrent file.

    +

    orig_files() returns the original (unmodified) file storage for +this torrent. This is used by the web server connection, which needs +to request files with the original names. Filename may be changed using +torrent_info::rename_file().

    +

    For more information on the file_storage object, see the separate +document on how to create torrents.

    +[report issue]
    +
    +

    rename_file()

    +
    +void rename_file (file_index_t index, std::string const& new_filename);
    +
    +

    Renames the file with the specified index to the new name. The new +filename is reflected by the file_storage returned by files() +but not by the one returned by orig_files().

    +

    If you want to rename the base name of the torrent (for a multi file +torrent), you can copy the file_storage (see files() and +orig_files() ), change the name, and then use remap_files().

    +

    The new_filename can both be a relative path, in which case the +file name is relative to the save_path of the torrent. If the +new_filename is an absolute path (i.e. is_complete(new_filename) +== true), then the file is detached from the save_path of the +torrent. In this case the file is not moved when move_storage() is +invoked.

    +[report issue]
    +
    +

    remap_files()

    +
    +void remap_files (file_storage const& f);
    +
    +

    Remaps the file storage to a new file layout. This can be used to, for +instance, download all data in a torrent to a single file, or to a +number of fixed size sector aligned files, regardless of the number +and sizes of the files in the torrent.

    +

    The new specified file_storage must have the exact same size as +the current one.

    + +[report issue]
    +
    +

    add_tracker() trackers()

    +
    +void add_tracker (std::string const& url, int tier
    +      , announce_entry::tracker_source source);
    +void add_tracker (std::string const& url, int tier = 0);
    +std::vector<announce_entry> const& trackers () const;
    +
    +

    add_tracker() adds a tracker to the announce-list. The tier +determines the order in which the trackers are to be tried. +The trackers() function will return a sorted vector of +announce_entry. Each announce entry contains a string, which is +the tracker url, and a tier index. The tier index is the high-level +priority. No matter which trackers that works or not, the ones with +lower tier will always be tried before the one with higher tier +number. For more information, see announce_entry.

    + +[report issue]
    +
    +

    similar_torrents() collections()

    +
    +std::vector<std::string> collections () const;
    +std::vector<sha1_hash> similar_torrents () const;
    +
    +

    These two functions are related to BEP 38 (mutable torrents). The +vectors returned from these correspond to the "similar" and +"collections" keys in the .torrent file. Both info-hashes and +collections from within the info-dict and from outside of it are +included.

    + + + +[report issue]
    +
    +

    add_http_seed() web_seeds() set_web_seeds() add_url_seed()

    +
    +std::vector<web_seed_entry> const& web_seeds () const;
    +void add_url_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +void add_http_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +void set_web_seeds (std::vector<web_seed_entry> seeds);
    +
    +

    web_seeds() returns all url seeds and http seeds in the torrent. +Each entry is a web_seed_entry and may refer to either a url seed +or http seed.

    +

    add_url_seed() and add_http_seed() adds one url to the list of +url/http seeds. Currently, the only transport protocol supported for +the url is http.

    +

    set_web_seeds() replaces all web seeds with the ones specified in +the seeds vector.

    +

    The extern_auth argument can be used for other authorization +schemes than basic HTTP authorization. If set, it will override any +username and password found in the URL itself. The string will be sent +as the HTTP authorization header's value (without specifying "Basic").

    +

    The extra_headers argument defaults to an empty list, but can be +used to insert custom HTTP headers in the requests to a specific web +seed.

    +

    See http seeding for more information.

    + + +[report issue]
    +
    +

    num_pieces() total_size() piece_length()

    +
    +std::int64_t total_size () const;
    +int piece_length () const;
    +int num_pieces () const;
    +
    +

    total_size(), piece_length() and num_pieces() returns the +total number of bytes the torrent-file represents (all the files in +it), the number of byte for each piece and the total number of pieces, +respectively. The difference between piece_size() and +piece_length() is that piece_size() takes the piece index as +argument and gives you the exact size of that piece. It will always be +the same as piece_length() except in the case of the last piece, +which may be smaller.

    + + +[report issue]
    +
    +

    end_piece() last_piece() piece_range()

    +
    +piece_index_t end_piece () const;
    +piece_index_t last_piece () const;
    +index_range<piece_index_t> piece_range () const;
    +
    +

    last_piece() returns the index to the last piece in the torrent and +end_piece() returns the index to the one-past-end piece in the +torrent +piece_range() returns an implementation-defined type that can be +used as the container in a range-for loop. Where the values are the +indices of all pieces in the file_storage.

    +[report issue]
    +
    +

    info_hash()

    +
    +const sha1_hash& info_hash () const;
    +
    +

    returns the info-hash of the torrent

    +[report issue]
    +
    +

    num_files()

    +
    +int num_files () const;
    +
    +

    If you need index-access to files you can use the num_files() along +with the file_path(), file_size()-family of functions to access +files using indices.

    +[report issue]
    +
    +

    map_block()

    +
    +std::vector<file_slice> map_block (piece_index_t const piece
    +      , std::int64_t offset, int size) const;
    +
    +

    This function will map a piece index, a byte offset within that piece +and a size (in bytes) into the corresponding files with offsets where +that data for that piece is supposed to be stored. See file_slice.

    +[report issue]
    +
    +

    map_file()

    +
    +peer_request map_file (file_index_t const file, std::int64_t offset, int size) const;
    +
    +

    This function will map a range in a specific file into a range in the +torrent. The file_offset parameter is the offset in the file, +given in bytes, where 0 is the start of the file. See peer_request.

    +

    The input range is assumed to be valid within the torrent. +file_offset + size is not allowed to be greater than the file +size. file_index must refer to a valid file, i.e. it cannot be >= +num_files().

    +[report issue]
    +
    +

    ssl_cert()

    +
    +string_view ssl_cert () const;
    +
    +

    Returns the SSL root certificate for the torrent, if it is an SSL +torrent. Otherwise returns an empty string. The certificate is +the public certificate in x509 format.

    +[report issue]
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if this torrent_info object has a torrent loaded. +This is primarily used to determine if a magnet link has had its +metadata resolved yet or not.

    +[report issue]
    +
    +

    priv()

    +
    +bool priv () const;
    +
    +

    returns true if this torrent is private. i.e., the client should not +advertise itself on the trackerless network (the Kademlia DHT) for this torrent.

    +[report issue]
    +
    +

    is_i2p()

    +
    +bool is_i2p () const;
    +
    +

    returns true if this is an i2p torrent. This is determined by whether +or not it has a tracker whose URL domain name ends with ".i2p". i2p +torrents disable the DHT and local peer discovery as well as talking +to peers over anything other than the i2p network.

    +[report issue]
    +
    +

    piece_size()

    +
    +int piece_size (piece_index_t index) const;
    +
    +

    returns the piece size of file with index. This will be the same as piece_length(), +except for the last piece, which may be shorter.

    + +[report issue]
    +
    +

    hash_for_piece_ptr() hash_for_piece()

    +
    +char const* hash_for_piece_ptr (piece_index_t const index) const;
    +sha1_hash hash_for_piece (piece_index_t index) const;
    +
    +

    hash_for_piece() takes a piece-index and returns the 20-bytes +sha1-hash for that piece and info_hash() returns the 20-bytes +sha1-hash for the info-section of the torrent file. +hash_for_piece_ptr() returns a pointer to the 20 byte sha1 digest +for the piece. Note that the string is not 0-terminated.

    + +[report issue]
    +
    +

    merkle_tree() set_merkle_tree()

    +
    +std::vector<sha1_hash> const& merkle_tree () const;
    +void set_merkle_tree (std::vector<sha1_hash>& h);
    +
    +

    merkle_tree() returns a reference to the merkle tree for this +torrent, if any. +set_merkle_tree() moves the passed in merkle tree into the +torrent_info object. i.e. h will not be identical after the call. +You need to set the merkle tree for a torrent that you've just created +(as a merkle torrent). The merkle tree is retrieved from the +create_torrent::merkle_tree() function, and need to be saved +separately from the torrent file itself. Once it's added to +libtorrent, the merkle tree will be persisted in the resume data.

    +[report issue]
    +
    +

    name()

    +
    +const std::string& name () const;
    +
    +

    name() returns the name of the torrent. +name contains UTF-8 encoded string.

    +[report issue]
    +
    +

    creation_date()

    +
    +std::time_t creation_date () const;
    +
    +

    creation_date() returns the creation date of the torrent as time_t +(posix time). If there's no time stamp in the torrent file, the +optional object will be uninitialized. +.. posix time: http://www.opengroup.org/onlinepubs/009695399/functions/time.html

    +[report issue]
    +
    +

    creator()

    +
    +const std::string& creator () const;
    +
    +

    creator() returns the creator string in the torrent. If there is +no creator string it will return an empty string.

    +[report issue]
    +
    +

    comment()

    +
    +const std::string& comment () const;
    +
    +

    comment() returns the comment associated with the torrent. If +there's no comment, it will return an empty string. +comment contains UTF-8 encoded string.

    +[report issue]
    +
    +

    nodes()

    +
    +std::vector<std::pair<std::string, int>> const& nodes () const;
    +
    +

    If this torrent contains any DHT nodes, they are put in this vector in +their original form (host name and port number).

    +[report issue]
    +
    +

    add_node()

    +
    +void add_node (std::pair<std::string, int> const& node);
    +
    +

    This is used when creating torrent. Use this to add a known DHT node. +It may be used, by the client, to bootstrap into the DHT network.

    +[report issue]
    +
    +

    parse_info_section()

    +
    +bool parse_info_section (bdecode_node const& e, error_code& ec);
    +bool parse_info_section (bdecode_node const& e, error_code& ec, int piece_limit);
    +
    +

    populates the torrent_info by providing just the info-dict buffer. +This is used when loading a torrent from a magnet link for instance, +where we only have the info-dict. The bdecode_node e points to a +parsed info-dictionary. ec returns an error code if something +fails (typically if the info dictionary is malformed). +the piece_limit parameter allows limiting the amount of memory +dedicated to loading the torrent, and fails for torrents that exceed +the limit

    +[report issue]
    +
    +

    info()

    +
    +bdecode_node info (char const* key) const;
    +
    +

    This function looks up keys from the info-dictionary of the loaded +torrent file. It can be used to access extension values put in the +.torrent file. If the specified key cannot be found, it returns nullptr.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (torrent_info& ti);
    +
    +

    swap the content of this and ti.

    + +[report issue]
    +
    +

    metadata() metadata_size()

    +
    +boost::shared_array<char> metadata () const;
    +int metadata_size () const;
    +
    +

    metadata() returns a the raw info section of the torrent file. The size +of the metadata is returned by metadata_size().

    +[report issue]
    +
    +

    is_merkle_torrent()

    +
    +bool is_merkle_torrent () const;
    +
    +

    returns whether or not this is a merkle torrent. +see BEP 30.

    +[report issue]
    +
    +
    +

    add_torrent_params

    +

    Declared in "libtorrent/add_torrent_params.hpp"

    +

    The add_torrent_params is a parameter pack for adding torrents to a +session. The key fields when adding a torrent are:

    +
      +
    • ti - when you have loaded a .torrent file into a torrent_info object
    • +
    • info_hash - when you don't have the metadata (.torrent file) but. This +is set when adding a magnet link.
    • +
    +

    one of those fields must be set. Another mandatory field is +save_path. The add_torrent_params object is passed into one of the +session::add_torrent() overloads or session::async_add_torrent().

    +

    If you only specify the info-hash, the torrent file will be downloaded +from peers, which requires them to support the metadata extension. For +the metadata extension to work, libtorrent must be built with extensions +enabled (TORRENT_DISABLE_EXTENSIONS must not be defined). It also +takes an optional name argument. This may be left empty in case no +name should be assigned to the torrent. In case it's not, the name is +used for the torrent as long as it doesn't have metadata. See +torrent_handle::name.

    +

    The add_torrent_params is also used when requesting resume data for a +torrent. It can be saved to and restored from a file and added back to a +new session. For serialization and de-serialization of +add_torrent_params objects, see read_resume_data() and +write_resume_data().

    +
    +struct add_torrent_params
    +{
    +   add_torrent_params (add_torrent_params&&) noexcept;
    +   add_torrent_params& operator= (add_torrent_params&&) = default;
    +   add_torrent_params& operator= (add_torrent_params const&);
    +   explicit add_torrent_params (storage_constructor_type sc = default_storage_constructor);
    +   add_torrent_params (add_torrent_params const&);
    +
    +   int version  = LIBTORRENT_VERSION_NUM;
    +   std::shared_ptr<torrent_info> ti;
    +   aux::noexcept_movable<std::vector<std::string>> trackers;
    +   aux::noexcept_movable<std::vector<int>> tracker_tiers;
    +   aux::noexcept_movable<std::vector<std::pair<std::string, int>>> dht_nodes;
    +   std::string name;
    +   std::string save_path;
    +   storage_mode_t storage_mode  = storage_mode_sparse;
    +   aux::noexcept_movable<storage_constructor_type> storage;
    +   void* userdata  = nullptr;
    +   aux::noexcept_movable<std::vector<download_priority_t>> file_priorities;
    +   std::string trackerid;
    +   torrent_flags_t flags  = torrent_flags::default_flags;
    +   sha1_hash info_hash;
    +   int max_uploads  = -1;
    +   int max_connections  = -1;
    +   int upload_limit  = -1;
    +   int download_limit  = -1;
    +   std::int64_t total_uploaded  = 0;
    +   std::int64_t total_downloaded  = 0;
    +   int active_time  = 0;
    +   int finished_time  = 0;
    +   int seeding_time  = 0;
    +   std::time_t added_time  = 0;
    +   std::time_t completed_time  = 0;
    +   std::time_t last_seen_complete  = 0;
    +   int num_complete  = -1;
    +   int num_incomplete  = -1;
    +   int num_downloaded  = -1;
    +   aux::noexcept_movable<std::vector<std::string>> http_seeds;
    +   aux::noexcept_movable<std::vector<std::string>> url_seeds;
    +   aux::noexcept_movable<std::vector<tcp::endpoint>> peers;
    +   aux::noexcept_movable<std::vector<tcp::endpoint>> banned_peers;
    +   aux::noexcept_movable<std::map<piece_index_t, bitfield>> unfinished_pieces;
    +   typed_bitfield<piece_index_t> have_pieces;
    +   typed_bitfield<piece_index_t> verified_pieces;
    +   aux::noexcept_movable<std::vector<download_priority_t>> piece_priorities;
    +   aux::noexcept_movable<std::vector<sha1_hash>> merkle_tree;
    +   aux::noexcept_movable<std::map<file_index_t, std::string>> renamed_files;
    +   std::time_t last_download  = 0;
    +   std::time_t last_upload  = 0;
    +};
    +
    + +[report issue]
    +

    add_torrent_params() operator=()

    +
    +add_torrent_params (add_torrent_params&&) noexcept;
    +add_torrent_params& operator= (add_torrent_params&&) = default;
    +add_torrent_params& operator= (add_torrent_params const&);
    +explicit add_torrent_params (storage_constructor_type sc = default_storage_constructor);
    +add_torrent_params (add_torrent_params const&);
    +
    +

    The constructor can be used to initialize the storage constructor, +which determines the storage mechanism for the downloaded or seeding +data for the torrent. For more information, see the storage field.

    +[report issue]
    +
    version
    +
    filled in by the constructor and should be left untouched. It is used +for forward binary compatibility.
    +
    +[report issue]
    +
    ti
    +
    torrent_info object with the torrent to add. Unless the +info_hash is set, this is required to be initialized.
    +
    +[report issue]
    +
    trackers
    +
    If the torrent doesn't have a tracker, but relies on the DHT to find +peers, the trackers can specify tracker URLs for the torrent.
    +
    +[report issue]
    +
    tracker_tiers
    +
    the tiers the URLs in trackers belong to. Trackers belonging to +different tiers may be treated differently, as defined by the multi +tracker extension. This is optional, if not specified trackers are +assumed to be part of tier 0, or whichever the last tier was as +iterating over the trackers.
    +
    +[report issue]
    +
    dht_nodes
    +
    a list of hostname and port pairs, representing DHT nodes to be added +to the session (if DHT is enabled). The hostname may be an IP address.
    +
    +[report issue]
    +
    name
    +
    in case there's no other name in this torrent, this name will be used. +The name out of the torrent_info object takes precedence if available.
    +
    +[report issue]
    +
    save_path
    +

    the path where the torrent is or will be stored.

    +
    +

    Note

    +

    On windows this path (and other paths) are interpreted as UNC +paths. This means they must use backslashes as directory separators +and may not contain the special directories "." or "..".

    +
    +

    Setting this to an absolute path performs slightly better than a +relative path.

    +
    +
    +[report issue]
    +
    storage_mode
    +
    One of the values from storage_mode_t. For more information, see +storage allocation.
    +
    +[report issue]
    +
    storage
    +
    can be used to customize how the data is stored. The default storage +will simply write the data to the files it belongs to, but it could be +overridden to save everything to a single file at a specific location +or encrypt the content on disk for instance. For more information +about the storage_interface that needs to be implemented for a custom +storage, see storage_interface.
    +
    +[report issue]
    +
    userdata
    +
    The userdata parameter is optional and will be passed on to the +extension constructor functions, if any +(see torrent_handle::add_extension()).
    +
    +[report issue]
    +
    file_priorities
    +
    can be set to control the initial file priorities when adding a +torrent. The semantics are the same as for +torrent_handle::prioritize_files(). The file priorities specified +in here take precedence over those specified in the resume data, if +any.
    +
    +[report issue]
    +
    trackerid
    +
    the default tracker id to be used when announcing to trackers. By +default this is empty, and no tracker ID is used, since this is an +optional argument. If a tracker returns a tracker ID, that ID is used +instead of this.
    +
    +[report issue]
    +
    flags
    +

    flags controlling aspects of this torrent and how it's added. See +torrent_flags_t for details.

    +
    +

    Note

    +

    The flags field is initialized with default flags by the +constructor. In order to preserve default behavior when clearing or +setting other flags, make sure to bitwise OR or in a flag or bitwise +AND the inverse of a flag to clear it.

    +
    +
    +
    +[report issue]
    +
    info_hash
    +
    set this to the info hash of the torrent to add in case the info-hash +is the only known property of the torrent. i.e. you don't have a +.torrent file nor a magnet link. +To add a magnet link, use parse_magnet_uri() to populate fields in the +add_torrent_params object.
    +
    + +[report issue]
    +
    max_uploads max_connections
    +

    max_uploads, max_connections, upload_limit, +download_limit correspond to the set_max_uploads(), +set_max_connections(), set_upload_limit() and +set_download_limit() functions on torrent_handle. These values let +you initialize these settings when the torrent is added, instead of +calling these functions immediately following adding it.

    +

    -1 means unlimited on these settings just like their counterpart +functions on torrent_handle

    +

    For fine grained control over rate limits, including making them apply +to local peers, see peer classes.

    +
    +
    + +[report issue]
    +
    upload_limit download_limit
    +
    the upload and download rate limits for this torrent, specified in +bytes per second. -1 means unlimited.
    +
    + +[report issue]
    +
    total_uploaded total_downloaded
    +
    the total number of bytes uploaded and downloaded by this torrent so +far.
    +
    + + +[report issue]
    +
    active_time finished_time seeding_time
    +
    the number of seconds this torrent has spent in started, finished and +seeding state so far, respectively.
    +
    + +[report issue]
    +
    added_time completed_time
    +
    if set to a non-zero value, this is the posix time of when this torrent +was first added, including previous runs/sessions. If set to zero, the +internal added_time will be set to the time of when add_torrent() is +called.
    +
    +[report issue]
    +
    last_seen_complete
    +
    if set to non-zero, initializes the time (expressed in posix time) when +we last saw a seed or peers that together formed a complete copy of the +torrent. If left set to zero, the internal counterpart to this field +will be updated when we see a seed or a distributed copies >= 1.0.
    +
    + + +[report issue]
    +
    num_complete num_incomplete num_downloaded
    +

    these field can be used to initialize the torrent's cached scrape data. +The scrape data is high level metadata about the current state of the +swarm, as returned by the tracker (either when announcing to it or by +sending a specific scrape request). num_complete is the number of +peers in the swarm that are seeds, or have every piece in the torrent. +num_incomplete is the number of peers in the swarm that do not have +every piece. num_downloaded is the number of times the torrent has +been downloaded (not initiated, but the number of times a download has +completed).

    +

    Leaving any of these values set to -1 indicates we don't know, or we +have not received any scrape data.

    +
    +
    + +[report issue]
    +
    http_seeds url_seeds
    +

    URLs can be added to these two lists to specify additional web +seeds to be used by the torrent. If the flag_override_web_seeds +is set, these will be the _only_ ones to be used. i.e. any web seeds +found in the .torrent file will be overridden.

    +

    http_seeds expects URLs to web servers implementing the original HTTP +seed specification BEP 17.

    +

    url_seeds expects URLs to regular web servers, aka "get right" style, +specified in BEP 19.

    +
    +
    +[report issue]
    +
    peers
    +
    peers to add to the torrent, to be tried to be connected to as +bittorrent peers.
    +
    +[report issue]
    +
    banned_peers
    +
    peers banned from this torrent. The will not be connected to
    +
    +[report issue]
    +
    unfinished_pieces
    +
    this is a map of partially downloaded piece. The key is the piece index +and the value is a bitfield where each bit represents a 16 kiB block. +A set bit means we have that block.
    +
    +[report issue]
    +
    have_pieces
    +
    this is a bitfield indicating which pieces we already have of this +torrent.
    +
    +[report issue]
    +
    verified_pieces
    +
    when in seed_mode, pieces with a set bit in this bitfield have been +verified to be valid. Other pieces will be verified the first time a +peer requests it.
    +
    +[report issue]
    +
    piece_priorities
    +
    this sets the priorities for each individual piece in the torrent. Each +element in the vector represent the piece with the same index. If you +set both file- and piece priorities, file priorities will take +precedence.
    +
    +[report issue]
    +
    merkle_tree
    +
    if this is a merkle tree torrent, and you're seeding, this field must +be set. It is all the hashes in the binary tree, with the root as the +first entry. See torrent_info::set_merkle_tree() for more info.
    +
    +[report issue]
    +
    renamed_files
    +
    this is a map of file indices in the torrent and new filenames to be +applied before the torrent is added.
    +
    + +[report issue]
    +
    last_download last_upload
    +
    the posix time of the last time payload was received or sent for this +torrent, respectively.
    +
    +[report issue]
    +
    +
    +

    peer_info

    +

    Declared in "libtorrent/peer_info.hpp"

    +

    holds information and statistics about one peer +that libtorrent is connected to

    +
    +struct peer_info
    +{
    +   enum connection_type_t
    +   {
    +      standard_bittorrent,
    +      web_seed,
    +      http_seed,
    +   };
    +
    +   std::string client;
    +   typed_bitfield<piece_index_t> pieces;
    +   std::int64_t total_download;
    +   std::int64_t total_upload;
    +   time_duration last_request;
    +   time_duration last_active;
    +   time_duration download_queue_time;
    +   static constexpr peer_flags_t interesting  = 0_bit;
    +   static constexpr peer_flags_t choked  = 1_bit;
    +   static constexpr peer_flags_t remote_interested  = 2_bit;
    +   static constexpr peer_flags_t remote_choked  = 3_bit;
    +   static constexpr peer_flags_t supports_extensions  = 4_bit;
    +   static constexpr peer_flags_t local_connection  = 5_bit;
    +   static constexpr peer_flags_t handshake  = 6_bit;
    +   static constexpr peer_flags_t connecting  = 7_bit;
    +   static constexpr peer_flags_t on_parole  = 9_bit;
    +   static constexpr peer_flags_t seed  = 10_bit;
    +   static constexpr peer_flags_t optimistic_unchoke  = 11_bit;
    +   static constexpr peer_flags_t snubbed  = 12_bit;
    +   static constexpr peer_flags_t upload_only  = 13_bit;
    +   static constexpr peer_flags_t endgame_mode  = 14_bit;
    +   static constexpr peer_flags_t holepunched  = 15_bit;
    +   static constexpr peer_flags_t i2p_socket  = 16_bit;
    +   static constexpr peer_flags_t utp_socket  = 17_bit;
    +   static constexpr peer_flags_t ssl_socket  = 18_bit;
    +   static constexpr peer_flags_t rc4_encrypted  = 19_bit;
    +   static constexpr peer_flags_t plaintext_encrypted  = 20_bit;
    +   peer_flags_t flags;
    +   static constexpr peer_source_flags_t tracker  = 0_bit;
    +   static constexpr peer_source_flags_t dht  = 1_bit;
    +   static constexpr peer_source_flags_t pex  = 2_bit;
    +   static constexpr peer_source_flags_t lsd  = 3_bit;
    +   static constexpr peer_source_flags_t resume_data  = 4_bit;
    +   static constexpr peer_source_flags_t incoming  = 5_bit;
    +   peer_source_flags_t source;
    +   int up_speed;
    +   int down_speed;
    +   int payload_up_speed;
    +   int payload_down_speed;
    +   peer_id pid;
    +   int queue_bytes;
    +   int request_timeout;
    +   int send_buffer_size;
    +   int used_send_buffer;
    +   int receive_buffer_size;
    +   int used_receive_buffer;
    +   int receive_buffer_watermark;
    +   int num_hashfails;
    +   int download_queue_length;
    +   int timed_out_requests;
    +   int busy_requests;
    +   int requests_in_buffer;
    +   int target_dl_queue_length;
    +   int upload_queue_length;
    +   int failcount;
    +   piece_index_t downloading_piece_index;
    +   int downloading_block_index;
    +   int downloading_progress;
    +   int downloading_total;
    +   int connection_type;
    +   int pending_disk_bytes;
    +   int pending_disk_read_bytes;
    +   int send_quota;
    +   int receive_quota;
    +   int rtt;
    +   int num_pieces;
    +   int download_rate_peak;
    +   int upload_rate_peak;
    +   float progress;
    +   int progress_ppm;
    +   int deprecated_estimated_reciprocation_rate;
    +   tcp::endpoint ip;
    +   tcp::endpoint local_endpoint;
    +   static constexpr bandwidth_state_flags_t bw_idle  = 0_bit;
    +   static constexpr bandwidth_state_flags_t bw_limit  = 1_bit;
    +   static constexpr bandwidth_state_flags_t bw_network  = 2_bit;
    +   static constexpr bandwidth_state_flags_t bw_disk  = 4_bit;
    +   bandwidth_state_flags_t read_state;
    +   bandwidth_state_flags_t write_state;
    +};
    +
    +[report issue]
    +

    enum connection_type_t

    +

    Declared in "libtorrent/peer_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    standard_bittorrent0Regular bittorrent connection
    web_seed1HTTP connection using the BEP 19 protocol
    http_seed2HTTP connection using the BEP 17 protocol
    +[report issue]
    +
    client
    +
    a string describing the software at the other end of the connection. +In some cases this information is not available, then it will contain +a string that may give away something about which software is running +in the other end. In the case of a web seed, the server type and +version will be a part of this string.
    +
    +[report issue]
    +
    pieces
    +
    a bitfield, with one bit per piece in the torrent. Each bit tells you +if the peer has that piece (if it's set to 1) or if the peer miss that +piece (set to 0).
    +
    + +[report issue]
    +
    total_download total_upload
    +
    the total number of bytes downloaded from and uploaded to this peer. +These numbers do not include the protocol chatter, but only the +payload data.
    +
    + +[report issue]
    +
    last_request last_active
    +
    the time since we last sent a request to this peer and since any +transfer occurred with this peer
    +
    +[report issue]
    +
    download_queue_time
    +
    the time until all blocks in the request queue will be downloaded
    +
    +[report issue]
    +
    interesting
    +
    we are interested in pieces from this peer.
    +
    +[report issue]
    +
    choked
    +
    we have choked this peer.
    +
    +[report issue]
    +
    remote_interested
    +
    the peer is interested in us
    +
    +[report issue]
    +
    remote_choked
    +
    the peer has choked us.
    +
    +[report issue]
    +
    supports_extensions
    +
    means that this peer supports the +extension protocol.
    +
    +[report issue]
    +
    local_connection
    +
    The connection was initiated by us, the peer has a +listen port open, and that port is the same as in the +address of this peer. If this flag is not set, this +peer connection was opened by this peer connecting to +us.
    +
    +[report issue]
    +
    handshake
    +
    The connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified.
    +
    +[report issue]
    +
    connecting
    +
    The connection is in a half-open state (i.e. it is +being connected).
    +
    +[report issue]
    +
    on_parole
    +
    The peer has participated in a piece that failed the +hash check, and is now "on parole", which means we're +only requesting whole pieces from this peer until +it either fails that piece or proves that it doesn't +send bad data.
    +
    +[report issue]
    +
    seed
    +
    This peer is a seed (it has all the pieces).
    +
    +[report issue]
    +
    optimistic_unchoke
    +
    This peer is subject to an optimistic unchoke. It has +been unchoked for a while to see if it might unchoke +us in return an earn an upload/unchoke slot. If it +doesn't within some period of time, it will be choked +and another peer will be optimistically unchoked.
    +
    +[report issue]
    +
    snubbed
    +
    This peer has recently failed to send a block within +the request timeout from when the request was sent. +We're currently picking one block at a time from this +peer.
    +
    +[report issue]
    +
    upload_only
    +
    This peer has either explicitly (with an extension) +or implicitly (by becoming a seed) told us that it +will not downloading anything more, regardless of +which pieces we have.
    +
    +[report issue]
    +
    endgame_mode
    +
    This means the last time this peer picket a piece, +it could not pick as many as it wanted because there +were not enough free ones. i.e. all pieces this peer +has were already requested from other peers.
    +
    +[report issue]
    +
    holepunched
    +
    This flag is set if the peer was in holepunch mode +when the connection succeeded. This typically only +happens if both peers are behind a NAT and the peers +connect via the NAT holepunch mechanism.
    +
    +[report issue]
    +
    i2p_socket
    +
    indicates that this socket is running on top of the +I2P transport.
    +
    +[report issue]
    +
    utp_socket
    +
    indicates that this socket is a uTP socket
    +
    +[report issue]
    +
    ssl_socket
    +
    indicates that this socket is running on top of an SSL +(TLS) channel
    +
    +[report issue]
    +
    rc4_encrypted
    +
    this connection is obfuscated with RC4
    +
    +[report issue]
    +
    plaintext_encrypted
    +
    the handshake of this connection was obfuscated +with a Diffie-Hellman exchange
    +
    +[report issue]
    +
    flags
    +
    tells you in which state the peer is in. It is set to +any combination of the peer_flags_t flags above.
    +
    +[report issue]
    +
    tracker
    +
    The peer was received from the tracker.
    +
    +[report issue]
    +
    dht
    +
    The peer was received from the kademlia DHT.
    +
    +[report issue]
    +
    pex
    +
    The peer was received from the peer exchange +extension.
    +
    +[report issue]
    +
    lsd
    +
    The peer was received from the local service +discovery (The peer is on the local network).
    +
    +[report issue]
    +
    resume_data
    +
    The peer was added from the fast resume data.
    +
    +[report issue]
    +
    incoming
    +
    we received an incoming connection from this peer
    +
    +[report issue]
    +
    source
    +
    a combination of flags describing from which sources this peer +was received. A combination of the peer_source_flags_t above.
    +
    + +[report issue]
    +
    up_speed down_speed
    +
    the current upload and download speed we have to and from this peer +(including any protocol messages). updated about once per second
    +
    + +[report issue]
    +
    payload_up_speed payload_down_speed
    +
    The transfer rates of payload data only updated about once per second
    +
    +[report issue]
    +
    pid
    +
    the peer's id as used in the bit torrent protocol. This id can be used +to extract 'fingerprints' from the peer. Sometimes it can tell you +which client the peer is using. See identify_client()_
    +
    +[report issue]
    +
    queue_bytes
    +
    the number of bytes we have requested from this peer, but not yet +received.
    +
    +[report issue]
    +
    request_timeout
    +
    the number of seconds until the current front piece request will time +out. This timeout can be adjusted through +settings_pack::request_timeout. +-1 means that there is not outstanding request.
    +
    + +[report issue]
    +
    send_buffer_size used_send_buffer
    +
    the number of bytes allocated +and used for the peer's send buffer, respectively.
    +
    + + +[report issue]
    +
    receive_buffer_size used_receive_buffer receive_buffer_watermark
    +
    the number of bytes +allocated and used as receive buffer, respectively.
    +
    +[report issue]
    +
    num_hashfails
    +
    the number of pieces this peer has participated in sending us that +turned out to fail the hash check.
    +
    +[report issue]
    +
    download_queue_length
    +
    this is the number of requests we have sent to this peer that we +haven't got a response for yet
    +
    +[report issue]
    +
    timed_out_requests
    +
    the number of block requests that have timed out, and are still in the +download queue
    +
    +[report issue]
    +
    busy_requests
    +
    the number of busy requests in the download queue. A busy request is a +request for a block we've also requested from a different peer
    +
    +[report issue]
    +
    requests_in_buffer
    +
    the number of requests messages that are currently in the send buffer +waiting to be sent.
    +
    +[report issue]
    +
    target_dl_queue_length
    +
    the number of requests that is tried to be maintained (this is +typically a function of download speed)
    +
    +[report issue]
    +
    upload_queue_length
    +
    the number of piece-requests we have received from this peer +that we haven't answered with a piece yet.
    +
    +[report issue]
    +
    failcount
    +
    the number of times this peer has "failed". i.e. failed to connect or +disconnected us. The failcount is decremented when we see this peer in +a tracker response or peer exchange message.
    +
    + + + +[report issue]
    +
    downloading_piece_index downloading_block_index downloading_progress downloading_total
    +
    You can know which piece, and which part of that piece, that is +currently being downloaded from a specific peer by looking at these +four members. downloading_piece_index is the index of the piece +that is currently being downloaded. This may be set to -1 if there's +currently no piece downloading from this peer. If it is >= 0, the +other three members are valid. downloading_block_index is the +index of the block (or sub-piece) that is being downloaded. +downloading_progress is the number of bytes of this block we have +received from the peer, and downloading_total is the total number +of bytes in this block.
    +
    +[report issue]
    +
    connection_type
    +
    the kind of connection this peer uses. See connection_type_t.
    +
    +[report issue]
    +
    pending_disk_bytes
    +
    the number of bytes this peer has pending in the disk-io thread. +Downloaded and waiting to be written to disk. This is what is capped +by settings_pack::max_queued_disk_bytes.
    +
    +[report issue]
    +
    pending_disk_read_bytes
    +
    number of outstanding bytes to read +from disk
    +
    + +[report issue]
    +
    send_quota receive_quota
    +
    the number of bytes this peer has been assigned to be allowed to send +and receive until it has to request more quota from the bandwidth +manager.
    +
    +[report issue]
    +
    rtt
    +
    an estimated round trip time to this peer, in milliseconds. It is +estimated by timing the TCP connect(). It may be 0 for +incoming connections.
    +
    +[report issue]
    +
    num_pieces
    +
    the number of pieces this peer has.
    +
    + +[report issue]
    +
    download_rate_peak upload_rate_peak
    +
    the highest download and upload rates seen on this connection. They +are given in bytes per second. This number is reset to 0 on reconnect.
    +
    +[report issue]
    +
    progress
    +
    the progress of the peer in the range [0, 1]. This is always 0 when +floating point operations are disabled, instead use progress_ppm.
    +
    +[report issue]
    +
    progress_ppm
    +
    indicates the download progress of the peer in the range [0, 1000000] +(parts per million).
    +
    +[report issue]
    +
    ip
    +
    the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio documentation.
    +
    +[report issue]
    +
    local_endpoint
    +
    the IP and port pair the socket is bound to locally. i.e. the IP +address of the interface it's going out over. This may be useful for +multi-homed clients with multiple interfaces to the internet.
    +
    +[report issue]
    +
    bw_idle
    +
    The peer is not waiting for any external events to +send or receive data.
    +
    +[report issue]
    +
    bw_limit
    +
    The peer is waiting for the rate limiter.
    +
    +[report issue]
    +
    bw_network
    +
    The peer has quota and is currently waiting for a +network read or write operation to complete. This is +the state all peers are in if there are no bandwidth +limits.
    +
    +[report issue]
    +
    bw_disk
    +
    The peer is waiting for the disk I/O thread to catch +up writing buffers to disk before downloading more.
    +
    + +[report issue]
    +
    read_state write_state
    +
    bitmasks indicating what state this peer +is in with regards to sending and receiving data. The states are +defined as independent flags of type bandwidth_state_flags_t, in this +class.
    +
    +[report issue]
    +
    +
    +

    peer_request

    +

    Declared in "libtorrent/peer_request.hpp"

    +

    represents a byte range within a piece. Internally this is +is used for incoming piece requests.

    +
    +struct peer_request
    +{
    +   bool operator== (peer_request const& r) const;
    +
    +   piece_index_t piece;
    +   int start;
    +   int length;
    +};
    +
    +[report issue]
    +

    operator==()

    +
    +bool operator== (peer_request const& r) const;
    +
    +

    returns true if the right hand side peer_request refers to the same +range as this does.

    +[report issue]
    +
    piece
    +
    the index of the piece in which the range starts.
    +
    +[report issue]
    +
    start
    +
    the offset within that piece where the range starts.
    +
    +[report issue]
    +
    length
    +
    the size of the range, in bytes.
    +
    + +[report issue]
    +
    +
    +

    write_resume_data() write_resume_data_buf()

    +

    Declared in "libtorrent/write_resume_data.hpp"

    +
    +std::vector<char> write_resume_data_buf (add_torrent_params const& atp);
    +entry write_resume_data (add_torrent_params const& atp);
    +
    +

    this function turns the resume data in an add_torrent_params object +into a bencoded structure

    +[report issue]
    +
    +

    make_magnet_uri()

    +

    Declared in "libtorrent/magnet_uri.hpp"

    +
    +std::string make_magnet_uri (torrent_info const& info);
    +std::string make_magnet_uri (torrent_handle const& handle);
    +
    +

    Generates a magnet URI from the specified torrent. If the torrent +handle is invalid, an empty string is returned.

    +

    For more information about magnet links, see magnet links.

    +[report issue]
    +
    +

    parse_magnet_uri()

    +

    Declared in "libtorrent/magnet_uri.hpp"

    +
    +void parse_magnet_uri (string_view uri, add_torrent_params& p, error_code& ec);
    +add_torrent_params parse_magnet_uri (string_view uri);
    +add_torrent_params parse_magnet_uri (string_view uri, error_code& ec);
    +
    +

    This function parses out information from the magnet link and populates the +add_torrent_params object. The overload that does not take an +error_code reference will throw a system_error on error +The overload taking an add_torrent_params reference will fill in the +fields specified in the magnet URI.

    +[report issue]
    +
    +

    version()

    +

    Declared in "libtorrent/version.hpp"

    +
    +char const* version ();
    +
    +

    returns the libtorrent version as string form in this format: +"<major>.<minor>.<tiny>.<tag>"

    +[report issue]
    +
    +

    generate_fingerprint()

    +

    Declared in "libtorrent/fingerprint.hpp"

    +
    +std::string generate_fingerprint (std::string name
    +   , int major, int minor = 0, int revision = 0, int tag = 0);
    +
    +

    This is a utility function to produce a client ID fingerprint formatted to +the most common convention. The fingerprint can be set via the +peer_fingerprint setting, in settings_pack.

    +

    The name string should contain exactly two characters. These are the +characters unique to your client, used to identify it. Make sure not to +clash with anybody else. Here are some taken id's:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id charsclient
    LTlibtorrent (default)
    UTuTorrent
    UMuTorrent Mac
    qBqBittorrent
    BPBitTorrent Pro
    BTBitTorrent
    DEDeluge
    AZAzureus
    TLTribler
    +

    There's an informal directory of client id's here.

    +

    The major, minor, revision and tag parameters are used to +identify the version of your client.

    +[report issue]
    +
    +

    hash_value()

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +
    +std::size_t hash_value (torrent_handle const& h);
    +
    +

    for std::hash (and to support using this type in unordered_map etc.)

    +[report issue]
    +
    +

    is_utp_stream_logging()

    +

    Declared in "libtorrent/utp_stream.hpp"

    +
    +bool is_utp_stream_logging ();
    +
    +[report issue]
    +
    +

    set_utp_stream_logging()

    +

    Declared in "libtorrent/utp_stream.hpp"

    +
    +void set_utp_stream_logging (bool enable);
    +
    +

    This function should be used at the very beginning and very end of your program.

    +[report issue]
    +
    +

    read_resume_data()

    +

    Declared in "libtorrent/read_resume_data.hpp"

    +
    +add_torrent_params read_resume_data (span<char const> buffer);
    +add_torrent_params read_resume_data (span<char const> buffer
    +   , error_code& ec);
    +add_torrent_params read_resume_data (bdecode_node const& rd
    +   , error_code& ec);
    +add_torrent_params read_resume_data (bdecode_node const& rd);
    +
    +

    these functions are used to parse resume data and populate the appropriate +fields in an add_torrent_params object. This object can then be used to add +the actual torrent_info object to and pass to session::add_torrent() or +session::async_add_torrent().

    +

    If the client wants to override any field that was loaded from the resume +data, e.g. save_path, those fields must be changed after loading resume +data but before adding the torrent.

    +[report issue]
    +
    +

    enum connection_type

    +

    Declared in "libtorrent/peer_connection.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    bittorrent0 
    url_seed1 
    http_seed2 
    +[report issue]
    +
    +

    enum portmap_transport

    +

    Declared in "libtorrent/portmap.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    natpmp0natpmp can be NAT-PMP or PCP
    upnp1 
    +[report issue]
    +
    +

    enum portmap_protocol

    +

    Declared in "libtorrent/portmap.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0 
    tcp1 
    udp2 
    +[report issue]
    +
    +

    torrent_flags_t

    +

    Declared in "libtorrent/torrent_flags.hpp"

    +
    +
    seed_mode
    +

    If seed_mode is set, libtorrent will assume that all files +are present for this torrent and that they all match the hashes in +the torrent file. Each time a peer requests to download a block, +the piece is verified against the hash, unless it has been verified +already. If a hash fails, the torrent will automatically leave the +seed mode and recheck all the files. The use case for this mode is +if a torrent is created and seeded, or if the user already know +that the files are complete, this is a way to avoid the initial +file checks, and significantly reduce the startup time.

    +

    Setting seed_mode on a torrent without metadata (a +.torrent file) is a no-op and will be ignored.

    +

    It is not possible to set the seed_mode flag on a torrent after it has +been added to a session. It is possible to clear it though.

    +
    +
    +
    +
    upload_mode
    +

    If upload_mode is set, the torrent will be initialized in +upload-mode, which means it will not make any piece requests. This +state is typically entered on disk I/O errors, and if the torrent +is also auto managed, it will be taken out of this state +periodically (see settings_pack::optimistic_disk_retry).

    +

    This mode can be used to avoid race conditions when +adjusting priorities of pieces before allowing the torrent to start +downloading.

    +

    If the torrent is auto-managed (auto_managed), the torrent +will eventually be taken out of upload-mode, regardless of how it +got there. If it's important to manually control when the torrent +leaves upload mode, don't make it auto managed.

    +
    +
    +
    +
    share_mode
    +

    determines if the torrent should be added in share mode or not. +Share mode indicates that we are not interested in downloading the +torrent, but merely want to improve our share ratio (i.e. increase +it). A torrent started in share mode will do its best to never +download more than it uploads to the swarm. If the swarm does not +have enough demand for upload capacity, the torrent will not +download anything. This mode is intended to be safe to add any +number of torrents to, without manual screening, without the risk +of downloading more than is uploaded.

    +

    A torrent in share mode sets the priority to all pieces to 0, +except for the pieces that are downloaded, when pieces are decided +to be downloaded. This affects the progress bar, which might be set +to "100% finished" most of the time. Do not change file or piece +priorities for torrents in share mode, it will make it not work.

    +

    The share mode has one setting, the share ratio target, see +settings_pack::share_mode_target for more info.

    +
    +
    +
    +
    apply_ip_filter
    +
    determines if the IP filter should apply to this torrent or not. By +default all torrents are subject to filtering by the IP filter +(i.e. this flag is set by default). This is useful if certain +torrents needs to be exempt for some reason, being an auto-update +torrent for instance.
    +
    +
    +
    paused
    +
    specifies whether or not the torrent is paused. i.e. it won't connect to the tracker or any of the peers +until it's resumed. Note that a paused torrent that also has the +auto_managed flag set can be started at any time by libtorrent's queuing +logic. See queuing.
    +
    +
    +
    auto_managed
    +

    If the torrent is auto-managed (auto_managed), the torrent +may be resumed at any point, regardless of how it paused. If it's +important to manually control when the torrent is paused and +resumed, don't make it auto managed.

    +

    If auto_managed is set, the torrent will be queued, +started and seeded automatically by libtorrent. When this is set, +the torrent should also be started as paused. The default queue +order is the order the torrents were added. They are all downloaded +in that order. For more details, see queuing.

    +
    +
    +
    +
    duplicate_is_error
    +
    used in add_torrent_params to indicate that it's an error to attempt +to add a torrent that's already in the session. If it's not considered an +error, a handle to the existing torrent is returned. +This flag is not saved by write_resume_data(), since it is only meant for +adding torrents.
    +
    +
    +
    update_subscribe
    +
    on by default and means that this torrent will be part of state +updates when calling post_torrent_updates(). +This flag is not saved by write_resume_data().
    +
    +
    +
    super_seeding
    +
    sets the torrent into super seeding/initial seeding mode. If the torrent +is not a seed, this flag has no effect.
    +
    +
    +
    sequential_download
    +
    sets the sequential download state for the torrent. In this mode the +piece picker will pick pieces with low index numbers before pieces with +high indices. The actual pieces that are picked depend on other factors +still, such as which pieces a peer has and whether it is in parole mode +or "prefer whole pieces"-mode. Sequential mode is not ideal for streaming +media. For that, see set_piece_deadline() instead.
    +
    +
    +
    stop_when_ready
    +

    When this flag is set, the torrent will force stop whenever it +transitions from a non-data-transferring state into a data-transferring +state (referred to as being ready to download or seed). This is useful +for torrents that should not start downloading or seeding yet, but want +to be made ready to do so. A torrent may need to have its files checked +for instance, so it needs to be started and possibly queued for checking +(auto-managed and started) but as soon as it's done, it should be +stopped.

    +

    Force stopped means auto-managed is set to false and it's paused. As +if the auto_manages flag is cleared and the paused flag is set on the torrent.

    +

    Note that the torrent may transition into a downloading state while +setting this flag, and since the logic is edge triggered you may +miss the edge. To avoid this race, if the torrent already is in a +downloading state when this call is made, it will trigger the +stop-when-ready immediately.

    +

    When the stop-when-ready logic fires, the flag is cleared. Any +subsequent transitions between downloading and non-downloading states +will not be affected, until this flag is set again.

    +

    The behavior is more robust when setting this flag as part of adding +the torrent. See add_torrent_params.

    +

    The stop-when-ready flag fixes the inherent race condition of waiting +for the state_changed_alert and then call pause(). The download/seeding +will most likely start in between posting the alert and receiving the +call to pause.

    +

    A downloading state is one where peers are being connected. Which means +just downloading the metadata via the ut_metadata extension counts +as a downloading state. In order to stop a torrent once the metadata +has been downloaded, instead set all file priorities to dont_download

    +
    +
    +
    +
    override_trackers
    +
    when this flag is set, the tracker list in the add_torrent_params +object override any trackers from the torrent file. If the flag is +not set, the trackers from the add_torrent_params object will be +added to the list of trackers used by the torrent. +This flag is set by read_resume_data() if there are trackers present in +the resume data file. This effectively makes the trackers saved in the +resume data take precedence over the original trackers. This includes if +there's an empty list of trackers, to support the case where they were +explicitly removed in the previous session. +This flag is not saved by write_resume_data()
    +
    +
    +
    override_web_seeds
    +
    If this flag is set, the web seeds from the add_torrent_params +object will override any web seeds in the torrent file. If it's not +set, web seeds in the add_torrent_params object will be added to the +list of web seeds used by the torrent. +This flag is set by read_resume_data() if there are web seeds present in +the resume data file. This effectively makes the web seeds saved in the +resume data take precedence over the original ones. This includes if +there's an empty list of web seeds, to support the case where they were +explicitly removed in the previous session. +This flag is not saved by write_resume_data()
    +
    +
    +
    need_save_resume
    +
    if this flag is set (which it is by default) the torrent will be +considered needing to save its resume data immediately as it's +added. New torrents that don't have any resume data should do that. +This flag is cleared by a successful call to save_resume_data() +This flag is not saved by write_resume_data(), since it represents an +ephemeral state of a running torrent.
    +
    +
    +
    disable_dht
    +
    set this flag to disable DHT for this torrent. This lets you have the DHT +enabled for the whole client, and still have specific torrents not +participating in it. i.e. not announcing to the DHT nor picking up peers +from it.
    +
    +
    +
    disable_lsd
    +
    set this flag to disable local service discovery for this torrent.
    +
    +
    +
    disable_pex
    +
    set this flag to disable peer exchange for this torrent.
    +
    +
    +
    all
    +
    all torrent flags combined. Can conveniently be used when creating masks +for flags
    +
    +[report issue]
    +
    +

    download_priority_t

    +

    Declared in "libtorrent/download_priority.hpp"

    +
    +
    dont_download
    +
    Don't download the file or piece. Partial pieces may still be downloaded when +setting file priorities.
    +
    +
    +
    default_priority
    +
    The default priority for files and pieces.
    +
    +
    +
    low_priority
    +
    The lowest priority for files and pieces.
    +
    +
    +
    top_priority
    +
    The highest priority for files and pieces.
    +
    +[report issue]
    +
    +

    pex_flags_t

    +

    Declared in "libtorrent/pex_flags.hpp"

    +
    +
    pex_encryption
    +
    the peer supports protocol encryption
    +
    +
    +
    pex_seed
    +
    the peer is a seed
    +
    +
    +
    pex_utp
    +
    the peer supports the uTP, transport protocol over UDP.
    +
    +
    +
    pex_holepunch
    +
    the peer supports the holepunch extension If this flag is received from a +peer, it can be used as a rendezvous point in case direct connections to +the peer fail
    +
    +[report issue]
    +
    +

    file_open_mode_t

    +

    Declared in "libtorrent/disk_interface.hpp"

    +
    +
    read_only
    +
    open the file for reading only
    +
    +
    +
    write_only
    +
    open the file for writing only
    +
    +
    +
    read_write
    +
    open the file for reading and writing
    +
    +
    +
    rw_mask
    +
    the mask for the bits determining read or write mode
    +
    +
    +
    sparse
    +
    open the file in sparse mode (if supported by the +filesystem).
    +
    +
    +
    no_atime
    +
    don't update the access timestamps on the file (if +supported by the operating system and filesystem). +this generally improves disk performance.
    +
    +
    +
    random_access
    +
    open the file for random access. This disables read-ahead +logic
    +
    +[report issue]
    +
    +

    open_mode_t

    +

    Declared in "libtorrent/file.hpp"

    +
    +
    read_only
    +
    open the file for reading only
    +
    +
    +
    write_only
    +
    open the file for writing only
    +
    +
    +
    read_write
    +
    open the file for reading and writing
    +
    +
    +
    rw_mask
    +
    the mask for the bits making up the read-write mode.
    +
    +
    +
    sparse
    +
    open the file in sparse mode (if supported by the +filesystem).
    +
    +
    +
    no_atime
    +
    don't update the access timestamps on the file (if +supported by the operating system and filesystem). +this generally improves disk performance.
    +
    +
    +
    random_access
    +
    open the file for random access. This disables read-ahead +logic
    +
    +
    +
    no_cache
    +
    don't put any pressure on the OS disk cache +because of access to this file. We expect our +files to be fairly large, and there is already +a cache at the bittorrent block level. This +may improve overall system performance by +leaving running applications in the page cache
    +
    +
    +
    coalesce_buffers
    +
    this is only used for readv/writev flags
    +
    +
    +
    attribute_hidden
    +
    when creating a file, set the hidden attribute (windows only)
    +
    +
    +
    attribute_executable
    +
    when creating a file, set the executable attribute
    +
    +
    +
    attribute_mask
    +
    the mask of all attribute bits
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Create_Torrents.html b/docs/reference-Create_Torrents.html new file mode 100644 index 0000000..45c94d8 --- /dev/null +++ b/docs/reference-Create_Torrents.html @@ -0,0 +1,453 @@ + + + + + + +reference-Create_Torrents.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Create Torrents

    + +

    This section describes the functions and classes that are used +to create torrent files. It is a layered API with low level classes +and higher level convenience functions. A torrent is created in 4 +steps:

    +
      +
    1. first the files that will be part of the torrent are determined.
    2. +
    3. the torrent properties are set, such as tracker url, web seeds, +DHT nodes etc.
    4. +
    5. Read through all the files in the torrent, SHA-1 all the data +and set the piece hashes.
    6. +
    7. The torrent is bencoded into a file or buffer.
    8. +
    +

    If there are a lot of files and or deep directory hierarchies to +traverse, step one can be time consuming.

    +

    Typically step 3 is by far the most time consuming step, since it +requires to read all the bytes from all the files in the torrent.

    +

    All of these classes and functions are declared by including +libtorrent/create_torrent.hpp.

    +

    example:

    +
    +file_storage fs;
    +
    +// recursively adds files in directories
    +add_files(fs, "./my_torrent");
    +
    +create_torrent t(fs);
    +t.add_tracker("http://my.tracker.com/announce");
    +t.set_creator("libtorrent example");
    +
    +// reads the files and calculates the hashes
    +set_piece_hashes(t, ".");
    +
    +ofstream out("my_torrent.torrent", std::ios_base::binary);
    +bencode(std::ostream_iterator<char>(out), t.generate());
    +
    +[report issue]
    +

    create_torrent

    +

    Declared in "libtorrent/create_torrent.hpp"

    +

    This class holds state for creating a torrent. After having added +all information to it, call create_torrent::generate() to generate +the torrent. The entry that's returned can then be bencoded into a +.torrent file using bencode().

    +
    +struct create_torrent
    +{
    +   explicit create_torrent (torrent_info const& ti);
    +   explicit create_torrent (file_storage& fs, int piece_size = 0
    +      , int pad_file_limit = -1, create_flags_t flags = optimize_alignment
    +      , int alignment = -1);
    +   entry generate () const;
    +   file_storage const& files () const;
    +   void set_comment (char const* str);
    +   void set_creator (char const* str);
    +   void set_hash (piece_index_t index, sha1_hash const& h);
    +   void set_file_hash (file_index_t index, sha1_hash const& h);
    +   void add_http_seed (string_view url);
    +   void add_url_seed (string_view url);
    +   void add_node (std::pair<std::string, int> node);
    +   void add_tracker (string_view url, int tier = 0);
    +   void set_root_cert (string_view pem);
    +   bool priv () const;
    +   void set_priv (bool p);
    +   int num_pieces () const;
    +   int piece_length () const;
    +   int piece_size (piece_index_t i) const;
    +   std::vector<sha1_hash> const& merkle_tree () const;
    +   void add_similar_torrent (sha1_hash ih);
    +   void add_collection (string_view c);
    +
    +   static constexpr create_flags_t optimize_alignment  = 0_bit;
    +   static constexpr create_flags_t merkle  = 1_bit;
    +   static constexpr create_flags_t modification_time  = 2_bit;
    +   static constexpr create_flags_t symlinks  = 3_bit;
    +   static constexpr create_flags_t mutable_torrent_support  = 4_bit;
    +};
    +
    +[report issue]
    +

    create_torrent()

    +
    +explicit create_torrent (torrent_info const& ti);
    +explicit create_torrent (file_storage& fs, int piece_size = 0
    +      , int pad_file_limit = -1, create_flags_t flags = optimize_alignment
    +      , int alignment = -1);
    +
    +

    The piece_size is the size of each piece in bytes. It must +be a multiple of 16 kiB. If a piece size of 0 is specified, a +piece_size will be calculated such that the torrent file is roughly 40 kB.

    +

    If a pad_file_limit is specified (other than -1), any file larger than +the specified number of bytes will be preceded by a pad file to align it +with the start of a piece. The pad_file_limit is ignored unless the +optimize_alignment flag is passed. Typically it doesn't make sense +to set this any lower than 4 kiB.

    +

    The overload that takes a torrent_info object will make a verbatim +copy of its info dictionary (to preserve the info-hash). The copy of +the info dictionary will be used by create_torrent::generate(). This means +that none of the member functions of create_torrent that affects +the content of the info dictionary (such as set_hash()), will +have any affect.

    +

    The flags arguments specifies options for the torrent creation. It can +be any combination of the flags defined by create_flags_t.

    +

    alignment is used when pad files are enabled. This is the size +eligible files are aligned to. The default is -1, which means the +piece size of the torrent.

    +[report issue]
    +
    +

    generate()

    +
    +entry generate () const;
    +
    +

    This function will generate the .torrent file as a bencode tree. In order to +generate the flat file, use the bencode() function.

    +

    It may be useful to add custom entries to the torrent file before bencoding it +and saving it to disk.

    +

    If anything goes wrong during torrent generation, this function will return +an empty entry structure. You can test for this condition by querying the +type of the entry:

    +
    +file_storage fs;
    +// add file ...
    +create_torrent t(fs);
    +// add trackers and piece hashes ...
    +e = t.generate();
    +
    +if (e.type() == entry::undefined_t)
    +{
    +        // something went wrong
    +}
    +
    +

    For instance, you cannot generate a torrent with 0 files in it. If you don't add +any files to the file_storage, torrent generation will fail.

    +[report issue]
    +
    +

    files()

    +
    +file_storage const& files () const;
    +
    +

    returns an immutable reference to the file_storage used to create +the torrent from.

    +[report issue]
    +
    +

    set_comment()

    +
    +void set_comment (char const* str);
    +
    +

    Sets the comment for the torrent. The string str should be utf-8 encoded. +The comment in a torrent file is optional.

    +[report issue]
    +
    +

    set_creator()

    +
    +void set_creator (char const* str);
    +
    +

    Sets the creator of the torrent. The string str should be utf-8 encoded. +This is optional.

    +[report issue]
    +
    +

    set_hash()

    +
    +void set_hash (piece_index_t index, sha1_hash const& h);
    +
    +

    This sets the SHA-1 hash for the specified piece (index). You are required +to set the hash for every piece in the torrent before generating it. If you have +the files on disk, you can use the high level convenience function to do this. +See set_piece_hashes().

    +[report issue]
    +
    +

    set_file_hash()

    +
    +void set_file_hash (file_index_t index, sha1_hash const& h);
    +
    +

    This sets the sha1 hash for this file. This hash will end up under the key sha1 +associated with this file (for multi-file torrents) or in the root info dictionary +for single-file torrents.

    + +[report issue]
    +
    +

    add_http_seed() add_url_seed()

    +
    +void add_http_seed (string_view url);
    +void add_url_seed (string_view url);
    +
    +

    This adds a url seed to the torrent. You can have any number of url seeds. For a +single file torrent, this should be an HTTP url, pointing to a file with identical +content as the file of the torrent. For a multi-file torrent, it should point to +a directory containing a directory with the same name as this torrent, and all the +files of the torrent in it.

    +

    The second function, add_http_seed() adds an HTTP seed instead.

    +[report issue]
    +
    +

    add_node()

    +
    +void add_node (std::pair<std::string, int> node);
    +
    +

    This adds a DHT node to the torrent. This especially useful if you're creating a +tracker less torrent. It can be used by clients to bootstrap their DHT node from. +The node is a hostname and a port number where there is a DHT node running. +You can have any number of DHT nodes in a torrent.

    +[report issue]
    +
    +

    add_tracker()

    +
    +void add_tracker (string_view url, int tier = 0);
    +
    +

    Adds a tracker to the torrent. This is not strictly required, but most torrents +use a tracker as their main source of peers. The url should be an http:// or udp:// +url to a machine running a bittorrent tracker that accepts announces for this torrent's +info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are +tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those +fail, trackers with tier 2 are tried, and so on.

    +[report issue]
    +
    +

    set_root_cert()

    +
    +void set_root_cert (string_view pem);
    +
    +

    This function sets an X.509 certificate in PEM format to the torrent. This makes the +torrent an SSL torrent. An SSL torrent requires that each peer has a valid certificate +signed by this root certificate. For SSL torrents, all peers are connecting over SSL +connections. For more information, see the section on ssl torrents.

    +

    The string is not the path to the cert, it's the actual content of the +certificate.

    + +[report issue]
    +
    +

    priv() set_priv()

    +
    +bool priv () const;
    +void set_priv (bool p);
    +
    +

    Sets and queries the private flag of the torrent. +Torrents with the private flag set ask the client to not use any other +sources than the tracker for peers, and to not use DHT to advertise itself publicly, +only the tracker.

    +[report issue]
    +
    +

    num_pieces()

    +
    +int num_pieces () const;
    +
    +

    returns the number of pieces in the associated file_storage object.

    + +[report issue]
    +
    +

    piece_size() piece_length()

    +
    +int piece_length () const;
    +int piece_size (piece_index_t i) const;
    +
    +

    piece_length() returns the piece size of all pieces but the +last one. piece_size() returns the size of the specified piece. +these functions are just forwarding to the associated file_storage.

    +[report issue]
    +
    +

    merkle_tree()

    +
    +std::vector<sha1_hash> const& merkle_tree () const;
    +
    +

    This function returns the merkle hash tree, if the torrent was created as a merkle +torrent. The tree is created by generate() and won't be valid until that function +has been called. When creating a merkle tree torrent, the actual tree itself has to +be saved off separately and fed into libtorrent the first time you start seeding it, +through the torrent_info::set_merkle_tree() function. From that point onwards, the +tree will be saved in the resume data.

    + +[report issue]
    +
    +

    add_collection() add_similar_torrent()

    +
    +void add_similar_torrent (sha1_hash ih);
    +void add_collection (string_view c);
    +
    +

    Add similar torrents (by info-hash) or collections of similar torrents. +Similar torrents are expected to share some files with this torrent. +Torrents sharing a collection name with this torrent are also expected +to share files with this torrent. A torrent may have more than one +collection and more than one similar torrents. For more information, +see BEP 38.

    +[report issue]
    +
    optimize_alignment
    +
    This will insert pad files to align the files to piece boundaries, for +optimized disk-I/O. This will minimize the number of bytes of pad- +files, to keep the impact down for clients that don't support +them.
    +
    +[report issue]
    +
    merkle
    +
    This will create a merkle hash tree torrent. A merkle torrent cannot +be opened in clients that don't specifically support merkle torrents. +The benefit is that the resulting torrent file will be much smaller and +not grow with more pieces. When this option is specified, it is +recommended to have a fairly small piece size, say 64 kiB. +When creating merkle torrents, the full hash tree is also generated +and should be saved off separately. It is accessed through the +create_torrent::merkle_tree() function.
    +
    +[report issue]
    +
    modification_time
    +
    This will include the file modification time as part of the torrent. +This is not enabled by default, as it might cause problems when you +create a torrent from separate files with the same content, hoping to +yield the same info-hash. If the files have different modification times, +with this option enabled, you would get different info-hashes for the +files.
    +
    +[report issue]
    +
    symlinks
    +
    If this flag is set, files that are symlinks get a symlink attribute +set on them and their data will not be included in the torrent. This +is useful if you need to reconstruct a file hierarchy which contains +symlinks.
    +
    +[report issue]
    +
    mutable_torrent_support
    +
    to create a torrent that can be updated via a mutable torrent +(see BEP 38). This also needs to be enabled for torrents that update +another torrent.
    +
    +[report issue]
    +
    +
    +

    add_files()

    +

    Declared in "libtorrent/create_torrent.hpp"

    +
    +void add_files (file_storage& fs, std::string const& file
    +   , create_flags_t flags = {});
    +void add_files (file_storage& fs, std::string const& file
    +   , std::function<bool(std::string)> p, create_flags_t flags = {});
    +
    +

    Adds the file specified by path to the file_storage object. In case path +refers to a directory, files will be added recursively from the directory.

    +

    If specified, the predicate p is called once for every file and directory that +is encountered. Files for which p returns true are added, and directories for +which p returns true are traversed. p must have the following signature:

    +
    +bool Pred(std::string const& p);
    +
    +

    The path that is passed in to the predicate is the full path of the file or +directory. If no predicate is specified, all files are added, and all directories +are traversed.

    +

    The ".." directory is never traversed.

    +

    The flags argument should be the same as the flags passed to the create_torrent +constructor.

    +[report issue]
    +
    +

    set_piece_hashes()

    +

    Declared in "libtorrent/create_torrent.hpp"

    +
    +inline void set_piece_hashes (create_torrent& t, std::string const& p);
    +void set_piece_hashes (create_torrent& t, std::string const& p
    +   , std::function<void(piece_index_t)> const& f, error_code& ec);
    +inline void set_piece_hashes (create_torrent& t, std::string const& p
    +   , std::function<void(piece_index_t)> const& f);
    +inline void set_piece_hashes (create_torrent& t, std::string const& p, error_code& ec);
    +
    +

    This function will assume that the files added to the torrent file exists at path +p, read those files and hash the content and set the hashes in the create_torrent +object. The optional function f is called in between every hash that is set. f +must have the following signature:

    +
    +void Fun(piece_index_t);
    +
    +

    The overloads that don't take an error_code& may throw an exception in case of a +file error, the other overloads sets the error code to reflect the error, if any.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Custom_Storage.html b/docs/reference-Custom_Storage.html new file mode 100644 index 0000000..55f36b5 --- /dev/null +++ b/docs/reference-Custom_Storage.html @@ -0,0 +1,535 @@ + + + + + + +reference-Custom_Storage.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Custom Storage

    +
    +

    Table of contents

    + +
    +

    libtorrent provides a customization point for storage of data. By default, +(default_storage) downloaded files are saved to disk according with the +general conventions of bittorrent clients, mimicking the original file layout +when the torrent was created. The libtorrent user may define a custom +storage to store piece data in a different way.

    +

    A custom storage implementation must derive from and implement the +storage_interface. You must also provide a function that constructs the +custom storage object and provide this function to the add_torrent() call +via add_torrent_params. Either passed in to the constructor or by setting +the add_torrent_params::storage field.

    +

    This is an example storage implementation that stores all pieces in a +std::map, i.e. in RAM. It's not necessarily very useful in practice, but +illustrates the basics of implementing a custom storage.

    +
    +struct temp_storage : lt::storage_interface
    +{
    +  explicit temp_storage(lt::file_storage const& fs) : lt::storage_interface(fs) {}
    +  void initialize(lt::storage_error&) override {}
    +  bool has_any_file(lt::storage_error&) override { return false; }
    +  void set_file_priority(lt::aux::vector<lt::download_priority_t, lt::file_index_t>&
    +    , lt::storage_error&) override {}
    +  int readv(lt::span<lt::iovec_t const> bufs, lt::piece_index_t piece
    +    , int offset, lt::open_mode_t, lt::storage_error&) override
    +  {
    +    auto const i = m_file_data.find(piece);
    +    if (i == m_file_data.end()) return 0;
    +    if (int(i->second.size()) <= offset) return 0;
    +    lt::iovec_t data{ i->second.data() + offset, int(i->second.size() - offset) };
    +    int ret = 0;
    +    for (lt::iovec_t const& b : bufs) {
    +      int const to_copy = std::min(int(b.size()), int(data.size()));
    +      memcpy(b.data(), data.data(), to_copy);
    +      data = data.subspan(to_copy);
    +      ret += to_copy;
    +      if (data.empty()) break;
    +    }
    +    return ret;
    +  }
    +  int writev(lt::span<lt::iovec_t const> bufs
    +    , lt::piece_index_t const piece, int offset, lt::open_mode_t, lt::storage_error&) override
    +  {
    +    auto& data = m_file_data[piece];
    +    int ret = 0;
    +    for (auto& b : bufs) {
    +      if (int(data.size()) < offset + b.size()) data.resize(offset + b.size());
    +      std::memcpy(data.data() + offset, b.data(), b.size());
    +      offset += int(b.size());
    +      ret += int(b.size());
    +    }
    +    return ret;
    +  }
    +  void rename_file(lt::file_index_t, std::string const&, lt::storage_error&) override
    +  { assert(false); }
    +  lt::status_t move_storage(std::string const&
    +    , lt::move_flags_t, lt::storage_error&) override { return lt::status_t::no_error; }
    +  bool verify_resume_data(lt::add_torrent_params const&
    +    , lt::aux::vector<std::string, lt::file_index_t> const&
    +    , lt::storage_error&) override
    +  { return false; }
    +  void release_files(lt::storage_error&) override {}
    +  void delete_files(lt::remove_flags_t, lt::storage_error&) override {}
    +
    +  std::map<lt::piece_index_t, std::vector<char>> m_file_data;
    +};
    +
    +lt::storage_interface* temp_storage_constructor(lt::storage_params const& params, lt::file_pool&)
    +{
    +  return new temp_storage(params.files);
    +}
    +
    +[report issue]
    +

    storage_interface

    +

    Declared in "libtorrent/storage.hpp"

    +

    The storage interface is a pure virtual class that can be implemented to +customize how and where data for a torrent is stored. The default storage +implementation uses regular files in the filesystem, mapping the files in +the torrent in the way one would assume a torrent is saved to disk. +Implementing your own storage interface makes it possible to store all +data in RAM, or in some optimized order on disk (the order the pieces are +received for instance), or saving multi file torrents in a single file in +order to be able to take advantage of optimized disk-I/O.

    +

    It is also possible to write a thin class that uses the default storage +but modifies some particular behavior, for instance encrypting the data +before it's written to disk, and decrypting it when it's read again.

    +

    The storage interface is based on pieces. Every read and write operation +happens in the piece-space. Each piece fits piece_size number +of bytes. All access is done by writing and reading whole or partial +pieces.

    +

    libtorrent comes with two built-in storage implementations; +default_storage and disabled_storage. Their constructor functions +are called default_storage_constructor() and +disabled_storage_constructor respectively. The disabled storage does +just what it sounds like. It throws away data that's written, and it +reads garbage. It's useful mostly for benchmarking and profiling purpose.

    +
    +struct storage_interface: std::enable_shared_from_this<storage_interface>, aux::disk_job_fence, aux::storage_piece_set
    +{
    +   explicit storage_interface (file_storage const& fs);
    +   storage_interface (storage_interface const&) = delete;
    +   storage_interface& operator= (storage_interface const&) = delete;
    +   virtual void initialize (storage_error& ec) = 0;
    +   virtual int writev (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +   virtual int readv (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +   virtual bool has_any_file (storage_error& ec) = 0;
    +   virtual void set_file_priority (aux::vector<download_priority_t, file_index_t>& prio
    +      , storage_error& ec) = 0;
    +   virtual status_t move_storage (std::string const& save_path
    +      , move_flags_t flags, storage_error& ec) = 0;
    +   virtual bool verify_resume_data (add_torrent_params const& rd
    +      , aux::vector<std::string, file_index_t> const& links
    +      , storage_error& ec) = 0;
    +   virtual void release_files (storage_error& ec) = 0;
    +   virtual void rename_file (file_index_t index, std::string const& new_filename
    +      , storage_error& ec) = 0;
    +   virtual void delete_files (remove_flags_t options, storage_error& ec) = 0;
    +   virtual bool tick ();
    +   file_storage const& files () const;
    +   bool set_need_tick ();
    +   void do_tick ();
    +   void set_owner (std::shared_ptr<void> const& tor);
    +   aux::session_settings const& settings () const;
    +   void set_storage_index (storage_index_t st);
    +   storage_index_t storage_index () const;
    +   void inc_refcount ();
    +   int dec_refcount ();
    +
    +   aux::session_settings const* m_settings  = nullptr;
    +};
    +
    +[report issue]
    +

    initialize()

    +
    +virtual void initialize (storage_error& ec) = 0;
    +
    +

    This function is called when the storage on disk is to be +initialized. The default storage will create directories and empty +files at this point. If allocate_files is true, it will also +ftruncate all files to their target size.

    +

    This function may be called multiple time on a single instance. When a +torrent is force-rechecked, the storage is re-initialized to trigger +the re-check from scratch.

    +

    The function is not necessarily called before other member functions. +For instance has_any_files() and verify_resume_data() are +called early to determine whether we may have to check all files or +not. If we're doing a full check of the files every piece will be +hashed, causing readv() to be called as well.

    +

    Any required internals that need initialization should be done in the +constructor. This function is called before the torrent starts to +download.

    +

    If an error occurs, storage_error should be set to reflect it.

    + +[report issue]
    +
    +

    readv() writev()

    +
    +virtual int writev (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +virtual int readv (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +
    +

    These functions should read and write the data in or to the given +piece at the given offset. It should read or write +num_bufs buffers sequentially, where the size of each buffer is +specified in the buffer array bufs. The iovec_t type has the +following members:

    +
    +struct iovec_t { void* iov_base; size_t iov_len; };
    +
    +

    These functions may be called simultaneously from multiple threads. +Make sure they are thread safe. The file in libtorrent is thread +safe when it can fall back to pread, preadv or the windows +equivalents. On targets where read operations cannot be thread safe +(i.e one has to seek first and then read), only one disk thread is +used.

    +

    The offset is aligned to 16 kiB boundaries most of the time, but +there are rare exceptions when it's not. Specifically if the read +cache is disabled/or full and a peer requests unaligned data. Most +clients request aligned data.

    +

    The number of bytes read or written should be returned, or -1 on +error. If there's an error, the storage_error must be filled out +to represent the error that occurred.

    +

    For possible values of flags, see open_mode_t.

    +[report issue]
    +
    +

    has_any_file()

    +
    +virtual bool has_any_file (storage_error& ec) = 0;
    +
    +

    This function is called when first checking (or re-checking) the +storage for a torrent. It should return true if any of the files that +is used in this storage exists on disk. If so, the storage will be +checked for existing pieces before starting the download.

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    set_file_priority()

    +
    +virtual void set_file_priority (aux::vector<download_priority_t, file_index_t>& prio
    +      , storage_error& ec) = 0;
    +
    +

    change the priorities of files. This is a fenced job and is +guaranteed to be the only running function on this storage +when called

    +[report issue]
    +
    +

    move_storage()

    +
    +virtual status_t move_storage (std::string const& save_path
    +      , move_flags_t flags, storage_error& ec) = 0;
    +
    +

    This function should move all the files belonging to the storage to +the new save_path. The default storage moves the single file or the +directory of the torrent.

    +

    Before moving the files, any open file handles may have to be closed, +like release_files().

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    verify_resume_data()

    +
    +virtual bool verify_resume_data (add_torrent_params const& rd
    +      , aux::vector<std::string, file_index_t> const& links
    +      , storage_error& ec) = 0;
    +
    +

    This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

    +

    The default storage may compare file sizes and time stamps of the files.

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

    +

    If the links pointer is non-empty, it has the same number +of elements as there are files. Each element is either empty or contains +the absolute path to a file identical to the corresponding file in this +torrent. The storage must create hard links (or copy) those files. If +any file does not exist or is inaccessible, the disk job must fail.

    +[report issue]
    +
    +

    release_files()

    +
    +virtual void release_files (storage_error& ec) = 0;
    +
    +

    This function should release all the file handles that it keeps open +to files belonging to this storage. The default implementation just +calls file_pool::release_files().

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    rename_file()

    +
    +virtual void rename_file (file_index_t index, std::string const& new_filename
    +      , storage_error& ec) = 0;
    +
    +

    Rename the file with index file to name new_name.

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    delete_files()

    +
    +virtual void delete_files (remove_flags_t options, storage_error& ec) = 0;
    +
    +

    This function should delete some or all of the storage for this torrent. +The options parameter specifies whether to delete all files or just +the partfile. options are set to the same value as the options +passed to session::remove_torrent().

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    The disk_buffer_pool is used to allocate and free disk buffers. It +has the following members:

    +
    +struct disk_buffer_pool
    +{
    +        char* allocate_buffer(char const* category);
    +        void free_buffer(char* buf);
    +
    +        char* allocate_buffers(int blocks, char const* category);
    +        void free_buffers(char* buf, int blocks);
    +
    +        int block_size() const { return m_block_size; }
    +
    +};
    +
    +[report issue]
    +
    +

    tick()

    +
    +virtual bool tick ();
    +
    +

    called periodically (useful for deferred flushing). When returning +false, it means no more ticks are necessary. Any disk job submitted +will re-enable ticking. The default will always turn ticking back +off again.

    +[report issue]
    +
    +

    settings()

    +
    +aux::session_settings const& settings () const;
    +
    +

    access global session_settings

    +[report issue]
    +
    m_settings
    +
    initialized in disk_io_thread::perform_async_job
    +
    +[report issue]
    +
    +
    +

    default_storage

    +

    Declared in "libtorrent/storage.hpp"

    +

    The default implementation of storage_interface. Behaves as a normal +bittorrent client. It is possible to derive from this class in order to +override some of its behavior, when implementing a custom storage.

    +
    +class default_storage : public storage_interface
    +{
    +   explicit default_storage (storage_params const& params, file_pool&);
    +   void delete_files (remove_flags_t options, storage_error& ec) override;
    +   status_t move_storage (std::string const& save_path
    +      , move_flags_t flags, storage_error& ec) override;
    +   bool tick () override;
    +   bool verify_resume_data (add_torrent_params const& rd
    +      , aux::vector<std::string, file_index_t> const& links
    +      , storage_error& error) override;
    +   void release_files (storage_error& ec) override;
    +   void initialize (storage_error& ec) override;
    +   void rename_file (file_index_t index, std::string const& new_filename
    +      , storage_error& ec) override;
    +   void set_file_priority (aux::vector<download_priority_t, file_index_t>& prio
    +      , storage_error& ec) override;
    +   bool has_any_file (storage_error& ec) override;
    +   int readv (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) override;
    +   int writev (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) override;
    +   file_storage const& files () const;
    +};
    +
    +[report issue]
    +

    default_storage()

    +
    +explicit default_storage (storage_params const& params, file_pool&);
    +
    +

    constructs the default_storage based on the give file_storage (fs). +mapped is an optional argument (it may be nullptr). If non-nullptr it +represents the file mapping that have been made to the torrent before +adding it. That's where files are supposed to be saved and looked for +on disk. save_path is the root save folder for this torrent. +file_pool is the cache of file handles that the storage will use. +All files it opens will ask the file_pool to open them. file_prio +is a vector indicating the priority of files on startup. It may be +an empty vector. Any file whose index is not represented by the vector +(because the vector is too short) are assumed to have priority 1. +this is used to treat files with priority 0 slightly differently.

    +[report issue]
    +
    +

    files()

    +
    +file_storage const& files () const;
    +
    +

    if the files in this storage are mapped, returns the mapped +file_storage, otherwise returns the original file_storage object.

    +[report issue]
    +
    +
    +

    file_pool

    +

    Declared in "libtorrent/file_pool.hpp"

    +

    this is an internal cache of open file handles. It's primarily used by +storage_interface implementations. It provides semi weak guarantees of +not opening more file handles than specified. Given multiple threads, +each with the ability to lock a file handle (via smart pointer), there +may be windows where more file handles are open.

    +
    +struct file_pool : boost::noncopyable
    +{
    +   ~file_pool ();
    +   explicit file_pool (int size = 40);
    +   file_handle open_file (storage_index_t st, std::string const& p
    +      , file_index_t file_index, file_storage const& fs, open_mode_t m
    +      , error_code& ec);
    +   void release ();
    +   void release (storage_index_t st);
    +   void release (storage_index_t st, file_index_t file_index);
    +   void resize (int size);
    +   int size_limit () const;
    +   void close_oldest ();
    +};
    +
    + +[report issue]
    +

    file_pool() ~file_pool()

    +
    +~file_pool ();
    +explicit file_pool (int size = 40);
    +
    +

    size specifies the number of allowed files handles +to hold open at any given time.

    +[report issue]
    +
    +

    open_file()

    +
    +file_handle open_file (storage_index_t st, std::string const& p
    +      , file_index_t file_index, file_storage const& fs, open_mode_t m
    +      , error_code& ec);
    +
    +

    return an open file handle to file at file_index in the +file_storage fs opened at save path p. m is the +file open mode (see file::open_mode_t).

    +[report issue]
    +
    +

    release()

    +
    +void release ();
    +void release (storage_index_t st);
    +void release (storage_index_t st, file_index_t file_index);
    +
    +

    release all files belonging to the specified storage_interface (st) +the overload that takes file_index releases only the file with +that index in storage st.

    +[report issue]
    +
    +

    resize()

    +
    +void resize (int size);
    +
    +

    update the allowed number of open file handles to size.

    +[report issue]
    +
    +

    size_limit()

    +
    +int size_limit () const;
    +
    +

    returns the current limit of number of allowed open file handles held +by the file_pool.

    +[report issue]
    +
    +

    close_oldest()

    +
    +void close_oldest ();
    +
    +

    close the file that was opened least recently (i.e. not accessed +least recently). The purpose is to make the OS (really just windows) +clear and flush its disk cache associated with this file. We don't want +any file to stay open for too long, allowing the disk cache to accrue.

    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-DHT.html b/docs/reference-DHT.html new file mode 100644 index 0000000..ddaec79 --- /dev/null +++ b/docs/reference-DHT.html @@ -0,0 +1,564 @@ + + + + + + +reference-DHT.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    DHT

    + +[report issue]
    +

    dht_settings

    +

    Declared in "libtorrent/kademlia/dht_settings.hpp"

    +

    structure used to hold configuration options for the DHT

    +

    The dht_settings struct used to contain a service_port member to +control which port the DHT would listen on and send messages from. This +field is deprecated and ignored. libtorrent always tries to open the UDP +socket on the same port as the TCP socket.

    +
    +struct dht_settings
    +{
    +   int max_peers_reply  = 100;
    +   int search_branching  = 5;
    +   int max_fail_count  = 20;
    +   int max_torrents  = 2000;
    +   int max_dht_items  = 700;
    +   int max_peers  = 500;
    +   int max_torrent_search_reply  = 20;
    +   bool restrict_routing_ips  = true;
    +   bool restrict_search_ips  = true;
    +   bool extended_routing_table  = true;
    +   bool aggressive_lookups  = true;
    +   bool privacy_lookups  = false;
    +   bool enforce_node_id  = false;
    +   bool ignore_dark_internet  = true;
    +   int block_timeout  = 5 * 60;
    +   int block_ratelimit  = 5;
    +   bool read_only  = false;
    +   int item_lifetime  = 0;
    +   int upload_rate_limit  = 8000;
    +   int sample_infohashes_interval  = 21600;
    +   int max_infohashes_sample_count  = 20;
    +};
    +
    +[report issue]
    +
    max_peers_reply
    +
    the maximum number of peers to send in a reply to get_peers
    +
    +[report issue]
    +
    search_branching
    +
    the number of concurrent search request the node will send when +announcing and refreshing the routing table. This parameter is called +alpha in the kademlia paper
    +
    +[report issue]
    +
    max_fail_count
    +
    the maximum number of failed tries to contact a node before it is +removed from the routing table. If there are known working nodes that +are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node +that can replace them.
    +
    +[report issue]
    +
    max_torrents
    +
    the total number of torrents to track from the DHT. This is simply an +upper limit to make sure malicious DHT nodes cannot make us allocate +an unbounded amount of memory.
    +
    +[report issue]
    +
    max_dht_items
    +
    max number of items the DHT will store
    +
    +[report issue]
    +
    max_peers
    +
    the max number of peers to store per torrent (for the DHT)
    +
    +[report issue]
    +
    max_torrent_search_reply
    +
    the max number of torrents to return in a torrent search query to the +DHT
    +
    +[report issue]
    +
    restrict_routing_ips
    +

    determines if the routing table entries should restrict entries to one +per IP. This defaults to true, which helps mitigate some attacks on +the DHT. It prevents adding multiple nodes with IPs with a very close +CIDR distance.

    +

    when set, nodes whose IP address that's in the same /24 (or /64 for +IPv6) range in the same routing table bucket. This is an attempt to +mitigate node ID spoofing attacks also restrict any IP to only have a +single entry in the whole routing table

    +
    +
    +[report issue]
    +
    restrict_search_ips
    +
    determines if DHT searches should prevent adding nodes with IPs with +very close CIDR distance. This also defaults to true and helps +mitigate certain attacks on the DHT.
    +
    +[report issue]
    +
    extended_routing_table
    +
    makes the first buckets in the DHT routing table fit 128, 64, 32 and +16 nodes respectively, as opposed to the standard size of 8. All other +buckets have size 8 still.
    +
    +[report issue]
    +
    aggressive_lookups
    +
    slightly changes the lookup behavior in terms of how many outstanding +requests we keep. Instead of having branch factor be a hard limit, we +always keep branch factor outstanding requests to the closest nodes. +i.e. every time we get results back with closer nodes, we query them +right away. It lowers the lookup times at the cost of more outstanding +queries.
    +
    +[report issue]
    +
    privacy_lookups
    +
    when set, perform lookups in a way that is slightly more expensive, +but which minimizes the amount of information leaked about you.
    +
    +[report issue]
    +
    enforce_node_id
    +
    when set, node's whose IDs that are not correctly generated based on +its external IP are ignored. When a query arrives from such node, an +error message is returned with a message saying "invalid node ID".
    +
    +[report issue]
    +
    ignore_dark_internet
    +
    ignore DHT messages from parts of the internet we wouldn't expect to +see any traffic from
    +
    +[report issue]
    +
    block_timeout
    +
    the number of seconds a DHT node is banned if it exceeds the rate +limit. The rate limit is averaged over 10 seconds to allow for bursts +above the limit.
    +
    +[report issue]
    +
    block_ratelimit
    +
    the max number of packets per second a DHT node is allowed to send +without getting banned.
    +
    +[report issue]
    +
    read_only
    +
    when set, the other nodes won't keep this node in their routing +tables, it's meant for low-power and/or ephemeral devices that +cannot support the DHT, it is also useful for mobile devices which +are sensitive to network traffic and battery life. +this node no longer responds to 'query' messages, and will place a +'ro' key (value = 1) in the top-level message dictionary of outgoing +query messages.
    +
    +[report issue]
    +
    item_lifetime
    +
    the number of seconds a immutable/mutable item will be expired. +default is 0, means never expires.
    +
    +[report issue]
    +
    upload_rate_limit
    +
    the number of bytes per second (on average) the DHT is allowed to send. +If the incoming requests causes to many bytes to be sent in responses, +incoming requests will be dropped until the quota has been replenished.
    +
    +[report issue]
    +
    sample_infohashes_interval
    +
    the info-hashes sample recomputation interval (in seconds). +The node will precompute a subset of the tracked info-hashes and return +that instead of calculating it upon each request. The permissible range +is between 0 and 21600 seconds (inclusive).
    +
    +[report issue]
    +
    max_infohashes_sample_count
    +
    the maximum number of elements in the sampled subset of info-hashes. +If this number is too big, expect the DHT storage implementations +to clamp it in order to allow UDP packets go through
    +
    +[report issue]
    +
    +

    dht_storage_counters

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +

    This structure hold the relevant counters for the storage

    +
    +struct dht_storage_counters
    +{
    +   void reset ();
    +
    +   std::int32_t torrents  = 0;
    +   std::int32_t peers  = 0;
    +   std::int32_t immutable_data  = 0;
    +   std::int32_t mutable_data  = 0;
    +};
    +
    +[report issue]
    +

    reset()

    +
    +void reset ();
    +
    +

    This member function set the counters to zero.

    +[report issue]
    +
    +
    +

    dht_storage_interface

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +

    The DHT storage interface is a pure virtual class that can +be implemented to customize how the data for the DHT is stored.

    +

    The default storage implementation uses three maps in RAM to save +the peers, mutable and immutable items and it's designed to +provide a fast and fully compliant behavior of the BEPs.

    +

    libtorrent comes with one built-in storage implementation: +dht_default_storage (private non-accessible class). Its +constructor function is called dht_default_storage_constructor(). +You should know that if this storage becomes full of DHT items, +the current implementation could degrade in performance.

    +
    +struct dht_storage_interface
    +{
    +   virtual void update_node_ids (std::vector<node_id> const& ids) = 0;
    +   virtual bool get_peers (sha1_hash const& info_hash
    +      , bool noseed, bool scrape, address const& requester
    +      , entry& peers) const = 0;
    +   virtual void announce_peer (sha1_hash const& info_hash
    +      , tcp::endpoint const& endp
    +      , string_view name, bool seed) = 0;
    +   virtual bool get_immutable_item (sha1_hash const& target
    +      , entry& item) const = 0;
    +   virtual void put_immutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , address const& addr) = 0;
    +   virtual bool get_mutable_item_seq (sha1_hash const& target
    +      , sequence_number& seq) const = 0;
    +   virtual bool get_mutable_item (sha1_hash const& target
    +      , sequence_number seq, bool force_fill
    +      , entry& item) const = 0;
    +   virtual void put_mutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , signature const& sig
    +      , sequence_number seq
    +      , public_key const& pk
    +      , span<char const> salt
    +      , address const& addr) = 0;
    +   virtual int get_infohashes_sample (entry& item) = 0;
    +   virtual void tick () = 0;
    +   virtual dht_storage_counters counters () const = 0;
    +};
    +
    +[report issue]
    +

    update_node_ids()

    +
    +virtual void update_node_ids (std::vector<node_id> const& ids) = 0;
    +
    +

    This member function notifies the list of all node's ids +of each DHT running inside libtorrent. It's advisable +that the concrete implementation keeps a copy of this list +for an eventual prioritization when deleting an element +to make room for a new one.

    +[report issue]
    +
    +

    get_peers()

    +
    +virtual bool get_peers (sha1_hash const& info_hash
    +      , bool noseed, bool scrape, address const& requester
    +      , entry& peers) const = 0;
    +
    +

    This function retrieve the peers tracked by the DHT +corresponding to the given info_hash. You can specify if +you want only seeds and/or you are scraping the data.

    +

    For future implementers: +If the torrent tracked contains a name, such a name +must be stored as a string in peers["n"]

    +

    If the scrape parameter is true, you should fill these keys:

    +
    +
    +
    peers["BFpe"]
    +
    with the standard bit representation of a +256 bloom filter containing the downloaders
    +
    peers["BFsd"]
    +
    with the standard bit representation of a +256 bloom filter containing the seeders
    +
    +
    +

    If the scrape parameter is false, you should fill the +key peers["values"] with a list containing a subset of +peers tracked by the given info_hash. Such a list should +consider the value of dht_settings::max_peers_reply. +If noseed is true only peers marked as no seed should be included.

    +

    returns true if the maximum number of peers are stored +for this info_hash.

    +[report issue]
    +
    +

    announce_peer()

    +
    +virtual void announce_peer (sha1_hash const& info_hash
    +      , tcp::endpoint const& endp
    +      , string_view name, bool seed) = 0;
    +
    +

    This function is named announce_peer for consistency with the +upper layers, but has nothing to do with networking. Its only +responsibility is store the peer in such a way that it's returned +in the entry with the lookup_peers.

    +

    The name parameter is the name of the torrent if provided in +the announce_peer DHT message. The length of this value should +have a maximum length in the final storage. The default +implementation truncate the value for a maximum of 50 characters.

    +[report issue]
    +
    +

    get_immutable_item()

    +
    +virtual bool get_immutable_item (sha1_hash const& target
    +      , entry& item) const = 0;
    +
    +

    This function retrieves the immutable item given its target hash.

    +

    For future implementers: +The value should be returned as an entry in the key item["v"].

    +

    returns true if the item is found and the data is returned +inside the (entry) out parameter item.

    +[report issue]
    +
    +

    put_immutable_item()

    +
    +virtual void put_immutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , address const& addr) = 0;
    +
    +

    Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer.

    +

    For implementers: +This data can be stored only if the target is not already +present. The implementation should consider the value of +dht_settings::max_dht_items.

    +[report issue]
    +
    +

    get_mutable_item_seq()

    +
    +virtual bool get_mutable_item_seq (sha1_hash const& target
    +      , sequence_number& seq) const = 0;
    +
    +

    This function retrieves the sequence number of a mutable item.

    +

    returns true if the item is found and the data is returned +inside the out parameter seq.

    +[report issue]
    +
    +

    get_mutable_item()

    +
    +virtual bool get_mutable_item (sha1_hash const& target
    +      , sequence_number seq, bool force_fill
    +      , entry& item) const = 0;
    +
    +

    This function retrieves the mutable stored in the DHT.

    +

    For implementers: +The item sequence should be stored in the key item["seq"]. +if force_fill is true or (0 <= seq and seq < item["seq"]) +the following keys should be filled +item["v"] - with the value no encoded. +item["sig"] - with a string representation of the signature. +item["k"] - with a string representation of the public key.

    +

    returns true if the item is found and the data is returned +inside the (entry) out parameter item.

    +[report issue]
    +
    +

    put_mutable_item()

    +
    +virtual void put_mutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , signature const& sig
    +      , sequence_number seq
    +      , public_key const& pk
    +      , span<char const> salt
    +      , address const& addr) = 0;
    +
    +

    Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer.

    +

    For implementers: +The sequence number should be checked if the item is already +present. The implementation should consider the value of +dht_settings::max_dht_items.

    +[report issue]
    +
    +

    get_infohashes_sample()

    +
    +virtual int get_infohashes_sample (entry& item) = 0;
    +
    +

    This function retrieves a sample info-hashes

    +

    For implementers: +The info-hashes should be stored in ["samples"] (N x 20 bytes). +the following keys should be filled +item["interval"] - the subset refresh interval in seconds. +item["num"] - number of info-hashes in storage.

    +

    Internally, this function is allowed to lazily evaluate, cache +and modify the actual sample to put in item

    +

    returns the number of info-hashes in the sample.

    +[report issue]
    +
    +

    tick()

    +
    +virtual void tick () = 0;
    +
    +

    This function is called periodically (non-constant frequency).

    +

    For implementers: +Use this functions for expire peers or items or any other +storage cleanup.

    +[report issue]
    +
    +

    counters()

    +
    +virtual dht_storage_counters counters () const = 0;
    +
    +

    return stats counters for the store

    +[report issue]
    +
    +
    +

    dht_state

    +

    Declared in "libtorrent/kademlia/dht_state.hpp"

    +

    This structure helps to store and load the state +of the dht_tracker. +At this moment the library is only a dual stack +implementation of the DHT. See BEP 32

    +
    +struct dht_state
    +{
    +   void clear ();
    +
    +   node_ids_t nids;
    +   std::vector<udp::endpoint> nodes;
    +   std::vector<udp::endpoint> nodes6;
    +};
    +
    +[report issue]
    +
    nodes
    +
    the bootstrap nodes saved from the buckets node
    +
    +[report issue]
    +
    nodes6
    +
    the bootstrap nodes saved from the IPv6 buckets node
    +
    +[report issue]
    +
    +

    dht_default_storage_constructor()

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +
    +std::unique_ptr<dht_storage_interface> dht_default_storage_constructor (
    +   dht_settings const& settings);
    +
    +

    constructor for the default DHT storage. The DHT storage is responsible +for maintaining peers and mutable and immutable items announced and +stored/put to the DHT node.

    +[report issue]
    +
    +

    sign_mutable_item()

    +

    Declared in "libtorrent/kademlia/item.hpp"

    +
    +signature sign_mutable_item (
    +   span<char const> v
    +   , span<char const> salt
    +   , sequence_number seq
    +   , public_key const& pk
    +   , secret_key const& sk);
    +
    +

    given a byte range v and an optional byte range salt, a +sequence number, public key pk (must be 32 bytes) and a secret key +sk (must be 64 bytes), this function produces a signature which +is written into a 64 byte buffer pointed to by sig. The caller +is responsible for allocating the destination buffer that's passed in +as the sig argument. Typically it would be allocated on the stack.

    +[report issue]
    +
    +

    announce_flags_t

    +

    Declared in "libtorrent/kademlia/announce_flags.hpp"

    +
    +
    seed
    +
    announce to DHT as a seed
    +
    +
    +
    implied_port
    +
    announce to DHT with the implied-port flag set. This tells the network to use +your source UDP port as your listen port, rather than the one specified in +the message. This may improve the chances of traversing NATs when using uTP.
    +
    +
    +
    ssl_torrent
    +
    Specify the port number for the SSL listen socket in the DHT announce.
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Error_Codes.html b/docs/reference-Error_Codes.html new file mode 100644 index 0000000..de3ab5c --- /dev/null +++ b/docs/reference-Error_Codes.html @@ -0,0 +1,1388 @@ + + + + + + +reference-Error_Codes.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Error Codes

    + +[report issue]
    +

    storage_error

    +

    Declared in "libtorrent/error_code.hpp"

    +

    used by storage to return errors +also includes which underlying file the +error happened on

    +
    +struct storage_error
    +{
    +   explicit operator bool () const;
    +   file_index_t file () const;
    +   void file (file_index_t f);
    +
    +   error_code ec;
    +   operation_t operation;
    +};
    +
    +[report issue]
    +

    bool()

    +
    +explicit operator bool () const;
    +
    +

    explicitly converts to true if this object represents an error, and +false if it does not.

    +[report issue]
    +
    +

    file()

    +
    +file_index_t file () const;
    +void file (file_index_t f);
    +
    +

    set and query the index (in the torrent) of the file this error +occurred on. This may also have special values defined in +torrent_status.

    +[report issue]
    +
    ec
    +
    the error that occurred
    +
    +[report issue]
    +
    operation
    +
    A code from operation_t enum, indicating what +kind of operation failed.
    +
    +[report issue]
    +
    +
    +

    libtorrent_category()

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +boost::system::error_category& libtorrent_category ();
    +
    +

    return the instance of the libtorrent_error_category which +maps libtorrent error codes to human readable error messages.

    +[report issue]
    +
    +

    http_category()

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +boost::system::error_category& http_category ();
    +
    +

    returns the error_category for HTTP errors

    +[report issue]
    +
    +

    pcp_category()

    +

    Declared in "libtorrent/natpmp.hpp"

    +
    +boost::system::error_category& pcp_category ();
    +
    +[report issue]
    +
    +

    upnp_category()

    +

    Declared in "libtorrent/upnp.hpp"

    +
    +boost::system::error_category& upnp_category ();
    +
    +

    the boost.system error category for UPnP errors

    +[report issue]
    +
    +

    gzip_category()

    +

    Declared in "libtorrent/gzip.hpp"

    +
    +boost::system::error_category& gzip_category ();
    +
    +

    get the error_category for zip errors

    +[report issue]
    +
    +

    bdecode_category()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +boost::system::error_category& bdecode_category ();
    +
    +[report issue]
    +
    +

    socks_category()

    +

    Declared in "libtorrent/socks5_stream.hpp"

    +
    +boost::system::error_category& socks_category ();
    +
    +

    returns the error_category for SOCKS5 errors

    +[report issue]
    +
    +

    i2p_category()

    +

    Declared in "libtorrent/i2p_stream.hpp"

    +
    +boost::system::error_category& i2p_category ();
    +
    +

    returns the error category for I2P errors

    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/error_code.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    file_collision1Two torrents has files which end up overwriting each other
    failed_hash_check2A piece did not match its piece hash
    torrent_is_no_dict3The .torrent file does not contain a bencoded dictionary at +its top level
    torrent_missing_info4The .torrent file does not have an info dictionary
    torrent_info_no_dict5The .torrent file's info entry is not a dictionary
    torrent_missing_piece_length6The .torrent file does not have a piece length entry
    torrent_missing_name7The .torrent file does not have a name entry
    torrent_invalid_name8The .torrent file's name entry is invalid
    torrent_invalid_length9The length of a file, or of the whole .torrent file is invalid. +Either negative or not an integer
    torrent_file_parse_failed10Failed to parse a file entry in the .torrent
    torrent_missing_pieces11The pieces field is missing or invalid in the .torrent file
    torrent_invalid_hashes12The pieces string has incorrect length
    too_many_pieces_in_torrent13The .torrent file has more pieces than is supported by libtorrent
    invalid_swarm_metadata14The metadata (.torrent file) that was received from the swarm +matched the info-hash, but failed to be parsed
    invalid_bencoding15The file or buffer is not correctly bencoded
    no_files_in_torrent16The .torrent file does not contain any files
    invalid_escaped_string17The string was not properly url-encoded as expected
    session_is_closing18Operation is not permitted since the session is shutting down
    duplicate_torrent19There's already a torrent with that info-hash added to the +session
    invalid_torrent_handle20The supplied torrent_handle is not referring to a valid torrent
    invalid_entry_type21The type requested from the entry did not match its type
    missing_info_hash_in_uri22The specified URI does not contain a valid info-hash
    file_too_short23One of the files in the torrent was unexpectedly small. This +might be caused by files being changed by an external process
    unsupported_url_protocol24The URL used an unknown protocol. Currently http and +https (if built with openssl support) are recognized. For +trackers udp is recognized as well.
    url_parse_error25The URL did not conform to URL syntax and failed to be parsed
    peer_sent_empty_piece26The peer sent a piece message of length 0
    parse_failed27A bencoded structure was corrupt and failed to be parsed
    invalid_file_tag28The fast resume file was missing or had an invalid file version +tag
    missing_info_hash29The fast resume file was missing or had an invalid info-hash
    mismatching_info_hash30The info-hash did not match the torrent
    invalid_hostname31The URL contained an invalid hostname
    invalid_port32The URL had an invalid port
    port_blocked33The port is blocked by the port-filter, and prevented the +connection
    expected_close_bracket_in_address34The IPv6 address was expected to end with "]"
    destructing_torrent35The torrent is being destructed, preventing the operation to +succeed
    timed_out36The connection timed out
    upload_upload_connection37The peer is upload only, and we are upload only. There's no point +in keeping the connection
    uninteresting_upload_peer38The peer is upload only, and we're not interested in it. There's +no point in keeping the connection
    invalid_info_hash39The peer sent an unknown info-hash
    torrent_paused40The torrent is paused, preventing the operation from succeeding
    invalid_have41The peer sent an invalid have message, either wrong size or +referring to a piece that doesn't exist in the torrent
    invalid_bitfield_size42The bitfield message had the incorrect size
    too_many_requests_when_choked43The peer kept requesting pieces after it was choked, possible +abuse attempt.
    invalid_piece44The peer sent a piece message that does not correspond to a +piece request sent by the client
    no_memory45memory allocation failed
    torrent_aborted46The torrent is aborted, preventing the operation to succeed
    self_connection47The peer is a connection to ourself, no point in keeping it
    invalid_piece_size48The peer sent a piece message with invalid size, either negative +or greater than one block
    timed_out_no_interest49The peer has not been interesting or interested in us for too +long, no point in keeping it around
    timed_out_inactivity50The peer has not said anything in a long time, possibly dead
    timed_out_no_handshake51The peer did not send a handshake within a reasonable amount of +time, it might not be a bittorrent peer
    timed_out_no_request52The peer has been unchoked for too long without requesting any +data. It might be lying about its interest in us
    invalid_choke53The peer sent an invalid choke message
    invalid_unchoke54The peer send an invalid unchoke message
    invalid_interested55The peer sent an invalid interested message
    invalid_not_interested56The peer sent an invalid not-interested message
    invalid_request57The peer sent an invalid piece request message
    invalid_hash_list58The peer sent an invalid hash-list message (this is part of the +merkle-torrent extension)
    invalid_hash_piece59The peer sent an invalid hash-piece message (this is part of the +merkle-torrent extension)
    invalid_cancel60The peer sent an invalid cancel message
    invalid_dht_port61The peer sent an invalid DHT port-message
    invalid_suggest62The peer sent an invalid suggest piece-message
    invalid_have_all63The peer sent an invalid have all-message
    invalid_have_none64The peer sent an invalid have none-message
    invalid_reject65The peer sent an invalid reject message
    invalid_allow_fast66The peer sent an invalid allow fast-message
    invalid_extended67The peer sent an invalid extension message ID
    invalid_message68The peer sent an invalid message ID
    sync_hash_not_found69The synchronization hash was not found in the encrypted handshake
    invalid_encryption_constant70The encryption constant in the handshake is invalid
    no_plaintext_mode71The peer does not support plain text, which is the selected mode
    no_rc4_mode72The peer does not support RC4, which is the selected mode
    unsupported_encryption_mode73The peer does not support any of the encryption modes that the +client supports
    unsupported_encryption_mode_selected74The peer selected an encryption mode that the client did not +advertise and does not support
    invalid_pad_size75The pad size used in the encryption handshake is of invalid size
    invalid_encrypt_handshake76The encryption handshake is invalid
    no_incoming_encrypted77The client is set to not support incoming encrypted connections +and this is an encrypted connection
    no_incoming_regular78The client is set to not support incoming regular bittorrent +connections, and this is a regular connection
    duplicate_peer_id79The client is already connected to this peer-ID
    torrent_removed80Torrent was removed
    packet_too_large81The packet size exceeded the upper sanity check-limit
    reserved82 
    http_error83The web server responded with an error
    missing_location84The web server response is missing a location header
    invalid_redirection85The web seed redirected to a path that no longer matches the +.torrent directory structure
    redirecting86The connection was closed because it redirected to a different +URL
    invalid_range87The HTTP range header is invalid
    no_content_length88The HTTP response did not have a content length
    banned_by_ip_filter89The IP is blocked by the IP filter
    too_many_connections90At the connection limit
    peer_banned91The peer is marked as banned
    stopping_torrent92The torrent is stopping, causing the operation to fail
    too_many_corrupt_pieces93The peer has sent too many corrupt pieces and is banned
    torrent_not_ready94The torrent is not ready to receive peers
    peer_not_constructed95The peer is not completely constructed yet
    session_closing96The session is closing, causing the operation to fail
    optimistic_disconnect97The peer was disconnected in order to leave room for a +potentially better peer
    torrent_finished98The torrent is finished
    no_router99No UPnP router found
    metadata_too_large100The metadata message says the metadata exceeds the limit
    invalid_metadata_request101The peer sent an invalid metadata request message
    invalid_metadata_size102The peer advertised an invalid metadata size
    invalid_metadata_offset103The peer sent a message with an invalid metadata offset
    invalid_metadata_message104The peer sent an invalid metadata message
    pex_message_too_large105The peer sent a peer exchange message that was too large
    invalid_pex_message106The peer sent an invalid peer exchange message
    invalid_lt_tracker_message107The peer sent an invalid tracker exchange message
    too_frequent_pex108The peer sent an pex messages too often. This is a possible +attempt of and attack
    no_metadata109The operation failed because it requires the torrent to have +the metadata (.torrent file) and it doesn't have it yet. +This happens for magnet links before they have downloaded the +metadata, and also torrents added by URL.
    invalid_dont_have110The peer sent an invalid dont_have message. The don't have +message is an extension to allow peers to advertise that the +no longer has a piece they previously had.
    requires_ssl_connection111The peer tried to connect to an SSL torrent without connecting +over SSL.
    invalid_ssl_cert112The peer tried to connect to a torrent with a certificate +for a different torrent.
    not_an_ssl_torrent113the torrent is not an SSL torrent, and the operation requires +an SSL torrent
    banned_by_port_filter114peer was banned because its listen port is within a banned port +range, as specified by the port_filter.
    invalid_session_handle115The session_handle is not referring to a valid session_impl
    invalid_listen_socket116the listen socket associated with this request was closed
    deprecated_120120 
    deprecated_121121 
    deprecated_122122 
    deprecated_123123 
    deprecated_124124 
    missing_file_sizes130The resume data file is missing the file sizes entry
    no_files_in_resume_data131The resume data file file sizes entry is empty
    missing_pieces132The resume data file is missing the pieces and slots entry
    mismatching_number_of_files133The number of files in the resume data does not match the number +of files in the torrent
    mismatching_file_size134One of the files on disk has a different size than in the fast +resume file
    mismatching_file_timestamp135One of the files on disk has a different timestamp than in the +fast resume file
    not_a_dictionary136The resume data file is not a dictionary
    invalid_blocks_per_piece137The blocks per piece entry is invalid in the resume data file
    missing_slots138The resume file is missing the slots entry, which is required +for torrents with compact allocation. DEPRECATED
    too_many_slots139The resume file contains more slots than the torrent
    invalid_slot_list140The slot entry is invalid in the resume data
    invalid_piece_index141One index in the slot list is invalid
    pieces_need_reorder142The pieces on disk needs to be re-ordered for the specified +allocation mode. This happens if you specify sparse allocation +and the files on disk are using compact storage. The pieces needs +to be moved to their right position. DEPRECATED
    resume_data_not_modified143this error is returned when asking to save resume data and +specifying the flag to only save when there's anything new to save +(torrent_handle::only_if_modified) and there wasn't anything changed.
    http_parse_error150The HTTP header was not correctly formatted
    http_missing_location151The HTTP response was in the 300-399 range but lacked a location +header
    http_failed_decompress152The HTTP response was encoded with gzip or deflate but +decompressing it failed
    no_i2p_router160The URL specified an i2p address, but no i2p router is configured
    no_i2p_endpoint161i2p acceptor is not available yet, can't announce without endpoint
    scrape_not_available170The tracker URL doesn't support transforming it into a scrape +URL. i.e. it doesn't contain "announce.
    invalid_tracker_response171invalid tracker response
    invalid_peer_dict172invalid peer dictionary entry. Not a dictionary
    tracker_failure173tracker sent a failure message
    invalid_files_entry174missing or invalid files entry
    invalid_hash_entry175missing or invalid hash entry
    invalid_peers_entry176missing or invalid peers and peers6 entry
    invalid_tracker_response_length177UDP tracker response packet has invalid size
    invalid_tracker_transaction_id178invalid transaction id in UDP tracker response
    invalid_tracker_action179invalid action field in UDP tracker response
    no_entropy200random number generation failed
    error_code_max201the number of error codes
    +[report issue]
    +
    +

    enum http_errors

    +

    Declared in "libtorrent/error_code.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    cont100 
    ok200 
    created201 
    accepted202 
    no_content204 
    multiple_choices300 
    moved_permanently301 
    moved_temporarily302 
    not_modified304 
    bad_request400 
    unauthorized401 
    forbidden403 
    not_found404 
    internal_server_error500 
    not_implemented501 
    bad_gateway502 
    service_unavailable503 
    +[report issue]
    +
    +

    enum pcp_errors

    +

    Declared in "libtorrent/natpmp.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pcp_success0 
    pcp_unsupp_version1 
    pcp_not_authorized2 
    pcp_malformed_request3 
    pcp_unsupp_opcode4 
    pcp_unsupp_option5 
    pcp_malformed_option6 
    pcp_network_failure7 
    pcp_no_resources8 
    pcp_unsupp_protocol9 
    pcp_user_ex_quota10 
    pcp_cannot_provide_external11 
    pcp_address_mismatch12 
    pcp_excessive_remote_peers13 
    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/upnp.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0No error
    invalid_argument402One of the arguments in the request is invalid
    action_failed501The request failed
    value_not_in_array714The specified value does not exist in the array
    source_ip_cannot_be_wildcarded715The source IP address cannot be wild-carded, but +must be fully specified
    external_port_cannot_be_wildcarded716The external port cannot be a wildcard, but must +be specified
    port_mapping_conflict718The port mapping entry specified conflicts with a +mapping assigned previously to another client
    internal_port_must_match_external724Internal and external port value must be the same
    only_permanent_leases_supported725The NAT implementation only supports permanent +lease times on port mappings
    remote_host_must_be_wildcard726RemoteHost must be a wildcard and cannot be a +specific IP address or DNS name
    external_port_must_be_wildcard727ExternalPort must be a wildcard and cannot be a +specific port
    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/gzip.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    invalid_gzip_header1the supplied gzip buffer has invalid header
    inflated_data_too_large2the gzip buffer would inflate to more bytes than the specified +maximum size, and was rejected.
    data_did_not_terminate3available inflate data did not terminate
    space_exhausted4output space exhausted before completing inflate
    invalid_block_type5invalid block type (type == 3)
    invalid_stored_block_length6stored block length did not match one's complement
    too_many_length_or_distance_codes7dynamic block code description: too many length or distance codes
    code_lengths_codes_incomplete8dynamic block code description: code lengths codes incomplete
    repeat_lengths_with_no_first_length9dynamic block code description: repeat lengths with no first length
    repeat_more_than_specified_lengths10dynamic block code description: repeat more than specified lengths
    invalid_literal_length_code_lengths11dynamic block code description: invalid literal/length code lengths
    invalid_distance_code_lengths12dynamic block code description: invalid distance code lengths
    invalid_literal_code_in_block13invalid literal/length or distance code in fixed or dynamic block
    distance_too_far_back_in_block14distance is too far back in fixed or dynamic block
    unknown_gzip_error15an unknown error occurred during gzip inflation
    error_code_max16the number of error codes
    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/bdecode.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    expected_digit1expected digit in bencoded string
    expected_colon2expected colon in bencoded string
    unexpected_eof3unexpected end of file in bencoded string
    expected_value4expected value (list, dict, int or string) in bencoded string
    depth_exceeded5bencoded recursion depth limit exceeded
    limit_exceeded6bencoded item count limit exceeded
    overflow7integer overflow
    error_code_max8the number of error codes
    +[report issue]
    +
    +

    enum socks_error_code

    +

    Declared in "libtorrent/socks5_stream.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    unsupported_version1 
    unsupported_authentication_method2 
    unsupported_authentication_version3 
    authentication_error4 
    username_required5 
    general_failure6 
    command_not_supported7 
    no_identd8 
    identd_error9 
    num_errors10 
    +[report issue]
    +
    +

    enum i2p_error_code

    +

    Declared in "libtorrent/i2p_stream.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    parse_failed1 
    cant_reach_peer2 
    i2p_error3 
    invalid_key4 
    invalid_id5 
    timeout6 
    key_not_found7 
    duplicated_id8 
    num_errors9 
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Filter.html b/docs/reference-Filter.html new file mode 100644 index 0000000..a14d3e7 --- /dev/null +++ b/docs/reference-Filter.html @@ -0,0 +1,235 @@ + + + + + + +reference-Filter.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Filter

    +
    +

    Table of contents

    + +
    +[report issue]
    +

    ip_filter

    +

    Declared in "libtorrent/ip_filter.hpp"

    +

    The ip_filter class is a set of rules that uniquely categorizes all +ip addresses as allowed or disallowed. The default constructor creates +a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +the IPv4 range, and the equivalent range covering all addresses for the +IPv6 range).

    +

    A default constructed ip_filter does not filter any address.

    +
    +struct ip_filter
    +{
    +   void add_rule (address const& first, address const& last, std::uint32_t flags);
    +   std::uint32_t access (address const& addr) const;
    +   filter_tuple_t export_filter () const;
    +
    +   enum access_flags
    +   {
    +      blocked,
    +   };
    +};
    +
    +[report issue]
    +

    add_rule()

    +
    +void add_rule (address const& first, address const& last, std::uint32_t flags);
    +
    +

    Adds a rule to the filter. first and last defines a range of +ip addresses that will be marked with the given flags. The flags +can currently be 0, which means allowed, or ip_filter::blocked, which +means disallowed.

    +

    precondition: +first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()

    +

    postcondition: +access(x) == flags for every x in the range [first, last]

    +

    This means that in a case of overlapping ranges, the last one applied takes +precedence.

    +[report issue]
    +
    +

    access()

    +
    +std::uint32_t access (address const& addr) const;
    +
    +

    Returns the access permissions for the given address (addr). The permission +can currently be 0 or ip_filter::blocked. The complexity of this operation +is O(log n), where n is the minimum number of non-overlapping ranges to describe +the current filter.

    +[report issue]
    +
    +

    export_filter()

    +
    +filter_tuple_t export_filter () const;
    +
    +

    This function will return the current state of the filter in the minimum number of +ranges possible. They are sorted from ranges in low addresses to high addresses. Each +entry in the returned vector is a range with the access control specified in its +flags field.

    +

    The return value is a tuple containing two range-lists. One for IPv4 addresses +and one for IPv6 addresses.

    +[report issue]
    +
    +

    enum access_flags

    +

    Declared in "libtorrent/ip_filter.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    blocked1indicates that IPs in this range should not be connected +to nor accepted as incoming connections
    +[report issue]
    +
    +
    +

    port_filter

    +

    Declared in "libtorrent/ip_filter.hpp"

    +

    the port filter maps non-overlapping port ranges to flags. This +is primarily used to indicate whether a range of ports should +be connected to or not. The default is to have the full port +range (0-65535) set to flag 0.

    +
    +class port_filter
    +{
    +   void add_rule (std::uint16_t first, std::uint16_t last, std::uint32_t flags);
    +   std::uint32_t access (std::uint16_t port) const;
    +
    +   enum access_flags
    +   {
    +      blocked,
    +   };
    +};
    +
    +[report issue]
    +

    add_rule()

    +
    +void add_rule (std::uint16_t first, std::uint16_t last, std::uint32_t flags);
    +
    +

    set the flags for the specified port range (first, last) to +flags overwriting any existing rule for those ports. The range +is inclusive, i.e. the port last also has the flag set on it.

    +[report issue]
    +
    +

    access()

    +
    +std::uint32_t access (std::uint16_t port) const;
    +
    +

    test the specified port (port) for whether it is blocked +or not. The returned value is the flags set for this port. +see access_flags.

    +[report issue]
    +
    +

    enum access_flags

    +

    Declared in "libtorrent/ip_filter.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    blocked1this flag indicates that destination ports in the +range should not be connected to
    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Plugins.html b/docs/reference-Plugins.html new file mode 100644 index 0000000..31253cf --- /dev/null +++ b/docs/reference-Plugins.html @@ -0,0 +1,752 @@ + + + + + + +reference-Plugins.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Plugins

    + +

    libtorrent has a plugin interface for implementing extensions to the protocol. +These can be general extensions for transferring metadata or peer exchange +extensions, or it could be used to provide a way to customize the protocol +to fit a particular (closed) network.

    +

    In short, the plugin interface makes it possible to:

    +
      +
    • register extension messages (sent in the extension handshake), see +extensions.
    • +
    • add data and parse data from the extension handshake.
    • +
    • send extension messages and standard bittorrent messages.
    • +
    • override or block the handling of standard bittorrent messages.
    • +
    • save and restore state via the session state
    • +
    • see all alerts that are posted
    • +
    +
    +

    a word of caution

    +

    Writing your own plugin is a very easy way to introduce serious bugs such as +dead locks and race conditions. Since a plugin has access to internal +structures it is also quite easy to sabotage libtorrent's operation.

    +

    All the callbacks are always called from the libtorrent network thread. In +case portions of your plugin are called from other threads, typically the main +thread, you cannot use any of the member functions on the internal structures +in libtorrent, since those require being called from the libtorrent network +thread . Furthermore, you also need to synchronize your own shared data +within the plugin, to make sure it is not accessed at the same time from the +libtorrent thread (through a callback). If you need to send out a message +from another thread, it is advised to use an internal queue, and do the +actual sending in tick().

    +

    Since the plugin interface gives you easy access to internal structures, it +is not supported as a stable API. Plugins should be considered specific to a +specific version of libtorrent. Although, in practice the internals mostly +don't change that dramatically.

    +
    +
    +
    +

    plugin-interface

    +

    The plugin interface consists of three base classes that the plugin may +implement. These are called plugin, torrent_plugin and peer_plugin. +They are found in the <libtorrent/extensions.hpp> header.

    +

    These plugins are instantiated for each session, torrent and possibly each peer, +respectively.

    +

    For plugins that only need per torrent state, it is enough to only implement +torrent_plugin and pass a constructor function or function object to +session::add_extension() or torrent_handle::add_extension() (if the +torrent has already been started and you want to hook in the extension at +run-time).

    +

    The signature of the function is:

    +
    +std::shared_ptr<torrent_plugin> (*)(torrent_handle const&, void*);
    +
    +

    The second argument is the userdata passed to session::add_torrent() or +torrent_handle::add_extension().

    +

    The function should return a std::shared_ptr<torrent_plugin> which +may or may not be 0. If it is a nullptr, the extension is simply ignored +for this torrent. If it is a valid pointer (to a class inheriting +torrent_plugin), it will be associated with this torrent and callbacks +will be made on torrent events.

    +

    For more elaborate plugins which require session wide state, you would +implement plugin, construct an object (in a std::shared_ptr) and pass +it in to session::add_extension().

    +
    +
    +

    custom alerts

    +

    Since plugins are running within internal libtorrent threads, one convenient +way to communicate with the client is to post custom alerts.

    +

    The expected interface of any alert, apart from deriving from the alert +base class, looks like this:

    +
    +static const int alert_type = <unique alert ID>;
    +virtual int type() const { return alert_type; }
    +
    +virtual std::string message() const;
    +
    +static const alert_category_t static_category = <bitmask of alert::category_t flags>;
    +virtual alert_category_t category() const { return static_category; }
    +
    +virtual char const* what() const { return <string literal of the name of this alert>; }
    +
    +

    The alert_type is used for the type-checking in alert_cast. It must +not collide with any other alert. The built-in alerts in libtorrent will +not use alert type IDs greater than user_alert_id. When defining your +own alert, make sure it's greater than this constant.

    +

    type() is the run-time equivalence of the alert_type.

    +

    The message() virtual function is expected to construct a useful +string representation of the alert and the event or data it represents. +Something convenient to put in a log file for instance.

    +

    clone() is used internally to copy alerts. The suggested implementation +of simply allocating a new instance as a copy of *this is all that's +expected.

    +

    The static category is required for checking whether or not the category +for a specific alert is enabled or not, without instantiating the alert. +The category virtual function is the run-time equivalence.

    +

    The what() virtual function may simply be a string literal of the class +name of your alert.

    +

    For more information, see the alert section.

    +[report issue]
    +

    plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    this is the base class for a session plugin. One primary feature +is that it is notified of all torrents that are added to the session, +and can add its own torrent_plugins.

    +
    +struct plugin
    +{
    +   virtual feature_flags_t implemented_features ();
    +   virtual std::shared_ptr<torrent_plugin> new_torrent (torrent_handle const&, void*);
    +   virtual void added (session_handle const&);
    +   virtual bool on_dht_request (string_view /* query */
    +      , udp::endpoint const& /* source */, bdecode_node const& /* message */
    +      , entry& /* response */);
    +   virtual void on_alert (alert const*);
    +   virtual bool on_unknown_torrent (sha1_hash const& /* info_hash */
    +      , peer_connection_handle const& /* pc */, add_torrent_params& /* p */);
    +   virtual void on_tick ();
    +   virtual uint64_t get_unchoke_priority (peer_connection_handle const& /* peer */);
    +   virtual void save_state (entry&);
    +   virtual void load_state (bdecode_node const&);
    +
    +   static constexpr feature_flags_t optimistic_unchoke_feature  = 1_bit;
    +   static constexpr feature_flags_t tick_feature  = 2_bit;
    +   static constexpr feature_flags_t dht_request_feature  = 3_bit;
    +   static constexpr feature_flags_t alert_feature  = 4_bit;
    +};
    +
    +[report issue]
    +

    implemented_features()

    +
    +virtual feature_flags_t implemented_features ();
    +
    +

    This function is expected to return a bitmask indicating which features +this plugin implements. Some callbacks on this object may not be called +unless the corresponding feature flag is returned here. Note that +callbacks may still be called even if the corresponding feature is not +specified in the return value here. See feature_flags_t for possible +flags to return.

    +[report issue]
    +
    +

    new_torrent()

    +
    +virtual std::shared_ptr<torrent_plugin> new_torrent (torrent_handle const&, void*);
    +
    +

    this is called by the session every time a new torrent is added. +The torrent* points to the internal torrent object created +for the new torrent. The void* is the userdata pointer as +passed in via add_torrent_params.

    +

    If the plugin returns a torrent_plugin instance, it will be added +to the new torrent. Otherwise, return an empty shared_ptr to a +torrent_plugin (the default).

    +[report issue]
    +
    +

    added()

    +
    +virtual void added (session_handle const&);
    +
    +

    called when plugin is added to a session

    +[report issue]
    +
    +

    on_dht_request()

    +
    +virtual bool on_dht_request (string_view /* query */
    +      , udp::endpoint const& /* source */, bdecode_node const& /* message */
    +      , entry& /* response */);
    +
    +

    called when a dht request is received. +If your plugin expects this to be called, make sure to include the flag +dht_request_feature in the return value from implemented_features().

    +[report issue]
    +
    +

    on_alert()

    +
    +virtual void on_alert (alert const*);
    +
    +

    called when an alert is posted alerts that are filtered are not posted. +If your plugin expects this to be called, make sure to include the flag +alert_feature in the return value from implemented_features().

    +[report issue]
    +
    +

    on_unknown_torrent()

    +
    +virtual bool on_unknown_torrent (sha1_hash const& /* info_hash */
    +      , peer_connection_handle const& /* pc */, add_torrent_params& /* p */);
    +
    +

    return true if the add_torrent_params should be added

    +[report issue]
    +
    +

    on_tick()

    +
    +virtual void on_tick ();
    +
    +

    called once per second. +If your plugin expects this to be called, make sure to include the flag +tick_feature in the return value from implemented_features().

    +[report issue]
    +
    +

    get_unchoke_priority()

    +
    +virtual uint64_t get_unchoke_priority (peer_connection_handle const& /* peer */);
    +
    +

    called when choosing peers to optimistically unchoke. The return value +indicates the peer's priority for unchoking. Lower return values +correspond to higher priority. Priorities above 2^63-1 are reserved. +If your plugin has no priority to assign a peer it should return 2^64-1. +If your plugin expects this to be called, make sure to include the flag +optimistic_unchoke_feature in the return value from implemented_features(). +If multiple plugins implement this function the lowest return value +(i.e. the highest priority) is used.

    +[report issue]
    +
    +

    save_state()

    +
    +virtual void save_state (entry&);
    +
    +

    called when saving settings state

    +[report issue]
    +
    +

    load_state()

    +
    +virtual void load_state (bdecode_node const&);
    +
    +

    called when loading settings state

    +[report issue]
    +
    optimistic_unchoke_feature
    +
    include this bit if your plugin needs to alter the order of the +optimistic unchoke of peers. i.e. have the on_optimistic_unchoke() +callback be called.
    +
    +[report issue]
    +
    tick_feature
    +
    include this bit if your plugin needs to have on_tick() called
    +
    +[report issue]
    +
    dht_request_feature
    +
    include this bit if your plugin needs to have on_dht_request() +called
    +
    +[report issue]
    +
    alert_feature
    +
    include this bit if your plugin needs to have on_alert() +called
    +
    +[report issue]
    +
    +
    +

    torrent_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    Torrent plugins are associated with a single torrent and have a number +of functions called at certain events. Many of its functions have the +ability to change or override the default libtorrent behavior.

    +
    +struct torrent_plugin
    +{
    +   virtual std::shared_ptr<peer_plugin> new_connection (peer_connection_handle const&);
    +   virtual void on_piece_failed (piece_index_t);
    +   virtual void on_piece_pass (piece_index_t);
    +   virtual void tick ();
    +   virtual bool on_pause ();
    +   virtual bool on_resume ();
    +   virtual void on_files_checked ();
    +   virtual void on_state (torrent_status::state_t);
    +   virtual void on_add_peer (tcp::endpoint const&,
    +      peer_source_flags_t, add_peer_flags_t);
    +
    +   static constexpr add_peer_flags_t first_time  = 1_bit;
    +   static constexpr add_peer_flags_t filtered  = 2_bit;
    +};
    +
    +[report issue]
    +

    new_connection()

    +
    +virtual std::shared_ptr<peer_plugin> new_connection (peer_connection_handle const&);
    +
    +

    This function is called each time a new peer is connected to the torrent. You +may choose to ignore this by just returning a default constructed +shared_ptr (in which case you don't need to override this member +function).

    +

    If you need an extension to the peer connection (which most plugins do) you +are supposed to return an instance of your peer_plugin class. Which in +turn will have its hook functions called on event specific to that peer.

    +

    The peer_connection_handle will be valid as long as the shared_ptr +is being held by the torrent object. So, it is generally a good idea to not +keep a shared_ptr to your own peer_plugin. If you want to keep references +to it, use weak_ptr.

    +

    If this function throws an exception, the connection will be closed.

    + +[report issue]
    +
    +

    on_piece_failed() on_piece_pass()

    +
    +virtual void on_piece_failed (piece_index_t);
    +virtual void on_piece_pass (piece_index_t);
    +
    +

    These hooks are called when a piece passes the hash check or fails the hash +check, respectively. The index is the piece index that was downloaded. +It is possible to access the list of peers that participated in sending the +piece through the torrent and the piece_picker.

    +[report issue]
    +
    +

    tick()

    +
    +virtual void tick ();
    +
    +

    This hook is called approximately once per second. It is a way of making it +easy for plugins to do timed events, for sending messages or whatever.

    + +[report issue]
    +
    +

    on_resume() on_pause()

    +
    +virtual bool on_pause ();
    +virtual bool on_resume ();
    +
    +

    These hooks are called when the torrent is paused and resumed respectively. +The return value indicates if the event was handled. A return value of +true indicates that it was handled, and no other plugin after this one +will have this hook function called, and the standard handler will also not be +invoked. So, returning true effectively overrides the standard behavior of +pause or resume.

    +

    Note that if you call pause() or resume() on the torrent from your +handler it will recurse back into your handler, so in order to invoke the +standard handler, you have to keep your own state on whether you want standard +behavior or overridden behavior.

    +[report issue]
    +
    +

    on_files_checked()

    +
    +virtual void on_files_checked ();
    +
    +

    This function is called when the initial files of the torrent have been +checked. If there are no files to check, this function is called immediately.

    +

    i.e. This function is always called when the torrent is in a state where it +can start downloading.

    +[report issue]
    +
    +

    on_state()

    +
    +virtual void on_state (torrent_status::state_t);
    +
    +

    called when the torrent changes state +the state is one of torrent_status::state_t +enum members

    +[report issue]
    +
    +

    on_add_peer()

    +
    +virtual void on_add_peer (tcp::endpoint const&,
    +      peer_source_flags_t, add_peer_flags_t);
    +
    +

    called every time a new peer is added to the peer list. +This is before the peer is connected to. For flags, see +torrent_plugin::flags_t. The source argument refers to +the source where we learned about this peer from. It's a +bitmask, because many sources may have told us about the same +peer. For peer source flags, see peer_info::peer_source_flags.

    +[report issue]
    +
    first_time
    +
    this is the first time we see this peer
    +
    +[report issue]
    +
    filtered
    +
    this peer was not added because it was +filtered by the IP filter
    +
    +[report issue]
    +
    +
    +

    peer_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    peer plugins are associated with a specific peer. A peer could be +both a regular bittorrent peer (bt_peer_connection) or one of the +web seed connections (web_peer_connection or http_seed_connection). +In order to only attach to certain peers, make your +torrent_plugin::new_connection only return a plugin for certain peer +connection types

    +
    +struct peer_plugin
    +{
    +   virtual string_view type () const;
    +   virtual void add_handshake (entry&);
    +   virtual void on_disconnect (error_code const&);
    +   virtual void on_connected ();
    +   virtual bool on_handshake (span<char const>);
    +   virtual bool on_extension_handshake (bdecode_node const&);
    +   virtual bool on_interested ();
    +   virtual bool on_allowed_fast (piece_index_t);
    +   virtual bool on_choke ();
    +   virtual bool on_not_interested ();
    +   virtual bool on_dont_have (piece_index_t);
    +   virtual bool on_have (piece_index_t);
    +   virtual bool on_bitfield (bitfield const& /*bitfield*/);
    +   virtual bool on_have_all ();
    +   virtual bool on_request (peer_request const&);
    +   virtual bool on_have_none ();
    +   virtual bool on_unchoke ();
    +   virtual bool on_piece (peer_request const& /*piece*/
    +      , span<char const> /*buf*/);
    +   virtual bool on_cancel (peer_request const&);
    +   virtual bool on_reject (peer_request const&);
    +   virtual bool on_suggest (piece_index_t);
    +   virtual void sent_unchoke ();
    +   virtual void sent_payload (int /* bytes */);
    +   virtual bool can_disconnect (error_code const& /*ec*/);
    +   virtual bool on_extended (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +   virtual bool on_unknown_message (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +   virtual void on_piece_failed (piece_index_t);
    +   virtual void on_piece_pass (piece_index_t);
    +   virtual void tick ();
    +   virtual bool write_request (peer_request const&);
    +};
    +
    +[report issue]
    +

    type()

    +
    +virtual string_view type () const;
    +
    +

    This function is expected to return the name of +the plugin.

    +[report issue]
    +
    +

    add_handshake()

    +
    +virtual void add_handshake (entry&);
    +
    +

    can add entries to the extension handshake +this is not called for web seeds

    +[report issue]
    +
    +

    on_disconnect()

    +
    +virtual void on_disconnect (error_code const&);
    +
    +

    called when the peer is being disconnected.

    +[report issue]
    +
    +

    on_connected()

    +
    +virtual void on_connected ();
    +
    +

    called when the peer is successfully connected. Note that +incoming connections will have been connected by the time +the peer plugin is attached to it, and won't have this hook +called.

    +[report issue]
    +
    +

    on_handshake()

    +
    +virtual bool on_handshake (span<char const>);
    +
    +

    this is called when the initial bittorrent handshake is received. +Returning false means that the other end doesn't support this extension +and will remove it from the list of plugins. this is not called for web +seeds

    +[report issue]
    +
    +

    on_extension_handshake()

    +
    +virtual bool on_extension_handshake (bdecode_node const&);
    +
    +

    called when the extension handshake from the other end is received +if this returns false, it means that this extension isn't +supported by this peer. It will result in this peer_plugin +being removed from the peer_connection and destructed. +this is not called for web seeds

    + + + + + + + + + + +[report issue]
    +
    +

    on_have_all() on_have() on_request() on_interested() on_bitfield() on_unchoke() on_have_none() on_not_interested() on_allowed_fast() on_choke() on_dont_have()

    +
    +virtual bool on_interested ();
    +virtual bool on_allowed_fast (piece_index_t);
    +virtual bool on_choke ();
    +virtual bool on_not_interested ();
    +virtual bool on_dont_have (piece_index_t);
    +virtual bool on_have (piece_index_t);
    +virtual bool on_bitfield (bitfield const& /*bitfield*/);
    +virtual bool on_have_all ();
    +virtual bool on_request (peer_request const&);
    +virtual bool on_have_none ();
    +virtual bool on_unchoke ();
    +
    +

    returning true from any of the message handlers +indicates that the plugin has handled the message. +it will break the plugin chain traversing and not let +anyone else handle the message, including the default +handler.

    +[report issue]
    +
    +

    on_piece()

    +
    +virtual bool on_piece (peer_request const& /*piece*/
    +      , span<char const> /*buf*/);
    +
    +

    This function is called when the peer connection is receiving +a piece. buf points (non-owning pointer) to the data in an +internal immutable disk buffer. The length of the data is specified +in the length member of the piece parameter. +returns true to indicate that the piece is handled and the +rest of the logic should be ignored.

    +[report issue]
    +
    +

    sent_unchoke()

    +
    +virtual void sent_unchoke ();
    +
    +

    called after a choke message has been sent to the peer

    +[report issue]
    +
    +

    sent_payload()

    +
    +virtual void sent_payload (int /* bytes */);
    +
    +

    called after piece data has been sent to the peer +this can be used for stats book keeping

    +[report issue]
    +
    +

    can_disconnect()

    +
    +virtual bool can_disconnect (error_code const& /*ec*/);
    +
    +

    called when libtorrent think this peer should be disconnected. +if the plugin returns false, the peer will not be disconnected.

    +[report issue]
    +
    +

    on_extended()

    +
    +virtual bool on_extended (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +
    +

    called when an extended message is received. If returning true, +the message is not processed by any other plugin and if false +is returned the next plugin in the chain will receive it to +be able to handle it. This is not called for web seeds. +thus function may be called more than once per incoming message, but +only the last of the calls will the body size equal the length. +i.e. Every time another fragment of the message is received, this +function will be called, until finally the whole message has been +received. The purpose of this is to allow early disconnects for invalid +messages and for reporting progress of receiving large messages.

    +[report issue]
    +
    +

    on_unknown_message()

    +
    +virtual bool on_unknown_message (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +
    +

    this is not called for web seeds

    + +[report issue]
    +
    +

    on_piece_failed() on_piece_pass()

    +
    +virtual void on_piece_failed (piece_index_t);
    +virtual void on_piece_pass (piece_index_t);
    +
    +

    called when a piece that this peer participated in either +fails or passes the hash_check

    +[report issue]
    +
    +

    tick()

    +
    +virtual void tick ();
    +
    +

    called approximately once every second

    +[report issue]
    +
    +

    write_request()

    +
    +virtual bool write_request (peer_request const&);
    +
    +

    called each time a request message is to be sent. If true +is returned, the original request message won't be sent and +no other plugin will have this function called.

    +[report issue]
    +
    +
    +

    crypto_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +
    +struct crypto_plugin
    +{
    +   virtual void set_outgoing_key (span<char const> key) = 0;
    +   virtual void set_incoming_key (span<char const> key) = 0;
    +   encrypt (span<span<char>> /*send_vec*/) = 0;
    +   virtual std::tuple<int, int, int> decrypt (span<span<char>> /*receive_vec*/) = 0;
    +};
    +
    +[report issue]
    +

    decrypt()

    +
    +virtual std::tuple<int, int, int> decrypt (span<span<char>> /*receive_vec*/) = 0;
    +
    +

    decrypt the provided buffers. +returns is a tuple representing the values +(consume, produce, packet_size)

    +

    consume is set to the number of bytes which should be trimmed from the +head of the buffers, default is 0

    +

    produce is set to the number of bytes of payload which are now ready to +be sent to the upper layer. default is the number of bytes passed in receive_vec

    +

    packet_size is set to the minimum number of bytes which must be read to +advance the next step of decryption. default is 0

    +[report issue]
    +
    +
    +

    create_ut_metadata_plugin()

    +

    Declared in "libtorrent/extensions/ut_metadata.hpp"

    +
    +std::shared_ptr<torrent_plugin> create_ut_metadata_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the ut_metadata extension. The ut_metadata +extension allows peers to request the .torrent file (or more +specifically the info-dictionary of the .torrent file) from each +other. This is the main building block in making magnet links work. +This extension is enabled by default unless explicitly disabled in +the session constructor.

    +

    This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension().

    +[report issue]
    +
    +

    create_smart_ban_plugin()

    +

    Declared in "libtorrent/extensions/smart_ban.hpp"

    +
    +std::shared_ptr<torrent_plugin> create_smart_ban_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the smart ban extension. The extension keeps +track of the data peers have sent us for failing pieces and once the +piece completes and passes the hash check bans the peers that turned +out to have sent corrupt data. +This function can either be passed in the add_torrent_params::extensions +field, or via torrent_handle::add_extension().

    +[report issue]
    +
    +

    create_ut_pex_plugin()

    +

    Declared in "libtorrent/extensions/ut_pex.hpp"

    +
    +std::shared_ptr<torrent_plugin> create_ut_pex_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the ut_pex extension. The ut_pex +extension allows peers to gossip about their connections, allowing +the swarm stay well connected and peers aware of more peers in the +swarm. This extension is enabled by default unless explicitly disabled in +the session constructor.

    +

    This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension().

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Session.html b/docs/reference-Session.html new file mode 100644 index 0000000..ffafee8 --- /dev/null +++ b/docs/reference-Session.html @@ -0,0 +1,1244 @@ + + + + + + +reference-Session.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Session

    + +[report issue]
    +

    stats_metric

    +

    Declared in "libtorrent/session_stats.hpp"

    +

    describes one statistics metric from the session. For more information, +see the session statistics section.

    +
    +struct stats_metric
    +{
    +   char const* name;
    +   int value_index;
    +   metric_type_t type;
    +};
    +
    +[report issue]
    +
    name
    +
    the name of the counter or gauge
    +
    + +[report issue]
    +
    value_index type
    +
    the index into the session stats array, where the underlying value of +this counter or gauge is found. The session stats array is part of the +session_stats_alert object.
    +
    +[report issue]
    +
    +

    session_handle

    +

    Declared in "libtorrent/session_handle.hpp"

    +

    this class provides a non-owning handle to a session and a subset of the +interface of the session class. If the underlying session is destructed +any handle to it will no longer be valid. is_valid() will return false and +any operation on it will throw a system_error exception, with error code +invalid_session_handle.

    +
    +struct session_handle
    +{
    +   bool is_valid () const;
    +   void load_state (bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all());
    +   void save_state (entry& e, save_state_flags_t flags = save_state_flags_t::all()) const;
    +   std::vector<torrent_status> get_torrent_status (
    +      std::function<bool(torrent_status const&)> const& pred
    +      , status_flags_t flags = {}) const;
    +   void refresh_torrent_status (std::vector<torrent_status>* ret
    +      , status_flags_t flags = {}) const;
    +   void post_torrent_updates (status_flags_t flags = status_flags_t::all());
    +   void post_session_stats ();
    +   void post_dht_stats ();
    +   std::vector<torrent_handle> get_torrents () const;
    +   torrent_handle find_torrent (sha1_hash const& info_hash) const;
    +   void async_add_torrent (add_torrent_params const& params);
    +   torrent_handle add_torrent (add_torrent_params const& params);
    +   torrent_handle add_torrent (add_torrent_params&& params);
    +   torrent_handle add_torrent (add_torrent_params&& params, error_code& ec);
    +   void async_add_torrent (add_torrent_params&& params);
    +   torrent_handle add_torrent (add_torrent_params const& params, error_code& ec);
    +   void resume ();
    +   bool is_paused () const;
    +   void pause ();
    +   void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    +   dht::dht_settings get_dht_settings () const;
    +   bool is_dht_running () const;
    +   void set_dht_settings (dht::dht_settings const& settings);
    +   void set_dht_storage (dht::dht_storage_constructor_type sc);
    +   void add_dht_node (std::pair<std::string, int> const& node);
    +   void dht_get_item (sha1_hash const& target);
    +   void dht_get_item (std::array<char, 32> key
    +      , std::string salt = std::string());
    +   sha1_hash dht_put_item (entry data);
    +   void dht_put_item (std::array<char, 32> key
    +      , std::function<void(entry&, std::array<char, 64>&
    +      , std::int64_t&, std::string const&)> cb
    +      , std::string salt = std::string());
    +   void dht_announce (sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {});
    +   void dht_get_peers (sha1_hash const& info_hash);
    +   void dht_live_nodes (sha1_hash const& nid);
    +   void dht_sample_infohashes (udp::endpoint const& ep, sha1_hash const& target);
    +   void dht_direct_request (udp::endpoint const& ep, entry const& e, void* userdata = nullptr);
    +   void add_extension (std::function<std::shared_ptr<torrent_plugin>(
    +      torrent_handle const&, void*)> ext);
    +   void add_extension (std::shared_ptr<plugin> ext);
    +   void set_ip_filter (ip_filter const& f);
    +   ip_filter get_ip_filter () const;
    +   void set_port_filter (port_filter const& f);
    +   unsigned short listen_port () const;
    +   bool is_listening () const;
    +   unsigned short ssl_listen_port () const;
    +   ip_filter get_peer_class_filter () const;
    +   void set_peer_class_filter (ip_filter const& f);
    +   void set_peer_class_type_filter (peer_class_type_filter const& f);
    +   peer_class_type_filter get_peer_class_type_filter () const;
    +   peer_class_t create_peer_class (char const* name);
    +   void delete_peer_class (peer_class_t cid);
    +   void set_peer_class (peer_class_t cid, peer_class_info const& pci);
    +   peer_class_info get_peer_class (peer_class_t cid) const;
    +   void remove_torrent (const torrent_handle& h, remove_flags_t options = {});
    +   void apply_settings (settings_pack const& s);
    +   settings_pack get_settings () const;
    +   void apply_settings (settings_pack&& s);
    +   void pop_alerts (std::vector<alert*>* alerts);
    +   alert* wait_for_alert (time_duration max_wait);
    +   void set_alert_notify (std::function<void()> const& fun);
    +   void delete_port_mapping (port_mapping_t handle);
    +   std::vector<port_mapping_t> add_port_mapping (portmap_protocol t, int external_port, int local_port);
    +   void reopen_network_sockets (reopen_network_flags_t options = reopen_map_ports);
    +   std::shared_ptr<aux::session_impl> native_handle () const;
    +
    +   static constexpr save_state_flags_t save_settings  = 0_bit;
    +   static constexpr save_state_flags_t save_dht_settings  = 1_bit;
    +   static constexpr save_state_flags_t save_dht_state  = 2_bit;
    +   static constexpr peer_class_t global_peer_class_id {0};
    +   static constexpr peer_class_t tcp_peer_class_id {1};
    +   static constexpr peer_class_t local_peer_class_id {2};
    +   static constexpr remove_flags_t delete_files  = 0_bit;
    +   static constexpr remove_flags_t delete_partfile  = 1_bit;
    +   static constexpr session_flags_t add_default_plugins  = 0_bit;
    +   static constexpr portmap_protocol udp  = portmap_protocol::udp;
    +   static constexpr portmap_protocol tcp  = portmap_protocol::tcp;
    +   static constexpr reopen_network_flags_t reopen_map_ports  = 0_bit;
    +};
    +
    +[report issue]
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if this handle refers to a valid session object. If the +session has been destroyed, all session_handle objects will expire and +not be valid.

    + +[report issue]
    +
    +

    save_state() load_state()

    +
    +void load_state (bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all());
    +void save_state (entry& e, save_state_flags_t flags = save_state_flags_t::all()) const;
    +
    +

    loads and saves all session settings, including dht_settings, +encryption settings and proxy settings. save_state writes all keys +to the entry that's passed in, which needs to either not be +initialized, or initialized as a dictionary.

    +

    load_state expects a bdecode_node which can be built from a bencoded +buffer with bdecode().

    +

    The flags argument is used to filter which parts of the session +state to save or load. By default, all state is saved/restored (except +for the individual torrents).

    +

    When saving settings, there are two fields that are not loaded. +peer_fingerprint and user_agent. Those are left as configured +by the session_settings passed to the session constructor or +subsequently set via apply_settings().

    + +[report issue]
    +
    +

    get_torrent_status() refresh_torrent_status()

    +
    +std::vector<torrent_status> get_torrent_status (
    +      std::function<bool(torrent_status const&)> const& pred
    +      , status_flags_t flags = {}) const;
    +void refresh_torrent_status (std::vector<torrent_status>* ret
    +      , status_flags_t flags = {}) const;
    +
    +
    +

    Note

    +

    these calls are potentially expensive and won't scale well with +lots of torrents. If you're concerned about performance, consider +using post_torrent_updates() instead.

    +
    +

    get_torrent_status returns a vector of the torrent_status for +every torrent which satisfies pred, which is a predicate function +which determines if a torrent should be included in the returned set +or not. Returning true means it should be included and false means +excluded. The flags argument is the same as to +torrent_handle::status(). Since pred is guaranteed to be +called for every torrent, it may be used to count the number of +torrents of different categories as well.

    +

    refresh_torrent_status takes a vector of torrent_status structs +(for instance the same vector that was returned by +get_torrent_status() ) and refreshes the status based on the +handle member. It is possible to use this function by first +setting up a vector of default constructed torrent_status objects, +only initializing the handle member, in order to request the +torrent status for multiple torrents in a single call. This can save a +significant amount of time if you have a lot of torrents.

    +

    Any torrent_status object whose handle member is not referring to +a valid torrent are ignored.

    +

    The intended use of these functions is to start off by calling +get_torrent_status() to get a list of all torrents that match your +criteria. Then call refresh_torrent_status() on that list. This +will only refresh the status for the torrents in your list, and thus +ignore all other torrents you might be running. This may save a +significant amount of time, especially if the number of torrents you're +interested in is small. In order to keep your list of interested +torrents up to date, you can either call get_torrent_status() from +time to time, to include torrents you might have become interested in +since the last time. In order to stop refreshing a certain torrent, +simply remove it from the list.

    +[report issue]
    +
    +

    post_torrent_updates()

    +
    +void post_torrent_updates (status_flags_t flags = status_flags_t::all());
    +
    +

    This functions instructs the session to post the state_update_alert, +containing the status of all torrents whose state changed since the +last time this function was called.

    +

    Only torrents who has the state subscription flag set will be +included. This flag is on by default. See add_torrent_params. +the flags argument is the same as for torrent_handle::status(). +see status_flags_t in torrent_handle.

    +[report issue]
    +
    +

    post_session_stats()

    +
    +void post_session_stats ();
    +
    +

    This function will post a session_stats_alert object, containing a +snapshot of the performance counters from the internals of libtorrent. +To interpret these counters, query the session via +session_stats_metrics().

    +

    For more information, see the session statistics section.

    +[report issue]
    +
    +

    post_dht_stats()

    +
    +void post_dht_stats ();
    +
    +

    This will cause a dht_stats_alert to be posted.

    + +[report issue]
    +
    +

    get_torrents() find_torrent()

    +
    +std::vector<torrent_handle> get_torrents () const;
    +torrent_handle find_torrent (sha1_hash const& info_hash) const;
    +
    +

    find_torrent() looks for a torrent with the given info-hash. In +case there is such a torrent in the session, a torrent_handle to that +torrent is returned. In case the torrent cannot be found, an invalid +torrent_handle is returned.

    +

    See torrent_handle::is_valid() to know if the torrent was found or +not.

    +

    get_torrents() returns a vector of torrent_handles to all the +torrents currently in the session.

    + +[report issue]
    +
    +

    add_torrent() async_add_torrent()

    +
    +void async_add_torrent (add_torrent_params const& params);
    +torrent_handle add_torrent (add_torrent_params const& params);
    +torrent_handle add_torrent (add_torrent_params&& params);
    +torrent_handle add_torrent (add_torrent_params&& params, error_code& ec);
    +void async_add_torrent (add_torrent_params&& params);
    +torrent_handle add_torrent (add_torrent_params const& params, error_code& ec);
    +
    +

    You add torrents through the add_torrent() function where you give an +object with all the parameters. The add_torrent() overloads will block +until the torrent has been added (or failed to be added) and returns +an error code and a torrent_handle. In order to add torrents more +efficiently, consider using async_add_torrent() which returns +immediately, without waiting for the torrent to add. Notification of +the torrent being added is sent as add_torrent_alert.

    +

    The overload that does not take an error_code throws an exception on +error and is not available when building without exception support. +The torrent_handle returned by add_torrent() can be used to retrieve +information about the torrent's progress, its peers etc. It is also +used to abort a torrent.

    +

    If the torrent you are trying to add already exists in the session (is +either queued for checking, being checked or downloading) +add_torrent() will throw system_error which derives from +std::exception unless duplicate_is_error is set to false. In that +case, add_torrent() will return the handle to the existing torrent.

    +

    The add_torrent_params class has a flags field. It can be used to +control what state the new torrent will be added in. Common flags to +want to control are torrent_flags::paused and +torrent_flags::auto_managed. In order to add a magnet link that will +just download the metadata, but no payload, set the +torrent_flags::upload_mode flag.

    + + +[report issue]
    +
    +

    pause() resume() is_paused()

    +
    +void resume ();
    +bool is_paused () const;
    +void pause ();
    +
    +

    Pausing the session has the same effect as pausing every torrent in +it, except that torrents will not be resumed by the auto-manage +mechanism. Resuming will restore the torrents to their previous paused +state. i.e. the session pause state is separate from the torrent pause +state. A torrent is inactive if it is paused or if the session is +paused.

    +[report issue]
    +
    +

    get_cache_info()

    +
    +void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    +
    +

    Fills in the cache_status struct with information about the given torrent. +If flags is session::disk_cache_no_pieces the cache_status::pieces field +will not be set. This may significantly reduce the cost of this call.

    + + +[report issue]
    +
    +

    is_dht_running() get_dht_settings() set_dht_settings()

    +
    +dht::dht_settings get_dht_settings () const;
    +bool is_dht_running () const;
    +void set_dht_settings (dht::dht_settings const& settings);
    +
    +

    set_dht_settings sets some parameters available to the dht node. +See dht_settings for more information.

    +

    is_dht_running() returns true if the DHT support has been started +and false +otherwise.

    +

    get_dht_settings() returns the current settings

    +[report issue]
    +
    +

    set_dht_storage()

    +
    +void set_dht_storage (dht::dht_storage_constructor_type sc);
    +
    +

    set_dht_storage set a dht custom storage constructor function +to be used internally when the dht is created.

    +

    Since the dht storage is a critical component for the dht behavior, +this function will only be effective the next time the dht is started. +If you never touch this feature, a default map-memory based storage +is used.

    +

    If you want to make sure the dht is initially created with your +custom storage, create a session with the setting +settings_pack::enable_dht to false, set your constructor function +and call apply_settings with settings_pack::enable_dht to true.

    +[report issue]
    +
    +

    add_dht_node()

    +
    +void add_dht_node (std::pair<std::string, int> const& node);
    +
    +

    add_dht_node takes a host name and port pair. That endpoint will be +pinged, and if a valid DHT reply is received, the node will be added to +the routing table.

    +[report issue]
    +
    +

    dht_get_item()

    +
    +void dht_get_item (sha1_hash const& target);
    +
    +

    query the DHT for an immutable item at the target hash. +the result is posted as a dht_immutable_item_alert.

    +[report issue]
    +
    +

    dht_get_item()

    +
    +void dht_get_item (std::array<char, 32> key
    +      , std::string salt = std::string());
    +
    +

    query the DHT for a mutable item under the public key key. +this is an ed25519 key. salt is optional and may be left +as an empty string if no salt is to be used. +if the item is found in the DHT, a dht_mutable_item_alert is +posted.

    +[report issue]
    +
    +

    dht_put_item()

    +
    +sha1_hash dht_put_item (entry data);
    +
    +

    store the given bencoded data as an immutable item in the DHT. +the returned hash is the key that is to be used to look the item +up again. It's just the SHA-1 hash of the bencoded form of the +structure.

    +[report issue]
    +
    +

    dht_put_item()

    +
    +void dht_put_item (std::array<char, 32> key
    +      , std::function<void(entry&, std::array<char, 64>&
    +      , std::int64_t&, std::string const&)> cb
    +      , std::string salt = std::string());
    +
    +

    store a mutable item. The key is the public key the blob is +to be stored under. The optional salt argument is a string that +is to be mixed in with the key when determining where in the DHT +the value is to be stored. The callback function is called from within +the libtorrent network thread once we've found where to store the blob, +possibly with the current value stored under the key. +The values passed to the callback functions are:

    +
    +
    entry& value
    +
    the current value stored under the key (may be empty). Also expected +to be set to the value to be stored by the function.
    +
    std::array<char,64>& signature
    +
    the signature authenticating the current value. This may be zeros +if there is currently no value stored. The function is expected to +fill in this buffer with the signature of the new value to store. +To generate the signature, you may want to use the +sign_mutable_item function.
    +
    std::int64_t& seq
    +
    current sequence number. May be zero if there is no current value. +The function is expected to set this to the new sequence number of +the value that is to be stored. Sequence numbers must be monotonically +increasing. Attempting to overwrite a value with a lower or equal +sequence number will fail, even if the signature is correct.
    +
    std::string const& salt
    +
    this is the salt that was used for this put call.
    +
    +

    Since the callback function cb is called from within libtorrent, +it is critical to not perform any blocking operations. Ideally not +even locking a mutex. Pass any data required for this function along +with the function object's context and make the function entirely +self-contained. The only reason data blob's value is computed +via a function instead of just passing in the new value is to avoid +race conditions. If you want to update the value in the DHT, you +must first retrieve it, then modify it, then write it back. The way +the DHT works, it is natural to always do a lookup before storing and +calling the callback in between is convenient.

    + +[report issue]
    +
    +

    dht_get_peers() dht_announce()

    +
    +void dht_announce (sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {});
    +void dht_get_peers (sha1_hash const& info_hash);
    +
    +

    dht_get_peers() will issue a DHT get_peer request to the DHT for the +specified info-hash. The response (the peers) will be posted back in a +dht_get_peers_reply_alert.

    +

    dht_announce() will issue a DHT announce request to the DHT to the +specified info-hash, advertising the specified port. If the port is +left at its default, 0, the port will be implied by the DHT message's +source port (which may improve connectivity through a NAT).

    +

    Both these functions are exposed for advanced custom use of the DHT. +All torrents eligible to be announce to the DHT will be automatically, +by libtorrent.

    +

    For possible flags, see announce_flags_t.

    +[report issue]
    +
    +

    dht_live_nodes()

    +
    +void dht_live_nodes (sha1_hash const& nid);
    +
    +

    Retrieve all the live DHT (identified by nid) nodes. All the +nodes id and endpoint will be returned in the list of nodes in the +alert dht_live_nodes_alert. +Since this alert is a response to an explicit call, it will always be +posted, regardless of the alert mask.

    +[report issue]
    +
    +

    dht_sample_infohashes()

    +
    +void dht_sample_infohashes (udp::endpoint const& ep, sha1_hash const& target);
    +
    +

    Query the DHT node specified by ep to retrieve a sample of the +info-hashes that the node currently have in their storage. +The target is included for iterative lookups so that indexing nodes +can perform a key space traversal with a single RPC per node by adjusting +the target value for each RPC. It has no effect on the returned sample value. +The result is posted as a dht_sample_infohashes_alert.

    +[report issue]
    +
    +

    dht_direct_request()

    +
    +void dht_direct_request (udp::endpoint const& ep, entry const& e, void* userdata = nullptr);
    +
    +

    Send an arbitrary DHT request directly to the specified endpoint. This +function is intended for use by plugins. When a response is received +or the request times out, a dht_direct_response_alert will be posted +with the response (if any) and the userdata pointer passed in here. +Since this alert is a response to an explicit call, it will always be +posted, regardless of the alert mask.

    +[report issue]
    +
    +

    add_extension()

    +
    +void add_extension (std::function<std::shared_ptr<torrent_plugin>(
    +      torrent_handle const&, void*)> ext);
    +void add_extension (std::shared_ptr<plugin> ext);
    +
    +

    This function adds an extension to this session. The argument is a +function object that is called with a torrent_handle and which should +return a std::shared_ptr<torrent_plugin>. To write custom +plugins, see libtorrent plugins. For the typical bittorrent client +all of these extensions should be added. The main plugins implemented +in libtorrent are:

    +
    +
    uTorrent metadata
    +
    Allows peers to download the metadata (.torrent files) from the swarm +directly. Makes it possible to join a swarm with just a tracker and +info-hash.
    +
    +
    +#include <libtorrent/extensions/ut_metadata.hpp>
    +ses.add_extension(&lt::create_ut_metadata_plugin);
    +
    +
    +
    uTorrent peer exchange
    +
    Exchanges peers between clients.
    +
    +
    +#include <libtorrent/extensions/ut_pex.hpp>
    +ses.add_extension(&lt::create_ut_pex_plugin);
    +
    +
    +
    smart ban plugin
    +
    A plugin that, with a small overhead, can ban peers +that sends bad data with very high accuracy. Should +eliminate most problems on poisoned torrents.
    +
    +
    +#include <libtorrent/extensions/smart_ban.hpp>
    +ses.add_extension(&lt::create_smart_ban_plugin);
    +
    + +[report issue]
    +
    +

    get_ip_filter() set_ip_filter()

    +
    +void set_ip_filter (ip_filter const& f);
    +ip_filter get_ip_filter () const;
    +
    +

    Sets a filter that will be used to reject and accept incoming as well +as outgoing connections based on their originating ip address. The +default filter will allow connections to any ip address. To build a +set of rules for which addresses are accepted and not, see ip_filter.

    +

    Each time a peer is blocked because of the IP filter, a +peer_blocked_alert is generated. get_ip_filter() Returns the +ip_filter currently in the session. See ip_filter.

    +[report issue]
    +
    +

    set_port_filter()

    +
    +void set_port_filter (port_filter const& f);
    +
    +

    apply port_filter f to incoming and outgoing peers. a port filter +will reject making outgoing peer connections to certain remote ports. +The main intention is to be able to avoid triggering certain +anti-virus software by connecting to SMTP, FTP ports.

    + + +[report issue]
    +
    +

    listen_port() ssl_listen_port() is_listening()

    +
    +unsigned short listen_port () const;
    +bool is_listening () const;
    +unsigned short ssl_listen_port () const;
    +
    +

    is_listening() will tell you whether or not the session has +successfully opened a listening port. If it hasn't, this function will +return false, and then you can set a new +settings_pack::listen_interfaces to try another interface and port to +bind to.

    +

    listen_port() returns the port we ended up listening on.

    + +[report issue]
    +
    +

    get_peer_class_filter() set_peer_class_filter()

    +
    +ip_filter get_peer_class_filter () const;
    +void set_peer_class_filter (ip_filter const& f);
    +
    +

    Sets the peer class filter for this session. All new peer connections +will take this into account and be added to the peer classes specified +by this filter, based on the peer's IP address.

    +

    The ip-filter essentially maps an IP -> uint32. Each bit in that 32 +bit integer represents a peer class. The least significant bit +represents class 0, the next bit class 1 and so on.

    +

    For more info, see ip_filter.

    +

    For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 +belong to their own peer class, apply the following filter:

    +
    +ip_filter f = ses.get_peer_class_filter();
    +peer_class_t my_class = ses.create_peer_class("200.1.x.x IP range");
    +f.add_rule(make_address("200.1.1.0"), make_address("200.1.255.255")
    +        , 1 << static_cast<std::uint32_t>(my_class));
    +ses.set_peer_class_filter(f);
    +
    +

    This setting only applies to new connections, it won't affect existing +peer connections.

    +

    This function is limited to only peer class 0-31, since there are only +32 bits in the IP range mapping. Only the set bits matter; no peer +class will be removed from a peer as a result of this call, peer +classes are only added.

    +

    The peer_class argument cannot be greater than 31. The bitmasks +representing peer classes in the peer_class_filter are 32 bits.

    +

    The get_peer_class_filter() function returns the current filter.

    +

    For more information, see peer classes.

    + +[report issue]
    +
    +

    get_peer_class_type_filter() set_peer_class_type_filter()

    +
    +void set_peer_class_type_filter (peer_class_type_filter const& f);
    +peer_class_type_filter get_peer_class_type_filter () const;
    +
    +

    Sets and gets the peer class type filter. This is controls automatic +peer class assignments to peers based on what kind of socket it is.

    +

    It does not only support assigning peer classes, it also supports +removing peer classes based on socket type.

    +

    The order of these rules being applied are:

    +
      +
    1. peer-class IP filter
    2. +
    3. peer-class type filter, removing classes
    4. +
    5. peer-class type filter, adding classes
    6. +
    +

    For more information, see peer classes.

    +[report issue]
    +
    +

    create_peer_class()

    +
    +peer_class_t create_peer_class (char const* name);
    +
    +

    Creates a new peer class (see peer classes) with the given name. The +returned integer is the new peer class identifier. Peer classes may +have the same name, so each invocation of this function creates a new +class and returns a unique identifier.

    +

    Identifiers are assigned from low numbers to higher. So if you plan on +using certain peer classes in a call to set_peer_class_filter(), +make sure to create those early on, to get low identifiers.

    +

    For more information on peer classes, see peer classes.

    +[report issue]
    +
    +

    delete_peer_class()

    +
    +void delete_peer_class (peer_class_t cid);
    +
    +

    This call dereferences the reference count of the specified peer +class. When creating a peer class it's automatically referenced by 1. +If you want to recycle a peer class, you may call this function. You +may only call this function once per peer class you create. +Calling it more than once for the same class will lead to memory +corruption.

    +

    Since peer classes are reference counted, this function will not +remove the peer class if it's still assigned to torrents or peers. It +will however remove it once the last peer and torrent drops their +references to it.

    +

    There is no need to call this function for custom peer classes. All +peer classes will be properly destructed when the session object +destructs.

    +

    For more information on peer classes, see peer classes.

    + +[report issue]
    +
    +

    set_peer_class() get_peer_class()

    +
    +void set_peer_class (peer_class_t cid, peer_class_info const& pci);
    +peer_class_info get_peer_class (peer_class_t cid) const;
    +
    +

    These functions queries information from a peer class and updates the +configuration of a peer class, respectively.

    +

    cid must refer to an existing peer class. If it does not, the +return value of get_peer_class() is undefined.

    +

    set_peer_class() sets all the information in the +peer_class_info object in the specified peer class. There is no +option to only update a single property.

    +

    A peer or torrent belonging to more than one class, the highest +priority among any of its classes is the one that is taken into +account.

    +

    For more information, see peer classes.

    +[report issue]
    +
    +

    remove_torrent()

    +
    +void remove_torrent (const torrent_handle& h, remove_flags_t options = {});
    +
    +

    remove_torrent() will close all peer connections associated with +the torrent and tell the tracker that we've stopped participating in +the swarm. This operation cannot fail. When it completes, you will +receive a torrent_removed_alert.

    +

    The optional second argument options can be used to delete all the +files downloaded by this torrent. To do so, pass in the value +session_handle::delete_files. The removal of the torrent is asynchronous, +there is no guarantee that adding the same torrent immediately after +it was removed will not throw a system_error exception. Once +the torrent is deleted, a torrent_deleted_alert is posted.

    +

    Note that when a queued or downloading torrent is removed, its position +in the download queue is vacated and every subsequent torrent in the +queue has their queue positions updated. This can potentially cause a +large state_update to be posted. When removing all torrents, it is +advised to remove them from the back of the queue, to minimize the +shifting.

    + +[report issue]
    +
    +

    get_settings() apply_settings()

    +
    +void apply_settings (settings_pack const& s);
    +settings_pack get_settings () const;
    +void apply_settings (settings_pack&& s);
    +
    +

    Applies the settings specified by the settings_pack s. This is an +asynchronous operation that will return immediately and actually apply +the settings to the main thread of libtorrent some time later.

    + + +[report issue]
    +
    +

    wait_for_alert() set_alert_notify() pop_alerts()

    +
    +void pop_alerts (std::vector<alert*>* alerts);
    +alert* wait_for_alert (time_duration max_wait);
    +void set_alert_notify (std::function<void()> const& fun);
    +
    +

    Alerts is the main mechanism for libtorrent to report errors and +events. pop_alerts fills in the vector passed to it with pointers +to new alerts. The session still owns these alerts and they will stay +valid until the next time pop_alerts is called. You may not delete +the alert objects.

    +

    It is safe to call pop_alerts from multiple different threads, as +long as the alerts themselves are not accessed once another thread +calls pop_alerts. Doing this requires manual synchronization +between the popping threads.

    +

    wait_for_alert will block the current thread for max_wait time +duration, or until another alert is posted. If an alert is available +at the time of the call, it returns immediately. The returned alert +pointer is the head of the alert queue. wait_for_alert does not +pop alerts from the queue, it merely peeks at it. The returned alert +will stay valid until pop_alerts is called twice. The first time +will pop it and the second will free it.

    +

    If there is no alert in the queue and no alert arrives within the +specified timeout, wait_for_alert returns nullptr.

    +

    In the python binding, wait_for_alert takes the number of +milliseconds to wait as an integer.

    +

    The alert queue in the session will not grow indefinitely. Make sure +to pop periodically to not miss notifications. To control the max +number of alerts that's queued by the session, see +settings_pack::alert_queue_size.

    +

    Some alerts are considered so important that they are posted even when +the alert queue is full. Some alerts are considered mandatory and cannot +be disabled by the alert_mask. For instance, +save_resume_data_alert and save_resume_data_failed_alert are always +posted, regardless of the alert mask.

    +

    To control which alerts are posted, set the alert_mask +(settings_pack::alert_mask).

    +

    If the alert queue fills up to the point where alerts are dropped, this +will be indicated by a alerts_dropped_alert, which contains a bitmask +of which types of alerts were dropped. Generally it is a good idea to +make sure the alert queue is large enough, the alert_mask doesn't have +unnecessary categories enabled and to call pop_alert() frequently, to +avoid alerts being dropped.

    +

    the set_alert_notify function lets the client set a function object +to be invoked every time the alert queue goes from having 0 alerts to +1 alert. This function is called from within libtorrent, it may be the +main thread, or it may be from within a user call. The intention of +of the function is that the client wakes up its main thread, to poll +for more alerts using pop_alerts(). If the notify function fails +to do so, it won't be called again, until pop_alerts is called for +some other reason. For instance, it could signal an eventfd, post a +message to an HWND or some other main message pump. The actual +retrieval of alerts should not be done in the callback. In fact, the +callback should not block. It should not perform any expensive work. +It really should just notify the main application thread.

    +

    The type of an alert is returned by the polymorphic function +alert::type() but can also be queries from a concrete type via +T::alert_type, as a static constant.

    + +[report issue]
    +
    +

    add_port_mapping() delete_port_mapping()

    +
    +void delete_port_mapping (port_mapping_t handle);
    +std::vector<port_mapping_t> add_port_mapping (portmap_protocol t, int external_port, int local_port);
    +
    +

    add_port_mapping adds one or more port forwards on UPnP and/or NAT-PMP, +whichever is enabled. A mapping is created for each listen socket +in the session. The return values are all handles referring to the +port mappings that were just created. Pass them to delete_port_mapping() +to remove them.

    +[report issue]
    +
    +

    reopen_network_sockets()

    +
    +void reopen_network_sockets (reopen_network_flags_t options = reopen_map_ports);
    +
    +

    Instructs the session to reopen all listen and outgoing sockets.

    +

    It's useful in the case your platform doesn't support the built in +IP notifier mechanism, or if you have a better more reliable way to +detect changes in the IP routing table.

    +[report issue]
    +
    +

    native_handle()

    +
    +std::shared_ptr<aux::session_impl> native_handle () const;
    +
    +

    This function is intended only for use by plugins. This type does +not have a stable API and should be relied on as little as possible.

    +[report issue]
    +
    save_settings
    +
    saves settings (i.e. the settings_pack)
    +
    +[report issue]
    +
    save_dht_settings
    +
    saves dht_settings
    +
    +[report issue]
    +
    save_dht_state
    +
    saves dht state such as nodes and node-id, possibly accelerating +joining the DHT if provided at next session startup.
    +
    + + +[report issue]
    +
    global_peer_class_id tcp_peer_class_id local_peer_class_id
    +
    built-in peer classes
    +
    +[report issue]
    +
    delete_files
    +
    delete the files belonging to the torrent from disk. +including the part-file, if there is one
    +
    +[report issue]
    +
    delete_partfile
    +
    delete just the part-file associated with this torrent
    +
    +[report issue]
    +
    add_default_plugins
    +
    this will add common extensions like ut_pex, ut_metadata, lt_tex +smart_ban and possibly others.
    +
    + +[report issue]
    +
    udp tcp
    +
    protocols used by add_port_mapping()
    +
    +[report issue]
    +
    reopen_map_ports
    +
    This option indicates if the ports are mapped using natpmp +and upnp. If mapping was already made, they are deleted and added +again. This only works if natpmp and/or upnp are configured to be +enable.
    +
    +[report issue]
    +
    +
    +

    session_proxy

    +

    Declared in "libtorrent/session.hpp"

    +

    this is a holder for the internal session implementation object. Once the +session destruction is explicitly initiated, this holder is used to +synchronize the completion of the shutdown. The lifetime of this object +may outlive session, causing the session destructor to not block. The +session_proxy destructor will block however, until the underlying session +is done shutting down.

    +
    +class session_proxy
    +{
    +   session_proxy (session_proxy const&);
    +   session_proxy& operator= (session_proxy const&);
    +   ~session_proxy ();
    +   session_proxy& operator= (session_proxy&&) noexcept;
    +   session_proxy ();
    +   session_proxy (session_proxy&&) noexcept;
    +};
    +
    + + +[report issue]
    +

    ~session_proxy() session_proxy() operator=()

    +
    +session_proxy (session_proxy const&);
    +session_proxy& operator= (session_proxy const&);
    +~session_proxy ();
    +session_proxy& operator= (session_proxy&&) noexcept;
    +session_proxy ();
    +session_proxy (session_proxy&&) noexcept;
    +
    +

    default constructor, does not refer to any session +implementation object.

    +[report issue]
    +
    +
    +

    session_params

    +

    Declared in "libtorrent/session.hpp"

    +

    The session_params is a parameters pack for configuring the session +before it's started.

    +
    +struct session_params
    +{
    +   explicit session_params (settings_pack&& sp);
    +   session_params ();
    +   explicit session_params (settings_pack const& sp);
    +   session_params (settings_pack const& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +   session_params (settings_pack&& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +
    +   settings_pack settings;
    +   std::vector<std::shared_ptr<plugin>> extensions;
    +   dht::dht_settings dht_settings;
    +   dht::dht_state dht_state;
    +   dht::dht_storage_constructor_type dht_storage_constructor;
    +};
    +
    +[report issue]
    +

    session_params()

    +
    +explicit session_params (settings_pack&& sp);
    +session_params ();
    +explicit session_params (settings_pack const& sp);
    +
    +

    This constructor can be used to start with the default plugins +(ut_metadata, ut_pex and smart_ban). The default values in the +settings is to start the default features like upnp, NAT-PMP, +and dht for example.

    +[report issue]
    +
    +

    session_params()

    +
    +session_params (settings_pack const& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +session_params (settings_pack&& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +
    +

    This constructor helps to configure the set of initial plugins +to be added to the session before it's started.

    +[report issue]
    +
    +
    +

    session

    +

    Declared in "libtorrent/session.hpp"

    +

    The session holds all state that spans multiple torrents. Among other +things it runs the network loop and manages all torrents. Once it's +created, the session object will spawn the main thread that will do all +the work. The main thread will be idle as long it doesn't have any +torrents to participate in.

    +

    You have some control over session configuration through the +session_handle::apply_settings() member function. To change one or more +configuration options, create a settings_pack. object and fill it with +the settings to be set and pass it in to session::apply_settings().

    +

    see apply_settings().

    +
    +class session : public session_handle
    +{
    +   explicit session (session_params&& params);
    +   session ();
    +   explicit session (session_params const& params);
    +   session (session_params const& params, io_service& ios);
    +   session (session_params&& params, io_service& ios);
    +   session (settings_pack&& pack
    +      , session_flags_t const flags = add_default_plugins);
    +   session (settings_pack const& pack
    +      , session_flags_t const flags = add_default_plugins);
    +   session& operator= (session&&) = default;
    +   session (session&&) = default;
    +   session& operator= (session const&) = delete;
    +   session (session const&) = delete;
    +   session (settings_pack&& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +   session (settings_pack const& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +   ~session ();
    +   session_proxy abort ();
    +};
    +
    +[report issue]
    +

    session()

    +
    +explicit session (session_params&& params);
    +session ();
    +explicit session (session_params const& params);
    +
    +

    Constructs the session objects which acts as the container of torrents. +In order to avoid a race condition between starting the session and +configuring it, you can pass in a session_params object. Its settings +will take effect before the session starts up.

    +[report issue]
    +
    +

    session()

    +
    +session (session_params const& params, io_service& ios);
    +session (session_params&& params, io_service& ios);
    +
    +

    Overload of the constructor that takes an external io_service to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_service, or low resource +systems where additional threads are expensive and sharing an +io_service with other events is fine.

    +
    +

    Warning

    +

    The session object does not cleanly terminate with an external +io_service. The io_service::run() call _must_ have returned +before it's safe to destruct the session. Which means you MUST +call session::abort() and save the session_proxy first, then +destruct the session object, then sync with the io_service, then +destruct the session_proxy object.

    +
    +[report issue]
    +
    +

    session()

    +
    +session (settings_pack&& pack
    +      , session_flags_t const flags = add_default_plugins);
    +session (settings_pack const& pack
    +      , session_flags_t const flags = add_default_plugins);
    +
    +

    Constructs the session objects which acts as the container of torrents. +It provides configuration options across torrents (such as rate limits, +disk cache, ip filter etc.). In order to avoid a race condition between +starting the session and configuring it, you can pass in a +settings_pack object. Its settings will take effect before the session +starts up.

    +

    The flags parameter can be used to start default features (UPnP & +NAT-PMP) and default plugins (ut_metadata, ut_pex and smart_ban). The +default is to start those features. If you do not want them to start, +pass 0 as the flags parameter.

    + +[report issue]
    +
    +

    session() operator=()

    +
    +session& operator= (session&&) = default;
    +session (session&&) = default;
    +
    +

    movable

    + +[report issue]
    +
    +

    session() operator=()

    +
    +session& operator= (session const&) = delete;
    +session (session const&) = delete;
    +
    +

    non-copyable

    +[report issue]
    +
    +

    session()

    +
    +session (settings_pack&& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +session (settings_pack const& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +
    +

    overload of the constructor that takes an external io_service to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_service, or low resource +systems where additional threads are expensive and sharing an +io_service with other events is fine.

    +
    +

    Warning

    +

    The session object does not cleanly terminate with an external +io_service. The io_service::run() call _must_ have returned +before it's safe to destruct the session. Which means you MUST +call session::abort() and save the session_proxy first, then +destruct the session object, then sync with the io_service, then +destruct the session_proxy object.

    +
    +[report issue]
    +
    +

    ~session()

    +
    +~session ();
    +
    +

    The destructor of session will notify all trackers that our torrents +have been shut down. If some trackers are down, they will time out. +All this before the destructor of session returns. So, it's advised +that any kind of interface (such as windows) are closed before +destructing the session object. Because it can take a few second for +it to finish. The timeout can be set with apply_settings().

    +[report issue]
    +
    +

    abort()

    +
    +session_proxy abort ();
    +
    +

    In case you want to destruct the session asynchronously, you can +request a session destruction proxy. If you don't do this, the +destructor of the session object will block while the trackers are +contacted. If you keep one session_proxy to the session when +destructing it, the destructor will not block, but start to close down +the session, the destructor of the proxy will then synchronize the +threads. So, the destruction of the session is performed from the +session destructor call until the session_proxy destructor +call. The session_proxy does not have any operations on it (since +the session is being closed down, no operations are allowed on it). +The only valid operation is calling the destructor:

    +
    +class session_proxy
    +{
    +public:
    +        session_proxy();
    +        ~session_proxy()
    +};
    +
    +[report issue]
    +
    +
    +

    session_stats_metrics()

    +

    Declared in "libtorrent/session_stats.hpp"

    +
    +std::vector<stats_metric> session_stats_metrics ();
    +
    +

    This free function returns the list of available metrics exposed by +libtorrent's statistics API. Each metric has a name and a value index. +The value index is the index into the array in session_stats_alert where +this metric's value can be found when the session stats is sampled (by +calling post_session_stats()).

    +[report issue]
    +
    +

    find_metric_idx()

    +

    Declared in "libtorrent/session_stats.hpp"

    +
    +int find_metric_idx (string_view name);
    +
    +

    given a name of a metric, this function returns the counter index of it, +or -1 if it could not be found. The counter index is the index into the +values array returned by session_stats_alert.

    +[report issue]
    +
    +

    read_session_params()

    +

    Declared in "libtorrent/session.hpp"

    +
    +session_params read_session_params (bdecode_node const& e
    +   , save_state_flags_t flags = save_state_flags_t::all());
    +
    +

    This function helps to construct a session_params from a +bencoded data generated by session_handle::save_state

    +[report issue]
    +
    +

    enum metric_type_t

    +

    Declared in "libtorrent/session_stats.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    counter0 
    gauge1 
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Settings.html b/docs/reference-Settings.html new file mode 100644 index 0000000..219d9b0 --- /dev/null +++ b/docs/reference-Settings.html @@ -0,0 +1,4578 @@ + + + + + + +reference-Settings.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Settings

    + +

    You have some control over session configuration through the session::apply_settings() +member function. To change one or more configuration options, create a settings_pack +object and fill it with the settings to be set and pass it in to session::apply_settings().

    +

    The settings_pack object is a collection of settings updates that are applied +to the session when passed to session::apply_settings(). It's empty when +constructed.

    +

    You have control over proxy and authorization settings and also the user-agent +that will be sent to the tracker. The user-agent will also be used to identify the +client with other peers.

    +

    Each configuration option is named with an enum value inside the +settings_pack class. These are the available settings:

    +[report issue]
    +

    settings_pack

    +

    Declared in "libtorrent/settings_pack.hpp"

    +

    The settings_pack struct, contains the names of all settings as +enum values. These values are passed in to the set_str(), +set_int(), set_bool() functions, to specify the setting to +change.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    user_agentstringlibtorrent/
    +

    this is the client identification to the tracker. The recommended +format of this string is: "client-name/client-version +libtorrent/libtorrent-version". This name will not only be used when +making HTTP requests, but also when sending extended headers to +peers that support that extension. It may not contain r or n

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_ipstringnullptr
    +

    announce_ip is the ip address passed along to trackers as the +&ip= parameter. If left as the default, that parameter is +omitted.

    +
    +

    Note

    +

    This setting is only meant for very special cases where a seed is +running on the same host as the tracker, and the tracker accepts +the IP parameter (which normal trackers don't). Do not set this +option unless you also control the tracker.

    +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    handshake_client_versionstringnullptr
    +

    this is the client name and version identifier sent to peers in the +handshake message. If this is an empty string, the user_agent is +used instead. This string must be a UTF-8 encoded unicode string.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    outgoing_interfacesstring 
    +

    This controls which IP address outgoing TCP peer connections are bound +to, in addition to controlling whether such connections are also +bound to a specific network interface/adapter (bind-to-device). +This string is a comma-separated list of IP addresses and +interface names. An empty string will not bind TCP sockets to a +device, and let the network stack assign the local address. A +list of names will be used to bind outgoing TCP sockets in a +round-robin fashion. An IP address will simply be used to bind() +the socket. An interface name will attempt to bind the socket to +that interface. If that fails, or is unsupported, one of the IP +addresses configured for that interface is used to bind() the +socket to. If the interface or adapter doesn't exist, the +outgoing peer connection will fail with an error message suggesting +the device cannot be found. Adapter names on Unix systems are of +the form "eth0", "eth1", "tun0", etc. This may be useful for +clients that are multi-homed. Binding an outgoing connection to a +local IP does not necessarily make the connection via the +associated NIC/Adapter.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_interfacesstring0.0.0.0:6881,[::]:6881
    +

    a comma-separated list of (IP or device name, port) pairs. These are +the listen ports that will be opened for accepting incoming uTP and +TCP peer connections. These are also used for outgoing uTP and UDP +tracker connections and DHT nodes.

    +

    It is possible to listen on multiple interfaces and +multiple ports. Binding to port 0 will make the operating system +pick the port.

    +
    +

    Note

    +

    There are reasons to stick to the same port across sessions, +which would mean only using port 0 on the first start, and +recording the port that was picked for subsequent startups. +Trackers, the DHT and other peers will remember the port they see +you use and hand that port out to other peers trying to connect +to you, as well as trying to connect to you themselves.

    +
    +

    A port that has an "s" suffix will accept SSL peer connections. (note +that SSL sockets are only available in builds with SSL support)

    +

    A port that has an "l" suffix will be considered a local network. +i.e. it's assumed to only be able to reach hosts in the same local +network as the IP address (based on the netmask associated with the +IP, queried from the operating system).

    +

    if binding fails, the listen_failed_alert is posted. Once a +socket binding succeeds (if it does), the listen_succeeded_alert +is posted. There may be multiple failures before a success.

    +

    If a device name that does not exist is configured, no listen +socket will be opened for that interface. If this is the only +interface configured, it will be as if no listen ports are +configured.

    +

    If no listen ports are configured (e.g. listen_interfaces is an +empty string), networking will be disabled. No DHT will start, no +outgoing uTP or tracker connections will be made. No incoming TCP +or uTP connections will be accepted. (outgoing TCP connections +will still be possible, depending on +settings_pack::outgoing_interfaces).

    +

    For example: +[::1]:8888 - will only accept connections on the IPv6 loopback +address on port 8888.

    +

    eth0:4444,eth1:4444 - will accept connections on port 4444 on +any IP address bound to device eth0 or eth1.

    +

    [::]:0s - will accept SSL connections on a port chosen by the +OS. And not accept non-SSL connections at all.

    +

    0.0.0.0:6881,[::]:6881 - binds to all interfaces on port 6881.

    +

    10.0.1.13:6881l - binds to the local IP address, port 6881, but +only allow talking to peers on the same local network. The netmask +is queried from the operating system. Interfaces marked l are +not announced to trackers, unless the tracker is also on the same +local network.

    +

    Windows OS network adapter device name must be specified with GUID. +It can be obtained from "netsh lan show interfaces" command output. +GUID must be uppercased string embraced in curly brackets. +{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777 - will accept +connections on port 7777 on adapter with this GUID.

    +

    For more information, see the Multi-homed hosts section.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_hostnamestring 
    +

    when using a proxy, this is the hostname where the proxy is running +see proxy_type. Note that when using a proxy, the +settings_pack::listen_interfaces setting is overridden and only a +single interface is created, just to contact the proxy. This +means a proxy cannot be combined with SSL torrents or multiple +listen interfaces. This proxy listen interface will not accept +incoming TCP connections, will not map ports with any gateway and +will not enable local service discovery. All traffic is supposed +to be channeled through the proxy.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    proxy_usernamestring 
    proxy_passwordstring 
    +

    when using a proxy, these are the credentials (if any) to use when +connecting to it. see proxy_type

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    i2p_hostnamestring 
    +

    sets the i2p SAM bridge to connect to. set the port with the +i2p_port setting.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_fingerprintstring-LT1290-
    +

    this is the fingerprint for the client. It will be used as the +prefix to the peer_id. If this is 20 bytes (or longer) it will be +truncated to 20 bytes and used as the entire peer-id

    +

    There is a utility function, generate_fingerprint() that can be used +to generate a standard client peer ID fingerprint prefix.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_bootstrap_nodesstringdht.libtorrent.org:25401
    +

    This is a comma-separated list of IP port-pairs. They will be added +to the DHT node (if it's enabled) as back-up nodes in case we don't +know of any.

    +

    Changing these after the DHT has been started may not have any +effect until the DHT is restarted.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_multiple_connections_per_ipboolfalse
    +

    determines if connections from the same IP address as existing +connections should be rejected or not. Rejecting multiple connections +from the same IP address will prevent abusive +behavior by peers. The logic for determining whether connections are +to the same peer is more complicated with this enabled, and more +likely to fail in some edge cases. It is not recommended to enable +this feature.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    send_redundant_havebooltrue
    +

    send_redundant_have controls if have messages will be sent to +peers that already have the piece. This is typically not necessary, +but it might be necessary for collecting statistics in some cases.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_dht_as_fallbackboolfalse
    +

    use_dht_as_fallback determines how the DHT is used. If this is +true, the DHT will only be used for torrents where all trackers in +its tracker list has failed. Either by an explicit error message or +a time out. If this is false, the DHT is used regardless of if the +trackers fail or not.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    upnp_ignore_nonroutersboolfalse
    +

    upnp_ignore_nonrouters indicates whether or not the UPnP +implementation should ignore any broadcast response from a device +whose address is not on our subnet. i.e. +it's a way to not talk to other people's routers by mistake.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_parole_modebooltrue
    +

    use_parole_mode specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_read_cachebooltrue
    +

    enable and disable caching of blocks read from disk. the purpose of +the read cache is partly read-ahead of requests but also to avoid +reading blocks back from the disk multiple times for popular +pieces.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    coalesce_readsboolfalse
    coalesce_writesboolfalse
    +

    allocate separate, contiguous, buffers for read and write calls. +Only used where writev/readv cannot be used will use more RAM but +may improve performance

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_prefer_seedsboolfalse
    +

    if true, prefer seeding torrents when determining which torrents to give +active slots to. If false, give preference to downloading torrents

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dont_count_slow_torrentsbooltrue
    +

    if dont_count_slow_torrents is true, torrents without any +payload transfers are not subject to the active_seeds and +active_downloads limits. This is intended to make it more +likely to utilize all available bandwidth, and avoid having +torrents that don't transfer anything block the active slots.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    close_redundant_connectionsbooltrue
    +

    close_redundant_connections specifies whether libtorrent should +close connections where both ends have no utility in keeping the +connection open. For instance if both ends have completed their +downloads, there's no point in keeping it open.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prioritize_partial_piecesboolfalse
    +

    If prioritize_partial_pieces is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    rate_limit_ip_overheadbooltrue
    +

    if set to true, the estimated TCP/IP overhead is drained from the +rate limiters, to avoid exceeding the limits with the total traffic

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    announce_to_all_tiersboolfalse
    announce_to_all_trackersboolfalse
    +

    announce_to_all_trackers controls how multi tracker torrents +are treated. If this is set to true, all trackers in the same tier +are announced to in parallel. If all trackers in tier 0 fails, all +trackers in tier 1 are announced as well. If it's set to false, the +behavior is as defined by the multi tracker specification.

    +

    announce_to_all_tiers also controls how multi tracker torrents +are treated. When this is set to true, one tracker from each tier +is announced to. This is the uTorrent behavior. To be compliant +with the Multi-tracker specification, set it to false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prefer_udp_trackersbooltrue
    +

    prefer_udp_trackers: true means that trackers +may be rearranged in a way that udp trackers are always tried +before http trackers for the same hostname. Setting this to false +means that the tracker's tier is respected and there's no +preference of one protocol over another.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    disable_hash_checksboolfalse
    +

    when set to true, all data downloaded from peers will be assumed to +be correct, and not tested to match the hashes in the torrent this +is only useful for simulation and testing purposes (typically +combined with disabled_storage)

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_i2p_mixedboolfalse
    +

    if this is true, i2p torrents are allowed to also get peers from +other sources than the tracker, and connect to regular IPs, not +providing any anonymization. This may be useful if the user is not +interested in the anonymization of i2p, but still wants to be able +to connect to i2p peers.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    volatile_read_cacheboolfalse
    +

    volatile_read_cache, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first +place.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_atime_storagebooltrue
    +

    no_atime_storage this is a Linux-only option and passes in the +O_NOATIME to open() when opening files. This may lead to +some disk performance improvements.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    incoming_starts_queued_torrentsboolfalse
    +

    incoming_starts_queued_torrents. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. the +torrent is paused and auto managed, this feature affects whether or +not it is automatically started on an incoming connection. The main +reason to queue torrents, is not to make them unavailable, but to +save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_true_downloadedboolfalse
    +

    when set to true, the downloaded counter sent to trackers will +include the actual number of payload bytes downloaded including +redundant bytes. If set to false, it will not include any redundancy +bytes

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    strict_end_game_modebooltrue
    +

    strict_end_game_mode controls when a +block may be requested twice. If this is true, a block may only +be requested twice when there's at least one request to every piece +that's left to download in the torrent. This may slow down progress +on some pieces sometimes, but it may also avoid downloading a lot +of redundant bytes. If this is false, libtorrent attempts to +use each peer connection to its max, by always requesting +something, even if it means requesting something that has been +requested from another peer already.

    + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    enable_outgoing_utpbooltrue
    enable_incoming_utpbooltrue
    enable_outgoing_tcpbooltrue
    enable_incoming_tcpbooltrue
    +

    when set to true, libtorrent will try to make outgoing utp +connections controls whether libtorrent will accept incoming +connections or make outgoing connections of specific type.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_recheck_incomplete_resumeboolfalse
    +

    no_recheck_incomplete_resume determines if the storage should +check the whole files when resume data is incomplete or missing or +whether it should simply assume we don't have any of the data. If +false, any existing files will be checked. +By setting this setting to true, the files won't be checked, but +will go straight to download mode.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    anonymous_modeboolfalse
    +

    anonymous_mode: When set to true, the client +tries to hide its identity to a certain degree. The user-agent will be +reset to an empty string (except for private torrents). Trackers +will only be used if they are using a proxy server. +The listen sockets are closed, and incoming +connections will only be accepted through a SOCKS5 or I2P proxy (if +a peer proxy is set up and is run on the same machine as the +tracker proxy). Since no incoming connections are accepted, +NAT-PMP, UPnP, DHT and local peer discovery are all turned off when +this setting is enabled.

    +

    If you're using I2P, it might make sense to enable anonymous mode +as well.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_web_seed_downloadsbooltrue
    +

    specifies whether downloads from web seeds is reported to the +tracker or not. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seeding_outgoing_connectionsbooltrue
    +

    seeding_outgoing_connections determines if seeding (and +finished) torrents should attempt to make outgoing connections or +not. It may be set to false in very +specific applications where the cost of making outgoing connections +is high, and there are no or small benefits of doing so. For +instance, if no nodes are behind a firewall or a NAT, seeds don't +need to make outgoing connections.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_connect_privileged_portsboolfalse
    +

    when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    smooth_connectsbooltrue
    +

    smooth_connects means the number of +connection attempts per second may be limited to below the +connection_speed, in case we're close to bump up against the +limit of number of connections. The intention of this setting is to +more evenly distribute our connection attempts over time, instead +of attempting to connect in batches, and timing them out in +batches.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    always_send_user_agentboolfalse
    +

    always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    apply_ip_filter_to_trackersbooltrue
    +

    apply_ip_filter_to_trackers determines +whether the IP filter applies to trackers as well as peers. If this +is set to false, trackers are exempt from the IP filter (if there +is one). If no IP filter is set, this setting is irrelevant.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    ban_web_seedsbooltrue
    +

    when true, web seeds sending bad data will be banned

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_partial_disk_writesbooltrue
    +

    when set to false, the write_cache_line_size will apply across +piece boundaries. this is a bad idea unless the piece picker also +is configured to have an affinity to pick pieces belonging to the +same write cache line as is configured in the disk cache.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    support_share_modebooltrue
    +

    if false, prevents libtorrent to advertise share-mode support

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    support_merkle_torrentsbooltrue
    +

    if this is false, don't advertise support for the Tribler merkle +tree piece message

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_redundant_bytesbooltrue
    +

    if this is true, the number of redundant bytes is sent to the +tracker

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_system_port_fallbackbooltrue
    +

    if this is true, libtorrent will fall back to listening on a port +chosen by the operating system (i.e. binding to port 0). If a +failure is preferred, set this to false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_crypto_supportbooltrue
    +

    when this is true, and incoming encrypted connections are enabled, +&supportcrypt=1 is included in http tracker announces

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_upnpbooltrue
    +

    Starts and stops the UPnP service. When started, the listen port +and the DHT port are attempted to be forwarded on local UPnP router +devices.

    +

    The upnp object returned by start_upnp() can be used to add and +remove arbitrary port mappings. Mapping status is returned through +the portmap_alert and the portmap_error_alert. The object will be +valid until stop_upnp() is called. See upnp and nat pmp.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_natpmpbooltrue
    +

    Starts and stops the NAT-PMP service. When started, the listen port +and the DHT port are attempted to be forwarded on the router +through NAT-PMP.

    +

    The natpmp object returned by start_natpmp() can be used to add +and remove arbitrary port mappings. Mapping status is returned +through the portmap_alert and the portmap_error_alert. The object +will be valid until stop_natpmp() is called. See +upnp and nat pmp.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_lsdbooltrue
    +

    Starts and stops Local Service Discovery. This service will +broadcast the info-hashes of all the non-private torrents on the +local network to look for peers on the same swarm within multicast +reach.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_dhtbooltrue
    +

    starts the dht node and makes the trackerless service available to +torrents.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prefer_rc4boolfalse
    +

    if the allowed encryption level is both, setting this to true will +prefer RC4 if both methods are offered, plain text otherwise

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_hostnamesbooltrue
    +

    if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_peer_connectionsbooltrue
    +

    if true, peer connections are made (and accepted) over the +configured proxy, if any. Web seeds as well as regular bittorrent +peer connections are considered "peer connections". Anything +transporting actual torrent payload (trackers and DHT traffic are +not considered peer connections).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_sequentialbooltrue
    +

    if this setting is true, torrents with a very high availability of +pieces (and seeds) are downloaded sequentially. This is more +efficient for the disk I/O. With many seeds, the download order is +unlikely to matter anyway

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_tracker_connectionsbooltrue
    +

    if true, tracker connections are made over the configured proxy, if +any.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_ip_notifierbooltrue
    +

    Starts and stops the internal IP table route changes notifier.

    +

    The current implementation supports multiple platforms, and it is +recommended to have it enable, but you may want to disable it if +it's supported but unreliable, or if you have a better way to +detect the changes. In the later case, you should manually call +session_handle::reopen_network_sockets to ensure network +changes are taken in consideration.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_prefer_verified_node_idsbooltrue
    +

    when this is true, nodes whose IDs are derived from their source +IP according to BEP 42 are preferred in the routing table.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    piece_extent_affinityboolfalse
    +

    when this is true, create an affinity for downloading 4 MiB extents +of adjacent pieces. This is an attempt to achieve better disk I/O +throughput by downloading larger extents of bytes, for torrents with +small piece sizes

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    validate_https_trackersboolfalse
    +

    when set to true, the certificate of HTTPS trackers will be +validated against the system's certificate store (as defined by +OpenSSL). If the system does not have one, enabling this may cause +HTTPS trackers to fail.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_completion_timeoutint30
    +

    tracker_completion_timeout is the number of seconds the tracker +connection will wait from when it sent the request until it +considers the tracker to have timed-out.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_receive_timeoutint10
    +

    tracker_receive_timeout is the number of seconds to wait to +receive any data from the tracker. If no data is received for this +number of seconds, the tracker will be considered as having timed +out. If a tracker is down, this is the kind of timeout that will +occur.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    stop_tracker_timeoutint5
    +

    stop_tracker_timeout is the number of seconds to wait when +sending a stopped message before considering a tracker to have +timed out. This is usually shorter, to make the client quit faster. +If the value is set to 0, the connections to trackers with the +stopped event are suppressed.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_maximum_response_lengthint1024*1024
    +

    this is the maximum number of bytes in a tracker response. If a +response size passes this number of bytes it will be rejected and +the connection will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get 20 bytes of gzip +response that'll expand to 2 megabytes, it will be interrupted +before the entire response has been uncompressed (assuming the +limit is lower than 2 MiB).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    piece_timeoutint20
    +

    the number of seconds from a request is sent until it times out if +no piece response is returned.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    request_timeoutint60
    +

    the number of seconds one block (16 kiB) is expected to be received +within. If it's not, the block is requested from a different peer

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    request_queue_timeint3
    +

    the length of the request queue given in the number of seconds it +should take for the other end to send all the pieces. i.e. the +actual number of requests depends on the download rate and this +number.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_allowed_in_request_queueint500
    +

    the number of outstanding block requests a peer is allowed to queue +up in the client. If a peer sends more requests than this (before +the first one has been sent) the last request will be dropped. the +higher this is, the faster upload speeds the client can get to a +single peer.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_out_request_queueint500
    +

    max_out_request_queue is the maximum number of outstanding +requests to send to a peer. This limit takes precedence over +request_queue_time. i.e. no matter the download speed, the +number of outstanding requests will never exceed this limit.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    whole_pieces_thresholdint20
    +

    if a whole piece can be downloaded in this number of seconds, or +less, the peer_connection will prefer to request whole pieces at a +time from this peer. The benefit of this is to better utilize disk +caches by doing localized accesses and also to make it easier to +identify bad peers if a piece fails the hash check.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_timeoutint120
    +

    peer_timeout is the number of seconds the peer connection +should wait (for any activity on the peer connection) before +closing it due to time out. 120 seconds is +specified in the protocol specification. After half +the time out, a keep alive message is sent.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_timeoutint20
    +

    same as peer_timeout, but only applies to url-seeds. this is +usually set lower, because web servers are expected to be more +reliable.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_pipeline_sizeint5
    +

    controls the pipelining size of url and http seeds. i.e. the number of HTTP +request to keep outstanding before waiting for the first one to +complete. It's common for web servers to limit this to a relatively +low number, like 5

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_wait_retryint30
    +

    number of seconds until a new retry of a url-seed takes place. +Default retry value for http-seeds that don't provide +a valid retry-after header.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    file_pool_sizeint40
    +

    sets the upper limit on the total number of files this session will +keep open. The reason why files are left open at all is that some +anti virus software hooks on every file close, and scans the file +for viruses. deferring the closing of the files will be the +difference between a usable system and a completely hogged down +system. Most operating systems also has a limit on the total number +of file descriptors a process may have open.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_failcountint3
    +

    max_failcount is the maximum times we try to +connect to a peer before stop connecting again. If a +peer succeeds, the failure counter is reset. If a +peer is retrieved from a peer source (other than DHT) +the failcount is decremented by one, allowing another +try.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    min_reconnect_timeint60
    +

    the number of seconds to wait to reconnect to a peer. this time is +multiplied with the failcount.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_connect_timeoutint15
    +

    peer_connect_timeout the number of seconds to wait after a +connection attempt is initiated to a peer until it is considered as +having timed out. This setting is especially important in case the +number of half-open connections are limited, since stale half-open +connection may delay the connection of other peers considerably.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connection_speedint30
    +

    connection_speed is the number of connection attempts that are +made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    inactivity_timeoutint600
    +

    if a peer is uninteresting and uninterested for longer than this +number of seconds, it will be disconnected.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    unchoke_intervalint15
    +

    unchoke_interval is the number of seconds between +chokes/unchokes. On this interval, peers are re-evaluated for being +choked/unchoked. This is defined as 30 seconds in the protocol, and +it should be significantly longer than what it takes for TCP to +ramp up to it's max rate.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    optimistic_unchoke_intervalint30
    +

    optimistic_unchoke_interval is the number of seconds between +each optimistic unchoke. On this timer, the currently +optimistically unchoked peer will change.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    num_wantint200
    +

    num_want is the number of peers we want from each tracker +request. It defines what is sent as the &num_want= parameter to +the tracker.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    initial_picker_thresholdint4
    +

    initial_picker_threshold specifies the number of pieces we need +before we switch to rarest first picking. The first +initial_picker_threshold pieces in any torrent are picked at random +, the following pieces are picked in rarest first order.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allowed_fast_set_sizeint5
    +

    the number of allowed pieces to send to peers that supports the +fast extensions

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    suggest_modeintsettings_pack::no_piece_suggestions
    +

    suggest_mode controls whether or not libtorrent will send out +suggest messages to create a bias of its peers to request certain +pieces. The modes are:

    +
      +
    • no_piece_suggestions which will not send out suggest messages.
    • +
    • suggest_read_cache which will send out suggest messages for +the most recent pieces that are in the read cache.
    • +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_queued_disk_bytesint1024 * 1024
    +

    max_queued_disk_bytes is the maximum number of bytes, to +be written to disk, that can wait in the disk I/O thread queue. +This queue is only for waiting for the disk I/O thread to receive +the job and either write it to disk or insert it in the write +cache. When this limit is reached, the peer connections will stop +reading data from their sockets, until the disk thread catches up. +Setting this too low will severely limit your download rate.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    handshake_timeoutint10
    +

    the number of seconds to wait for a handshake response from a peer. +If no response is received within this time, the peer is +disconnected.

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    send_buffer_low_watermarkint10 * 1024
    send_buffer_watermarkint500 * 1024
    send_buffer_watermark_factorint50
    +

    send_buffer_low_watermark the minimum send buffer target size +(send buffer includes bytes pending being read from disk). For good +and snappy seeding performance, set this fairly high, to at least +fit a few blocks. This is essentially the initial window size which +will determine how fast we can ramp up the send rate

    +

    if the send buffer has fewer bytes than send_buffer_watermark, +we'll read another 16 kiB block onto it. If set too small, upload +rate capacity will suffer. If set too high, memory will be wasted. +The actual watermark may be lower than this in case the upload rate +is low, this is the upper limit.

    +

    the current upload rate to a peer is multiplied by this factor to +get the send buffer watermark. The factor is specified as a +percentage. i.e. 50 -> 0.5 This product is clamped to the +send_buffer_watermark setting to not exceed the max. For high +speed upload, this should be set to a greater value than 100. For +high capacity connections, setting this higher can improve upload +performance and disk throughput. Setting it too high may waste RAM +and create a bias towards read jobs over write jobs.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    choking_algorithmintsettings_pack::fixed_slots_choker
    seed_choking_algorithmintsettings_pack::round_robin
    +

    choking_algorithm specifies which algorithm to use to determine +how many peers to unchoke. The unchoking algorithm for +downloading torrents is always "tit-for-tat", i.e. the peers we +download the fastest from are unchoked.

    +

    The options for choking algorithms are defined in the +choking_algorithm_t enum.

    +

    seed_choking_algorithm controls the seeding unchoke behavior. +i.e. How we select which peers to unchoke for seeding torrents. +Since a seeding torrent isn't downloading anything, the +tit-for-tat mechanism cannot be used. The available options are +defined in the seed_choking_algorithm_t enum.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    cache_sizeint2048
    cache_expiryint300
    +

    cache_size is the disk write and read cache. It is specified +in units of 16 kiB blocks. Buffers that are part of a peer's send +or receive buffer also count against this limit. Send and receive +buffers will never be denied to be allocated, but they will cause +the actual cached blocks to be flushed or evicted. If this is set +to -1, the cache size is automatically set based on the amount of +physical RAM on the machine. If the amount of physical RAM cannot +be determined, it's set to 1024 (= 16 MiB).

    +

    cache_expiry is the number of seconds from the last cached write +to a piece in the write cache, to when it's forcefully flushed to +disk.

    +

    On 32 bit builds, the effective cache size will be limited to 3/4 of +2 GiB to avoid exceeding the virtual address space limit.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    disk_io_write_modeintsettings_pack::enable_os_cache
    disk_io_read_modeintsettings_pack::enable_os_cache
    +

    determines how files are opened when they're in read only mode +versus read and write mode. The options are:

    +
    +
    enable_os_cache
    +
    Files are opened normally, with the OS caching reads and writes.
    +
    disable_os_cache
    +
    This opens all files in no-cache mode. This corresponds to the +OS not letting blocks for the files linger in the cache. This +makes sense in order to avoid the bittorrent client to +potentially evict all other processes' cache by simply handling +high throughput and large files. If libtorrent's read cache is +disabled, enabling this may reduce performance.
    +
    +

    One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    outgoing_portint0
    num_outgoing_portsint0
    +

    this is the first port to use for binding outgoing connections to. +This is useful for users that have routers that allow QoS settings +based on local port. when binding outgoing connections to specific +ports, num_outgoing_ports is the size of the range. It should +be more than a few

    +
    +

    Warning

    +

    setting outgoing ports will limit the ability to keep +multiple connections to the same client, even for different +torrents. It is not recommended to change this setting. Its main +purpose is to use as an escape hatch for cheap routers with QoS +capability but can only classify flows based on port numbers.

    +
    +

    It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in TIME_WAIT state.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_tosint0x20
    +

    peer_tos determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). 0x0 means no marking, +0x20 represents the QBone scavenger service. For more +details, see QBSS.

    + + + + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    active_downloadsint3
    active_seedsint5
    active_checkingint1
    active_dht_limitint88
    active_tracker_limitint1600
    active_lsd_limitint60
    active_limitint500
    +

    for auto managed torrents, these are the limits they are subject +to. If there are too many torrents some of the auto managed ones +will be paused until some slots free up. active_downloads and +active_seeds controls how many active seeding and downloading +torrents the queuing mechanism allows. The target number of active +torrents is min(active_downloads + active_seeds, active_limit). +active_downloads and active_seeds are upper limits on the +number of downloading torrents and seeding torrents respectively. +Setting the value to -1 means unlimited.

    +

    For example if there are 10 seeding torrents and 10 downloading +torrents, and active_downloads is 4 and active_seeds is 4, +there will be 4 seeds active and 4 downloading torrents. If the +settings are active_downloads = 2 and active_seeds = 4, +then there will be 2 downloading torrents and 4 seeding torrents +active. Torrents that are not auto managed are not counted against +these limits.

    +

    active_checking is the limit of number of simultaneous checking +torrents.

    +

    active_limit is a hard limit on the number of active (auto +managed) torrents. This limit also applies to slow torrents.

    +

    active_dht_limit is the max number of torrents to announce to +the DHT.

    +

    active_tracker_limit is the max number of torrents to announce +to their trackers.

    +

    active_lsd_limit is the max number of torrents to announce to +the local network over the local service discovery protocol.

    +

    You can have more torrents active, even though they are not +announced to the DHT, lsd or their tracker. If some peer knows +about you for any reason and tries to connect, it will still be +accepted, unless the torrent is paused, which means it won't accept +any connections.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_intervalint30
    +

    auto_manage_interval is the number of seconds between the +torrent queue is updated, and rotated.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seed_time_limitint24 * 60 * 60
    +

    this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed +limit criteria. See queuing.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    auto_scrape_intervalint1800
    auto_scrape_min_intervalint300
    +

    auto_scrape_interval is the number of seconds between scrapes +of queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to +determine which torrents to seed and which to pause.

    +

    auto_scrape_min_interval is the minimum number of seconds +between any automatic scrape (regardless of torrent). In case there +are a large number of paused auto managed torrents, this puts a +limit on how often a scrape request is sent.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    max_peerlist_sizeint3000
    max_paused_peerlist_sizeint1000
    +

    max_peerlist_size is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this +number should be much greater than the maximum number of connected +peers. Peers are evicted from the cache when the list grows passed +90% of this limit, and once the size hits the limit, peers are no +longer added to the list. If this limit is set to 0, there is no +limit on how many peers we'll keep in the peer list.

    +

    max_paused_peerlist_size is the max peer list size used for +torrents that are paused. This can be used to save memory for paused +torrents, since it's not as important for them to keep a large peer +list.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    min_announce_intervalint5 * 60
    +

    this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering mis-configured +trackers.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_startupint60
    +

    this is the number of seconds a torrent is considered active after +it was started, regardless of upload and download speed. This is so +that newly started torrents are not considered inactive until they +have a fair chance to start downloading.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seeding_piece_quotaint20
    +

    seeding_piece_quota is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_rejectsint50
    +

    max_rejects is the number of piece requests we will reject in a +row while a peer is choked before the peer is considered abusive +and is disconnected.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    recv_socket_buffer_sizeint0
    send_socket_buffer_sizeint0
    +

    specifies the buffer sizes set on peer sockets. 0 means the OS +default (i.e. don't change the buffer sizes). +The socket buffer sizes are changed using setsockopt() with +SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_peer_recv_buffer_sizeint2 * 1024 * 1024
    +

    the max number of bytes a single peer connection's receive buffer is +allowed to grow to.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    read_cache_line_sizeint32
    write_cache_line_sizeint16
    +

    read_cache_line_size is the number of blocks to read into the +read cache when a read cache miss occurs. Setting this to 0 is +essentially the same thing as disabling read cache. The number of +blocks read into the read cache is always capped by the piece +boundary.

    +

    When a piece in the write cache has write_cache_line_size +contiguous blocks in it, they will be flushed. Setting this to 1 +effectively disables the write cache.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    optimistic_disk_retryint10 * 60
    +

    optimistic_disk_retry is the number of seconds from a disk +write errors occur on a torrent until libtorrent will take it out +of the upload mode, to test if the error condition has been fixed.

    +

    libtorrent will only do this automatically for auto managed +torrents.

    +

    You can explicitly take a torrent out of upload only mode using +set_upload_mode().

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_suggest_piecesint16
    +

    max_suggest_pieces is the max number of suggested piece indices +received from a peer that's remembered. If a peer floods suggest +messages, this limit prevents libtorrent from using too much RAM.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    local_service_announce_intervalint5 * 60
    +

    local_service_announce_interval is the time between local +network announces for a torrent. +This interval is specified in seconds.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_announce_intervalint15 * 60
    +

    dht_announce_interval is the number of seconds between +announcing torrents to the distributed hash table (DHT).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    udp_tracker_token_expiryint60
    +

    udp_tracker_token_expiry is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is +specified to be 60 seconds. The higher this +value is, the fewer packets have to be sent to the UDP tracker. In +order for higher values to work, the tracker needs to be configured +to match the expiration time for tokens.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    num_optimistic_unchoke_slotsint0
    +

    num_optimistic_unchoke_slots is the number of optimistic +unchoke slots to use. +Having a higher number of optimistic unchoke slots mean you will +find the good peers faster but with the trade-off to use up more +bandwidth. 0 means automatic, where libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_pex_peersint50
    +

    the max number of peers we accept from pex messages from a single +peer. this limits the number of concurrent peers any of our peers +claims to be connected to. If they claim to be connected to more +than this, we'll ignore any peer that exceeds this limit

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tick_intervalint500
    +

    tick_interval specifies the number of milliseconds between +internal ticks. This is the frequency with which bandwidth quota is +distributed to peers. It should not be more than one second (i.e. +1000 ms). Setting this to a low value (around 100) means higher +resolution bandwidth quota distribution, setting it to a higher +value saves CPU cycles.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    share_mode_targetint3
    +

    share_mode_target specifies the target share ratio for share +mode torrents. If set to 3, we'll try to upload 3 +times as much as we download. Setting this very high, will make it +very conservative and you might end up not downloading anything +ever (and not affecting your share ratio). It does not make any +sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a +single piece and upload it more than 3 times. If the +share_mode_target is set to more than 3, nothing is downloaded.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    upload_rate_limitint0
    download_rate_limitint0
    +

    upload_rate_limit and download_rate_limit sets +the session-global limits of upload and download rate limits, in +bytes per second. By default peers on the local network are not rate +limited.

    +

    A value of 0 means unlimited.

    +

    For fine grained control over rate limits, including making them apply +to local peers, see peer classes.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_upload_rate_limitint8000
    +

    dht_upload_rate_limit sets the rate limit on the DHT. This is +specified in bytes per second. For busy boxes +with lots of torrents that requires more DHT traffic, this should +be raised.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    unchoke_slots_limitint8
    +

    unchoke_slots_limit is the max number of unchoked peers in the +session. The number of unchoke slots may be ignored depending on +what choking_algorithm is set to. Setting this limit to -1 +means unlimited, i.e. all peers will always be unchoked.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connections_limitint200
    +

    connections_limit sets a global limit on the number of +connections opened. The number of connections is set to a hard +minimum of at least two per torrent, so if you set a too low +connections limit, and open too many torrents, the limit will not +be met.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connections_slackint10
    +

    connections_slack is the number of incoming connections +exceeding the connection limit to accept in order to potentially +replace existing ones.

    + + + + + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    utp_target_delayint100
    utp_gain_factorint3000
    utp_min_timeoutint500
    utp_syn_resendsint2
    utp_fin_resendsint2
    utp_num_resendsint3
    utp_connect_timeoutint3000
    utp_loss_multiplierint50
    +

    utp_target_delay is the target delay for uTP sockets in +milliseconds. A high value will make uTP connections more +aggressive and cause longer queues in the upload bottleneck. It +cannot be too low, since the noise in the measurements would cause +it to send too slow. +utp_gain_factor is the number of bytes the uTP congestion +window can increase at the most in one RTT. +If this is set too high, the congestion controller reacts +too hard to noise and will not be stable, if it's set too low, it +will react slow to congestion and not back off as fast.

    +

    utp_min_timeout is the shortest allowed uTP socket timeout, +specified in milliseconds. The +timeout depends on the RTT of the connection, but is never smaller +than this value. A connection times out when every packet in a +window is lost, or when a packet is lost twice in a row (i.e. the +resent packet is lost as well).

    +

    The shorter the timeout is, the faster the connection will recover +from this situation, assuming the RTT is low enough. +utp_syn_resends is the number of SYN packets that are sent (and +timed out) before giving up and closing the socket. +utp_num_resends is the number of times a packet is sent (and +lost or timed out) before giving up and closing the connection. +utp_connect_timeout is the number of milliseconds of timeout +for the initial SYN packet for uTP connections. For each timed out +packet (in a row), the timeout is doubled. utp_loss_multiplier +controls how the congestion window is changed when a packet loss is +experienced. It's specified as a percentage multiplier for +cwnd. Do not change this value unless you know what you're doing. +Never set it higher than 100.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    mixed_mode_algorithmintsettings_pack::peer_proportional
    +

    The mixed_mode_algorithm determines how to treat TCP +connections when there are uTP connections. Since uTP is designed +to yield to TCP, there's an inherent problem when using swarms that +have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP +connections. This mode is prefer_tcp. The peer_proportional +mode simply looks at the current throughput and rate limits all TCP +connections to their proportional share based on how many of the +connections are TCP. This works best if uTP connections are not +rate limited by the global rate limiter (which they aren't by +default).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_queue_sizeint5
    +

    listen_queue_size is the value passed in to listen() for the +listen socket. It is the number of outstanding incoming connections +to queue up while we're not actively waiting for a connection to be +accepted. 5 should be sufficient for any +normal client. If this is a high performance server which expects +to receive a lot of connections, or used in a simulator or test, it +might make sense to raise this number. It will not take affect +until the listen_interfaces settings is updated.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    torrent_connect_boostint30
    +

    torrent_connect_boost is the number of peers to try to connect +to immediately when the first tracker response is received for a +torrent. This is a boost to given to new torrents to accelerate +them starting up. The normal connect scheduler is run once every +second, this allows peers to be connected immediately instead of +waiting for the session tick to trigger connections. +This may not be set higher than 255.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    alert_queue_sizeint1000
    +

    alert_queue_size is the maximum number of alerts queued up +internally. If alerts are not popped, the queue will eventually +fill up to this level. Once the alert queue is full, additional +alerts will be dropped, and not delivered to the client. Once the +client drains the queue, new alerts may be delivered again. In order +to know that alerts have been dropped, see +session_handle::dropped_alerts().

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_metadata_sizeint3 * 1024 * 10240
    +

    max_metadata_size is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    checking_mem_usageint1024
    +

    the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    predictive_piece_announceint0
    +

    if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The +intention is to gain 1.5 potential round trip times per downloaded +piece. When non-zero, this indicates how many milliseconds in +advance pieces should be announced, before they are expected to be +completed.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    aio_threadsint4
    +

    for some aio back-ends, aio_threads specifies the number of +io-threads to use.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_backoffint250
    +

    tracker_backoff determines how aggressively to back off from +retrying failing trackers. This value determines x in the +following formula, determining the number of seconds to wait until +the next retry:

    +
    +delay = 5 + 5 * x / 100 * fails^2
    +

    This setting may be useful to make libtorrent more or less +aggressive in hitting trackers.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    share_ratio_limitint200
    seed_time_ratio_limitint700
    +

    when a seeding torrent reaches either the share ratio (bytes up / +bytes down) or the seed time ratio (seconds as seed / seconds as +downloader) or the seed time limit (seconds as seed) it is +considered done, and it will leave room for other torrents. These +are specified as percentages. Torrents that are considered done will +still be allowed to be seeded, they just won't have priority anymore. +For more, see queuing.

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    peer_turnoverint4
    peer_turnover_cutoffint90
    peer_turnover_intervalint300
    +

    peer_turnover is the percentage of peers to disconnect every +turnover peer_turnover_interval (if we're at the peer limit), this +is specified in percent when we are connected to more than limit * +peer_turnover_cutoff peers disconnect peer_turnover fraction of the +peers. It is specified in percent peer_turnover_interval is the +interval (in seconds) between optimistic disconnects if the +disconnects happen and how many peers are disconnected is +controlled by peer_turnover and peer_turnover_cutoff

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connect_seed_every_n_downloadint10
    +

    this setting controls the priority of downloading torrents over +seeding or finished torrents when it comes to making peer +connections. Peer connections are throttled by the connection_speed +and the half-open connection limit. This makes peer connections a +limited resource. Torrents that still have pieces to download are +prioritized by default, to avoid having many seeding torrents use +most of the connection attempts and only give one peer every now +and then to the downloading torrent. libtorrent will loop over the +downloading torrents to connect a peer each, and every n:th +connection attempt, a finished torrent is picked to be allowed to +connect to a peer. This setting controls n.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_http_recv_buffer_sizeint4*1024*204
    +

    the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via the +url provided in add_torrent_params.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_retry_port_bindint10
    +

    if binding to a specific port fails, should the port be incremented +by one and tried again? This setting specifies how many times to +retry a failed port bind

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    alert_maskintint
    +

    a bitmask combining flags from alert_category_t defining which +kinds of alerts to receive

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    out_enc_policyintsettings_pack::pe_enabled
    in_enc_policyintsettings_pack::pe_enabled
    +

    control the settings for incoming and outgoing connections +respectively. see enc_policy enum for the available options. +Keep in mind that protocol encryption degrades performance in +several respects:

    +
      +
    1. It prevents "zero copy" disk buffers being sent to peers, since +each peer needs to mutate the data (i.e. encrypt it) the data +must be copied per peer connection rather than sending the same +buffer to multiple peers.
    2. +
    3. The encryption itself requires more CPU than plain bittorrent +protocol. The highest cost is the Diffie Hellman exchange on +connection setup.
    4. +
    5. The encryption handshake adds several round-trips to the +connection setup, and delays transferring data.
    6. +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allowed_enc_levelintsettings_pack::pe_both
    +

    determines the encryption level of the connections. This setting +will adjust which encryption scheme is offered to the other peer, +as well as which encryption scheme is selected by the client. See +enc_level enum for options.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    inactive_down_rateint2048
    inactive_up_rateint2048
    +

    the download and upload rate limits for a torrent to be considered +active by the queuing mechanism. A torrent whose download rate is +less than inactive_down_rate and whose upload rate is less than +inactive_up_rate for auto_manage_startup seconds, is +considered inactive, and another queued torrent may be started. +This logic is disabled if dont_count_slow_torrents is false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_typeintsettings_pack::none
    +

    proxy to use. see proxy_type_t.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_portint0
    +

    the port of the proxy server

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    i2p_portint0
    +

    sets the i2p SAM bridge port to connect to. set the hostname with +the i2p_hostname setting.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    cache_size_volatileint256
    +

    this determines the max number of volatile disk cache blocks. If the +number of volatile blocks exceed this limit, other volatile blocks +will start to be evicted. A disk cache block is volatile if it has +low priority, and should be one of the first blocks to be evicted +under pressure. For instance, blocks pulled into the cache as the +result of calculating a piece hash are volatile. These blocks don't +represent potential interest among peers, so the value of keeping +them in the cache is limited.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_max_request_bytesint16 * 1024 * 1024
    +

    The maximum request range of an url seed in bytes. This value +defines the largest possible sequential web seed request. Lower values +are possible but will be ignored if they are lower then piece size. +This value should be related to your download speed to prevent +libtorrent from creating too many expensive http requests per +second. You can select a value as high as you want but keep in mind +that libtorrent can't create parallel requests if the first request +did already select the whole file. +If you combine bittorrent seeds with web seeds and pick strategies +like rarest first you may find your web seed requests split into +smaller parts because we don't download already picked pieces +twice.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    web_seed_name_lookup_retryint1800
    +

    time to wait until a new retry of a web seed name lookup

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    close_file_intervalintCLOSE_FILE_INTERVAL
    +

    the number of seconds between closing the file opened the longest +ago. 0 means to disable the feature. The purpose of this is to +periodically close files to trigger the operating system flushing +disk cache. Specifically it has been observed to be required on +windows to not have the disk cache grow indefinitely. +This defaults to 120 seconds on windows, and disabled on other +systems.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    utp_cwnd_reduce_timerint100
    +

    When uTP experiences packet loss, it will reduce the congestion +window, and not reduce it again for this many milliseconds, even if +experiencing another lost packet.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_web_seed_connectionsint3
    +

    the max number of web seeds to have connected per torrent at any +given time.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    resolver_cache_timeoutint1200
    +

    the number of seconds before the internal host name resolver +considers a cache value timed out, negative values are interpreted +as zero.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    send_not_sent_low_watermarkint16384
    +

    specify the not-sent low watermark for socket send buffers. This +corresponds to the, Linux-specific, TCP_NOTSENT_LOWAT TCP socket +option.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    rate_choker_initial_thresholdint1024
    +

    the rate based choker compares the upload rate to peers against a +threshold that increases proportionally by its size for every +peer it visits, visiting peers in decreasing upload rate. The +number of upload slots is determined by the number of peers whose +upload rate exceeds the threshold. This option sets the start +value for this threshold. A higher value leads to fewer unchoke +slots, a lower value leads to more.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    upnp_lease_durationint3600
    +

    The expiration time of UPnP port-mappings, specified in seconds. 0 +means permanent lease. Some routers do not support expiration times +on port-maps (nor correctly returning an error indicating lack of +support). In those cases, set this to 0. Otherwise, don't set it any +lower than 5 minutes.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_concurrent_http_announcesint50
    +

    limits the number of concurrent HTTP tracker announces. Once the +limit is hit, tracker requests are queued and issued when an +outstanding announce completes.

    +
    +struct settings_pack
    +{
    +   void set_str (int name, std::string val);
    +   void set_int (int name, flags::bitfield_flag<Type, Tag> const val);
    +   void set_bool (int name, bool val);
    +   void set_int (int name, int val);
    +   bool has_val (int name) const;
    +   void clear ();
    +   void clear (int name);
    +   bool get_bool (int name) const;
    +   std::string const& get_str (int name) const;
    +   int get_int (int name) const;
    +
    +   enum type_bases
    +   {
    +      string_type_base,
    +      int_type_base,
    +      bool_type_base,
    +      type_mask,
    +      index_mask,
    +   };
    +
    +   enum suggest_mode_t
    +   {
    +      no_piece_suggestions,
    +      suggest_read_cache,
    +   };
    +
    +   enum choking_algorithm_t
    +   {
    +      fixed_slots_choker,
    +      rate_based_choker,
    +      deprecated_bittyrant_choker,
    +   };
    +
    +   enum seed_choking_algorithm_t
    +   {
    +      round_robin,
    +      fastest_upload,
    +      anti_leech,
    +   };
    +
    +   enum io_buffer_mode_t
    +   {
    +      enable_os_cache,
    +      deprecated_disable_os_cache_for_aligned_files,
    +      disable_os_cache,
    +   };
    +
    +   enum bandwidth_mixed_algo_t
    +   {
    +      prefer_tcp,
    +      peer_proportional,
    +   };
    +
    +   enum enc_policy
    +   {
    +      pe_forced,
    +      pe_enabled,
    +      pe_disabled,
    +   };
    +
    +   enum enc_level
    +   {
    +      pe_plaintext,
    +      pe_rc4,
    +      pe_both,
    +   };
    +
    +   enum proxy_type_t
    +   {
    +      none,
    +      socks4,
    +      socks5,
    +      socks5_pw,
    +      http,
    +      http_pw,
    +      i2p_proxy,
    +   };
    +};
    +
    + + +[report issue]
    +

    set_str() set_bool() set_int()

    +
    +void set_str (int name, std::string val);
    +void set_int (int name, flags::bitfield_flag<Type, Tag> const val);
    +void set_bool (int name, bool val);
    +void set_int (int name, int val);
    +
    +

    set a configuration option in the settings_pack. name is one of +the enum values from string_types, int_types or bool_types. They must +match the respective type of the set_* function.

    +[report issue]
    +
    +

    has_val()

    +
    +bool has_val (int name) const;
    +
    +

    queries whether the specified configuration option has a value set in +this pack. name can be any enumeration value from string_types, +int_types or bool_types.

    +[report issue]
    +
    +

    clear()

    +
    +void clear ();
    +
    +

    clear the settings pack from all settings

    +[report issue]
    +
    +

    clear()

    +
    +void clear (int name);
    +
    +

    clear a specific setting from the pack

    + + +[report issue]
    +
    +

    get_str() get_int() get_bool()

    +
    +bool get_bool (int name) const;
    +std::string const& get_str (int name) const;
    +int get_int (int name) const;
    +
    +

    queries the current configuration option from the settings_pack. +name is one of the enumeration values from string_types, int_types +or bool_types. The enum value must match the type of the get_* +function.

    +[report issue]
    +
    +

    enum type_bases

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    string_type_base0 
    int_type_base16384 
    bool_type_base32768 
    type_mask49152 
    index_mask16383 
    +[report issue]
    +
    +

    enum suggest_mode_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_piece_suggestions0 
    suggest_read_cache1 
    +[report issue]
    +
    +

    enum choking_algorithm_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    fixed_slots_choker0This is the traditional choker with a fixed number of unchoke +slots (as specified by settings_pack::unchoke_slots_limit).
    rate_based_choker2

    This opens up unchoke slots based on the upload rate achieved to +peers. The more slots that are opened, the marginal upload rate +required to open up another slot increases. Configure the initial +threshold with settings_pack::rate_choker_initial_threshold.

    +

    For more information, see rate based choking.

    +
    deprecated_bittyrant_choker3 
    +[report issue]
    +
    +

    enum seed_choking_algorithm_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    round_robin0which round-robins the peers that are unchoked +when seeding. This distributes the upload bandwidth uniformly and +fairly. It minimizes the ability for a peer to download everything +without redistributing it.
    fastest_upload1unchokes the peers we can send to the fastest. This might be a +bit more reliable in utilizing all available capacity.
    anti_leech2prioritizes peers who have just started or are +just about to finish the download. The intention is to force +peers in the middle of the download to trade with each other. +This does not just take into account the pieces a peer is +reporting having downloaded, but also the pieces we have sent +to it.
    +[report issue]
    +
    +

    enum io_buffer_mode_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    enable_os_cache0 
    deprecated_disable_os_cache_for_aligned_files1 
    disable_os_cache2 
    +[report issue]
    +
    +

    enum bandwidth_mixed_algo_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    prefer_tcp0disables the mixed mode bandwidth balancing
    peer_proportional1does not throttle uTP, throttles TCP to the same proportion +of throughput as there are TCP connections
    +[report issue]
    +
    +

    enum enc_policy

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pe_forced0Only encrypted connections are allowed. Incoming connections that +are not encrypted are closed and if the encrypted outgoing +connection fails, a non-encrypted retry will not be made.
    pe_enabled1encrypted connections are enabled, but non-encrypted connections +are allowed. An incoming non-encrypted connection will be accepted, +and if an outgoing encrypted connection fails, a non- encrypted +connection will be tried.
    pe_disabled2only non-encrypted connections are allowed.
    +[report issue]
    +
    +

    enum enc_level

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pe_plaintext1use only plain text encryption
    pe_rc42use only RC4 encryption
    pe_both3allow both
    +[report issue]
    +
    +

    enum proxy_type_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0No proxy server is used and all other fields are ignored.
    socks41The server is assumed to be a SOCKS4 server that requires a +username.
    socks52The server is assumed to be a SOCKS5 server (RFC 1928) that does +not require any authentication. The username and password are +ignored.
    socks5_pw3The server is assumed to be a SOCKS5 server that supports plain +text username and password authentication (RFC 1929). The +username and password specified may be sent to the proxy if it +requires.
    http4The server is assumed to be an HTTP proxy. If the transport used +for the connection is non-HTTP, the server is assumed to support +the CONNECT method. i.e. for web seeds and HTTP trackers, a plain +proxy will suffice. The proxy is assumed to not require +authorization. The username and password will not be used.
    http_pw5The server is assumed to be an HTTP proxy that requires user +authorization. The username and password will be sent to the proxy.
    i2p_proxy6route through a i2p SAM proxy
    + +[report issue]
    +
    +
    +

    high_performance_seed() min_memory_usage()

    +

    Declared in "libtorrent/session.hpp"

    +
    +settings_pack high_performance_seed ();
    +settings_pack min_memory_usage ();
    +
    +

    The default values of the session settings are set for a regular +bittorrent client running on a desktop system. There are functions that +can set the session settings to pre set settings for other environments. +These can be used for the basis, and should be tweaked to fit your needs +better.

    +

    min_memory_usage returns settings that will use the minimal amount of +RAM, at the potential expense of upload and download performance. It +adjusts the socket buffer sizes, disables the disk cache, lowers the send +buffer watermarks so that each connection only has at most one block in +use at any one time. It lowers the outstanding blocks send to the disk +I/O thread so that connections only have one block waiting to be flushed +to disk at any given time. It lowers the max number of peers in the peer +list for torrents. It performs multiple smaller reads when it hashes +pieces, instead of reading it all into memory before hashing.

    +

    This configuration is intended to be the starting point for embedded +devices. It will significantly reduce memory usage.

    +

    high_performance_seed returns settings optimized for a seed box, +serving many peers and that doesn't do any downloading. It has a 128 MB +disk cache and has a limit of 400 files in its file pool. It support fast +upload rates by allowing large send buffers.

    + +[report issue]
    +
    +

    setting_by_name() name_for_setting()

    +

    Declared in "libtorrent/settings_pack.hpp"

    +
    +char const* name_for_setting (int s);
    +int setting_by_name (string_view name);
    +
    +

    converts a setting integer (from the enums string_types, int_types or +bool_types) to a string, and vice versa.

    +[report issue]
    +
    +

    default_settings()

    +

    Declared in "libtorrent/settings_pack.hpp"

    +
    +settings_pack default_settings ();
    +
    +

    returns a settings_pack with every setting set to its default value

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Storage.html b/docs/reference-Storage.html new file mode 100644 index 0000000..319925e --- /dev/null +++ b/docs/reference-Storage.html @@ -0,0 +1,693 @@ + + + + + + +reference-Storage.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Storage

    + +[report issue]
    +

    storage_params

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +struct storage_params
    +{
    +   storage_params (file_storage const& f, file_storage const* mf
    +      , std::string const& sp, storage_mode_t const sm
    +      , aux::vector<download_priority_t, file_index_t> const& prio
    +      , sha1_hash const& ih);
    +
    +   file_storage const& files;
    +   file_storage const* mapped_files  = nullptr;
    +   std::string const& path;
    +   storage_mode_t mode {storage_mode_sparse};
    +   aux::vector<download_priority_t, file_index_t> const& priorities;
    +   sha1_hash const& info_hash;
    +};
    +
    +[report issue]
    +
    +

    file_slice

    +

    Declared in "libtorrent/file_storage.hpp"

    +

    represents a window of a file in a torrent.

    +

    The file_index refers to the index of the file (in the torrent_info). +To get the path and filename, use file_path() and give the file_index +as argument. The offset is the byte offset in the file where the range +starts, and size is the number of bytes this range is. The size + offset +will never be greater than the file size.

    +
    +struct file_slice
    +{
    +   file_index_t file_index;
    +   std::int64_t offset;
    +   std::int64_t size;
    +};
    +
    +[report issue]
    +
    file_index
    +
    the index of the file
    +
    +[report issue]
    +
    offset
    +
    the offset from the start of the file, in bytes
    +
    +[report issue]
    +
    size
    +
    the size of the window, in bytes
    +
    +[report issue]
    +
    +

    file_storage

    +

    Declared in "libtorrent/file_storage.hpp"

    +

    The file_storage class represents a file list and the piece +size. Everything necessary to interpret a regular bittorrent storage +file structure.

    +
    +class file_storage
    +{
    +   bool is_valid () const;
    +   void reserve (int num_files);
    +   void add_file (std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}
    +      , std::time_t mtime = 0, string_view symlink_path = string_view());
    +   void add_file_borrow (string_view filename
    +      , std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}, char const* filehash = nullptr
    +      , std::int64_t mtime = 0, string_view symlink_path = string_view());
    +   void rename_file (file_index_t index, std::string const& new_filename);
    +   std::vector<file_slice> map_block (piece_index_t piece, std::int64_t offset
    +      , int size) const;
    +   peer_request map_file (file_index_t file, std::int64_t offset, int size) const;
    +   int num_files () const noexcept;
    +   file_index_t end_file () const noexcept;
    +   index_range<file_index_t> file_range () const noexcept;
    +   std::int64_t total_size () const;
    +   void set_num_pieces (int n);
    +   int num_pieces () const;
    +   piece_index_t end_piece () const;
    +   piece_index_t last_piece () const;
    +   index_range<piece_index_t> piece_range () const noexcept;
    +   int piece_length () const;
    +   void set_piece_length (int l);
    +   int piece_size (piece_index_t index) const;
    +   std::string const& name () const;
    +   void set_name (std::string const& n);
    +   void swap (file_storage& ti) noexcept;
    +   void optimize (int pad_file_limit = -1, int alignment = -1
    +      , bool tail_padding = false);
    +   std::string const& symlink (file_index_t index) const;
    +   bool pad_file_at (file_index_t index) const;
    +   std::time_t mtime (file_index_t index) const;
    +   std::int64_t file_offset (file_index_t index) const;
    +   std::int64_t file_size (file_index_t index) const;
    +   string_view file_name (file_index_t index) const;
    +   sha1_hash hash (file_index_t index) const;
    +   std::string file_path (file_index_t index, std::string const& save_path = "") const;
    +   std::uint32_t file_path_hash (file_index_t index, std::string const& save_path) const;
    +   void all_path_hashes (std::unordered_set<std::uint32_t>& table) const;
    +   std::vector<std::string> const& paths () const;
    +   file_flags_t file_flags (file_index_t index) const;
    +   bool file_absolute_path (file_index_t index) const;
    +   file_index_t file_index_at_offset (std::int64_t offset) const;
    +   int file_name_len (file_index_t index) const;
    +   char const* file_name_ptr (file_index_t index) const;
    +   void apply_pointer_offset (std::ptrdiff_t off);
    +   void sanitize_symlinks ();
    +
    +   static constexpr file_flags_t flag_pad_file  = 0_bit;
    +   static constexpr file_flags_t flag_hidden  = 1_bit;
    +   static constexpr file_flags_t flag_executable  = 2_bit;
    +   static constexpr file_flags_t flag_symlink  = 3_bit;
    +};
    +
    +[report issue]
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if the piece length has been initialized +on the file_storage. This is typically taken as a proxy +of whether the file_storage as a whole is initialized or +not.

    +[report issue]
    +
    +

    reserve()

    +
    +void reserve (int num_files);
    +
    +

    allocates space for num_files in the internal file list. This can +be used to avoid reallocating the internal file list when the number +of files to be added is known up-front.

    + +[report issue]
    +
    +

    add_file_borrow() add_file()

    +
    +void add_file (std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}
    +      , std::time_t mtime = 0, string_view symlink_path = string_view());
    +void add_file_borrow (string_view filename
    +      , std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}, char const* filehash = nullptr
    +      , std::int64_t mtime = 0, string_view symlink_path = string_view());
    +
    +

    Adds a file to the file storage. The add_file_borrow version +expects that filename is the file name (without a path) of +the file that's being added. +This memory is borrowed, i.e. it is the caller's +responsibility to make sure it stays valid throughout the lifetime +of this file_storage object or any copy of it. The same thing applies +to filehash, which is an optional pointer to a 20 byte binary +SHA-1 hash of the file.

    +

    if filename is empty, the filename from path is used and not +borrowed.

    +

    The path argument is the full path (in the torrent file) to +the file to add. Note that this is not supposed to be an absolute +path, but it is expected to include the name of the torrent as the +first path element.

    +

    file_size is the size of the file in bytes.

    +

    The file_flags argument sets attributes on the file. The file +attributes is an extension and may not work in all bittorrent clients.

    +

    For possible file attributes, see file_storage::flags_t.

    +

    The mtime argument is optional and can be set to 0. If non-zero, +it is the posix time of the last modification time of this file.

    +

    symlink_path is the path the file is a symlink to. To make this a +symlink you also need to set the file_storage::flag_symlink file flag.

    +

    If more files than one are added, certain restrictions to their paths +apply. In a multi-file file storage (torrent), all files must share +the same root directory.

    +

    That is, the first path element of all files must be the same. +This shared path element is also set to the name of the torrent. It +can be changed by calling set_name.

    +[report issue]
    +
    +

    rename_file()

    +
    +void rename_file (file_index_t index, std::string const& new_filename);
    +
    +

    renames the file at index to new_filename. Keep in mind +that filenames are expected to be UTF-8 encoded.

    +[report issue]
    +
    +

    map_block()

    +
    +std::vector<file_slice> map_block (piece_index_t piece, std::int64_t offset
    +      , int size) const;
    +
    +

    returns a list of file_slice objects representing the portions of +files the specified piece index, byte offset and size range overlaps. +this is the inverse mapping of map_file().

    +

    Preconditions of this function is that the input range is within the +torrents address space. piece may not be negative and

    +
    +piece * piece_size + offset + size
    +

    may not exceed the total size of the torrent.

    +[report issue]
    +
    +

    map_file()

    +
    +peer_request map_file (file_index_t file, std::int64_t offset, int size) const;
    +
    +

    returns a peer_request representing the piece index, byte offset +and size the specified file range overlaps. This is the inverse +mapping over map_block(). Note that the peer_request return type +is meant to hold bittorrent block requests, which may not be larger +than 16 kiB. Mapping a range larger than that may return an overflown +integer.

    +[report issue]
    +
    +

    num_files()

    +
    +int num_files () const noexcept;
    +
    +

    returns the number of files in the file_storage

    +[report issue]
    +
    +

    end_file()

    +
    +file_index_t end_file () const noexcept;
    +
    +

    returns the index of the one-past-end file in the file storage

    +[report issue]
    +
    +

    file_range()

    +
    +index_range<file_index_t> file_range () const noexcept;
    +
    +

    returns an implementation-defined type that can be used as the +container in a range-for loop. Where the values are the indices of all +files in the file_storage.

    +[report issue]
    +
    +

    total_size()

    +
    +std::int64_t total_size () const;
    +
    +

    returns the total number of bytes all the files in this torrent spans

    + +[report issue]
    +
    +

    num_pieces() set_num_pieces()

    +
    +void set_num_pieces (int n);
    +int num_pieces () const;
    +
    +

    set and get the number of pieces in the torrent

    +[report issue]
    +
    +

    end_piece()

    +
    +piece_index_t end_piece () const;
    +
    +

    returns the index of the one-past-end piece in the file storage

    +[report issue]
    +
    +

    last_piece()

    +
    +piece_index_t last_piece () const;
    +
    +

    returns the index of the last piece in the torrent. The last piece is +special in that it may be smaller than the other pieces (and the other +pieces are all the same size).

    +[report issue]
    +
    +

    piece_range()

    +
    +index_range<piece_index_t> piece_range () const noexcept;
    +
    +

    returns an implementation-defined type that can be used as the +container in a range-for loop. Where the values are the indices of all +pieces in the file_storage.

    + +[report issue]
    +
    +

    set_piece_length() piece_length()

    +
    +int piece_length () const;
    +void set_piece_length (int l);
    +
    +

    set and get the size of each piece in this torrent. This size is typically an even power +of 2. It doesn't have to be though. It should be divisible by 16 kiB however.

    +[report issue]
    +
    +

    piece_size()

    +
    +int piece_size (piece_index_t index) const;
    +
    +

    returns the piece size of index. This will be the same as piece_length(), except +for the last piece, which may be shorter.

    + +[report issue]
    +
    +

    name() set_name()

    +
    +std::string const& name () const;
    +void set_name (std::string const& n);
    +
    +

    set and get the name of this torrent. For multi-file torrents, this is also +the name of the root directory all the files are stored in.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (file_storage& ti) noexcept;
    +
    +

    swap all content of this with ti.

    +[report issue]
    +
    +

    optimize()

    +
    +void optimize (int pad_file_limit = -1, int alignment = -1
    +      , bool tail_padding = false);
    +
    +

    if pad_file_limit >= 0, files larger than that limit will be padded, +default is to not add any padding (-1). The alignment specifies the +alignment files should be padded to. This defaults to the piece size +(-1) but it may also make sense to set it to 16 kiB, or something +divisible by 16 kiB. +If pad_file_limit is 0, every file will be padded (except empty ones). +tail_padding indicates whether aligned files also are padded at +the end to make them end aligned. This is required for mutable +torrents, since piece hashes are compared

    + + + + + + + +[report issue]
    + +
    +

    file_path_hash()

    +
    +std::uint32_t file_path_hash (file_index_t index, std::string const& save_path) const;
    +
    +

    returns the crc32 hash of file_path(index)

    +[report issue]
    +
    +

    all_path_hashes()

    +
    +void all_path_hashes (std::unordered_set<std::uint32_t>& table) const;
    +
    +

    this will add the CRC32 hash of all directory entries to the table. No +filename will be included, just directories. Every depth of directories +are added separately to allow test for collisions with files at all +levels. i.e. if one path in the torrent is foo/bar/baz, the CRC32 +hashes for foo, foo/bar and foo/bar/baz will be added to +the set.

    +[report issue]
    +
    +

    paths()

    +
    +std::vector<std::string> const& paths () const;
    +
    +

    returns all directories used in the torrent. Files in the torrent are +located in one of these directories. This is not a tree, it's a flat +list of all leaf directories. i.e. the union of the parent paths of +all files.

    +[report issue]
    +
    +

    file_flags()

    +
    +file_flags_t file_flags (file_index_t index) const;
    +
    +

    returns a bitmask of flags from file_flags_t that apply +to file at index.

    +[report issue]
    +
    +

    file_absolute_path()

    +
    +bool file_absolute_path (file_index_t index) const;
    +
    +

    returns true if the file at the specified index has been renamed to +have an absolute path, i.e. is not anchored in the save path of the +torrent.

    +[report issue]
    +
    +

    file_index_at_offset()

    +
    +file_index_t file_index_at_offset (std::int64_t offset) const;
    +
    +

    returns the index of the file at the given offset in the torrent

    + +[report issue]
    +
    +

    file_name_len() file_name_ptr()

    +
    +int file_name_len (file_index_t index) const;
    +char const* file_name_ptr (file_index_t index) const;
    +
    +

    low-level function. returns a pointer to the internal storage for +the filename. This string may not be 0-terminated! +the file_name_len() function returns the length of the filename. +prefer to use file_name() instead, which returns a string_view.

    +[report issue]
    +
    +

    apply_pointer_offset()

    +
    +void apply_pointer_offset (std::ptrdiff_t off);
    +
    +

    if the backing buffer changed for this storage, this is the pointer +offset to add to any pointers to make them point into the new buffer

    +[report issue]
    + +
    +
    +

    default_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* default_storage_constructor (storage_params const&
    +   , file_pool& p);
    +
    +

    the constructor function for the regular file storage. This is the +default value for add_torrent_params::storage.

    +[report issue]
    +
    +

    disabled_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* disabled_storage_constructor (storage_params const&, file_pool&);
    +
    +

    the constructor function for the disabled storage. This can be used for +testing and benchmarking. It will throw away any data written to +it and return garbage for anything read from it.

    +[report issue]
    +
    +

    zero_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* zero_storage_constructor (storage_params const&, file_pool&);
    +
    +

    the constructor function for the "zero" storage. This will always read +zeros and ignore all writes.

    +[report issue]
    +
    +

    enum storage_mode_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    storage_mode_allocate0All pieces will be written to their final position, all files will be +allocated in full when the torrent is first started. This is done with +fallocate() and similar calls. This mode minimizes fragmentation.
    storage_mode_sparse1All pieces will be written to the place where they belong and sparse files +will be used. This is the recommended, and default mode.
    +[report issue]
    +
    +

    enum status_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    fatal_disk_error1 
    need_full_check2 
    file_exist3 
    +[report issue]
    +
    +

    enum move_flags_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    always_replace_files0replace any files in the destination when copying +or moving the storage
    fail_if_exist1if any files that we want to copy exist in the destination +exist, fail the whole operation and don't perform +any copy or move. There is an inherent race condition +in this mode. The files are checked for existence before +the operation starts. In between the check and performing +the copy, the destination files may be created, in which +case they are replaced.
    dont_replace2if any file exist in the target, take those files instead +of the ones we may have in the source.
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-Utility.html b/docs/reference-Utility.html new file mode 100644 index 0000000..7b7cb62 --- /dev/null +++ b/docs/reference-Utility.html @@ -0,0 +1,323 @@ + + + + + + +reference-Utility.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    Utility

    +
    +

    Table of contents

    + +
    +[report issue]
    +

    bitfield

    +

    Declared in "libtorrent/bitfield.hpp"

    +

    The bitfield type stores any number of bits as a bitfield +in a heap allocated array.

    +
    +struct bitfield
    +{
    +   bitfield () noexcept = default;
    +   bitfield (bitfield const& rhs);
    +   explicit bitfield (int bits);
    +   bitfield (bitfield&& rhs) noexcept = default;
    +   bitfield (int bits, bool val);
    +   bitfield (char const* b, int bits);
    +   void assign (char const* b, int const bits);
    +   bool operator[] (int index) const noexcept;
    +   bool get_bit (int index) const noexcept;
    +   void set_bit (int index) noexcept;
    +   void clear_bit (int index) noexcept;
    +   bool all_set () const noexcept;
    +   bool none_set () const noexcept;
    +   int size () const noexcept;
    +   int num_words () const noexcept;
    +   bool empty () const noexcept;
    +   char* data () noexcept;
    +   char const* data () const noexcept;
    +   void swap (bitfield& rhs) noexcept;
    +   int count () const noexcept;
    +   int find_first_set () const noexcept;
    +   int find_last_clear () const noexcept;
    +};
    +
    +[report issue]
    +

    bitfield()

    +
    +bitfield () noexcept = default;
    +bitfield (bitfield const& rhs);
    +explicit bitfield (int bits);
    +bitfield (bitfield&& rhs) noexcept = default;
    +bitfield (int bits, bool val);
    +bitfield (char const* b, int bits);
    +
    +

    constructs a new bitfield. The default constructor creates an empty +bitfield. bits is the size of the bitfield (specified in bits). +val is the value to initialize the bits to. If not specified +all bits are initialized to 0.

    +

    The constructor taking a pointer b and bits copies a bitfield +from the specified buffer, and bits number of bits (rounded up to +the nearest byte boundary).

    +[report issue]
    +
    +

    assign()

    +
    +void assign (char const* b, int const bits);
    +
    +

    copy bitfield from buffer b of bits number of bits, rounded up to +the nearest byte boundary.

    + +[report issue]
    +
    +

    get_bit() operator[]()

    +
    +bool operator[] (int index) const noexcept;
    +bool get_bit (int index) const noexcept;
    +
    +

    query bit at index. Returns true if bit is 1, otherwise false.

    + +[report issue]
    +
    +

    set_bit() clear_bit()

    +
    +void set_bit (int index) noexcept;
    +void clear_bit (int index) noexcept;
    +
    +

    set bit at index to 0 (clear_bit) or 1 (set_bit).

    +[report issue]
    +
    +

    all_set()

    +
    +bool all_set () const noexcept;
    +
    +

    returns true if all bits in the bitfield are set

    +[report issue]
    +
    +

    none_set()

    +
    +bool none_set () const noexcept;
    +
    +

    returns true if no bit in the bitfield is set

    +[report issue]
    +
    +

    size()

    +
    +int size () const noexcept;
    +
    +

    returns the size of the bitfield in bits.

    +[report issue]
    +
    +

    num_words()

    +
    +int num_words () const noexcept;
    +
    +

    returns the number of 32 bit words are needed to represent all bits in +this bitfield.

    +[report issue]
    +
    +

    empty()

    +
    +bool empty () const noexcept;
    +
    +

    returns true if the bitfield has zero size.

    +[report issue]
    +
    +

    data()

    +
    +char* data () noexcept;
    +char const* data () const noexcept;
    +
    +

    returns a pointer to the internal buffer of the bitfield, or +nullptr if it's empty.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (bitfield& rhs) noexcept;
    +
    +

    swaps the bit-fields two variables refer to

    +[report issue]
    +
    +

    count()

    +
    +int count () const noexcept;
    +
    +

    count the number of bits in the bitfield that are set to 1.

    +[report issue]
    +
    +

    find_first_set()

    +
    +int find_first_set () const noexcept;
    +
    +

    returns the index of the first set bit in the bitfield, i.e. 1 bit.

    +[report issue]
    +
    +

    find_last_clear()

    +
    +int find_last_clear () const noexcept;
    +
    +

    returns the index to the last cleared bit in the bitfield, i.e. 0 bit.

    +[report issue]
    +
    +
    +

    hasher

    +

    Declared in "libtorrent/hasher.hpp"

    +

    this is a SHA-1 hash class.

    +

    You use it by first instantiating it, then call update() to feed it +with data. i.e. you don't have to keep the entire buffer of which you want to +create the hash in memory. You can feed the hasher parts of it at a time. When +You have fed the hasher with all the data, you call final() and it +will return the sha1-hash of the data.

    +

    The constructor that takes a char const* and an integer will construct the +sha1 context and feed it the data passed in.

    +

    If you want to reuse the hasher object once you have created a hash, you have to +call reset() to reinitialize it.

    +

    The built-in software version of sha1-algorithm was implemented +by Steve Reid and released as public domain. +For more info, see src/sha1.cpp.

    +
    +class hasher
    +{
    +   hasher ();
    +   hasher& operator= (hasher const&) &;
    +   hasher (hasher const&);
    +   explicit hasher (span<char const> data);
    +   hasher (char const* data, int len);
    +   hasher& update (span<char const> data);
    +   hasher& update (char const* data, int len);
    +   sha1_hash final ();
    +   void reset ();
    +};
    +
    + +[report issue]
    +

    hasher() operator=()

    +
    +hasher& operator= (hasher const&) &;
    +hasher (hasher const&);
    +explicit hasher (span<char const> data);
    +hasher (char const* data, int len);
    +
    +

    this is the same as default constructing followed by a call to +update(data, len).

    +[report issue]
    +
    +

    update()

    +
    +hasher& update (span<char const> data);
    +hasher& update (char const* data, int len);
    +
    +

    append the following bytes to what is being hashed

    +[report issue]
    +
    +

    final()

    +
    +sha1_hash final ();
    +
    +

    returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor.

    +[report issue]
    +
    +

    reset()

    +
    +void reset ();
    +
    +

    restore the hasher state to be as if the hasher has just been +default constructed.

    +[report issue]
    +
    +
    +

    operator<<()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +std::ostream& operator<< (std::ostream& os, sha1_hash const& peer);
    +
    +

    print a sha1_hash object to an ostream as 40 hexadecimal digits

    +[report issue]
    +
    +

    operator>>()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +std::istream& operator>> (std::istream& is, sha1_hash& peer);
    +
    +

    read 40 hexadecimal digits from an istream into a sha1_hash

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference-ed25519.html b/docs/reference-ed25519.html new file mode 100644 index 0000000..f973b77 --- /dev/null +++ b/docs/reference-ed25519.html @@ -0,0 +1,170 @@ + + + + + + +reference-ed25519.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +

    ed25519

    + +[report issue]
    +

    ed25519_create_seed()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +std::array<char, 32> ed25519_create_seed ();
    +
    +

    See documentation of internal random_bytes

    +[report issue]
    +
    +

    ed25519_create_keypair()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +std::tuple<public_key, secret_key> ed25519_create_keypair (
    +   std::array<char, 32> const& seed);
    +
    +

    Creates a new key pair from the given seed.

    +

    It's important to clarify that the seed completely determines +the key pair. Then it's enough to save the seed and the +public key as the key-pair in a buffer of 64 bytes. The standard +is (32 bytes seed, 32 bytes public key).

    +

    This function does work with a given seed, giving you a pair of +(64 bytes private key, 32 bytes public key). It's a trade-off between +space and CPU, saving in one format or another.

    +

    The smaller format is not weaker by any means, in fact, it is only +the seed (32 bytes) that determines the point in the curve.

    +[report issue]
    +
    +

    ed25519_sign()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +signature ed25519_sign (span<char const> msg
    +   , public_key const& pk, secret_key const& sk);
    +
    +

    Creates a signature of the given message with the given key pair.

    +[report issue]
    +
    +

    ed25519_verify()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +bool ed25519_verify (signature const& sig
    +   , span<char const> msg, public_key const& pk);
    +
    +

    Verifies the signature on the given message using pk

    +[report issue]
    +
    +

    ed25519_add_scalar()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +secret_key ed25519_add_scalar (secret_key const& sk
    +   , std::array<char, 32> const& scalar);
    +public_key ed25519_add_scalar (public_key const& pk
    +   , std::array<char, 32> const& scalar);
    +
    +

    Adds a scalar to the given key pair where scalar is a 32 byte buffer +(possibly generated with ed25519_create_seed), generating a new key pair.

    +

    You can calculate the public key sum without knowing the private key and +vice versa by passing in null for the key you don't know. This is useful +when a third party (an authoritative server for example) needs to enforce +randomness on a key pair while only knowing the public key of the other +side.

    +

    Warning: the last bit of the scalar is ignored - if comparing scalars make +sure to clear it with scalar[31] &= 127.

    +

    see http://crypto.stackexchange.com/a/6215/4697 +see test_ed25519 for a practical example

    +[report issue]
    +
    +

    ed25519_key_exchange()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +std::array<char, 32> ed25519_key_exchange (
    +   public_key const& pk, secret_key const& sk);
    +
    +

    Performs a key exchange on the given public key and private key, producing a +shared secret. It is recommended to hash the shared secret before using it.

    +

    This is useful when two parties want to share a secret but both only knows +their respective public keys. +see test_ed25519 for a practical example

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/reference.html b/docs/reference.html new file mode 100644 index 0000000..de872cf --- /dev/null +++ b/docs/reference.html @@ -0,0 +1,344 @@ + + + + + + +reference documentation + + + + + + +
    +
    + + + + +
    +

    reference documentation

    + +

    single-page version

    + + + +
    +

    Alerts

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/rst.css b/docs/rst.css new file mode 100644 index 0000000..0deca83 --- /dev/null +++ b/docs/rst.css @@ -0,0 +1,241 @@ +.document a { + border: none; + color: black; +} + +.document a:hover { + background: none; +} + +.document a.reference { + color: #8D370A; + border-bottom: dotted 1px #8D370A; +} + +.document a.reference:hover { + border-bottom: solid 1px #8D370A; + background: #eee; +} + +div.section { + margin-bottom: 3em; +} + +div.section div.section div.section { + margin-bottom: 2em; +} + +div.section p, div.section ul, div.section dl { +} + +div.main-toc { + column-count: 4; + -webkit-column-count: 4; + -moz-column-count: 4; + column-width: 13em; + -webkit-column-width: 13em; + -moz-column-width: 13em; + border: 1px solid #999; + padding: 5px; + margin-bottom: 10px; +} + +.rubric +{ + margin-top: 5px; + margin-bottom: 5px; + font-size: 120%; + font-weight: bold; +} + +table.docinfo { + text-align: left; + float: right; + width: 200px; + margin-right: 0px; + margin-left: 20px; + margin-bottom: 20px; +} + +table.docinfo th { + border-top: none; + font-size: 72%; + padding-left: 10px; +} + +table.docinfo td { + padding-left: 10px; + font-size: 88%; +} + +table.docinfo tr.field td, table.docinfo tr.field th {display: none;} + +#h1.title { display: none; } + +dt { + margin-bottom: 0.5em; + color: #000; + font-weight: bold; +} + +dd { + margin-left: 2em; + margin-bottom: 1em; +} + +tt { + font: 1em "Courier New", "Courier"; + font-size: 90%; +} + +pre { + font-family: "Courier", monospace; + margin-right: 10px; + background: #C1E5F6; + border-left: solid 2px #6185A6; + border-right: solid 2px #6185A6; + padding: 5px 10px 5px 10px; + + background: #f6f6f6; + border: solid 1px #ddd; + margin: 1em 0; +} + +div.warning, div.note, div.important { + width: 80%; + margin: 1.5em auto; + background: #C1E5F6; + background: #F1FFF5; + border: solid 1px #D1DFD5; + padding: 5px 10px 5px 10px; +} + +p.admonition-title { + font-family: Georgia, "Lucida Grande"; + font-size: 128%; + letter-spacing: 2px; + text-transform: uppercase; + margin: 0 0 0.5em 0; + border-bottom: solid 1px #D1DFD5 +} + +h1 { font-size: 200%; } +h2 { font-size: 130%; } +h3 { font-size: 100%; font-style: italic; } + +h1.title { text-align: center; } + +table { margin-bottom: 1em; border-collapse: collapse; } +table, th, td { border: none; } + +th, td { padding: 0.3em; } + +th { + text-align: left; + background: #f0f0e0; + border-right: solid 1px #f0f0e0; + border-top: solid 1px #e8e8d8; + border-bottom: solid 1px #e8e8d8; +} + +td { + background: #f8f8e8; + border-right: solid 1px #f8f8e8; + border-bottom: solid 1px #e8e8d8; +} + +td td { + background: #e8e8d8; + border-right: solid 1px #e8e8d8; + border-bottom: solid 1px #d8d8c8; +} + +div.topic { + border-left: solid 1px #eee; + padding-left: 1em; + margin: 0 0 1.5em; +} + +p.topic-title { + font: 1.3em Georgia, "Times New Roman", serif; +} + +/* TOC */ + +div.contents { + border: none; +} + +#table-of-contents { + margin-left: 20px; + padding: 0 0 1em; + width: 200px; + float: right; + clear: right; + background: url('img/blue_bottom.png') no-repeat bottom left; + border-right: solid 1px #A1C5D6; +} + +#table-of-contents p { + font-family: Georgia, "Times New Roman", serif; + background: #A1C5D6 url('img/blue_top.png') no-repeat top left; + color: #AD370A; + padding: 0.5em; + margin: 0; +} + +#table-of-contents li { + margin: 0 0.5em 0 0.5em; +} + +#table-of-contents ul { + margin: 0; + padding: 0 0 0 0.8em; + list-style: none; + text-align: left; + line-height: 1.5em; +} + +#table-of-contents ul ul { + background: url('img/dotline.gif') repeat-y; +} + +#table-of-contents a.reference { + border: none; + font: 0.88em Tahoma; + font-weight: bold; + color: #000050; + margin-right: 1em; + background: url('img/minus.gif') no-repeat left 50%; + padding-left: 15px; +} + +#table-of-contents li li a.reference { + font-weight: normal; + background: none; + padding: 0; +} + +#table-of-contents a.reference:hover +{ + border-bottom: solid 1px #8D370A; +} + +#librarySidebar { + float: left; + width: 170px; +} + +#libraryBody { + border-left: solid 1px #eee; + padding-left: 10px; + margin-left: 178px; + margin-right: 10px; +} + +.keyword { font-weight: bold } +.string { color: #771; } +.comment { font-style: italic; color: #559; } +.preproc { font-style: italic; color: #959; } +.number { color: #595; } + diff --git a/docs/screenshot.png b/docs/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e2301c58a4d467dc8613bb3e0f5dffd06a541bc2 GIT binary patch literal 501532 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⪇IY#~4S0qYUu~ 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

    %8@mA>j(%)0&z9t>{7CTF?Vfn= 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≪{_jS$A3{XB6R@A5pP@tJPi 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 u_Ps8A*2eVUX8M-H!WuSO9`6nB zYMW>QtKFBS$;0j0&fn+n36OM{HO-#MI3!aV?yf7Hl;l^wH*pk3{+oQaJZ~TWKQS(U zAhH4213o@7XwwEBRh9b3u`pO4egKyXOJxy(cDXGhQ2lVnfI>+Xy^296fcEY(G=)PR zHV~J}hYj`rR|%KJ@wXck1{XZV`uZ(!|J!yJJ6v`auBTVj%olCkbOjD1S>#Hr+P(%> zl*GhD3ubisR$N@%V6iy`oSYyhDE32Tya6QztOkfP3rs9uwTdplMegWWx)2Zj6_CWF z1e-`SkNFL_$Sa{Ce3UkZXWwIk0tqeu7s4taPg?XdAd}8V4s6RUKYoa(5re7{f-D|_ z*w$vIb$-XbKN!1G?CuicdJ5Hw9+NvBV*#0Ewn;Y7rpQpxhm(+yApR5pN!~_~#U^+p zvH2qiJ9>btC_=l;W6n^CZdu|QOB1Z$4_SmzMq<_7Ccd+ygOk~O&y7*bjvWZe;zv)E z4TZ*{XP;bK}ssSj93RR0>Cj9tS)2mQ*h_xCZM=DFlpkv&3rC+4km?{jEB@G3EvRa%q z*odR%!@|_7)M@3MsR8cZ=f8%<5tCx7YWKGHUaLSK2Ka4Lv)6P+kG-@G-npD=8V-^i zk}*BKO1z?cw9<|7_)aXhv3vFKuAr&}o3|byoR5dP3oUvQfcn4$kT(iOc02C0ng)ol zD=n77#OE~Tb}3Zh=+{CLcB;4k`#&5i?-OMF`r@i{S*g!@bvAYe)tivKd~K%UNy^o0 zqry}_^y?$NCdHjLIm!r|?M__Bn3MNoyR<;ukIssZS1(^;ym0FMnM>ixN=81U1UE)M zL4(KYf;(^568#LrA-ATw5zk|zeyP@m@DINYxqRp)9-Ys;d+y`aaC&h`XNqSp+)=U| z0w)bVYSbJ$#UBdY�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

    W zfvknmNeb?{h6YKqT8vTz3{m#tBKQ3NasKJneI%Kln+u7LZ~ywdwkH?YEC9%gd-v|C zLoX8eVIVT^YRIo1=n5d>wHU2yZ9NBF0bODsE{41X8`e0aI#I8|AIZZtH|GG8&uYO2 zNmWzRCx8Z_0Osh4rBTvfC**yvu~i>93dl{8@Ix-dbWAnRf3NZzZQnIFP+2hX{ohJp zU@Ou6NE#XF6nl9LaB!Rmy3^9Ya_D4bT}&8u^-80cXaLe14!}%4Nbvx%KlRRqz~(eC zfi94UvEqhO5QSgQpzIh75&ydE7z`1aK;DSiNcGJ2dY+fdGq2A+>uo11R z+QZvzv^z6E`KE#NyqGYC5UC3|;(I3+jR$oBhsv=VQ!KZ$6vsGx*2WlXpDysGH*YPx zdeP*aF8zvSy1CwS2au($jNJyd%)Ky|Ccm)V5Zz zFX1~NuKszG=5~1as7XJ=;2@sR=H@;BZFzaE*H)h#UTmQd<+qH?Ya1JK9PRN$*y`~C z$|7%K=|};YVzJ{+#3Nxb;dna)a_wjM^z#LDdC_5E6#oS)1fU4+tOwSXmRA`VCheao z(*8ny#gID;Dh)jh4wY)ui2cdd$)@1P*V|;hnyT66EBN5R+v=0u#h?qpq6fdMaz?m2 zwfD2=HD~%Jo{|5&7*x)NbRk=_?iYv}{WKQ^qu_*P?S}f~TltUYjA3{#O%W0Cr}iE=ONjTs;P%H!8+zNKxp4{fBx;TuU1OK5m? ztDqPI0`&KDC*EudRDA7zj$=;Xl8$!1y!m|jl`qH%z4y7_ef&ln{lUh|F!Sd#ih1i^^oT)A(5fhFH^}~;`W_)SH4{RhZkU-!K#%I3_JZSmIe;z{*pY_ zo+lR*DO8=dbz4+qEH5iwz&$Dzb$#@3bFaNkPa6yO_$!yVljb@Z*5}U6o&0`Calfax zq0CT5Kv%b+T&~BKhi{>KAH1}%Hw0hH4cI-EESUWB+I}hQP|vtCDHx)9aGlb?l(?+v zhg=nrhY&tk-ack#$~yU%gToYNV_=I!fQ+S?tBuG3ckUbom?gtq5oyIKGV6H=6>L!7 zJBkkfJ???FwzdWclpXfS4O96vADp*(erl? zf^YeXzW~X<6*oE#_~c@F)b<cB$VZL5I zoK>!vaB8wprRd6=9V?Y&)LBOnb9|1eu~wql=QXE`HXkPIAV~|V&A#*Ud(m{cVUt@S zB!j4q{?>mfZj8zbquEyJi=OgB`7vU$Ls^Jw%w;!^$EhIEiBCkwV8pDqdO@9aPHjP? znqRs)s98TgxO341uRk$JI6I?rQWUO{w%*^|D7bvJwPAU8(vt8o$kxDrQgg6qwt2tm zP6MpaaNgFick(*tic>hoY+NI^^oa=$s>?9_2e&~0FSu$RoR)k;IWpmveec-w_kZM$ zhz)K0hzD{_!tp5_*F7Xmn=P)bkeJaB5g=KknjHSrqC;E$VldS_i_pt}PzNQWF!htjzv= zG^MxgCxUw^yxA`nvWi8MaCN)Yi+zqV0PskOPY?r#C6DnQd&qI?-o6Nj48H_nUY=%c zW;09E3kVm=1^-lm5c`GLNqr0kF0nYO3-@d}Jx*9!y7lLhC^HUG-$a5Iw|HWV=6i*;uEBio(V? zdzdmN?r(7efdPR_f)=QKP(p%`Hhs2*ub}{TfrK$A1LYJHQonq8P`n=m*+PhCLd#kR zctK)UhD*2p?m!+mZ(%|EDHjvNv)~xdqNZZzI@=sC0}T=W z&$doZH$WtWr4T-Gl}iK~Z^g#jB5h<1Qb@_XVu zI^L3Ma&1io4hd)*Fb30!h&*olBqrPJXlH%>eR{edWUaw<(%jsPWEyie$R}WO&7OlZ zC$*&H9mt+AJa(sS96q@X(QVk8A#j<6o{Z8*O)%wva%%_VN5n13&p!ZXl*2)V`H__h z7S1TX&5WDaN{C#1Hf$n>M?tox*fGSN4Czsr4O9&bQ~+-mT?}WLX+F+u3R~93^ivdv z9;iW@&Y(?&5cVH`iz>f&I;LuY%?|4q;Fy9|vW=(W+L0(64YTqV^tMFC$LBbc{e(I+ z4-XHd!~ojCnwtE-daZRBz;mM!_H{@bC_IQ5Ae)<}u-1VBsRBs^In4b)-YkFmk&B%X znF8qohg>i+c|+uR^V{jY*?jn`S^~NSz6?R0*)H|7BNfR`*kK)%v!qzDl*vFwBGGuy zoWY4u{GHJbXX0)i4ZfQflsF|k$9JTlI3+HSxB2q3<}~}@)e7kq1Hq3stM-zsqiE9Z zNpB`RyF=+=YuG~s=jY_!i{!@rQx(K}bE9@1%4VNT*(}vd;&W5J(6GieYe)H3$%XVi zW`!CT@GMJlMCH_ZaO^ew{1JAp6Bb;ha|9}A_NAus*g4ZI8?zbfp(YAE3%u>z?Qn(Z z?aQNqaSFWV-c?%eYF=OC-l$zLuepfQ3Buu{kflKc=EmDqRx9q{(@#Wekv0YMZVMsN zyL(&=;Df$da8tG|EvC$A+3{sMn%9p1-WW5}+SVQMuOum0ZIPLc8N!FW$GjXc#C_w2 zktDveWyur=nO{|dhfkDp7v9=B=D7Xz#O4%B6S9g~D)@s*{r}3-y zZsdJ^gO{iVOBSpR$!p)=v$3C7y~N8aXoW`RD>3XoJy4vGUOhOGW*(_CLRTaXJ|kGeQ~K+eprR6A0|2}Y9p>W-3I`^!S4GBTu< z&gHf)AD-H90v_v=#7aU$1X3aeA4A|9MT2K<6ZB3e9dxNpM%Ga0$Vj3$X4Zad_IPfc z=KoxtcLfc4-ijnkmyB{3(4MSGyg`!o%|;f@wR`;!vt!ZkS1iv zpIVPr^vyO-!JQk4KPkLpdjcp4Fn4~jo087m#1%#7vrZR^9Q3G*T1~4&dXBRX58Koz zLBCIYBc6(iilvnm7-3R?FaXC*WJE;w0dG0wW|uy$rx9cKPUWuf-;jOa>jUA-kC>(v zrdir@>^!hWm7bZHt1q%I~OVCHigA$q@ zj>G|;;LjO5a)PEVL~sH6 z-VyJgE_=`_>|9)cNL_wK;C(`JaWqP4NyTa>lF!lE3w`Ui2l_~1qU(LJMzi~8ULXp* zrMX#O+j>ldT%ztk%Fj<|L&g7GEQ)PxjofT#ggUDH34-oIcD8c|izQ{W~I+1s?p&QOt1u)rIB>8)?2FR~w47Ia^R zWD+-}k2Pq`kD+MJdH*2g!vNDS``jOdboxh5PcHfyht&z;I4)VQcRpUsv3m??=M-vL!%M2&GIrneU#?)D9r~CUC#?w zUVr##lw01zts|qnJgK{T|1f&+?b|ERw`QhWzIl_BRQMQ>Gfvlzj~nEQ!8-DKxGb2N z@hB2RF(>To^0H0llut+5ozqU9s2DHnGgoGe{*YtwUgJ>x-^B^Eh1vb|jC`AHH)ZDN z)QpV1U)@^F(~#b9l2wk6UjnhEw{;D(xxu_SPU*7BJogOFIb;@c#AI_Ab{xSNfwbMN z5YQ=2F+o}a?_r506^y&`{*8M-Ai)-U$Bm1Uk`f|QJsF{!##AIEB#MfP^W!yQ1}F?K zM5Bx$T+$xP?SFw)6G8a?b+D}6Z_7yEG-E}@JuEaP0)rXSC=Mi+d6Y}rZHgCALuFzhJDAFV7;=$Qe|joZ49P%G07zz;5|hGCR5Lna@1j3sPLiiA zh=Yo&pO>`YiVc=%pq~`4TXhh@ET(~9Q~6qwuHk7xYgr!^jBl#U6bNz)B+%@ur%(Hz z?zhv~sy_`Lm7R)`K_mL2@4>LRg!ijOGq(GeQI`WR zYxvzf=5@|X_o?nY#?oe|;|$S`g{j4_R@IMTH$fY{Rm9y}fxVjHDWGIW2e18JGNCx^ zn3|_${Z=mRHnhm(uj`l9%wzA;uhhRkMu!qoL@BUFFp0Dc2K1=De=J>R$BUxG$8TR+ zk}L99;%CP3vH3KZq<_A_@RqUs3XXQq{fy4kD*B_M&dO{lhjyHb zCnj==DN@u3|LhbKD5edSU4RdM)LeBr028Du$PPoEg`V(+hsPPxT?~>r?Ea>g z2R+bc5UIFCMYVyTszpl@A|tmXi{$>7c?$bINF?B}7}y=sN6CYk)DWx!=7C<|>Db>l zeff$XA`A^Nw>_e^0^i{~udTjP*Y0QtV2Tx~eEz5~@3#B4_1y!J9AHHO9UR$3=GS0_ zax`;4z@rxYp^J-~2fI2XRaKO?Je)g-n_khMKCx*Ck5;)wdSlRC z&}jkQF*wdXe*EYIPYkd-_wtPh7k}*)7v~AfMgx4-v^7#$f27fSGkm*mzR`=ro)ej8eYXyKt-{XLVbxZLe)g5Vcp%ZztHe_ zLNHO-DLMr|e5f&wh>vfBsvZr@2y{rFI!B3A7DG`HSL`Q^eg7GlArNsdASKzua$(Mh-*Vqf4`Huq80 z`7S7mN+jbPMIYEzs=f9)rEjHpOrTl7F=e5GC{;qiN-C*2u?{qOn*nUowA$Uf4^aeX zj((i~pJyv_at?$f{{JdXaDz0_9?zsHQc`j+wCYYMy_cHW#e@X93V@Wap>p!BEoL8` z!$`a@1e4J#;4c!)kk2Dq*Ws!P1?J>y$tuE75t(hV1m^L3_pCy3u%eg-Iqlc*u-Mz9 z^0Kn?q@){_xfvnT2Jn5mvy!<&CnwMFEceIc+j)u%=>+o`K^HX+lg7l9BS}H2$~UrS z;1n%3z)Vu zRMBjUsrIyPo($mcKK*uJ!8ehZ-d1fwqHnzZ@$_Rv!+?nxNrqI_2ug?0M3Pud&)K#7 z*TjV(rm;7O#E)WpHZ1?zp?o&(1(MFOjpwE{}clX+eYM z{?)IX^BH0nWnAo+!>jSU`}vUM-b@yE>~#0;o3U@Le@3U(8XZf9HhJ%V%(EF8x!;>Ax5%^O`1El?6G3|(+_eyZ$3y$ zky50Pl2V*E4U_6mUs&A>4auc>XMK{QxMuFo+}&|)>rs!KMa|KS#V7EzgVsT$vJIAT z>a=*2XHG)%S|O}7K_o_+9bm+J{ra_jYO+R-W_Vr*G;HpqLc*ld{x8 zCzg-SBqfECUv!Cm|Na7tyXidYbqJQ`V)RV`A?b-V9PV>54gQ~ih`QIx?q51dHmc!u z=_N6&j-}<1Mrc~vfS%c5Fm?IzuP0D6yFWrcIG&6Cqm#(z&(ts-HdS9mt?q5~L7D|j z#&!UA2iZ_aX@Cc3UXKIwF?gWCI{yOXA-Hu{=6Zo}%m!ucbg&i%2;+sn)QivkSpW20 zC&?OWtc2E1Pt?(9m#ESXB!?L*(l>?cI*73Fp98hx5hzJ%X=&|oyi0it`SiG=oT>Uz z(`4eIClK2M-y`5>?dR_V>wAg1 zPyzG1u={k^65Vp+kl9Czm+43D;v(ACML!=5zfAgi)hw{v_Arpzi~ex2$Tj-CO=f3v zB7KV&fAscT_t^t!jL-LV9FfepDs4*n`BJ8l^Ej6DSLbOn+P@_R>S@unkZA zFZi)<5cZUV5jVg_z*r|M&tSE=fQjHA#6<^-=c!^=OZ!PAbR;&#T4cb`+&lU#dMg1ckBG)P>1<_<6jkPR_iur3J`&`%f-IA|sD7c( ztZ#l{>GzenpjyRa&jSL_vKO;MbK?ZXl8R4 zxBlkW_>ayFT&5nHc9;pfrY{uvM$t~edoB3Pw;#V^h(54E#sfb_$M%c3;sPzLS^r6T zuj5O8r?-{^3@P||=J7}TDaQ5LYrprtb%9@P*q|TP%mZH;FE1~}#fy*)c&W&!mYOW+ zAic8(W*p!X+yGbLF-}fS@U#LZ=Q^yHp@{$t#I94p{J%8Z7)E#Ju*9l$jcQ?u8hBR6 z@bFj}l}Fk-#Z*3gdEm9Wv4;V!nc8~{!#b$;nw*8uSWZC=nFZadDi{7BRZl-)l2{?2g?-J^3_`K#ai`NFPC z=rgigUAV|;{bZsx<2KMvE7z>6-E(D_g1kbgv;1kp zM$2z?PmfVi;Z9Hx(qi$U>J={jUs!_GwG}ip4#%aOY|o^oq@1x>`UWB{gP_}WDXGKa zY}81Z%`IN1iIMWs+qXW~cHK@`8t5sSt%1=NHi7|qmCmG)?gBz+3|y~sNV4|SG2F(_ zY*&Vk18@t>=3;)tdL0{6owhrR`nj7rI)?xr^S}EzsJOV3cIuta(2!^F?s41oiF*kN z3Hvp;Lot9P!2ab9lZrWxEU@xWQ92kM*4sPN+oyNWz~H$qhXk8)7rd&@sm@oozQVE~ zA}p*H8saGMNt2fS2e%;!( zw1~C+hZmp?5+JF6^-I@Peh^c_nwd2Ed-RFY@O;V-4id1RK7*)v+{W#y@g5$B=z73& z9mm6eY-IEua*DA$W)X2XPjV1Q(z3G&;Qs@9{r4tX)F<@2cvv$*;syE&a!!~*V*AK5 z4fh2y*r3fIYYzCWPuj(A0NMkMF!DGTz(RQL|Mo}VK`>QaJAVce&Ttbjl^TbIHz58e z3~=aZb4vwz`N^5+D6ECFH2!JgD-*z;YU=C^gz?A-lhPU6%qIiD5_zdX_5<;g=oPP8AoHzD`0EJ(P;$0cef&zZ3FqcPmJrt@2T zs*Kr$I>YeO(fqpU)0JdLW1Bq>zY^;uJVsw8t747m-B)LrAyd@t8_(l+=4z~KdZ}iY zVOsWKB*EFEE;!sEczU`$^77WW&)fqQdPragYvtWoveNRTjP`~mHXGYqIW`)MLe6q6i?u792xz zn2khcXKu!ks^BY4LUB`$<+n$x6mU#mp+*skq+Io?Lr)x5aBAUPqH+v$`%G!$=BqnI zvpdzNDr$?>Ht##$E#&5#^)DcI+g?B3prlk58#^9O-0q9-8WTVD)Z9;1B^d;4j)DH{ zdEAvNwGRx$(ytu(R#2Md9AP@xKHui48>$Usj13CFB*^7g<-MEjdk#Fo=q;TE-r{kW z>A8a20zm_K{so8fq6QbHOCQO2tQUWg&dgUCN?z>w{kuNvjXkA1(PIB!{Tfg{4*eQX ze1l9WeAXk){eW+S6#(D1q zZ-EBz8Q2L)ZnIPX7(&7d0Q9tp!mXij!f-$%Wi@TloO=XbBUNM{0Z#=6G`Jz3AtL%d zGI9kXN3h9o+;KVn7oQ3$Vul&@IBe1@w0*+wC-W`v@f|0m+T;?$P$(mODl;Uf2TeV9 z!P7P~GXp^{x+s<=lZ(+UWo3$tx4TaHisvOvWpm!Ln+%8R_Zv^FOb{h%5;+sM}&zMiGInlNM$m zKO<)wawLtS)z0#B71*n*?gak!N$&)Y;LYxnqLEt`xPAxB|0-vJu z)IFVF=f&8c3N01qPn8FYm@x+x|X* zh;`s)O#kv=0VmHuhA?uhF)Lk$PoIME7V-+QonkdJHSp*TgLkE>LH@QuPfyR^#o7G) z{D2_Lfp2v~4IfA|jZ9WQH4e)nk4P$TX%d}xmome4R4sgr#anlypFB2tUMqQbu+!*Q z(C2&OvIg`RXNnt?#X`j&l(2p;@gGd7ogKTN_`5{>r6_S-UFXXW4Rnc&9+!Z3S0rV2 zrv2n!u&R%QsZ3;+qjD{1kqVlKrKTa|DGK3#IU=(+F67N~jP+3$_@e|32xM1Wwgn~clNhRpm zha~U{?pM#8ejc7k`h_v=lgf`faiD#YN}vU2mr(s}1uF(cLJl zc%$MVW!Y9ES=Drm7hUmbi%nk680RD7%Km;wWZ(drx6QK;Qs!BxB_B1ERb+L&vb2ny znf-<{N-&Gs+uk9NUctS%S%ay&=vH3EPv=AON}~APJ&2k>Y^45hUU^1z`I7N)bWnEE zSx5P_2i}++E$8K5U5jsC0q`;$rT}=v4QL*~hs+0_oy5e%cXoQ!;JnDz`+WjIg1`=H zU~vy-gH2fU-?AFw;jJALxRzwZqV&%rCVY`0ysV< zAZEpQ`!;E~tPeE2+OOtNrerWR^gEZaR72C4lG0NR+oevp^}UgN;4Aq+?#b`aUqi(8 zt&5tfLv0qK56qyd+uwSRxXgLw+G&zTC>8gYlx7)cU`M*T|Doh`L8VcX*_Bf=p`!Pm zeh6F~EU4GNF!G>P#_i?Otb>PbPLv93Gz|2k7n=L}F8=HxpPUy+uXki;Z;f{F5m)OF zE!GX0P;odM{bZ05mYTsdKmF?wM4i(#&1_0Tp}O2h{5p6Mm-w*4Jz{XPoV!PBP<&4Y zIdlF7h;@II%icr)V$^}7NKqMp!rEZA_zR^HsUn(Hj7w>$`6*d5fPKY>y|1-79=vPT zoy><`IRm*kV+hbfzb-P&lzf<*nF$9MKCBYm4p&M+v-zRdmB@%wAKpkQEm?cwFG=?V zl-Y*>$6HwmuB_bbp#-o?%+Usw&)8?I>HgI0yLfN2989p7GM@HB)1+~FIyy*oBQEio z(jv>18qjPsYSE115)$AA%%0HHf<$fz0Ivd=k^uc7wFA$pl3M?0+jzu>KW*ziX1209?uS;`xq_M& zfWmG`2ARi`<{$3dc-W^nKc-5D3Vqf~l8R(W8VGk-89g=8VKp~vIwJ4m8C;fyaEfHQ zBv}O~g<0@hI2^Rw?vAlBSr}0sU&0#_dC^abkF&V-q{fGIUH=$C*2|A!4CdW^Di_bP z`v^1%_N;YzFOs9~;hA9V@!^~a5TN`vZy;mH|06`LQ04gJUrp!BCG)<%xUTlz^!{U0 zi_GGqikJ8DiRmm2E|E6Tof)GE+V*=}}2i-4|jLOClVeW{z8&21!gl?!ez1V&TJYJ)4aJM;7YTxAr(Zu z7u;QAZuWS9!-XNjJY7++-7r+|$?mdlW60bmf3ARMg#XfB&g*N9#wJk9XH-Ntp3;A^U;YFre%N z!0H=2JKx*dmO_0o8f4^p1*IfLv+Y|AO&39^k;h@}%weglXgqeOV93yHVbll%bK6W# z{)nAjY~%7^d3&tdYdC=cqqP;U*xwLNzjc;qbluni18<-pGP&>3`$#Q^d7i_tQ= zx=F2Hc?J=Tl(DffJTxM+vsr*+74Z7Csgu+btpl5U)X3UjH`2zYCVH@2Dk@4|I}gh* zLC^iq{o5h421-Y)6a*lZsZ(13&<}DcK&GikvpVtO?)Pyx_yoXtljX%9o|u^Xr5+AVuE({<{w8T5hQUhbU`WsY5q&%! zpyG~tegu*1WqSz~g+h)6T0As5VIi;Xtu;lPe14v2DHMDy2!Q&J#AEJMQ4OQiuU`+r zmmqWhKIyopgN3&kuoXC~x5Q9Yrl%Put-k+T7Mg4;d)9|7T4jT`ExK z$jr=qO<0&ZRM-79VpO{17eJjCVdSk>U`=c@6^mim*;{Jj^t+!GWP z59}2wE}CaQv|rpitsD|BF71#~@dh{jY=JR-_%R31W0U=dc?xl^vkkXDZXV{KL~uu2 z2z-p)H0>2J34w8Y^Tg4COH12uI=LUQ(BaSDFB)L)0BjsBuS%%_Q5wgegBip^^Zm&} z^R~FV7bo0h*KeIHbNqSlH2l`T{D;3k9$JWW{-@4)w3;cXKNTy6r<-ZFx^d+m(5tb+LNATH zB`OkCn8?+<;{~Fe>Lso=V~cM-=k*&XF*3od^E{d3*;~;fc$yl zq|;AtYvPBE?XhzJ*XlW&8X0q&9#5t7U{rG~^FH+%5-so4hVE{i73~I5ub1vsB6}aa z)znO`(9l;lq4w8M8d~R_F5)V%DH2*p%ng_W=@!Yvqi-N*$Ctm_U94h~EaXUnpa@_; zUQlS^fpq~GDAyM+oD_L=X)5;-{FH?5%&QJdnOF2n?q*wt_+0(noJpTcHfPm5weK_a z7N@BUyw(Wu2xc+eJYGF=p;pvJZXnnx)0hihR}r}#n?7RoXVuA@FZ!i)3)TvIP6qH0 zWZ0Bky4St6mJ}o^aJh2Bxvbsk`ru0u()qL7$1Zu<^3c14oJ3S0S|TP} zi3ysPHRTDiwU=9lKM+0_E*}$RMO4&1)Y>cuhb5#QVWtug99=X3#h(tkq+((&QF7`lK6(^8Br|Q( zWEd*qhlEu6Ki$Vb>1HF?(P4hQ48azcYQ4C{nt>xH>95rVNQn34%a;h31)gm;n3-S0 z6*F@lLTrGYKr9c8N^uQ{FkNPss!c`LC38jeij10?TmI5Taz@Y2-YgkgXYZ3LolOCO zm5QoA2`*qzxImWW^ePOxcT_{v$3S~V^5Fv zZ3+rDv_#&$(|1W*;kTatV(2e2?m6Y4@(fkFxVN1k^LT=_`W-H5nRd_psC>LhOKHnL z9i8m8`-qZVTN0Wg5lt9GBj7k+@Y-MhZ4UVkx*E)Gyt(2+%YnkR^Kc<kBUZQK#pXNB%6@p4(j?K&Ph&R^!)QBlII>5Jx3X`+~kQ>_wd5djomGBzwjTu3r5`75{m1*$m6;=%aaX7_W0H zJ70t|Ep5}IJSOp!TUAu-HwEmA+-`!Mok0q&xQ6wDn9-7yGq&YL|H^z0c3u}o#I8+6F_IrU(Fe;rmX2oxBe`=QV=19TUo zdOsqocUy>bQg@E?T4alQ&Bebq>b9L@n|gjy$&6V685+7 zjlg0P{~PgaGydO_!cfxJ^`0vil9B3=lj^-0HrMBL37*i4;3+AWSwVe<$wIKm6a0A zYht(~?$ab!pFtU8!ksLP0wL%iPg>vB3kWJTw3waf>S6*Q6?-wy12c9Fjo9qD#$W|= z$J73|&^v*31N@K%QLwd({osU|YV@k#UTA{NU?XHnLPCBgZiT*igW&1_tD2C#27AoI zygo5QY5Z^Bmf&?ZHO&dm{$T&!!LGe@^e0Mk{rn}|lR(jj54W`}#^Fdrc&xA|&B)4< zGpacTFOq?kH4yMsg@B5Ndhzj_*wIlFE)(VJc=+w#$3zKbnrxR>>Wfxs_qO)8t_9`O zbkGj<;$Xc1Ck$l>tyqXG!-tfV7clQ#z<^0Idk*#-A0b}?tKJ^)>`ZM%D>BmGxs!kt zSVH)19e(U^J4vIw`?CQD6WyiTPfvnflMU8};K7D*Z+HbOqjGRsFRJF>)VWANzrz&+ zCPJ4E9He22Q^ovG~QrDA^~~540d;a%EX=Hy?KiBS+Q$~F0A(|9h;Sf z+u;$#Y^wo(NCq~|pv%Ds>ks*Rb0aZ8f$i__Pt46VK>+bA0fD(g2q-M|Kpz5G59V7i zgVywK!`Iq_lQvx?`G=Z{h!k+J0xynlhMN3Hko!Au0$WgUr9;_LQ98ibeCz#5kkvM< zt83v?;kuNogS7$;1nF0R?+B8Hf&&#uBE_=?J!rLItXIZVDC3C5VbAYxuh6UM`iE!Z zS>vPdo+eXyffYDoT-?ziKI$z$>|U+y)6EV2G@`r8aq^i4QI zYf6uJ-sQidR4r2BS~kq=Rg>Z#Wc?D(-&Szh+_zT!`PXph%&4mWK%K{9YrP=oXGP`r zBI@`tb|-e0>VH;1SU^@r!X`eN;!tye4p~v?D8F3HGuBpx98}-K35v%9|9c5!B-}Np zCtHvYdkI8xw9n2rn3D3aYb;tdWkXr^ccNXNnp!l+)+F4=7*86%th%p^Wja6_KE13( zX8c>Jik_Z_J!8i7K-%%|imwAI&w|oCe|G0H;pdMj@9zm8-Z$;q)qhIcXOeBFH!);o zU&#C5YnuL8Z^IYvjIieEpwBCTv8t~N?%wUO3X#=m&d)L02ryhe6IwTQp z1JqC?WUDOz8b#{A)F`ZX>fvG*0Z(uMr1WJN_)aF3+*iZwMb!Lg-t+!6-lOKGu?ip8 z;g4-I9qL+vx40I27)Ki!pMyd>n~_|XmDOU#-2tC-(B)_WCY<&sZ}RcM%>MEu7ZoTN z42%`;RhhlY7Z=YdChSA!Wo5YR+ZSc6tqzEGq4`eH&5ebeX?3PG%I5ZMyY~eJ>bYMV z8@cYj2nfJC1`vLL!`wD%r2zhk5nuvh09+6o8+&g3b*XQMgS zmK||r<4wt@pLnjAVtNY0^Om8sbe_eunbH`x%d_Y8|Z9LFhx zqOO*%_ew?x|6+1=yNLN{IH(4Bz0^|QUz(d>(T3}k18T~;ll{dec#2Mt_w zB#S+lJiciIU)LPRYmu3+g)T+XflN%12?;^*@zkIq8-Z0*yEs1;Ea#X?rUC!GZmgs0 ztrREqX7@~9aqjCzhi$h$bKQMR_kMYf%ToH+dz+8>;oib;$O1EEv**WHbxkVuLM$ytTiGabIuRasxDBKPiHUFggp|H34Bp|^?#)Yr_)hcbg-xxgjUWS5DXqb4t#pljO2Jp38Md+UlVCZg6qopP^B?zOX=65rIX%Op+dSyS_iqWPTTne4 zQ179O=B`pP7=3;iW*B2-z?JQ;@?MQD9MLO~2o#97qk3-kV6w#{CnpDA!#xNwz+33= zJ`Nzax3sjF$1BiF2mR%&f`UMy8!jh?TKBwjaqGY95i&cFNnB!-S#t5-zDO9puM_XD z*zZ%g)!@7(Bq#`rL1a+~MY?o!;b+v;)MiGs*u_;;UgxgZYPfqGs>a1NWKZ-bKd}&| z2>3`4@G+vUj<%qf#xJTb_kB_Hfak|0b2>N6|07te-6S0Set-?*(9dg~$)1_F#a^06-zrJwB+U%U#-u4j? z3R(DtGrOO3hmt^v_ub9tHqtXE_da?1iiad6y;pN)iRwqHIW&fVY=|Tck zA>h>7N25y!teqNA@UI{Pfwym=U%L}hK?Q?)Fd3x^Pjg1eAM6Ym02;bUuku7u&gOt* zO#ld|+!p<8%85eXK{VINx7peRzyR{OuEOjN!(cX-SG4AQ=Ww|Y@@2(p*tKeFSdynqLY5H`J(@h~e#?FWvM(y9{&4y1?LC2KWBG8Y3RvoDfa(*eV^;Nd5D zd8gnIV5T5Qa-BS@tE;OX3aV1PSCI(rzS`lC4$6GEW|*2*2v6x?%@?z<{#cxSQyp!1 zQ|_Bwf{r_}ukO7EwqzN!VnodOr_LST2pU?}j!2D=b7MFKI}9Lb^uEc{uI(*Rt*VTP zSnFGx?|oZCmvy4+GHL2pT4Z<`5UXz14*wn;L@m&?u)45JETHVdu&7QUA$ke%-rdjm zaH&AgO)3t^SgrIo5i(X1h4L@XCvYF5r@tBOWLA01n!Eyw^{xgYDIRvFdqZOaSH51J`Jb-gr%h^4~5 zNnexXYr<<}xE2rZ%jQ@Yt56>#1kB}z%3WY z6TCQUteKwNrHxNk9yDW?<;i2t1AkP-@q_E%|6Q=yxts(kgqDsD4H2z8<{Hc+Rn@zo z@IT0+FfcFxgJ%nLEyJ}0SXWR`)z^A3@ruNCtAl^I3Oc(*q0a+(q@Q?k446uwhpuT; z^Y)=Q4|+7eR+nSJS4s#lCRmdoTVz0D0k&3K3SuYIm1|$=>FMzxk_nVi5JJ>WrMwLs z`X>p(9t%w#|KCXvt*peXtlYl}n?jL!t)M9n^om#I<);l$BIkh>7#>cFJLp6~U=G4` zv5=Sm0Ot?jGO1&lA$J0UAplbUL++%&wnqNx2iD!)l=cL-r2T!mr%xX&jEgA;i@}_S zc;-FW*sCi);VLR}Ja?jmIXHS^M1-Rub^@YB+6Qqj@5q)o0sCj7DJj(0pPd7vPf>dn zSRHB+S_g2_fo$?RuuW}nKBP1{fwUAEoGuA)y z|6O9D{6|%b3@i2m!6~pSX4gIh4-Ry(118jZN6fujY(D!4hgm~8067N!ELk}@ATr7; z)re%((<@RQjtIl>&4V_IdIW?eQO%0(>iru38G1e-V4Isq zwL}CX)zW+>%6t(e5&+j@Zrh2wNE96LVw#(?fD)!3(%0_|ME-!_U?U*c0*z~s{H6Q8Fm(7D4>{NJ))O8B zo7s^Fhvu|Re{({(h=e^X#VQJKD}?q>J$u?3ZTO)~RqcC(T8)^k{Rk|F&h=>=F)=Cd z-yX$xNI5QC-(;`X-N4Nhay?4yA!Z2@M@S8U`8j?X$1;OkuydH}tWnS(<4e5dXzB}u z3(b7}4$%J)kz$X%#AaOC-CYO(>3MlxLi6tnRcKa^1Iqh3^vxqpd1oa`vAy^Fl_mby zaz&-q*63Np4&#tzQVK;NoiRVFQwY5s*736ZpYI%Zy)%_k7%KLyw)^>gv7o2Te%I%z zM^F-TDROGDD`Pdot%qy#XWktUEjWuNf-(bYEG)F#t{o1fhjrZ%oBwu2xZ(t>gaom2 zLf!1o+n46Yad|DUyJ&)+ozt7Xl*8HC=SJ{p1BkV7;w_;7DsHaz?Syfwnc&OCFJO&y z47zi2^a6insHldP&gu8sm@fvU8a~_GC&6JjAy4X5x|zR3E97_q(J_GwP_eu4GZ)4N z(=Brjy+g;{ENGX7UrrE1T@|h@nL|P9fsTIo&lXjvzQ)=syZ#k%vEIjY3#DiIt+TT8 zq#;2<-7I!ves@At1e6bKW&rw1catGtvwlXqhc_kl=;C;V?84UvFm5*whn>FrqsG4M z%gh_)K$qk0X!jt+D*;X1S3v@}HF0npQ4$z`Nk=u_7&|rBlu&TCAO&Y|NbbZ!kaL&y z!sx%!Gjv|ZxyNKL7Oof6uE_DzsYpJ>z4nS<>|o zcb^7?T)sb?K%Jl}ak>PoE`d&G#!1(cUa4sF#PRO&2P;&Y-_A>W3Z4*oarIa#Nr>_E zk8lL7wVwL>r1hC9`h(H40M2?VMDnH1^SvNg9d6s->ej}^4Y}m6NVrS12E>rRMqS{Z zUB48x98}R)a>5FoDUuv?xqcvgige!0y5_D*=vtHV^ufJXXA2AGP8p#75-BngyLxa1JR62N$^OzvpLl?->s;Q{};sAQ&@ zwQ=1G%rYy-H;5_oErScjmK zs0HRm=((7l{)qMh_|Ek7^m%gfdiV`UNG@s64pt8wjjrufg0e|L(EyB=!3rlkyb2YKeXZvML?_pO3&8!^Ll2h-V1RUGk(Y&A^serr%DN*) zFJEeA&Znf{t_z`Ebam4w<}bad!QtxrH4ljLGBaVcJ0+f?8ik#;fdHv?pV-p|ogi8q zI}~MR12$$UuL#o^tn0k|c`n|@b2VUjO%s!pv~`BiGi_jVJQ9;y8rlgHVce{W(vHn& zMHF1!$tfut;pKt{VGRn({0yW3S$7f<~EwrXa?8lGNubmjMy&#l)2*$YW^l5=t z1}cGFbPjp?^0P2;`#pZRkfZ3l4>?C*<6*>lXrtoxm$JQ_PukenfPMkX6$JeN%q^s< z9E^E-1u8G@B5RknckJ|FNr3!z=6+W{s(N6h=N@;3g_lEVN-hRrAwey1@jJFInie_nlAJC}I~{^Wz+#2Je4eRjBnio=et0*Qd5`biH1b zR=UOIhAa!i{-^DG+ZD2`6^y2>`Aog3Gt%cEMFFvj@DgUC;jWT0PWu6~4UW6lwZh=W z`)&3_wzB5^&*jHJZo}`#&*qT|kfK0oD1x(ik#m6F;_da^@Dq=r8D!~%R(Fzo>Ufa% zr5#@JB59w_?EZ7w{}=s2;~nSC@FG5Q39l-HO~$#7D-`y=i!L?A-iV6bU!PmBUDyD( zVC`elh9>g*=3 zaA6=-C+OQ=_EmiGpCTf2=aO`_UCwgb6e_o^DceP!{!bE!Hjbt^pMOwhVeA3uJPmt9 zz-0(;d54XY&(?V*E$7$#VFI7UT9Wq0`g zzY;VkUxW1-uHnBYC&dBc1>8@(&k+%O zV3TK{(xdJD|0HJ+?Sy6t3;1;98&G`xdbT}L=Os9lbC+oOSEcezp%7eqyM@^pUvx9eAEljmZDODXtEO>?*YEyi)=&yJw6 z4065V`K+D-@dXJWfVCtFa{{5NKtdUk6CD7%f=GvF#=eV7a!Z1sFX5$I7d;Md0T&FW z-=2zpggKmfQ08hipc_LIAgH6CZN^2R$QblvmWqENK(L^Z77Q>HK=N9EF8no+z~LeT zakjX{foU<*NfmOvNL;suV-%9-Xh5kf<P2mUp7R``p1~WW^n*c#V*tEJBn|EL zS2MZGcTuCil0pb|4bVhB$eRqzKE=mmWv~`DpkUCXR`&CY_^OCu-onNW$_G_|4FTV* zC!i&04~C|sT!)JhbZyve&))}eduVz5PmBZJl&|3L1_$s!+*b&4C@0-%fnyjkgM)GA zF~sFq(6Ai13Dh_s%0Nz$6tII2AI9`=v+I=D0?vj}IiVTQafm6^rrN2krlYy}F^s=H z{{CAd4xRV4wdEZhi~5gTjJ&e#aB}~rxVMh0a$omFQB({B1(6m71Zk0Ol$Mm16cCW^ zl2VZ_X#@dj>6C8imTr*lZU&6|jJei1d+oK?-Dlr(_xT(?^A9I;G8o|d{@&;LrvJ{I z&S9Qy8He2TetnH<+zB>Kogxz`+-J zDH1~PLG>q>vE*k^sPdM=B2tfS03~g7rEs0c;X;d-gE{^T6P-?9F8?ndN8HCH99c}W+gy85ESb~L`1!x(-cUns z{FD`0dwlrHHH~QaYA#=2?Zpbdpf&}?1D^MYpk1b2qQ)?-sZMGB&WKa)gF+09{DI+M z8VvWuT#+$QTd>?R#SF}6f0`mer3addKDxZDR42BFU&0m^5X$eRH;zb8oJ=vX<__v~ z)@wq0RIZ+w4gG!q{T+8#*TN3dPi-BKEi;q8d1`r7{FD;G_^1nG3@qX|Zrz&ed%Tc# zjB133OCA`!foP;XN(?tY|MqZe+I_sa0v*slVPRo8HMKG0KURA$0|9FTVFna}L#Nk0 z@WDZF8>CxTq@R`zBlwmNoKc}!kbPttYg=CRb=CN;sHp2$3aTRfZEFYW>_LTQrJ;fW z1Q6TkAY6ko20bGqU&Tz41d!l6!UG#3>%fzcb$L=zLHKG+P30@(!P*M#`PbM=<~Ma3xmuH1aqcP%Nf{X=U$ZkbS!Rj^-3I?TwNpmPr-lpu~$F1Tm&c-C>>fOV(paLfcIv{FJB=J19i3wlfOz|MrHBr&VvW1><3~Fw#aQz0TenPnGgCK zgINt4(MvjnocBQ%1TSl3ZoatJV{uXn{~wBn9~h=P9|siyG#rCbqZ#-b$PO$-dC$g% zBMyuVdxws9^3PPbCy3vX;uS)QXs?v`=mn}x{7&@sCuilDv3J>*iC0Wgrh~wQK^scT zt;GLC)-a_x9c39p$`d9IXl$D?wg$OiwgV$IoJ!{|-u@l*R$b}2{Bj*>2&%4HdTc&=j z@V5)!prb&BxsarDXZ~hDr?(2Qr(Eh9isc%(i9pfk}Lx0Igr%fRq-H9g0HQl`)jfraC#MO49XUwa3g@sc^ zQvx7KfV3}VcX0P4g}*(Nb^JVhS*>;nYbVg7kX`dOo;+NSF;<(m_BvY0A~q$n#w^;96djzrWecWE zd3E)U6p1kNKoBHndU}?iv<0XR4?DI%;eQhF&zHl)9Xchxaaw(X*$y(VP<>dSV*oWO zTp`Y$Htu;R&b?~_F2_$%>6-8|q-kL`c|5gN7j@hFDwifMhD*W{eDU+ zDpYL|w2LzgL6>i`XrO#*ZTUzw;lwEmh(pLj6d#2s%Bvnzl$89+FRZ`|KirK8R#cOf zXiFTgM$gscX|4BQ)+pP46cRFi2_Da2wLlKJ5Cb&;fF%%kippu|H-jFyergoPA1u=I zuBI(Z4G%uOkvs08?hYNu&$Xuo>CPcIOAx4&`ct-A_J?{0UAhV7=P~?`&T0~_^uW=N~aB=2G603 z-{T+bG1@=7jHF>6GETtaE3H|f(WtP!0r=vVN2Kok>S5;tgEUL-xBN?%0Pp0cX8_P- zEZHB77lP;ix$z<^xdb65y;0eJu(IKJIBE!4{Ki>@JVXMlD19&;>E`VLa@kaY$k2P= z8p~6ci6Q707Q);`|_BXo*^|KkX|B|V8*z`TtJdAae6b! zpTk)3Qa($+!Hl^;Jie7e=vgo0$XV7;pLaOMxPXk zeXc&eS)zKEO;mxk*}_(Z<0L1|=&ed1jlrbwaNzIgLL#=vXacroujl#SuhU+q?Hm7a zF9W@^b1T?48MhtN@3Esmk4*x`V)fCty_%)!^eGz|)o9Tky6JjZ0et|6x(+|&L6bGW zO6_z!>Q?seAn*v7I#zb}*@Xp9fKot?kpkAwg?2o^Q4)N3ohDY+Fn27bst6(L>)pF2 zs2~6U)K#>cL}(oB?DVH=T!GIAuj~^>MpV>YyTlWb^pcNrEfhB}H9&sm#Jkt|K%}xF zBQlQ1`C*9S)A#!~(qy^}+QZi{iA!+hz8OYkl61Bia&9dOAsiEiDTKVoG;)z?7V#AA~0c zN9Ncm@;Ass9-v=yffWQFvX2tl!t#1k{RO-7uXNpABSAM?WojKg)QI?mFU9qr|qnU-Wt-pRP7qGlgQCZqPKjO2rBFMM4oTJK5n^=m(Zx~OTrE%U9@o{YVm^ND4{2$IR$3BchgzI^UMYl zR%dHLl?+t*48$-z1~&s(C;+h&v)rD<{=t7TkISfCBc-MF05Vo#?xn-%EB^TtH4B3R z4b&#I=&RV{0;Q&-z<~3k#%|pLg1=A- z2sIw2elGBFXywO6*RNT40ysK4ZkN`c2ZHVn2yDeEQt9(_cyhvg3i1;nc;}F6I)p@Z zzN1=wDJVR^%LfkHj1XQrx?6x}gMwHDu342lEf8F1x_mi!2zckr^0K(4W&SRyw759@ zj=?_pqkqfoEZqTG06}2K#bB;Cs(g~TdD1;qgX75iZs*JmUuUV|iixeR?qxmh>Y$(^ zk`?|$o~5*J&OdetY&pL7;Jf~KnKj!!OBdH2RAb_r@iK?pL?Lp9?C85*w$OCmX`f}* zN6A}Ek4(b(l=NFoIk3s&-ak78-4s!9T;g3MF^=dMiC6WmpDSGYXip(! z$@iG|Hm(NvcmCCfECzY~mCLd>>oGp5PsVJyT|eeU8y3kWjhFG*ymn;^JMAcQDz48B zesZh$HTvTgvR<}+{TW&G9jFEtKk8AjeGp9WEBpZ;4}Dc0hIjtRc;2smBg1!%1>4No zEBFI69p$&5d%W(=;<%g{y|!Y->cSfP$@@FqDndF8`u!lOjp1$8hef-hEnO<noY_I;(@!C-dv z>ecp~yH~G_b-=Gg70MmICkT3j_HANzNbB+^_nWq;ASC!jcBrbCPVkqMmQu489~}B~ zpM=Rc_c84YT;=AWO%^}6dZ3}{775G(O~|*=iN=W5o7MT`>wV+2#7(Mc_1dKp7m?Pp>|(w z%)d_aBPais54>VUWQA;s+tD0ZU@0*d%LV4Fb`vHS^A%Te)Ya{`W~BSF{aE}{j@foe{@YwDE|`Gadf zp;GhlL3>RL3xwFhL8*i#;odjr0Lje{AAW#9J5&!iT-D2css8f(rXwgR_9*m#1BQ<> zxnd98aSENB6j#~hxiW24p z5HNp&0^Ym#QjUEd(bI#N2O!uWa3|Fg5)iz*=Z?G}d+KtGt*UnVgo%ZvR4nOiV|@5z zlZW_leaD5q#9eWxvZ5O%?hR`8b5sKtycI|Wa;Vr%FN(@rT@~Qr;ej6>SQnI17+|&P zYU4Au=%b)_xch3mgLH$t#=BED#8EBsF`SyCgbEJP%zD=>YA4^HiNql!#QOJ3 z2at*?H9tK|O4tDorq`;9hQCTkGtcbI6Ocs8f8R@#-y^c+s9Ph`8VM>~*j>v+k76Ta zo4;U+`p)-U1G&uI{Gi#9agTC?%t#fxNSRK>yJG35ZSlK`Nnr&gTs3kAoF&TH{ZF`> zLX|hmkFP(qXKS{Kq%frDnXPGzi!otK$r1l$Q=Z-&7VU-Q#j3>ih$i&mUCA#U^9)nz zsmY!_E&R)B%b`^d35{|TX|lR9Qxmf<@zL?6*vV6Rh8pzv?+cgJu5=F`rWT^CArEvA3gunw|X z8U0ShDxE&>+ZuQC*%w}pUY^-c!iAelQ7e1E<;&dNm80($uZnB03-Jh5DZdY4Wy;Va zvP?7RwBLKPU+{~5@7BYd!q8Owo;|mO3lfIT;U}1`A?yN}#PyjS5>@w(h`qgnP0M@n zeUD8ApK9*!N84~m#-vfDt{d3ll~Uql?;m7Du)mU18Q10QKwNg*dvRI*MDy%C%l*#K zwQJdulBqJZO-+sAMX9=e7nawp)K|3onZBs>d?T+dLEV`0Zmn$%^{qa^r&o7%C)xz0 zD{5gOYdT9!_K1kUgT)J+=B!6HuC9M|kUMX%rSw(ms{sCwD$oH5nCW=QhHTdKuR0Ks zKuYEvAQ2&c1p?P^LIRsEz_}!xmNT+<-Qg=>u!V>sl$rG9OL;yf7azGRmBs z4dwXl4zf}6x(L)FaGt;ZYCJhNw|9-7-{MPr&DW%)={vr*@K0NpQi=r# z1|Tu$Ca8MAsmlr0PVgX6uqo{%cK0T#=jU%z(HLF5c5P^XrRbYvlyX>P$z8eZ5A8hy zQZn6c+1ZGlJ!k*M#s!Va!^aOE%m^eySwF(4qt%5HoNGiU}a7niI~IbQ*VV z=aWo{^QHB5DSiFNpuD+v|9<8`92!hl^>uYl1{ z3RJN`;eM9CcASRhd6vxoJb<9Tdl#lp;1PnwDRmFmMQN9I` z&5Y+Fsy%XZ8Bs~t%F4>9kR5P(q^`rj8Xg{wa=SonIb1U@yai`Ib_htfJJ|gZ@VjWL z`-tv@JU;>**Ahq-)02{3EMVpiO=0u#@xcI!n&}{t27t^T#d%vJbz@^h5c|)|hqnPj z!xvBzD!L2w1E{6D{P5`VOG*q-hHn`41BvlA<|M+Xc0fg0@U{&H60v^|4T;i5Ktg(6 z2%>|j7KmI=Kp@DeD#sRAiBH?O)ic+7_QVxhl?}UmGAVo~V-R#a~ z?H1J4#Z%=W-yft!z>Yw!V3sDCDsT5is^F6FL4PRw{gI(4YOa43PGb-7YrH9QTfj3i=)UGPyLvc?=Urljx;ePooi*^w zXP-@b7KQcnDfSQ0V4Xn?=HfDR z+`AU1b#I;SyQPJWzg4l91QTwtZSfDOT+>oFQXPTR!218Pkb|20+G0=I? zrTC<|@a=-eOq=FE_xsCW9=o9#QvbAg-2H1Pj{=cA`{F8$*gWg!g?%b6sao>dJ+rr# zzKHAZm#+F11Yunp8ym^$u_KB~A48;S1E3a$3H55)rY&Fjb`U>zdo?3U#h_t&D`k4KU@iveUL3VtBve zUCI4 zO6pCg&3m?5?(3P62IS^iRqk>@5+}G2Y!Js*?+@48;Ks@ReMLpA$$d>Z^chl0>YJsN zlg!+WTt7e7)iud4FV`}Dh@0%h4lgBlDB zV0D0`MKYsPw!m5jCzAsU=Y4mAQ@hXSbPGuyAL!p8a;>th{ygnLZ* z*6LKPw*<(=!Hn?>3e3}{1x1^kjCG%FG4Qd0mIw88DK0LyZxah7 zYWndi8EQlXsT7KXAw2RJ#BrlPP4M;g{SFHNYlcwPoU}x?6X03^P9=etayb~BJlufw z4wd-I#^!fu1{)yEW6%*gJ3rrO)5DkkZALy_Tm8J}Fx+AlB@0KDQxfvMXXUUcvB1b_ z8LYjx{Ouht-P_+Smr3+H964{$-@9B+Bb^5zwA8wQkm_ zK-c`Gw)L((c7B4}&cA`|R&35@$1$r%%2kOg8@J_UjHe{c>=_1!Gimr$r52l|Dgsy1 z$T{}dlE=%;Q}SL~yz*A~C2PgLOYbzj@Y~tW!iE3v_sF%umBH57dDx>$C-1&{UK>=H zU?rY(;amwiY5IsJJsI7`7H)BY9^j%_)f*?jdX(_9x@|YxC zgkRr$&&jE>kY3eaY;3W9*C;-9VCL2>VjzuY6oH2bkNQh6v0nbdl;`h)m3dzE2*=H@I$rrC-4 z+hy$!SL!`5V5|7!fz2;x-(6H#h_<~`u0DP3$`v^c4JlXG^R2AGdBowk|N6+E?^QKt z-J)FOsrkXWSBK-)rrL4@$ipP=__B__C{H54%D_NcVMpAz=qfGk7wl|n8!+HS6V}#S zlWy)du+y>d3~rh6h4jF{!R6H$BipU&i=C%^(>SFTbYoC4U=WvyIyX$tT=GL;#)yDB zSEjs483s}4xrO;)VndREm=5FGR&SOH6H41=l2?7G+Lz1dX16=Esx5usu4(BI5|h`FDSDnv?eg+F zh<@t?gi^Koz1<$zGq1scrUOMys9;N}Sa#`U%>TD3g6itT0<|SQ z`^rad6R&Rru|2j+pk-$lYvwUD4+f6*8&Nz!ioysDe;3#<;5G-a6-v?XVqjn_=8{&}vbnM2M_A9)vGdwaq`#e$-PwORM^7wW`fawv-dS`Y?-GpN-x;&nyn|Vdwz3ig?qCxIG|;qBRr8= zh|t#Y4&2`o@d?@12|*cHhp!H86MCzi=Fa1^Fu6H>z9>ApvUxLQ$DYEsDAwYYJZP}W zLaYWBhVJ4Je@aYF)Qx!KgkFEGUuMkvi%WTx7&YUTO% z$2TwLgl_N?NWmGjLf6Mu@-h3l=xU*&%kZi0^1BhnbM6K=GzcyJQK6Qq@pupiTetSi z;SML?B5SjGGiD%R`xiYS!D~x~-`tb-*RCU%iTd!;qrR!deXb+}$EMfCE=zS6gE#@p z$B~epQ03Qc>&MfIn=FHa`l7Nbym}|0G*+uu;smGST+ruJ;GKYfJx$OgR|}?m(Pc zlIzm9^~jJ@>C)rfzD+M#@!g49o=RUr_cD@{Gx|X@)r-Wl@MqcT4vQ5>x^2KO#;}^0 zm=qcHQ^EQVRSc+dKJbRJo3y&0uB5}#l-a~L3x$k9s`LgRa(HKA0#_85)of$J%UbWz zj3dCi;b{t(GkiHjP2L0#704l>qd7o>ks6BcAlL~PreHWQlEOhaS_rx(Ko&y8DFM_d z0wvG_Wq6Xpc-_ui$dzlkI;Z=*@lFQ8Nf+-jp;_{QI3->H@QfJ>MPHvSaR2h!xN>jl zb!vM0+}he-P#xGCfdd44%S}bqy*{<^QIQsMb`y|@B>^}T`g(f> z!TkiQVzR|_%?I629fN}rU~p1URu%mLrUA({7T0pTi-7>L>&!sMu^Q@Uj}UZkkT|hK6)&ITg+&Z-yIdfjZ4B zR*;dwVq|25!3Jbl?GSegSJ6YDtDx8{;*#CR^jHiUBrDDI_)Vma&EBFkm~~M}38HYu zz|l{;Rg$(PCM1Lo*9~f)t*YXMH87bRqKA68AV3%>ZJ>kmp%gE09;z}pd3aFvGL*$0 zoE7pJiVGgu7>}r_F~r2gK6!XVIH6KS0EMV*c$KbgaxN>~!2!A-$Orxf*kNo_HK2X_ z2L~tMNe0#LU$NFoEj?<^7gk41SGlRx5H;GuBjr!)iQ=N8>!M}ylIQ5fDrA?t%W1G3 z-VA)NyyRN5ETp7{r*dC=#nygbb4^mo81Hp#;T2>V2FyaIZi7)?>bC*?x$!!AJ%d)r zpwj8i%0O5_3%0<3<2q*G@${Fc^0XbV9XD;$i{`w(*QY3k?J_z@ec@owqVO(SB*dF4gwhF6Z2Rbz!_x~-SDE&DA>}Q=*RM=vvWeJL}b11@l4{{ z9_K8_v%+-s(%v8=0he<&RN;H?K3oI}a&jMXa&l(2FCR7iM=<}B+y5HPm;LWT^RXH_ zTcj3t&jx}okZZw_CHK*$$Q=6}B(>>J)!v*3utBC)Qp@@S()(_)q$Y+G4c(qPgztPa z(rILvxbD8ELJ6Q5rl!rLI07^S%F8zmyvJHk2~Do8lhie-FomAxE~*C@`4&U@DO$Hp zTmfcbqy9&Kc{1tWrN61yH3$|jEy82?4e$juIW_Y9$IEZ7nUbMBDd{IZ9hXB4pG-%c ztG&>?sJs}As29V~f zZjm#sWRcH6-Ni>Ljm0STUN}7^M==n za&CS{ zp!<(B0jjD3p0Zcqo3;arpcO29G+vswH|fB>K00@zYKr_Z>tGiLUWsv8(^COpYav?+ zc=Ju$g`R|J7bO)WblXDnF=U7|Kxr+chpcaG#pg}tX$wS{z+;?*$6o5S!H+C~!6u~O zhJ%?ItN^d8AkH>15omKT*Gh?t`|QS}#J3RLeC;+-Ba|IUf>+FY&7lE|` z_T*?@r(KG@9`8|p*O61c4O`(0{`K9t=e+~&7Z7ZKgNHXbOW1-2`8_imrRo;7Kf(l7 z-o;!*o~Ey^CSV*0*u9gly>Y!5BO+!P4rVRnc7uI5LzK1(X<2sLk4H!D2_Z~hl1ihF?k3Uc?d8Ze*0Ys z0;sl~KWl6LAm4-gGFiLUEhUYXe}ItN_SXITQgwuZ_+2AyR{D(l`q(e~;mMtx zc(=8U21i5)xVc$`M3;Rt8LnykHHRl9^&V^klMx(H^8=0HArliG6BQ9Y@$rO%gIxFS zKM@$d;;Jnh3A{!|HTzcNb_IG)U}dgiQeaPN=ws z1==)k(*N(A1e#dzgQo88pra{gY#gSOO@u*C{f@e}RALHF+NZbfC~Q0QXQ(Mfzcc>|?c`A15^ zM8iGPANg@?OP&T;P^AlZXf^^-Na{1e^_3OW1tba>BA6>cJOgs?q&{;_DDH+&v*S|B zm>!z{DC&X4g{M!S)^&CUKm{>M#0v_MJ@GLy7_F602S;Ub&-gbF6S$F8{}t{cxKn^9 z)Af{d-+64s2jM!h;LVNFJ6^*gX@xXtS=cyGErT$J(;8a-ToeHA1O&)$%+0$Yp$C=4 zI)nx4=f?kb4nzBO3ADLx52*uI2xmU9J%3={Y#bkt1<(S8gu=IHds$m?kx_BF^!!|w zbaj>E(#ON3QzHrDTSi3fME9;rsPF-s`KS-~l~VzZQO-Re8;%@e1k!U%Jb~z&PAQN+41F=xr$yqAX*u#ET1nN#jaV*!oPbPVc8N+T*Z~e zEi8TOpn8m&R;|8nHH@|y8f6+8Q*o^7e9rY``y~WoCei!Po=b}Px6ij@6z28ck^v=z_-uzS018G3 z4H4a|>0Svh;J-6^RLmS;Gg;SkJz9z-!HmNU8Ek8vh?eNZemB{ z=3i&AcAkIyEFa^)B3y!S*>?w+#1Lnr zF8=T%iy(C-Eo+;l@)2CfNHIZ7j1ABs1T3@-D$4Ha_6JVgzi(O#WGN9Y6Mo6}TRSM(CI#Htlv!bla`Zk{o*7nCPpDEX38S@HYsnZsAYY;2N70mNA$ z9Z%R+(KGdcWVWhKhTglr;s_y^%d%ukFD0WlhSoxwX=)7%4}-zvFP9Fvqlj2lauo+r;Q@a#>k4OjyvpdmRg_ zxv}vI&~2-0Yo5nsDP0TyUDSuB=Btnwj>kQ34K`np)1ZIPUWX9hf_wNa^75gAj>m3= zMecRIy^$3a(!-9n?u%*q2 zf@SrRDo>#>5z-0eeXOaAuQo93o}Pfe-dp`aei~sCN|&A@uf_??84t zoO%k89x!Opq9bA+{%Z~dbD^^0lnFvA92^{M?d%Al^a5&13<1k<0ckPqk%*WWbdd;w zhOTl|R(f?hXeQ77oDTIk-CzC<5U_){&%Y!)&_>3_aEOT|#l^2f#P>cB37Ut{arUnn z702Zb3hCKlR(JKHgtXn3$qSPX0Vr~II+}EVu8tIlY-q9!SZ#0;;+;Rt*}(LALvEvVt-B=;#5i_#b+PRxfb9)95k-69V+sH( zfC-k<)|ORMQ>&Z@(Yx-z4Y&HtG=ZBn zhwKV$*mJKR`SagjCRmem`9Yv*qgj0WC(T7FLB~a;&%TK9!V&Nt!F|zv_MX^&gM>Hw zaj#Dhif;~ST;|8F8yF9-B6WHpV}GZaFVO1SO6#D$ijo_LHG9L79vP0d1f_-HgQ)vj z86&v3kH@Sl%@!X4a$Wt$An4c(ggK zHH$V&Mc|s~Y38B_FCIP~t}tj}LXR{bRI3~{!>b3HDa2_7R!IaoeK>I)9Rk!9Ak_(( za%oV6D^r(_6_S0ZlIYG+D1Cw`kSphwpH?F?s-E@tE(+_m)^Ed0vGYS7RID1A)(*)< zA@T6(-enp!*cUSdMg)qlrGSJd72)Dk)Rq&upT(4&o{@GV>ybn4aW8wH^6GZV-`SML zpt|vf&_+XQ>x`s;#9G9}>c5dFo4UsBh9B!Y0|Ir{x`?aZFW!@6(wdn@zm_>sN_Fte zW>$}jRhM$>lwDwnyeQ4Dxc#PEdxwsu8toiuoZ|eYNM$K|>&5E%#jyS*i->32y|MxP z_R$v4a`*Bsp1a&9$S$~0p`&{j+O|9_q8f2l+&`R2*R~)#B)kM-u^o7j4h=gutOpQtujR5MLPA0k9u*iUo#oW~ce+J(Tme}H zaz+O8uDlH7&%-LJgW3;*&T{|%_!V^J|BPSx?8?n^6A&dJY2ftx3~T|yrV~!Di1+=D)B96BuO;SU#rNMvRp9;&Z_u`_~t{C+jb{R z7NNKT&mJf+DR?zw>Z9l1E%-T%0Wm<78WX zO}@TPXoIjUa?G8~Rh0RzNx%~Vbc%S2d^;shkZ16pzOpvJHUld&2=9uyy>hbGPjEw= z8Q(0W>?(EBSRD4oy{jkTW{<;sS1;q;yEm`wt$XmsDu(`?f%*mP-SPue1JHxH1DKrQJEpo`? zfK3-z9rlormFQ^BBIi;J`3yLrf-j--Ltn@(sf2jQJ5X8fYf2FE_Coi&!3a5&kOL$Q zhBXkX0g?GTsP)cX(ojW{^&Vb|Tb_xEKr4<+>lhaVLf zF^-QN+dDd92i;Mbnlv;Li9{#)_bS#m@HIg80#m$yn(M zJ60e!miI)+GW#;i3Xfl=7bS10X=lo3wT^{9K5pTIS+4getA=&Lcm_Z@D7=+bB78R9 z^`%ZmR`-@W^J494i_Lb6OcZh?;zeMBomUjr9RI` zyX&{QEU-d}Jkr`tpsl4f=XmyJS0di|oHy{O{Qc@^xnVxB3qfVQ_YR5Ey6=z!y z_8y))x$HL`c;nd1=#aXfFX?}K;nrleoYyt4fFHL6-`GdGtZCTUX8ErDXCZ2T9(qIs zvRriQcgv`Im5($}Q308Cw8zHv+M=bNusWoATtn}Y%+ARny)Le~RaVl8@IYGJ=zg3d z4!n1od!<`wi%vaUMd#D!1)E7ns{1^56r`5-tPuH+zVskhrWwy~H64uH=I<<*Tu9N# zBVTxrcXjrVOmH&veCm`lLeu-^2*Q<}4rz1yLlaqA{$Up8g3Wz{3d;4{`m;Wpj>06# zKjts6YA;$|$ljb44YhYXP8gB}z-r}pG6qD3toB+uY)y5=TGU8@Gsk}yGQS1-JSur` zf#`%_3tWi6kdg{`^F?3=lv${*nkP@bgI2kwLABDJ0c=^&&5{6Q2LPDig^z^1AmptM zw(T2OSO#E20HY83%;qN0_EeOVSKQs*QKbz~ZUeenx7l^pe-!xN2%>oS?>@E_lb_Wu zaP1NZB$V;|>Fc{0MT)V@Ap(D zS--img4&dzrbj?=>|n}dHWgi7iROyQtrl~yZ0DXR3;xw{smKt8Um1I)s1XkCxup~SJPCCL)7L#i@?Kw=TRMOp*-53n0xJSl`W><_ql~&T&vtQfr zt7SV|S$_BG^`N-E9WJ>U0ml|S#kJRaryaRn4PV)Z;!k>x-n#9lim9nyI2M12{}q(K zC&-`96lt}JV35(K?>|0=tw6W;`*WbjP!dTu#Ew|RrTt+3Qy->U1T5lrL<5OL68K%! zq`W~8j*2e;v16;7K>G5}VJ+~9Orh2F_2IzLiUxWFaR3C+pip5B6xRVHTsQ&(0;l^c z9tjE4TZhB-qoaiQ`1mkc!(}a2>+b^+EK{@IWx@20)gHpn`*-$O0%AK>buoN_c38hllHvl~FVbY1>h?2+#g> z*&@)UEL<5Th1gtFEFxeA0|NuFl4MySAXx;J&xJ|K9bSuAV47c8U^eU~iEwstbp?|Q zY#m9dse_E?pe+FLJ0{4s^xzi!^b1V7|Ly~gd&Bk3sj74H=dsH(3&ss)PKHiag}9mw zZ0aF6Gy2_M+%yUL@@{cm{khKjrKjlEy+2MB(Y=H6*g|}F;~A-rQhW2iTBR5&qdzrC zq99}3%nAR=7C*oGwzEX`bZ0%wkft_A^{1|Jwuc?QYv7zY9is2q4K#MmgV8%1!8J8L zED{Tf!|bn^LUh09O&XlKpGbFp_1Ax!5FpoVLzNK_!`(E>e-@0tv*)Y3Z!1!@8>HJO zi{Hjpg3Mb88&~cufm#{Fkx*>gSbKuD ztS=V{9TgoFNr-repalmDRh4~Y>#NBX-P~>LX)S|x#MYKcedBO9vyL!FYE=#*D@=UU z>S4lO6kYG{p$0+iM&F{;&bT3TG!~Fl?6armm2w@HTniwZf@`k!g{b-M$E8GL#oF+I zi0ZSzt#rMfE7OdYjnt-k#3Ez$UgdgIDdlHAGO^B~`Vo5|d`J4PXliZA zAaT>oYGKR_4umTwuUW$~`pF)a$>R56VW$^Jbno=1(gT`&wdZJ_spx%XpnBZ7a?R#d zK|3>Cot~B*xVy5Cd)P|O!&~s_KqlBi`svK}kAON0nbXINJZjxB)iZ!<3M!DCoN7GyfSMnGyf_i1Yn{6JSpTpDWGuXR5uHBw( zzTG6ofw~bH8H1qNySFGkDd45Fv=aXucqYIJLI}jrr2U61I?W2(dT1nvKm<@mO1^mU zsd=Rg;-3E|JVuq61BrlA<#6%K8z2V1!m{XdAyLbNmhvV3* zul393W_3Tajq8+?z4dyg8=7BgbCnNHEs2mbq&ypZ&VZ{&N}Cm{R;3-bEo(~ZGTltT zo=>NOg@r!T+or4K#F5c7s2W?_(>yiw;df#QkvHkR-m40Fr1nVpx?8xUmqwh+K0G?F z_<=c98hJ6bnZ_!y(5P9~^yNaiYyLuO@tsk9B=+fcXI(CX7xU9P`EmyJFvWtG9dHdt z4mzm#RUJiX?XQ@1f6pjDUA{^djz-}E5BQ|tg|0VxtvL<9#KFyJ-q13Dn zOfAp#t71E+gMsUvF@5FG?U^j>dn@Nlr?!3O${dxYsX0djqdf(&kJ6gCNqp&E$!re{ zu<5)_aTLMa7hazx5LS}LRbrE#$EQyi&2%q0*PdF>sBHP({ zDx@OjU(~kHW%^wP>HK@}H@Qnb8%y)3&GuWXq8qu9`@@ZH9EB#1n+JQ1ro^2;Mh!>y z5-ypZ(FAu*FR}X1O{7|%o~ZINU(KjE>KPdD0WZH7D8J~a#Zkrv@C~4Wa0|48Fvr%x z=hXw#VbTd8V)KLJIs@SbMB9|>ktW3BpdyvG;f{bn0Z2WwJUcWCJou53ajCAg^@oTP z#JfZ8W0xDssSfH@KyIKl#X+0B3aV#eIl2sMaV#|Zk%x%K%+<+O^7`OgS9J8?=JxS;*P{gIZI zHZo2}H44u*8YU)KIK3HYsTCx4S>q^?`r#6K50wfi$C z&9QvJYT)PB+l=C+z@wT%I6o6v^o>H=DHp6BsJp+)4AJrcEx2b zZwR9L%;)%LpSbnJ*NsmGYzg$6X7QwNdc7qfu1}n9bPVDZ?a>kA3{#XLv#?3!T@&Us z3~gU#;lH+&PjN-;ym?dmX1GpOHB|&Zt}f~=yHYp4G0N?zojNqS9~G5F6NQcWtLgXk zD8r!8uj*OsFZ<0i(a_L$C4`?Vx_{dUEVDAbGVa#?%k21-OG~_Sa8ufEQMLQEm^?LM~vECOJ2_V^aEdh8^()R(Bob4tdq2G*6?OnwsTK`qRE=ncLR| zyp~$^e1z3V&J6CLEv8kJ9XwUm;#MmM^VSrtigfPTaL1i`%Z7_u%`kBgw^+kA&jRvEt|<2qe$aT=of- zD0jBYWY|duQ;228x2SZ^jL!#aG|X!?)ootyYN^v6Dji*bSezLqYm_Dk4m2)c#o_FF z`}RpU0Ua&vEV#w=K`emS{sqB~mS`{{p~#Eb6HvQ!1Jpda9zg$9su}8sAO;V0qQb#U z0(KPWdLFi4ftKS&q{Y_7^VN!3&JW1_yA^C#Iv>7WOKRwlqvp9ve($jty&vVByGDur z#P3sc6Lq(Kx z_lXr(N7P;=aPG`}e58ZMY+ydgodB317>dPpaGeAd0lc&r)&|j1^iPe zZ;W4(4}C|HBCW`)dj;j?PIhGFt~?NgG-agcXk~SG!J4b0vT|;F`zcYN=(mJ~77q-I zqn4VQnjgb!1wVh{wY0RrJ$mopJ)c^RaB-dD%=C%H z71U?Y&|Hi+UjFS*f-kRV{OwPaB6U~)`jepif9+Fa&MmYDyn@|4_@Qh*p literal 0 HcmV?d00001 diff --git a/docs/screenshot_thumb.png b/docs/screenshot_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..34357b0c7b146ba0567156b589eb2d62c7d07517 GIT binary patch literal 124732 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 literal 0 HcmV?d00001 diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 0000000..25254f9 --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,3089 @@ +.. _user_agent: + +.. raw:: html + + + ++------------+--------+-------------+ +| name | type | default | ++============+========+=============+ +| user_agent | string | libtorrent/ | ++------------+--------+-------------+ + +this is the client identification to the tracker. The recommended +format of this string is: "client-name/client-version +libtorrent/libtorrent-version". This name will not only be used when +making HTTP requests, but also when sending extended headers to +peers that support that extension. It may not contain \r or \n + +.. _announce_ip: + +.. raw:: html + + + ++-------------+--------+---------+ +| name | type | default | ++=============+========+=========+ +| announce_ip | string | nullptr | ++-------------+--------+---------+ + +``announce_ip`` is the ip address passed along to trackers as the +``&ip=`` parameter. If left as the default, that parameter is +omitted. + +.. note:: + This setting is only meant for very special cases where a seed is + running on the same host as the tracker, and the tracker accepts + the IP parameter (which normal trackers don't). Do not set this + option unless you also control the tracker. + +.. _handshake_client_version: + +.. raw:: html + + + ++--------------------------+--------+---------+ +| name | type | default | ++==========================+========+=========+ +| handshake_client_version | string | nullptr | ++--------------------------+--------+---------+ + +this is the client name and version identifier sent to peers in the +handshake message. If this is an empty string, the user_agent is +used instead. This string must be a UTF-8 encoded unicode string. + +.. _outgoing_interfaces: + +.. raw:: html + + + ++---------------------+--------+---------+ +| name | type | default | ++=====================+========+=========+ +| outgoing_interfaces | string | | ++---------------------+--------+---------+ + +This controls which IP address outgoing TCP peer connections are bound +to, in addition to controlling whether such connections are also +bound to a specific network interface/adapter (*bind-to-device*). +This string is a comma-separated list of IP addresses and +interface names. An empty string will not bind TCP sockets to a +device, and let the network stack assign the local address. A +list of names will be used to bind outgoing TCP sockets in a +round-robin fashion. An IP address will simply be used to `bind()` +the socket. An interface name will attempt to bind the socket to +that interface. If that fails, or is unsupported, one of the IP +addresses configured for that interface is used to `bind()` the +socket to. If the interface or adapter doesn't exist, the +outgoing peer connection will fail with an error message suggesting +the device cannot be found. Adapter names on Unix systems are of +the form "eth0", "eth1", "tun0", etc. This may be useful for +clients that are multi-homed. Binding an outgoing connection to a +local IP does not necessarily make the connection via the +associated NIC/Adapter. + +.. _listen_interfaces: + +.. raw:: html + + + ++-------------------+--------+------------------------+ +| name | type | default | ++===================+========+========================+ +| listen_interfaces | string | 0.0.0.0:6881,[::]:6881 | ++-------------------+--------+------------------------+ + +a comma-separated list of (IP or device name, port) pairs. These are +the listen ports that will be opened for accepting incoming uTP and +TCP peer connections. These are also used for *outgoing* uTP and UDP +tracker connections and DHT nodes. + +It is possible to listen on multiple interfaces and +multiple ports. Binding to port 0 will make the operating system +pick the port. + +.. note:: + There are reasons to stick to the same port across sessions, + which would mean only using port 0 on the first start, and + recording the port that was picked for subsequent startups. + Trackers, the DHT and other peers will remember the port they see + you use and hand that port out to other peers trying to connect + to you, as well as trying to connect to you themselves. + +A port that has an "s" suffix will accept SSL peer connections. (note +that SSL sockets are only available in builds with SSL support) + +A port that has an "l" suffix will be considered a local network. +i.e. it's assumed to only be able to reach hosts in the same local +network as the IP address (based on the netmask associated with the +IP, queried from the operating system). + +if binding fails, the listen_failed_alert is posted. Once a +socket binding succeeds (if it does), the listen_succeeded_alert +is posted. There may be multiple failures before a success. + +If a device name that does not exist is configured, no listen +socket will be opened for that interface. If this is the only +interface configured, it will be as if no listen ports are +configured. + +If no listen ports are configured (e.g. listen_interfaces is an +empty string), networking will be disabled. No DHT will start, no +outgoing uTP or tracker connections will be made. No incoming TCP +or uTP connections will be accepted. (outgoing TCP connections +will still be possible, depending on +settings_pack::outgoing_interfaces). + +For example: +``[::1]:8888`` - will only accept connections on the IPv6 loopback +address on port 8888. + +``eth0:4444,eth1:4444`` - will accept connections on port 4444 on +any IP address bound to device ``eth0`` or ``eth1``. + +``[::]:0s`` - will accept SSL connections on a port chosen by the +OS. And not accept non-SSL connections at all. + +``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881. + +``10.0.1.13:6881l`` - binds to the local IP address, port 6881, but +only allow talking to peers on the same local network. The netmask +is queried from the operating system. Interfaces marked ``l`` are +not announced to trackers, unless the tracker is also on the same +local network. + +Windows OS network adapter device name must be specified with GUID. +It can be obtained from "netsh lan show interfaces" command output. +GUID must be uppercased string embraced in curly brackets. +``{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777`` - will accept +connections on port 7777 on adapter with this GUID. + +For more information, see the `Multi-homed hosts`_ section. + +.. _`Multi-homed hosts`: manual-ref.html#multi-homed-hosts + +.. _proxy_hostname: + +.. raw:: html + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_hostname | string | | ++----------------+--------+---------+ + +when using a proxy, this is the hostname where the proxy is running +see proxy_type. Note that when using a proxy, the +settings_pack::listen_interfaces setting is overridden and only a +single interface is created, just to contact the proxy. This +means a proxy cannot be combined with SSL torrents or multiple +listen interfaces. This proxy listen interface will not accept +incoming TCP connections, will not map ports with any gateway and +will not enable local service discovery. All traffic is supposed +to be channeled through the proxy. + +.. _proxy_username: + +.. _proxy_password: + +.. raw:: html + + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_username | string | | ++----------------+--------+---------+ +| proxy_password | string | | ++----------------+--------+---------+ + +when using a proxy, these are the credentials (if any) to use when +connecting to it. see proxy_type + +.. _i2p_hostname: + +.. raw:: html + + + ++--------------+--------+---------+ +| name | type | default | ++==============+========+=========+ +| i2p_hostname | string | | ++--------------+--------+---------+ + +sets the i2p_ SAM bridge to connect to. set the port with the +``i2p_port`` setting. + +.. _i2p: http://www.i2p2.de + +.. _peer_fingerprint: + +.. raw:: html + + + ++------------------+--------+----------+ +| name | type | default | ++==================+========+==========+ +| peer_fingerprint | string | -LT1290- | ++------------------+--------+----------+ + +this is the fingerprint for the client. It will be used as the +prefix to the peer_id. If this is 20 bytes (or longer) it will be +truncated to 20 bytes and used as the entire peer-id + +There is a utility function, generate_fingerprint() that can be used +to generate a standard client peer ID fingerprint prefix. + +.. _dht_bootstrap_nodes: + +.. raw:: html + + + ++---------------------+--------+--------------------------+ +| name | type | default | ++=====================+========+==========================+ +| dht_bootstrap_nodes | string | dht.libtorrent.org:25401 | ++---------------------+--------+--------------------------+ + +This is a comma-separated list of IP port-pairs. They will be added +to the DHT node (if it's enabled) as back-up nodes in case we don't +know of any. + +Changing these after the DHT has been started may not have any +effect until the DHT is restarted. + +.. _allow_multiple_connections_per_ip: + +.. raw:: html + + + ++-----------------------------------+------+---------+ +| name | type | default | ++===================================+======+=========+ +| allow_multiple_connections_per_ip | bool | false | ++-----------------------------------+------+---------+ + +determines if connections from the same IP address as existing +connections should be rejected or not. Rejecting multiple connections +from the same IP address will prevent abusive +behavior by peers. The logic for determining whether connections are +to the same peer is more complicated with this enabled, and more +likely to fail in some edge cases. It is not recommended to enable +this feature. + +.. _send_redundant_have: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| send_redundant_have | bool | true | ++---------------------+------+---------+ + +``send_redundant_have`` controls if have messages will be sent to +peers that already have the piece. This is typically not necessary, +but it might be necessary for collecting statistics in some cases. + +.. _use_dht_as_fallback: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_dht_as_fallback | bool | false | ++---------------------+------+---------+ + +``use_dht_as_fallback`` determines how the DHT is used. If this is +true, the DHT will only be used for torrents where all trackers in +its tracker list has failed. Either by an explicit error message or +a time out. If this is false, the DHT is used regardless of if the +trackers fail or not. + +.. _upnp_ignore_nonrouters: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| upnp_ignore_nonrouters | bool | false | ++------------------------+------+---------+ + +``upnp_ignore_nonrouters`` indicates whether or not the UPnP +implementation should ignore any broadcast response from a device +whose address is not on our subnet. i.e. +it's a way to not talk to other people's routers by mistake. + +.. _use_parole_mode: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| use_parole_mode | bool | true | ++-----------------+------+---------+ + +``use_parole_mode`` specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode. + +.. _use_read_cache: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| use_read_cache | bool | true | ++----------------+------+---------+ + +enable and disable caching of blocks read from disk. the purpose of +the read cache is partly read-ahead of requests but also to avoid +reading blocks back from the disk multiple times for popular +pieces. + +.. _coalesce_reads: + +.. _coalesce_writes: + +.. raw:: html + + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| coalesce_reads | bool | false | ++-----------------+------+---------+ +| coalesce_writes | bool | false | ++-----------------+------+---------+ + +allocate separate, contiguous, buffers for read and write calls. +Only used where writev/readv cannot be used will use more RAM but +may improve performance + +.. _auto_manage_prefer_seeds: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_manage_prefer_seeds | bool | false | ++--------------------------+------+---------+ + +if true, prefer seeding torrents when determining which torrents to give +active slots to. If false, give preference to downloading torrents + +.. _dont_count_slow_torrents: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dont_count_slow_torrents | bool | true | ++--------------------------+------+---------+ + +if ``dont_count_slow_torrents`` is true, torrents without any +payload transfers are not subject to the ``active_seeds`` and +``active_downloads`` limits. This is intended to make it more +likely to utilize all available bandwidth, and avoid having +torrents that don't transfer anything block the active slots. + +.. _close_redundant_connections: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| close_redundant_connections | bool | true | ++-----------------------------+------+---------+ + +``close_redundant_connections`` specifies whether libtorrent should +close connections where both ends have no utility in keeping the +connection open. For instance if both ends have completed their +downloads, there's no point in keeping it open. + +.. _prioritize_partial_pieces: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| prioritize_partial_pieces | bool | false | ++---------------------------+------+---------+ + +If ``prioritize_partial_pieces`` is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion. + +.. _rate_limit_ip_overhead: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| rate_limit_ip_overhead | bool | true | ++------------------------+------+---------+ + +if set to true, the estimated TCP/IP overhead is drained from the +rate limiters, to avoid exceeding the limits with the total traffic + +.. _announce_to_all_tiers: + +.. _announce_to_all_trackers: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| announce_to_all_tiers | bool | false | ++--------------------------+------+---------+ +| announce_to_all_trackers | bool | false | ++--------------------------+------+---------+ + +``announce_to_all_trackers`` controls how multi tracker torrents +are treated. If this is set to true, all trackers in the same tier +are announced to in parallel. If all trackers in tier 0 fails, all +trackers in tier 1 are announced as well. If it's set to false, the +behavior is as defined by the multi tracker specification. + +``announce_to_all_tiers`` also controls how multi tracker torrents +are treated. When this is set to true, one tracker from each tier +is announced to. This is the uTorrent behavior. To be compliant +with the Multi-tracker specification, set it to false. + +.. _prefer_udp_trackers: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| prefer_udp_trackers | bool | true | ++---------------------+------+---------+ + +``prefer_udp_trackers``: true means that trackers +may be rearranged in a way that udp trackers are always tried +before http trackers for the same hostname. Setting this to false +means that the tracker's tier is respected and there's no +preference of one protocol over another. + +.. _disable_hash_checks: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| disable_hash_checks | bool | false | ++---------------------+------+---------+ + +when set to true, all data downloaded from peers will be assumed to +be correct, and not tested to match the hashes in the torrent this +is only useful for simulation and testing purposes (typically +combined with disabled_storage) + +.. _allow_i2p_mixed: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| allow_i2p_mixed | bool | false | ++-----------------+------+---------+ + +if this is true, i2p torrents are allowed to also get peers from +other sources than the tracker, and connect to regular IPs, not +providing any anonymization. This may be useful if the user is not +interested in the anonymization of i2p, but still wants to be able +to connect to i2p peers. + +.. _volatile_read_cache: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| volatile_read_cache | bool | false | ++---------------------+------+---------+ + +``volatile_read_cache``, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first +place. + +.. _no_atime_storage: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| no_atime_storage | bool | true | ++------------------+------+---------+ + +``no_atime_storage`` this is a Linux-only option and passes in the +``O_NOATIME`` to ``open()`` when opening files. This may lead to +some disk performance improvements. + +.. _incoming_starts_queued_torrents: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| incoming_starts_queued_torrents | bool | false | ++---------------------------------+------+---------+ + +``incoming_starts_queued_torrents``. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. the +torrent is paused and auto managed, this feature affects whether or +not it is automatically started on an incoming connection. The main +reason to queue torrents, is not to make them unavailable, but to +save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it. + +.. _report_true_downloaded: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_true_downloaded | bool | false | ++------------------------+------+---------+ + +when set to true, the downloaded counter sent to trackers will +include the actual number of payload bytes downloaded including +redundant bytes. If set to false, it will not include any redundancy +bytes + +.. _strict_end_game_mode: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| strict_end_game_mode | bool | true | ++----------------------+------+---------+ + +``strict_end_game_mode`` controls when a +block may be requested twice. If this is ``true``, a block may only +be requested twice when there's at least one request to every piece +that's left to download in the torrent. This may slow down progress +on some pieces sometimes, but it may also avoid downloading a lot +of redundant bytes. If this is ``false``, libtorrent attempts to +use each peer connection to its max, by always requesting +something, even if it means requesting something that has been +requested from another peer already. + +.. _enable_outgoing_utp: + +.. _enable_incoming_utp: + +.. _enable_outgoing_tcp: + +.. _enable_incoming_tcp: + +.. raw:: html + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| enable_outgoing_utp | bool | true | ++---------------------+------+---------+ +| enable_incoming_utp | bool | true | ++---------------------+------+---------+ +| enable_outgoing_tcp | bool | true | ++---------------------+------+---------+ +| enable_incoming_tcp | bool | true | ++---------------------+------+---------+ + +when set to true, libtorrent will try to make outgoing utp +connections controls whether libtorrent will accept incoming +connections or make outgoing connections of specific type. + +.. _no_recheck_incomplete_resume: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| no_recheck_incomplete_resume | bool | false | ++------------------------------+------+---------+ + +``no_recheck_incomplete_resume`` determines if the storage should +check the whole files when resume data is incomplete or missing or +whether it should simply assume we don't have any of the data. If +false, any existing files will be checked. +By setting this setting to true, the files won't be checked, but +will go straight to download mode. + +.. _anonymous_mode: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| anonymous_mode | bool | false | ++----------------+------+---------+ + +``anonymous_mode``: When set to true, the client +tries to hide its identity to a certain degree. The user-agent will be +reset to an empty string (except for private torrents). Trackers +will only be used if they are using a proxy server. +The listen sockets are closed, and incoming +connections will only be accepted through a SOCKS5 or I2P proxy (if +a peer proxy is set up and is run on the same machine as the +tracker proxy). Since no incoming connections are accepted, +NAT-PMP, UPnP, DHT and local peer discovery are all turned off when +this setting is enabled. + +If you're using I2P, it might make sense to enable anonymous mode +as well. + +.. _report_web_seed_downloads: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| report_web_seed_downloads | bool | true | ++---------------------------+------+---------+ + +specifies whether downloads from web seeds is reported to the +tracker or not. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API. + +.. _seeding_outgoing_connections: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| seeding_outgoing_connections | bool | true | ++------------------------------+------+---------+ + +``seeding_outgoing_connections`` determines if seeding (and +finished) torrents should attempt to make outgoing connections or +not. It may be set to false in very +specific applications where the cost of making outgoing connections +is high, and there are no or small benefits of doing so. For +instance, if no nodes are behind a firewall or a NAT, seeds don't +need to make outgoing connections. + +.. _no_connect_privileged_ports: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| no_connect_privileged_ports | bool | false | ++-----------------------------+------+---------+ + +when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack + +.. _smooth_connects: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| smooth_connects | bool | true | ++-----------------+------+---------+ + +``smooth_connects`` means the number of +connection attempts per second may be limited to below the +``connection_speed``, in case we're close to bump up against the +limit of number of connections. The intention of this setting is to +more evenly distribute our connection attempts over time, instead +of attempting to connect in batches, and timing them out in +batches. + +.. _always_send_user_agent: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| always_send_user_agent | bool | false | ++------------------------+------+---------+ + +always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent + +.. _apply_ip_filter_to_trackers: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| apply_ip_filter_to_trackers | bool | true | ++-----------------------------+------+---------+ + +``apply_ip_filter_to_trackers`` determines +whether the IP filter applies to trackers as well as peers. If this +is set to false, trackers are exempt from the IP filter (if there +is one). If no IP filter is set, this setting is irrelevant. + +.. _ban_web_seeds: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| ban_web_seeds | bool | true | ++---------------+------+---------+ + +when true, web seeds sending bad data will be banned + +.. _allow_partial_disk_writes: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| allow_partial_disk_writes | bool | true | ++---------------------------+------+---------+ + +when set to false, the ``write_cache_line_size`` will apply across +piece boundaries. this is a bad idea unless the piece picker also +is configured to have an affinity to pick pieces belonging to the +same write cache line as is configured in the disk cache. + +.. _support_share_mode: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| support_share_mode | bool | true | ++--------------------+------+---------+ + +if false, prevents libtorrent to advertise share-mode support + +.. _support_merkle_torrents: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| support_merkle_torrents | bool | true | ++-------------------------+------+---------+ + +if this is false, don't advertise support for the Tribler merkle +tree piece message + +.. _report_redundant_bytes: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_redundant_bytes | bool | true | ++------------------------+------+---------+ + +if this is true, the number of redundant bytes is sent to the +tracker + +.. _listen_system_port_fallback: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| listen_system_port_fallback | bool | true | ++-----------------------------+------+---------+ + +if this is true, libtorrent will fall back to listening on a port +chosen by the operating system (i.e. binding to port 0). If a +failure is preferred, set this to false. + +.. _announce_crypto_support: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| announce_crypto_support | bool | true | ++-------------------------+------+---------+ + +when this is true, and incoming encrypted connections are enabled, +&supportcrypt=1 is included in http tracker announces + +.. _enable_upnp: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| enable_upnp | bool | true | ++-------------+------+---------+ + +Starts and stops the UPnP service. When started, the listen port +and the DHT port are attempted to be forwarded on local UPnP router +devices. + +The upnp object returned by ``start_upnp()`` can be used to add and +remove arbitrary port mappings. Mapping status is returned through +the portmap_alert and the portmap_error_alert. The object will be +valid until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + +.. _enable_natpmp: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| enable_natpmp | bool | true | ++---------------+------+---------+ + +Starts and stops the NAT-PMP service. When started, the listen port +and the DHT port are attempted to be forwarded on the router +through NAT-PMP. + +The natpmp object returned by ``start_natpmp()`` can be used to add +and remove arbitrary port mappings. Mapping status is returned +through the portmap_alert and the portmap_error_alert. The object +will be valid until ``stop_natpmp()`` is called. See +upnp-and-nat-pmp_. + +.. _enable_lsd: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| enable_lsd | bool | true | ++------------+------+---------+ + +Starts and stops Local Service Discovery. This service will +broadcast the info-hashes of all the non-private torrents on the +local network to look for peers on the same swarm within multicast +reach. + +.. _enable_dht: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| enable_dht | bool | true | ++------------+------+---------+ + +starts the dht node and makes the trackerless service available to +torrents. + +.. _prefer_rc4: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| prefer_rc4 | bool | false | ++------------+------+---------+ + +if the allowed encryption level is both, setting this to true will +prefer RC4 if both methods are offered, plain text otherwise + +.. _proxy_hostnames: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| proxy_hostnames | bool | true | ++-----------------+------+---------+ + +if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP. + +.. _proxy_peer_connections: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| proxy_peer_connections | bool | true | ++------------------------+------+---------+ + +if true, peer connections are made (and accepted) over the +configured proxy, if any. Web seeds as well as regular bittorrent +peer connections are considered "peer connections". Anything +transporting actual torrent payload (trackers and DHT traffic are +not considered peer connections). + +.. _auto_sequential: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| auto_sequential | bool | true | ++-----------------+------+---------+ + +if this setting is true, torrents with a very high availability of +pieces (and seeds) are downloaded sequentially. This is more +efficient for the disk I/O. With many seeds, the download order is +unlikely to matter anyway + +.. _proxy_tracker_connections: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| proxy_tracker_connections | bool | true | ++---------------------------+------+---------+ + +if true, tracker connections are made over the configured proxy, if +any. + +.. _enable_ip_notifier: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| enable_ip_notifier | bool | true | ++--------------------+------+---------+ + +Starts and stops the internal IP table route changes notifier. + +The current implementation supports multiple platforms, and it is +recommended to have it enable, but you may want to disable it if +it's supported but unreliable, or if you have a better way to +detect the changes. In the later case, you should manually call +``session_handle::reopen_network_sockets`` to ensure network +changes are taken in consideration. + +.. _dht_prefer_verified_node_ids: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| dht_prefer_verified_node_ids | bool | true | ++------------------------------+------+---------+ + +when this is true, nodes whose IDs are derived from their source +IP according to `BEP 42`_ are preferred in the routing table. + +.. _piece_extent_affinity: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| piece_extent_affinity | bool | false | ++-----------------------+------+---------+ + +when this is true, create an affinity for downloading 4 MiB extents +of adjacent pieces. This is an attempt to achieve better disk I/O +throughput by downloading larger extents of bytes, for torrents with +small piece sizes + +.. _validate_https_trackers: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| validate_https_trackers | bool | false | ++-------------------------+------+---------+ + +when set to true, the certificate of HTTPS trackers will be +validated against the system's certificate store (as defined by +OpenSSL). If the system does not have one, enabling this may cause +HTTPS trackers to fail. + +.. _tracker_completion_timeout: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| tracker_completion_timeout | int | 30 | ++----------------------------+------+---------+ + +``tracker_completion_timeout`` is the number of seconds the tracker +connection will wait from when it sent the request until it +considers the tracker to have timed-out. + +.. _tracker_receive_timeout: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| tracker_receive_timeout | int | 10 | ++-------------------------+------+---------+ + +``tracker_receive_timeout`` is the number of seconds to wait to +receive any data from the tracker. If no data is received for this +number of seconds, the tracker will be considered as having timed +out. If a tracker is down, this is the kind of timeout that will +occur. + +.. _stop_tracker_timeout: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| stop_tracker_timeout | int | 5 | ++----------------------+------+---------+ + +``stop_tracker_timeout`` is the number of seconds to wait when +sending a stopped message before considering a tracker to have +timed out. This is usually shorter, to make the client quit faster. +If the value is set to 0, the connections to trackers with the +stopped event are suppressed. + +.. _tracker_maximum_response_length: + +.. raw:: html + + + ++---------------------------------+------+-----------+ +| name | type | default | ++=================================+======+===========+ +| tracker_maximum_response_length | int | 1024*1024 | ++---------------------------------+------+-----------+ + +this is the maximum number of bytes in a tracker response. If a +response size passes this number of bytes it will be rejected and +the connection will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get 20 bytes of gzip +response that'll expand to 2 megabytes, it will be interrupted +before the entire response has been uncompressed (assuming the +limit is lower than 2 MiB). + +.. _piece_timeout: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| piece_timeout | int | 20 | ++---------------+------+---------+ + +the number of seconds from a request is sent until it times out if +no piece response is returned. + +.. _request_timeout: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| request_timeout | int | 60 | ++-----------------+------+---------+ + +the number of seconds one block (16 kiB) is expected to be received +within. If it's not, the block is requested from a different peer + +.. _request_queue_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| request_queue_time | int | 3 | ++--------------------+------+---------+ + +the length of the request queue given in the number of seconds it +should take for the other end to send all the pieces. i.e. the +actual number of requests depends on the download rate and this +number. + +.. _max_allowed_in_request_queue: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| max_allowed_in_request_queue | int | 500 | ++------------------------------+------+---------+ + +the number of outstanding block requests a peer is allowed to queue +up in the client. If a peer sends more requests than this (before +the first one has been sent) the last request will be dropped. the +higher this is, the faster upload speeds the client can get to a +single peer. + +.. _max_out_request_queue: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| max_out_request_queue | int | 500 | ++-----------------------+------+---------+ + +``max_out_request_queue`` is the maximum number of outstanding +requests to send to a peer. This limit takes precedence over +``request_queue_time``. i.e. no matter the download speed, the +number of outstanding requests will never exceed this limit. + +.. _whole_pieces_threshold: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| whole_pieces_threshold | int | 20 | ++------------------------+------+---------+ + +if a whole piece can be downloaded in this number of seconds, or +less, the peer_connection will prefer to request whole pieces at a +time from this peer. The benefit of this is to better utilize disk +caches by doing localized accesses and also to make it easier to +identify bad peers if a piece fails the hash check. + +.. _peer_timeout: + +.. raw:: html + + + ++--------------+------+---------+ +| name | type | default | ++==============+======+=========+ +| peer_timeout | int | 120 | ++--------------+------+---------+ + +``peer_timeout`` is the number of seconds the peer connection +should wait (for any activity on the peer connection) before +closing it due to time out. 120 seconds is +specified in the protocol specification. After half +the time out, a keep alive message is sent. + +.. _urlseed_timeout: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| urlseed_timeout | int | 20 | ++-----------------+------+---------+ + +same as peer_timeout, but only applies to url-seeds. this is +usually set lower, because web servers are expected to be more +reliable. + +.. _urlseed_pipeline_size: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| urlseed_pipeline_size | int | 5 | ++-----------------------+------+---------+ + +controls the pipelining size of url and http seeds. i.e. the number of HTTP +request to keep outstanding before waiting for the first one to +complete. It's common for web servers to limit this to a relatively +low number, like 5 + +.. _urlseed_wait_retry: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| urlseed_wait_retry | int | 30 | ++--------------------+------+---------+ + +number of seconds until a new retry of a url-seed takes place. +Default retry value for http-seeds that don't provide +a valid ``retry-after`` header. + +.. _file_pool_size: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| file_pool_size | int | 40 | ++----------------+------+---------+ + +sets the upper limit on the total number of files this session will +keep open. The reason why files are left open at all is that some +anti virus software hooks on every file close, and scans the file +for viruses. deferring the closing of the files will be the +difference between a usable system and a completely hogged down +system. Most operating systems also has a limit on the total number +of file descriptors a process may have open. + +.. _max_failcount: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| max_failcount | int | 3 | ++---------------+------+---------+ + +``max_failcount`` is the maximum times we try to +connect to a peer before stop connecting again. If a +peer succeeds, the failure counter is reset. If a +peer is retrieved from a peer source (other than DHT) +the failcount is decremented by one, allowing another +try. + +.. _min_reconnect_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| min_reconnect_time | int | 60 | ++--------------------+------+---------+ + +the number of seconds to wait to reconnect to a peer. this time is +multiplied with the failcount. + +.. _peer_connect_timeout: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| peer_connect_timeout | int | 15 | ++----------------------+------+---------+ + +``peer_connect_timeout`` the number of seconds to wait after a +connection attempt is initiated to a peer until it is considered as +having timed out. This setting is especially important in case the +number of half-open connections are limited, since stale half-open +connection may delay the connection of other peers considerably. + +.. _connection_speed: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| connection_speed | int | 30 | ++------------------+------+---------+ + +``connection_speed`` is the number of connection attempts that are +made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all. + +.. _inactivity_timeout: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| inactivity_timeout | int | 600 | ++--------------------+------+---------+ + +if a peer is uninteresting and uninterested for longer than this +number of seconds, it will be disconnected. + +.. _unchoke_interval: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| unchoke_interval | int | 15 | ++------------------+------+---------+ + +``unchoke_interval`` is the number of seconds between +chokes/unchokes. On this interval, peers are re-evaluated for being +choked/unchoked. This is defined as 30 seconds in the protocol, and +it should be significantly longer than what it takes for TCP to +ramp up to it's max rate. + +.. _optimistic_unchoke_interval: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| optimistic_unchoke_interval | int | 30 | ++-----------------------------+------+---------+ + +``optimistic_unchoke_interval`` is the number of seconds between +each *optimistic* unchoke. On this timer, the currently +optimistically unchoked peer will change. + +.. _num_want: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| num_want | int | 200 | ++----------+------+---------+ + +``num_want`` is the number of peers we want from each tracker +request. It defines what is sent as the ``&num_want=`` parameter to +the tracker. + +.. _initial_picker_threshold: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| initial_picker_threshold | int | 4 | ++--------------------------+------+---------+ + +``initial_picker_threshold`` specifies the number of pieces we need +before we switch to rarest first picking. The first +``initial_picker_threshold`` pieces in any torrent are picked at random +, the following pieces are picked in rarest first order. + +.. _allowed_fast_set_size: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| allowed_fast_set_size | int | 5 | ++-----------------------+------+---------+ + +the number of allowed pieces to send to peers that supports the +fast extensions + +.. _suggest_mode: + +.. raw:: html + + + ++--------------+------+-------------------------------------+ +| name | type | default | ++==============+======+=====================================+ +| suggest_mode | int | settings_pack::no_piece_suggestions | ++--------------+------+-------------------------------------+ + +``suggest_mode`` controls whether or not libtorrent will send out +suggest messages to create a bias of its peers to request certain +pieces. The modes are: + +* ``no_piece_suggestions`` which will not send out suggest messages. +* ``suggest_read_cache`` which will send out suggest messages for + the most recent pieces that are in the read cache. + +.. _max_queued_disk_bytes: + +.. raw:: html + + + ++-----------------------+------+-------------+ +| name | type | default | ++=======================+======+=============+ +| max_queued_disk_bytes | int | 1024 * 1024 | ++-----------------------+------+-------------+ + +``max_queued_disk_bytes`` is the maximum number of bytes, to +be written to disk, that can wait in the disk I/O thread queue. +This queue is only for waiting for the disk I/O thread to receive +the job and either write it to disk or insert it in the write +cache. When this limit is reached, the peer connections will stop +reading data from their sockets, until the disk thread catches up. +Setting this too low will severely limit your download rate. + +.. _handshake_timeout: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| handshake_timeout | int | 10 | ++-------------------+------+---------+ + +the number of seconds to wait for a handshake response from a peer. +If no response is received within this time, the peer is +disconnected. + +.. _send_buffer_low_watermark: + +.. _send_buffer_watermark: + +.. _send_buffer_watermark_factor: + +.. raw:: html + + + + + ++------------------------------+------+------------+ +| name | type | default | ++==============================+======+============+ +| send_buffer_low_watermark | int | 10 * 1024 | ++------------------------------+------+------------+ +| send_buffer_watermark | int | 500 * 1024 | ++------------------------------+------+------------+ +| send_buffer_watermark_factor | int | 50 | ++------------------------------+------+------------+ + +``send_buffer_low_watermark`` the minimum send buffer target size +(send buffer includes bytes pending being read from disk). For good +and snappy seeding performance, set this fairly high, to at least +fit a few blocks. This is essentially the initial window size which +will determine how fast we can ramp up the send rate + +if the send buffer has fewer bytes than ``send_buffer_watermark``, +we'll read another 16 kiB block onto it. If set too small, upload +rate capacity will suffer. If set too high, memory will be wasted. +The actual watermark may be lower than this in case the upload rate +is low, this is the upper limit. + +the current upload rate to a peer is multiplied by this factor to +get the send buffer watermark. The factor is specified as a +percentage. i.e. 50 -> 0.5 This product is clamped to the +``send_buffer_watermark`` setting to not exceed the max. For high +speed upload, this should be set to a greater value than 100. For +high capacity connections, setting this higher can improve upload +performance and disk throughput. Setting it too high may waste RAM +and create a bias towards read jobs over write jobs. + +.. _choking_algorithm: + +.. _seed_choking_algorithm: + +.. raw:: html + + + + ++------------------------+------+-----------------------------------+ +| name | type | default | ++========================+======+===================================+ +| choking_algorithm | int | settings_pack::fixed_slots_choker | ++------------------------+------+-----------------------------------+ +| seed_choking_algorithm | int | settings_pack::round_robin | ++------------------------+------+-----------------------------------+ + +``choking_algorithm`` specifies which algorithm to use to determine +how many peers to unchoke. The unchoking algorithm for +downloading torrents is always "tit-for-tat", i.e. the peers we +download the fastest from are unchoked. + +The options for choking algorithms are defined in the +choking_algorithm_t enum. + +``seed_choking_algorithm`` controls the seeding unchoke behavior. +i.e. How we select which peers to unchoke for seeding torrents. +Since a seeding torrent isn't downloading anything, the +tit-for-tat mechanism cannot be used. The available options are +defined in the seed_choking_algorithm_t enum. + +.. _cache_size: + +.. _cache_expiry: + +.. raw:: html + + + + ++--------------+------+---------+ +| name | type | default | ++==============+======+=========+ +| cache_size | int | 2048 | ++--------------+------+---------+ +| cache_expiry | int | 300 | ++--------------+------+---------+ + +``cache_size`` is the disk write and read cache. It is specified +in units of 16 kiB blocks. Buffers that are part of a peer's send +or receive buffer also count against this limit. Send and receive +buffers will never be denied to be allocated, but they will cause +the actual cached blocks to be flushed or evicted. If this is set +to -1, the cache size is automatically set based on the amount of +physical RAM on the machine. If the amount of physical RAM cannot +be determined, it's set to 1024 (= 16 MiB). + +``cache_expiry`` is the number of seconds from the last cached write +to a piece in the write cache, to when it's forcefully flushed to +disk. + +On 32 bit builds, the effective cache size will be limited to 3/4 of +2 GiB to avoid exceeding the virtual address space limit. + +.. _disk_io_write_mode: + +.. _disk_io_read_mode: + +.. raw:: html + + + + ++--------------------+------+--------------------------------+ +| name | type | default | ++====================+======+================================+ +| disk_io_write_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ +| disk_io_read_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ + +determines how files are opened when they're in read only mode +versus read and write mode. The options are: + +enable_os_cache + Files are opened normally, with the OS caching reads and writes. +disable_os_cache + This opens all files in no-cache mode. This corresponds to the + OS not letting blocks for the files linger in the cache. This + makes sense in order to avoid the bittorrent client to + potentially evict all other processes' cache by simply handling + high throughput and large files. If libtorrent's read cache is + disabled, enabling this may reduce performance. + +One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely. + +.. _outgoing_port: + +.. _num_outgoing_ports: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| outgoing_port | int | 0 | ++--------------------+------+---------+ +| num_outgoing_ports | int | 0 | ++--------------------+------+---------+ + +this is the first port to use for binding outgoing connections to. +This is useful for users that have routers that allow QoS settings +based on local port. when binding outgoing connections to specific +ports, ``num_outgoing_ports`` is the size of the range. It should +be more than a few + +.. warning:: setting outgoing ports will limit the ability to keep + multiple connections to the same client, even for different + torrents. It is not recommended to change this setting. Its main + purpose is to use as an escape hatch for cheap routers with QoS + capability but can only classify flows based on port numbers. + +It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in ``TIME_WAIT`` state. + +.. _peer_tos: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| peer_tos | int | 0x20 | ++----------+------+---------+ + +``peer_tos`` determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). ``0x0`` means no marking, +``0x20`` represents the *QBone scavenger service*. For more +details, see QBSS_. + +.. _`QBSS`: http://qbone.internet2.edu/qbss/ + +.. _active_downloads: + +.. _active_seeds: + +.. _active_checking: + +.. _active_dht_limit: + +.. _active_tracker_limit: + +.. _active_lsd_limit: + +.. _active_limit: + +.. raw:: html + + + + + + + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| active_downloads | int | 3 | ++----------------------+------+---------+ +| active_seeds | int | 5 | ++----------------------+------+---------+ +| active_checking | int | 1 | ++----------------------+------+---------+ +| active_dht_limit | int | 88 | ++----------------------+------+---------+ +| active_tracker_limit | int | 1600 | ++----------------------+------+---------+ +| active_lsd_limit | int | 60 | ++----------------------+------+---------+ +| active_limit | int | 500 | ++----------------------+------+---------+ + +for auto managed torrents, these are the limits they are subject +to. If there are too many torrents some of the auto managed ones +will be paused until some slots free up. ``active_downloads`` and +``active_seeds`` controls how many active seeding and downloading +torrents the queuing mechanism allows. The target number of active +torrents is ``min(active_downloads + active_seeds, active_limit)``. +``active_downloads`` and ``active_seeds`` are upper limits on the +number of downloading torrents and seeding torrents respectively. +Setting the value to -1 means unlimited. + +For example if there are 10 seeding torrents and 10 downloading +torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4, +there will be 4 seeds active and 4 downloading torrents. If the +settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, +then there will be 2 downloading torrents and 4 seeding torrents +active. Torrents that are not auto managed are not counted against +these limits. + +``active_checking`` is the limit of number of simultaneous checking +torrents. + +``active_limit`` is a hard limit on the number of active (auto +managed) torrents. This limit also applies to slow torrents. + +``active_dht_limit`` is the max number of torrents to announce to +the DHT. + +``active_tracker_limit`` is the max number of torrents to announce +to their trackers. + +``active_lsd_limit`` is the max number of torrents to announce to +the local network over the local service discovery protocol. + +You can have more torrents *active*, even though they are not +announced to the DHT, lsd or their tracker. If some peer knows +about you for any reason and tries to connect, it will still be +accepted, unless the torrent is paused, which means it won't accept +any connections. + +.. _auto_manage_interval: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| auto_manage_interval | int | 30 | ++----------------------+------+---------+ + +``auto_manage_interval`` is the number of seconds between the +torrent queue is updated, and rotated. + +.. _seed_time_limit: + +.. raw:: html + + + ++-----------------+------+--------------+ +| name | type | default | ++=================+======+==============+ +| seed_time_limit | int | 24 * 60 * 60 | ++-----------------+------+--------------+ + +this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed +limit criteria. See queuing_. + +.. _auto_scrape_interval: + +.. _auto_scrape_min_interval: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_scrape_interval | int | 1800 | ++--------------------------+------+---------+ +| auto_scrape_min_interval | int | 300 | ++--------------------------+------+---------+ + +``auto_scrape_interval`` is the number of seconds between scrapes +of queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to +determine which torrents to seed and which to pause. + +``auto_scrape_min_interval`` is the minimum number of seconds +between any automatic scrape (regardless of torrent). In case there +are a large number of paused auto managed torrents, this puts a +limit on how often a scrape request is sent. + +.. _max_peerlist_size: + +.. _max_paused_peerlist_size: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| max_peerlist_size | int | 3000 | ++--------------------------+------+---------+ +| max_paused_peerlist_size | int | 1000 | ++--------------------------+------+---------+ + +``max_peerlist_size`` is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this +number should be much greater than the maximum number of connected +peers. Peers are evicted from the cache when the list grows passed +90% of this limit, and once the size hits the limit, peers are no +longer added to the list. If this limit is set to 0, there is no +limit on how many peers we'll keep in the peer list. + +``max_paused_peerlist_size`` is the max peer list size used for +torrents that are paused. This can be used to save memory for paused +torrents, since it's not as important for them to keep a large peer +list. + +.. _min_announce_interval: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| min_announce_interval | int | 5 * 60 | ++-----------------------+------+---------+ + +this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering mis-configured +trackers. + +.. _auto_manage_startup: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| auto_manage_startup | int | 60 | ++---------------------+------+---------+ + +this is the number of seconds a torrent is considered active after +it was started, regardless of upload and download speed. This is so +that newly started torrents are not considered inactive until they +have a fair chance to start downloading. + +.. _seeding_piece_quota: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| seeding_piece_quota | int | 20 | ++---------------------+------+---------+ + +``seeding_piece_quota`` is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set. + +.. _max_rejects: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| max_rejects | int | 50 | ++-------------+------+---------+ + +``max_rejects`` is the number of piece requests we will reject in a +row while a peer is choked before the peer is considered abusive +and is disconnected. + +.. _recv_socket_buffer_size: + +.. _send_socket_buffer_size: + +.. raw:: html + + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| recv_socket_buffer_size | int | 0 | ++-------------------------+------+---------+ +| send_socket_buffer_size | int | 0 | ++-------------------------+------+---------+ + +specifies the buffer sizes set on peer sockets. 0 means the OS +default (i.e. don't change the buffer sizes). +The socket buffer sizes are changed using setsockopt() with +SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER. + +.. _max_peer_recv_buffer_size: + +.. raw:: html + + + ++---------------------------+------+-----------------+ +| name | type | default | ++===========================+======+=================+ +| max_peer_recv_buffer_size | int | 2 * 1024 * 1024 | ++---------------------------+------+-----------------+ + +the max number of bytes a single peer connection's receive buffer is +allowed to grow to. + +.. _read_cache_line_size: + +.. _write_cache_line_size: + +.. raw:: html + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| read_cache_line_size | int | 32 | ++-----------------------+------+---------+ +| write_cache_line_size | int | 16 | ++-----------------------+------+---------+ + +``read_cache_line_size`` is the number of blocks to read into the +read cache when a read cache miss occurs. Setting this to 0 is +essentially the same thing as disabling read cache. The number of +blocks read into the read cache is always capped by the piece +boundary. + +When a piece in the write cache has ``write_cache_line_size`` +contiguous blocks in it, they will be flushed. Setting this to 1 +effectively disables the write cache. + +.. _optimistic_disk_retry: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| optimistic_disk_retry | int | 10 * 60 | ++-----------------------+------+---------+ + +``optimistic_disk_retry`` is the number of seconds from a disk +write errors occur on a torrent until libtorrent will take it out +of the upload mode, to test if the error condition has been fixed. + +libtorrent will only do this automatically for auto managed +torrents. + +You can explicitly take a torrent out of upload only mode using +set_upload_mode(). + +.. _max_suggest_pieces: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| max_suggest_pieces | int | 16 | ++--------------------+------+---------+ + +``max_suggest_pieces`` is the max number of suggested piece indices +received from a peer that's remembered. If a peer floods suggest +messages, this limit prevents libtorrent from using too much RAM. + +.. _local_service_announce_interval: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| local_service_announce_interval | int | 5 * 60 | ++---------------------------------+------+---------+ + +``local_service_announce_interval`` is the time between local +network announces for a torrent. +This interval is specified in seconds. + +.. _dht_announce_interval: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_announce_interval | int | 15 * 60 | ++-----------------------+------+---------+ + +``dht_announce_interval`` is the number of seconds between +announcing torrents to the distributed hash table (DHT). + +.. _udp_tracker_token_expiry: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| udp_tracker_token_expiry | int | 60 | ++--------------------------+------+---------+ + +``udp_tracker_token_expiry`` is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is +specified to be 60 seconds. The higher this +value is, the fewer packets have to be sent to the UDP tracker. In +order for higher values to work, the tracker needs to be configured +to match the expiration time for tokens. + +.. _num_optimistic_unchoke_slots: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| num_optimistic_unchoke_slots | int | 0 | ++------------------------------+------+---------+ + +``num_optimistic_unchoke_slots`` is the number of optimistic +unchoke slots to use. +Having a higher number of optimistic unchoke slots mean you will +find the good peers faster but with the trade-off to use up more +bandwidth. 0 means automatic, where libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots. + +.. _max_pex_peers: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| max_pex_peers | int | 50 | ++---------------+------+---------+ + +the max number of peers we accept from pex messages from a single +peer. this limits the number of concurrent peers any of our peers +claims to be connected to. If they claim to be connected to more +than this, we'll ignore any peer that exceeds this limit + +.. _tick_interval: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| tick_interval | int | 500 | ++---------------+------+---------+ + +``tick_interval`` specifies the number of milliseconds between +internal ticks. This is the frequency with which bandwidth quota is +distributed to peers. It should not be more than one second (i.e. +1000 ms). Setting this to a low value (around 100) means higher +resolution bandwidth quota distribution, setting it to a higher +value saves CPU cycles. + +.. _share_mode_target: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| share_mode_target | int | 3 | ++-------------------+------+---------+ + +``share_mode_target`` specifies the target share ratio for share +mode torrents. If set to 3, we'll try to upload 3 +times as much as we download. Setting this very high, will make it +very conservative and you might end up not downloading anything +ever (and not affecting your share ratio). It does not make any +sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a +single piece and upload it more than 3 times. If the +share_mode_target is set to more than 3, nothing is downloaded. + +.. _upload_rate_limit: + +.. _download_rate_limit: + +.. raw:: html + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| upload_rate_limit | int | 0 | ++---------------------+------+---------+ +| download_rate_limit | int | 0 | ++---------------------+------+---------+ + +``upload_rate_limit`` and ``download_rate_limit`` sets +the session-global limits of upload and download rate limits, in +bytes per second. By default peers on the local network are not rate +limited. + +A value of 0 means unlimited. + +For fine grained control over rate limits, including making them apply +to local peers, see peer-classes_. + +.. _dht_upload_rate_limit: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_upload_rate_limit | int | 8000 | ++-----------------------+------+---------+ + +``dht_upload_rate_limit`` sets the rate limit on the DHT. This is +specified in bytes per second. For busy boxes +with lots of torrents that requires more DHT traffic, this should +be raised. + +.. _unchoke_slots_limit: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| unchoke_slots_limit | int | 8 | ++---------------------+------+---------+ + +``unchoke_slots_limit`` is the max number of unchoked peers in the +session. The number of unchoke slots may be ignored depending on +what ``choking_algorithm`` is set to. Setting this limit to -1 +means unlimited, i.e. all peers will always be unchoked. + +.. _connections_limit: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| connections_limit | int | 200 | ++-------------------+------+---------+ + +``connections_limit`` sets a global limit on the number of +connections opened. The number of connections is set to a hard +minimum of at least two per torrent, so if you set a too low +connections limit, and open too many torrents, the limit will not +be met. + +.. _connections_slack: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| connections_slack | int | 10 | ++-------------------+------+---------+ + +``connections_slack`` is the number of incoming connections +exceeding the connection limit to accept in order to potentially +replace existing ones. + +.. _utp_target_delay: + +.. _utp_gain_factor: + +.. _utp_min_timeout: + +.. _utp_syn_resends: + +.. _utp_fin_resends: + +.. _utp_num_resends: + +.. _utp_connect_timeout: + +.. _utp_loss_multiplier: + +.. raw:: html + + + + + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| utp_target_delay | int | 100 | ++---------------------+------+---------+ +| utp_gain_factor | int | 3000 | ++---------------------+------+---------+ +| utp_min_timeout | int | 500 | ++---------------------+------+---------+ +| utp_syn_resends | int | 2 | ++---------------------+------+---------+ +| utp_fin_resends | int | 2 | ++---------------------+------+---------+ +| utp_num_resends | int | 3 | ++---------------------+------+---------+ +| utp_connect_timeout | int | 3000 | ++---------------------+------+---------+ +| utp_loss_multiplier | int | 50 | ++---------------------+------+---------+ + +``utp_target_delay`` is the target delay for uTP sockets in +milliseconds. A high value will make uTP connections more +aggressive and cause longer queues in the upload bottleneck. It +cannot be too low, since the noise in the measurements would cause +it to send too slow. +``utp_gain_factor`` is the number of bytes the uTP congestion +window can increase at the most in one RTT. +If this is set too high, the congestion controller reacts +too hard to noise and will not be stable, if it's set too low, it +will react slow to congestion and not back off as fast. + +``utp_min_timeout`` is the shortest allowed uTP socket timeout, +specified in milliseconds. The +timeout depends on the RTT of the connection, but is never smaller +than this value. A connection times out when every packet in a +window is lost, or when a packet is lost twice in a row (i.e. the +resent packet is lost as well). + +The shorter the timeout is, the faster the connection will recover +from this situation, assuming the RTT is low enough. +``utp_syn_resends`` is the number of SYN packets that are sent (and +timed out) before giving up and closing the socket. +``utp_num_resends`` is the number of times a packet is sent (and +lost or timed out) before giving up and closing the connection. +``utp_connect_timeout`` is the number of milliseconds of timeout +for the initial SYN packet for uTP connections. For each timed out +packet (in a row), the timeout is doubled. ``utp_loss_multiplier`` +controls how the congestion window is changed when a packet loss is +experienced. It's specified as a percentage multiplier for +``cwnd``. Do not change this value unless you know what you're doing. +Never set it higher than 100. + +.. _mixed_mode_algorithm: + +.. raw:: html + + + ++----------------------+------+----------------------------------+ +| name | type | default | ++======================+======+==================================+ +| mixed_mode_algorithm | int | settings_pack::peer_proportional | ++----------------------+------+----------------------------------+ + +The ``mixed_mode_algorithm`` determines how to treat TCP +connections when there are uTP connections. Since uTP is designed +to yield to TCP, there's an inherent problem when using swarms that +have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP +connections. This mode is ``prefer_tcp``. The ``peer_proportional`` +mode simply looks at the current throughput and rate limits all TCP +connections to their proportional share based on how many of the +connections are TCP. This works best if uTP connections are not +rate limited by the global rate limiter (which they aren't by +default). + +.. _listen_queue_size: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| listen_queue_size | int | 5 | ++-------------------+------+---------+ + +``listen_queue_size`` is the value passed in to listen() for the +listen socket. It is the number of outstanding incoming connections +to queue up while we're not actively waiting for a connection to be +accepted. 5 should be sufficient for any +normal client. If this is a high performance server which expects +to receive a lot of connections, or used in a simulator or test, it +might make sense to raise this number. It will not take affect +until the ``listen_interfaces`` settings is updated. + +.. _torrent_connect_boost: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| torrent_connect_boost | int | 30 | ++-----------------------+------+---------+ + +``torrent_connect_boost`` is the number of peers to try to connect +to immediately when the first tracker response is received for a +torrent. This is a boost to given to new torrents to accelerate +them starting up. The normal connect scheduler is run once every +second, this allows peers to be connected immediately instead of +waiting for the session tick to trigger connections. +This may not be set higher than 255. + +.. _alert_queue_size: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| alert_queue_size | int | 1000 | ++------------------+------+---------+ + +``alert_queue_size`` is the maximum number of alerts queued up +internally. If alerts are not popped, the queue will eventually +fill up to this level. Once the alert queue is full, additional +alerts will be dropped, and not delivered to the client. Once the +client drains the queue, new alerts may be delivered again. In order +to know that alerts have been dropped, see +session_handle::dropped_alerts(). + +.. _max_metadata_size: + +.. raw:: html + + + ++-------------------+------+------------------+ +| name | type | default | ++===================+======+==================+ +| max_metadata_size | int | 3 * 1024 * 10240 | ++-------------------+------+------------------+ + +``max_metadata_size`` is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links. + +.. _checking_mem_usage: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| checking_mem_usage | int | 1024 | ++--------------------+------+---------+ + +the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks + +.. _predictive_piece_announce: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| predictive_piece_announce | int | 0 | ++---------------------------+------+---------+ + +if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The +intention is to gain 1.5 potential round trip times per downloaded +piece. When non-zero, this indicates how many milliseconds in +advance pieces should be announced, before they are expected to be +completed. + +.. _aio_threads: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| aio_threads | int | 4 | ++-------------+------+---------+ + +for some aio back-ends, ``aio_threads`` specifies the number of +io-threads to use. + +.. _tracker_backoff: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| tracker_backoff | int | 250 | ++-----------------+------+---------+ + +``tracker_backoff`` determines how aggressively to back off from +retrying failing trackers. This value determines *x* in the +following formula, determining the number of seconds to wait until +the next retry: + + delay = 5 + 5 * x / 100 * fails^2 + +This setting may be useful to make libtorrent more or less +aggressive in hitting trackers. + +.. _share_ratio_limit: + +.. _seed_time_ratio_limit: + +.. raw:: html + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| share_ratio_limit | int | 200 | ++-----------------------+------+---------+ +| seed_time_ratio_limit | int | 700 | ++-----------------------+------+---------+ + +when a seeding torrent reaches either the share ratio (bytes up / +bytes down) or the seed time ratio (seconds as seed / seconds as +downloader) or the seed time limit (seconds as seed) it is +considered done, and it will leave room for other torrents. These +are specified as percentages. Torrents that are considered done will +still be allowed to be seeded, they just won't have priority anymore. +For more, see queuing_. + +.. _peer_turnover: + +.. _peer_turnover_cutoff: + +.. _peer_turnover_interval: + +.. raw:: html + + + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| peer_turnover | int | 4 | ++------------------------+------+---------+ +| peer_turnover_cutoff | int | 90 | ++------------------------+------+---------+ +| peer_turnover_interval | int | 300 | ++------------------------+------+---------+ + +peer_turnover is the percentage of peers to disconnect every +turnover peer_turnover_interval (if we're at the peer limit), this +is specified in percent when we are connected to more than limit * +peer_turnover_cutoff peers disconnect peer_turnover fraction of the +peers. It is specified in percent peer_turnover_interval is the +interval (in seconds) between optimistic disconnects if the +disconnects happen and how many peers are disconnected is +controlled by peer_turnover and peer_turnover_cutoff + +.. _connect_seed_every_n_download: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| connect_seed_every_n_download | int | 10 | ++-------------------------------+------+---------+ + +this setting controls the priority of downloading torrents over +seeding or finished torrents when it comes to making peer +connections. Peer connections are throttled by the connection_speed +and the half-open connection limit. This makes peer connections a +limited resource. Torrents that still have pieces to download are +prioritized by default, to avoid having many seeding torrents use +most of the connection attempts and only give one peer every now +and then to the downloading torrent. libtorrent will loop over the +downloading torrents to connect a peer each, and every n:th +connection attempt, a finished torrent is picked to be allowed to +connect to a peer. This setting controls n. + +.. _max_http_recv_buffer_size: + +.. raw:: html + + + ++---------------------------+------+------------+ +| name | type | default | ++===========================+======+============+ +| max_http_recv_buffer_size | int | 4*1024*204 | ++---------------------------+------+------------+ + +the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via the +``url`` provided in ``add_torrent_params``. + +.. _max_retry_port_bind: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| max_retry_port_bind | int | 10 | ++---------------------+------+---------+ + +if binding to a specific port fails, should the port be incremented +by one and tried again? This setting specifies how many times to +retry a failed port bind + +.. _alert_mask: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| alert_mask | int | int | ++------------+------+---------+ + +a bitmask combining flags from alert_category_t defining which +kinds of alerts to receive + +.. _out_enc_policy: + +.. _in_enc_policy: + +.. raw:: html + + + + ++----------------+------+---------------------------+ +| name | type | default | ++================+======+===========================+ +| out_enc_policy | int | settings_pack::pe_enabled | ++----------------+------+---------------------------+ +| in_enc_policy | int | settings_pack::pe_enabled | ++----------------+------+---------------------------+ + +control the settings for incoming and outgoing connections +respectively. see enc_policy enum for the available options. +Keep in mind that protocol encryption degrades performance in +several respects: + +1. It prevents "zero copy" disk buffers being sent to peers, since + each peer needs to mutate the data (i.e. encrypt it) the data + must be copied per peer connection rather than sending the same + buffer to multiple peers. +2. The encryption itself requires more CPU than plain bittorrent + protocol. The highest cost is the Diffie Hellman exchange on + connection setup. +3. The encryption handshake adds several round-trips to the + connection setup, and delays transferring data. + +.. _allowed_enc_level: + +.. raw:: html + + + ++-------------------+------+------------------------+ +| name | type | default | ++===================+======+========================+ +| allowed_enc_level | int | settings_pack::pe_both | ++-------------------+------+------------------------+ + +determines the encryption level of the connections. This setting +will adjust which encryption scheme is offered to the other peer, +as well as which encryption scheme is selected by the client. See +enc_level enum for options. + +.. _inactive_down_rate: + +.. _inactive_up_rate: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| inactive_down_rate | int | 2048 | ++--------------------+------+---------+ +| inactive_up_rate | int | 2048 | ++--------------------+------+---------+ + +the download and upload rate limits for a torrent to be considered +active by the queuing mechanism. A torrent whose download rate is +less than ``inactive_down_rate`` and whose upload rate is less than +``inactive_up_rate`` for ``auto_manage_startup`` seconds, is +considered inactive, and another queued torrent may be started. +This logic is disabled if ``dont_count_slow_torrents`` is false. + +.. _proxy_type: + +.. raw:: html + + + ++------------+------+---------------------+ +| name | type | default | ++============+======+=====================+ +| proxy_type | int | settings_pack::none | ++------------+------+---------------------+ + +proxy to use. see proxy_type_t. + +.. _proxy_port: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| proxy_port | int | 0 | ++------------+------+---------+ + +the port of the proxy server + +.. _i2p_port: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| i2p_port | int | 0 | ++----------+------+---------+ + +sets the i2p_ SAM bridge port to connect to. set the hostname with +the ``i2p_hostname`` setting. + +.. _i2p: http://www.i2p2.de + +.. _cache_size_volatile: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| cache_size_volatile | int | 256 | ++---------------------+------+---------+ + +this determines the max number of volatile disk cache blocks. If the +number of volatile blocks exceed this limit, other volatile blocks +will start to be evicted. A disk cache block is volatile if it has +low priority, and should be one of the first blocks to be evicted +under pressure. For instance, blocks pulled into the cache as the +result of calculating a piece hash are volatile. These blocks don't +represent potential interest among peers, so the value of keeping +them in the cache is limited. + +.. _urlseed_max_request_bytes: + +.. raw:: html + + + ++---------------------------+------+------------------+ +| name | type | default | ++===========================+======+==================+ +| urlseed_max_request_bytes | int | 16 * 1024 * 1024 | ++---------------------------+------+------------------+ + +The maximum request range of an url seed in bytes. This value +defines the largest possible sequential web seed request. Lower values +are possible but will be ignored if they are lower then piece size. +This value should be related to your download speed to prevent +libtorrent from creating too many expensive http requests per +second. You can select a value as high as you want but keep in mind +that libtorrent can't create parallel requests if the first request +did already select the whole file. +If you combine bittorrent seeds with web seeds and pick strategies +like rarest first you may find your web seed requests split into +smaller parts because we don't download already picked pieces +twice. + +.. _web_seed_name_lookup_retry: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| web_seed_name_lookup_retry | int | 1800 | ++----------------------------+------+---------+ + +time to wait until a new retry of a web seed name lookup + +.. _close_file_interval: + +.. raw:: html + + + ++---------------------+------+---------------------+ +| name | type | default | ++=====================+======+=====================+ +| close_file_interval | int | CLOSE_FILE_INTERVAL | ++---------------------+------+---------------------+ + +the number of seconds between closing the file opened the longest +ago. 0 means to disable the feature. The purpose of this is to +periodically close files to trigger the operating system flushing +disk cache. Specifically it has been observed to be required on +windows to not have the disk cache grow indefinitely. +This defaults to 120 seconds on windows, and disabled on other +systems. + +.. _utp_cwnd_reduce_timer: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| utp_cwnd_reduce_timer | int | 100 | ++-----------------------+------+---------+ + +When uTP experiences packet loss, it will reduce the congestion +window, and not reduce it again for this many milliseconds, even if +experiencing another lost packet. + +.. _max_web_seed_connections: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| max_web_seed_connections | int | 3 | ++--------------------------+------+---------+ + +the max number of web seeds to have connected per torrent at any +given time. + +.. _resolver_cache_timeout: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| resolver_cache_timeout | int | 1200 | ++------------------------+------+---------+ + +the number of seconds before the internal host name resolver +considers a cache value timed out, negative values are interpreted +as zero. + +.. _send_not_sent_low_watermark: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| send_not_sent_low_watermark | int | 16384 | ++-----------------------------+------+---------+ + +specify the not-sent low watermark for socket send buffers. This +corresponds to the, Linux-specific, ``TCP_NOTSENT_LOWAT`` TCP socket +option. + +.. _rate_choker_initial_threshold: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| rate_choker_initial_threshold | int | 1024 | ++-------------------------------+------+---------+ + +the rate based choker compares the upload rate to peers against a +threshold that increases proportionally by its size for every +peer it visits, visiting peers in decreasing upload rate. The +number of upload slots is determined by the number of peers whose +upload rate exceeds the threshold. This option sets the start +value for this threshold. A higher value leads to fewer unchoke +slots, a lower value leads to more. + +.. _upnp_lease_duration: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| upnp_lease_duration | int | 3600 | ++---------------------+------+---------+ + +The expiration time of UPnP port-mappings, specified in seconds. 0 +means permanent lease. Some routers do not support expiration times +on port-maps (nor correctly returning an error indicating lack of +support). In those cases, set this to 0. Otherwise, don't set it any +lower than 5 minutes. + +.. _max_concurrent_http_announces: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| max_concurrent_http_announces | int | 50 | ++-------------------------------+------+---------+ + +limits the number of concurrent HTTP tracker announces. Once the +limit is hit, tracker requests are queued and issued when an +outstanding announce completes. + diff --git a/docs/single-page-ref.html b/docs/single-page-ref.html new file mode 100644 index 0000000..0a3b0cb --- /dev/null +++ b/docs/single-page-ref.html @@ -0,0 +1,22039 @@ + + + + + + +single-page-ref.rst + + + + + + + +

    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +
    +

    libtorrent API Documentation

    + +
    +

    overview

    +

    The interface of libtorrent consists of a few classes. The main class is +the session, it contains the main loop that serves all torrents.

    +

    The basic usage is as follows:

    + +

    Each class and function is described in this manual, you may want to have a +look at the tutorial as well.

    +

    For a description on how to create torrent files, see create_torrent.

    +
    +
    +

    forward declarations

    +

    Forward declaring types from the libtorrent namespace is discouraged as it may +break in future releases. Instead include libtorrent/fwd.hpp for forward +declarations of all public types in libtorrent.

    +
    +
    +

    trouble shooting

    +

    A common problem developers are facing is torrents stopping without explanation. +Here is a description on which conditions libtorrent will stop your torrents, +how to find out about it and what to do about it.

    +

    Make sure to keep track of the paused state, the error state and the upload +mode of your torrents. By default, torrents are auto-managed, which means +libtorrent will pause, resume, scrape them and take them out +of upload-mode automatically.

    +

    Whenever a torrent encounters a fatal error, it will be stopped, and the +torrent_status::error will describe the error that caused it. If a torrent +is auto managed, it is scraped periodically and paused or resumed based on +the number of downloaders per seed. This will effectively seed torrents that +are in the greatest need of seeds.

    +

    If a torrent hits a disk write error, it will be put into upload mode. This +means it will not download anything, but only upload. The assumption is that +the write error is caused by a full disk or write permission errors. If the +torrent is auto-managed, it will periodically be taken out of the upload +mode, trying to write things to the disk again. This means torrent will recover +from certain disk errors if the problem is resolved. If the torrent is not +auto managed, you have to call set_upload_mode() to turn +downloading back on again.

    +

    For a more detailed guide on how to trouble shoot performance issues, see +troubleshooting

    +
    +
    +

    ABI considerations

    +

    libtorrent maintains a stable ABI for versions with the same major and minor versions.

    +

    e.g. libtorrent-1.2.0 is ABI compatible with libtorrent-1.2.1 but not with libtorrent-1.1

    +
    +
    +

    network primitives

    +

    There are a few typedefs in the libtorrent namespace which pulls +in network types from the boost::asio namespace. These are:

    +
    +using address = boost::asio::ip::address;
    +using address_v4 = boost::asio::ip::address_v4;
    +using address_v6 = boost::asio::ip::address_v6;
    +using boost::asio::ip::tcp;
    +using boost::asio::ip::udp;
    +
    +

    These are declared in the <libtorrent/socket.hpp> header.

    +

    The using statements will give easy access to:

    +
    +tcp::endpoint
    +udp::endpoint
    +
    +

    Which are the endpoint types used in libtorrent. An endpoint is an address +with an associated port.

    +

    For documentation on these types, please refer to the asio documentation.

    +
    +
    +

    exceptions

    +

    Many functions in libtorrent have two versions, one that throws exceptions on +errors and one that takes an error_code reference which is filled with the +error code on errors.

    +

    On exceptions, libtorrent will throw boost::system::system_error exceptions +carrying an error_code describing the underlying error.

    +
    +

    translating error codes

    +

    The error_code::message() function will typically return a localized error string, +for system errors. That is, errors that belong to the generic or system category.

    +

    Errors that belong to the libtorrent error category are not localized however, they +are only available in English. In order to translate libtorrent errors, compare the +error category of the error_code object against lt::libtorrent_category(), +and if matches, you know the error code refers to the list above. You can provide +your own mapping from error code to string, which is localized. In this case, you +cannot rely on error_code::message() to generate your strings.

    +

    The numeric values of the errors are part of the API and will stay the same, although +new error codes may be appended at the end.

    +

    Here's a simple example of how to translate error codes:

    +
    +std::string error_code_to_string(boost::system::error_code const& ec)
    +{
    +        if (ec.category() != lt::libtorrent_category())
    +        {
    +                return ec.message();
    +        }
    +        // the error is a libtorrent error
    +
    +        int code = ec.value();
    +        static const char const* swedish[] =
    +        {
    +                "inget fel",
    +                "en fil i torrenten kolliderar med en fil fran en annan torrent",
    +                "hash check misslyckades",
    +                "torrentfilen ar inte en dictionary",
    +                "'info'-nyckeln saknas eller ar korrupt i torrentfilen",
    +                "'info'-faltet ar inte en dictionary",
    +                "'piece length' faltet saknas eller ar korrupt i torrentfilen",
    +                "torrentfilen saknar namnfaltet",
    +                "ogiltigt namn i torrentfilen (kan vara en attack)",
    +                // ... more strings here
    +        };
    +
    +        // use the default error string in case we don't have it
    +        // in our translated list
    +        if (code < 0 || code >= sizeof(swedish)/sizeof(swedish[0]))
    +                return ec.message();
    +
    +        return swedish[code];
    +}
    +
    +
    +
    + +
    +

    queuing

    +

    libtorrent supports queuing. Queuing is a mechanism to automatically pause and +resume torrents based on certain criteria. The criteria depends on the overall +state the torrent is in (checking, downloading or seeding).

    +

    To opt-out of the queuing logic, make sure your torrents are added with the +torrent_flags::auto_managed bit cleared from add_torrent_params::flags. +Or call torrent_handle::unset_flags() and pass in torrent_flags::auto_managed on +the torrent handle.

    +

    The overall purpose of the queuing logic is to improve performance under arbitrary +torrent downloading and seeding load. For example, if you want to download 100 +torrents on a limited home connection, you improve performance by downloading +them one at a time (or maybe two at a time), over downloading them all in +parallel. The benefits are:

    +
      +
    • the average completion time of a torrent is half of what it would be if all +downloaded in parallel.
    • +
    • The amount of upload capacity is more likely to reach the reciprocation rate +of your peers, and is likely to improve your return on investment (download +to upload ratio)
    • +
    • your disk I/O load is likely to be more local which may improve I/O +performance and decrease fragmentation.
    • +
    +

    There are fundamentally 3 separate queues:

    +
      +
    • checking torrents
    • +
    • downloading torrents
    • +
    • seeding torrents
    • +
    +

    Every torrent that is not seeding has a queue number associated with it, this is +its place in line to be started. See torrent_status::queue_position.

    +

    On top of the limits of each queue, there is an over arching limit, set in +settings_pack::active_limit. The auto manager will never start more than this +number of torrents (with one exception described below). Non-auto-managed +torrents are exempt from this logic, and not counted.

    +

    At a regular interval, torrents are checked if there needs to be any +re-ordering of which torrents are active and which are queued. This interval +can be controlled via settings_pack::auto_manage_interval.

    +

    For queuing to work, resume data needs to be saved and restored for all +torrents. See torrent_handle::save_resume_data().

    +
    +

    queue position

    +

    The torrents in the front of the queue are started and the rest are ordered by +their queue position. Any newly added torrent is placed at the end of the queue. +Once a torrent is removed or turns into a seed, its queue position is -1 and all +torrents that used to be after it in the queue, decreases their position in +order to fill the gap.

    +

    The queue positions are always contiguous, in a sequence without any gaps.

    +

    Lower queue position means closer to the front of the queue, and will be +started sooner than torrents with higher queue positions.

    +

    To query a torrent for its position in the queue, or change its position, see: +torrent_handle::queue_position(), torrent_handle::queue_position_up(), +torrent_handle::queue_position_down(), torrent_handle::queue_position_top() +and torrent_handle::queue_position_bottom().

    +
    +
    +

    checking queue

    +

    The checking queue affects torrents in the torrent_status::checking or +torrent_status::allocating state that are auto-managed.

    +

    The checking queue will make sure that (of the torrents in its queue) no more than +settings_pack::active_checking_limit torrents are started at any given time. +Once a torrent completes checking and moves into a different state, the next in +line will be started for checking.

    +

    Any torrent added force-started or force-stopped (i.e. the auto managed flag is +not set), will not be subject to this limit and they will all check +independently and in parallel.

    +

    Once a torrent completes the checking of its files, or resume data, it will +be put in the queue for downloading and potentially start downloading immediately. +In order to add a torrent and check its files without starting the download, it +can be added in stop_when_ready mode. +See add_torrent_params::flag_stop_when_ready. This flag will stop the torrent +once it is ready to start downloading.

    +

    This is conceptually the same as waiting for the torrent_checked_alert and +then call:

    +
    +h.set_flags(torrent_flags::paused, torrent_flags::paused | torrent_flags::auto_managed);
    +
    +

    With the important distinction that it entirely avoids the brief window where +the torrent is in downloading state.

    +
    +
    +

    downloading queue

    +

    Similarly to the checking queue, the downloading queue will make sure that no +more than settings_pack::active_downloads torrents are in the downloading +state at any given time.

    +

    The torrent_status::queue_position is used again here to determine who is next +in line to be started once a downloading torrent completes or is stopped/removed.

    +
    +
    +

    seeding queue

    +

    The seeding queue does not use torrent_status::queue_position to determine which +torrent to seed. Instead, it estimates the demand for the torrent to be +seeded. A torrent with few other seeds and many downloaders is assumed to have a +higher demand of more seeds than one with many seeds and few downloaders.

    +

    It limits the number of started seeds to settings_pack::active_seeds.

    +

    On top of this basic bias, seed priority can be controller by specifying a +seed ratio (the upload to download ratio), a seed-time ratio (the download +time to seeding time ratio) and a seed-time (the absolute time to be seeding a +torrent). Until all those targets are hit, the torrent will be prioritized for +seeding.

    +

    Among torrents that have met their seed target, torrents where we don't know of +any other seed take strict priority.

    +

    In order to avoid flapping, torrents that were started less than 30 minutes ago +also have priority to keep seeding.

    +

    Finally, for torrents where none of the above apply, they are prioritized based +on the download to seed ratio.

    +

    The relevant settings to control these limits are +settings_pack::share_ratio_limit, settings_pack::seed_time_ratio_limit and +settings_pack::seed_time_limit.

    +
    +
    +

    queuing options

    +

    In addition to simply starting and stopping torrents, the queuing mechanism can +have more fine grained control of the resources used by torrents.

    +
    +

    half-started torrents

    +

    In addition to the downloading and seeding limits, there are limits on actions +torrents perform. The downloading and seeding limits control whether peers are +allowed at all, and if peers are not allowed, torrents are stopped and don't do +anything. If peers are allowed, torrents may:

    +
      +
    1. announce to trackers
    2. +
    3. announce to the DHT
    4. +
    5. announce to local peer discovery (local service discovery)
    6. +
    +

    Each of those actions are associated with a cost and hence may need a separate +limit. These limits are controlled by settings_pack::active_tracker_limit, +settings_pack::active_dht_limit and settings_pack::active_lsd_limit +respectively.

    +

    Specifically, announcing to a tracker is typically cheaper than +announcing to the DHT. settings_pack::active_dht_limit will limit the number of +torrents that are allowed to announce to the DHT. The highest priority ones +will, and the lower priority ones won't. The will still be considered started +though, and any incoming peers will still be accepted.

    +

    If you do not wish to impose such limits (basically, if you do not wish to have +half-started torrents) make sure to set these limits to -1 (infinite).

    +
    +
    +

    prefer seeds

    +

    In the case where active_downloads + active_seeds > active_limit, +there's an ambiguity whether the downloads should be satisfied first or the +seeds. To disambiguate this case, the settings_pack::auto_manage_prefer_seeds +determines whether seeds are preferred or not.

    +
    +
    +

    inactive torrents

    +

    Torrents that are not transferring any bytes (downloading or uploading) have a +relatively low cost to be started. It's possible to exempt such torrents from +the download and seed queues by setting settings_pack::dont_count_slow_torrents +to true.

    +

    Since it sometimes may take a few minutes for a newly started torrent to find +peers and be unchoked, or find peers that are interested in requesting data, +torrents are not considered inactive immediately. There must be an extended +period of no transfers before it is considered inactive and exempt from the +queuing limits.

    +
    +
    +
    +
    +

    fast resume

    +

    The fast resume mechanism is a way to remember which pieces are downloaded +and where they are put between sessions. You can generate fast resume data by +calling save_resume_data() on torrent_handle. You can +then save this data to disk and use it when resuming the torrent. libtorrent +will not check the piece hashes then, and rely on the information given in the +fast-resume data. The fast-resume data also contains information about which +blocks, in the unfinished pieces, were downloaded, so it will not have to +start from scratch on the partially downloaded pieces.

    +

    To use the fast-resume data you pass it to read_resume_data(), which will return +an add_torrent_params object. Fields of this object can then be altered before +passing it to async_add_torrent() or add_torrent(). +The session will then skip the time consuming checks. It may have to do +the checking anyway, if the fast-resume data is corrupt or doesn't fit the +storage for that torrent.

    +
    +

    file format

    +

    The file format is a bencoded dictionary containing the following fields:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    file-formatstring: "libtorrent resume file"
    info-hashstring, the info hash of the torrent this data is saved for.
    piecesA string with piece flags, one character per piece. +Bit 1 means we have that piece. +Bit 2 means we have verified that this piece is correct. +This only applies when the torrent is in seed_mode.
    total_uploadedinteger. The number of bytes that have been uploaded in +total for this torrent.
    total_downloadedinteger. The number of bytes that have been downloaded in +total for this torrent.
    active_timeinteger. The number of seconds this torrent has been active. +i.e. not paused.
    seeding_timeinteger. The number of seconds this torrent has been active +and seeding.
    last_uploadinteger. The number of seconds since epoch when we last +uploaded payload to a peer on this torrent.
    last_downloadinteger. The number of seconds since epoch when we last +downloaded payload from a peer on this torrent.
    upload_rate_limitinteger. In case this torrent has a per-torrent upload rate +limit, this is that limit. In bytes per second.
    download_rate_limitinteger. The download rate limit for this torrent in case +one is set, in bytes per second.
    max_connectionsinteger. The max number of peer connections this torrent +may have, if a limit is set.
    max_uploadsinteger. The max number of unchoked peers this torrent may +have, if a limit is set.
    file_prioritylist of integers. One entry per file in the torrent. Each +entry is the priority of the file with the same index.
    piece_prioritystring of bytes. Each byte is interpreted as an integer and +is the priority of that piece.
    seed_modeinteger. 1 if the torrent is in seed mode, 0 otherwise.
    upload_modeinteger. 1 if the torrent_flags::upload_mode is set.
    share_modeinteger. 1 if the torrent_flags::share_mode is set.
    apply_ip_filterinteger. 1 if the torrent_flags::apply_ip_filter is set.
    pausedinteger. 1 if the torrent is paused, 0 otherwise.
    auto_managedinteger. 1 if the torrent is auto managed, otherwise 0.
    super_seedinginteger. 1 if the torrent_flags::super_seeding is set.
    sequential_downloadinteger. 1 if the torrent is in sequential download mode, +0 otherwise.
    stop_when_readyinteger. 1 if the torrent_flags::stop_when_ready is set.
    disable_dhtinteger. 1 if the torrent_flags::disable_dht is set.
    disable_lsdinteger. 1 if the torrent_flags::disable_lsd is set.
    disable_pexinteger. 1 if the torrent_flags::disable_pex is set.
    trackerslist of lists of strings. The top level list lists all +tracker tiers. Each second level list is one tier of +trackers.
    mapped_fileslist of strings. If any file in the torrent has been +renamed, this entry contains a list of all the filenames. +In the same order as in the torrent file.
    url-listlist of strings. List of url-seed URLs used by this torrent. +The URLs are expected to be properly encoded and not contain +any illegal url characters.
    httpseedslist of strings. List of HTTP seed URLs used by this torrent. +The URLs are expected to be properly encoded and not contain +any illegal url characters.
    merkle treestring. In case this torrent is a merkle torrent, this is a +string containing the entire merkle tree, all nodes, +including the root and all leaves. The tree is not +necessarily complete, but complete enough to be able to send +any piece that we have, indicated by the have bitmask.
    save_pathstring. The save path where this torrent was saved. This is +especially useful when moving torrents with move_storage() +since this will be updated.
    peersstring. This string contains IPv4 and port pairs of peers we +were connected to last session. The endpoints are in compact +representation. 4 bytes IPv4 address followed by 2 bytes +port. Hence, the length of this string should be divisible +by 6.
    banned_peersstring. This string has the same format as peers but +instead represent IPv4 peers that we have banned.
    peers6string. This string contains IPv6 and port pairs of peers we +were connected to last session. The endpoints are in compact +representation. 16 bytes IPv6 address followed by 2 bytes +port. The length of this string should be divisible by 18.
    banned_peers6string. This string has the same format as peers6 but +instead represent IPv6 peers that we have banned.
    infoIf this field is present, it should be the info-dictionary +of the torrent this resume data is for. Its SHA-1 hash must +match the one in the info-hash field. When present, +the torrent is loaded from here, meaning the torrent can be +added purely from resume data (no need to load the .torrent +file separately). This may have performance advantages.
    unfinished

    list of dictionaries. Each dictionary represents an +piece, and has the following layout:

    + ++++ + + + + + + + + + + + +
    pieceinteger, the index of the piece this entry +refers to.
    bitmaskstring, a binary bitmask representing the +blocks that have been downloaded in this +piece.
    adler32The adler32 checksum of the data in the +blocks specified by bitmask.
    +
    allocationThe allocation mode for the storage. Can be either +allocate or sparse.
    +
    +
    +
    +

    storage allocation

    +

    There are two modes in which storage (files on disk) are allocated in libtorrent.

    +
      +
    1. The traditional full allocation mode, where the entire files are filled up +with zeros before anything is downloaded. Files are allocated on demand, the +first time anything is written to them. The main benefit of this mode is that +it avoids creating heavily fragmented files.
    2. +
    3. The sparse allocation, sparse files are used, and pieces are downloaded +directly to where they belong. This is the recommended (and default) mode.
    4. +
    +
    +

    sparse allocation

    +

    On filesystems that supports sparse files, this allocation mode will only use +as much space as has been downloaded.

    +

    The main drawback of this mode is that it may create heavily fragmented files.

    +
    +
      +
    • It does not require an allocation pass on startup.
    • +
    +
    +
    +
    +

    full allocation

    +

    When a torrent is started in full allocation mode, the disk-io thread +will make sure that the entire storage is allocated, and fill any gaps with zeros. +It will of course still check for existing pieces and fast resume data. The main +drawbacks of this mode are:

    +
    +
      +
    • It may take longer to start the torrent, since it will need to fill the files +with zeros. This delay is linear to the size of the download.
    • +
    • The download may occupy unnecessary disk space between download sessions.
    • +
    • Disk caches usually perform poorly with random access to large files +and may slow down the download some.
    • +
    +
    +

    The benefits of this mode are:

    +
    +
      +
    • Downloaded pieces are written directly to their final place in the files and +the total number of disk operations will be fewer and may also play nicer to +the filesystem file allocation, and reduce fragmentation.
    • +
    • No risk of a download failing because of a full disk during download, once +all files have been created.
    • +
    +
    +
    +
    +
    +

    HTTP seeding

    +

    There are two kinds of HTTP seeding. One with that assumes a smart (and polite) +client and one that assumes a smart server. These are specified in BEP 19 +and BEP 17 respectively.

    +

    libtorrent supports both. In the libtorrent source code and API, BEP 19 URLs +are typically referred to as url seeds and BEP 17 URLs are typically referred +to as HTTP seeds.

    +

    The libtorrent implementation of BEP 19 assumes that, if the URL ends with a +slash ('/'), the filename should be appended to it in order to request pieces +from that file. The way this works is that if the torrent is a single-file +torrent, only that filename is appended. If the torrent is a multi-file +torrent, the torrent's name '/' the file name is appended. This is the same +directory structure that libtorrent will download torrents into.

    +

    There is limited support for HTTP redirects. In case some files are redirected +to different hosts, the files must be piece aligned or padded to be piece +aligned.

    +
    +
    +

    piece picker

    +

    The piece picker in libtorrent has the following features:

    +
      +
    • rarest first
    • +
    • sequential download
    • +
    • random pick
    • +
    • reverse order picking
    • +
    • parole mode
    • +
    • prioritize partial pieces
    • +
    • prefer whole pieces
    • +
    • piece affinity by speed category
    • +
    • piece priorities
    • +
    +
    +

    internal representation

    +

    It is optimized by, at all times, keeping a list of pieces ordered by rarity, +randomly shuffled within each rarity class. This list is organized as a single +vector of contiguous memory in RAM, for optimal memory locality and to eliminate +heap allocations and frees when updating rarity of pieces.

    +

    Expensive events, like a peer joining or leaving, are evaluated lazily, since +it's cheaper to rebuild the whole list rather than updating every single piece +in it. This means as long as no blocks are picked, peers joining and leaving is +no more costly than a single peer joining or leaving. Of course the special +cases of peers that have all or no pieces are optimized to not require +rebuilding the list.

    +
    +
    +

    picker strategy

    +

    The normal mode of the picker is of course rarest first, meaning pieces that +few peers have are preferred to be downloaded over pieces that more peers have. +This is a fundamental algorithm that is the basis of the performance of +bittorrent. However, the user may set the piece picker into sequential download +mode. This mode simply picks pieces sequentially, always preferring lower piece +indices.

    +

    When a torrent starts out, picking the rarest pieces means increased risk that +pieces won't be completed early (since there are only a few peers they can be +downloaded from), leading to a delay of having any piece to offer to other +peers. This lack of pieces to trade, delays the client from getting started +into the normal tit-for-tat mode of bittorrent, and will result in a long +ramp-up time. The heuristic to mitigate this problem is to, for the first few +pieces, pick random pieces rather than rare pieces. The threshold for when to +leave this initial picker mode is determined by +settings_pack::initial_picker_threshold.

    +
    +
    +

    reverse order

    +

    An orthogonal setting is reverse order, which is used for snubbed peers. +Snubbed peers are peers that appear very slow, and might have timed out a piece +request. The idea behind this is to make all snubbed peers more likely to be +able to do download blocks from the same piece, concentrating slow peers on as +few pieces as possible. The reverse order means that the most common pieces are +picked, instead of the rarest pieces (or in the case of sequential download, +the last pieces, instead of the first).

    +
    +
    +

    parole mode

    +

    Peers that have participated in a piece that failed the hash check, may be put +in parole mode. This means we prefer downloading a full piece from this +peer, in order to distinguish which peer is sending corrupt data. Whether to do +this is or not is controlled by settings_pack::use_parole_mode.

    +

    In parole mode, the piece picker prefers picking one whole piece at a time for +a given peer, avoiding picking any blocks from a piece any other peer has +contributed to (since that would defeat the purpose of parole mode).

    +
    +
    +

    prioritize partial pieces

    +

    This setting determines if partially downloaded or requested pieces should +always be preferred over other pieces. The benefit of doing this is that the +number of partial pieces is minimized (and hence the turn-around time for +downloading a block until it can be uploaded to others is minimized). It also +puts less stress on the disk cache, since fewer partial pieces need to be kept +in the cache. Whether or not to enable this is controlled by +setting_pack::prioritize_partial_pieces.

    +

    The main benefit of not prioritizing partial pieces is that the rarest first +algorithm gets to have more influence on which pieces are picked. The picker is +more likely to truly pick the rarest piece, and hence improving the performance +of the swarm.

    +

    This setting is turned on automatically whenever the number of partial pieces +in the piece picker exceeds the number of peers we're connected to times 1.5. +This is in order to keep the waste of partial pieces to a minimum, but still +prefer rarest pieces.

    +
    +
    +

    prefer whole pieces

    +

    The prefer whole pieces setting makes the piece picker prefer picking entire +pieces at a time. This is used by web connections (both http seeding +standards), in order to be able to coalesce the small bittorrent requests to +larger HTTP requests. This significantly improves performance when downloading +over HTTP.

    +

    It is also used by peers that are downloading faster than a certain threshold. +The main advantage is that these peers will better utilize the other peer's +disk cache, by requesting all blocks in a single piece, from the same peer.

    +

    This threshold is controlled by the settings_pack::whole_pieces_threshold +setting.

    +

    TODO: piece priorities

    +
    +
    +
    +

    Multi-homed hosts

    +

    The settings_pack::listen_interfaces setting is used to specify which interfaces/IP addresses +to listen on, and accept incoming connections via.

    +

    Each item in listen_interfaces is an IP address or a device name, followed +by a listen port number. Each item (called listen_socket_t) will have the +following objects associated with it:

    +
      +
    • a listen socket accepting incoming TCP connections
    • +
    • a UDP socket: +1. to accept incoming uTP connections +2. to run a DHT instance on +3. to announce to UDP trackers from +4. a SOCKS5 UDP tunnel (if applicable)
    • +
    • a listen address and netmask, describing the network the sockets are bound to
    • +
    • a Local service discovery object, broadcasting to the specified subnet
    • +
    • a NAT-PMP/PCP port mapper (if applicable), to map ports on the gateway +for the specified subnet.
    • +
    • a UPnP port mapper (if applicable), to map ports on any
    • +
    • InternetGatewayDevice found on the specified local subnet.
    • +
    +

    A listen_socket_t item may be specified to only be a local network (with +the l suffix). Such listen socket will only be used to talk to peers and +trackers within the same local network. The netmask defining the network is +queried from the operating system by enumerating network interfaces.

    +

    An item that's considered to be "local network" will not be used to announce to +trackers outside of that network. For example, 10.0.0.2:6881l is marked as "local +network" and it will only be used as the source address announcing to a tracker +if the tracker is also within the same local network (e.g. 10.0.0.0/8).

    +

    The NAT-PMP/PCP and UPnP port mapper objects are only created for networks that +are expected to be externally available (i.e. not "local network"). If there are +multiple subnets connected to the internet, they will have separate port mappings.

    +
    +

    expanding device names

    +

    If a device name is specified, libtorrent will expand it to the IP addresses +associated with that device, but also retain the device name in order to attempt +to bind the listen sockets to that specific device.

    +
    +
    +

    expanding unspecified addresses

    +

    If an IP address is the unspecified address (i.e. 0.0.0.0 or ::), +libtorrent will expand it to specific IP addresses. This expansion will +enumerate all addresses it can find for the corresponding address family. +The expanded IP addresses are considered "local network" if any of the following +conditions are met:

    +
      +
    • the IP address is in a known link-local range
    • +
    • the IP address is in a known loopback range
    • +
    • the item the IP address was expanded from was marked local (l)
    • +
    • the network interface has the loopback flag set
    • +
    • NONE of the following conditions are met: +1. the IP address is in a globally reachable IP address range +2. the network interface has the point-to-point flag set +3. the routing table contains a route for at least one global internet address +(e.g. a default route) for the address family of the expanded IP that points to +the network interface of the expanded IP.
    • +
    +
    +
    +

    routing

    +

    A listen_socket_t item is considered able to route to a destination address +if any of these hold:

    +
      +
    • the destination address falls inside its subnet (i.e. interface address masked +by netmask is the same as the destination address masked by the netmask).
    • +
    • the listen_socket_t does not have the "local network" flag set, and the +address family matches the destination address.
    • +
    +

    The ability to route to an address is used when determining whether to announce +to a tracker from a listen_socket_t and whether to open a SOCKS5 UDP tunnel +for a listen_socket_t.

    +

    Note that the actual IP stack routing table is not considered for this purpose. +This mechanism is to determine which IP addresses should be announced to trackers.

    +
    +
    +

    tracker announces

    +

    Trackers are announced to from all network interfaces listening for incoming +connections. However, interfaces that cannot be used to reach the tracker, such +as loopback, are not used as the source address for announces. A +listen_socket_t item that can route to at least one of the tracker IP +addresses will be used as the source address for an announce. Each such item +will also have an announce_endpoint item associated with it, in the tracker +list.

    +

    If a tracker can be reached on a loopback address, then the loopback interface +will be used to announce to that tracker. But under normal circumstances, +loopback will not be used for announcing to trackers.

    +

    For more details, see BEP 7.

    +
    +
    +

    SOCKS5 UDP tunnels

    +

    When using a SOCKS5 proxy, each interface that can route to one of the SOCKS5 +proxy's addresses will be used to open a UDP tunnel, via that proxy. For +example, if a client has both IPv4 and IPv6 connectivity, but the socks5 proxy +only resolves to IPv4, only the IPv4 address will have a UDP tunnel. In that case, +the IPv6 connection will not be used, since it cannot use the proxy.

    +
    +
    +
    +

    rate based choking

    +

    libtorrent supports a choking algorithm that automatically determines the number +of upload slots (unchoke slots) based on the upload rate to peers. It is +controlled by the settings_pack::choking_algorithm setting. The +settings_pack::unchoke_slots_limit is ignored in this mode.

    +

    The algorithm is designed to stay stable, and not oscillate the number of upload +slots.

    +

    The initial rate threshold is set to settings_pack::rate_choker_initial_threshold.

    +

    It sorts all peers by on the rate at which we are uploading to them.

    +
      +
    1. Compare the fastest peer against the initial threshold.
    2. +
    3. Increment the threshold by 2 kiB/s.
    4. +
    5. The next fastest peer is compared against the threshold. +If the peer rate is higher than the threshold. goto 2
    6. +
    7. Terminate. The number of peers visited is the number of unchoke slots, but +never less than 2.
    8. +
    +

    In other words, the more upload slots you have, the higher rate does the slowest +unchoked peer upload at in order to open another slot.

    +
    +
    +

    predictive piece announce

    +

    In order to improve performance, libtorrent supports a feature called +predictive piece announce. When enabled, it will make libtorrent announce +that we have pieces to peers, before we truly have them. The most important +case is to announce a piece as soon as it has been downloaded and passed the +hash check, but not yet been written to disk. In this case, there is a risk the +piece will fail to be written to disk, in which case we won't have the piece +anymore, even though we announced it to peers.

    +

    The other case is when we're very close to completing the download of a piece +and assume it will pass the hash check, we can announce it to peers to make it +available one round-trip sooner than otherwise. This lets libtorrent start +uploading the piece to interested peers immediately when the piece complete, +instead of waiting one round-trip for the peers to request it.

    +

    This makes for the implementation slightly more complicated, since piece will +have more states and more complicated transitions. For instance, a piece could +be:

    +
      +
    1. hashed but not fully written to disk
    2. +
    3. fully written to disk but not hashed
    4. +
    5. not fully downloaded
    6. +
    7. downloaded and hash checked
    8. +
    +

    Once a piece is fully downloaded, the hash check could complete before any of +the write operations or it could complete after all write operations are +complete.

    +
    +
    +

    peer classes

    +

    The peer classes feature in libtorrent allows a client to define custom groups +of peers and rate limit them individually. Each such group is called a peer +class. There are a few default peer classes that are always created:

    +
      +
    • global - all peers belong to this class, except peers on the local network
    • +
    • local peers - all peers on the local network belongs to this class TCP peers
    • +
    • tcp class - all peers connected over TCP belong to this class
    • +
    +

    The TCP peers class is used by the uTP/TCP balancing logic, if it's enabled, to +throttle TCP peers. The global and local classes are used to adjust the global +rate limits.

    +

    When the rate limits are adjusted for a specific torrent, a class is created +implicitly for that torrent.

    +

    The default peer class IDs are defined as enums in the session class:

    +
    +enum {
    +        global_peer_class_id,
    +        tcp_peer_class_id,
    +        local_peer_class_id
    +};
    +
    +

    The default peer classes are automatically created on session startup, and +configured to apply to each respective type of connection. There's nothing +preventing a client from reconfiguring the peer class ip- and type filters +to disable or customize which peers they apply to. See set_peer_class_filter() +and set_peer_class_type_filter().

    +

    A peer class can be considered a more general form of labels that some +clients have. Peer classes however are not just applied to torrents, but +ultimately the peers.

    +

    Peer classes can be created with the create_peer_class() call (on the session +object), and deleted with the delete_peer_class() call.

    +

    Peer classes are configured with the set_peer_class() get_peer_class() calls.

    +

    Custom peer classes can be assigned based on the peer's IP address or the type +of transport protocol used. See set_peer_class_filter() and +set_peer_class_type_filter() for more information.

    +
    +

    peer class examples

    +

    Here are a few examples of common peer class operations.

    +

    To make the global rate limit apply to local peers as well, update the IP-filter +based peer class assignment:

    +
    +std::uint32_t const mask = 1 << lt::session::global_peer_class_id;
    +ip_filter f;
    +
    +// for every IPv4 address, assign the global peer class
    +f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask);
    +
    +// for every IPv6 address, assign the global peer class
    +f.add_rule(make_address("::")
    +        , make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")
    +        , mask);
    +ses.set_peer_class_filter(f);
    +
    +

    To make uTP sockets exempt from rate limiting:

    +
    +peer_class_type_filter flt = ses.get_peer_class_type_filter();
    +// filter out the global and local peer class for uTP sockets, if these
    +// classes are set by the IP filter
    +flt.disallow(peer_class_type_filter::utp_socket, session::global_peer_class_id);
    +flt.disallow(peer_class_type_filter::utp_socket, session::local_peer_class_id);
    +
    +// this filter should not add the global or local peer class to utp sockets
    +flt.remove(peer_class_type_filter::utp_socket, session::global_peer_class_id);
    +flt.remove(peer_class_type_filter::utp_socket, session::local_peer_class_id);
    +
    +ses.set_peer_class_type_filter(flt);
    +
    +

    To make all peers on the internal network not subject to throttling:

    +
    +std::uint32_t const mask = 1 << lt::session::global_peer_class_id;
    +ip_filter f;
    +
    +// for every IPv4 address, assign the global peer class
    +f.add_rule(make_address("0.0.0.0"), make_address("255.255.255.255"), mask);
    +
    +// for every address on the local metwork, set the mask to 0
    +f.add_rule(make_address("10.0.0.0"), make_address("10.255.255.255"), 0);
    +ses.set_peer_class_filter(f);
    +
    +
    +
    +
    +

    SSL torrents

    +

    Torrents may have an SSL root (CA) certificate embedded in them. Such torrents +are called SSL torrents. An SSL torrent talks to all bittorrent peers over +SSL. The protocols are layered like this:

    +utp_stack.png +

    During the SSL handshake, both peers need to authenticate by providing a +certificate that is signed by the CA certificate found in the .torrent file. +These peer certificates are expected to be provided to peers through some other +means than bittorrent. Typically by a peer generating a certificate request +which is sent to the publisher of the torrent, and the publisher returning a +signed certificate.

    +

    In libtorrent, set_ssl_certificate() in torrent_handle is used to tell +libtorrent where to find the peer certificate and the private key for it. When +an SSL torrent is loaded, the torrent_need_cert_alert is posted to remind the +user to provide a certificate.

    +

    A peer connecting to an SSL torrent MUST provide the SNI TLS extension +(server name indication). The server name is the hex encoded info-hash of the +torrent to connect to. This is required for the client accepting the connection +to know which certificate to present.

    +

    SSL connections are accepted on a separate socket from normal bittorrent +connections. To enable support for SSL torrents, add a listen interface to the +settings_pack::listen_interfaces setting with the s suffix. For example:

    +
    +0.0.0.0:6881,0.0.0.0:6882s
    +
    +

    That will listen for normal bittorrent connections on port 6881 and for SSL +torrent connections on port 6882.

    +

    This feature is only available if libtorrent is build with openssl support +(TORRENT_USE_OPENSSL) and requires at least OpenSSL version 1.0, since it +needs SNI support.

    +

    Peer certificates must have at least one SubjectAltName field of type +DNSName. At least one of the fields must exactly match the name of the +torrent. This is a byte-by-byte comparison, the UTF-8 encoding must be +identical (i.e. there's no unicode normalization going on). This is the +recommended way of verifying certificates for HTTPS servers according to RFC +2818. Note the difference that for torrents only DNSName fields are taken +into account (not IP address fields). The most specific (i.e. last) Common +Name field is also taken into account if no SubjectAltName did not match.

    +

    If any of these fields contain a single asterisk ("*"), the certificate is +considered covering any torrent, allowing it to be reused for any torrent.

    +

    The purpose of matching the torrent name with the fields in the peer +certificate is to allow a publisher to have a single root certificate for all +torrents it distributes, and issue separate peer certificates for each torrent. +A peer receiving a certificate will not necessarily be able to access all +torrents published by this root certificate (only if it has a "star cert").

    +
    +

    testing

    +

    To test incoming SSL connections to an SSL torrent, one can use the following +openssl command:

    +
    +openssl s_client -cert <peer-certificate>.pem -key <peer-private-key>.pem -CAfile \
    +   <torrent-cert>.pem -debug -connect 127.0.0.1:4433 -tls1 -servername <info-hash>
    +
    +

    To create a root certificate, the Distinguished Name (DN) is not taken into +account by bittorrent peers. You still need to specify something, but from +libtorrent's point of view, it doesn't matter what it is. libtorrent only makes +sure the peer certificates are signed by the correct root certificate.

    +

    One way to create the certificates is to use the CA.sh script that comes +with openssl, like this (don't forget to enter a common Name for the +certificate):

    +
    +CA.sh -newca
    +CA.sh -newreq
    +CA.sh -sign
    +
    +

    The torrent certificate is located in ./demoCA/private/demoCA/cacert.pem, +this is the pem file to include in the .torrent file.

    +

    The peer's certificate is located in ./newcert.pem and the certificate's +private key in ./newkey.pem.

    +
    +
    +
    +

    session statistics

    +

    libtorrent provides a mechanism to query performance and statistics counters +from its internals.

    +

    The statistics consists of two fundamental types. counters and gauges. A +counter is a monotonically increasing value, incremented every time some event +occurs. For example, every time the network thread wakes up because a socket +became readable will increment a counter. Another example is every time a +socket receives n bytes, a counter is incremented by n.

    +

    Counters are the most flexible of metrics. It allows the program to sample +the counter at any interval, and calculate average rates of increments to the +counter. Some events may be rare and need to be sampled over a longer period in +order to get useful rates, where other events may be more frequent and evenly +distributed that sampling it frequently yields useful values. Counters also +provides accurate overall counts. For example, converting samples of a download +rate into a total transfer count is not accurate and takes more samples. +Converting an increasing counter into a rate is easy and flexible.

    +

    Gauges measure the instantaneous state of some kind. This is used for metrics +that are not counting events or flows, but states that can fluctuate. For +example, the number of torrents that are currently being downloaded.

    +

    It's important to know whether a value is a counter or a gauge in order to +interpret it correctly. In order to query libtorrent for which counters and +gauges are available, call session_stats_metrics(). This will return metadata +about the values available for inspection in libtorrent. It will include +whether a value is a counter or a gauge. The key information it includes is the +index used to extract the actual measurements for a specific counter or gauge.

    +

    In order to take a sample, call post_session_stats() in the session object. +This will result in a session_stats_alert being posted. In this alert object, +there is an array of values, these values make up the sample. The value index +in the stats metric indicates which index the metric's value is stored in.

    +

    The mapping between metric and value is not stable across versions of +libtorrent. Always query the metrics first, to find out the index at which the +value is stored, before interpreting the values array in the +session_stats_alert. The mapping will not change during the runtime of your +process though, it's tied to a specific libtorrent version. You only have to +query the mapping once on startup (or every time libtorrent.so is loaded, +if it's done dynamically).

    +

    The available stats metrics are:

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_peerscounter
    peer.disconnected_peerscounter
    +

    error_peers is the total number of peer disconnects +caused by an error (not initiated by this client) and +disconnected initiated by this client (disconnected_peers).

    + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.eof_peerscounter
    peer.connreset_peerscounter
    peer.connrefused_peerscounter
    peer.connaborted_peerscounter
    peer.notconnected_peerscounter
    peer.perm_peerscounter
    peer.buffer_peerscounter
    peer.unreachable_peerscounter
    peer.broken_pipe_peerscounter
    peer.addrinuse_peerscounter
    peer.no_access_peerscounter
    peer.invalid_arg_peerscounter
    peer.aborted_peerscounter
    +

    these counters break down the peer errors into more specific +categories. These errors are what the underlying transport +reported (i.e. TCP or uTP)

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.piece_requestscounter
    peer.max_piece_requestscounter
    peer.invalid_piece_requestscounter
    peer.choked_piece_requestscounter
    peer.cancelled_piece_requestscounter
    peer.piece_rejectscounter
    +

    the total number of incoming piece requests we've received followed +by the number of rejected piece requests for various reasons. +max_piece_requests mean we already had too many outstanding requests +from this peer, so we rejected it. cancelled_piece_requests are ones +where the other end explicitly asked for the piece to be rejected.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_incoming_peerscounter
    peer.error_outgoing_peerscounter
    +

    these counters break down the peer errors into +whether they happen on incoming or outgoing peers.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_rc4_peerscounter
    peer.error_encrypted_peerscounter
    +

    these counters break down the peer errors into +whether they happen on encrypted peers (just +encrypted handshake) and rc4 peers (full stream +encryption). These can indicate whether encrypted +peers are more or less likely to fail

    + + ++++ + + + + + + + + + + + + + +
    nametype
    peer.error_tcp_peerscounter
    peer.error_utp_peerscounter
    +

    these counters break down the peer errors into +whether they happen on uTP peers or TCP peers. +these may indicate whether one protocol is +more error prone

    + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.connect_timeoutscounter
    peer.uninteresting_peerscounter
    peer.timeout_peerscounter
    peer.no_memory_peerscounter
    peer.too_many_peerscounter
    peer.transport_timeout_peerscounter
    peer.num_banned_peerscounter
    peer.banned_for_hash_failurecounter
    peer.connection_attemptscounter
    peer.connection_attempt_loopscounter
    peer.boost_connection_attemptscounter
    peer.missed_connection_attemptscounter
    peer.no_peer_connection_attemptscounter
    peer.incoming_connectionscounter
    +

    these counters break down the reasons to +disconnect peers.

    + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    peer.num_tcp_peersgauge
    peer.num_socks5_peersgauge
    peer.num_http_proxy_peersgauge
    peer.num_utp_peersgauge
    peer.num_i2p_peersgauge
    peer.num_ssl_peersgauge
    peer.num_ssl_socks5_peersgauge
    peer.num_ssl_http_proxy_peersgauge
    peer.num_ssl_utp_peersgauge
    peer.num_peers_half_opengauge
    peer.num_peers_connectedgauge
    peer.num_peers_up_interestedgauge
    peer.num_peers_down_interestedgauge
    peer.num_peers_up_unchoked_allgauge
    peer.num_peers_up_unchoked_optimisticgauge
    peer.num_peers_up_unchokedgauge
    peer.num_peers_down_unchokedgauge
    peer.num_peers_up_requestsgauge
    peer.num_peers_down_requestsgauge
    peer.num_peers_end_gamegauge
    peer.num_peers_up_diskgauge
    peer.num_peers_down_diskgauge
    +

    the number of peer connections for each kind of socket. +num_peers_half_open counts half-open (connecting) peers, no other +count includes those peers. +num_peers_up_unchoked_all is the total number of unchoked peers, +whereas num_peers_up_unchoked only are unchoked peers that count +against the limit (i.e. excluding peers that are unchoked because the +limit doesn't apply to them). num_peers_up_unchoked_optimistic is +the number of optimistically unchoked peers.

    + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    net.on_read_countercounter
    net.on_write_countercounter
    net.on_tick_countercounter
    net.on_lsd_countercounter
    net.on_lsd_peer_countercounter
    net.on_udp_countercounter
    net.on_accept_countercounter
    net.on_disk_queue_countercounter
    net.on_disk_countercounter
    +

    These counters count the number of times the +network thread wakes up for each respective +reason. If these counters are very large, it +may indicate a performance issue, causing the +network thread to wake up too ofte, wasting CPU. +mitigate it by increasing buffers and limits +for the specific trigger that wakes up the +thread.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    net.sent_payload_bytescounter
    net.sent_bytescounter
    net.sent_ip_overhead_bytescounter
    net.sent_tracker_bytescounter
    net.recv_payload_bytescounter
    net.recv_bytescounter
    net.recv_ip_overhead_bytescounter
    net.recv_tracker_bytescounter
    +

    total number of bytes sent and received by the session

    + + ++++ + + + + + + + + + + + + + +
    nametype
    net.limiter_up_queuegauge
    net.limiter_down_queuegauge
    +

    the number of sockets currently waiting for upload and download +bandwidth from the rate limiter.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    net.limiter_up_bytesgauge
    net.limiter_down_bytesgauge
    +

    the number of upload and download bytes waiting to be handed out from +the rate limiter.

    + ++++ + + + + + + + + + + +
    nametype
    net.recv_failed_bytescounter
    +

    the number of bytes downloaded that had to be discarded because they +failed the hash check

    + ++++ + + + + + + + + + + +
    nametype
    net.recv_redundant_bytescounter
    +

    the number of downloaded bytes that were discarded because they +were downloaded multiple times (from different peers)

    + ++++ + + + + + + + + + + +
    nametype
    net.has_incoming_connectionsgauge
    +

    is false by default and set to true when +the first incoming connection is established +this is used to know if the client is behind +NAT or not.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_checking_torrentsgauge
    ses.num_stopped_torrentsgauge
    ses.num_upload_only_torrentsgauge
    ses.num_downloading_torrentsgauge
    ses.num_seeding_torrentsgauge
    ses.num_queued_seeding_torrentsgauge
    ses.num_queued_download_torrentsgauge
    ses.num_error_torrentsgauge
    +

    these gauges count the number of torrents in +different states. Each torrent only belongs to +one of these states. For torrents that could +belong to multiple of these, the most prominent +in picked. For instance, a torrent with an error +counts as an error-torrent, regardless of its other +state.

    + ++++ + + + + + + + + + + +
    nametype
    ses.non_filter_torrentsgauge
    +

    the number of torrents that don't have the +IP filter applied to them.

    + + + + ++++ + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_piece_passedcounter
    ses.num_piece_failedcounter
    ses.num_have_piecescounter
    ses.num_total_pieces_addedcounter
    +

    these count the number of times a piece has passed the +hash check, the number of times a piece was successfully +written to disk and the number of total possible pieces +added by adding torrents. e.g. when adding a torrent with +1000 piece, num_total_pieces_added is incremented by 1000.

    + ++++ + + + + + + + + + + +
    nametype
    ses.num_unchoke_slotsgauge
    +

    the number of allowed unchoked peers

    + ++++ + + + + + + + + + + +
    nametype
    ses.num_outstanding_acceptgauge
    +

    the number of listen sockets that are currently accepting incoming +connections

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.num_incoming_chokecounter
    ses.num_incoming_unchokecounter
    ses.num_incoming_interestedcounter
    ses.num_incoming_not_interestedcounter
    ses.num_incoming_havecounter
    ses.num_incoming_bitfieldcounter
    ses.num_incoming_requestcounter
    ses.num_incoming_piececounter
    ses.num_incoming_cancelcounter
    ses.num_incoming_dht_portcounter
    ses.num_incoming_suggestcounter
    ses.num_incoming_have_allcounter
    ses.num_incoming_have_nonecounter
    ses.num_incoming_rejectcounter
    ses.num_incoming_allowed_fastcounter
    ses.num_incoming_ext_handshakecounter
    ses.num_incoming_pexcounter
    ses.num_incoming_metadatacounter
    ses.num_incoming_extendedcounter
    ses.num_outgoing_chokecounter
    ses.num_outgoing_unchokecounter
    ses.num_outgoing_interestedcounter
    ses.num_outgoing_not_interestedcounter
    ses.num_outgoing_havecounter
    ses.num_outgoing_bitfieldcounter
    ses.num_outgoing_requestcounter
    ses.num_outgoing_piececounter
    ses.num_outgoing_cancelcounter
    ses.num_outgoing_dht_portcounter
    ses.num_outgoing_suggestcounter
    ses.num_outgoing_have_allcounter
    ses.num_outgoing_have_nonecounter
    ses.num_outgoing_rejectcounter
    ses.num_outgoing_allowed_fastcounter
    ses.num_outgoing_ext_handshakecounter
    ses.num_outgoing_pexcounter
    ses.num_outgoing_metadatacounter
    ses.num_outgoing_extendedcounter
    +

    bittorrent message counters. These counters are incremented +every time a message of the corresponding type is received from +or sent to a bittorrent peer.

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    ses.waste_piece_timed_outcounter
    ses.waste_piece_cancelledcounter
    ses.waste_piece_unknowncounter
    ses.waste_piece_seedcounter
    ses.waste_piece_end_gamecounter
    ses.waste_piece_closingcounter
    +

    the number of wasted downloaded bytes by reason of the bytes being +wasted.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    picker.piece_picker_partial_loopscounter
    picker.piece_picker_suggest_loopscounter
    picker.piece_picker_sequential_loopscounter
    picker.piece_picker_reverse_rare_loopscounter
    picker.piece_picker_rare_loopscounter
    picker.piece_picker_rand_start_loopscounter
    picker.piece_picker_rand_loopscounter
    picker.piece_picker_busy_loopscounter
    +

    the number of pieces considered while picking pieces

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    picker.reject_piece_pickscounter
    picker.unchoke_piece_pickscounter
    picker.incoming_redundant_piece_pickscounter
    picker.incoming_piece_pickscounter
    picker.end_game_piece_pickscounter
    picker.snubbed_piece_pickscounter
    picker.interesting_piece_pickscounter
    picker.hash_fail_piece_pickscounter
    +

    This breaks down the piece picks into the event that +triggered it

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.write_cache_blocksgauge
    disk.read_cache_blocksgauge
    +

    These gauges indicate how many blocks are currently in use as dirty +disk blocks (write_cache_blocks) and read cache blocks, +respectively. deprecates cache_status::read_cache_size. +The sum of these gauges deprecates cache_status::cache_size.

    + ++++ + + + + + + + + + + +
    nametype
    disk.request_latencygauge
    +

    the number of microseconds it takes from receiving a request from a +peer until we're sending the response back on the socket.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.pinned_blocksgauge
    disk.disk_blocks_in_usegauge
    +

    disk_blocks_in_use indicates how many disk blocks are currently in +use, either as dirty blocks waiting to be written or blocks kept around +in the hope that a peer will request it or in a peer send buffer. This +gauge deprecates cache_status::total_used_buffers.

    + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.queued_disk_jobsgauge
    disk.num_running_disk_jobsgauge
    disk.num_read_jobsgauge
    disk.num_write_jobsgauge
    disk.num_jobsgauge
    disk.blocked_disk_jobsgauge
    disk.num_writing_threadsgauge
    disk.num_running_threadsgauge
    +

    queued_disk_jobs is the number of disk jobs currently queued, +waiting to be executed by a disk thread. Deprecates +cache_status::job_queue_length.

    + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.queued_write_bytesgauge
    disk.arc_mru_sizegauge
    disk.arc_mru_ghost_sizegauge
    disk.arc_mfu_sizegauge
    disk.arc_mfu_ghost_sizegauge
    disk.arc_write_sizegauge
    disk.arc_volatile_sizegauge
    +

    the number of bytes we have sent to the disk I/O +thread for writing. Every time we hear back from +the disk I/O thread with a completed write job, this +is updated to the number of bytes the disk I/O thread +is actually waiting for to be written (as opposed to +bytes just hanging out in the cache)

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.num_blocks_writtencounter
    disk.num_blocks_readcounter
    +

    the number of blocks written and read from disk in total. A block is 16 +kiB. num_blocks_written and num_blocks_read deprecates +cache_status::blocks_written and cache_status::blocks_read respectively.

    + ++++ + + + + + + + + + + +
    nametype
    disk.num_blocks_hashedcounter
    +

    the total number of blocks run through SHA-1 hashing

    + ++++ + + + + + + + + + + +
    nametype
    disk.num_blocks_cache_hitscounter
    +

    the number of blocks read from the disk cache +Deprecates cache_info::blocks_read_hit.

    + + ++++ + + + + + + + + + + + + + +
    nametype
    disk.num_write_opscounter
    disk.num_read_opscounter
    +

    the number of disk I/O operation for reads and writes. One disk +operation may transfer more then one block. +These counters deprecates cache_status::writes and +cache_status::reads.

    + ++++ + + + + + + + + + + +
    nametype
    disk.num_read_backcounter
    +

    the number of blocks that had to be read back from disk in order to +hash a piece (when verifying against the piece hash)

    + + + + ++++ + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.disk_read_timecounter
    disk.disk_write_timecounter
    disk.disk_hash_timecounter
    disk.disk_job_timecounter
    +

    cumulative time spent in various disk jobs, as well +as total for all disk jobs. Measured in microseconds

    + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    disk.num_fenced_readgauge
    disk.num_fenced_writegauge
    disk.num_fenced_hashgauge
    disk.num_fenced_move_storagegauge
    disk.num_fenced_release_filesgauge
    disk.num_fenced_delete_filesgauge
    disk.num_fenced_check_fastresumegauge
    disk.num_fenced_save_resume_datagauge
    disk.num_fenced_rename_filegauge
    disk.num_fenced_stop_torrentgauge
    disk.num_fenced_flush_piecegauge
    disk.num_fenced_flush_hashedgauge
    disk.num_fenced_flush_storagegauge
    disk.num_fenced_trim_cachegauge
    disk.num_fenced_file_prioritygauge
    disk.num_fenced_load_torrentgauge
    disk.num_fenced_clear_piecegauge
    disk.num_fenced_tick_storagegauge
    +

    for each kind of disk job, a counter of how many jobs of that kind +are currently blocked by a disk fence

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_nodesgauge
    +

    The number of nodes in the DHT routing table

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_node_cachegauge
    +

    The number of replacement nodes in the DHT routing table

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_torrentsgauge
    +

    the number of torrents currently tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_peersgauge
    +

    the number of peers currently tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_immutable_datagauge
    +

    the number of immutable data items tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_mutable_datagauge
    +

    the number of mutable data items tracked by our DHT node

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_allocated_observersgauge
    +

    the number of RPC observers currently allocated

    + + ++++ + + + + + + + + + + + + + +
    nametype
    dht.dht_messages_incounter
    dht.dht_messages_outcounter
    +

    the total number of DHT messages sent and received

    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_messages_in_droppedcounter
    +

    the number of incoming DHT requests that were dropped. There are a few +different reasons why incoming DHT packets may be dropped:

    +
      +
    1. there wasn't enough send quota to respond to them.
    2. +
    3. the Denial of service logic kicked in, blocking the peer
    4. +
    5. ignore_dark_internet is enabled, and the packet came from a +non-public IP address
    6. +
    7. the bencoding of the message was invalid
    8. +
    + ++++ + + + + + + + + + + +
    nametype
    dht.dht_messages_out_droppedcounter
    +

    the number of outgoing messages that failed to be +sent

    + + ++++ + + + + + + + + + + + + + +
    nametype
    dht.dht_bytes_incounter
    dht.dht_bytes_outcounter
    +

    the total number of bytes sent and received by the DHT

    + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    dht.dht_ping_incounter
    dht.dht_ping_outcounter
    dht.dht_find_node_incounter
    dht.dht_find_node_outcounter
    dht.dht_get_peers_incounter
    dht.dht_get_peers_outcounter
    dht.dht_announce_peer_incounter
    dht.dht_announce_peer_outcounter
    dht.dht_get_incounter
    dht.dht_get_outcounter
    dht.dht_put_incounter
    dht.dht_put_outcounter
    dht.dht_sample_infohashes_incounter
    dht.dht_sample_infohashes_outcounter
    +

    the number of DHT messages we've sent and received +by kind.

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    dht.dht_invalid_announcecounter
    dht.dht_invalid_get_peerscounter
    dht.dht_invalid_find_nodecounter
    dht.dht_invalid_putcounter
    dht.dht_invalid_getcounter
    dht.dht_invalid_sample_infohashescounter
    +

    the number of failed incoming DHT requests by kind of request

    + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    utp.utp_packet_losscounter
    utp.utp_timeoutcounter
    utp.utp_packets_incounter
    utp.utp_packets_outcounter
    utp.utp_fast_retransmitcounter
    utp.utp_packet_resendcounter
    utp.utp_samples_above_targetcounter
    utp.utp_samples_below_targetcounter
    utp.utp_payload_pkts_incounter
    utp.utp_payload_pkts_outcounter
    utp.utp_invalid_pkts_incounter
    utp.utp_redundant_pkts_incounter
    +

    uTP counters. Each counter represents the number of time each event +has occurred.

    + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    utp.num_utp_idlegauge
    utp.num_utp_syn_sentgauge
    utp.num_utp_connectedgauge
    utp.num_utp_fin_sentgauge
    utp.num_utp_close_waitgauge
    utp.num_utp_deletedgauge
    +

    the number of uTP sockets in each respective state

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametype
    sock_bufs.socket_send_size3counter
    sock_bufs.socket_send_size4counter
    sock_bufs.socket_send_size5counter
    sock_bufs.socket_send_size6counter
    sock_bufs.socket_send_size7counter
    sock_bufs.socket_send_size8counter
    sock_bufs.socket_send_size9counter
    sock_bufs.socket_send_size10counter
    sock_bufs.socket_send_size11counter
    sock_bufs.socket_send_size12counter
    sock_bufs.socket_send_size13counter
    sock_bufs.socket_send_size14counter
    sock_bufs.socket_send_size15counter
    sock_bufs.socket_send_size16counter
    sock_bufs.socket_send_size17counter
    sock_bufs.socket_send_size18counter
    sock_bufs.socket_send_size19counter
    sock_bufs.socket_send_size20counter
    sock_bufs.socket_recv_size3counter
    sock_bufs.socket_recv_size4counter
    sock_bufs.socket_recv_size5counter
    sock_bufs.socket_recv_size6counter
    sock_bufs.socket_recv_size7counter
    sock_bufs.socket_recv_size8counter
    sock_bufs.socket_recv_size9counter
    sock_bufs.socket_recv_size10counter
    sock_bufs.socket_recv_size11counter
    sock_bufs.socket_recv_size12counter
    sock_bufs.socket_recv_size13counter
    sock_bufs.socket_recv_size14counter
    sock_bufs.socket_recv_size15counter
    sock_bufs.socket_recv_size16counter
    sock_bufs.socket_recv_size17counter
    sock_bufs.socket_recv_size18counter
    sock_bufs.socket_recv_size19counter
    sock_bufs.socket_recv_size20counter
    +

    the buffer sizes accepted by +socket send and receive calls respectively. +The larger the buffers are, the more efficient, +because it reqire fewer system calls per byte. +The size is 1 << n, where n is the number +at the end of the counter name. i.e. +8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, +16384, 32768, 65536, 131072, 262144, 524288, 1048576 +bytes

    + ++++ + + + + + + + + + + +
    nametype
    tracker.num_queued_tracker_announcesgauge
    +

    if the outstanding tracker announce limit is reached, tracker +announces are queued, to be issued when an announce slot opens up. +this measure the number of tracker announces currently in the +queue

    +
    +
    +

    glossary

    +

    The libtorrent documentation use words that are bittorrent terms of art. This +section defines some of these words. For an overview of what bittorrent is and +how it works, see these slides. For an introduction to the bittorrent DHT, see +this presentation.

    +
    +
    announce
    +
    The act of telling a tracker or the DHT network about the existence of +oneself and how other peers can connect, by specifying port one is listening +on.
    +
    block
    +
    A subset of a piece. Almost always 16 kiB of payload, unless the piece size is +smaller. This is the granularity file payload is requested from peers on the +network.
    +
    DHT
    +
    The distributed hash table is a cross-swarm, world-wide network of bittorrent +peers. It's loosely connected, implementing the Kademlia protocol. Its purpose +is to act as a tracker. Peers can announce their presence to nodes on the DHT +and other peers can discover them to join the swarm.
    +
    HTTP tracker
    +
    A tracker that uses the HTTP protocol for announces.
    +
    info dictionary
    +
    The subset of a torrent file that describes piece hashes and file names. This +is the only mandatory part necessary to join the swarm (network of peers) for +the torrent.
    +
    info hash
    +
    The hash of the info dictionary. This uniquely identifies a torrent and is +used by the protocol to ensure peers talking to each other agree on which swarm +they are participating in. Sometimes spelled info-hash.
    +
    leecher
    +
    A peer that is still interested in downloading more pieces for the torrent. +It is not a seed.
    +
    magnet link
    +
    A URI containing the info hash for a torrent, allowing peers to join its +swarm. May optionally contain a display name, trackers and web seeds. +Typically magnet links rely on peers joining the swarm via the DHT.
    +
    metadata
    +
    Synonymous to a torrent file
    +
    peer
    +
    A computer running bittorrent client software that participates in the network +for a particular torrent/set of files.
    +
    piece
    +
    The smallest number of bytes that can be validated when downloading (no +longer the case in bittorrent V2). The smallest part of the files that can be +advertised to other peers. The size of a piece is determined by the info +dictionary inside the torrent file.
    +
    seed
    +
    A computer running bittorrent client software that has the complete files for +a specific torrent, able to share any piece for that file with other peers in +the network
    +
    swarm
    +
    The network of peers participating in sharing and downloading of a specific torrent.
    +
    torrent
    +
    May refer to a torrent file or the swarm (network of peers) created around +the torrent file.
    +
    torrent file
    +
    A file ending in .torrent describing the content of a set of files (but not +containing the content). Importantly, it contains hashes of all files, split +up into pieces. It may optionally contain references to trackers and nodes on +the DHT network to aid peers in joining the network of peers sharing +these files.
    +
    tracker
    +
    A server peers can announce to and receive other peers back belonging to the +same swarm. Trackers are used to introduce peers to each other, within a swarm. +When announcing, the info hash of the torrent is included. Trackers can +introduce peers to any info-hash that's specified, given other peers also use +the same tracker. Some trackers restrict which info hashes they support based +on a white list.
    +
    UDP tracker
    +
    A tracker that uses a UDP based protocol for announces.
    +
    web seed
    +
    A web server that is acting a seed, providing access to all pieces of all +files over HTTP. This is an extension that client software may or may not +support.
    +
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +
    +
    +

    tuning libtorrent

    +

    libtorrent expose most parameters used in the bittorrent engine for +customization through the settings_pack. This makes it possible to +test and tweak the parameters for certain algorithms to make a client +that fits a wide range of needs. From low memory embedded devices to +servers seeding thousands of torrents. The default settings in libtorrent +are tuned for an end-user bittorrent client running on a normal desktop +computer.

    +

    This document describes techniques to benchmark libtorrent performance +and how parameters are likely to affect it.

    +
    +
    +

    profiling

    +

    libtorrent is instrumented with a number of counters and gauges you can have +access to via the session_stats_alert. First, enable these alerts in the +alert mask:

    +
    +settings_pack p;
    +p.set_int(settings_mask::alert_mask, alert::stats_notification);
    +ses.apply_settings(p);
    +
    +

    Then print alerts to a file:

    +
    +std::vector<alert*> alerts;
    +ses.pop_alerts(&alerts);
    +
    +for (auto* a : alerts) {
    +        std::cout << a->message() << "\n";
    +}
    +
    +

    If you want to separate generic alerts from session stats, you can filter on the +alert category in the alert, alert::category().

    +

    The alerts with data will have the type session_stats_alert and there is one +session_log_alert that will be posted on startup containing the column names +for all metrics. Logging this line will greatly simplify interpreting the output.

    +

    The python scrip in tools/parse_session_stats.py can parse the resulting +file and produce graphs of relevant stats. It requires gnuplot.

    +
    +
    +

    reducing memory footprint

    +

    These are things you can do to reduce the memory footprint of libtorrent. You get +some of this by basing your default settings_pack on the min_memory_usage() +setting preset function.

    +

    Keep in mind that lowering memory usage will affect performance, always profile +and benchmark your settings to determine if it's worth the trade-off.

    +

    The typical buffer usage of libtorrent, for a single download, with the cache +size set to 256 blocks (256 * 16 kiB = 4 MiB) is:

    +
    +read cache:      128.6 (2058 kiB)
    +write cache:     103.5 (1656 kiB)
    +receive buffers: 7.3   (117 kiB)
    +send buffers:    4.8   (77 kiB)
    +hash temp:       0.001 (19 Bytes)
    +
    +

    The receive buffers is proportional to the number of connections we make, and is +limited by the total number of connections in the session (default is 200).

    +

    The send buffers is proportional to the number of upload slots that are allowed +in the session. The default is auto configured based on the observed upload rate.

    +

    The read and write cache can be controlled (see section below).

    +

    The "hash temp" entry size depends on whether or not hashing is optimized for +speed or memory usage. In this test run it was optimized for memory usage.

    +
    +

    disable disk cache

    +

    The bulk of the memory libtorrent will use is used for the disk cache. To save +the absolute most amount of memory, you can disable the cache by setting +settings_pack::cache_size to 0. You might want to consider using the cache +but just disable caching read operations. You do this by settings +settings_pack::use_read_cache to false. This is the main factor in how much +memory will be used by the client. Keep in mind that you will degrade performance +by disabling the cache. You should benchmark the disk access in order to make an +informed trade-off.

    +
    +
    +

    remove torrents

    +

    Torrents that have been added to libtorrent will inevitably use up memory, even +when it's paused. A paused torrent will not use any peer connection objects or +any send or receive buffers though. Any added torrent holds the entire .torrent +file in memory, it also remembers the entire list of peers that it's heard about +(which can be fairly long unless it's capped). It also retains information about +which blocks and pieces we have on disk, which can be significant for torrents +with many pieces.

    +

    If you need to minimize the memory footprint, consider removing torrents from +the session rather than pausing them. This will likely only make a difference +when you have a very large number of torrents in a session.

    +

    The downside of removing them is that they will no longer be auto-managed. Paused +auto managed torrents are scraped periodically, to determine which torrents are +in the greatest need of seeding, and libtorrent will prioritize to seed those.

    +
    +
    +

    socket buffer sizes

    +

    You can make libtorrent explicitly set the kernel buffer sizes of all its peer +sockets. If you set this to a low number, you may see reduced throughput, especially +for high latency connections. It is however an opportunity to save memory per +connection, and might be worth considering if you have a very large number of +peer connections. This memory will not be visible in your process, this sets +the amount of kernel memory is used for your sockets.

    +

    Change this by setting settings_pack::recv_socket_buffer_size and +settings_pack::send_socket_buffer_size.

    +
    +
    +

    peer list size

    +

    The default maximum for the peer list is 4000 peers. For IPv4 peers, each peer +entry uses 32 bytes, which ends up using 128 kB per torrent. If seeding 4 popular +torrents, the peer lists alone uses about half a megabyte.

    +

    The default limit is the same for paused torrents as well, so if you have a +large number of paused torrents (that are popular) it will be even more +significant.

    +

    If you're short of memory, you should consider lowering the limit. 500 is probably +enough. You can do this by setting settings_pack::max_peerlist_size to +the max number of peers you want in a torrent's peer list. This limit applies per +torrent. For 5 torrents, the total number of peers in peer lists will be 5 times +the setting.

    +

    You should also lower the same limit but for paused torrents. It might even make sense +to set that even lower, since you only need a few peers to start up while waiting +for the tracker and DHT to give you fresh ones. The max peer list size for paused +torrents is set by settings_pack::max_paused_peerlist_size.

    +

    The drawback of lowering this number is that if you end up in a position where +the tracker is down for an extended period of time, your only hope of finding live +peers is to go through your list of all peers you've ever seen. Having a large +peer list will also help increase performance when starting up, since the torrent +can start connecting to peers in parallel with connecting to the tracker.

    +
    +
    +

    send buffer watermark

    +

    The send buffer watermark controls when libtorrent will ask the disk I/O thread +to read blocks from disk, and append it to a peer's send buffer.

    +

    When the send buffer has fewer than or equal number of bytes as +settings_pack::send_buffer_watermark, the peer will ask the disk I/O thread +for more data to send. The trade-off here is between wasting memory by having too +much data in the send buffer, and hurting send rate by starving out the socket, +waiting for the disk read operation to complete.

    +

    If your main objective is memory usage and you're not concerned about being able +to achieve high send rates, you can set the watermark to 9 bytes. This will guarantee +that no more than a single (16 kiB) block will be on the send buffer at a time, for +all peers. This is the least amount of memory possible for the send buffer.

    +

    You should benchmark your max send rate when adjusting this setting. If you have +a very fast disk, you are less likely see a performance hit.

    +
    +
    +

    reduce executable size

    +

    Compilers generally add a significant number of bytes to executables that make use +of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can +reduce the executable size with up to 45%. In order to build without exception +support, you need to patch parts of boost.

    +

    Also make sure to optimize for size when compiling.

    +

    Another way of reducing the executable size is to disable code that isn't used. +There are a number of TORRENT_* macros that control which features are included +in libtorrent. If these macros are used to strip down libtorrent, make sure the same +macros are defined when building libtorrent as when linking against it. If these +are different the structures will look different from the libtorrent side and from +the client side and memory corruption will follow.

    +

    One, probably, safe macro to define is TORRENT_NO_DEPRECATE which removes all +deprecated functions and struct members. As long as no deprecated functions are +relied upon, this should be a simple way to eliminate a little bit of code.

    +

    For all available options, see the building libtorrent section.

    +
    +
    +
    +

    high performance seeding

    +

    In the case of a high volume seed, there are two main concerns. Performance and scalability. +This translates into high send rates, and low memory and CPU usage per peer connection.

    +
    +

    file pool

    +

    libtorrent keeps an LRU file cache. Each file that is opened, is stuck in the cache. The main +purpose of this is because of anti-virus software that hooks on file-open and file close to +scan the file. Anti-virus software that does that will significantly increase the cost of +opening and closing files. However, for a high performance seed, the file open/close might +be so frequent that it becomes a significant cost. It might therefore be a good idea to allow +a large file descriptor cache. Adjust this though settings_pack::file_pool_size.

    +

    Don't forget to set a high rlimit for file descriptors in your process as well. This limit +must be high enough to keep all connections and files open.

    +
    +
    +

    disk cache

    +

    You typically want to set the cache size to as high as possible. The +settings_pack::cache_size is specified in 16 kiB blocks. Since you're seeding, +the cache would be useless unless you also set settings_pack::use_read_cache +to true.

    +

    In order to increase the possibility of read cache hits, set the +settings_pack::cache_expiry to a large number. This won't degrade anything as +long as the client is only seeding, and not downloading any torrents.

    +

    There's a guided cache mode. This means the size of the read cache line that's +stored in the cache is determined based on the upload rate to the peer that +triggered the read operation. The idea being that slow peers don't use up a +disproportional amount of space in the cache. This is enabled through +settings_pack::guided_read_cache.

    +

    In cases where the assumption is that the cache is only used as a read-ahead, and that no +other peer will ever request the same block while it's still in the cache, the read +cache can be set to be volatile. This means that every block that is requested out of +the read cache is removed immediately. This saves a significant amount of cache space +which can be used as read-ahead for other peers. To enable volatile read cache, set +settings_pack::volatile_read_cache to true.

    +
    +
    +

    uTP-TCP mixed mode

    +

    libtorrent supports uTP, which has a delay based congestion controller. In order to +avoid having a single TCP bittorrent connection completely starve out any uTP connection, +there is a mixed mode algorithm. This attempts to detect congestion on the uTP peers and +throttle TCP to avoid it taking over all bandwidth. This balances the bandwidth resources +between the two protocols. When running on a network where the bandwidth is in such an +abundance that it's virtually infinite, this algorithm is no longer necessary, and might +even be harmful to throughput. It is advised to experiment with the +settings_pack::mixed_mode_algorithm, setting it to settings_pack::prefer_tcp. +This setting entirely disables the balancing and un-throttles all connections. On a typical +home connection, this would mean that none of the benefits of uTP would be preserved +(the modem's send buffer would be full at all times) and uTP connections would for the most +part be squashed by the TCP traffic.

    +
    +
    +

    send buffer low watermark

    +

    libtorrent uses a low watermark for send buffers to determine when a new piece should +be requested from the disk I/O subsystem, to be appended to the send buffer. The low +watermark is determined based on the send rate of the socket. It needs to be large +enough to not draining the socket's send buffer before the disk operation completes.

    +

    The watermark is bound to a max value, to avoid buffer sizes growing out of control. +The default max send buffer size might not be enough to sustain very high upload rates, +and you might have to increase it. It's specified in bytes in +settings_pack::send_buffer_watermark.

    +
    +
    +

    peers

    +

    First of all, in order to allow many connections, set the global connection limit +high, settings_pack::connections_limit. Also set the upload rate limit to +infinite, settings_pack::upload_rate_limit, 0 means infinite.

    +

    When dealing with a large number of peers, it might be a good idea to have slightly +stricter timeouts, to get rid of lingering connections as soon as possible.

    +

    There are a couple of relevant settings: settings_pack::request_timeout, +settings_pack::peer_timeout and settings_pack::inactivity_timeout.

    +

    For seeds that are critical for a delivery system, you most likely want to allow +multiple connections from the same IP. That way two people from behind the same NAT +can use the service simultaneously. This is controlled by +settings_pack::allow_multiple_connections_per_ip.

    +

    In order to always unchoke peers, turn off automatic unchoke by setting +settings_pack::choking_algorithm to settings_pack::fixed_slot_choker and set the number +of upload slots to a large number via settings_pack::unchoke_slots_limit, +or use -1 (which means infinite).

    +
    +
    +

    torrent limits

    +

    To seed thousands of torrents, you need to increase the settings_pack::active_limit +and settings_pack::active_seeds.

    +
    +
    +

    SHA-1 hashing

    +

    When downloading at very high rates, it is possible to have the CPU be the +bottleneck for passing every downloaded byte through SHA-1. In order to enable +calculating SHA-1 hashes in parallel, on multi-core systems, set +settings_pack::aio_threads to the number of threads libtorrent should +perform I/O and do SHA-1 hashing in. Only if that thread is close to saturating +one core does it make sense to increase the number of threads.

    +
    +
    +
    +

    scalability

    +

    In order to make more efficient use of the libtorrent interface when running a large +number of torrents simultaneously, one can use the session::get_torrent_status() call +together with session::post_torrent_updates(). Keep in mind that every call into +libtorrent that return some value have to block your thread while posting a message to +the main network thread and then wait for a response. Calls that don't return any data +will simply post the message and then immediately return, performing the work +asynchronously. The time this takes might become significant once you reach a +few hundred torrents, depending on how many calls you make to each torrent and how often. +session::get_torrent_status() lets you query the status of all torrents in a single call. +This will actually loop through all torrents and run a provided predicate function to +determine whether or not to include it in the returned vector.

    +

    To use session::post_torrent_updates() torrents need to have the flag_update_subscribe +flag set. When post_torrent_updates() is called, a state_update_alert alert +is posted, with all the torrents that have updated since the last time this +function was called. The client have to keep its own state of all torrents, and +update it based on this alert.

    +
    +
    +

    benchmarking

    +

    There is a bunch of built-in instrumentation of libtorrent that can be used to get an insight +into what it's doing and how well it performs. This instrumentation is enabled by defining +preprocessor symbols when building.

    +

    There are also a number of scripts that parses the log files and generates graphs (requires +gnuplot and python).

    +
    +
    +

    understanding the disk threads

    +

    This section is somewhat outdated, there are potentially more than one disk +thread

    +

    All disk operations are funneled through a separate thread, referred to as the +disk thread. The main interface to the disk thread is a queue where disk jobs +are posted, and the results of these jobs are then posted back on the main +thread's io_service.

    +

    A disk job is essentially one of:

    +
      +
    1. +
      write this block to disk, i.e. a write job. For the most part this is just a
      +
      matter of sticking the block in the disk cache, but if we've run out of +cache space or completed a whole piece, we'll also flush blocks to disk. +This is typically very fast, since the OS just sticks these buffers in its +write cache which will be flushed at a later time, presumably when the drive +head will pass the place on the platter where the blocks go.
      +
      +
    2. +
    3. +
      read this block from disk. The first thing that happens is we look in the
      +
      cache to see if the block is already in RAM. If it is, we'll return +immediately with this block. If it's a cache miss, we'll have to hit the +disk. Here we decide to defer this job. We find the physical offset on the +drive for this block and insert the job in an ordered queue, sorted by the +physical location. At a later time, once we don't have any more non-read +jobs left in the queue, we pick one read job out of the ordered queue and +service it. The order we pick jobs out of the queue is according to an +elevator cursor moving up and down along the ordered queue of read jobs. If +we have enough space in the cache we'll read read_cache_line_size number of +blocks and stick those in the cache. This defaults to 32 blocks. If the +system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for +instance), jobs will be issued immediately to the OS. This especially +increases read throughput, since the OS has a much greater flexibility to +reorder the read jobs.
      +
      +
    4. +
    +

    Other disk job consist of operations that needs to be synchronized with the +disk I/O, like renaming files, closing files, flushing the cache, updating the +settings etc. These are relatively rare though.

    +
    +
    +

    contributions

    +

    If you have added instrumentation for some part of libtorrent that is not +covered here, or if you have improved any of the parser scrips, please consider +contributing it back to the project.

    +

    If you have run tests and found that some algorithm or default value in +libtorrent are suboptimal, please contribute that knowledge back as well, to +allow us to improve the library.

    +

    If you have additional suggestions on how to tune libtorrent for any specific +use case, please let us know and we'll update this document.

    +
    +
    +
    +

    Upgrading to libtorrent 1.2

    +

    libtorrent version 1.2 comes with some significant updates in the API. +This document summarizes the changes affecting library users.

    +
    +

    C++98 no longer supported

    +

    With libtorrent 1.2, C++98 is no longer supported, you need a compiler capable +of at least C++11 to build libtorrent.

    +

    This also means libtorrent types now support move.

    +
    +
    +

    listen interfaces

    +

    There's a subtle change in how the listen_interfaces setting is interpreted +in 1.2 compared to 1.1.

    +

    In libtorrent-1.1, if you listen to 0.0.0.0:6881 (which was the default), +not only would an IPv4 listen socket be opened (bound to INADDR_ANY) but also an +IPv6 socket would be opened.

    +

    In libtorrent-1.2, if you listen to 0.0.0.0:6881 only the IPv4 INADDR_ANY is +opened as a listen socket. If you want to listen to both IPv4 and IPv6, you need +to listen to 0.0.0.0:6881,[::]:6881.

    +
    +
    +

    forward declaring libtorrent types deprecated

    +

    Clients are discouraged from forward declaring types from libtorrent. +Instead, include the <libtorrent/fwd.hpp> header.

    +

    A future release will introduce ABI versioning using an inline namespace, which will break any forward declarations by clients.

    +

    There is a new namespace alias, lt which is shorthand for libtorrent. +In the future, libtorrent will be the alias and lt the namespace name. +With no forward declarations inside libtorrent's namespace though, there should not be any reason for clients to re-open the namespace.

    +
    +
    +

    resume data handling

    +

    To significantly simplify handling of resume data, the previous way of handling it is deprecated. +resume data is no longer passed in as a flat buffer in the add_torrent_params. +The add_torrent_params structure itself is the resume data now.

    +

    In order to parse the bencoded fast resume file (which is still the same format, and backwards compatible) use the read_resume_data() function.

    +

    Similarly, when saving resume data, the save_resume_data_alert now has a params field of type add_torrent_params which contains the resume data. +This object can be serialized into the bencoded form using write_resume_data().

    +

    This give the client full control over which properties should be loaded from the resume data and which should be controlled by the client directly. +The flags flag_override_resume_data, flag_merge_resume_trackers, flag_use_resume_save_path and flag_merge_resume_http_seeds have all been deprecated, since they are no longer needed.

    +

    The old API is still supported as long as libtorrent is built with deprecated functions enabled (which is the default). +It will be performing slightly better without deprecated functions present.

    +
    +
    +

    rate_limit_utp changed defaults

    +

    The setting rate_limit_utp was deprecated in libtorrent 1.1. +When building without deprecated features (deprecated-functions=off) the default behavior also changed to have rate limits apply to utp sockets too. +In order to be more consistent between the two build configurations, the default value has changed to true. +The new mechanism provided to apply special rate limiting rules is peer classes. +In order to implement the old behavior of not rate limiting uTP peers, one can set up a peer class for all uTP peers, to make the normal peer classes not apply to them (which is where the rate limits are set).

    +
    +
    +

    announce entry multi-home support

    +

    The announce_entry type now captures status on individual endpoints, as opposed to treating every tracker behind the same name as a single tracker. +This means some properties has moved into the announce_endpoint structure, and an announce entry has 0 or more endpoints.

    +
    +
    +

    alerts no longer cloneable

    +

    As part of the transition to a more efficient handling of alerts, 1.1 allocated them in a contiguous, heterogeneous, vector. +This means they are no longer heap allocated nor held by a smart pointer. +The clone() member on alerts was deprecated in 1.1 and removed in 1.2. +To pass alerts across threads, instead pull out the relevant information from the alerts and pass that across.

    +
    +
    +

    progress alert category

    +

    The alert::progress_notification category has been deprecated. +Alerts posted in this category are now also posted in one of these new categories:

    +
      +
    • alert::block_progress_notification
    • +
    • alert::piece_progress_notification
    • +
    • alert::file_progress_notification
    • +
    • alert::upload_notification
    • +
    +
    +
    +

    boost replaced by std

    +

    boost::shared_ptr has been replaced by std::shared_ptr in the libtorrent API. +The same goes for <cstdint> types, instead of boost::int64_t, libtorrent now uses std::int64_t. +Instead of boost::array, std::array is used, and boost::function has been replaced by std::function.

    +
    +
    +

    strong typedefs

    +

    In order to strengthen type-safety, libtorrent now uses special types to represent certain indexes and ID types. +Any integer referring to a piece index, now has the type piece_index_t, and indices to files in a torrent, use file_index_t. +Similarly, time points and duration now use time_point and duration from the <chrono> standard library.

    +

    The specific types have typedefs at lt::time_point and lt::duration, and the clock used by libtorrent is lt::clock_type.`

    +
    +
    +

    strongly typed flags

    +

    Enum flags have been replaced by strongly typed flags. +This means their implicit conversion to and from int is deprecated. +For example, the following expressions are deprecated:

    +
    +if ((atp.flags & add_torrent_params::flag_paused) == 0)
    +
    +atp.flags = 0;
    +
    +

    Insted say:

    +
    +if (!(atp.flags & torrent_flags::paused))
    +
    +atp.flags = {};
    +
    +

    (Also note that in this specific example, the flags moved out of the add_torrent_params structure, but this is unrelated to them also having stronger types).

    +
    +
    +

    span<> and string_view

    +

    The interface has adopted string_view (from boost for now) and span<> (custom implementation for now). +This means some function calls that previously took char const* or std::string may now take an lt::string_view. +Similarly, functions that previously would take a pointer and length pair will now take a span<>.

    +
    +
    +

    periphery utility functions no longer exported

    +

    Historically, libtorrent has exported functions not essential to its core bittorrent functionality. +Such as filesystem functions like directory, file classes and remove, create_directory functions. +Path manipulation functions like combine_path, extension, split_path etc. +String manipulation functions like from_hex and to_hex. +Time functions like time_now. These functions are no longer available to clients, and some have been removed from the library. +Instead, it is recommended to use boost.filesystem or the experimental filesystem TS.

    +
    +
    +

    plugins

    +

    libtorrent session plugins no longer have all callbacks called unconditionally. +The plugin has to register which callbacks it's interested in receiving by returning a bitmask from feature_flags_t implemented_features(). +The return value is documented in the plugin class.

    +
    +
    +

    RSS functions removed

    +

    The deprecated RSS functions have been removed from the library interface.

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +

    Core

    +[report issue]
    +

    peer_class_info

    +

    Declared in "libtorrent/peer_class.hpp"

    +

    holds settings for a peer class. Used in set_peer_class() and +get_peer_class() calls.

    +
    +struct peer_class_info
    +{
    +   bool ignore_unchoke_slots;
    +   int connection_limit_factor;
    +   std::string label;
    +   int upload_limit;
    +   int download_limit;
    +   int upload_priority;
    +   int download_priority;
    +};
    +
    +[report issue]
    +
    ignore_unchoke_slots
    +
    ignore_unchoke_slots determines whether peers should always +unchoke a peer, regardless of the choking algorithm, or if it should +honor the unchoke slot limits. It's used for local peers by default. +If any of the peer classes a peer belongs to has this set to true, +that peer will be unchoked at all times.
    +
    +[report issue]
    +
    connection_limit_factor
    +
    adjusts the connection limit (global and per torrent) that applies to +this peer class. By default, local peers are allowed to exceed the +normal connection limit for instance. This is specified as a percent +factor. 100 makes the peer class apply normally to the limit. 200 +means as long as there are fewer connections than twice the limit, we +accept this peer. This factor applies both to the global connection +limit and the per-torrent limit. Note that if not used carefully one +peer class can potentially completely starve out all other over time.
    +
    +[report issue]
    +
    label
    +
    not used by libtorrent. It's intended as a potentially user-facing +identifier of this peer class.
    +
    + +[report issue]
    +
    upload_limit download_limit
    +
    transfer rates limits for the whole peer class. They are specified in +bytes per second and apply to the sum of all peers that are members of +this class.
    +
    + +[report issue]
    +
    upload_priority download_priority
    +
    relative priorities used by the bandwidth allocator in the rate +limiter. If no rate limits are in use, the priority is not used +either. Priorities start at 1 (0 is not a valid priority) and may not +exceed 255.
    +
    +[report issue]
    +
    +

    peer_connection_handle

    +

    Declared in "libtorrent/peer_connection_handle.hpp"

    +

    the peer_connection_handle class provides a handle to the internal peer +connection object, to be used by plugins. This is a low level interface that +may not be stable across libtorrent versions

    +
    +struct peer_connection_handle
    +{
    +   explicit peer_connection_handle (std::weak_ptr<peer_connection> impl);
    +   connection_type type () const;
    +   void add_extension (std::shared_ptr<peer_plugin>);
    +   peer_plugin const* find_plugin (string_view type) const;
    +   bool is_seed () const;
    +   bool upload_only () const;
    +   bool has_piece (piece_index_t i) const;
    +   peer_id const& pid () const;
    +   bool is_interesting () const;
    +   bool is_choked () const;
    +   bool has_peer_choked () const;
    +   bool is_peer_interested () const;
    +   void choke_this_peer ();
    +   void maybe_unchoke_this_peer ();
    +   void get_peer_info (peer_info& p) const;
    +   torrent_handle associated_torrent () const;
    +   tcp::endpoint local_endpoint () const;
    +   tcp::endpoint const& remote () const;
    +   bool is_connecting () const;
    +   void disconnect (error_code const& ec, operation_t op
    +      , disconnect_severity_t = peer_connection_interface::normal);
    +   bool is_outgoing () const;
    +   bool is_disconnecting () const;
    +   bool on_local_network () const;
    +   bool ignore_unchoke_slots () const;
    +   bool failed () const;
    +   bool should_log (peer_log_alert::direction_t direction) const;
    +   void peer_log (peer_log_alert::direction_t direction
    +      , char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5);
    +   bool can_disconnect (error_code const& ec) const;
    +   bool has_metadata () const;
    +   bool in_handshake () const;
    +   void send_buffer (char const* begin, int size);
    +   std::time_t last_seen_complete () const;
    +   time_point time_of_last_unchoke () const;
    +   bool operator== (peer_connection_handle const& o) const;
    +   bool operator!= (peer_connection_handle const& o) const;
    +   bool operator< (peer_connection_handle const& o) const;
    +   std::shared_ptr<peer_connection> native_handle () const;
    +};
    +
    +[report issue]
    +
    +

    bt_peer_connection_handle

    +

    Declared in "libtorrent/peer_connection_handle.hpp"

    +

    The bt_peer_connection_handle provides a handle to the internal bittorrent +peer connection object to plugins. It's low level and may not be a stable API +across libtorrent versions.

    +
    +struct bt_peer_connection_handle : peer_connection_handle
    +{
    +   explicit bt_peer_connection_handle (peer_connection_handle pc);
    +   bool support_extensions () const;
    +   bool packet_finished () const;
    +   bool supports_encryption () const;
    +   void switch_recv_crypto (std::shared_ptr<crypto_plugin> crypto);
    +   void switch_send_crypto (std::shared_ptr<crypto_plugin> crypto);
    +   std::shared_ptr<bt_peer_connection> native_handle () const;
    +};
    +
    +[report issue]
    +
    +

    torrent_status

    +

    Declared in "libtorrent/torrent_status.hpp"

    +

    holds a snapshot of the status of a torrent, as queried by +torrent_handle::status().

    +
    +struct torrent_status
    +{
    +   bool operator== (torrent_status const& st) const;
    +   time_duration next_announce = seconds (0);
    +
    +   enum state_t
    +   {
    +      checking_files,
    +      downloading_metadata,
    +      downloading,
    +      finished,
    +      seeding,
    +      allocating,
    +      checking_resume_data,
    +   };
    +
    +   torrent_handle handle;
    +   error_code errc;
    +   file_index_t error_file  = torrent_status::error_file_none;
    +   static constexpr file_index_t error_file_none {-1};
    +   static constexpr file_index_t error_file_ssl_ctx {-3};
    +   static constexpr file_index_t error_file_metadata {-4};
    +   static constexpr file_index_t error_file_exception {-5};
    +   static constexpr file_index_t error_file_partfile {-6};
    +   std::string save_path;
    +   std::string name;
    +   std::weak_ptr<const torrent_info> torrent_file;
    +   std::string current_tracker;
    +   std::int64_t total_download  = 0;
    +   std::int64_t total_upload  = 0;
    +   std::int64_t total_payload_download  = 0;
    +   std::int64_t total_payload_upload  = 0;
    +   std::int64_t total_failed_bytes  = 0;
    +   std::int64_t total_redundant_bytes  = 0;
    +   typed_bitfield<piece_index_t> pieces;
    +   typed_bitfield<piece_index_t> verified_pieces;
    +   std::int64_t total_done  = 0;
    +   std::int64_t total  = 0;
    +   std::int64_t total_wanted_done  = 0;
    +   std::int64_t total_wanted  = 0;
    +   std::int64_t all_time_upload  = 0;
    +   std::int64_t all_time_download  = 0;
    +   std::time_t added_time  = 0;
    +   std::time_t completed_time  = 0;
    +   std::time_t last_seen_complete  = 0;
    +   storage_mode_t storage_mode  = storage_mode_sparse;
    +   float progress  = 0.f;
    +   int progress_ppm  = 0;
    +   queue_position_t queue_position {};
    +   int download_rate  = 0;
    +   int upload_rate  = 0;
    +   int download_payload_rate  = 0;
    +   int upload_payload_rate  = 0;
    +   int num_seeds  = 0;
    +   int num_peers  = 0;
    +   int num_complete  = -1;
    +   int num_incomplete  = -1;
    +   int list_seeds  = 0;
    +   int list_peers  = 0;
    +   int connect_candidates  = 0;
    +   int num_pieces  = 0;
    +   int distributed_full_copies  = 0;
    +   int distributed_fraction  = 0;
    +   float distributed_copies  = 0.f;
    +   int block_size  = 0;
    +   int num_uploads  = 0;
    +   int num_connections  = 0;
    +   int uploads_limit  = 0;
    +   int connections_limit  = 0;
    +   int up_bandwidth_queue  = 0;
    +   int down_bandwidth_queue  = 0;
    +   int seed_rank  = 0;
    +   state_t state  = checking_resume_data;
    +   bool need_save_resume  = false;
    +   bool is_seeding  = false;
    +   bool is_finished  = false;
    +   bool has_metadata  = false;
    +   bool has_incoming  = false;
    +   bool moving_storage  = false;
    +   bool announcing_to_trackers  = false;
    +   bool announcing_to_lsd  = false;
    +   bool announcing_to_dht  = false;
    +   sha1_hash info_hash;
    +   time_point last_upload;
    +   time_point last_download;
    +   seconds active_duration;
    +   seconds finished_duration;
    +   seconds seeding_duration;
    +   torrent_flags_t flags {};
    +};
    +
    +[report issue]
    +

    operator==()

    +
    +bool operator== (torrent_status const& st) const;
    +
    +

    compares if the torrent status objects come from the same torrent. i.e. +only the torrent_handle field is compared.

    +[report issue]
    +
    +

    seconds()

    +
    +time_duration next_announce = seconds (0);
    +
    +

    the time until the torrent will announce itself to the tracker.

    +[report issue]
    +
    +

    enum state_t

    +

    Declared in "libtorrent/torrent_status.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    checking_files1The torrent has not started its download yet, and is +currently checking existing files.
    downloading_metadata2The torrent is trying to download metadata from peers. +This implies the ut_metadata extension is in use.
    downloading3The torrent is being downloaded. This is the state +most torrents will be in most of the time. The progress +meter will tell how much of the files that has been +downloaded.
    finished4In this state the torrent has finished downloading but +still doesn't have the entire torrent. i.e. some pieces +are filtered and won't get downloaded.
    seeding5In this state the torrent has finished downloading and +is a pure seeder.
    allocating6If the torrent was started in full allocation mode, this +indicates that the (disk) storage for the torrent is +allocated.
    checking_resume_data7The torrent is currently checking the fast resume data and +comparing it to the files on disk. This is typically +completed in a fraction of a second, but if you add a +large number of torrents at once, they will queue up.
    +[report issue]
    +
    handle
    +
    a handle to the torrent whose status the object represents.
    +
    +[report issue]
    +
    errc
    +
    may be set to an error code describing why the torrent was paused, in +case it was paused by an error. If the torrent is not paused or if it's +paused but not because of an error, this error_code is not set. +if the error is attributed specifically to a file, error_file is set to +the index of that file in the .torrent file.
    +
    +[report issue]
    +
    error_file
    +
    if the torrent is stopped because of an disk I/O error, this field +contains the index of the file in the torrent that encountered the +error. If the error did not originate in a file in the torrent, there +are a few special values this can be set to: error_file_none, +error_file_ssl_ctx, error_file_exception, error_file_partfile or +error_file_metadata;
    +
    +[report issue]
    +
    error_file_none
    +
    special values for error_file to describe which file or component +encountered the error (errc). +the error did not occur on a file
    +
    +[report issue]
    +
    error_file_ssl_ctx
    +
    the error occurred setting up the SSL context
    +
    +[report issue]
    +
    error_file_metadata
    +
    the error occurred while loading the .torrent file via the user +supplied load function
    +
    +[report issue]
    +
    error_file_exception
    +
    there was a serious error reported in this torrent. The error code +or a torrent log alert may provide more information.
    +
    +[report issue]
    +
    error_file_partfile
    +
    the error occurred with the partfile
    +
    +[report issue]
    +
    save_path
    +
    the path to the directory where this torrent's files are stored. +It's typically the path as was given to async_add_torrent() or +add_torrent() when this torrent was started. This field is only +included if the torrent status is queried with +torrent_handle::query_save_path.
    +
    +[report issue]
    +
    name
    +
    the name of the torrent. Typically this is derived from the +.torrent file. In case the torrent was started without metadata, +and hasn't completely received it yet, it returns the name given +to it when added to the session. See session::add_torrent. +This field is only included if the torrent status is queried +with torrent_handle::query_name.
    +
    +[report issue]
    +
    torrent_file
    +
    set to point to the torrent_info object for this torrent. It's +only included if the torrent status is queried with +torrent_handle::query_torrent_file.
    +
    +[report issue]
    +
    current_tracker
    +
    the URL of the last working tracker. If no tracker request has +been successful yet, it's set to an empty string.
    +
    + +[report issue]
    +
    total_download total_upload
    +
    the number of bytes downloaded and uploaded to all peers, accumulated, +this session only. The session is considered to restart when a +torrent is paused and restarted again. When a torrent is paused, these +counters are reset to 0. If you want complete, persistent, stats, see +all_time_upload and all_time_download.
    +
    + +[report issue]
    +
    total_payload_download total_payload_upload
    +
    counts the amount of bytes send and received this session, but only +the actual payload data (i.e the interesting data), these counters +ignore any protocol overhead. The session is considered to restart +when a torrent is paused and restarted again. When a torrent is +paused, these counters are reset to 0.
    +
    +[report issue]
    +
    total_failed_bytes
    +
    the number of bytes that has been downloaded and that has failed the +piece hash test. In other words, this is just how much crap that has +been downloaded since the torrent was last started. If a torrent is +paused and then restarted again, this counter will be reset.
    +
    +[report issue]
    +
    total_redundant_bytes
    +
    the number of bytes that has been downloaded even though that data +already was downloaded. The reason for this is that in some situations +the same data can be downloaded by mistake. When libtorrent sends +requests to a peer, and the peer doesn't send a response within a +certain timeout, libtorrent will re-request that block. Another +situation when libtorrent may re-request blocks is when the requests +it sends out are not replied in FIFO-order (it will re-request blocks +that are skipped by an out of order block). This is supposed to be as +low as possible. This only counts bytes since the torrent was last +started. If a torrent is paused and then restarted again, this counter +will be reset.
    +
    +[report issue]
    +
    pieces
    +
    a bitmask that represents which pieces we have (set to true) and the +pieces we don't have. It's a pointer and may be set to 0 if the +torrent isn't downloading or seeding.
    +
    +[report issue]
    +
    verified_pieces
    +
    a bitmask representing which pieces has had their hash checked. This +only applies to torrents in seed mode. If the torrent is not in seed +mode, this bitmask may be empty.
    +
    +[report issue]
    +
    total_done
    +
    the total number of bytes of the file(s) that we have. All this does +not necessarily has to be downloaded during this session (that's +total_payload_download).
    +
    +[report issue]
    +
    total
    +
    the total number of bytes to download for this torrent. This +may be less than the size of the torrent in case there are +pad files. This number only counts bytes that will actually +be requested from peers.
    +
    +[report issue]
    +
    total_wanted_done
    +
    the number of bytes we have downloaded, only counting the pieces that +we actually want to download. i.e. excluding any pieces that we have +but have priority 0 (i.e. not wanted).
    +
    +[report issue]
    +
    total_wanted
    +
    The total number of bytes we want to download. This may be smaller +than the total torrent size in case any pieces are prioritized to 0, +i.e. not wanted
    +
    + +[report issue]
    +
    all_time_upload all_time_download
    +
    are accumulated upload and download payload byte counters. They are +saved in and restored from resume data to keep totals across sessions.
    +
    +[report issue]
    +
    added_time
    +
    the posix-time when this torrent was added. i.e. what time(nullptr) +returned at the time.
    +
    +[report issue]
    +
    completed_time
    +
    the posix-time when this torrent was finished. If the torrent is not +yet finished, this is 0.
    +
    +[report issue]
    +
    last_seen_complete
    +
    the time when we, or one of our peers, last saw a complete copy of +this torrent.
    +
    +[report issue]
    +
    storage_mode
    +
    The allocation mode for the torrent. See storage_mode_t for the +options. For more information, see storage allocation.
    +
    +[report issue]
    +
    progress
    +
    a value in the range [0, 1], that represents the progress of the +torrent's current task. It may be checking files or downloading.
    +
    +[report issue]
    +
    progress_ppm
    +

    progress parts per million (progress * 1000000) when disabling +floating point operations, this is the only option to query progress

    +

    reflects the same value as progress, but instead in a range [0, +1000000] (ppm = parts per million). When floating point operations are +disabled, this is the only alternative to the floating point value in +progress.

    +
    +
    +[report issue]
    +
    queue_position
    +
    the position this torrent has in the download +queue. If the torrent is a seed or finished, this is -1.
    +
    + +[report issue]
    +
    download_rate upload_rate
    +
    the total rates for all peers for this torrent. These will usually +have better precision than summing the rates from all peers. The rates +are given as the number of bytes per second.
    +
    + +[report issue]
    +
    download_payload_rate upload_payload_rate
    +
    the total transfer rate of payload only, not counting protocol +chatter. This might be slightly smaller than the other rates, but if +projected over a long time (e.g. when calculating ETA:s) the +difference may be noticeable.
    +
    +[report issue]
    +
    num_seeds
    +
    the number of peers that are seeding that this client is +currently connected to.
    +
    +[report issue]
    +
    num_peers
    +
    the number of peers this torrent currently is connected to. Peer +connections that are in the half-open state (is attempting to connect) +or are queued for later connection attempt do not count. Although they +are visible in the peer list when you call get_peer_info().
    +
    + +[report issue]
    +
    num_complete num_incomplete
    +
    if the tracker sends scrape info in its announce reply, these fields +will be set to the total number of peers that have the whole file and +the total number of peers that are still downloading. set to -1 if the +tracker did not send any scrape data in its announce reply.
    +
    + +[report issue]
    +
    list_seeds list_peers
    +
    the number of seeds in our peer list and the total number of peers +(including seeds). We are not necessarily connected to all the peers +in our peer list. This is the number of peers we know of in total, +including banned peers and peers that we have failed to connect to.
    +
    +[report issue]
    +
    connect_candidates
    +
    the number of peers in this torrent's peer list that is a candidate to +be connected to. i.e. It has fewer connect attempts than the max fail +count, it is not a seed if we are a seed, it is not banned etc. If +this is 0, it means we don't know of any more peers that we can try.
    +
    +[report issue]
    +
    num_pieces
    +
    the number of pieces that has been downloaded. It is equivalent to: +std::accumulate(pieces->begin(), pieces->end()). So you don't have +to count yourself. This can be used to see if anything has updated +since last time if you want to keep a graph of the pieces up to date.
    +
    +[report issue]
    +
    distributed_full_copies
    +
    the number of distributed copies of the torrent. Note that one copy +may be spread out among many peers. It tells how many copies there are +currently of the rarest piece(s) among the peers this client is +connected to.
    +
    +[report issue]
    +
    distributed_fraction
    +

    tells the share of pieces that have more copies than the rarest +piece(s). Divide this number by 1000 to get the fraction.

    +

    For example, if distributed_full_copies is 2 and +distributed_fraction is 500, it means that the rarest pieces have +only 2 copies among the peers this torrent is connected to, and that +50% of all the pieces have more than two copies.

    +

    If we are a seed, the piece picker is deallocated as an optimization, +and piece availability is no longer tracked. In this case the +distributed copies members are set to -1.

    +
    +
    +[report issue]
    +
    distributed_copies
    +

    the number of distributed copies of the file. note that one copy may +be spread out among many peers. This is a floating point +representation of the distributed copies.

    +
    +
    the integer part tells how many copies
    +
    there are of the rarest piece(s)
    +
    the fractional part tells the fraction of pieces that
    +
    have more copies than the rarest piece(s).
    +
    +
    +
    +[report issue]
    +
    block_size
    +
    the size of a block, in bytes. A block is a sub piece, it is the +number of bytes that each piece request asks for and the number of +bytes that each bit in the partial_piece_info's bitset represents, +see get_download_queue(). This is typically 16 kB, but it may be +smaller, if the pieces are smaller.
    +
    +[report issue]
    +
    num_uploads
    +
    the number of unchoked peers in this torrent.
    +
    +[report issue]
    +
    num_connections
    +
    the number of peer connections this torrent has, including half-open +connections that hasn't completed the bittorrent handshake yet. This +is always >= num_peers.
    +
    +[report issue]
    +
    uploads_limit
    +
    the set limit of upload slots (unchoked peers) for this torrent.
    +
    +[report issue]
    +
    connections_limit
    +
    the set limit of number of connections for this torrent.
    +
    + +[report issue]
    +
    up_bandwidth_queue down_bandwidth_queue
    +
    the number of peers in this torrent that are waiting for more +bandwidth quota from the torrent rate limiter. This can determine if +the rate you get from this torrent is bound by the torrents limit or +not. If there is no limit set on this torrent, the peers might still +be waiting for bandwidth quota from the global limiter, but then they +are counted in the session_status object.
    +
    +[report issue]
    +
    seed_rank
    +
    A rank of how important it is to seed the torrent, it is used to +determine which torrents to seed and which to queue. It is based on +the peer to seed ratio from the tracker scrape. For more information, +see queuing. Higher value means more important to seed
    +
    +[report issue]
    +
    state
    +
    the main state the torrent is in. See torrent_status::state_t.
    +
    +[report issue]
    +
    need_save_resume
    +
    true if this torrent has unsaved changes +to its download state and statistics since the last resume data +was saved.
    +
    +[report issue]
    +
    is_seeding
    +
    true if all pieces have been downloaded.
    +
    +[report issue]
    +
    is_finished
    +
    true if all pieces that have a priority > 0 are downloaded. There is +only a distinction between finished and seeding if some pieces or +files have been set to priority 0, i.e. are not downloaded.
    +
    +[report issue]
    +
    has_metadata
    +
    true if this torrent has metadata (either it was started from a +.torrent file or the metadata has been downloaded). The only scenario +where this can be false is when the torrent was started torrent-less +(i.e. with just an info-hash and tracker ip, a magnet link for +instance).
    +
    +[report issue]
    +
    has_incoming
    +
    true if there has ever been an incoming connection attempt to this +torrent.
    +
    +[report issue]
    +
    moving_storage
    +
    this is true if this torrent's storage is currently being moved from +one location to another. This may potentially be a long operation +if a large file ends up being copied from one drive to another.
    +
    + + +[report issue]
    +
    announcing_to_trackers announcing_to_lsd announcing_to_dht
    +
    these are set to true if this torrent is allowed to announce to the +respective peer source. Whether they are true or false is determined by +the queue logic/auto manager. Torrents that are not auto managed will +always be allowed to announce to all peer sources.
    +
    +[report issue]
    +
    info_hash
    +
    the info-hash for this torrent
    +
    + +[report issue]
    +
    last_upload last_download
    +
    the timestamps of the last time this torrent uploaded or downloaded +payload to any peer.
    +
    + + +[report issue]
    +
    active_duration finished_duration seeding_duration
    +
    these are cumulative counters of for how long the torrent has been in +different states. active means not paused and added to session. Whether +it has found any peers or not is not relevant. +finished means all selected files/pieces were downloaded and available +to other peers (this is always a subset of active time). +seeding means all files/pieces were downloaded and available to +peers. Being available to peers does not imply there are other peers +asking for the payload.
    +
    +[report issue]
    +
    flags
    +
    reflects several of the torrent's flags. For more +information, see torrent_handle::flags().
    +
    +[report issue]
    +
    +
    +

    announce_endpoint

    +

    Declared in "libtorrent/announce_entry.hpp"

    +

    announces are sent to each tracker using every listen socket +this class holds information about one listen socket for one tracker

    +
    +struct announce_endpoint
    +{
    +   void reset ();
    +   void failed (int backoff_ratio, seconds32 retry_interval = seconds32(0));
    +   bool can_announce (time_point now, bool is_seed, std::uint8_t fail_limit) const;
    +   bool is_working () const;
    +
    +   std::string message;
    +   error_code last_error;
    +   tcp::endpoint local_endpoint;
    +   int scrape_incomplete  = -1;
    +   int scrape_complete  = -1;
    +   int scrape_downloaded  = -1;
    +   std::uint8_t fails : 7;
    +   bool updating : 1;
    +   bool start_sent : 1;
    +   bool complete_sent : 1;
    +   bool enabled : 1;
    +};
    +
    +[report issue]
    +

    reset()

    +
    +void reset ();
    +
    +

    reset announce counters and clears the started sent flag. +The announce_endpoint will look like we've never talked to +the tracker.

    +[report issue]
    +
    +

    failed()

    +
    +void failed (int backoff_ratio, seconds32 retry_interval = seconds32(0));
    +
    +

    updates the failure counter and time-outs for re-trying. +This is called when the tracker announce fails.

    +[report issue]
    +
    +

    can_announce()

    +
    +bool can_announce (time_point now, bool is_seed, std::uint8_t fail_limit) const;
    +
    +

    returns true if we can announce to this tracker now. +The current time is passed in as now. The is_seed +argument is necessary because once we become a seed, we +need to announce right away, even if the re-announce timer +hasn't expired yet.

    +[report issue]
    +
    +

    is_working()

    +
    +bool is_working () const;
    +
    +

    returns true if the last time we tried to announce to this +tracker succeeded, or if we haven't tried yet.

    +[report issue]
    +
    message
    +
    if this tracker has returned an error or warning message +that message is stored here
    +
    +[report issue]
    +
    last_error
    +
    if this tracker failed the last time it was contacted +this error code specifies what error occurred
    +
    +[report issue]
    +
    local_endpoint
    +
    the local endpoint of the listen interface associated with this endpoint
    +
    + + +[report issue]
    +
    scrape_incomplete scrape_complete scrape_downloaded
    +
    if this tracker has returned scrape data, these fields are filled in +with valid numbers. Otherwise they are set to -1. incomplete counts +the number of current downloaders. complete counts the number of +current peers completed the download, or "seeds". downloaded is the +cumulative number of completed downloads.
    +
    +[report issue]
    +
    fails
    +
    the number of times in a row we have failed to announce to this +tracker.
    +
    +[report issue]
    +
    updating
    +
    true while we're waiting for a response from the tracker.
    +
    +[report issue]
    +
    start_sent
    +
    set to true when we get a valid response from an announce +with event=started. If it is set, we won't send start in the subsequent +announces.
    +
    +[report issue]
    +
    complete_sent
    +
    set to true when we send a event=completed.
    +
    +[report issue]
    +
    enabled
    +
    set to false to not announce from this endpoint
    +
    +[report issue]
    +
    +
    +

    announce_entry

    +

    Declared in "libtorrent/announce_entry.hpp"

    +

    this class holds information about one bittorrent tracker, as it +relates to a specific torrent.

    +
    +struct announce_entry
    +{
    +   explicit announce_entry (string_view u);
    +   announce_entry (announce_entry const&);
    +   announce_entry ();
    +   announce_entry& operator= (announce_entry const&);
    +   ~announce_entry ();
    +   void reset ();
    +   void trim ();
    +
    +   enum tracker_source
    +   {
    +      source_torrent,
    +      source_client,
    +      source_magnet_link,
    +      source_tex,
    +   };
    +
    +   std::string url;
    +   std::string trackerid;
    +   std::vector<announce_endpoint> endpoints;
    +   std::uint8_t tier  = 0;
    +   std::uint8_t fail_limit  = 0;
    +   std::uint8_t source:4;
    +   bool verified:1;
    +};
    +
    + + +[report issue]
    +

    ~announce_entry() operator=() announce_entry()

    +
    +explicit announce_entry (string_view u);
    +announce_entry (announce_entry const&);
    +announce_entry ();
    +announce_entry& operator= (announce_entry const&);
    +~announce_entry ();
    +
    +

    constructs a tracker announce entry with u as the URL.

    +[report issue]
    +
    +

    reset()

    +
    +void reset ();
    +
    +

    reset announce counters and clears the started sent flag. +The announce_entry will look like we've never talked to +the tracker.

    +[report issue]
    +
    +

    trim()

    +
    +void trim ();
    +
    +

    trims whitespace characters from the beginning of the URL.

    +[report issue]
    +
    +

    enum tracker_source

    +

    Declared in "libtorrent/announce_entry.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    source_torrent1the tracker was part of the .torrent file
    source_client2the tracker was added programmatically via the add_tracker() function
    source_magnet_link4the tracker was part of a magnet link
    source_tex8the tracker was received from the swarm via tracker exchange
    +[report issue]
    +
    url
    +
    tracker URL as it appeared in the torrent file
    +
    +[report issue]
    +
    trackerid
    +
    the current &trackerid= argument passed to the tracker. +this is optional and is normally empty (in which case no +trackerid is sent).
    +
    +[report issue]
    +
    endpoints
    +
    each local listen socket (endpoint) will announce to the tracker. This +list contains state per endpoint.
    +
    +[report issue]
    +
    tier
    +
    the tier this tracker belongs to
    +
    +[report issue]
    +
    fail_limit
    +
    the max number of failures to announce to this tracker in +a row, before this tracker is not used anymore. 0 means unlimited
    +
    +[report issue]
    +
    source
    +
    a bitmask specifying which sources we got this tracker from.
    +
    +[report issue]
    +
    verified
    +
    set to true the first time we receive a valid response +from this tracker.
    +
    +[report issue]
    +
    +
    +

    open_file_state

    +

    Declared in "libtorrent/disk_interface.hpp"

    +

    this contains information about a file that's currently open by the +libtorrent disk I/O subsystem. It's associated with a single torrent.

    +
    +struct open_file_state
    +{
    +   file_index_t file_index;
    +   file_open_mode_t open_mode;
    +   time_point last_use;
    +};
    +
    +[report issue]
    +
    file_index
    +
    the index of the file this entry refers to into the file_storage +file list of this torrent. This starts indexing at 0.
    +
    +[report issue]
    +
    open_mode
    +

    open_mode is a bitmask of the file flags this file is currently +opened with. These are the flags used in the file::open() function. +For possible flags, see file_open_mode_t.

    +

    Note that the read/write mode is not a bitmask. The two least significant bits are used +to represent the read/write mode. Those bits can be masked out using the rw_mask constant.

    +
    +
    +[report issue]
    +
    last_use
    +
    a (high precision) timestamp of when the file was last used.
    +
    +[report issue]
    +
    +

    peer_class_type_filter

    +

    Declared in "libtorrent/peer_class_type_filter.hpp"

    +

    peer_class_type_filter is a simple container for rules for adding and subtracting +peer-classes from peers. It is applied after the peer class filter is applied (which +is based on the peer's IP address).

    +
    +struct peer_class_type_filter
    +{
    +   void remove (socket_type_t const st, peer_class_t const peer_class);
    +   void add (socket_type_t const st, peer_class_t const peer_class);
    +   void disallow (socket_type_t const st, peer_class_t const peer_class);
    +   void allow (socket_type_t const st, peer_class_t const peer_class);
    +   std::uint32_t apply (socket_type_t const st, std::uint32_t peer_class_mask);
    +
    +   enum socket_type_t
    +   {
    +      tcp_socket,
    +      utp_socket,
    +      ssl_tcp_socket,
    +      ssl_utp_socket,
    +      i2p_socket,
    +      num_socket_types,
    +   };
    +};
    +
    + +[report issue]
    +

    add() remove()

    +
    +void remove (socket_type_t const st, peer_class_t const peer_class);
    +void add (socket_type_t const st, peer_class_t const peer_class);
    +
    +

    add() and remove() adds and removes a peer class to be added +to new peers based on socket type.

    + +[report issue]
    +
    +

    disallow() allow()

    +
    +void disallow (socket_type_t const st, peer_class_t const peer_class);
    +void allow (socket_type_t const st, peer_class_t const peer_class);
    +
    +

    disallow() and allow() adds and removes a peer class to be +removed from new peers based on socket type.

    +

    The peer_class argument cannot be greater than 31. The bitmasks representing +peer classes in the peer_class_type_filter are 32 bits.

    +[report issue]
    +
    +

    apply()

    +
    +std::uint32_t apply (socket_type_t const st, std::uint32_t peer_class_mask);
    +
    +

    takes a bitmask of peer classes and returns a new bitmask of +peer classes after the rules have been applied, based on the socket type argument +(st).

    +[report issue]
    +
    +

    enum socket_type_t

    +

    Declared in "libtorrent/peer_class_type_filter.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp_socket0these match the socket types from socket_type.hpp +shifted one down
    utp_socket1 
    ssl_tcp_socket2 
    ssl_utp_socket3 
    i2p_socket4 
    num_socket_types5 
    +[report issue]
    +
    +
    +

    block_info

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    holds the state of a block in a piece. Who we requested +it from and how far along we are at downloading it.

    +
    +struct block_info
    +{
    +   void set_peer (tcp::endpoint const& ep);
    +   tcp::endpoint peer () const;
    +
    +   enum block_state_t
    +   {
    +      none,
    +      requested,
    +      writing,
    +      finished,
    +   };
    +
    +   unsigned bytes_progress:15;
    +   unsigned block_size:15;
    +   unsigned state:2;
    +   unsigned num_peers:14;
    +};
    +
    + +[report issue]
    +

    peer() set_peer()

    +
    +void set_peer (tcp::endpoint const& ep);
    +tcp::endpoint peer () const;
    +
    +

    The peer is the ip address of the peer this block was downloaded from.

    +[report issue]
    +
    +

    enum block_state_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0This block has not been downloaded or requested form any peer.
    requested1The block has been requested, but not completely downloaded yet.
    writing2The block has been downloaded and is currently queued for being +written to disk.
    finished3The block has been written to disk.
    +[report issue]
    +
    bytes_progress
    +
    the number of bytes that have been received for this block
    +
    +[report issue]
    +
    block_size
    +
    the total number of bytes in this block.
    +
    +[report issue]
    +
    state
    +
    the state this block is in (see block_state_t)
    +
    +[report issue]
    +
    num_peers
    +
    the number of peers that is currently requesting this block. Typically +this is 0 or 1, but at the end of the torrent blocks may be requested +by more peers in parallel to speed things up.
    +
    +[report issue]
    +
    +
    +

    partial_piece_info

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    This class holds information about pieces that have outstanding requests +or outstanding writes

    +
    +struct partial_piece_info
    +{
    +   piece_index_t piece_index;
    +   int blocks_in_piece;
    +   int finished;
    +   int writing;
    +   int requested;
    +   block_info* blocks;
    +};
    +
    +[report issue]
    +
    piece_index
    +
    the index of the piece in question. blocks_in_piece is the number +of blocks in this particular piece. This number will be the same for +most pieces, but +the last piece may have fewer blocks than the standard pieces.
    +
    +[report issue]
    +
    blocks_in_piece
    +
    the number of blocks in this piece
    +
    +[report issue]
    +
    finished
    +
    the number of blocks that are in the finished state
    +
    +[report issue]
    +
    writing
    +
    the number of blocks that are in the writing state
    +
    +[report issue]
    +
    requested
    +
    the number of blocks that are in the requested state
    +
    +[report issue]
    +
    blocks
    +

    this is an array of blocks_in_piece number of +items. One for each block in the piece.

    +
    +

    Warning

    +

    This is a pointer that points to an array +that's owned by the session object. The next time +get_download_queue() is called, it will be invalidated.

    +
    +
    +
    +[report issue]
    +
    +

    torrent_handle

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +

    You will usually have to store your torrent handles somewhere, since it's +the object through which you retrieve information about the torrent and +aborts the torrent.

    +
    +

    Warning

    +

    Any member function that returns a value or fills in a value has to be +made synchronously. This means it has to wait for the main thread to +complete the query before it can return. This might potentially be +expensive if done from within a GUI thread that needs to stay +responsive. Try to avoid querying for information you don't need, and +try to do it in as few calls as possible. You can get most of the +interesting information about a torrent from the +torrent_handle::status() call.

    +
    +

    The default constructor will initialize the handle to an invalid state. +Which means you cannot perform any operation on it, unless you first +assign it a valid handle. If you try to perform any operation on an +uninitialized handle, it will throw invalid_handle.

    +
    +

    Warning

    +

    All operations on a torrent_handle may throw system_error +exception, in case the handle is no longer referring to a torrent. +There is one exception is_valid() will never throw. Since the torrents +are processed by a background thread, there is no guarantee that a +handle will remain valid between two calls.

    +
    +
    +struct torrent_handle
    +{
    +   torrent_handle () noexcept = default;
    +   void add_piece (piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const;
    +   void read_piece (piece_index_t piece) const;
    +   bool have_piece (piece_index_t piece) const;
    +   void get_peer_info (std::vector<peer_info>& v) const;
    +   torrent_status status (status_flags_t flags = status_flags_t::all()) const;
    +   void get_download_queue (std::vector<partial_piece_info>& queue) const;
    +   void set_piece_deadline (piece_index_t index, int deadline, deadline_flags_t flags = {}) const;
    +   void reset_piece_deadline (piece_index_t index) const;
    +   void clear_piece_deadlines () const;
    +   void file_progress (std::vector<std::int64_t>& progress, int flags = 0) const;
    +   std::vector<open_file_state> file_status () const;
    +   void clear_error () const;
    +   void replace_trackers (std::vector<announce_entry> const&) const;
    +   std::vector<announce_entry> trackers () const;
    +   void add_tracker (announce_entry const&) const;
    +   std::set<std::string> url_seeds () const;
    +   void add_url_seed (std::string const& url) const;
    +   void remove_url_seed (std::string const& url) const;
    +   void add_http_seed (std::string const& url) const;
    +   void remove_http_seed (std::string const& url) const;
    +   std::set<std::string> http_seeds () const;
    +   void add_extension (
    +      std::function<std::shared_ptr<torrent_plugin>(torrent_handle const&, void*)> const& ext
    +      , void* userdata = nullptr);
    +   bool set_metadata (span<char const> metadata) const;
    +   bool is_valid () const;
    +   void resume () const;
    +   void pause (pause_flags_t flags = {}) const;
    +   void unset_flags (torrent_flags_t flags) const;
    +   void set_flags (torrent_flags_t flags) const;
    +   torrent_flags_t flags () const;
    +   void set_flags (torrent_flags_t flags, torrent_flags_t mask) const;
    +   void flush_cache () const;
    +   void force_recheck () const;
    +   void save_resume_data (resume_data_flags_t flags = {}) const;
    +   bool need_save_resume_data () const;
    +   queue_position_t queue_position () const;
    +   void queue_position_down () const;
    +   void queue_position_up () const;
    +   void queue_position_top () const;
    +   void queue_position_bottom () const;
    +   void queue_position_set (queue_position_t p) const;
    +   void set_ssl_certificate (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params
    +      , std::string const& passphrase = "");
    +   void set_ssl_certificate_buffer (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params);
    +   storage_interface* get_storage_impl () const;
    +   std::shared_ptr<const torrent_info> torrent_file () const;
    +   void piece_availability (std::vector<int>& avail) const;
    +   download_priority_t piece_priority (piece_index_t index) const;
    +   void prioritize_pieces (std::vector<std::pair<piece_index_t, download_priority_t>> const& pieces) const;
    +   std::vector<download_priority_t> get_piece_priorities () const;
    +   void piece_priority (piece_index_t index, download_priority_t priority) const;
    +   void prioritize_pieces (std::vector<download_priority_t> const& pieces) const;
    +   std::vector<download_priority_t> get_file_priorities () const;
    +   void file_priority (file_index_t index, download_priority_t priority) const;
    +   download_priority_t file_priority (file_index_t index) const;
    +   void prioritize_files (std::vector<download_priority_t> const& files) const;
    +   void force_dht_announce () const;
    +   void force_reannounce (int seconds = 0, int tracker_index = -1, reannounce_flags_t = {}) const;
    +   void scrape_tracker (int idx = -1) const;
    +   void set_download_limit (int limit) const;
    +   int download_limit () const;
    +   void set_upload_limit (int limit) const;
    +   int upload_limit () const;
    +   void connect_peer (tcp::endpoint const& adr, peer_source_flags_t source = {}
    +      , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const;
    +   void clear_peers ();
    +   void set_max_uploads (int max_uploads) const;
    +   int max_uploads () const;
    +   void set_max_connections (int max_connections) const;
    +   int max_connections () const;
    +   void move_storage (std::string const& save_path
    +      , move_flags_t flags = move_flags_t::always_replace_files
    +      ) const;
    +   void rename_file (file_index_t index, std::string const& new_name) const;
    +   sha1_hash info_hash () const;
    +   bool operator== (const torrent_handle& h) const;
    +   bool operator!= (const torrent_handle& h) const;
    +   bool operator< (const torrent_handle& h) const;
    +   std::uint32_t id () const;
    +   std::shared_ptr<torrent> native_handle () const;
    +
    +   enum file_progress_flags_t
    +   {
    +      piece_granularity,
    +   };
    +
    +   static constexpr add_piece_flags_t overwrite_existing  = 0_bit;
    +   static constexpr status_flags_t query_distributed_copies  = 0_bit;
    +   static constexpr status_flags_t query_accurate_download_counters  = 1_bit;
    +   static constexpr status_flags_t query_last_seen_complete  = 2_bit;
    +   static constexpr status_flags_t query_pieces  = 3_bit;
    +   static constexpr status_flags_t query_verified_pieces  = 4_bit;
    +   static constexpr status_flags_t query_torrent_file  = 5_bit;
    +   static constexpr status_flags_t query_name  = 6_bit;
    +   static constexpr status_flags_t query_save_path  = 7_bit;
    +   static constexpr deadline_flags_t alert_when_available  = 0_bit;
    +   static constexpr pause_flags_t graceful_pause  = 0_bit;
    +   static constexpr pause_flags_t clear_disk_cache  = 1_bit;
    +   static constexpr resume_data_flags_t flush_disk_cache  = 0_bit;
    +   static constexpr resume_data_flags_t save_info_dict  = 1_bit;
    +   static constexpr resume_data_flags_t only_if_modified  = 2_bit;
    +   static constexpr reannounce_flags_t ignore_min_interval  = 0_bit;
    +};
    +
    +[report issue]
    +

    torrent_handle()

    +
    +torrent_handle () noexcept = default;
    +
    +

    constructs a torrent handle that does not refer to a torrent. +i.e. is_valid() will return false.

    +[report issue]
    +
    +

    add_piece()

    +
    +void add_piece (piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const;
    +
    +

    This function will write data to the storage as piece piece, +as if it had been downloaded from a peer. data is expected to +point to a buffer of as many bytes as the size of the specified piece. +The data in the buffer is copied and passed on to the disk IO thread +to be written at a later point.

    +

    By default, data that's already been downloaded is not overwritten by +this buffer. If you trust this data to be correct (and pass the piece +hash check) you may pass the overwrite_existing flag. This will +instruct libtorrent to overwrite any data that may already have been +downloaded with this data.

    +

    Since the data is written asynchronously, you may know that is passed +or failed the hash check by waiting for piece_finished_alert or +hash_failed_alert.

    +

    Adding pieces while the torrent is being checked (i.e. in +torrent_status::checking_files state) is not supported.

    +[report issue]
    +
    +

    read_piece()

    +
    +void read_piece (piece_index_t piece) const;
    +
    +

    This function starts an asynchronous read operation of the specified +piece from this torrent. You must have completed the download of the +specified piece before calling this function.

    +

    When the read operation is completed, it is passed back through an +alert, read_piece_alert. Since this alert is a response to an explicit +call, it will always be posted, regardless of the alert mask.

    +

    Note that if you read multiple pieces, the read operations are not +guaranteed to finish in the same order as you initiated them.

    +[report issue]
    +
    +

    have_piece()

    +
    +bool have_piece (piece_index_t piece) const;
    +
    +

    Returns true if this piece has been completely downloaded and written +to disk, and false otherwise.

    +[report issue]
    +
    +

    get_peer_info()

    +
    +void get_peer_info (std::vector<peer_info>& v) const;
    +
    +

    takes a reference to a vector that will be cleared and filled with one +entry for each peer connected to this torrent, given the handle is +valid. If the torrent_handle is invalid, it will throw +system_error exception. Each entry in the vector contains +information about that particular peer. See peer_info.

    +[report issue]
    +
    +

    status()

    +
    +torrent_status status (status_flags_t flags = status_flags_t::all()) const;
    +
    +

    status() will return a structure with information about the status +of this torrent. If the torrent_handle is invalid, it will throw +system_error exception. See torrent_status. The flags +argument filters what information is returned in the torrent_status. +Some information in there is relatively expensive to calculate, and if +you're not interested in it (and see performance issues), you can +filter them out.

    +

    By default everything is included. The flags you can use to decide +what to include are defined in this class.

    +[report issue]
    +
    +

    get_download_queue()

    +
    +void get_download_queue (std::vector<partial_piece_info>& queue) const;
    +
    +

    get_download_queue() takes a non-const reference to a vector which +it will fill with information about pieces that are partially +downloaded or not downloaded at all but partially requested. See +partial_piece_info for the fields in the returned vector.

    + + +[report issue]
    +
    +

    reset_piece_deadline() clear_piece_deadlines() set_piece_deadline()

    +
    +void set_piece_deadline (piece_index_t index, int deadline, deadline_flags_t flags = {}) const;
    +void reset_piece_deadline (piece_index_t index) const;
    +void clear_piece_deadlines () const;
    +
    +

    This function sets or resets the deadline associated with a specific +piece index (index). libtorrent will attempt to download this +entire piece before the deadline expires. This is not necessarily +possible, but pieces with a more recent deadline will always be +prioritized over pieces with a deadline further ahead in time. The +deadline (and flags) of a piece can be changed by calling this +function again.

    +

    If the piece is already downloaded when this call is made, nothing +happens, unless the alert_when_available flag is set, in which case it +will have the same effect as calling read_piece() for index.

    +

    deadline is the number of milliseconds until this piece should be +completed.

    +

    reset_piece_deadline removes the deadline from the piece. If it +hasn't already been downloaded, it will no longer be considered a +priority.

    +

    clear_piece_deadlines() removes deadlines on all pieces in +the torrent. As if reset_piece_deadline() was called on all pieces.

    +[report issue]
    +
    +

    file_progress()

    +
    +void file_progress (std::vector<std::int64_t>& progress, int flags = 0) const;
    +
    +

    This function fills in the supplied vector with the number of +bytes downloaded of each file in this torrent. The progress values are +ordered the same as the files in the torrent_info. This operation is +not very cheap. Its complexity is O(n + mj). Where n is the number +of files, m is the number of downloading pieces and j is the +number of blocks in a piece.

    +

    The flags parameter can be used to specify the granularity of the +file progress. If left at the default value of 0, the progress will be +as accurate as possible, but also more expensive to calculate. If +torrent_handle::piece_granularity is specified, the progress will +be specified in piece granularity. i.e. only pieces that have been +fully downloaded and passed the hash check count. When specifying +piece granularity, the operation is a lot cheaper, since libtorrent +already keeps track of this internally and no calculation is required.

    +[report issue]
    +
    +

    file_status()

    +
    +std::vector<open_file_state> file_status () const;
    +
    +

    This function returns a vector with status about files +that are open for this torrent. Any file that is not open +will not be reported in the vector, i.e. it's possible that +the vector is empty when returning, if none of the files in the +torrent are currently open.

    +

    See open_file_state

    +[report issue]
    +
    +

    clear_error()

    +
    +void clear_error () const;
    +
    +

    If the torrent is in an error state (i.e. torrent_status::error is +non-empty), this will clear the error and start the torrent again.

    + + +[report issue]
    +
    +

    add_tracker() replace_trackers() trackers()

    +
    +void replace_trackers (std::vector<announce_entry> const&) const;
    +std::vector<announce_entry> trackers () const;
    +void add_tracker (announce_entry const&) const;
    +
    +

    trackers() will return the list of trackers for this torrent. The +announce entry contains both a string url which specify the +announce url for the tracker as well as an int tier, which is +specifies the order in which this tracker is tried. If you want +libtorrent to use another list of trackers for this torrent, you can +use replace_trackers() which takes a list of the same form as the +one returned from trackers() and will replace it. If you want an +immediate effect, you have to call force_reannounce(). See +announce_entry.

    +

    add_tracker() will look if the specified tracker is already in the +set. If it is, it doesn't do anything. If it's not in the current set +of trackers, it will insert it in the tier specified in the +announce_entry.

    +

    The updated set of trackers will be saved in the resume data, and when +a torrent is started with resume data, the trackers from the resume +data will replace the original ones.

    + + +[report issue]
    +
    +

    url_seeds() remove_url_seed() add_url_seed()

    +
    +std::set<std::string> url_seeds () const;
    +void add_url_seed (std::string const& url) const;
    +void remove_url_seed (std::string const& url) const;
    +
    +

    add_url_seed() adds another url to the torrent's list of url +seeds. If the given url already exists in that list, the call has no +effect. The torrent will connect to the server and try to download +pieces from it, unless it's paused, queued, checking or seeding. +remove_url_seed() removes the given url if it exists already. +url_seeds() return a set of the url seeds currently in this +torrent. Note that URLs that fails may be removed automatically from +the list.

    +

    See http seeding for more information.

    + + +[report issue]
    +
    +

    add_http_seed() remove_http_seed() http_seeds()

    +
    +void add_http_seed (std::string const& url) const;
    +void remove_http_seed (std::string const& url) const;
    +std::set<std::string> http_seeds () const;
    +
    +

    These functions are identical as the *_url_seed() variants, but +they operate on BEP 17 web seeds instead of BEP 19.

    +

    See http seeding for more information.

    +[report issue]
    +
    +

    add_extension()

    +
    +void add_extension (
    +      std::function<std::shared_ptr<torrent_plugin>(torrent_handle const&, void*)> const& ext
    +      , void* userdata = nullptr);
    +
    +

    add the specified extension to this torrent. The ext argument is +a function that will be called from within libtorrent's context +passing in the internal torrent object and the specified userdata +pointer. The function is expected to return a shared pointer to +a torrent_plugin instance.

    +[report issue]
    +
    +

    set_metadata()

    +
    +bool set_metadata (span<char const> metadata) const;
    +
    +

    set_metadata expects the info section of metadata. i.e. The +buffer passed in will be hashed and verified against the info-hash. If +it fails, a metadata_failed_alert will be generated. If it passes, +a metadata_received_alert is generated. The function returns true +if the metadata is successfully set on the torrent, and false +otherwise. If the torrent already has metadata, this function will not +affect the torrent, and false will be returned.

    +[report issue]
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    Returns true if this handle refers to a valid torrent and false if it +hasn't been initialized or if the torrent it refers to has been +aborted. Note that a handle may become invalid after it has been added +to the session. Usually this is because the storage for the torrent is +somehow invalid or if the filenames are not allowed (and hence cannot +be opened/created) on your filesystem. If such an error occurs, a +file_error_alert is generated and all handles that refers to that +torrent will become invalid.

    + +[report issue]
    +
    +

    pause() resume()

    +
    +void resume () const;
    +void pause (pause_flags_t flags = {}) const;
    +
    +

    pause(), and resume() will disconnect all peers and reconnect +all peers respectively. When a torrent is paused, it will however +remember all share ratios to all peers and remember all potential (not +connected) peers. Torrents may be paused automatically if there is a +file error (e.g. disk full) or something similar. See +file_error_alert.

    +

    To know if a torrent is paused or not, call +torrent_handle::status() and inspect torrent_status::paused.

    +
    +

    Note

    +

    Torrents that are auto-managed may be automatically resumed again. It +does not make sense to pause an auto-managed torrent without making it +not auto-managed first. Torrents are auto-managed by default when added +to the session. For more information, see queuing.

    +
    + + +[report issue]
    +
    +

    flags() set_flags() unset_flags()

    +
    +void unset_flags (torrent_flags_t flags) const;
    +void set_flags (torrent_flags_t flags) const;
    +torrent_flags_t flags () const;
    +void set_flags (torrent_flags_t flags, torrent_flags_t mask) const;
    +
    +

    sets and gets the torrent state flags. See torrent_flags_t. +The set_flags overload that take a mask will affect all +flags part of the mask, and set their values to what the +flags argument is set to. This allows clearing and +setting flags in a single function call. +The set_flags overload that just takes flags, sets all +the specified flags and leave any other flags unchanged. +unset_flags clears the specified flags, while leaving +any other flags unchanged.

    +

    The seed_mode flag is special, it can only be cleared once the +torrent has been added, and it can only be set as part of the +add_torrent_params flags, when adding the torrent.

    +[report issue]
    +
    +

    flush_cache()

    +
    +void flush_cache () const;
    +
    +

    Instructs libtorrent to flush all the disk caches for this torrent and +close all file handles. This is done asynchronously and you will be +notified that it's complete through cache_flushed_alert.

    +

    Note that by the time you get the alert, libtorrent may have cached +more data for the torrent, but you are guaranteed that whatever cached +data libtorrent had by the time you called +torrent_handle::flush_cache() has been written to disk.

    +[report issue]
    +
    +

    force_recheck()

    +
    +void force_recheck () const;
    +
    +

    force_recheck puts the torrent back in a state where it assumes to +have no resume data. All peers will be disconnected and the torrent +will stop announcing to the tracker. The torrent will be added to the +checking queue, and will be checked (all the files will be read and +compared to the piece hashes). Once the check is complete, the torrent +will start connecting to peers again, as normal. +The torrent will be placed last in queue, i.e. its queue position +will be the highest of all torrents in the session.

    +[report issue]
    +
    +

    save_resume_data()

    +
    +void save_resume_data (resume_data_flags_t flags = {}) const;
    +
    +

    save_resume_data() asks libtorrent to generate fast-resume data for +this torrent.

    +

    This operation is asynchronous, save_resume_data will return +immediately. The resume data is delivered when it's done through an +save_resume_data_alert.

    +

    The fast resume data will be empty in the following cases:

    +
    +
      +
    1. The torrent handle is invalid.
    2. +
    3. The torrent hasn't received valid metadata and was started without +metadata (see libtorrent's metadata from peers extension)
    4. +
    +
    +

    Note that by the time you receive the fast resume data, it may already +be invalid if the torrent is still downloading! The recommended +practice is to first pause the session, then generate the fast resume +data, and then close it down. Make sure to not remove_torrent() before +you receive the save_resume_data_alert though. There's no need to +pause when saving intermittent resume data.

    +
    +

    Warning

    +

    If you pause every torrent individually instead of pausing the +session, every torrent will have its paused state saved in the +resume data!

    +
    +
    +

    Note

    +

    It is typically a good idea to save resume data whenever a torrent +is completed or paused. In those cases you don't need to pause the +torrent or the session, since the torrent will do no more writing to +its files. If you save resume data for torrents when they are +paused, you can accelerate the shutdown process by not saving resume +data again for paused torrents. Completed torrents should have their +resume data saved when they complete and on exit, since their +statistics might be updated.

    +
    +In full allocation mode the resume data is never invalidated by +subsequent writes to the files, since pieces won't move around. This +means that you don't need to pause before writing resume data in full +or sparse mode. If you don't, however, any data written to disk after +you saved resume data and before the session closed is lost.
    +
    +

    It also means that if the resume data is out dated, libtorrent will +not re-check the files, but assume that it is fairly recent. The +assumption is that it's better to loose a little bit than to re-check +the entire file.

    +

    It is still a good idea to save resume data periodically during +download as well as when closing down.

    +

    Example code to pause and save resume data for all torrents and wait +for the alerts:

    +
    +extern int outstanding_resume_data; // global counter of outstanding resume data
    +std::vector<torrent_handle> handles = ses.get_torrents();
    +ses.pause();
    +for (torrent_handle const& h : handles)
    +{
    +        if (!h.is_valid()) continue;
    +        torrent_status s = h.status();
    +        if (!s.has_metadata || !s.need_save_resume_data()) continue;
    +
    +        h.save_resume_data();
    +        ++outstanding_resume_data;
    +}
    +
    +while (outstanding_resume_data > 0)
    +{
    +        alert const* a = ses.wait_for_alert(seconds(10));
    +
    +        // if we don't get an alert within 10 seconds, abort
    +        if (a == nullptr) break;
    +
    +        std::vector<alert*> alerts;
    +        ses.pop_alerts(&alerts);
    +
    +        for (alert* i : alerts)
    +        {
    +                if (alert_cast<save_resume_data_failed_alert>(i))
    +                {
    +                        process_alert(i);
    +                        --outstanding_resume_data;
    +                        continue;
    +                }
    +
    +                save_resume_data_alert const* rd = alert_cast<save_resume_data_alert>(i);
    +                if (rd == nullptr)
    +                {
    +                        process_alert(i);
    +                        continue;
    +                }
    +
    +                torrent_handle h = rd->handle;
    +                torrent_status st = h.status(torrent_handle::query_save_path
    +                        | torrent_handle::query_name);
    +                std::ofstream out((st.save_path
    +                        + "/" + st.name + ".fastresume").c_str()
    +                        , std::ios_base::binary);
    +                std::vector<char> buf = write_resume_data_buf(rd->params);
    +                out.write(buf.data(), buf.size());
    +                --outstanding_resume_data;
    +        }
    +}
    +
    +
    +

    Note

    +

    Note how outstanding_resume_data is a global counter in this +example. This is deliberate, otherwise there is a race condition for +torrents that was just asked to save their resume data, they posted +the alert, but it has not been received yet. Those torrents would +report that they don't need to save resume data again, and skipped by +the initial loop, and thwart the counter otherwise.

    +
    +[report issue]
    +
    +

    need_save_resume_data()

    +
    +bool need_save_resume_data () const;
    +
    +

    This function returns true if any whole chunk has been downloaded +since the torrent was first loaded or since the last time the resume +data was saved. When saving resume data periodically, it makes sense +to skip any torrent which hasn't downloaded anything since the last +time.

    +
    +

    Note

    +

    A torrent's resume data is considered saved as soon as the +save_resume_data_alert is posted. It is important to make sure this +alert is received and handled in order for this function to be +meaningful.

    +
    + + + + +[report issue]
    +
    +

    queue_position_top() queue_position_bottom() queue_position() queue_position_down() queue_position_up()

    +
    +queue_position_t queue_position () const;
    +void queue_position_down () const;
    +void queue_position_up () const;
    +void queue_position_top () const;
    +void queue_position_bottom () const;
    +
    +

    Every torrent that is added is assigned a queue position exactly one +greater than the greatest queue position of all existing torrents. +Torrents that are being seeded have -1 as their queue position, since +they're no longer in line to be downloaded.

    +

    When a torrent is removed or turns into a seed, all torrents with +greater queue positions have their positions decreased to fill in the +space in the sequence.

    +

    queue_position() returns the torrent's position in the download +queue. The torrents with the smallest numbers are the ones that are +being downloaded. The smaller number, the closer the torrent is to the +front of the line to be started.

    +

    The queue position is also available in the torrent_status.

    +

    The queue_position_*() functions adjust the torrents position in +the queue. Up means closer to the front and down means closer to the +back of the queue. Top and bottom refers to the front and the back of +the queue respectively.

    +[report issue]
    +
    +

    queue_position_set()

    +
    +void queue_position_set (queue_position_t p) const;
    +
    +

    updates the position in the queue for this torrent. The relative order +of all other torrents remain intact but their numerical queue position +shifts to make space for this torrent's new position

    + +[report issue]
    +
    +

    set_ssl_certificate() set_ssl_certificate_buffer()

    +
    +void set_ssl_certificate (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params
    +      , std::string const& passphrase = "");
    +void set_ssl_certificate_buffer (std::string const& certificate
    +      , std::string const& private_key
    +      , std::string const& dh_params);
    +
    +

    For SSL torrents, use this to specify a path to a .pem file to use as +this client's certificate. The certificate must be signed by the +certificate in the .torrent file to be valid.

    +

    The set_ssl_certificate_buffer() overload takes the actual certificate, +private key and DH params as strings, rather than paths to files.

    +

    cert is a path to the (signed) certificate in .pem format +corresponding to this torrent.

    +

    private_key is a path to the private key for the specified +certificate. This must be in .pem format.

    +

    dh_params is a path to the Diffie-Hellman parameter file, which +needs to be in .pem format. You can generate this file using the +openssl command like this: openssl dhparam -outform PEM -out +dhparams.pem 512.

    +

    passphrase may be specified if the private key is encrypted and +requires a passphrase to be decrypted.

    +

    Note that when a torrent first starts up, and it needs a certificate, +it will suspend connecting to any peers until it has one. It's +typically desirable to resume the torrent after setting the SSL +certificate.

    +

    If you receive a torrent_need_cert_alert, you need to call this to +provide a valid cert. If you don't have a cert you won't be allowed to +connect to any peers.

    +[report issue]
    +
    +

    get_storage_impl()

    +
    +storage_interface* get_storage_impl () const;
    +
    +

    Returns the storage implementation for this torrent. This depends on the +storage constructor function that was passed to add_torrent.

    +[report issue]
    +
    +

    torrent_file()

    +
    +std::shared_ptr<const torrent_info> torrent_file () const;
    +
    +

    Returns a pointer to the torrent_info object associated with this +torrent. The torrent_info object may be a copy of the internal object. +If the torrent doesn't have metadata, the pointer will not be +initialized (i.e. a nullptr). The torrent may be in a state +without metadata only if it was started without a .torrent file, e.g. +by being added by magnet link

    +[report issue]
    +
    +

    piece_availability()

    +
    +void piece_availability (std::vector<int>& avail) const;
    +
    +

    Fills the specified std::vector<int> with the availability for +each piece in this torrent. libtorrent does not keep track of +availability for seeds, so if the torrent is seeding the availability +for all pieces is reported as 0.

    +

    The piece availability is the number of peers that we are connected +that has advertised having a particular piece. This is the information +that libtorrent uses in order to prefer picking rare pieces.

    + + +[report issue]
    +
    +

    prioritize_pieces() get_piece_priorities() piece_priority()

    +
    +download_priority_t piece_priority (piece_index_t index) const;
    +void prioritize_pieces (std::vector<std::pair<piece_index_t, download_priority_t>> const& pieces) const;
    +std::vector<download_priority_t> get_piece_priorities () const;
    +void piece_priority (piece_index_t index, download_priority_t priority) const;
    +void prioritize_pieces (std::vector<download_priority_t> const& pieces) const;
    +
    +

    These functions are used to set and get the priority of individual +pieces. By default all pieces have priority 4. That means that the +random rarest first algorithm is effectively active for all pieces. +You may however change the priority of individual pieces. There are 8 +priority levels. 0 means not to download the piece at all. Otherwise, +lower priority values means less likely to be picked. Piece priority +takes precedence over piece availability. Every piece with priority 7 +will be attempted to be picked before a priority 6 piece and so on.

    +

    The default priority of pieces is 4.

    +

    Piece priorities can not be changed for torrents that have not +downloaded the metadata yet. Magnet links won't have metadata +immediately. see the metadata_received_alert.

    +

    piece_priority sets or gets the priority for an individual piece, +specified by index.

    +

    prioritize_pieces takes a vector of integers, one integer per +piece in the torrent. All the piece priorities will be updated with +the priorities in the vector. +The second overload of prioritize_pieces that takes a vector of pairs +will update the priorities of only select pieces, and leave all other +unaffected. Each pair is (piece, priority). That is, the first item is +the piece index and the second item is the priority of that piece. +Invalid entries, where the piece index or priority is out of range, are +not allowed.

    +

    get_piece_priorities returns a vector with one element for each piece +in the torrent. Each element is the current priority of that piece.

    +

    It's possible to cancel the effect of file priorities by setting the +priorities for the affected pieces. Care has to be taken when mixing +usage of file- and piece priorities.

    + + +[report issue]
    +
    +

    file_priority() prioritize_files() get_file_priorities()

    +
    +std::vector<download_priority_t> get_file_priorities () const;
    +void file_priority (file_index_t index, download_priority_t priority) const;
    +download_priority_t file_priority (file_index_t index) const;
    +void prioritize_files (std::vector<download_priority_t> const& files) const;
    +
    +

    index must be in the range [0, number_of_files).

    +

    file_priority() queries or sets the priority of file index.

    +

    prioritize_files() takes a vector that has at as many elements as +there are files in the torrent. Each entry is the priority of that +file. The function sets the priorities of all the pieces in the +torrent based on the vector.

    +

    get_file_priorities() returns a vector with the priorities of all +files.

    +

    The priority values are the same as for piece_priority(). See +download_priority_t.

    +

    Whenever a file priority is changed, all other piece priorities are +reset to match the file priorities. In order to maintain special +priorities for particular pieces, piece_priority() has to be called +again for those pieces.

    +

    You cannot set the file priorities on a torrent that does not yet have +metadata or a torrent that is a seed. file_priority(int, int) and +prioritize_files() are both no-ops for such torrents.

    +

    Since changing file priorities may involve disk operations (of moving +files in- and out of the part file), the internal accounting of file +priorities happen asynchronously. i.e. setting file priorities and then +immediately querying them may not yield the same priorities just set. +However, the piece priorities are updated immediately.

    +

    when combining file- and piece priorities, the resume file will record +both. When loading the resume data, the file priorities will be applied +first, then the piece priorities.

    + +[report issue]
    +
    +

    force_reannounce() force_dht_announce()

    +
    +void force_dht_announce () const;
    +void force_reannounce (int seconds = 0, int tracker_index = -1, reannounce_flags_t = {}) const;
    +
    +

    force_reannounce() will force this torrent to do another tracker +request, to receive new peers. The seconds argument specifies how +many seconds from now to issue the tracker announces.

    +

    If the tracker's min_interval has not passed since the last +announce, the forced announce will be scheduled to happen immediately +as the min_interval expires. This is to honor trackers minimum +re-announce interval settings.

    +

    The tracker_index argument specifies which tracker to re-announce. +If set to -1 (which is the default), all trackers are re-announce.

    +

    The flags argument can be used to affect the re-announce. See +ignore_min_interval.

    +

    force_dht_announce will announce the torrent to the DHT +immediately.

    +[report issue]
    +
    +

    scrape_tracker()

    +
    +void scrape_tracker (int idx = -1) const;
    +
    +

    scrape_tracker() will send a scrape request to a tracker. By +default (idx = -1) it will scrape the last working tracker. If +idx is >= 0, the tracker with the specified index will scraped.

    +

    A scrape request queries the tracker for statistics such as total +number of incomplete peers, complete peers, number of downloads etc.

    +

    This request will specifically update the num_complete and +num_incomplete fields in the torrent_status struct once it +completes. When it completes, it will generate a scrape_reply_alert. +If it fails, it will generate a scrape_failed_alert.

    + + + +[report issue]
    +
    +

    set_download_limit() upload_limit() download_limit() set_upload_limit()

    +
    +void set_download_limit (int limit) const;
    +int download_limit () const;
    +void set_upload_limit (int limit) const;
    +int upload_limit () const;
    +
    +

    set_upload_limit will limit the upload bandwidth used by this +particular torrent to the limit you set. It is given as the number of +bytes per second the torrent is allowed to upload. +set_download_limit works the same way but for download bandwidth +instead of upload bandwidth. Note that setting a higher limit on a +torrent then the global limit +(settings_pack::upload_rate_limit) will not override the global +rate limit. The torrent can never upload more than the global rate +limit.

    +

    upload_limit and download_limit will return the current limit +setting, for upload and download, respectively.

    +

    Local peers are not rate limited by default. see peer classes.

    +[report issue]
    +
    +

    connect_peer()

    +
    +void connect_peer (tcp::endpoint const& adr, peer_source_flags_t source = {}
    +      , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const;
    +
    +

    connect_peer() is a way to manually connect to peers that one +believe is a part of the torrent. If the peer does not respond, or is +not a member of this torrent, it will simply be disconnected. No harm +can be done by using this other than an unnecessary connection attempt +is made. If the torrent is uninitialized or in queued or checking +mode, this will throw system_error. The second (optional) +argument will be bitwise ORed into the source mask of this peer. +Typically this is one of the source flags in peer_info. i.e. +tracker, pex, dht etc.

    +

    For possible values of flags, see pex_flags_t.

    +[report issue]
    +
    +

    clear_peers()

    +
    +void clear_peers ();
    +
    +

    This will disconnect all peers and clear the peer list for this +torrent. New peers will have to be acquired before resuming, from +trackers, DHT or local service discovery, for example.

    + +[report issue]
    +
    +

    set_max_uploads() max_uploads()

    +
    +void set_max_uploads (int max_uploads) const;
    +int max_uploads () const;
    +
    +

    set_max_uploads() sets the maximum number of peers that's unchoked +at the same time on this torrent. If you set this to -1, there will be +no limit. This defaults to infinite. The primary setting controlling +this is the global unchoke slots limit, set by unchoke_slots_limit in +settings_pack.

    +

    max_uploads() returns the current settings.

    + +[report issue]
    +
    +

    max_connections() set_max_connections()

    +
    +void set_max_connections (int max_connections) const;
    +int max_connections () const;
    +
    +

    set_max_connections() sets the maximum number of connection this +torrent will open. If all connections are used up, incoming +connections may be refused or poor connections may be closed. This +must be at least 2. The default is unlimited number of connections. If +-1 is given to the function, it means unlimited. There is also a +global limit of the number of connections, set by +connections_limit in settings_pack.

    +

    max_connections() returns the current settings.

    +[report issue]
    +
    +

    move_storage()

    +
    +void move_storage (std::string const& save_path
    +      , move_flags_t flags = move_flags_t::always_replace_files
    +      ) const;
    +
    +

    Moves the file(s) that this torrent are currently seeding from or +downloading to. If the given save_path is not located on the same +drive as the original save path, the files will be copied to the new +drive and removed from their original location. This will block all +other disk IO, and other torrents download and upload rates may drop +while copying the file.

    +

    Since disk IO is performed in a separate thread, this operation is +also asynchronous. Once the operation completes, the +storage_moved_alert is generated, with the new path as the +message. If the move fails for some reason, +storage_moved_failed_alert is generated instead, containing the +error message.

    +

    The flags argument determines the behavior of the copying/moving +of the files in the torrent. see move_flags_t.

    +

    always_replace_files is the default and replaces any file that +exist in both the source directory and the target directory.

    +

    fail_if_exist first check to see that none of the copy operations +would cause an overwrite. If it would, it will fail. Otherwise it will +proceed as if it was in always_replace_files mode. Note that there +is an inherent race condition here. If the files in the target +directory appear after the check but before the copy or move +completes, they will be overwritten. When failing because of files +already existing in the target path, the error of +move_storage_failed_alert is set to +boost::system::errc::file_exists.

    +

    The intention is that a client may use this as a probe, and if it +fails, ask the user which mode to use. The client may then re-issue +the move_storage call with one of the other modes.

    +

    dont_replace always keeps the existing file in the target +directory, if there is one. The source files will still be removed in +that case. Note that it won't automatically re-check files. If an +incomplete torrent is moved into a directory with the complete files, +pause, move, force-recheck and resume. Without the re-checking, the +torrent will keep downloading and files in the new download directory +will be overwritten.

    +

    Files that have been renamed to have absolute paths are not moved by +this function. Keep in mind that files that don't belong to the +torrent but are stored in the torrent's directory may be moved as +well. This goes for files that have been renamed to absolute paths +that still end up inside the save path.

    +[report issue]
    +
    +

    rename_file()

    +
    +void rename_file (file_index_t index, std::string const& new_name) const;
    +
    +

    Renames the file with the given index asynchronously. The rename +operation is complete when either a file_renamed_alert or +file_rename_failed_alert is posted.

    +[report issue]
    +
    +

    info_hash()

    +
    +sha1_hash info_hash () const;
    +
    +

    info_hash() returns the info-hash of the torrent. If this handle +is to a torrent that hasn't loaded yet (for instance by being added) +by a URL, the returned value is undefined.

    + + +[report issue]
    +
    +

    operator==() operator!=() operator<()

    +
    +bool operator== (const torrent_handle& h) const;
    +bool operator!= (const torrent_handle& h) const;
    +bool operator< (const torrent_handle& h) const;
    +
    +

    comparison operators. The order of the torrents is unspecified +but stable.

    +[report issue]
    +
    +

    id()

    +
    +std::uint32_t id () const;
    +
    +

    returns a unique identifier for this torrent. It's not a dense index. +It's not preserved across sessions.

    +[report issue]
    +
    +

    native_handle()

    +
    +std::shared_ptr<torrent> native_handle () const;
    +
    +

    This function is intended only for use by plugins and the alert +dispatch function. This type does not have a stable ABI and should +be relied on as little as possible. Accessing the handle returned by +this function is not thread safe outside of libtorrent's internal +thread (which is used to invoke plugin callbacks). +The torrent class is not only eligible for changing ABI across +minor versions of libtorrent, its layout is also dependent on build +configuration. This adds additional requirements on a client to be +built with the exact same build configuration as libtorrent itself. +i.e. the TORRENT_ macros must match between libtorrent and the +client builds.

    +[report issue]
    +
    +

    enum file_progress_flags_t

    +

    Declared in "libtorrent/torrent_handle.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    piece_granularity1only calculate file progress at piece granularity. This makes +the file_progress() call cheaper and also only takes bytes that +have passed the hash check into account, so progress cannot +regress in this mode.
    +[report issue]
    +
    overwrite_existing
    +
    instruct libtorrent to overwrite any data that may already have been +downloaded with the data of the new piece being added.
    +
    +[report issue]
    +
    query_distributed_copies
    +
    calculates distributed_copies, distributed_full_copies and +distributed_fraction.
    +
    +[report issue]
    +
    query_accurate_download_counters
    +
    includes partial downloaded blocks in total_done and +total_wanted_done.
    +
    +[report issue]
    +
    query_last_seen_complete
    +
    includes last_seen_complete.
    +
    +[report issue]
    +
    query_pieces
    +
    populate the pieces field in torrent_status.
    +
    +[report issue]
    +
    query_verified_pieces
    +
    includes verified_pieces (only applies to torrents in seed +mode).
    +
    +[report issue]
    +
    query_torrent_file
    +
    includes torrent_file, which is all the static information from +the .torrent file.
    +
    +[report issue]
    +
    query_name
    +
    includes name, the name of the torrent. This is either derived +from the .torrent file, or from the &dn= magnet link argument +or possibly some other source. If the name of the torrent is not +known, this is an empty string.
    +
    +[report issue]
    +
    query_save_path
    +
    includes save_path, the path to the directory the files of the +torrent are saved to.
    +
    +[report issue]
    +
    alert_when_available
    +
    used to ask libtorrent to send an alert once the piece has been +downloaded, by passing alert_when_available. When set, the +read_piece_alert alert will be delivered, with the piece data, when +it's downloaded.
    +
    + +[report issue]
    +
    graceful_pause clear_disk_cache
    +
    will delay the disconnect of peers that we're still downloading +outstanding requests from. The torrent will not accept any more +requests and will disconnect all idle peers. As soon as a peer is done +transferring the blocks that were requested from it, it is +disconnected. This is a graceful shut down of the torrent in the sense +that no downloaded bytes are wasted.
    +
    +[report issue]
    +
    flush_disk_cache
    +
    the disk cache will be flushed before creating the resume data. +This avoids a problem with file timestamps in the resume data in +case the cache hasn't been flushed yet.
    +
    +[report issue]
    +
    save_info_dict
    +
    the resume data will contain the metadata from the torrent file as +well. This is default for any torrent that's added without a +torrent file (such as a magnet link or a URL).
    +
    +[report issue]
    +
    only_if_modified
    +
    if nothing significant has changed in the torrent since the last +time resume data was saved, fail this attempt. Significant changes +primarily include more data having been downloaded, file or piece +priorities having changed etc. If the resume data doesn't need +saving, a save_resume_data_failed_alert is posted with the error +resume_data_not_modified.
    +
    +[report issue]
    +
    ignore_min_interval
    +
    by default, force-reannounce will still honor the min-interval +published by the tracker. If this flag is set, it will be ignored +and the tracker is announced immediately.
    +
    +[report issue]
    +
    +
    +

    cache_status

    +

    Declared in "libtorrent/disk_io_thread.hpp"

    +

    this struct holds a number of statistics counters +relevant for the disk io thread and disk cache.

    +
    +struct cache_status
    +{
    +   cache_status ();
    +
    +   std::vector<cached_piece_info> pieces;
    +};
    +
    +[report issue]
    +

    cache_status()

    +
    +cache_status ();
    +
    +

    initializes all counters to 0

    +[report issue]
    +
    +
    +

    web_seed_entry

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    the web_seed_entry holds information about a web seed (also known +as URL seed or HTTP seed). It is essentially a URL with some state +associated with it. For more information, see BEP 17 and BEP 19.

    +
    +struct web_seed_entry
    +{
    +   bool operator== (web_seed_entry const& e) const;
    +   bool operator< (web_seed_entry const& e) const;
    +
    +   enum type_t
    +   {
    +      url_seed,
    +      http_seed,
    +   };
    +
    +   std::string url;
    +   std::string auth;
    +   headers_t extra_headers;
    +   std::uint8_t type;
    +};
    +
    +[report issue]
    +

    operator==()

    +
    +bool operator== (web_seed_entry const& e) const;
    +
    +

    URL and type comparison

    +[report issue]
    +
    +

    operator<()

    +
    +bool operator< (web_seed_entry const& e) const;
    +
    +

    URL and type less-than comparison

    +[report issue]
    +
    +

    enum type_t

    +

    Declared in "libtorrent/torrent_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    url_seed0 
    http_seed1 
    +[report issue]
    +
    url
    +
    The URL of the web seed
    +
    +[report issue]
    +
    auth
    +
    Optional authentication. If this is set, it's passed +in as HTTP basic auth to the web seed. The format is: +username:password.
    +
    +[report issue]
    +
    extra_headers
    +
    Any extra HTTP headers that need to be passed to the web seed
    +
    +[report issue]
    +
    type
    +
    The type of web seed (see type_t)
    +
    +[report issue]
    +
    +
    +

    load_torrent_limits

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    this object holds configuration options for limits to use when loading +torrents. They are meant to prevent loading potentially malicious torrents +that cause excessive memory allocations.

    +
    +struct load_torrent_limits
    +{
    +   int max_buffer_size  = 10000000;
    +   int max_pieces  = 0x200000;
    +   int max_decode_depth  = 100;
    +   int max_decode_tokens  = 3000000;
    +};
    +
    +[report issue]
    +
    max_buffer_size
    +
    the max size of a .torrent file to load into RAM
    +
    +[report issue]
    +
    max_pieces
    +
    the max number of pieces allowed in the torrent
    +
    +[report issue]
    +
    max_decode_depth
    +
    the max recursion depth in the bdecoded structure
    +
    +[report issue]
    +
    max_decode_tokens
    +
    the max number of bdecode tokens
    +
    +[report issue]
    +
    +

    torrent_info

    +

    Declared in "libtorrent/torrent_info.hpp"

    +

    the torrent_info class holds the information found in a .torrent file.

    +
    +class torrent_info
    +{
    +   torrent_info (std::string const& filename, load_torrent_limits const& cfg);
    +   explicit torrent_info (bdecode_node const& torrent_file);
    +   torrent_info (torrent_info const& t);
    +   explicit torrent_info (span<char const> buffer, from_span_t);
    +   torrent_info (span<char const> buffer, load_torrent_limits const& cfg, from_span_t);
    +   explicit torrent_info (sha1_hash const& info_hash);
    +   torrent_info (char const* buffer, int size, error_code& ec);
    +   torrent_info (std::string const& filename, error_code& ec);
    +   torrent_info (bdecode_node const& torrent_file, error_code& ec);
    +   torrent_info (char const* buffer, int size);
    +   explicit torrent_info (std::string const& filename);
    +   torrent_info (bdecode_node const& torrent_file, load_torrent_limits const& cfg);
    +   torrent_info (span<char const> buffer, error_code& ec, from_span_t);
    +   ~torrent_info ();
    +   file_storage const& files () const;
    +   file_storage const& orig_files () const;
    +   void rename_file (file_index_t index, std::string const& new_filename);
    +   void remap_files (file_storage const& f);
    +   void add_tracker (std::string const& url, int tier
    +      , announce_entry::tracker_source source);
    +   void add_tracker (std::string const& url, int tier = 0);
    +   std::vector<announce_entry> const& trackers () const;
    +   std::vector<std::string> collections () const;
    +   std::vector<sha1_hash> similar_torrents () const;
    +   std::vector<web_seed_entry> const& web_seeds () const;
    +   void add_url_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +   void add_http_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +   void set_web_seeds (std::vector<web_seed_entry> seeds);
    +   std::int64_t total_size () const;
    +   int piece_length () const;
    +   int num_pieces () const;
    +   piece_index_t end_piece () const;
    +   piece_index_t last_piece () const;
    +   index_range<piece_index_t> piece_range () const;
    +   const sha1_hash& info_hash () const;
    +   int num_files () const;
    +   std::vector<file_slice> map_block (piece_index_t const piece
    +      , std::int64_t offset, int size) const;
    +   peer_request map_file (file_index_t const file, std::int64_t offset, int size) const;
    +   string_view ssl_cert () const;
    +   bool is_valid () const;
    +   bool priv () const;
    +   bool is_i2p () const;
    +   int piece_size (piece_index_t index) const;
    +   char const* hash_for_piece_ptr (piece_index_t const index) const;
    +   sha1_hash hash_for_piece (piece_index_t index) const;
    +   bool is_loaded () const;
    +   std::vector<sha1_hash> const& merkle_tree () const;
    +   void set_merkle_tree (std::vector<sha1_hash>& h);
    +   const std::string& name () const;
    +   std::time_t creation_date () const;
    +   const std::string& creator () const;
    +   const std::string& comment () const;
    +   std::vector<std::pair<std::string, int>> const& nodes () const;
    +   void add_node (std::pair<std::string, int> const& node);
    +   bool parse_info_section (bdecode_node const& e, error_code& ec);
    +   bool parse_info_section (bdecode_node const& e, error_code& ec, int piece_limit);
    +   bdecode_node info (char const* key) const;
    +   void swap (torrent_info& ti);
    +   boost::shared_array<char> metadata () const;
    +   int metadata_size () const;
    +   bool is_merkle_torrent () const;
    +};
    +
    +[report issue]
    +

    torrent_info()

    +
    +torrent_info (std::string const& filename, load_torrent_limits const& cfg);
    +explicit torrent_info (bdecode_node const& torrent_file);
    +torrent_info (torrent_info const& t);
    +explicit torrent_info (span<char const> buffer, from_span_t);
    +torrent_info (span<char const> buffer, load_torrent_limits const& cfg, from_span_t);
    +explicit torrent_info (sha1_hash const& info_hash);
    +torrent_info (char const* buffer, int size, error_code& ec);
    +torrent_info (std::string const& filename, error_code& ec);
    +torrent_info (bdecode_node const& torrent_file, error_code& ec);
    +torrent_info (char const* buffer, int size);
    +explicit torrent_info (std::string const& filename);
    +torrent_info (bdecode_node const& torrent_file, load_torrent_limits const& cfg);
    +torrent_info (span<char const> buffer, error_code& ec, from_span_t);
    +
    +

    The constructor that takes an info-hash will initialize the info-hash +to the given value, but leave all other fields empty. This is used +internally when downloading torrents without the metadata. The +metadata will be created by libtorrent as soon as it has been +downloaded from the swarm.

    +

    The constructor that takes a bdecode_node will create a torrent_info +object from the information found in the given torrent_file. The +bdecode_node represents a tree node in an bencoded file. To load an +ordinary .torrent file into a bdecode_node, use bdecode().

    +

    The version that takes a buffer pointer and a size will decode it as a +.torrent file and initialize the torrent_info object for you.

    +

    The version that takes a filename will simply load the torrent file +and decode it inside the constructor, for convenience. This might not +be the most suitable for applications that want to be able to report +detailed errors on what might go wrong.

    +

    There is an upper limit on the size of the torrent file that will be +loaded by the overload taking a filename. If it's important that even +very large torrent files are loaded, use one of the other overloads.

    +

    The overloads that takes an error_code const& never throws if an +error occur, they will simply set the error code to describe what went +wrong and not fully initialize the torrent_info object. The overloads +that do not take the extra error_code parameter will always throw if +an error occurs. These overloads are not available when building +without exception support.

    +

    The overload that takes a span also needs an extra parameter of +type from_span_t to disambiguate the std::string overload for +string literals. There is an object in the libtorrent namespace of this +type called from_span.

    +[report issue]
    +
    +

    ~torrent_info()

    +
    +~torrent_info ();
    +
    +

    frees all storage associated with this torrent_info object

    + +[report issue]
    +
    +

    files() orig_files()

    +
    +file_storage const& files () const;
    +file_storage const& orig_files () const;
    +
    +

    The file_storage object contains the information on how to map the +pieces to files. It is separated from the torrent_info object because +when creating torrents a storage object needs to be created without +having a torrent file. When renaming files in a storage, the storage +needs to make its own copy of the file_storage in order to make its +mapping differ from the one in the torrent file.

    +

    orig_files() returns the original (unmodified) file storage for +this torrent. This is used by the web server connection, which needs +to request files with the original names. Filename may be changed using +torrent_info::rename_file().

    +

    For more information on the file_storage object, see the separate +document on how to create torrents.

    +[report issue]
    +
    +

    rename_file()

    +
    +void rename_file (file_index_t index, std::string const& new_filename);
    +
    +

    Renames the file with the specified index to the new name. The new +filename is reflected by the file_storage returned by files() +but not by the one returned by orig_files().

    +

    If you want to rename the base name of the torrent (for a multi file +torrent), you can copy the file_storage (see files() and +orig_files() ), change the name, and then use remap_files().

    +

    The new_filename can both be a relative path, in which case the +file name is relative to the save_path of the torrent. If the +new_filename is an absolute path (i.e. is_complete(new_filename) +== true), then the file is detached from the save_path of the +torrent. In this case the file is not moved when move_storage() is +invoked.

    +[report issue]
    +
    +

    remap_files()

    +
    +void remap_files (file_storage const& f);
    +
    +

    Remaps the file storage to a new file layout. This can be used to, for +instance, download all data in a torrent to a single file, or to a +number of fixed size sector aligned files, regardless of the number +and sizes of the files in the torrent.

    +

    The new specified file_storage must have the exact same size as +the current one.

    + +[report issue]
    +
    +

    add_tracker() trackers()

    +
    +void add_tracker (std::string const& url, int tier
    +      , announce_entry::tracker_source source);
    +void add_tracker (std::string const& url, int tier = 0);
    +std::vector<announce_entry> const& trackers () const;
    +
    +

    add_tracker() adds a tracker to the announce-list. The tier +determines the order in which the trackers are to be tried. +The trackers() function will return a sorted vector of +announce_entry. Each announce entry contains a string, which is +the tracker url, and a tier index. The tier index is the high-level +priority. No matter which trackers that works or not, the ones with +lower tier will always be tried before the one with higher tier +number. For more information, see announce_entry.

    + +[report issue]
    +
    +

    similar_torrents() collections()

    +
    +std::vector<std::string> collections () const;
    +std::vector<sha1_hash> similar_torrents () const;
    +
    +

    These two functions are related to BEP 38 (mutable torrents). The +vectors returned from these correspond to the "similar" and +"collections" keys in the .torrent file. Both info-hashes and +collections from within the info-dict and from outside of it are +included.

    + + + +[report issue]
    +
    +

    add_http_seed() web_seeds() set_web_seeds() add_url_seed()

    +
    +std::vector<web_seed_entry> const& web_seeds () const;
    +void add_url_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +void add_http_seed (std::string const& url
    +      , std::string const& extern_auth = std::string()
    +      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
    +void set_web_seeds (std::vector<web_seed_entry> seeds);
    +
    +

    web_seeds() returns all url seeds and http seeds in the torrent. +Each entry is a web_seed_entry and may refer to either a url seed +or http seed.

    +

    add_url_seed() and add_http_seed() adds one url to the list of +url/http seeds. Currently, the only transport protocol supported for +the url is http.

    +

    set_web_seeds() replaces all web seeds with the ones specified in +the seeds vector.

    +

    The extern_auth argument can be used for other authorization +schemes than basic HTTP authorization. If set, it will override any +username and password found in the URL itself. The string will be sent +as the HTTP authorization header's value (without specifying "Basic").

    +

    The extra_headers argument defaults to an empty list, but can be +used to insert custom HTTP headers in the requests to a specific web +seed.

    +

    See http seeding for more information.

    + + +[report issue]
    +
    +

    num_pieces() total_size() piece_length()

    +
    +std::int64_t total_size () const;
    +int piece_length () const;
    +int num_pieces () const;
    +
    +

    total_size(), piece_length() and num_pieces() returns the +total number of bytes the torrent-file represents (all the files in +it), the number of byte for each piece and the total number of pieces, +respectively. The difference between piece_size() and +piece_length() is that piece_size() takes the piece index as +argument and gives you the exact size of that piece. It will always be +the same as piece_length() except in the case of the last piece, +which may be smaller.

    + + +[report issue]
    +
    +

    end_piece() last_piece() piece_range()

    +
    +piece_index_t end_piece () const;
    +piece_index_t last_piece () const;
    +index_range<piece_index_t> piece_range () const;
    +
    +

    last_piece() returns the index to the last piece in the torrent and +end_piece() returns the index to the one-past-end piece in the +torrent +piece_range() returns an implementation-defined type that can be +used as the container in a range-for loop. Where the values are the +indices of all pieces in the file_storage.

    +[report issue]
    +
    +

    info_hash()

    +
    +const sha1_hash& info_hash () const;
    +
    +

    returns the info-hash of the torrent

    +[report issue]
    +
    +

    num_files()

    +
    +int num_files () const;
    +
    +

    If you need index-access to files you can use the num_files() along +with the file_path(), file_size()-family of functions to access +files using indices.

    +[report issue]
    +
    +

    map_block()

    +
    +std::vector<file_slice> map_block (piece_index_t const piece
    +      , std::int64_t offset, int size) const;
    +
    +

    This function will map a piece index, a byte offset within that piece +and a size (in bytes) into the corresponding files with offsets where +that data for that piece is supposed to be stored. See file_slice.

    +[report issue]
    +
    +

    map_file()

    +
    +peer_request map_file (file_index_t const file, std::int64_t offset, int size) const;
    +
    +

    This function will map a range in a specific file into a range in the +torrent. The file_offset parameter is the offset in the file, +given in bytes, where 0 is the start of the file. See peer_request.

    +

    The input range is assumed to be valid within the torrent. +file_offset + size is not allowed to be greater than the file +size. file_index must refer to a valid file, i.e. it cannot be >= +num_files().

    +[report issue]
    +
    +

    ssl_cert()

    +
    +string_view ssl_cert () const;
    +
    +

    Returns the SSL root certificate for the torrent, if it is an SSL +torrent. Otherwise returns an empty string. The certificate is +the public certificate in x509 format.

    +[report issue]
    +
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if this torrent_info object has a torrent loaded. +This is primarily used to determine if a magnet link has had its +metadata resolved yet or not.

    +[report issue]
    +
    +

    priv()

    +
    +bool priv () const;
    +
    +

    returns true if this torrent is private. i.e., the client should not +advertise itself on the trackerless network (the Kademlia DHT) for this torrent.

    +[report issue]
    +
    +

    is_i2p()

    +
    +bool is_i2p () const;
    +
    +

    returns true if this is an i2p torrent. This is determined by whether +or not it has a tracker whose URL domain name ends with ".i2p". i2p +torrents disable the DHT and local peer discovery as well as talking +to peers over anything other than the i2p network.

    +[report issue]
    +
    +

    piece_size()

    +
    +int piece_size (piece_index_t index) const;
    +
    +

    returns the piece size of file with index. This will be the same as piece_length(), +except for the last piece, which may be shorter.

    + +[report issue]
    +
    +

    hash_for_piece_ptr() hash_for_piece()

    +
    +char const* hash_for_piece_ptr (piece_index_t const index) const;
    +sha1_hash hash_for_piece (piece_index_t index) const;
    +
    +

    hash_for_piece() takes a piece-index and returns the 20-bytes +sha1-hash for that piece and info_hash() returns the 20-bytes +sha1-hash for the info-section of the torrent file. +hash_for_piece_ptr() returns a pointer to the 20 byte sha1 digest +for the piece. Note that the string is not 0-terminated.

    + +[report issue]
    +
    +

    merkle_tree() set_merkle_tree()

    +
    +std::vector<sha1_hash> const& merkle_tree () const;
    +void set_merkle_tree (std::vector<sha1_hash>& h);
    +
    +

    merkle_tree() returns a reference to the merkle tree for this +torrent, if any. +set_merkle_tree() moves the passed in merkle tree into the +torrent_info object. i.e. h will not be identical after the call. +You need to set the merkle tree for a torrent that you've just created +(as a merkle torrent). The merkle tree is retrieved from the +create_torrent::merkle_tree() function, and need to be saved +separately from the torrent file itself. Once it's added to +libtorrent, the merkle tree will be persisted in the resume data.

    +[report issue]
    +
    +

    name()

    +
    +const std::string& name () const;
    +
    +

    name() returns the name of the torrent. +name contains UTF-8 encoded string.

    +[report issue]
    +
    +

    creation_date()

    +
    +std::time_t creation_date () const;
    +
    +

    creation_date() returns the creation date of the torrent as time_t +(posix time). If there's no time stamp in the torrent file, the +optional object will be uninitialized. +.. posix time: http://www.opengroup.org/onlinepubs/009695399/functions/time.html

    +[report issue]
    +
    +

    creator()

    +
    +const std::string& creator () const;
    +
    +

    creator() returns the creator string in the torrent. If there is +no creator string it will return an empty string.

    +[report issue]
    +
    +

    comment()

    +
    +const std::string& comment () const;
    +
    +

    comment() returns the comment associated with the torrent. If +there's no comment, it will return an empty string. +comment contains UTF-8 encoded string.

    +[report issue]
    +
    +

    nodes()

    +
    +std::vector<std::pair<std::string, int>> const& nodes () const;
    +
    +

    If this torrent contains any DHT nodes, they are put in this vector in +their original form (host name and port number).

    +[report issue]
    +
    +

    add_node()

    +
    +void add_node (std::pair<std::string, int> const& node);
    +
    +

    This is used when creating torrent. Use this to add a known DHT node. +It may be used, by the client, to bootstrap into the DHT network.

    +[report issue]
    +
    +

    parse_info_section()

    +
    +bool parse_info_section (bdecode_node const& e, error_code& ec);
    +bool parse_info_section (bdecode_node const& e, error_code& ec, int piece_limit);
    +
    +

    populates the torrent_info by providing just the info-dict buffer. +This is used when loading a torrent from a magnet link for instance, +where we only have the info-dict. The bdecode_node e points to a +parsed info-dictionary. ec returns an error code if something +fails (typically if the info dictionary is malformed). +the piece_limit parameter allows limiting the amount of memory +dedicated to loading the torrent, and fails for torrents that exceed +the limit

    +[report issue]
    +
    +

    info()

    +
    +bdecode_node info (char const* key) const;
    +
    +

    This function looks up keys from the info-dictionary of the loaded +torrent file. It can be used to access extension values put in the +.torrent file. If the specified key cannot be found, it returns nullptr.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (torrent_info& ti);
    +
    +

    swap the content of this and ti.

    + +[report issue]
    +
    +

    metadata() metadata_size()

    +
    +boost::shared_array<char> metadata () const;
    +int metadata_size () const;
    +
    +

    metadata() returns a the raw info section of the torrent file. The size +of the metadata is returned by metadata_size().

    +[report issue]
    +
    +

    is_merkle_torrent()

    +
    +bool is_merkle_torrent () const;
    +
    +

    returns whether or not this is a merkle torrent. +see BEP 30.

    +[report issue]
    +
    +
    +

    add_torrent_params

    +

    Declared in "libtorrent/add_torrent_params.hpp"

    +

    The add_torrent_params is a parameter pack for adding torrents to a +session. The key fields when adding a torrent are:

    +
      +
    • ti - when you have loaded a .torrent file into a torrent_info object
    • +
    • info_hash - when you don't have the metadata (.torrent file) but. This +is set when adding a magnet link.
    • +
    +

    one of those fields must be set. Another mandatory field is +save_path. The add_torrent_params object is passed into one of the +session::add_torrent() overloads or session::async_add_torrent().

    +

    If you only specify the info-hash, the torrent file will be downloaded +from peers, which requires them to support the metadata extension. For +the metadata extension to work, libtorrent must be built with extensions +enabled (TORRENT_DISABLE_EXTENSIONS must not be defined). It also +takes an optional name argument. This may be left empty in case no +name should be assigned to the torrent. In case it's not, the name is +used for the torrent as long as it doesn't have metadata. See +torrent_handle::name.

    +

    The add_torrent_params is also used when requesting resume data for a +torrent. It can be saved to and restored from a file and added back to a +new session. For serialization and de-serialization of +add_torrent_params objects, see read_resume_data() and +write_resume_data().

    +
    +struct add_torrent_params
    +{
    +   add_torrent_params (add_torrent_params&&) noexcept;
    +   add_torrent_params& operator= (add_torrent_params&&) = default;
    +   add_torrent_params& operator= (add_torrent_params const&);
    +   explicit add_torrent_params (storage_constructor_type sc = default_storage_constructor);
    +   add_torrent_params (add_torrent_params const&);
    +
    +   int version  = LIBTORRENT_VERSION_NUM;
    +   std::shared_ptr<torrent_info> ti;
    +   aux::noexcept_movable<std::vector<std::string>> trackers;
    +   aux::noexcept_movable<std::vector<int>> tracker_tiers;
    +   aux::noexcept_movable<std::vector<std::pair<std::string, int>>> dht_nodes;
    +   std::string name;
    +   std::string save_path;
    +   storage_mode_t storage_mode  = storage_mode_sparse;
    +   aux::noexcept_movable<storage_constructor_type> storage;
    +   void* userdata  = nullptr;
    +   aux::noexcept_movable<std::vector<download_priority_t>> file_priorities;
    +   std::string trackerid;
    +   torrent_flags_t flags  = torrent_flags::default_flags;
    +   sha1_hash info_hash;
    +   int max_uploads  = -1;
    +   int max_connections  = -1;
    +   int upload_limit  = -1;
    +   int download_limit  = -1;
    +   std::int64_t total_uploaded  = 0;
    +   std::int64_t total_downloaded  = 0;
    +   int active_time  = 0;
    +   int finished_time  = 0;
    +   int seeding_time  = 0;
    +   std::time_t added_time  = 0;
    +   std::time_t completed_time  = 0;
    +   std::time_t last_seen_complete  = 0;
    +   int num_complete  = -1;
    +   int num_incomplete  = -1;
    +   int num_downloaded  = -1;
    +   aux::noexcept_movable<std::vector<std::string>> http_seeds;
    +   aux::noexcept_movable<std::vector<std::string>> url_seeds;
    +   aux::noexcept_movable<std::vector<tcp::endpoint>> peers;
    +   aux::noexcept_movable<std::vector<tcp::endpoint>> banned_peers;
    +   aux::noexcept_movable<std::map<piece_index_t, bitfield>> unfinished_pieces;
    +   typed_bitfield<piece_index_t> have_pieces;
    +   typed_bitfield<piece_index_t> verified_pieces;
    +   aux::noexcept_movable<std::vector<download_priority_t>> piece_priorities;
    +   aux::noexcept_movable<std::vector<sha1_hash>> merkle_tree;
    +   aux::noexcept_movable<std::map<file_index_t, std::string>> renamed_files;
    +   std::time_t last_download  = 0;
    +   std::time_t last_upload  = 0;
    +};
    +
    + +[report issue]
    +

    add_torrent_params() operator=()

    +
    +add_torrent_params (add_torrent_params&&) noexcept;
    +add_torrent_params& operator= (add_torrent_params&&) = default;
    +add_torrent_params& operator= (add_torrent_params const&);
    +explicit add_torrent_params (storage_constructor_type sc = default_storage_constructor);
    +add_torrent_params (add_torrent_params const&);
    +
    +

    The constructor can be used to initialize the storage constructor, +which determines the storage mechanism for the downloaded or seeding +data for the torrent. For more information, see the storage field.

    +[report issue]
    +
    version
    +
    filled in by the constructor and should be left untouched. It is used +for forward binary compatibility.
    +
    +[report issue]
    +
    ti
    +
    torrent_info object with the torrent to add. Unless the +info_hash is set, this is required to be initialized.
    +
    +[report issue]
    +
    trackers
    +
    If the torrent doesn't have a tracker, but relies on the DHT to find +peers, the trackers can specify tracker URLs for the torrent.
    +
    +[report issue]
    +
    tracker_tiers
    +
    the tiers the URLs in trackers belong to. Trackers belonging to +different tiers may be treated differently, as defined by the multi +tracker extension. This is optional, if not specified trackers are +assumed to be part of tier 0, or whichever the last tier was as +iterating over the trackers.
    +
    +[report issue]
    +
    dht_nodes
    +
    a list of hostname and port pairs, representing DHT nodes to be added +to the session (if DHT is enabled). The hostname may be an IP address.
    +
    +[report issue]
    +
    name
    +
    in case there's no other name in this torrent, this name will be used. +The name out of the torrent_info object takes precedence if available.
    +
    +[report issue]
    +
    save_path
    +

    the path where the torrent is or will be stored.

    +
    +

    Note

    +

    On windows this path (and other paths) are interpreted as UNC +paths. This means they must use backslashes as directory separators +and may not contain the special directories "." or "..".

    +
    +

    Setting this to an absolute path performs slightly better than a +relative path.

    +
    +
    +[report issue]
    +
    storage_mode
    +
    One of the values from storage_mode_t. For more information, see +storage allocation.
    +
    +[report issue]
    +
    storage
    +
    can be used to customize how the data is stored. The default storage +will simply write the data to the files it belongs to, but it could be +overridden to save everything to a single file at a specific location +or encrypt the content on disk for instance. For more information +about the storage_interface that needs to be implemented for a custom +storage, see storage_interface.
    +
    +[report issue]
    +
    userdata
    +
    The userdata parameter is optional and will be passed on to the +extension constructor functions, if any +(see torrent_handle::add_extension()).
    +
    +[report issue]
    +
    file_priorities
    +
    can be set to control the initial file priorities when adding a +torrent. The semantics are the same as for +torrent_handle::prioritize_files(). The file priorities specified +in here take precedence over those specified in the resume data, if +any.
    +
    +[report issue]
    +
    trackerid
    +
    the default tracker id to be used when announcing to trackers. By +default this is empty, and no tracker ID is used, since this is an +optional argument. If a tracker returns a tracker ID, that ID is used +instead of this.
    +
    +[report issue]
    +
    flags
    +

    flags controlling aspects of this torrent and how it's added. See +torrent_flags_t for details.

    +
    +

    Note

    +

    The flags field is initialized with default flags by the +constructor. In order to preserve default behavior when clearing or +setting other flags, make sure to bitwise OR or in a flag or bitwise +AND the inverse of a flag to clear it.

    +
    +
    +
    +[report issue]
    +
    info_hash
    +
    set this to the info hash of the torrent to add in case the info-hash +is the only known property of the torrent. i.e. you don't have a +.torrent file nor a magnet link. +To add a magnet link, use parse_magnet_uri() to populate fields in the +add_torrent_params object.
    +
    + +[report issue]
    +
    max_uploads max_connections
    +

    max_uploads, max_connections, upload_limit, +download_limit correspond to the set_max_uploads(), +set_max_connections(), set_upload_limit() and +set_download_limit() functions on torrent_handle. These values let +you initialize these settings when the torrent is added, instead of +calling these functions immediately following adding it.

    +

    -1 means unlimited on these settings just like their counterpart +functions on torrent_handle

    +

    For fine grained control over rate limits, including making them apply +to local peers, see peer classes.

    +
    +
    + +[report issue]
    +
    upload_limit download_limit
    +
    the upload and download rate limits for this torrent, specified in +bytes per second. -1 means unlimited.
    +
    + +[report issue]
    +
    total_uploaded total_downloaded
    +
    the total number of bytes uploaded and downloaded by this torrent so +far.
    +
    + + +[report issue]
    +
    active_time finished_time seeding_time
    +
    the number of seconds this torrent has spent in started, finished and +seeding state so far, respectively.
    +
    + +[report issue]
    +
    added_time completed_time
    +
    if set to a non-zero value, this is the posix time of when this torrent +was first added, including previous runs/sessions. If set to zero, the +internal added_time will be set to the time of when add_torrent() is +called.
    +
    +[report issue]
    +
    last_seen_complete
    +
    if set to non-zero, initializes the time (expressed in posix time) when +we last saw a seed or peers that together formed a complete copy of the +torrent. If left set to zero, the internal counterpart to this field +will be updated when we see a seed or a distributed copies >= 1.0.
    +
    + + +[report issue]
    +
    num_complete num_incomplete num_downloaded
    +

    these field can be used to initialize the torrent's cached scrape data. +The scrape data is high level metadata about the current state of the +swarm, as returned by the tracker (either when announcing to it or by +sending a specific scrape request). num_complete is the number of +peers in the swarm that are seeds, or have every piece in the torrent. +num_incomplete is the number of peers in the swarm that do not have +every piece. num_downloaded is the number of times the torrent has +been downloaded (not initiated, but the number of times a download has +completed).

    +

    Leaving any of these values set to -1 indicates we don't know, or we +have not received any scrape data.

    +
    +
    + +[report issue]
    +
    http_seeds url_seeds
    +

    URLs can be added to these two lists to specify additional web +seeds to be used by the torrent. If the flag_override_web_seeds +is set, these will be the _only_ ones to be used. i.e. any web seeds +found in the .torrent file will be overridden.

    +

    http_seeds expects URLs to web servers implementing the original HTTP +seed specification BEP 17.

    +

    url_seeds expects URLs to regular web servers, aka "get right" style, +specified in BEP 19.

    +
    +
    +[report issue]
    +
    peers
    +
    peers to add to the torrent, to be tried to be connected to as +bittorrent peers.
    +
    +[report issue]
    +
    banned_peers
    +
    peers banned from this torrent. The will not be connected to
    +
    +[report issue]
    +
    unfinished_pieces
    +
    this is a map of partially downloaded piece. The key is the piece index +and the value is a bitfield where each bit represents a 16 kiB block. +A set bit means we have that block.
    +
    +[report issue]
    +
    have_pieces
    +
    this is a bitfield indicating which pieces we already have of this +torrent.
    +
    +[report issue]
    +
    verified_pieces
    +
    when in seed_mode, pieces with a set bit in this bitfield have been +verified to be valid. Other pieces will be verified the first time a +peer requests it.
    +
    +[report issue]
    +
    piece_priorities
    +
    this sets the priorities for each individual piece in the torrent. Each +element in the vector represent the piece with the same index. If you +set both file- and piece priorities, file priorities will take +precedence.
    +
    +[report issue]
    +
    merkle_tree
    +
    if this is a merkle tree torrent, and you're seeding, this field must +be set. It is all the hashes in the binary tree, with the root as the +first entry. See torrent_info::set_merkle_tree() for more info.
    +
    +[report issue]
    +
    renamed_files
    +
    this is a map of file indices in the torrent and new filenames to be +applied before the torrent is added.
    +
    + +[report issue]
    +
    last_download last_upload
    +
    the posix time of the last time payload was received or sent for this +torrent, respectively.
    +
    +[report issue]
    +
    +
    +

    peer_info

    +

    Declared in "libtorrent/peer_info.hpp"

    +

    holds information and statistics about one peer +that libtorrent is connected to

    +
    +struct peer_info
    +{
    +   enum connection_type_t
    +   {
    +      standard_bittorrent,
    +      web_seed,
    +      http_seed,
    +   };
    +
    +   std::string client;
    +   typed_bitfield<piece_index_t> pieces;
    +   std::int64_t total_download;
    +   std::int64_t total_upload;
    +   time_duration last_request;
    +   time_duration last_active;
    +   time_duration download_queue_time;
    +   static constexpr peer_flags_t interesting  = 0_bit;
    +   static constexpr peer_flags_t choked  = 1_bit;
    +   static constexpr peer_flags_t remote_interested  = 2_bit;
    +   static constexpr peer_flags_t remote_choked  = 3_bit;
    +   static constexpr peer_flags_t supports_extensions  = 4_bit;
    +   static constexpr peer_flags_t local_connection  = 5_bit;
    +   static constexpr peer_flags_t handshake  = 6_bit;
    +   static constexpr peer_flags_t connecting  = 7_bit;
    +   static constexpr peer_flags_t on_parole  = 9_bit;
    +   static constexpr peer_flags_t seed  = 10_bit;
    +   static constexpr peer_flags_t optimistic_unchoke  = 11_bit;
    +   static constexpr peer_flags_t snubbed  = 12_bit;
    +   static constexpr peer_flags_t upload_only  = 13_bit;
    +   static constexpr peer_flags_t endgame_mode  = 14_bit;
    +   static constexpr peer_flags_t holepunched  = 15_bit;
    +   static constexpr peer_flags_t i2p_socket  = 16_bit;
    +   static constexpr peer_flags_t utp_socket  = 17_bit;
    +   static constexpr peer_flags_t ssl_socket  = 18_bit;
    +   static constexpr peer_flags_t rc4_encrypted  = 19_bit;
    +   static constexpr peer_flags_t plaintext_encrypted  = 20_bit;
    +   peer_flags_t flags;
    +   static constexpr peer_source_flags_t tracker  = 0_bit;
    +   static constexpr peer_source_flags_t dht  = 1_bit;
    +   static constexpr peer_source_flags_t pex  = 2_bit;
    +   static constexpr peer_source_flags_t lsd  = 3_bit;
    +   static constexpr peer_source_flags_t resume_data  = 4_bit;
    +   static constexpr peer_source_flags_t incoming  = 5_bit;
    +   peer_source_flags_t source;
    +   int up_speed;
    +   int down_speed;
    +   int payload_up_speed;
    +   int payload_down_speed;
    +   peer_id pid;
    +   int queue_bytes;
    +   int request_timeout;
    +   int send_buffer_size;
    +   int used_send_buffer;
    +   int receive_buffer_size;
    +   int used_receive_buffer;
    +   int receive_buffer_watermark;
    +   int num_hashfails;
    +   int download_queue_length;
    +   int timed_out_requests;
    +   int busy_requests;
    +   int requests_in_buffer;
    +   int target_dl_queue_length;
    +   int upload_queue_length;
    +   int failcount;
    +   piece_index_t downloading_piece_index;
    +   int downloading_block_index;
    +   int downloading_progress;
    +   int downloading_total;
    +   int connection_type;
    +   int pending_disk_bytes;
    +   int pending_disk_read_bytes;
    +   int send_quota;
    +   int receive_quota;
    +   int rtt;
    +   int num_pieces;
    +   int download_rate_peak;
    +   int upload_rate_peak;
    +   float progress;
    +   int progress_ppm;
    +   int deprecated_estimated_reciprocation_rate;
    +   tcp::endpoint ip;
    +   tcp::endpoint local_endpoint;
    +   static constexpr bandwidth_state_flags_t bw_idle  = 0_bit;
    +   static constexpr bandwidth_state_flags_t bw_limit  = 1_bit;
    +   static constexpr bandwidth_state_flags_t bw_network  = 2_bit;
    +   static constexpr bandwidth_state_flags_t bw_disk  = 4_bit;
    +   bandwidth_state_flags_t read_state;
    +   bandwidth_state_flags_t write_state;
    +};
    +
    +[report issue]
    +

    enum connection_type_t

    +

    Declared in "libtorrent/peer_info.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    standard_bittorrent0Regular bittorrent connection
    web_seed1HTTP connection using the BEP 19 protocol
    http_seed2HTTP connection using the BEP 17 protocol
    +[report issue]
    +
    client
    +
    a string describing the software at the other end of the connection. +In some cases this information is not available, then it will contain +a string that may give away something about which software is running +in the other end. In the case of a web seed, the server type and +version will be a part of this string.
    +
    +[report issue]
    +
    pieces
    +
    a bitfield, with one bit per piece in the torrent. Each bit tells you +if the peer has that piece (if it's set to 1) or if the peer miss that +piece (set to 0).
    +
    + +[report issue]
    +
    total_download total_upload
    +
    the total number of bytes downloaded from and uploaded to this peer. +These numbers do not include the protocol chatter, but only the +payload data.
    +
    + +[report issue]
    +
    last_request last_active
    +
    the time since we last sent a request to this peer and since any +transfer occurred with this peer
    +
    +[report issue]
    +
    download_queue_time
    +
    the time until all blocks in the request queue will be downloaded
    +
    +[report issue]
    +
    interesting
    +
    we are interested in pieces from this peer.
    +
    +[report issue]
    +
    choked
    +
    we have choked this peer.
    +
    +[report issue]
    +
    remote_interested
    +
    the peer is interested in us
    +
    +[report issue]
    +
    remote_choked
    +
    the peer has choked us.
    +
    +[report issue]
    +
    supports_extensions
    +
    means that this peer supports the +extension protocol.
    +
    +[report issue]
    +
    local_connection
    +
    The connection was initiated by us, the peer has a +listen port open, and that port is the same as in the +address of this peer. If this flag is not set, this +peer connection was opened by this peer connecting to +us.
    +
    +[report issue]
    +
    handshake
    +
    The connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified.
    +
    +[report issue]
    +
    connecting
    +
    The connection is in a half-open state (i.e. it is +being connected).
    +
    +[report issue]
    +
    on_parole
    +
    The peer has participated in a piece that failed the +hash check, and is now "on parole", which means we're +only requesting whole pieces from this peer until +it either fails that piece or proves that it doesn't +send bad data.
    +
    +[report issue]
    +
    seed
    +
    This peer is a seed (it has all the pieces).
    +
    +[report issue]
    +
    optimistic_unchoke
    +
    This peer is subject to an optimistic unchoke. It has +been unchoked for a while to see if it might unchoke +us in return an earn an upload/unchoke slot. If it +doesn't within some period of time, it will be choked +and another peer will be optimistically unchoked.
    +
    +[report issue]
    +
    snubbed
    +
    This peer has recently failed to send a block within +the request timeout from when the request was sent. +We're currently picking one block at a time from this +peer.
    +
    +[report issue]
    +
    upload_only
    +
    This peer has either explicitly (with an extension) +or implicitly (by becoming a seed) told us that it +will not downloading anything more, regardless of +which pieces we have.
    +
    +[report issue]
    +
    endgame_mode
    +
    This means the last time this peer picket a piece, +it could not pick as many as it wanted because there +were not enough free ones. i.e. all pieces this peer +has were already requested from other peers.
    +
    +[report issue]
    +
    holepunched
    +
    This flag is set if the peer was in holepunch mode +when the connection succeeded. This typically only +happens if both peers are behind a NAT and the peers +connect via the NAT holepunch mechanism.
    +
    +[report issue]
    +
    i2p_socket
    +
    indicates that this socket is running on top of the +I2P transport.
    +
    +[report issue]
    +
    utp_socket
    +
    indicates that this socket is a uTP socket
    +
    +[report issue]
    +
    ssl_socket
    +
    indicates that this socket is running on top of an SSL +(TLS) channel
    +
    +[report issue]
    +
    rc4_encrypted
    +
    this connection is obfuscated with RC4
    +
    +[report issue]
    +
    plaintext_encrypted
    +
    the handshake of this connection was obfuscated +with a Diffie-Hellman exchange
    +
    +[report issue]
    +
    flags
    +
    tells you in which state the peer is in. It is set to +any combination of the peer_flags_t flags above.
    +
    +[report issue]
    +
    tracker
    +
    The peer was received from the tracker.
    +
    +[report issue]
    +
    dht
    +
    The peer was received from the kademlia DHT.
    +
    +[report issue]
    +
    pex
    +
    The peer was received from the peer exchange +extension.
    +
    +[report issue]
    +
    lsd
    +
    The peer was received from the local service +discovery (The peer is on the local network).
    +
    +[report issue]
    +
    resume_data
    +
    The peer was added from the fast resume data.
    +
    +[report issue]
    +
    incoming
    +
    we received an incoming connection from this peer
    +
    +[report issue]
    +
    source
    +
    a combination of flags describing from which sources this peer +was received. A combination of the peer_source_flags_t above.
    +
    + +[report issue]
    +
    up_speed down_speed
    +
    the current upload and download speed we have to and from this peer +(including any protocol messages). updated about once per second
    +
    + +[report issue]
    +
    payload_up_speed payload_down_speed
    +
    The transfer rates of payload data only updated about once per second
    +
    +[report issue]
    +
    pid
    +
    the peer's id as used in the bit torrent protocol. This id can be used +to extract 'fingerprints' from the peer. Sometimes it can tell you +which client the peer is using. See identify_client()_
    +
    +[report issue]
    +
    queue_bytes
    +
    the number of bytes we have requested from this peer, but not yet +received.
    +
    +[report issue]
    +
    request_timeout
    +
    the number of seconds until the current front piece request will time +out. This timeout can be adjusted through +settings_pack::request_timeout. +-1 means that there is not outstanding request.
    +
    + +[report issue]
    +
    send_buffer_size used_send_buffer
    +
    the number of bytes allocated +and used for the peer's send buffer, respectively.
    +
    + + +[report issue]
    +
    receive_buffer_size used_receive_buffer receive_buffer_watermark
    +
    the number of bytes +allocated and used as receive buffer, respectively.
    +
    +[report issue]
    +
    num_hashfails
    +
    the number of pieces this peer has participated in sending us that +turned out to fail the hash check.
    +
    +[report issue]
    +
    download_queue_length
    +
    this is the number of requests we have sent to this peer that we +haven't got a response for yet
    +
    +[report issue]
    +
    timed_out_requests
    +
    the number of block requests that have timed out, and are still in the +download queue
    +
    +[report issue]
    +
    busy_requests
    +
    the number of busy requests in the download queue. A busy request is a +request for a block we've also requested from a different peer
    +
    +[report issue]
    +
    requests_in_buffer
    +
    the number of requests messages that are currently in the send buffer +waiting to be sent.
    +
    +[report issue]
    +
    target_dl_queue_length
    +
    the number of requests that is tried to be maintained (this is +typically a function of download speed)
    +
    +[report issue]
    +
    upload_queue_length
    +
    the number of piece-requests we have received from this peer +that we haven't answered with a piece yet.
    +
    +[report issue]
    +
    failcount
    +
    the number of times this peer has "failed". i.e. failed to connect or +disconnected us. The failcount is decremented when we see this peer in +a tracker response or peer exchange message.
    +
    + + + +[report issue]
    +
    downloading_piece_index downloading_block_index downloading_progress downloading_total
    +
    You can know which piece, and which part of that piece, that is +currently being downloaded from a specific peer by looking at these +four members. downloading_piece_index is the index of the piece +that is currently being downloaded. This may be set to -1 if there's +currently no piece downloading from this peer. If it is >= 0, the +other three members are valid. downloading_block_index is the +index of the block (or sub-piece) that is being downloaded. +downloading_progress is the number of bytes of this block we have +received from the peer, and downloading_total is the total number +of bytes in this block.
    +
    +[report issue]
    +
    connection_type
    +
    the kind of connection this peer uses. See connection_type_t.
    +
    +[report issue]
    +
    pending_disk_bytes
    +
    the number of bytes this peer has pending in the disk-io thread. +Downloaded and waiting to be written to disk. This is what is capped +by settings_pack::max_queued_disk_bytes.
    +
    +[report issue]
    +
    pending_disk_read_bytes
    +
    number of outstanding bytes to read +from disk
    +
    + +[report issue]
    +
    send_quota receive_quota
    +
    the number of bytes this peer has been assigned to be allowed to send +and receive until it has to request more quota from the bandwidth +manager.
    +
    +[report issue]
    +
    rtt
    +
    an estimated round trip time to this peer, in milliseconds. It is +estimated by timing the TCP connect(). It may be 0 for +incoming connections.
    +
    +[report issue]
    +
    num_pieces
    +
    the number of pieces this peer has.
    +
    + +[report issue]
    +
    download_rate_peak upload_rate_peak
    +
    the highest download and upload rates seen on this connection. They +are given in bytes per second. This number is reset to 0 on reconnect.
    +
    +[report issue]
    +
    progress
    +
    the progress of the peer in the range [0, 1]. This is always 0 when +floating point operations are disabled, instead use progress_ppm.
    +
    +[report issue]
    +
    progress_ppm
    +
    indicates the download progress of the peer in the range [0, 1000000] +(parts per million).
    +
    +[report issue]
    +
    ip
    +
    the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio documentation.
    +
    +[report issue]
    +
    local_endpoint
    +
    the IP and port pair the socket is bound to locally. i.e. the IP +address of the interface it's going out over. This may be useful for +multi-homed clients with multiple interfaces to the internet.
    +
    +[report issue]
    +
    bw_idle
    +
    The peer is not waiting for any external events to +send or receive data.
    +
    +[report issue]
    +
    bw_limit
    +
    The peer is waiting for the rate limiter.
    +
    +[report issue]
    +
    bw_network
    +
    The peer has quota and is currently waiting for a +network read or write operation to complete. This is +the state all peers are in if there are no bandwidth +limits.
    +
    +[report issue]
    +
    bw_disk
    +
    The peer is waiting for the disk I/O thread to catch +up writing buffers to disk before downloading more.
    +
    + +[report issue]
    +
    read_state write_state
    +
    bitmasks indicating what state this peer +is in with regards to sending and receiving data. The states are +defined as independent flags of type bandwidth_state_flags_t, in this +class.
    +
    +[report issue]
    +
    +
    +

    peer_request

    +

    Declared in "libtorrent/peer_request.hpp"

    +

    represents a byte range within a piece. Internally this is +is used for incoming piece requests.

    +
    +struct peer_request
    +{
    +   bool operator== (peer_request const& r) const;
    +
    +   piece_index_t piece;
    +   int start;
    +   int length;
    +};
    +
    +[report issue]
    +

    operator==()

    +
    +bool operator== (peer_request const& r) const;
    +
    +

    returns true if the right hand side peer_request refers to the same +range as this does.

    +[report issue]
    +
    piece
    +
    the index of the piece in which the range starts.
    +
    +[report issue]
    +
    start
    +
    the offset within that piece where the range starts.
    +
    +[report issue]
    +
    length
    +
    the size of the range, in bytes.
    +
    + +[report issue]
    +
    +
    +

    write_resume_data() write_resume_data_buf()

    +

    Declared in "libtorrent/write_resume_data.hpp"

    +
    +std::vector<char> write_resume_data_buf (add_torrent_params const& atp);
    +entry write_resume_data (add_torrent_params const& atp);
    +
    +

    this function turns the resume data in an add_torrent_params object +into a bencoded structure

    +[report issue]
    +
    +

    make_magnet_uri()

    +

    Declared in "libtorrent/magnet_uri.hpp"

    +
    +std::string make_magnet_uri (torrent_info const& info);
    +std::string make_magnet_uri (torrent_handle const& handle);
    +
    +

    Generates a magnet URI from the specified torrent. If the torrent +handle is invalid, an empty string is returned.

    +

    For more information about magnet links, see magnet links.

    +[report issue]
    +
    +

    parse_magnet_uri()

    +

    Declared in "libtorrent/magnet_uri.hpp"

    +
    +void parse_magnet_uri (string_view uri, add_torrent_params& p, error_code& ec);
    +add_torrent_params parse_magnet_uri (string_view uri);
    +add_torrent_params parse_magnet_uri (string_view uri, error_code& ec);
    +
    +

    This function parses out information from the magnet link and populates the +add_torrent_params object. The overload that does not take an +error_code reference will throw a system_error on error +The overload taking an add_torrent_params reference will fill in the +fields specified in the magnet URI.

    +[report issue]
    +
    +

    version()

    +

    Declared in "libtorrent/version.hpp"

    +
    +char const* version ();
    +
    +

    returns the libtorrent version as string form in this format: +"<major>.<minor>.<tiny>.<tag>"

    +[report issue]
    +
    +

    generate_fingerprint()

    +

    Declared in "libtorrent/fingerprint.hpp"

    +
    +std::string generate_fingerprint (std::string name
    +   , int major, int minor = 0, int revision = 0, int tag = 0);
    +
    +

    This is a utility function to produce a client ID fingerprint formatted to +the most common convention. The fingerprint can be set via the +peer_fingerprint setting, in settings_pack.

    +

    The name string should contain exactly two characters. These are the +characters unique to your client, used to identify it. Make sure not to +clash with anybody else. Here are some taken id's:

    + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    id charsclient
    LTlibtorrent (default)
    UTuTorrent
    UMuTorrent Mac
    qBqBittorrent
    BPBitTorrent Pro
    BTBitTorrent
    DEDeluge
    AZAzureus
    TLTribler
    +

    There's an informal directory of client id's here.

    +

    The major, minor, revision and tag parameters are used to +identify the version of your client.

    +[report issue]
    +
    +

    hash_value()

    +

    Declared in "libtorrent/torrent_handle.hpp"

    +
    +std::size_t hash_value (torrent_handle const& h);
    +
    +

    for std::hash (and to support using this type in unordered_map etc.)

    +[report issue]
    +
    +

    is_utp_stream_logging()

    +

    Declared in "libtorrent/utp_stream.hpp"

    +
    +bool is_utp_stream_logging ();
    +
    +[report issue]
    +
    +

    set_utp_stream_logging()

    +

    Declared in "libtorrent/utp_stream.hpp"

    +
    +void set_utp_stream_logging (bool enable);
    +
    +

    This function should be used at the very beginning and very end of your program.

    +[report issue]
    +
    +

    read_resume_data()

    +

    Declared in "libtorrent/read_resume_data.hpp"

    +
    +add_torrent_params read_resume_data (span<char const> buffer);
    +add_torrent_params read_resume_data (span<char const> buffer
    +   , error_code& ec);
    +add_torrent_params read_resume_data (bdecode_node const& rd
    +   , error_code& ec);
    +add_torrent_params read_resume_data (bdecode_node const& rd);
    +
    +

    these functions are used to parse resume data and populate the appropriate +fields in an add_torrent_params object. This object can then be used to add +the actual torrent_info object to and pass to session::add_torrent() or +session::async_add_torrent().

    +

    If the client wants to override any field that was loaded from the resume +data, e.g. save_path, those fields must be changed after loading resume +data but before adding the torrent.

    +[report issue]
    +
    +

    enum connection_type

    +

    Declared in "libtorrent/peer_connection.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    bittorrent0 
    url_seed1 
    http_seed2 
    +[report issue]
    +
    +

    enum portmap_transport

    +

    Declared in "libtorrent/portmap.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    natpmp0natpmp can be NAT-PMP or PCP
    upnp1 
    +[report issue]
    +
    +

    enum portmap_protocol

    +

    Declared in "libtorrent/portmap.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0 
    tcp1 
    udp2 
    +[report issue]
    +
    +

    torrent_flags_t

    +

    Declared in "libtorrent/torrent_flags.hpp"

    +
    +
    seed_mode
    +

    If seed_mode is set, libtorrent will assume that all files +are present for this torrent and that they all match the hashes in +the torrent file. Each time a peer requests to download a block, +the piece is verified against the hash, unless it has been verified +already. If a hash fails, the torrent will automatically leave the +seed mode and recheck all the files. The use case for this mode is +if a torrent is created and seeded, or if the user already know +that the files are complete, this is a way to avoid the initial +file checks, and significantly reduce the startup time.

    +

    Setting seed_mode on a torrent without metadata (a +.torrent file) is a no-op and will be ignored.

    +

    It is not possible to set the seed_mode flag on a torrent after it has +been added to a session. It is possible to clear it though.

    +
    +
    +
    +
    upload_mode
    +

    If upload_mode is set, the torrent will be initialized in +upload-mode, which means it will not make any piece requests. This +state is typically entered on disk I/O errors, and if the torrent +is also auto managed, it will be taken out of this state +periodically (see settings_pack::optimistic_disk_retry).

    +

    This mode can be used to avoid race conditions when +adjusting priorities of pieces before allowing the torrent to start +downloading.

    +

    If the torrent is auto-managed (auto_managed), the torrent +will eventually be taken out of upload-mode, regardless of how it +got there. If it's important to manually control when the torrent +leaves upload mode, don't make it auto managed.

    +
    +
    +
    +
    share_mode
    +

    determines if the torrent should be added in share mode or not. +Share mode indicates that we are not interested in downloading the +torrent, but merely want to improve our share ratio (i.e. increase +it). A torrent started in share mode will do its best to never +download more than it uploads to the swarm. If the swarm does not +have enough demand for upload capacity, the torrent will not +download anything. This mode is intended to be safe to add any +number of torrents to, without manual screening, without the risk +of downloading more than is uploaded.

    +

    A torrent in share mode sets the priority to all pieces to 0, +except for the pieces that are downloaded, when pieces are decided +to be downloaded. This affects the progress bar, which might be set +to "100% finished" most of the time. Do not change file or piece +priorities for torrents in share mode, it will make it not work.

    +

    The share mode has one setting, the share ratio target, see +settings_pack::share_mode_target for more info.

    +
    +
    +
    +
    apply_ip_filter
    +
    determines if the IP filter should apply to this torrent or not. By +default all torrents are subject to filtering by the IP filter +(i.e. this flag is set by default). This is useful if certain +torrents needs to be exempt for some reason, being an auto-update +torrent for instance.
    +
    +
    +
    paused
    +
    specifies whether or not the torrent is paused. i.e. it won't connect to the tracker or any of the peers +until it's resumed. Note that a paused torrent that also has the +auto_managed flag set can be started at any time by libtorrent's queuing +logic. See queuing.
    +
    +
    +
    auto_managed
    +

    If the torrent is auto-managed (auto_managed), the torrent +may be resumed at any point, regardless of how it paused. If it's +important to manually control when the torrent is paused and +resumed, don't make it auto managed.

    +

    If auto_managed is set, the torrent will be queued, +started and seeded automatically by libtorrent. When this is set, +the torrent should also be started as paused. The default queue +order is the order the torrents were added. They are all downloaded +in that order. For more details, see queuing.

    +
    +
    +
    +
    duplicate_is_error
    +
    used in add_torrent_params to indicate that it's an error to attempt +to add a torrent that's already in the session. If it's not considered an +error, a handle to the existing torrent is returned. +This flag is not saved by write_resume_data(), since it is only meant for +adding torrents.
    +
    +
    +
    update_subscribe
    +
    on by default and means that this torrent will be part of state +updates when calling post_torrent_updates(). +This flag is not saved by write_resume_data().
    +
    +
    +
    super_seeding
    +
    sets the torrent into super seeding/initial seeding mode. If the torrent +is not a seed, this flag has no effect.
    +
    +
    +
    sequential_download
    +
    sets the sequential download state for the torrent. In this mode the +piece picker will pick pieces with low index numbers before pieces with +high indices. The actual pieces that are picked depend on other factors +still, such as which pieces a peer has and whether it is in parole mode +or "prefer whole pieces"-mode. Sequential mode is not ideal for streaming +media. For that, see set_piece_deadline() instead.
    +
    +
    +
    stop_when_ready
    +

    When this flag is set, the torrent will force stop whenever it +transitions from a non-data-transferring state into a data-transferring +state (referred to as being ready to download or seed). This is useful +for torrents that should not start downloading or seeding yet, but want +to be made ready to do so. A torrent may need to have its files checked +for instance, so it needs to be started and possibly queued for checking +(auto-managed and started) but as soon as it's done, it should be +stopped.

    +

    Force stopped means auto-managed is set to false and it's paused. As +if the auto_manages flag is cleared and the paused flag is set on the torrent.

    +

    Note that the torrent may transition into a downloading state while +setting this flag, and since the logic is edge triggered you may +miss the edge. To avoid this race, if the torrent already is in a +downloading state when this call is made, it will trigger the +stop-when-ready immediately.

    +

    When the stop-when-ready logic fires, the flag is cleared. Any +subsequent transitions between downloading and non-downloading states +will not be affected, until this flag is set again.

    +

    The behavior is more robust when setting this flag as part of adding +the torrent. See add_torrent_params.

    +

    The stop-when-ready flag fixes the inherent race condition of waiting +for the state_changed_alert and then call pause(). The download/seeding +will most likely start in between posting the alert and receiving the +call to pause.

    +

    A downloading state is one where peers are being connected. Which means +just downloading the metadata via the ut_metadata extension counts +as a downloading state. In order to stop a torrent once the metadata +has been downloaded, instead set all file priorities to dont_download

    +
    +
    +
    +
    override_trackers
    +
    when this flag is set, the tracker list in the add_torrent_params +object override any trackers from the torrent file. If the flag is +not set, the trackers from the add_torrent_params object will be +added to the list of trackers used by the torrent. +This flag is set by read_resume_data() if there are trackers present in +the resume data file. This effectively makes the trackers saved in the +resume data take precedence over the original trackers. This includes if +there's an empty list of trackers, to support the case where they were +explicitly removed in the previous session. +This flag is not saved by write_resume_data()
    +
    +
    +
    override_web_seeds
    +
    If this flag is set, the web seeds from the add_torrent_params +object will override any web seeds in the torrent file. If it's not +set, web seeds in the add_torrent_params object will be added to the +list of web seeds used by the torrent. +This flag is set by read_resume_data() if there are web seeds present in +the resume data file. This effectively makes the web seeds saved in the +resume data take precedence over the original ones. This includes if +there's an empty list of web seeds, to support the case where they were +explicitly removed in the previous session. +This flag is not saved by write_resume_data()
    +
    +
    +
    need_save_resume
    +
    if this flag is set (which it is by default) the torrent will be +considered needing to save its resume data immediately as it's +added. New torrents that don't have any resume data should do that. +This flag is cleared by a successful call to save_resume_data() +This flag is not saved by write_resume_data(), since it represents an +ephemeral state of a running torrent.
    +
    +
    +
    disable_dht
    +
    set this flag to disable DHT for this torrent. This lets you have the DHT +enabled for the whole client, and still have specific torrents not +participating in it. i.e. not announcing to the DHT nor picking up peers +from it.
    +
    +
    +
    disable_lsd
    +
    set this flag to disable local service discovery for this torrent.
    +
    +
    +
    disable_pex
    +
    set this flag to disable peer exchange for this torrent.
    +
    +
    +
    all
    +
    all torrent flags combined. Can conveniently be used when creating masks +for flags
    +
    +[report issue]
    +
    +

    download_priority_t

    +

    Declared in "libtorrent/download_priority.hpp"

    +
    +
    dont_download
    +
    Don't download the file or piece. Partial pieces may still be downloaded when +setting file priorities.
    +
    +
    +
    default_priority
    +
    The default priority for files and pieces.
    +
    +
    +
    low_priority
    +
    The lowest priority for files and pieces.
    +
    +
    +
    top_priority
    +
    The highest priority for files and pieces.
    +
    +[report issue]
    +
    +

    pex_flags_t

    +

    Declared in "libtorrent/pex_flags.hpp"

    +
    +
    pex_encryption
    +
    the peer supports protocol encryption
    +
    +
    +
    pex_seed
    +
    the peer is a seed
    +
    +
    +
    pex_utp
    +
    the peer supports the uTP, transport protocol over UDP.
    +
    +
    +
    pex_holepunch
    +
    the peer supports the holepunch extension If this flag is received from a +peer, it can be used as a rendezvous point in case direct connections to +the peer fail
    +
    +[report issue]
    +
    +

    file_open_mode_t

    +

    Declared in "libtorrent/disk_interface.hpp"

    +
    +
    read_only
    +
    open the file for reading only
    +
    +
    +
    write_only
    +
    open the file for writing only
    +
    +
    +
    read_write
    +
    open the file for reading and writing
    +
    +
    +
    rw_mask
    +
    the mask for the bits determining read or write mode
    +
    +
    +
    sparse
    +
    open the file in sparse mode (if supported by the +filesystem).
    +
    +
    +
    no_atime
    +
    don't update the access timestamps on the file (if +supported by the operating system and filesystem). +this generally improves disk performance.
    +
    +
    +
    random_access
    +
    open the file for random access. This disables read-ahead +logic
    +
    +[report issue]
    +
    +

    open_mode_t

    +

    Declared in "libtorrent/file.hpp"

    +
    +
    read_only
    +
    open the file for reading only
    +
    +
    +
    write_only
    +
    open the file for writing only
    +
    +
    +
    read_write
    +
    open the file for reading and writing
    +
    +
    +
    rw_mask
    +
    the mask for the bits making up the read-write mode.
    +
    +
    +
    sparse
    +
    open the file in sparse mode (if supported by the +filesystem).
    +
    +
    +
    no_atime
    +
    don't update the access timestamps on the file (if +supported by the operating system and filesystem). +this generally improves disk performance.
    +
    +
    +
    random_access
    +
    open the file for random access. This disables read-ahead +logic
    +
    +
    +
    no_cache
    +
    don't put any pressure on the OS disk cache +because of access to this file. We expect our +files to be fairly large, and there is already +a cache at the bittorrent block level. This +may improve overall system performance by +leaving running applications in the page cache
    +
    +
    +
    coalesce_buffers
    +
    this is only used for readv/writev flags
    +
    +
    +
    attribute_hidden
    +
    when creating a file, set the hidden attribute (windows only)
    +
    +
    +
    attribute_executable
    +
    when creating a file, set the executable attribute
    +
    +
    +
    attribute_mask
    +
    the mask of all attribute bits
    +
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    DHT

    +[report issue]
    +

    dht_settings

    +

    Declared in "libtorrent/kademlia/dht_settings.hpp"

    +

    structure used to hold configuration options for the DHT

    +

    The dht_settings struct used to contain a service_port member to +control which port the DHT would listen on and send messages from. This +field is deprecated and ignored. libtorrent always tries to open the UDP +socket on the same port as the TCP socket.

    +
    +struct dht_settings
    +{
    +   int max_peers_reply  = 100;
    +   int search_branching  = 5;
    +   int max_fail_count  = 20;
    +   int max_torrents  = 2000;
    +   int max_dht_items  = 700;
    +   int max_peers  = 500;
    +   int max_torrent_search_reply  = 20;
    +   bool restrict_routing_ips  = true;
    +   bool restrict_search_ips  = true;
    +   bool extended_routing_table  = true;
    +   bool aggressive_lookups  = true;
    +   bool privacy_lookups  = false;
    +   bool enforce_node_id  = false;
    +   bool ignore_dark_internet  = true;
    +   int block_timeout  = 5 * 60;
    +   int block_ratelimit  = 5;
    +   bool read_only  = false;
    +   int item_lifetime  = 0;
    +   int upload_rate_limit  = 8000;
    +   int sample_infohashes_interval  = 21600;
    +   int max_infohashes_sample_count  = 20;
    +};
    +
    +[report issue]
    +
    max_peers_reply
    +
    the maximum number of peers to send in a reply to get_peers
    +
    +[report issue]
    +
    search_branching
    +
    the number of concurrent search request the node will send when +announcing and refreshing the routing table. This parameter is called +alpha in the kademlia paper
    +
    +[report issue]
    +
    max_fail_count
    +
    the maximum number of failed tries to contact a node before it is +removed from the routing table. If there are known working nodes that +are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node +that can replace them.
    +
    +[report issue]
    +
    max_torrents
    +
    the total number of torrents to track from the DHT. This is simply an +upper limit to make sure malicious DHT nodes cannot make us allocate +an unbounded amount of memory.
    +
    +[report issue]
    +
    max_dht_items
    +
    max number of items the DHT will store
    +
    +[report issue]
    +
    max_peers
    +
    the max number of peers to store per torrent (for the DHT)
    +
    +[report issue]
    +
    max_torrent_search_reply
    +
    the max number of torrents to return in a torrent search query to the +DHT
    +
    +[report issue]
    +
    restrict_routing_ips
    +

    determines if the routing table entries should restrict entries to one +per IP. This defaults to true, which helps mitigate some attacks on +the DHT. It prevents adding multiple nodes with IPs with a very close +CIDR distance.

    +

    when set, nodes whose IP address that's in the same /24 (or /64 for +IPv6) range in the same routing table bucket. This is an attempt to +mitigate node ID spoofing attacks also restrict any IP to only have a +single entry in the whole routing table

    +
    +
    +[report issue]
    +
    restrict_search_ips
    +
    determines if DHT searches should prevent adding nodes with IPs with +very close CIDR distance. This also defaults to true and helps +mitigate certain attacks on the DHT.
    +
    +[report issue]
    +
    extended_routing_table
    +
    makes the first buckets in the DHT routing table fit 128, 64, 32 and +16 nodes respectively, as opposed to the standard size of 8. All other +buckets have size 8 still.
    +
    +[report issue]
    +
    aggressive_lookups
    +
    slightly changes the lookup behavior in terms of how many outstanding +requests we keep. Instead of having branch factor be a hard limit, we +always keep branch factor outstanding requests to the closest nodes. +i.e. every time we get results back with closer nodes, we query them +right away. It lowers the lookup times at the cost of more outstanding +queries.
    +
    +[report issue]
    +
    privacy_lookups
    +
    when set, perform lookups in a way that is slightly more expensive, +but which minimizes the amount of information leaked about you.
    +
    +[report issue]
    +
    enforce_node_id
    +
    when set, node's whose IDs that are not correctly generated based on +its external IP are ignored. When a query arrives from such node, an +error message is returned with a message saying "invalid node ID".
    +
    +[report issue]
    +
    ignore_dark_internet
    +
    ignore DHT messages from parts of the internet we wouldn't expect to +see any traffic from
    +
    +[report issue]
    +
    block_timeout
    +
    the number of seconds a DHT node is banned if it exceeds the rate +limit. The rate limit is averaged over 10 seconds to allow for bursts +above the limit.
    +
    +[report issue]
    +
    block_ratelimit
    +
    the max number of packets per second a DHT node is allowed to send +without getting banned.
    +
    +[report issue]
    +
    read_only
    +
    when set, the other nodes won't keep this node in their routing +tables, it's meant for low-power and/or ephemeral devices that +cannot support the DHT, it is also useful for mobile devices which +are sensitive to network traffic and battery life. +this node no longer responds to 'query' messages, and will place a +'ro' key (value = 1) in the top-level message dictionary of outgoing +query messages.
    +
    +[report issue]
    +
    item_lifetime
    +
    the number of seconds a immutable/mutable item will be expired. +default is 0, means never expires.
    +
    +[report issue]
    +
    upload_rate_limit
    +
    the number of bytes per second (on average) the DHT is allowed to send. +If the incoming requests causes to many bytes to be sent in responses, +incoming requests will be dropped until the quota has been replenished.
    +
    +[report issue]
    +
    sample_infohashes_interval
    +
    the info-hashes sample recomputation interval (in seconds). +The node will precompute a subset of the tracked info-hashes and return +that instead of calculating it upon each request. The permissible range +is between 0 and 21600 seconds (inclusive).
    +
    +[report issue]
    +
    max_infohashes_sample_count
    +
    the maximum number of elements in the sampled subset of info-hashes. +If this number is too big, expect the DHT storage implementations +to clamp it in order to allow UDP packets go through
    +
    +[report issue]
    +
    +

    dht_storage_counters

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +

    This structure hold the relevant counters for the storage

    +
    +struct dht_storage_counters
    +{
    +   void reset ();
    +
    +   std::int32_t torrents  = 0;
    +   std::int32_t peers  = 0;
    +   std::int32_t immutable_data  = 0;
    +   std::int32_t mutable_data  = 0;
    +};
    +
    +[report issue]
    +

    reset()

    +
    +void reset ();
    +
    +

    This member function set the counters to zero.

    +[report issue]
    +
    +
    +

    dht_storage_interface

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +

    The DHT storage interface is a pure virtual class that can +be implemented to customize how the data for the DHT is stored.

    +

    The default storage implementation uses three maps in RAM to save +the peers, mutable and immutable items and it's designed to +provide a fast and fully compliant behavior of the BEPs.

    +

    libtorrent comes with one built-in storage implementation: +dht_default_storage (private non-accessible class). Its +constructor function is called dht_default_storage_constructor(). +You should know that if this storage becomes full of DHT items, +the current implementation could degrade in performance.

    +
    +struct dht_storage_interface
    +{
    +   virtual void update_node_ids (std::vector<node_id> const& ids) = 0;
    +   virtual bool get_peers (sha1_hash const& info_hash
    +      , bool noseed, bool scrape, address const& requester
    +      , entry& peers) const = 0;
    +   virtual void announce_peer (sha1_hash const& info_hash
    +      , tcp::endpoint const& endp
    +      , string_view name, bool seed) = 0;
    +   virtual bool get_immutable_item (sha1_hash const& target
    +      , entry& item) const = 0;
    +   virtual void put_immutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , address const& addr) = 0;
    +   virtual bool get_mutable_item_seq (sha1_hash const& target
    +      , sequence_number& seq) const = 0;
    +   virtual bool get_mutable_item (sha1_hash const& target
    +      , sequence_number seq, bool force_fill
    +      , entry& item) const = 0;
    +   virtual void put_mutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , signature const& sig
    +      , sequence_number seq
    +      , public_key const& pk
    +      , span<char const> salt
    +      , address const& addr) = 0;
    +   virtual int get_infohashes_sample (entry& item) = 0;
    +   virtual void tick () = 0;
    +   virtual dht_storage_counters counters () const = 0;
    +};
    +
    +[report issue]
    +

    update_node_ids()

    +
    +virtual void update_node_ids (std::vector<node_id> const& ids) = 0;
    +
    +

    This member function notifies the list of all node's ids +of each DHT running inside libtorrent. It's advisable +that the concrete implementation keeps a copy of this list +for an eventual prioritization when deleting an element +to make room for a new one.

    +[report issue]
    +
    +

    get_peers()

    +
    +virtual bool get_peers (sha1_hash const& info_hash
    +      , bool noseed, bool scrape, address const& requester
    +      , entry& peers) const = 0;
    +
    +

    This function retrieve the peers tracked by the DHT +corresponding to the given info_hash. You can specify if +you want only seeds and/or you are scraping the data.

    +

    For future implementers: +If the torrent tracked contains a name, such a name +must be stored as a string in peers["n"]

    +

    If the scrape parameter is true, you should fill these keys:

    +
    +
    +
    peers["BFpe"]
    +
    with the standard bit representation of a +256 bloom filter containing the downloaders
    +
    peers["BFsd"]
    +
    with the standard bit representation of a +256 bloom filter containing the seeders
    +
    +
    +

    If the scrape parameter is false, you should fill the +key peers["values"] with a list containing a subset of +peers tracked by the given info_hash. Such a list should +consider the value of dht_settings::max_peers_reply. +If noseed is true only peers marked as no seed should be included.

    +

    returns true if the maximum number of peers are stored +for this info_hash.

    +[report issue]
    +
    +

    announce_peer()

    +
    +virtual void announce_peer (sha1_hash const& info_hash
    +      , tcp::endpoint const& endp
    +      , string_view name, bool seed) = 0;
    +
    +

    This function is named announce_peer for consistency with the +upper layers, but has nothing to do with networking. Its only +responsibility is store the peer in such a way that it's returned +in the entry with the lookup_peers.

    +

    The name parameter is the name of the torrent if provided in +the announce_peer DHT message. The length of this value should +have a maximum length in the final storage. The default +implementation truncate the value for a maximum of 50 characters.

    +[report issue]
    +
    +

    get_immutable_item()

    +
    +virtual bool get_immutable_item (sha1_hash const& target
    +      , entry& item) const = 0;
    +
    +

    This function retrieves the immutable item given its target hash.

    +

    For future implementers: +The value should be returned as an entry in the key item["v"].

    +

    returns true if the item is found and the data is returned +inside the (entry) out parameter item.

    +[report issue]
    +
    +

    put_immutable_item()

    +
    +virtual void put_immutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , address const& addr) = 0;
    +
    +

    Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer.

    +

    For implementers: +This data can be stored only if the target is not already +present. The implementation should consider the value of +dht_settings::max_dht_items.

    +[report issue]
    +
    +

    get_mutable_item_seq()

    +
    +virtual bool get_mutable_item_seq (sha1_hash const& target
    +      , sequence_number& seq) const = 0;
    +
    +

    This function retrieves the sequence number of a mutable item.

    +

    returns true if the item is found and the data is returned +inside the out parameter seq.

    +[report issue]
    +
    +

    get_mutable_item()

    +
    +virtual bool get_mutable_item (sha1_hash const& target
    +      , sequence_number seq, bool force_fill
    +      , entry& item) const = 0;
    +
    +

    This function retrieves the mutable stored in the DHT.

    +

    For implementers: +The item sequence should be stored in the key item["seq"]. +if force_fill is true or (0 <= seq and seq < item["seq"]) +the following keys should be filled +item["v"] - with the value no encoded. +item["sig"] - with a string representation of the signature. +item["k"] - with a string representation of the public key.

    +

    returns true if the item is found and the data is returned +inside the (entry) out parameter item.

    +[report issue]
    +
    +

    put_mutable_item()

    +
    +virtual void put_mutable_item (sha1_hash const& target
    +      , span<char const> buf
    +      , signature const& sig
    +      , sequence_number seq
    +      , public_key const& pk
    +      , span<char const> salt
    +      , address const& addr) = 0;
    +
    +

    Store the item's data. This layer is only for storage. +The authentication of the item is performed by the upper layer.

    +

    For implementers: +The sequence number should be checked if the item is already +present. The implementation should consider the value of +dht_settings::max_dht_items.

    +[report issue]
    +
    +

    get_infohashes_sample()

    +
    +virtual int get_infohashes_sample (entry& item) = 0;
    +
    +

    This function retrieves a sample info-hashes

    +

    For implementers: +The info-hashes should be stored in ["samples"] (N x 20 bytes). +the following keys should be filled +item["interval"] - the subset refresh interval in seconds. +item["num"] - number of info-hashes in storage.

    +

    Internally, this function is allowed to lazily evaluate, cache +and modify the actual sample to put in item

    +

    returns the number of info-hashes in the sample.

    +[report issue]
    +
    +

    tick()

    +
    +virtual void tick () = 0;
    +
    +

    This function is called periodically (non-constant frequency).

    +

    For implementers: +Use this functions for expire peers or items or any other +storage cleanup.

    +[report issue]
    +
    +

    counters()

    +
    +virtual dht_storage_counters counters () const = 0;
    +
    +

    return stats counters for the store

    +[report issue]
    +
    +
    +

    dht_state

    +

    Declared in "libtorrent/kademlia/dht_state.hpp"

    +

    This structure helps to store and load the state +of the dht_tracker. +At this moment the library is only a dual stack +implementation of the DHT. See BEP 32

    +
    +struct dht_state
    +{
    +   void clear ();
    +
    +   node_ids_t nids;
    +   std::vector<udp::endpoint> nodes;
    +   std::vector<udp::endpoint> nodes6;
    +};
    +
    +[report issue]
    +
    nodes
    +
    the bootstrap nodes saved from the buckets node
    +
    +[report issue]
    +
    nodes6
    +
    the bootstrap nodes saved from the IPv6 buckets node
    +
    +[report issue]
    +
    +

    dht_default_storage_constructor()

    +

    Declared in "libtorrent/kademlia/dht_storage.hpp"

    +
    +std::unique_ptr<dht_storage_interface> dht_default_storage_constructor (
    +   dht_settings const& settings);
    +
    +

    constructor for the default DHT storage. The DHT storage is responsible +for maintaining peers and mutable and immutable items announced and +stored/put to the DHT node.

    +[report issue]
    +
    +

    sign_mutable_item()

    +

    Declared in "libtorrent/kademlia/item.hpp"

    +
    +signature sign_mutable_item (
    +   span<char const> v
    +   , span<char const> salt
    +   , sequence_number seq
    +   , public_key const& pk
    +   , secret_key const& sk);
    +
    +

    given a byte range v and an optional byte range salt, a +sequence number, public key pk (must be 32 bytes) and a secret key +sk (must be 64 bytes), this function produces a signature which +is written into a 64 byte buffer pointed to by sig. The caller +is responsible for allocating the destination buffer that's passed in +as the sig argument. Typically it would be allocated on the stack.

    +[report issue]
    +
    +

    announce_flags_t

    +

    Declared in "libtorrent/kademlia/announce_flags.hpp"

    +
    +
    seed
    +
    announce to DHT as a seed
    +
    +
    +
    implied_port
    +
    announce to DHT with the implied-port flag set. This tells the network to use +your source UDP port as your listen port, rather than the one specified in +the message. This may improve the chances of traversing NATs when using uTP.
    +
    +
    +
    ssl_torrent
    +
    Specify the port number for the SSL listen socket in the DHT announce.
    +
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Session

    +[report issue]
    +

    stats_metric

    +

    Declared in "libtorrent/session_stats.hpp"

    +

    describes one statistics metric from the session. For more information, +see the session statistics section.

    +
    +struct stats_metric
    +{
    +   char const* name;
    +   int value_index;
    +   metric_type_t type;
    +};
    +
    +[report issue]
    +
    name
    +
    the name of the counter or gauge
    +
    + +[report issue]
    +
    value_index type
    +
    the index into the session stats array, where the underlying value of +this counter or gauge is found. The session stats array is part of the +session_stats_alert object.
    +
    +[report issue]
    +
    +

    session_handle

    +

    Declared in "libtorrent/session_handle.hpp"

    +

    this class provides a non-owning handle to a session and a subset of the +interface of the session class. If the underlying session is destructed +any handle to it will no longer be valid. is_valid() will return false and +any operation on it will throw a system_error exception, with error code +invalid_session_handle.

    +
    +struct session_handle
    +{
    +   bool is_valid () const;
    +   void load_state (bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all());
    +   void save_state (entry& e, save_state_flags_t flags = save_state_flags_t::all()) const;
    +   std::vector<torrent_status> get_torrent_status (
    +      std::function<bool(torrent_status const&)> const& pred
    +      , status_flags_t flags = {}) const;
    +   void refresh_torrent_status (std::vector<torrent_status>* ret
    +      , status_flags_t flags = {}) const;
    +   void post_torrent_updates (status_flags_t flags = status_flags_t::all());
    +   void post_session_stats ();
    +   void post_dht_stats ();
    +   std::vector<torrent_handle> get_torrents () const;
    +   torrent_handle find_torrent (sha1_hash const& info_hash) const;
    +   void async_add_torrent (add_torrent_params const& params);
    +   torrent_handle add_torrent (add_torrent_params const& params);
    +   torrent_handle add_torrent (add_torrent_params&& params);
    +   torrent_handle add_torrent (add_torrent_params&& params, error_code& ec);
    +   void async_add_torrent (add_torrent_params&& params);
    +   torrent_handle add_torrent (add_torrent_params const& params, error_code& ec);
    +   void resume ();
    +   bool is_paused () const;
    +   void pause ();
    +   void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    +   dht::dht_settings get_dht_settings () const;
    +   bool is_dht_running () const;
    +   void set_dht_settings (dht::dht_settings const& settings);
    +   void set_dht_storage (dht::dht_storage_constructor_type sc);
    +   void add_dht_node (std::pair<std::string, int> const& node);
    +   void dht_get_item (sha1_hash const& target);
    +   void dht_get_item (std::array<char, 32> key
    +      , std::string salt = std::string());
    +   sha1_hash dht_put_item (entry data);
    +   void dht_put_item (std::array<char, 32> key
    +      , std::function<void(entry&, std::array<char, 64>&
    +      , std::int64_t&, std::string const&)> cb
    +      , std::string salt = std::string());
    +   void dht_announce (sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {});
    +   void dht_get_peers (sha1_hash const& info_hash);
    +   void dht_live_nodes (sha1_hash const& nid);
    +   void dht_sample_infohashes (udp::endpoint const& ep, sha1_hash const& target);
    +   void dht_direct_request (udp::endpoint const& ep, entry const& e, void* userdata = nullptr);
    +   void add_extension (std::function<std::shared_ptr<torrent_plugin>(
    +      torrent_handle const&, void*)> ext);
    +   void add_extension (std::shared_ptr<plugin> ext);
    +   void set_ip_filter (ip_filter const& f);
    +   ip_filter get_ip_filter () const;
    +   void set_port_filter (port_filter const& f);
    +   unsigned short listen_port () const;
    +   bool is_listening () const;
    +   unsigned short ssl_listen_port () const;
    +   ip_filter get_peer_class_filter () const;
    +   void set_peer_class_filter (ip_filter const& f);
    +   void set_peer_class_type_filter (peer_class_type_filter const& f);
    +   peer_class_type_filter get_peer_class_type_filter () const;
    +   peer_class_t create_peer_class (char const* name);
    +   void delete_peer_class (peer_class_t cid);
    +   void set_peer_class (peer_class_t cid, peer_class_info const& pci);
    +   peer_class_info get_peer_class (peer_class_t cid) const;
    +   void remove_torrent (const torrent_handle& h, remove_flags_t options = {});
    +   void apply_settings (settings_pack const& s);
    +   settings_pack get_settings () const;
    +   void apply_settings (settings_pack&& s);
    +   void pop_alerts (std::vector<alert*>* alerts);
    +   alert* wait_for_alert (time_duration max_wait);
    +   void set_alert_notify (std::function<void()> const& fun);
    +   void delete_port_mapping (port_mapping_t handle);
    +   std::vector<port_mapping_t> add_port_mapping (portmap_protocol t, int external_port, int local_port);
    +   void reopen_network_sockets (reopen_network_flags_t options = reopen_map_ports);
    +   std::shared_ptr<aux::session_impl> native_handle () const;
    +
    +   static constexpr save_state_flags_t save_settings  = 0_bit;
    +   static constexpr save_state_flags_t save_dht_settings  = 1_bit;
    +   static constexpr save_state_flags_t save_dht_state  = 2_bit;
    +   static constexpr peer_class_t global_peer_class_id {0};
    +   static constexpr peer_class_t tcp_peer_class_id {1};
    +   static constexpr peer_class_t local_peer_class_id {2};
    +   static constexpr remove_flags_t delete_files  = 0_bit;
    +   static constexpr remove_flags_t delete_partfile  = 1_bit;
    +   static constexpr session_flags_t add_default_plugins  = 0_bit;
    +   static constexpr portmap_protocol udp  = portmap_protocol::udp;
    +   static constexpr portmap_protocol tcp  = portmap_protocol::tcp;
    +   static constexpr reopen_network_flags_t reopen_map_ports  = 0_bit;
    +};
    +
    +[report issue]
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if this handle refers to a valid session object. If the +session has been destroyed, all session_handle objects will expire and +not be valid.

    + +[report issue]
    +
    +

    save_state() load_state()

    +
    +void load_state (bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all());
    +void save_state (entry& e, save_state_flags_t flags = save_state_flags_t::all()) const;
    +
    +

    loads and saves all session settings, including dht_settings, +encryption settings and proxy settings. save_state writes all keys +to the entry that's passed in, which needs to either not be +initialized, or initialized as a dictionary.

    +

    load_state expects a bdecode_node which can be built from a bencoded +buffer with bdecode().

    +

    The flags argument is used to filter which parts of the session +state to save or load. By default, all state is saved/restored (except +for the individual torrents).

    +

    When saving settings, there are two fields that are not loaded. +peer_fingerprint and user_agent. Those are left as configured +by the session_settings passed to the session constructor or +subsequently set via apply_settings().

    + +[report issue]
    +
    +

    get_torrent_status() refresh_torrent_status()

    +
    +std::vector<torrent_status> get_torrent_status (
    +      std::function<bool(torrent_status const&)> const& pred
    +      , status_flags_t flags = {}) const;
    +void refresh_torrent_status (std::vector<torrent_status>* ret
    +      , status_flags_t flags = {}) const;
    +
    +
    +

    Note

    +

    these calls are potentially expensive and won't scale well with +lots of torrents. If you're concerned about performance, consider +using post_torrent_updates() instead.

    +
    +

    get_torrent_status returns a vector of the torrent_status for +every torrent which satisfies pred, which is a predicate function +which determines if a torrent should be included in the returned set +or not. Returning true means it should be included and false means +excluded. The flags argument is the same as to +torrent_handle::status(). Since pred is guaranteed to be +called for every torrent, it may be used to count the number of +torrents of different categories as well.

    +

    refresh_torrent_status takes a vector of torrent_status structs +(for instance the same vector that was returned by +get_torrent_status() ) and refreshes the status based on the +handle member. It is possible to use this function by first +setting up a vector of default constructed torrent_status objects, +only initializing the handle member, in order to request the +torrent status for multiple torrents in a single call. This can save a +significant amount of time if you have a lot of torrents.

    +

    Any torrent_status object whose handle member is not referring to +a valid torrent are ignored.

    +

    The intended use of these functions is to start off by calling +get_torrent_status() to get a list of all torrents that match your +criteria. Then call refresh_torrent_status() on that list. This +will only refresh the status for the torrents in your list, and thus +ignore all other torrents you might be running. This may save a +significant amount of time, especially if the number of torrents you're +interested in is small. In order to keep your list of interested +torrents up to date, you can either call get_torrent_status() from +time to time, to include torrents you might have become interested in +since the last time. In order to stop refreshing a certain torrent, +simply remove it from the list.

    +[report issue]
    +
    +

    post_torrent_updates()

    +
    +void post_torrent_updates (status_flags_t flags = status_flags_t::all());
    +
    +

    This functions instructs the session to post the state_update_alert, +containing the status of all torrents whose state changed since the +last time this function was called.

    +

    Only torrents who has the state subscription flag set will be +included. This flag is on by default. See add_torrent_params. +the flags argument is the same as for torrent_handle::status(). +see status_flags_t in torrent_handle.

    +[report issue]
    +
    +

    post_session_stats()

    +
    +void post_session_stats ();
    +
    +

    This function will post a session_stats_alert object, containing a +snapshot of the performance counters from the internals of libtorrent. +To interpret these counters, query the session via +session_stats_metrics().

    +

    For more information, see the session statistics section.

    +[report issue]
    +
    +

    post_dht_stats()

    +
    +void post_dht_stats ();
    +
    +

    This will cause a dht_stats_alert to be posted.

    + +[report issue]
    +
    +

    get_torrents() find_torrent()

    +
    +std::vector<torrent_handle> get_torrents () const;
    +torrent_handle find_torrent (sha1_hash const& info_hash) const;
    +
    +

    find_torrent() looks for a torrent with the given info-hash. In +case there is such a torrent in the session, a torrent_handle to that +torrent is returned. In case the torrent cannot be found, an invalid +torrent_handle is returned.

    +

    See torrent_handle::is_valid() to know if the torrent was found or +not.

    +

    get_torrents() returns a vector of torrent_handles to all the +torrents currently in the session.

    + +[report issue]
    +
    +

    add_torrent() async_add_torrent()

    +
    +void async_add_torrent (add_torrent_params const& params);
    +torrent_handle add_torrent (add_torrent_params const& params);
    +torrent_handle add_torrent (add_torrent_params&& params);
    +torrent_handle add_torrent (add_torrent_params&& params, error_code& ec);
    +void async_add_torrent (add_torrent_params&& params);
    +torrent_handle add_torrent (add_torrent_params const& params, error_code& ec);
    +
    +

    You add torrents through the add_torrent() function where you give an +object with all the parameters. The add_torrent() overloads will block +until the torrent has been added (or failed to be added) and returns +an error code and a torrent_handle. In order to add torrents more +efficiently, consider using async_add_torrent() which returns +immediately, without waiting for the torrent to add. Notification of +the torrent being added is sent as add_torrent_alert.

    +

    The overload that does not take an error_code throws an exception on +error and is not available when building without exception support. +The torrent_handle returned by add_torrent() can be used to retrieve +information about the torrent's progress, its peers etc. It is also +used to abort a torrent.

    +

    If the torrent you are trying to add already exists in the session (is +either queued for checking, being checked or downloading) +add_torrent() will throw system_error which derives from +std::exception unless duplicate_is_error is set to false. In that +case, add_torrent() will return the handle to the existing torrent.

    +

    The add_torrent_params class has a flags field. It can be used to +control what state the new torrent will be added in. Common flags to +want to control are torrent_flags::paused and +torrent_flags::auto_managed. In order to add a magnet link that will +just download the metadata, but no payload, set the +torrent_flags::upload_mode flag.

    + + +[report issue]
    +
    +

    pause() resume() is_paused()

    +
    +void resume ();
    +bool is_paused () const;
    +void pause ();
    +
    +

    Pausing the session has the same effect as pausing every torrent in +it, except that torrents will not be resumed by the auto-manage +mechanism. Resuming will restore the torrents to their previous paused +state. i.e. the session pause state is separate from the torrent pause +state. A torrent is inactive if it is paused or if the session is +paused.

    +[report issue]
    +
    +

    get_cache_info()

    +
    +void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    +
    +

    Fills in the cache_status struct with information about the given torrent. +If flags is session::disk_cache_no_pieces the cache_status::pieces field +will not be set. This may significantly reduce the cost of this call.

    + + +[report issue]
    +
    +

    is_dht_running() get_dht_settings() set_dht_settings()

    +
    +dht::dht_settings get_dht_settings () const;
    +bool is_dht_running () const;
    +void set_dht_settings (dht::dht_settings const& settings);
    +
    +

    set_dht_settings sets some parameters available to the dht node. +See dht_settings for more information.

    +

    is_dht_running() returns true if the DHT support has been started +and false +otherwise.

    +

    get_dht_settings() returns the current settings

    +[report issue]
    +
    +

    set_dht_storage()

    +
    +void set_dht_storage (dht::dht_storage_constructor_type sc);
    +
    +

    set_dht_storage set a dht custom storage constructor function +to be used internally when the dht is created.

    +

    Since the dht storage is a critical component for the dht behavior, +this function will only be effective the next time the dht is started. +If you never touch this feature, a default map-memory based storage +is used.

    +

    If you want to make sure the dht is initially created with your +custom storage, create a session with the setting +settings_pack::enable_dht to false, set your constructor function +and call apply_settings with settings_pack::enable_dht to true.

    +[report issue]
    +
    +

    add_dht_node()

    +
    +void add_dht_node (std::pair<std::string, int> const& node);
    +
    +

    add_dht_node takes a host name and port pair. That endpoint will be +pinged, and if a valid DHT reply is received, the node will be added to +the routing table.

    +[report issue]
    +
    +

    dht_get_item()

    +
    +void dht_get_item (sha1_hash const& target);
    +
    +

    query the DHT for an immutable item at the target hash. +the result is posted as a dht_immutable_item_alert.

    +[report issue]
    +
    +

    dht_get_item()

    +
    +void dht_get_item (std::array<char, 32> key
    +      , std::string salt = std::string());
    +
    +

    query the DHT for a mutable item under the public key key. +this is an ed25519 key. salt is optional and may be left +as an empty string if no salt is to be used. +if the item is found in the DHT, a dht_mutable_item_alert is +posted.

    +[report issue]
    +
    +

    dht_put_item()

    +
    +sha1_hash dht_put_item (entry data);
    +
    +

    store the given bencoded data as an immutable item in the DHT. +the returned hash is the key that is to be used to look the item +up again. It's just the SHA-1 hash of the bencoded form of the +structure.

    +[report issue]
    +
    +

    dht_put_item()

    +
    +void dht_put_item (std::array<char, 32> key
    +      , std::function<void(entry&, std::array<char, 64>&
    +      , std::int64_t&, std::string const&)> cb
    +      , std::string salt = std::string());
    +
    +

    store a mutable item. The key is the public key the blob is +to be stored under. The optional salt argument is a string that +is to be mixed in with the key when determining where in the DHT +the value is to be stored. The callback function is called from within +the libtorrent network thread once we've found where to store the blob, +possibly with the current value stored under the key. +The values passed to the callback functions are:

    +
    +
    entry& value
    +
    the current value stored under the key (may be empty). Also expected +to be set to the value to be stored by the function.
    +
    std::array<char,64>& signature
    +
    the signature authenticating the current value. This may be zeros +if there is currently no value stored. The function is expected to +fill in this buffer with the signature of the new value to store. +To generate the signature, you may want to use the +sign_mutable_item function.
    +
    std::int64_t& seq
    +
    current sequence number. May be zero if there is no current value. +The function is expected to set this to the new sequence number of +the value that is to be stored. Sequence numbers must be monotonically +increasing. Attempting to overwrite a value with a lower or equal +sequence number will fail, even if the signature is correct.
    +
    std::string const& salt
    +
    this is the salt that was used for this put call.
    +
    +

    Since the callback function cb is called from within libtorrent, +it is critical to not perform any blocking operations. Ideally not +even locking a mutex. Pass any data required for this function along +with the function object's context and make the function entirely +self-contained. The only reason data blob's value is computed +via a function instead of just passing in the new value is to avoid +race conditions. If you want to update the value in the DHT, you +must first retrieve it, then modify it, then write it back. The way +the DHT works, it is natural to always do a lookup before storing and +calling the callback in between is convenient.

    + +[report issue]
    +
    +

    dht_get_peers() dht_announce()

    +
    +void dht_announce (sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {});
    +void dht_get_peers (sha1_hash const& info_hash);
    +
    +

    dht_get_peers() will issue a DHT get_peer request to the DHT for the +specified info-hash. The response (the peers) will be posted back in a +dht_get_peers_reply_alert.

    +

    dht_announce() will issue a DHT announce request to the DHT to the +specified info-hash, advertising the specified port. If the port is +left at its default, 0, the port will be implied by the DHT message's +source port (which may improve connectivity through a NAT).

    +

    Both these functions are exposed for advanced custom use of the DHT. +All torrents eligible to be announce to the DHT will be automatically, +by libtorrent.

    +

    For possible flags, see announce_flags_t.

    +[report issue]
    +
    +

    dht_live_nodes()

    +
    +void dht_live_nodes (sha1_hash const& nid);
    +
    +

    Retrieve all the live DHT (identified by nid) nodes. All the +nodes id and endpoint will be returned in the list of nodes in the +alert dht_live_nodes_alert. +Since this alert is a response to an explicit call, it will always be +posted, regardless of the alert mask.

    +[report issue]
    +
    +

    dht_sample_infohashes()

    +
    +void dht_sample_infohashes (udp::endpoint const& ep, sha1_hash const& target);
    +
    +

    Query the DHT node specified by ep to retrieve a sample of the +info-hashes that the node currently have in their storage. +The target is included for iterative lookups so that indexing nodes +can perform a key space traversal with a single RPC per node by adjusting +the target value for each RPC. It has no effect on the returned sample value. +The result is posted as a dht_sample_infohashes_alert.

    +[report issue]
    +
    +

    dht_direct_request()

    +
    +void dht_direct_request (udp::endpoint const& ep, entry const& e, void* userdata = nullptr);
    +
    +

    Send an arbitrary DHT request directly to the specified endpoint. This +function is intended for use by plugins. When a response is received +or the request times out, a dht_direct_response_alert will be posted +with the response (if any) and the userdata pointer passed in here. +Since this alert is a response to an explicit call, it will always be +posted, regardless of the alert mask.

    +[report issue]
    +
    +

    add_extension()

    +
    +void add_extension (std::function<std::shared_ptr<torrent_plugin>(
    +      torrent_handle const&, void*)> ext);
    +void add_extension (std::shared_ptr<plugin> ext);
    +
    +

    This function adds an extension to this session. The argument is a +function object that is called with a torrent_handle and which should +return a std::shared_ptr<torrent_plugin>. To write custom +plugins, see libtorrent plugins. For the typical bittorrent client +all of these extensions should be added. The main plugins implemented +in libtorrent are:

    +
    +
    uTorrent metadata
    +
    Allows peers to download the metadata (.torrent files) from the swarm +directly. Makes it possible to join a swarm with just a tracker and +info-hash.
    +
    +
    +#include <libtorrent/extensions/ut_metadata.hpp>
    +ses.add_extension(&lt::create_ut_metadata_plugin);
    +
    +
    +
    uTorrent peer exchange
    +
    Exchanges peers between clients.
    +
    +
    +#include <libtorrent/extensions/ut_pex.hpp>
    +ses.add_extension(&lt::create_ut_pex_plugin);
    +
    +
    +
    smart ban plugin
    +
    A plugin that, with a small overhead, can ban peers +that sends bad data with very high accuracy. Should +eliminate most problems on poisoned torrents.
    +
    +
    +#include <libtorrent/extensions/smart_ban.hpp>
    +ses.add_extension(&lt::create_smart_ban_plugin);
    +
    + +[report issue]
    +
    +

    get_ip_filter() set_ip_filter()

    +
    +void set_ip_filter (ip_filter const& f);
    +ip_filter get_ip_filter () const;
    +
    +

    Sets a filter that will be used to reject and accept incoming as well +as outgoing connections based on their originating ip address. The +default filter will allow connections to any ip address. To build a +set of rules for which addresses are accepted and not, see ip_filter.

    +

    Each time a peer is blocked because of the IP filter, a +peer_blocked_alert is generated. get_ip_filter() Returns the +ip_filter currently in the session. See ip_filter.

    +[report issue]
    +
    +

    set_port_filter()

    +
    +void set_port_filter (port_filter const& f);
    +
    +

    apply port_filter f to incoming and outgoing peers. a port filter +will reject making outgoing peer connections to certain remote ports. +The main intention is to be able to avoid triggering certain +anti-virus software by connecting to SMTP, FTP ports.

    + + +[report issue]
    +
    +

    listen_port() ssl_listen_port() is_listening()

    +
    +unsigned short listen_port () const;
    +bool is_listening () const;
    +unsigned short ssl_listen_port () const;
    +
    +

    is_listening() will tell you whether or not the session has +successfully opened a listening port. If it hasn't, this function will +return false, and then you can set a new +settings_pack::listen_interfaces to try another interface and port to +bind to.

    +

    listen_port() returns the port we ended up listening on.

    + +[report issue]
    +
    +

    get_peer_class_filter() set_peer_class_filter()

    +
    +ip_filter get_peer_class_filter () const;
    +void set_peer_class_filter (ip_filter const& f);
    +
    +

    Sets the peer class filter for this session. All new peer connections +will take this into account and be added to the peer classes specified +by this filter, based on the peer's IP address.

    +

    The ip-filter essentially maps an IP -> uint32. Each bit in that 32 +bit integer represents a peer class. The least significant bit +represents class 0, the next bit class 1 and so on.

    +

    For more info, see ip_filter.

    +

    For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 +belong to their own peer class, apply the following filter:

    +
    +ip_filter f = ses.get_peer_class_filter();
    +peer_class_t my_class = ses.create_peer_class("200.1.x.x IP range");
    +f.add_rule(make_address("200.1.1.0"), make_address("200.1.255.255")
    +        , 1 << static_cast<std::uint32_t>(my_class));
    +ses.set_peer_class_filter(f);
    +
    +

    This setting only applies to new connections, it won't affect existing +peer connections.

    +

    This function is limited to only peer class 0-31, since there are only +32 bits in the IP range mapping. Only the set bits matter; no peer +class will be removed from a peer as a result of this call, peer +classes are only added.

    +

    The peer_class argument cannot be greater than 31. The bitmasks +representing peer classes in the peer_class_filter are 32 bits.

    +

    The get_peer_class_filter() function returns the current filter.

    +

    For more information, see peer classes.

    + +[report issue]
    +
    +

    get_peer_class_type_filter() set_peer_class_type_filter()

    +
    +void set_peer_class_type_filter (peer_class_type_filter const& f);
    +peer_class_type_filter get_peer_class_type_filter () const;
    +
    +

    Sets and gets the peer class type filter. This is controls automatic +peer class assignments to peers based on what kind of socket it is.

    +

    It does not only support assigning peer classes, it also supports +removing peer classes based on socket type.

    +

    The order of these rules being applied are:

    +
      +
    1. peer-class IP filter
    2. +
    3. peer-class type filter, removing classes
    4. +
    5. peer-class type filter, adding classes
    6. +
    +

    For more information, see peer classes.

    +[report issue]
    +
    +

    create_peer_class()

    +
    +peer_class_t create_peer_class (char const* name);
    +
    +

    Creates a new peer class (see peer classes) with the given name. The +returned integer is the new peer class identifier. Peer classes may +have the same name, so each invocation of this function creates a new +class and returns a unique identifier.

    +

    Identifiers are assigned from low numbers to higher. So if you plan on +using certain peer classes in a call to set_peer_class_filter(), +make sure to create those early on, to get low identifiers.

    +

    For more information on peer classes, see peer classes.

    +[report issue]
    +
    +

    delete_peer_class()

    +
    +void delete_peer_class (peer_class_t cid);
    +
    +

    This call dereferences the reference count of the specified peer +class. When creating a peer class it's automatically referenced by 1. +If you want to recycle a peer class, you may call this function. You +may only call this function once per peer class you create. +Calling it more than once for the same class will lead to memory +corruption.

    +

    Since peer classes are reference counted, this function will not +remove the peer class if it's still assigned to torrents or peers. It +will however remove it once the last peer and torrent drops their +references to it.

    +

    There is no need to call this function for custom peer classes. All +peer classes will be properly destructed when the session object +destructs.

    +

    For more information on peer classes, see peer classes.

    + +[report issue]
    +
    +

    set_peer_class() get_peer_class()

    +
    +void set_peer_class (peer_class_t cid, peer_class_info const& pci);
    +peer_class_info get_peer_class (peer_class_t cid) const;
    +
    +

    These functions queries information from a peer class and updates the +configuration of a peer class, respectively.

    +

    cid must refer to an existing peer class. If it does not, the +return value of get_peer_class() is undefined.

    +

    set_peer_class() sets all the information in the +peer_class_info object in the specified peer class. There is no +option to only update a single property.

    +

    A peer or torrent belonging to more than one class, the highest +priority among any of its classes is the one that is taken into +account.

    +

    For more information, see peer classes.

    +[report issue]
    +
    +

    remove_torrent()

    +
    +void remove_torrent (const torrent_handle& h, remove_flags_t options = {});
    +
    +

    remove_torrent() will close all peer connections associated with +the torrent and tell the tracker that we've stopped participating in +the swarm. This operation cannot fail. When it completes, you will +receive a torrent_removed_alert.

    +

    The optional second argument options can be used to delete all the +files downloaded by this torrent. To do so, pass in the value +session_handle::delete_files. The removal of the torrent is asynchronous, +there is no guarantee that adding the same torrent immediately after +it was removed will not throw a system_error exception. Once +the torrent is deleted, a torrent_deleted_alert is posted.

    +

    Note that when a queued or downloading torrent is removed, its position +in the download queue is vacated and every subsequent torrent in the +queue has their queue positions updated. This can potentially cause a +large state_update to be posted. When removing all torrents, it is +advised to remove them from the back of the queue, to minimize the +shifting.

    + +[report issue]
    +
    +

    get_settings() apply_settings()

    +
    +void apply_settings (settings_pack const& s);
    +settings_pack get_settings () const;
    +void apply_settings (settings_pack&& s);
    +
    +

    Applies the settings specified by the settings_pack s. This is an +asynchronous operation that will return immediately and actually apply +the settings to the main thread of libtorrent some time later.

    + + +[report issue]
    +
    +

    wait_for_alert() set_alert_notify() pop_alerts()

    +
    +void pop_alerts (std::vector<alert*>* alerts);
    +alert* wait_for_alert (time_duration max_wait);
    +void set_alert_notify (std::function<void()> const& fun);
    +
    +

    Alerts is the main mechanism for libtorrent to report errors and +events. pop_alerts fills in the vector passed to it with pointers +to new alerts. The session still owns these alerts and they will stay +valid until the next time pop_alerts is called. You may not delete +the alert objects.

    +

    It is safe to call pop_alerts from multiple different threads, as +long as the alerts themselves are not accessed once another thread +calls pop_alerts. Doing this requires manual synchronization +between the popping threads.

    +

    wait_for_alert will block the current thread for max_wait time +duration, or until another alert is posted. If an alert is available +at the time of the call, it returns immediately. The returned alert +pointer is the head of the alert queue. wait_for_alert does not +pop alerts from the queue, it merely peeks at it. The returned alert +will stay valid until pop_alerts is called twice. The first time +will pop it and the second will free it.

    +

    If there is no alert in the queue and no alert arrives within the +specified timeout, wait_for_alert returns nullptr.

    +

    In the python binding, wait_for_alert takes the number of +milliseconds to wait as an integer.

    +

    The alert queue in the session will not grow indefinitely. Make sure +to pop periodically to not miss notifications. To control the max +number of alerts that's queued by the session, see +settings_pack::alert_queue_size.

    +

    Some alerts are considered so important that they are posted even when +the alert queue is full. Some alerts are considered mandatory and cannot +be disabled by the alert_mask. For instance, +save_resume_data_alert and save_resume_data_failed_alert are always +posted, regardless of the alert mask.

    +

    To control which alerts are posted, set the alert_mask +(settings_pack::alert_mask).

    +

    If the alert queue fills up to the point where alerts are dropped, this +will be indicated by a alerts_dropped_alert, which contains a bitmask +of which types of alerts were dropped. Generally it is a good idea to +make sure the alert queue is large enough, the alert_mask doesn't have +unnecessary categories enabled and to call pop_alert() frequently, to +avoid alerts being dropped.

    +

    the set_alert_notify function lets the client set a function object +to be invoked every time the alert queue goes from having 0 alerts to +1 alert. This function is called from within libtorrent, it may be the +main thread, or it may be from within a user call. The intention of +of the function is that the client wakes up its main thread, to poll +for more alerts using pop_alerts(). If the notify function fails +to do so, it won't be called again, until pop_alerts is called for +some other reason. For instance, it could signal an eventfd, post a +message to an HWND or some other main message pump. The actual +retrieval of alerts should not be done in the callback. In fact, the +callback should not block. It should not perform any expensive work. +It really should just notify the main application thread.

    +

    The type of an alert is returned by the polymorphic function +alert::type() but can also be queries from a concrete type via +T::alert_type, as a static constant.

    + +[report issue]
    +
    +

    add_port_mapping() delete_port_mapping()

    +
    +void delete_port_mapping (port_mapping_t handle);
    +std::vector<port_mapping_t> add_port_mapping (portmap_protocol t, int external_port, int local_port);
    +
    +

    add_port_mapping adds one or more port forwards on UPnP and/or NAT-PMP, +whichever is enabled. A mapping is created for each listen socket +in the session. The return values are all handles referring to the +port mappings that were just created. Pass them to delete_port_mapping() +to remove them.

    +[report issue]
    +
    +

    reopen_network_sockets()

    +
    +void reopen_network_sockets (reopen_network_flags_t options = reopen_map_ports);
    +
    +

    Instructs the session to reopen all listen and outgoing sockets.

    +

    It's useful in the case your platform doesn't support the built in +IP notifier mechanism, or if you have a better more reliable way to +detect changes in the IP routing table.

    +[report issue]
    +
    +

    native_handle()

    +
    +std::shared_ptr<aux::session_impl> native_handle () const;
    +
    +

    This function is intended only for use by plugins. This type does +not have a stable API and should be relied on as little as possible.

    +[report issue]
    +
    save_settings
    +
    saves settings (i.e. the settings_pack)
    +
    +[report issue]
    +
    save_dht_settings
    +
    saves dht_settings
    +
    +[report issue]
    +
    save_dht_state
    +
    saves dht state such as nodes and node-id, possibly accelerating +joining the DHT if provided at next session startup.
    +
    + + +[report issue]
    +
    global_peer_class_id tcp_peer_class_id local_peer_class_id
    +
    built-in peer classes
    +
    +[report issue]
    +
    delete_files
    +
    delete the files belonging to the torrent from disk. +including the part-file, if there is one
    +
    +[report issue]
    +
    delete_partfile
    +
    delete just the part-file associated with this torrent
    +
    +[report issue]
    +
    add_default_plugins
    +
    this will add common extensions like ut_pex, ut_metadata, lt_tex +smart_ban and possibly others.
    +
    + +[report issue]
    +
    udp tcp
    +
    protocols used by add_port_mapping()
    +
    +[report issue]
    +
    reopen_map_ports
    +
    This option indicates if the ports are mapped using natpmp +and upnp. If mapping was already made, they are deleted and added +again. This only works if natpmp and/or upnp are configured to be +enable.
    +
    +[report issue]
    +
    +
    +

    session_proxy

    +

    Declared in "libtorrent/session.hpp"

    +

    this is a holder for the internal session implementation object. Once the +session destruction is explicitly initiated, this holder is used to +synchronize the completion of the shutdown. The lifetime of this object +may outlive session, causing the session destructor to not block. The +session_proxy destructor will block however, until the underlying session +is done shutting down.

    +
    +class session_proxy
    +{
    +   session_proxy (session_proxy const&);
    +   session_proxy& operator= (session_proxy const&);
    +   ~session_proxy ();
    +   session_proxy& operator= (session_proxy&&) noexcept;
    +   session_proxy ();
    +   session_proxy (session_proxy&&) noexcept;
    +};
    +
    + + +[report issue]
    +

    ~session_proxy() session_proxy() operator=()

    +
    +session_proxy (session_proxy const&);
    +session_proxy& operator= (session_proxy const&);
    +~session_proxy ();
    +session_proxy& operator= (session_proxy&&) noexcept;
    +session_proxy ();
    +session_proxy (session_proxy&&) noexcept;
    +
    +

    default constructor, does not refer to any session +implementation object.

    +[report issue]
    +
    +
    +

    session_params

    +

    Declared in "libtorrent/session.hpp"

    +

    The session_params is a parameters pack for configuring the session +before it's started.

    +
    +struct session_params
    +{
    +   explicit session_params (settings_pack&& sp);
    +   session_params ();
    +   explicit session_params (settings_pack const& sp);
    +   session_params (settings_pack const& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +   session_params (settings_pack&& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +
    +   settings_pack settings;
    +   std::vector<std::shared_ptr<plugin>> extensions;
    +   dht::dht_settings dht_settings;
    +   dht::dht_state dht_state;
    +   dht::dht_storage_constructor_type dht_storage_constructor;
    +};
    +
    +[report issue]
    +

    session_params()

    +
    +explicit session_params (settings_pack&& sp);
    +session_params ();
    +explicit session_params (settings_pack const& sp);
    +
    +

    This constructor can be used to start with the default plugins +(ut_metadata, ut_pex and smart_ban). The default values in the +settings is to start the default features like upnp, NAT-PMP, +and dht for example.

    +[report issue]
    +
    +

    session_params()

    +
    +session_params (settings_pack const& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +session_params (settings_pack&& sp
    +      , std::vector<std::shared_ptr<plugin>> exts);
    +
    +

    This constructor helps to configure the set of initial plugins +to be added to the session before it's started.

    +[report issue]
    +
    +
    +

    session

    +

    Declared in "libtorrent/session.hpp"

    +

    The session holds all state that spans multiple torrents. Among other +things it runs the network loop and manages all torrents. Once it's +created, the session object will spawn the main thread that will do all +the work. The main thread will be idle as long it doesn't have any +torrents to participate in.

    +

    You have some control over session configuration through the +session_handle::apply_settings() member function. To change one or more +configuration options, create a settings_pack. object and fill it with +the settings to be set and pass it in to session::apply_settings().

    +

    see apply_settings().

    +
    +class session : public session_handle
    +{
    +   explicit session (session_params&& params);
    +   session ();
    +   explicit session (session_params const& params);
    +   session (session_params const& params, io_service& ios);
    +   session (session_params&& params, io_service& ios);
    +   session (settings_pack&& pack
    +      , session_flags_t const flags = add_default_plugins);
    +   session (settings_pack const& pack
    +      , session_flags_t const flags = add_default_plugins);
    +   session& operator= (session&&) = default;
    +   session (session&&) = default;
    +   session& operator= (session const&) = delete;
    +   session (session const&) = delete;
    +   session (settings_pack&& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +   session (settings_pack const& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +   ~session ();
    +   session_proxy abort ();
    +};
    +
    +[report issue]
    +

    session()

    +
    +explicit session (session_params&& params);
    +session ();
    +explicit session (session_params const& params);
    +
    +

    Constructs the session objects which acts as the container of torrents. +In order to avoid a race condition between starting the session and +configuring it, you can pass in a session_params object. Its settings +will take effect before the session starts up.

    +[report issue]
    +
    +

    session()

    +
    +session (session_params const& params, io_service& ios);
    +session (session_params&& params, io_service& ios);
    +
    +

    Overload of the constructor that takes an external io_service to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_service, or low resource +systems where additional threads are expensive and sharing an +io_service with other events is fine.

    +
    +

    Warning

    +

    The session object does not cleanly terminate with an external +io_service. The io_service::run() call _must_ have returned +before it's safe to destruct the session. Which means you MUST +call session::abort() and save the session_proxy first, then +destruct the session object, then sync with the io_service, then +destruct the session_proxy object.

    +
    +[report issue]
    +
    +

    session()

    +
    +session (settings_pack&& pack
    +      , session_flags_t const flags = add_default_plugins);
    +session (settings_pack const& pack
    +      , session_flags_t const flags = add_default_plugins);
    +
    +

    Constructs the session objects which acts as the container of torrents. +It provides configuration options across torrents (such as rate limits, +disk cache, ip filter etc.). In order to avoid a race condition between +starting the session and configuring it, you can pass in a +settings_pack object. Its settings will take effect before the session +starts up.

    +

    The flags parameter can be used to start default features (UPnP & +NAT-PMP) and default plugins (ut_metadata, ut_pex and smart_ban). The +default is to start those features. If you do not want them to start, +pass 0 as the flags parameter.

    + +[report issue]
    +
    +

    session() operator=()

    +
    +session& operator= (session&&) = default;
    +session (session&&) = default;
    +
    +

    movable

    + +[report issue]
    +
    +

    session() operator=()

    +
    +session& operator= (session const&) = delete;
    +session (session const&) = delete;
    +
    +

    non-copyable

    +[report issue]
    +
    +

    session()

    +
    +session (settings_pack&& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +session (settings_pack const& pack
    +      , io_service& ios
    +      , session_flags_t const flags = add_default_plugins);
    +
    +

    overload of the constructor that takes an external io_service to run +the session object on. This is primarily useful for tests that may want +to run multiple sessions on a single io_service, or low resource +systems where additional threads are expensive and sharing an +io_service with other events is fine.

    +
    +

    Warning

    +

    The session object does not cleanly terminate with an external +io_service. The io_service::run() call _must_ have returned +before it's safe to destruct the session. Which means you MUST +call session::abort() and save the session_proxy first, then +destruct the session object, then sync with the io_service, then +destruct the session_proxy object.

    +
    +[report issue]
    +
    +

    ~session()

    +
    +~session ();
    +
    +

    The destructor of session will notify all trackers that our torrents +have been shut down. If some trackers are down, they will time out. +All this before the destructor of session returns. So, it's advised +that any kind of interface (such as windows) are closed before +destructing the session object. Because it can take a few second for +it to finish. The timeout can be set with apply_settings().

    +[report issue]
    +
    +

    abort()

    +
    +session_proxy abort ();
    +
    +

    In case you want to destruct the session asynchronously, you can +request a session destruction proxy. If you don't do this, the +destructor of the session object will block while the trackers are +contacted. If you keep one session_proxy to the session when +destructing it, the destructor will not block, but start to close down +the session, the destructor of the proxy will then synchronize the +threads. So, the destruction of the session is performed from the +session destructor call until the session_proxy destructor +call. The session_proxy does not have any operations on it (since +the session is being closed down, no operations are allowed on it). +The only valid operation is calling the destructor:

    +
    +class session_proxy
    +{
    +public:
    +        session_proxy();
    +        ~session_proxy()
    +};
    +
    +[report issue]
    +
    +
    +

    session_stats_metrics()

    +

    Declared in "libtorrent/session_stats.hpp"

    +
    +std::vector<stats_metric> session_stats_metrics ();
    +
    +

    This free function returns the list of available metrics exposed by +libtorrent's statistics API. Each metric has a name and a value index. +The value index is the index into the array in session_stats_alert where +this metric's value can be found when the session stats is sampled (by +calling post_session_stats()).

    +[report issue]
    +
    +

    find_metric_idx()

    +

    Declared in "libtorrent/session_stats.hpp"

    +
    +int find_metric_idx (string_view name);
    +
    +

    given a name of a metric, this function returns the counter index of it, +or -1 if it could not be found. The counter index is the index into the +values array returned by session_stats_alert.

    +[report issue]
    +
    +

    read_session_params()

    +

    Declared in "libtorrent/session.hpp"

    +
    +session_params read_session_params (bdecode_node const& e
    +   , save_state_flags_t flags = save_state_flags_t::all());
    +
    +

    This function helps to construct a session_params from a +bencoded data generated by session_handle::save_state

    +[report issue]
    +
    +

    enum metric_type_t

    +

    Declared in "libtorrent/session_stats.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    counter0 
    gauge1 
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Plugins

    +

    libtorrent has a plugin interface for implementing extensions to the protocol. +These can be general extensions for transferring metadata or peer exchange +extensions, or it could be used to provide a way to customize the protocol +to fit a particular (closed) network.

    +

    In short, the plugin interface makes it possible to:

    +
      +
    • register extension messages (sent in the extension handshake), see +extensions.
    • +
    • add data and parse data from the extension handshake.
    • +
    • send extension messages and standard bittorrent messages.
    • +
    • override or block the handling of standard bittorrent messages.
    • +
    • save and restore state via the session state
    • +
    • see all alerts that are posted
    • +
    +
    +

    a word of caution

    +

    Writing your own plugin is a very easy way to introduce serious bugs such as +dead locks and race conditions. Since a plugin has access to internal +structures it is also quite easy to sabotage libtorrent's operation.

    +

    All the callbacks are always called from the libtorrent network thread. In +case portions of your plugin are called from other threads, typically the main +thread, you cannot use any of the member functions on the internal structures +in libtorrent, since those require being called from the libtorrent network +thread . Furthermore, you also need to synchronize your own shared data +within the plugin, to make sure it is not accessed at the same time from the +libtorrent thread (through a callback). If you need to send out a message +from another thread, it is advised to use an internal queue, and do the +actual sending in tick().

    +

    Since the plugin interface gives you easy access to internal structures, it +is not supported as a stable API. Plugins should be considered specific to a +specific version of libtorrent. Although, in practice the internals mostly +don't change that dramatically.

    +
    +
    +
    +

    plugin-interface

    +

    The plugin interface consists of three base classes that the plugin may +implement. These are called plugin, torrent_plugin and peer_plugin. +They are found in the <libtorrent/extensions.hpp> header.

    +

    These plugins are instantiated for each session, torrent and possibly each peer, +respectively.

    +

    For plugins that only need per torrent state, it is enough to only implement +torrent_plugin and pass a constructor function or function object to +session::add_extension() or torrent_handle::add_extension() (if the +torrent has already been started and you want to hook in the extension at +run-time).

    +

    The signature of the function is:

    +
    +std::shared_ptr<torrent_plugin> (*)(torrent_handle const&, void*);
    +
    +

    The second argument is the userdata passed to session::add_torrent() or +torrent_handle::add_extension().

    +

    The function should return a std::shared_ptr<torrent_plugin> which +may or may not be 0. If it is a nullptr, the extension is simply ignored +for this torrent. If it is a valid pointer (to a class inheriting +torrent_plugin), it will be associated with this torrent and callbacks +will be made on torrent events.

    +

    For more elaborate plugins which require session wide state, you would +implement plugin, construct an object (in a std::shared_ptr) and pass +it in to session::add_extension().

    +
    +
    +

    custom alerts

    +

    Since plugins are running within internal libtorrent threads, one convenient +way to communicate with the client is to post custom alerts.

    +

    The expected interface of any alert, apart from deriving from the alert +base class, looks like this:

    +
    +static const int alert_type = <unique alert ID>;
    +virtual int type() const { return alert_type; }
    +
    +virtual std::string message() const;
    +
    +static const alert_category_t static_category = <bitmask of alert::category_t flags>;
    +virtual alert_category_t category() const { return static_category; }
    +
    +virtual char const* what() const { return <string literal of the name of this alert>; }
    +
    +

    The alert_type is used for the type-checking in alert_cast. It must +not collide with any other alert. The built-in alerts in libtorrent will +not use alert type IDs greater than user_alert_id. When defining your +own alert, make sure it's greater than this constant.

    +

    type() is the run-time equivalence of the alert_type.

    +

    The message() virtual function is expected to construct a useful +string representation of the alert and the event or data it represents. +Something convenient to put in a log file for instance.

    +

    clone() is used internally to copy alerts. The suggested implementation +of simply allocating a new instance as a copy of *this is all that's +expected.

    +

    The static category is required for checking whether or not the category +for a specific alert is enabled or not, without instantiating the alert. +The category virtual function is the run-time equivalence.

    +

    The what() virtual function may simply be a string literal of the class +name of your alert.

    +

    For more information, see the alert section.

    +[report issue]
    +

    plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    this is the base class for a session plugin. One primary feature +is that it is notified of all torrents that are added to the session, +and can add its own torrent_plugins.

    +
    +struct plugin
    +{
    +   virtual feature_flags_t implemented_features ();
    +   virtual std::shared_ptr<torrent_plugin> new_torrent (torrent_handle const&, void*);
    +   virtual void added (session_handle const&);
    +   virtual bool on_dht_request (string_view /* query */
    +      , udp::endpoint const& /* source */, bdecode_node const& /* message */
    +      , entry& /* response */);
    +   virtual void on_alert (alert const*);
    +   virtual bool on_unknown_torrent (sha1_hash const& /* info_hash */
    +      , peer_connection_handle const& /* pc */, add_torrent_params& /* p */);
    +   virtual void on_tick ();
    +   virtual uint64_t get_unchoke_priority (peer_connection_handle const& /* peer */);
    +   virtual void save_state (entry&);
    +   virtual void load_state (bdecode_node const&);
    +
    +   static constexpr feature_flags_t optimistic_unchoke_feature  = 1_bit;
    +   static constexpr feature_flags_t tick_feature  = 2_bit;
    +   static constexpr feature_flags_t dht_request_feature  = 3_bit;
    +   static constexpr feature_flags_t alert_feature  = 4_bit;
    +};
    +
    +[report issue]
    +

    implemented_features()

    +
    +virtual feature_flags_t implemented_features ();
    +
    +

    This function is expected to return a bitmask indicating which features +this plugin implements. Some callbacks on this object may not be called +unless the corresponding feature flag is returned here. Note that +callbacks may still be called even if the corresponding feature is not +specified in the return value here. See feature_flags_t for possible +flags to return.

    +[report issue]
    +
    +

    new_torrent()

    +
    +virtual std::shared_ptr<torrent_plugin> new_torrent (torrent_handle const&, void*);
    +
    +

    this is called by the session every time a new torrent is added. +The torrent* points to the internal torrent object created +for the new torrent. The void* is the userdata pointer as +passed in via add_torrent_params.

    +

    If the plugin returns a torrent_plugin instance, it will be added +to the new torrent. Otherwise, return an empty shared_ptr to a +torrent_plugin (the default).

    +[report issue]
    +
    +

    added()

    +
    +virtual void added (session_handle const&);
    +
    +

    called when plugin is added to a session

    +[report issue]
    +
    +

    on_dht_request()

    +
    +virtual bool on_dht_request (string_view /* query */
    +      , udp::endpoint const& /* source */, bdecode_node const& /* message */
    +      , entry& /* response */);
    +
    +

    called when a dht request is received. +If your plugin expects this to be called, make sure to include the flag +dht_request_feature in the return value from implemented_features().

    +[report issue]
    +
    +

    on_alert()

    +
    +virtual void on_alert (alert const*);
    +
    +

    called when an alert is posted alerts that are filtered are not posted. +If your plugin expects this to be called, make sure to include the flag +alert_feature in the return value from implemented_features().

    +[report issue]
    +
    +

    on_unknown_torrent()

    +
    +virtual bool on_unknown_torrent (sha1_hash const& /* info_hash */
    +      , peer_connection_handle const& /* pc */, add_torrent_params& /* p */);
    +
    +

    return true if the add_torrent_params should be added

    +[report issue]
    +
    +

    on_tick()

    +
    +virtual void on_tick ();
    +
    +

    called once per second. +If your plugin expects this to be called, make sure to include the flag +tick_feature in the return value from implemented_features().

    +[report issue]
    +
    +

    get_unchoke_priority()

    +
    +virtual uint64_t get_unchoke_priority (peer_connection_handle const& /* peer */);
    +
    +

    called when choosing peers to optimistically unchoke. The return value +indicates the peer's priority for unchoking. Lower return values +correspond to higher priority. Priorities above 2^63-1 are reserved. +If your plugin has no priority to assign a peer it should return 2^64-1. +If your plugin expects this to be called, make sure to include the flag +optimistic_unchoke_feature in the return value from implemented_features(). +If multiple plugins implement this function the lowest return value +(i.e. the highest priority) is used.

    +[report issue]
    +
    +

    save_state()

    +
    +virtual void save_state (entry&);
    +
    +

    called when saving settings state

    +[report issue]
    +
    +

    load_state()

    +
    +virtual void load_state (bdecode_node const&);
    +
    +

    called when loading settings state

    +[report issue]
    +
    optimistic_unchoke_feature
    +
    include this bit if your plugin needs to alter the order of the +optimistic unchoke of peers. i.e. have the on_optimistic_unchoke() +callback be called.
    +
    +[report issue]
    +
    tick_feature
    +
    include this bit if your plugin needs to have on_tick() called
    +
    +[report issue]
    +
    dht_request_feature
    +
    include this bit if your plugin needs to have on_dht_request() +called
    +
    +[report issue]
    +
    alert_feature
    +
    include this bit if your plugin needs to have on_alert() +called
    +
    +[report issue]
    +
    +
    +

    torrent_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    Torrent plugins are associated with a single torrent and have a number +of functions called at certain events. Many of its functions have the +ability to change or override the default libtorrent behavior.

    +
    +struct torrent_plugin
    +{
    +   virtual std::shared_ptr<peer_plugin> new_connection (peer_connection_handle const&);
    +   virtual void on_piece_failed (piece_index_t);
    +   virtual void on_piece_pass (piece_index_t);
    +   virtual void tick ();
    +   virtual bool on_pause ();
    +   virtual bool on_resume ();
    +   virtual void on_files_checked ();
    +   virtual void on_state (torrent_status::state_t);
    +   virtual void on_add_peer (tcp::endpoint const&,
    +      peer_source_flags_t, add_peer_flags_t);
    +
    +   static constexpr add_peer_flags_t first_time  = 1_bit;
    +   static constexpr add_peer_flags_t filtered  = 2_bit;
    +};
    +
    +[report issue]
    +

    new_connection()

    +
    +virtual std::shared_ptr<peer_plugin> new_connection (peer_connection_handle const&);
    +
    +

    This function is called each time a new peer is connected to the torrent. You +may choose to ignore this by just returning a default constructed +shared_ptr (in which case you don't need to override this member +function).

    +

    If you need an extension to the peer connection (which most plugins do) you +are supposed to return an instance of your peer_plugin class. Which in +turn will have its hook functions called on event specific to that peer.

    +

    The peer_connection_handle will be valid as long as the shared_ptr +is being held by the torrent object. So, it is generally a good idea to not +keep a shared_ptr to your own peer_plugin. If you want to keep references +to it, use weak_ptr.

    +

    If this function throws an exception, the connection will be closed.

    + +[report issue]
    +
    +

    on_piece_failed() on_piece_pass()

    +
    +virtual void on_piece_failed (piece_index_t);
    +virtual void on_piece_pass (piece_index_t);
    +
    +

    These hooks are called when a piece passes the hash check or fails the hash +check, respectively. The index is the piece index that was downloaded. +It is possible to access the list of peers that participated in sending the +piece through the torrent and the piece_picker.

    +[report issue]
    +
    +

    tick()

    +
    +virtual void tick ();
    +
    +

    This hook is called approximately once per second. It is a way of making it +easy for plugins to do timed events, for sending messages or whatever.

    + +[report issue]
    +
    +

    on_resume() on_pause()

    +
    +virtual bool on_pause ();
    +virtual bool on_resume ();
    +
    +

    These hooks are called when the torrent is paused and resumed respectively. +The return value indicates if the event was handled. A return value of +true indicates that it was handled, and no other plugin after this one +will have this hook function called, and the standard handler will also not be +invoked. So, returning true effectively overrides the standard behavior of +pause or resume.

    +

    Note that if you call pause() or resume() on the torrent from your +handler it will recurse back into your handler, so in order to invoke the +standard handler, you have to keep your own state on whether you want standard +behavior or overridden behavior.

    +[report issue]
    +
    +

    on_files_checked()

    +
    +virtual void on_files_checked ();
    +
    +

    This function is called when the initial files of the torrent have been +checked. If there are no files to check, this function is called immediately.

    +

    i.e. This function is always called when the torrent is in a state where it +can start downloading.

    +[report issue]
    +
    +

    on_state()

    +
    +virtual void on_state (torrent_status::state_t);
    +
    +

    called when the torrent changes state +the state is one of torrent_status::state_t +enum members

    +[report issue]
    +
    +

    on_add_peer()

    +
    +virtual void on_add_peer (tcp::endpoint const&,
    +      peer_source_flags_t, add_peer_flags_t);
    +
    +

    called every time a new peer is added to the peer list. +This is before the peer is connected to. For flags, see +torrent_plugin::flags_t. The source argument refers to +the source where we learned about this peer from. It's a +bitmask, because many sources may have told us about the same +peer. For peer source flags, see peer_info::peer_source_flags.

    +[report issue]
    +
    first_time
    +
    this is the first time we see this peer
    +
    +[report issue]
    +
    filtered
    +
    this peer was not added because it was +filtered by the IP filter
    +
    +[report issue]
    +
    +
    +

    peer_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +

    peer plugins are associated with a specific peer. A peer could be +both a regular bittorrent peer (bt_peer_connection) or one of the +web seed connections (web_peer_connection or http_seed_connection). +In order to only attach to certain peers, make your +torrent_plugin::new_connection only return a plugin for certain peer +connection types

    +
    +struct peer_plugin
    +{
    +   virtual string_view type () const;
    +   virtual void add_handshake (entry&);
    +   virtual void on_disconnect (error_code const&);
    +   virtual void on_connected ();
    +   virtual bool on_handshake (span<char const>);
    +   virtual bool on_extension_handshake (bdecode_node const&);
    +   virtual bool on_interested ();
    +   virtual bool on_allowed_fast (piece_index_t);
    +   virtual bool on_choke ();
    +   virtual bool on_not_interested ();
    +   virtual bool on_dont_have (piece_index_t);
    +   virtual bool on_have (piece_index_t);
    +   virtual bool on_bitfield (bitfield const& /*bitfield*/);
    +   virtual bool on_have_all ();
    +   virtual bool on_request (peer_request const&);
    +   virtual bool on_have_none ();
    +   virtual bool on_unchoke ();
    +   virtual bool on_piece (peer_request const& /*piece*/
    +      , span<char const> /*buf*/);
    +   virtual bool on_cancel (peer_request const&);
    +   virtual bool on_reject (peer_request const&);
    +   virtual bool on_suggest (piece_index_t);
    +   virtual void sent_unchoke ();
    +   virtual void sent_payload (int /* bytes */);
    +   virtual bool can_disconnect (error_code const& /*ec*/);
    +   virtual bool on_extended (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +   virtual bool on_unknown_message (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +   virtual void on_piece_failed (piece_index_t);
    +   virtual void on_piece_pass (piece_index_t);
    +   virtual void tick ();
    +   virtual bool write_request (peer_request const&);
    +};
    +
    +[report issue]
    +

    type()

    +
    +virtual string_view type () const;
    +
    +

    This function is expected to return the name of +the plugin.

    +[report issue]
    +
    +

    add_handshake()

    +
    +virtual void add_handshake (entry&);
    +
    +

    can add entries to the extension handshake +this is not called for web seeds

    +[report issue]
    +
    +

    on_disconnect()

    +
    +virtual void on_disconnect (error_code const&);
    +
    +

    called when the peer is being disconnected.

    +[report issue]
    +
    +

    on_connected()

    +
    +virtual void on_connected ();
    +
    +

    called when the peer is successfully connected. Note that +incoming connections will have been connected by the time +the peer plugin is attached to it, and won't have this hook +called.

    +[report issue]
    +
    +

    on_handshake()

    +
    +virtual bool on_handshake (span<char const>);
    +
    +

    this is called when the initial bittorrent handshake is received. +Returning false means that the other end doesn't support this extension +and will remove it from the list of plugins. this is not called for web +seeds

    +[report issue]
    +
    +

    on_extension_handshake()

    +
    +virtual bool on_extension_handshake (bdecode_node const&);
    +
    +

    called when the extension handshake from the other end is received +if this returns false, it means that this extension isn't +supported by this peer. It will result in this peer_plugin +being removed from the peer_connection and destructed. +this is not called for web seeds

    + + + + + + + + + + +[report issue]
    +
    +

    on_have_all() on_have() on_request() on_interested() on_bitfield() on_unchoke() on_have_none() on_not_interested() on_allowed_fast() on_choke() on_dont_have()

    +
    +virtual bool on_interested ();
    +virtual bool on_allowed_fast (piece_index_t);
    +virtual bool on_choke ();
    +virtual bool on_not_interested ();
    +virtual bool on_dont_have (piece_index_t);
    +virtual bool on_have (piece_index_t);
    +virtual bool on_bitfield (bitfield const& /*bitfield*/);
    +virtual bool on_have_all ();
    +virtual bool on_request (peer_request const&);
    +virtual bool on_have_none ();
    +virtual bool on_unchoke ();
    +
    +

    returning true from any of the message handlers +indicates that the plugin has handled the message. +it will break the plugin chain traversing and not let +anyone else handle the message, including the default +handler.

    +[report issue]
    +
    +

    on_piece()

    +
    +virtual bool on_piece (peer_request const& /*piece*/
    +      , span<char const> /*buf*/);
    +
    +

    This function is called when the peer connection is receiving +a piece. buf points (non-owning pointer) to the data in an +internal immutable disk buffer. The length of the data is specified +in the length member of the piece parameter. +returns true to indicate that the piece is handled and the +rest of the logic should be ignored.

    +[report issue]
    +
    +

    sent_unchoke()

    +
    +virtual void sent_unchoke ();
    +
    +

    called after a choke message has been sent to the peer

    +[report issue]
    +
    +

    sent_payload()

    +
    +virtual void sent_payload (int /* bytes */);
    +
    +

    called after piece data has been sent to the peer +this can be used for stats book keeping

    +[report issue]
    +
    +

    can_disconnect()

    +
    +virtual bool can_disconnect (error_code const& /*ec*/);
    +
    +

    called when libtorrent think this peer should be disconnected. +if the plugin returns false, the peer will not be disconnected.

    +[report issue]
    +
    +

    on_extended()

    +
    +virtual bool on_extended (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +
    +

    called when an extended message is received. If returning true, +the message is not processed by any other plugin and if false +is returned the next plugin in the chain will receive it to +be able to handle it. This is not called for web seeds. +thus function may be called more than once per incoming message, but +only the last of the calls will the body size equal the length. +i.e. Every time another fragment of the message is received, this +function will be called, until finally the whole message has been +received. The purpose of this is to allow early disconnects for invalid +messages and for reporting progress of receiving large messages.

    +[report issue]
    +
    +

    on_unknown_message()

    +
    +virtual bool on_unknown_message (int /*length*/, int /*msg*/,
    +      span<char const> /*body*/);
    +
    +

    this is not called for web seeds

    + +[report issue]
    +
    +

    on_piece_failed() on_piece_pass()

    +
    +virtual void on_piece_failed (piece_index_t);
    +virtual void on_piece_pass (piece_index_t);
    +
    +

    called when a piece that this peer participated in either +fails or passes the hash_check

    +[report issue]
    +
    +

    tick()

    +
    +virtual void tick ();
    +
    +

    called approximately once every second

    +[report issue]
    +
    +

    write_request()

    +
    +virtual bool write_request (peer_request const&);
    +
    +

    called each time a request message is to be sent. If true +is returned, the original request message won't be sent and +no other plugin will have this function called.

    +[report issue]
    +
    +
    +

    crypto_plugin

    +

    Declared in "libtorrent/extensions.hpp"

    +
    +struct crypto_plugin
    +{
    +   virtual void set_outgoing_key (span<char const> key) = 0;
    +   virtual void set_incoming_key (span<char const> key) = 0;
    +   encrypt (span<span<char>> /*send_vec*/) = 0;
    +   virtual std::tuple<int, int, int> decrypt (span<span<char>> /*receive_vec*/) = 0;
    +};
    +
    +[report issue]
    +

    decrypt()

    +
    +virtual std::tuple<int, int, int> decrypt (span<span<char>> /*receive_vec*/) = 0;
    +
    +

    decrypt the provided buffers. +returns is a tuple representing the values +(consume, produce, packet_size)

    +

    consume is set to the number of bytes which should be trimmed from the +head of the buffers, default is 0

    +

    produce is set to the number of bytes of payload which are now ready to +be sent to the upper layer. default is the number of bytes passed in receive_vec

    +

    packet_size is set to the minimum number of bytes which must be read to +advance the next step of decryption. default is 0

    +[report issue]
    +
    +
    +

    create_ut_metadata_plugin()

    +

    Declared in "libtorrent/extensions/ut_metadata.hpp"

    +
    +std::shared_ptr<torrent_plugin> create_ut_metadata_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the ut_metadata extension. The ut_metadata +extension allows peers to request the .torrent file (or more +specifically the info-dictionary of the .torrent file) from each +other. This is the main building block in making magnet links work. +This extension is enabled by default unless explicitly disabled in +the session constructor.

    +

    This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension().

    +[report issue]
    +
    +

    create_smart_ban_plugin()

    +

    Declared in "libtorrent/extensions/smart_ban.hpp"

    +
    +std::shared_ptr<torrent_plugin> create_smart_ban_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the smart ban extension. The extension keeps +track of the data peers have sent us for failing pieces and once the +piece completes and passes the hash check bans the peers that turned +out to have sent corrupt data. +This function can either be passed in the add_torrent_params::extensions +field, or via torrent_handle::add_extension().

    +[report issue]
    +
    +

    create_ut_pex_plugin()

    +

    Declared in "libtorrent/extensions/ut_pex.hpp"

    +
    +std::shared_ptr<torrent_plugin> create_ut_pex_plugin (torrent_handle const&, void*);
    +
    +

    constructor function for the ut_pex extension. The ut_pex +extension allows peers to gossip about their connections, allowing +the swarm stay well connected and peers aware of more peers in the +swarm. This extension is enabled by default unless explicitly disabled in +the session constructor.

    +

    This can either be passed in the add_torrent_params::extensions field, or +via torrent_handle::add_extension().

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Create Torrents

    +

    This section describes the functions and classes that are used +to create torrent files. It is a layered API with low level classes +and higher level convenience functions. A torrent is created in 4 +steps:

    +
      +
    1. first the files that will be part of the torrent are determined.
    2. +
    3. the torrent properties are set, such as tracker url, web seeds, +DHT nodes etc.
    4. +
    5. Read through all the files in the torrent, SHA-1 all the data +and set the piece hashes.
    6. +
    7. The torrent is bencoded into a file or buffer.
    8. +
    +

    If there are a lot of files and or deep directory hierarchies to +traverse, step one can be time consuming.

    +

    Typically step 3 is by far the most time consuming step, since it +requires to read all the bytes from all the files in the torrent.

    +

    All of these classes and functions are declared by including +libtorrent/create_torrent.hpp.

    +

    example:

    +
    +file_storage fs;
    +
    +// recursively adds files in directories
    +add_files(fs, "./my_torrent");
    +
    +create_torrent t(fs);
    +t.add_tracker("http://my.tracker.com/announce");
    +t.set_creator("libtorrent example");
    +
    +// reads the files and calculates the hashes
    +set_piece_hashes(t, ".");
    +
    +ofstream out("my_torrent.torrent", std::ios_base::binary);
    +bencode(std::ostream_iterator<char>(out), t.generate());
    +
    +[report issue]
    +

    create_torrent

    +

    Declared in "libtorrent/create_torrent.hpp"

    +

    This class holds state for creating a torrent. After having added +all information to it, call create_torrent::generate() to generate +the torrent. The entry that's returned can then be bencoded into a +.torrent file using bencode().

    +
    +struct create_torrent
    +{
    +   explicit create_torrent (torrent_info const& ti);
    +   explicit create_torrent (file_storage& fs, int piece_size = 0
    +      , int pad_file_limit = -1, create_flags_t flags = optimize_alignment
    +      , int alignment = -1);
    +   entry generate () const;
    +   file_storage const& files () const;
    +   void set_comment (char const* str);
    +   void set_creator (char const* str);
    +   void set_hash (piece_index_t index, sha1_hash const& h);
    +   void set_file_hash (file_index_t index, sha1_hash const& h);
    +   void add_http_seed (string_view url);
    +   void add_url_seed (string_view url);
    +   void add_node (std::pair<std::string, int> node);
    +   void add_tracker (string_view url, int tier = 0);
    +   void set_root_cert (string_view pem);
    +   bool priv () const;
    +   void set_priv (bool p);
    +   int num_pieces () const;
    +   int piece_length () const;
    +   int piece_size (piece_index_t i) const;
    +   std::vector<sha1_hash> const& merkle_tree () const;
    +   void add_similar_torrent (sha1_hash ih);
    +   void add_collection (string_view c);
    +
    +   static constexpr create_flags_t optimize_alignment  = 0_bit;
    +   static constexpr create_flags_t merkle  = 1_bit;
    +   static constexpr create_flags_t modification_time  = 2_bit;
    +   static constexpr create_flags_t symlinks  = 3_bit;
    +   static constexpr create_flags_t mutable_torrent_support  = 4_bit;
    +};
    +
    +[report issue]
    +

    create_torrent()

    +
    +explicit create_torrent (torrent_info const& ti);
    +explicit create_torrent (file_storage& fs, int piece_size = 0
    +      , int pad_file_limit = -1, create_flags_t flags = optimize_alignment
    +      , int alignment = -1);
    +
    +

    The piece_size is the size of each piece in bytes. It must +be a multiple of 16 kiB. If a piece size of 0 is specified, a +piece_size will be calculated such that the torrent file is roughly 40 kB.

    +

    If a pad_file_limit is specified (other than -1), any file larger than +the specified number of bytes will be preceded by a pad file to align it +with the start of a piece. The pad_file_limit is ignored unless the +optimize_alignment flag is passed. Typically it doesn't make sense +to set this any lower than 4 kiB.

    +

    The overload that takes a torrent_info object will make a verbatim +copy of its info dictionary (to preserve the info-hash). The copy of +the info dictionary will be used by create_torrent::generate(). This means +that none of the member functions of create_torrent that affects +the content of the info dictionary (such as set_hash()), will +have any affect.

    +

    The flags arguments specifies options for the torrent creation. It can +be any combination of the flags defined by create_flags_t.

    +

    alignment is used when pad files are enabled. This is the size +eligible files are aligned to. The default is -1, which means the +piece size of the torrent.

    +[report issue]
    +
    +

    generate()

    +
    +entry generate () const;
    +
    +

    This function will generate the .torrent file as a bencode tree. In order to +generate the flat file, use the bencode() function.

    +

    It may be useful to add custom entries to the torrent file before bencoding it +and saving it to disk.

    +

    If anything goes wrong during torrent generation, this function will return +an empty entry structure. You can test for this condition by querying the +type of the entry:

    +
    +file_storage fs;
    +// add file ...
    +create_torrent t(fs);
    +// add trackers and piece hashes ...
    +e = t.generate();
    +
    +if (e.type() == entry::undefined_t)
    +{
    +        // something went wrong
    +}
    +
    +

    For instance, you cannot generate a torrent with 0 files in it. If you don't add +any files to the file_storage, torrent generation will fail.

    +[report issue]
    +
    +

    files()

    +
    +file_storage const& files () const;
    +
    +

    returns an immutable reference to the file_storage used to create +the torrent from.

    +[report issue]
    +
    +

    set_comment()

    +
    +void set_comment (char const* str);
    +
    +

    Sets the comment for the torrent. The string str should be utf-8 encoded. +The comment in a torrent file is optional.

    +[report issue]
    +
    +

    set_creator()

    +
    +void set_creator (char const* str);
    +
    +

    Sets the creator of the torrent. The string str should be utf-8 encoded. +This is optional.

    +[report issue]
    +
    +

    set_hash()

    +
    +void set_hash (piece_index_t index, sha1_hash const& h);
    +
    +

    This sets the SHA-1 hash for the specified piece (index). You are required +to set the hash for every piece in the torrent before generating it. If you have +the files on disk, you can use the high level convenience function to do this. +See set_piece_hashes().

    +[report issue]
    +
    +

    set_file_hash()

    +
    +void set_file_hash (file_index_t index, sha1_hash const& h);
    +
    +

    This sets the sha1 hash for this file. This hash will end up under the key sha1 +associated with this file (for multi-file torrents) or in the root info dictionary +for single-file torrents.

    + +[report issue]
    +
    +

    add_http_seed() add_url_seed()

    +
    +void add_http_seed (string_view url);
    +void add_url_seed (string_view url);
    +
    +

    This adds a url seed to the torrent. You can have any number of url seeds. For a +single file torrent, this should be an HTTP url, pointing to a file with identical +content as the file of the torrent. For a multi-file torrent, it should point to +a directory containing a directory with the same name as this torrent, and all the +files of the torrent in it.

    +

    The second function, add_http_seed() adds an HTTP seed instead.

    +[report issue]
    +
    +

    add_node()

    +
    +void add_node (std::pair<std::string, int> node);
    +
    +

    This adds a DHT node to the torrent. This especially useful if you're creating a +tracker less torrent. It can be used by clients to bootstrap their DHT node from. +The node is a hostname and a port number where there is a DHT node running. +You can have any number of DHT nodes in a torrent.

    +[report issue]
    +
    +

    add_tracker()

    +
    +void add_tracker (string_view url, int tier = 0);
    +
    +

    Adds a tracker to the torrent. This is not strictly required, but most torrents +use a tracker as their main source of peers. The url should be an http:// or udp:// +url to a machine running a bittorrent tracker that accepts announces for this torrent's +info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are +tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those +fail, trackers with tier 2 are tried, and so on.

    +[report issue]
    +
    +

    set_root_cert()

    +
    +void set_root_cert (string_view pem);
    +
    +

    This function sets an X.509 certificate in PEM format to the torrent. This makes the +torrent an SSL torrent. An SSL torrent requires that each peer has a valid certificate +signed by this root certificate. For SSL torrents, all peers are connecting over SSL +connections. For more information, see the section on ssl torrents.

    +

    The string is not the path to the cert, it's the actual content of the +certificate.

    + +[report issue]
    +
    +

    priv() set_priv()

    +
    +bool priv () const;
    +void set_priv (bool p);
    +
    +

    Sets and queries the private flag of the torrent. +Torrents with the private flag set ask the client to not use any other +sources than the tracker for peers, and to not use DHT to advertise itself publicly, +only the tracker.

    +[report issue]
    +
    +

    num_pieces()

    +
    +int num_pieces () const;
    +
    +

    returns the number of pieces in the associated file_storage object.

    + +[report issue]
    +
    +

    piece_size() piece_length()

    +
    +int piece_length () const;
    +int piece_size (piece_index_t i) const;
    +
    +

    piece_length() returns the piece size of all pieces but the +last one. piece_size() returns the size of the specified piece. +these functions are just forwarding to the associated file_storage.

    +[report issue]
    +
    +

    merkle_tree()

    +
    +std::vector<sha1_hash> const& merkle_tree () const;
    +
    +

    This function returns the merkle hash tree, if the torrent was created as a merkle +torrent. The tree is created by generate() and won't be valid until that function +has been called. When creating a merkle tree torrent, the actual tree itself has to +be saved off separately and fed into libtorrent the first time you start seeding it, +through the torrent_info::set_merkle_tree() function. From that point onwards, the +tree will be saved in the resume data.

    + +[report issue]
    +
    +

    add_collection() add_similar_torrent()

    +
    +void add_similar_torrent (sha1_hash ih);
    +void add_collection (string_view c);
    +
    +

    Add similar torrents (by info-hash) or collections of similar torrents. +Similar torrents are expected to share some files with this torrent. +Torrents sharing a collection name with this torrent are also expected +to share files with this torrent. A torrent may have more than one +collection and more than one similar torrents. For more information, +see BEP 38.

    +[report issue]
    +
    optimize_alignment
    +
    This will insert pad files to align the files to piece boundaries, for +optimized disk-I/O. This will minimize the number of bytes of pad- +files, to keep the impact down for clients that don't support +them.
    +
    +[report issue]
    +
    merkle
    +
    This will create a merkle hash tree torrent. A merkle torrent cannot +be opened in clients that don't specifically support merkle torrents. +The benefit is that the resulting torrent file will be much smaller and +not grow with more pieces. When this option is specified, it is +recommended to have a fairly small piece size, say 64 kiB. +When creating merkle torrents, the full hash tree is also generated +and should be saved off separately. It is accessed through the +create_torrent::merkle_tree() function.
    +
    +[report issue]
    +
    modification_time
    +
    This will include the file modification time as part of the torrent. +This is not enabled by default, as it might cause problems when you +create a torrent from separate files with the same content, hoping to +yield the same info-hash. If the files have different modification times, +with this option enabled, you would get different info-hashes for the +files.
    +
    +[report issue]
    +
    symlinks
    +
    If this flag is set, files that are symlinks get a symlink attribute +set on them and their data will not be included in the torrent. This +is useful if you need to reconstruct a file hierarchy which contains +symlinks.
    +
    +[report issue]
    +
    mutable_torrent_support
    +
    to create a torrent that can be updated via a mutable torrent +(see BEP 38). This also needs to be enabled for torrents that update +another torrent.
    +
    +[report issue]
    +
    +
    +

    add_files()

    +

    Declared in "libtorrent/create_torrent.hpp"

    +
    +void add_files (file_storage& fs, std::string const& file
    +   , create_flags_t flags = {});
    +void add_files (file_storage& fs, std::string const& file
    +   , std::function<bool(std::string)> p, create_flags_t flags = {});
    +
    +

    Adds the file specified by path to the file_storage object. In case path +refers to a directory, files will be added recursively from the directory.

    +

    If specified, the predicate p is called once for every file and directory that +is encountered. Files for which p returns true are added, and directories for +which p returns true are traversed. p must have the following signature:

    +
    +bool Pred(std::string const& p);
    +
    +

    The path that is passed in to the predicate is the full path of the file or +directory. If no predicate is specified, all files are added, and all directories +are traversed.

    +

    The ".." directory is never traversed.

    +

    The flags argument should be the same as the flags passed to the create_torrent +constructor.

    +[report issue]
    +
    +

    set_piece_hashes()

    +

    Declared in "libtorrent/create_torrent.hpp"

    +
    +inline void set_piece_hashes (create_torrent& t, std::string const& p);
    +void set_piece_hashes (create_torrent& t, std::string const& p
    +   , std::function<void(piece_index_t)> const& f, error_code& ec);
    +inline void set_piece_hashes (create_torrent& t, std::string const& p
    +   , std::function<void(piece_index_t)> const& f);
    +inline void set_piece_hashes (create_torrent& t, std::string const& p, error_code& ec);
    +
    +

    This function will assume that the files added to the torrent file exists at path +p, read those files and hash the content and set the hashes in the create_torrent +object. The optional function f is called in between every hash that is set. f +must have the following signature:

    +
    +void Fun(piece_index_t);
    +
    +

    The overloads that don't take an error_code& may throw an exception in case of a +file error, the other overloads sets the error code to reflect the error, if any.

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Error Codes

    +[report issue]
    +

    storage_error

    +

    Declared in "libtorrent/error_code.hpp"

    +

    used by storage to return errors +also includes which underlying file the +error happened on

    +
    +struct storage_error
    +{
    +   explicit operator bool () const;
    +   file_index_t file () const;
    +   void file (file_index_t f);
    +
    +   error_code ec;
    +   operation_t operation;
    +};
    +
    +[report issue]
    +

    bool()

    +
    +explicit operator bool () const;
    +
    +

    explicitly converts to true if this object represents an error, and +false if it does not.

    +[report issue]
    +
    +

    file()

    +
    +file_index_t file () const;
    +void file (file_index_t f);
    +
    +

    set and query the index (in the torrent) of the file this error +occurred on. This may also have special values defined in +torrent_status.

    +[report issue]
    +
    ec
    +
    the error that occurred
    +
    +[report issue]
    +
    operation
    +
    A code from operation_t enum, indicating what +kind of operation failed.
    +
    +[report issue]
    +
    +
    +

    libtorrent_category()

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +boost::system::error_category& libtorrent_category ();
    +
    +

    return the instance of the libtorrent_error_category which +maps libtorrent error codes to human readable error messages.

    +[report issue]
    +
    +

    http_category()

    +

    Declared in "libtorrent/error_code.hpp"

    +
    +boost::system::error_category& http_category ();
    +
    +

    returns the error_category for HTTP errors

    +[report issue]
    +
    +

    pcp_category()

    +

    Declared in "libtorrent/natpmp.hpp"

    +
    +boost::system::error_category& pcp_category ();
    +
    +[report issue]
    +
    +

    upnp_category()

    +

    Declared in "libtorrent/upnp.hpp"

    +
    +boost::system::error_category& upnp_category ();
    +
    +

    the boost.system error category for UPnP errors

    +[report issue]
    +
    +

    gzip_category()

    +

    Declared in "libtorrent/gzip.hpp"

    +
    +boost::system::error_category& gzip_category ();
    +
    +

    get the error_category for zip errors

    +[report issue]
    +
    +

    bdecode_category()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +boost::system::error_category& bdecode_category ();
    +
    +[report issue]
    +
    +

    socks_category()

    +

    Declared in "libtorrent/socks5_stream.hpp"

    +
    +boost::system::error_category& socks_category ();
    +
    +

    returns the error_category for SOCKS5 errors

    +[report issue]
    +
    +

    i2p_category()

    +

    Declared in "libtorrent/i2p_stream.hpp"

    +
    +boost::system::error_category& i2p_category ();
    +
    +

    returns the error category for I2P errors

    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/error_code.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    file_collision1Two torrents has files which end up overwriting each other
    failed_hash_check2A piece did not match its piece hash
    torrent_is_no_dict3The .torrent file does not contain a bencoded dictionary at +its top level
    torrent_missing_info4The .torrent file does not have an info dictionary
    torrent_info_no_dict5The .torrent file's info entry is not a dictionary
    torrent_missing_piece_length6The .torrent file does not have a piece length entry
    torrent_missing_name7The .torrent file does not have a name entry
    torrent_invalid_name8The .torrent file's name entry is invalid
    torrent_invalid_length9The length of a file, or of the whole .torrent file is invalid. +Either negative or not an integer
    torrent_file_parse_failed10Failed to parse a file entry in the .torrent
    torrent_missing_pieces11The pieces field is missing or invalid in the .torrent file
    torrent_invalid_hashes12The pieces string has incorrect length
    too_many_pieces_in_torrent13The .torrent file has more pieces than is supported by libtorrent
    invalid_swarm_metadata14The metadata (.torrent file) that was received from the swarm +matched the info-hash, but failed to be parsed
    invalid_bencoding15The file or buffer is not correctly bencoded
    no_files_in_torrent16The .torrent file does not contain any files
    invalid_escaped_string17The string was not properly url-encoded as expected
    session_is_closing18Operation is not permitted since the session is shutting down
    duplicate_torrent19There's already a torrent with that info-hash added to the +session
    invalid_torrent_handle20The supplied torrent_handle is not referring to a valid torrent
    invalid_entry_type21The type requested from the entry did not match its type
    missing_info_hash_in_uri22The specified URI does not contain a valid info-hash
    file_too_short23One of the files in the torrent was unexpectedly small. This +might be caused by files being changed by an external process
    unsupported_url_protocol24The URL used an unknown protocol. Currently http and +https (if built with openssl support) are recognized. For +trackers udp is recognized as well.
    url_parse_error25The URL did not conform to URL syntax and failed to be parsed
    peer_sent_empty_piece26The peer sent a piece message of length 0
    parse_failed27A bencoded structure was corrupt and failed to be parsed
    invalid_file_tag28The fast resume file was missing or had an invalid file version +tag
    missing_info_hash29The fast resume file was missing or had an invalid info-hash
    mismatching_info_hash30The info-hash did not match the torrent
    invalid_hostname31The URL contained an invalid hostname
    invalid_port32The URL had an invalid port
    port_blocked33The port is blocked by the port-filter, and prevented the +connection
    expected_close_bracket_in_address34The IPv6 address was expected to end with "]"
    destructing_torrent35The torrent is being destructed, preventing the operation to +succeed
    timed_out36The connection timed out
    upload_upload_connection37The peer is upload only, and we are upload only. There's no point +in keeping the connection
    uninteresting_upload_peer38The peer is upload only, and we're not interested in it. There's +no point in keeping the connection
    invalid_info_hash39The peer sent an unknown info-hash
    torrent_paused40The torrent is paused, preventing the operation from succeeding
    invalid_have41The peer sent an invalid have message, either wrong size or +referring to a piece that doesn't exist in the torrent
    invalid_bitfield_size42The bitfield message had the incorrect size
    too_many_requests_when_choked43The peer kept requesting pieces after it was choked, possible +abuse attempt.
    invalid_piece44The peer sent a piece message that does not correspond to a +piece request sent by the client
    no_memory45memory allocation failed
    torrent_aborted46The torrent is aborted, preventing the operation to succeed
    self_connection47The peer is a connection to ourself, no point in keeping it
    invalid_piece_size48The peer sent a piece message with invalid size, either negative +or greater than one block
    timed_out_no_interest49The peer has not been interesting or interested in us for too +long, no point in keeping it around
    timed_out_inactivity50The peer has not said anything in a long time, possibly dead
    timed_out_no_handshake51The peer did not send a handshake within a reasonable amount of +time, it might not be a bittorrent peer
    timed_out_no_request52The peer has been unchoked for too long without requesting any +data. It might be lying about its interest in us
    invalid_choke53The peer sent an invalid choke message
    invalid_unchoke54The peer send an invalid unchoke message
    invalid_interested55The peer sent an invalid interested message
    invalid_not_interested56The peer sent an invalid not-interested message
    invalid_request57The peer sent an invalid piece request message
    invalid_hash_list58The peer sent an invalid hash-list message (this is part of the +merkle-torrent extension)
    invalid_hash_piece59The peer sent an invalid hash-piece message (this is part of the +merkle-torrent extension)
    invalid_cancel60The peer sent an invalid cancel message
    invalid_dht_port61The peer sent an invalid DHT port-message
    invalid_suggest62The peer sent an invalid suggest piece-message
    invalid_have_all63The peer sent an invalid have all-message
    invalid_have_none64The peer sent an invalid have none-message
    invalid_reject65The peer sent an invalid reject message
    invalid_allow_fast66The peer sent an invalid allow fast-message
    invalid_extended67The peer sent an invalid extension message ID
    invalid_message68The peer sent an invalid message ID
    sync_hash_not_found69The synchronization hash was not found in the encrypted handshake
    invalid_encryption_constant70The encryption constant in the handshake is invalid
    no_plaintext_mode71The peer does not support plain text, which is the selected mode
    no_rc4_mode72The peer does not support RC4, which is the selected mode
    unsupported_encryption_mode73The peer does not support any of the encryption modes that the +client supports
    unsupported_encryption_mode_selected74The peer selected an encryption mode that the client did not +advertise and does not support
    invalid_pad_size75The pad size used in the encryption handshake is of invalid size
    invalid_encrypt_handshake76The encryption handshake is invalid
    no_incoming_encrypted77The client is set to not support incoming encrypted connections +and this is an encrypted connection
    no_incoming_regular78The client is set to not support incoming regular bittorrent +connections, and this is a regular connection
    duplicate_peer_id79The client is already connected to this peer-ID
    torrent_removed80Torrent was removed
    packet_too_large81The packet size exceeded the upper sanity check-limit
    reserved82 
    http_error83The web server responded with an error
    missing_location84The web server response is missing a location header
    invalid_redirection85The web seed redirected to a path that no longer matches the +.torrent directory structure
    redirecting86The connection was closed because it redirected to a different +URL
    invalid_range87The HTTP range header is invalid
    no_content_length88The HTTP response did not have a content length
    banned_by_ip_filter89The IP is blocked by the IP filter
    too_many_connections90At the connection limit
    peer_banned91The peer is marked as banned
    stopping_torrent92The torrent is stopping, causing the operation to fail
    too_many_corrupt_pieces93The peer has sent too many corrupt pieces and is banned
    torrent_not_ready94The torrent is not ready to receive peers
    peer_not_constructed95The peer is not completely constructed yet
    session_closing96The session is closing, causing the operation to fail
    optimistic_disconnect97The peer was disconnected in order to leave room for a +potentially better peer
    torrent_finished98The torrent is finished
    no_router99No UPnP router found
    metadata_too_large100The metadata message says the metadata exceeds the limit
    invalid_metadata_request101The peer sent an invalid metadata request message
    invalid_metadata_size102The peer advertised an invalid metadata size
    invalid_metadata_offset103The peer sent a message with an invalid metadata offset
    invalid_metadata_message104The peer sent an invalid metadata message
    pex_message_too_large105The peer sent a peer exchange message that was too large
    invalid_pex_message106The peer sent an invalid peer exchange message
    invalid_lt_tracker_message107The peer sent an invalid tracker exchange message
    too_frequent_pex108The peer sent an pex messages too often. This is a possible +attempt of and attack
    no_metadata109The operation failed because it requires the torrent to have +the metadata (.torrent file) and it doesn't have it yet. +This happens for magnet links before they have downloaded the +metadata, and also torrents added by URL.
    invalid_dont_have110The peer sent an invalid dont_have message. The don't have +message is an extension to allow peers to advertise that the +no longer has a piece they previously had.
    requires_ssl_connection111The peer tried to connect to an SSL torrent without connecting +over SSL.
    invalid_ssl_cert112The peer tried to connect to a torrent with a certificate +for a different torrent.
    not_an_ssl_torrent113the torrent is not an SSL torrent, and the operation requires +an SSL torrent
    banned_by_port_filter114peer was banned because its listen port is within a banned port +range, as specified by the port_filter.
    invalid_session_handle115The session_handle is not referring to a valid session_impl
    invalid_listen_socket116the listen socket associated with this request was closed
    deprecated_120120 
    deprecated_121121 
    deprecated_122122 
    deprecated_123123 
    deprecated_124124 
    missing_file_sizes130The resume data file is missing the file sizes entry
    no_files_in_resume_data131The resume data file file sizes entry is empty
    missing_pieces132The resume data file is missing the pieces and slots entry
    mismatching_number_of_files133The number of files in the resume data does not match the number +of files in the torrent
    mismatching_file_size134One of the files on disk has a different size than in the fast +resume file
    mismatching_file_timestamp135One of the files on disk has a different timestamp than in the +fast resume file
    not_a_dictionary136The resume data file is not a dictionary
    invalid_blocks_per_piece137The blocks per piece entry is invalid in the resume data file
    missing_slots138The resume file is missing the slots entry, which is required +for torrents with compact allocation. DEPRECATED
    too_many_slots139The resume file contains more slots than the torrent
    invalid_slot_list140The slot entry is invalid in the resume data
    invalid_piece_index141One index in the slot list is invalid
    pieces_need_reorder142The pieces on disk needs to be re-ordered for the specified +allocation mode. This happens if you specify sparse allocation +and the files on disk are using compact storage. The pieces needs +to be moved to their right position. DEPRECATED
    resume_data_not_modified143this error is returned when asking to save resume data and +specifying the flag to only save when there's anything new to save +(torrent_handle::only_if_modified) and there wasn't anything changed.
    http_parse_error150The HTTP header was not correctly formatted
    http_missing_location151The HTTP response was in the 300-399 range but lacked a location +header
    http_failed_decompress152The HTTP response was encoded with gzip or deflate but +decompressing it failed
    no_i2p_router160The URL specified an i2p address, but no i2p router is configured
    no_i2p_endpoint161i2p acceptor is not available yet, can't announce without endpoint
    scrape_not_available170The tracker URL doesn't support transforming it into a scrape +URL. i.e. it doesn't contain "announce.
    invalid_tracker_response171invalid tracker response
    invalid_peer_dict172invalid peer dictionary entry. Not a dictionary
    tracker_failure173tracker sent a failure message
    invalid_files_entry174missing or invalid files entry
    invalid_hash_entry175missing or invalid hash entry
    invalid_peers_entry176missing or invalid peers and peers6 entry
    invalid_tracker_response_length177UDP tracker response packet has invalid size
    invalid_tracker_transaction_id178invalid transaction id in UDP tracker response
    invalid_tracker_action179invalid action field in UDP tracker response
    no_entropy200random number generation failed
    error_code_max201the number of error codes
    +[report issue]
    +
    +

    enum http_errors

    +

    Declared in "libtorrent/error_code.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    cont100 
    ok200 
    created201 
    accepted202 
    no_content204 
    multiple_choices300 
    moved_permanently301 
    moved_temporarily302 
    not_modified304 
    bad_request400 
    unauthorized401 
    forbidden403 
    not_found404 
    internal_server_error500 
    not_implemented501 
    bad_gateway502 
    service_unavailable503 
    +[report issue]
    +
    +

    enum pcp_errors

    +

    Declared in "libtorrent/natpmp.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pcp_success0 
    pcp_unsupp_version1 
    pcp_not_authorized2 
    pcp_malformed_request3 
    pcp_unsupp_opcode4 
    pcp_unsupp_option5 
    pcp_malformed_option6 
    pcp_network_failure7 
    pcp_no_resources8 
    pcp_unsupp_protocol9 
    pcp_user_ex_quota10 
    pcp_cannot_provide_external11 
    pcp_address_mismatch12 
    pcp_excessive_remote_peers13 
    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/upnp.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0No error
    invalid_argument402One of the arguments in the request is invalid
    action_failed501The request failed
    value_not_in_array714The specified value does not exist in the array
    source_ip_cannot_be_wildcarded715The source IP address cannot be wild-carded, but +must be fully specified
    external_port_cannot_be_wildcarded716The external port cannot be a wildcard, but must +be specified
    port_mapping_conflict718The port mapping entry specified conflicts with a +mapping assigned previously to another client
    internal_port_must_match_external724Internal and external port value must be the same
    only_permanent_leases_supported725The NAT implementation only supports permanent +lease times on port mappings
    remote_host_must_be_wildcard726RemoteHost must be a wildcard and cannot be a +specific IP address or DNS name
    external_port_must_be_wildcard727ExternalPort must be a wildcard and cannot be a +specific port
    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/gzip.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    invalid_gzip_header1the supplied gzip buffer has invalid header
    inflated_data_too_large2the gzip buffer would inflate to more bytes than the specified +maximum size, and was rejected.
    data_did_not_terminate3available inflate data did not terminate
    space_exhausted4output space exhausted before completing inflate
    invalid_block_type5invalid block type (type == 3)
    invalid_stored_block_length6stored block length did not match one's complement
    too_many_length_or_distance_codes7dynamic block code description: too many length or distance codes
    code_lengths_codes_incomplete8dynamic block code description: code lengths codes incomplete
    repeat_lengths_with_no_first_length9dynamic block code description: repeat lengths with no first length
    repeat_more_than_specified_lengths10dynamic block code description: repeat more than specified lengths
    invalid_literal_length_code_lengths11dynamic block code description: invalid literal/length code lengths
    invalid_distance_code_lengths12dynamic block code description: invalid distance code lengths
    invalid_literal_code_in_block13invalid literal/length or distance code in fixed or dynamic block
    distance_too_far_back_in_block14distance is too far back in fixed or dynamic block
    unknown_gzip_error15an unknown error occurred during gzip inflation
    error_code_max16the number of error codes
    +[report issue]
    +
    +

    enum error_code_enum

    +

    Declared in "libtorrent/bdecode.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0Not an error
    expected_digit1expected digit in bencoded string
    expected_colon2expected colon in bencoded string
    unexpected_eof3unexpected end of file in bencoded string
    expected_value4expected value (list, dict, int or string) in bencoded string
    depth_exceeded5bencoded recursion depth limit exceeded
    limit_exceeded6bencoded item count limit exceeded
    overflow7integer overflow
    error_code_max8the number of error codes
    +[report issue]
    +
    +

    enum socks_error_code

    +

    Declared in "libtorrent/socks5_stream.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    unsupported_version1 
    unsupported_authentication_method2 
    unsupported_authentication_version3 
    authentication_error4 
    username_required5 
    general_failure6 
    command_not_supported7 
    no_identd8 
    identd_error9 
    num_errors10 
    +[report issue]
    +
    +

    enum i2p_error_code

    +

    Declared in "libtorrent/i2p_stream.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    parse_failed1 
    cant_reach_peer2 
    i2p_error3 
    invalid_key4 
    invalid_id5 
    timeout6 
    key_not_found7 
    duplicated_id8 
    num_errors9 
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Storage

    +[report issue]
    +

    storage_params

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +struct storage_params
    +{
    +   storage_params (file_storage const& f, file_storage const* mf
    +      , std::string const& sp, storage_mode_t const sm
    +      , aux::vector<download_priority_t, file_index_t> const& prio
    +      , sha1_hash const& ih);
    +
    +   file_storage const& files;
    +   file_storage const* mapped_files  = nullptr;
    +   std::string const& path;
    +   storage_mode_t mode {storage_mode_sparse};
    +   aux::vector<download_priority_t, file_index_t> const& priorities;
    +   sha1_hash const& info_hash;
    +};
    +
    +[report issue]
    +
    +

    file_slice

    +

    Declared in "libtorrent/file_storage.hpp"

    +

    represents a window of a file in a torrent.

    +

    The file_index refers to the index of the file (in the torrent_info). +To get the path and filename, use file_path() and give the file_index +as argument. The offset is the byte offset in the file where the range +starts, and size is the number of bytes this range is. The size + offset +will never be greater than the file size.

    +
    +struct file_slice
    +{
    +   file_index_t file_index;
    +   std::int64_t offset;
    +   std::int64_t size;
    +};
    +
    +[report issue]
    +
    file_index
    +
    the index of the file
    +
    +[report issue]
    +
    offset
    +
    the offset from the start of the file, in bytes
    +
    +[report issue]
    +
    size
    +
    the size of the window, in bytes
    +
    +[report issue]
    +
    +

    file_storage

    +

    Declared in "libtorrent/file_storage.hpp"

    +

    The file_storage class represents a file list and the piece +size. Everything necessary to interpret a regular bittorrent storage +file structure.

    +
    +class file_storage
    +{
    +   bool is_valid () const;
    +   void reserve (int num_files);
    +   void add_file (std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}
    +      , std::time_t mtime = 0, string_view symlink_path = string_view());
    +   void add_file_borrow (string_view filename
    +      , std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}, char const* filehash = nullptr
    +      , std::int64_t mtime = 0, string_view symlink_path = string_view());
    +   void rename_file (file_index_t index, std::string const& new_filename);
    +   std::vector<file_slice> map_block (piece_index_t piece, std::int64_t offset
    +      , int size) const;
    +   peer_request map_file (file_index_t file, std::int64_t offset, int size) const;
    +   int num_files () const noexcept;
    +   file_index_t end_file () const noexcept;
    +   index_range<file_index_t> file_range () const noexcept;
    +   std::int64_t total_size () const;
    +   void set_num_pieces (int n);
    +   int num_pieces () const;
    +   piece_index_t end_piece () const;
    +   piece_index_t last_piece () const;
    +   index_range<piece_index_t> piece_range () const noexcept;
    +   int piece_length () const;
    +   void set_piece_length (int l);
    +   int piece_size (piece_index_t index) const;
    +   std::string const& name () const;
    +   void set_name (std::string const& n);
    +   void swap (file_storage& ti) noexcept;
    +   void optimize (int pad_file_limit = -1, int alignment = -1
    +      , bool tail_padding = false);
    +   std::string const& symlink (file_index_t index) const;
    +   bool pad_file_at (file_index_t index) const;
    +   std::time_t mtime (file_index_t index) const;
    +   std::int64_t file_offset (file_index_t index) const;
    +   std::int64_t file_size (file_index_t index) const;
    +   string_view file_name (file_index_t index) const;
    +   sha1_hash hash (file_index_t index) const;
    +   std::string file_path (file_index_t index, std::string const& save_path = "") const;
    +   std::uint32_t file_path_hash (file_index_t index, std::string const& save_path) const;
    +   void all_path_hashes (std::unordered_set<std::uint32_t>& table) const;
    +   std::vector<std::string> const& paths () const;
    +   file_flags_t file_flags (file_index_t index) const;
    +   bool file_absolute_path (file_index_t index) const;
    +   file_index_t file_index_at_offset (std::int64_t offset) const;
    +   int file_name_len (file_index_t index) const;
    +   char const* file_name_ptr (file_index_t index) const;
    +   void apply_pointer_offset (std::ptrdiff_t off);
    +   void sanitize_symlinks ();
    +
    +   static constexpr file_flags_t flag_pad_file  = 0_bit;
    +   static constexpr file_flags_t flag_hidden  = 1_bit;
    +   static constexpr file_flags_t flag_executable  = 2_bit;
    +   static constexpr file_flags_t flag_symlink  = 3_bit;
    +};
    +
    +[report issue]
    +

    is_valid()

    +
    +bool is_valid () const;
    +
    +

    returns true if the piece length has been initialized +on the file_storage. This is typically taken as a proxy +of whether the file_storage as a whole is initialized or +not.

    +[report issue]
    +
    +

    reserve()

    +
    +void reserve (int num_files);
    +
    +

    allocates space for num_files in the internal file list. This can +be used to avoid reallocating the internal file list when the number +of files to be added is known up-front.

    + +[report issue]
    +
    +

    add_file_borrow() add_file()

    +
    +void add_file (std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}
    +      , std::time_t mtime = 0, string_view symlink_path = string_view());
    +void add_file_borrow (string_view filename
    +      , std::string const& path, std::int64_t file_size
    +      , file_flags_t file_flags = {}, char const* filehash = nullptr
    +      , std::int64_t mtime = 0, string_view symlink_path = string_view());
    +
    +

    Adds a file to the file storage. The add_file_borrow version +expects that filename is the file name (without a path) of +the file that's being added. +This memory is borrowed, i.e. it is the caller's +responsibility to make sure it stays valid throughout the lifetime +of this file_storage object or any copy of it. The same thing applies +to filehash, which is an optional pointer to a 20 byte binary +SHA-1 hash of the file.

    +

    if filename is empty, the filename from path is used and not +borrowed.

    +

    The path argument is the full path (in the torrent file) to +the file to add. Note that this is not supposed to be an absolute +path, but it is expected to include the name of the torrent as the +first path element.

    +

    file_size is the size of the file in bytes.

    +

    The file_flags argument sets attributes on the file. The file +attributes is an extension and may not work in all bittorrent clients.

    +

    For possible file attributes, see file_storage::flags_t.

    +

    The mtime argument is optional and can be set to 0. If non-zero, +it is the posix time of the last modification time of this file.

    +

    symlink_path is the path the file is a symlink to. To make this a +symlink you also need to set the file_storage::flag_symlink file flag.

    +

    If more files than one are added, certain restrictions to their paths +apply. In a multi-file file storage (torrent), all files must share +the same root directory.

    +

    That is, the first path element of all files must be the same. +This shared path element is also set to the name of the torrent. It +can be changed by calling set_name.

    +[report issue]
    +
    +

    rename_file()

    +
    +void rename_file (file_index_t index, std::string const& new_filename);
    +
    +

    renames the file at index to new_filename. Keep in mind +that filenames are expected to be UTF-8 encoded.

    +[report issue]
    +
    +

    map_block()

    +
    +std::vector<file_slice> map_block (piece_index_t piece, std::int64_t offset
    +      , int size) const;
    +
    +

    returns a list of file_slice objects representing the portions of +files the specified piece index, byte offset and size range overlaps. +this is the inverse mapping of map_file().

    +

    Preconditions of this function is that the input range is within the +torrents address space. piece may not be negative and

    +
    +piece * piece_size + offset + size
    +

    may not exceed the total size of the torrent.

    +[report issue]
    +
    +

    map_file()

    +
    +peer_request map_file (file_index_t file, std::int64_t offset, int size) const;
    +
    +

    returns a peer_request representing the piece index, byte offset +and size the specified file range overlaps. This is the inverse +mapping over map_block(). Note that the peer_request return type +is meant to hold bittorrent block requests, which may not be larger +than 16 kiB. Mapping a range larger than that may return an overflown +integer.

    +[report issue]
    +
    +

    num_files()

    +
    +int num_files () const noexcept;
    +
    +

    returns the number of files in the file_storage

    +[report issue]
    +
    +

    end_file()

    +
    +file_index_t end_file () const noexcept;
    +
    +

    returns the index of the one-past-end file in the file storage

    +[report issue]
    +
    +

    file_range()

    +
    +index_range<file_index_t> file_range () const noexcept;
    +
    +

    returns an implementation-defined type that can be used as the +container in a range-for loop. Where the values are the indices of all +files in the file_storage.

    +[report issue]
    +
    +

    total_size()

    +
    +std::int64_t total_size () const;
    +
    +

    returns the total number of bytes all the files in this torrent spans

    + +[report issue]
    +
    +

    num_pieces() set_num_pieces()

    +
    +void set_num_pieces (int n);
    +int num_pieces () const;
    +
    +

    set and get the number of pieces in the torrent

    +[report issue]
    +
    +

    end_piece()

    +
    +piece_index_t end_piece () const;
    +
    +

    returns the index of the one-past-end piece in the file storage

    +[report issue]
    +
    +

    last_piece()

    +
    +piece_index_t last_piece () const;
    +
    +

    returns the index of the last piece in the torrent. The last piece is +special in that it may be smaller than the other pieces (and the other +pieces are all the same size).

    +[report issue]
    +
    +

    piece_range()

    +
    +index_range<piece_index_t> piece_range () const noexcept;
    +
    +

    returns an implementation-defined type that can be used as the +container in a range-for loop. Where the values are the indices of all +pieces in the file_storage.

    + +[report issue]
    +
    +

    set_piece_length() piece_length()

    +
    +int piece_length () const;
    +void set_piece_length (int l);
    +
    +

    set and get the size of each piece in this torrent. This size is typically an even power +of 2. It doesn't have to be though. It should be divisible by 16 kiB however.

    +[report issue]
    +
    +

    piece_size()

    +
    +int piece_size (piece_index_t index) const;
    +
    +

    returns the piece size of index. This will be the same as piece_length(), except +for the last piece, which may be shorter.

    + +[report issue]
    +
    +

    name() set_name()

    +
    +std::string const& name () const;
    +void set_name (std::string const& n);
    +
    +

    set and get the name of this torrent. For multi-file torrents, this is also +the name of the root directory all the files are stored in.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (file_storage& ti) noexcept;
    +
    +

    swap all content of this with ti.

    +[report issue]
    +
    +

    optimize()

    +
    +void optimize (int pad_file_limit = -1, int alignment = -1
    +      , bool tail_padding = false);
    +
    +

    if pad_file_limit >= 0, files larger than that limit will be padded, +default is to not add any padding (-1). The alignment specifies the +alignment files should be padded to. This defaults to the piece size +(-1) but it may also make sense to set it to 16 kiB, or something +divisible by 16 kiB. +If pad_file_limit is 0, every file will be padded (except empty ones). +tail_padding indicates whether aligned files also are padded at +the end to make them end aligned. This is required for mutable +torrents, since piece hashes are compared

    + + + + + + + +[report issue]
    + +
    +

    file_path_hash()

    +
    +std::uint32_t file_path_hash (file_index_t index, std::string const& save_path) const;
    +
    +

    returns the crc32 hash of file_path(index)

    +[report issue]
    +
    +

    all_path_hashes()

    +
    +void all_path_hashes (std::unordered_set<std::uint32_t>& table) const;
    +
    +

    this will add the CRC32 hash of all directory entries to the table. No +filename will be included, just directories. Every depth of directories +are added separately to allow test for collisions with files at all +levels. i.e. if one path in the torrent is foo/bar/baz, the CRC32 +hashes for foo, foo/bar and foo/bar/baz will be added to +the set.

    +[report issue]
    +
    +

    paths()

    +
    +std::vector<std::string> const& paths () const;
    +
    +

    returns all directories used in the torrent. Files in the torrent are +located in one of these directories. This is not a tree, it's a flat +list of all leaf directories. i.e. the union of the parent paths of +all files.

    +[report issue]
    +
    +

    file_flags()

    +
    +file_flags_t file_flags (file_index_t index) const;
    +
    +

    returns a bitmask of flags from file_flags_t that apply +to file at index.

    +[report issue]
    +
    +

    file_absolute_path()

    +
    +bool file_absolute_path (file_index_t index) const;
    +
    +

    returns true if the file at the specified index has been renamed to +have an absolute path, i.e. is not anchored in the save path of the +torrent.

    +[report issue]
    +
    +

    file_index_at_offset()

    +
    +file_index_t file_index_at_offset (std::int64_t offset) const;
    +
    +

    returns the index of the file at the given offset in the torrent

    + +[report issue]
    +
    +

    file_name_len() file_name_ptr()

    +
    +int file_name_len (file_index_t index) const;
    +char const* file_name_ptr (file_index_t index) const;
    +
    +

    low-level function. returns a pointer to the internal storage for +the filename. This string may not be 0-terminated! +the file_name_len() function returns the length of the filename. +prefer to use file_name() instead, which returns a string_view.

    +[report issue]
    +
    +

    apply_pointer_offset()

    +
    +void apply_pointer_offset (std::ptrdiff_t off);
    +
    +

    if the backing buffer changed for this storage, this is the pointer +offset to add to any pointers to make them point into the new buffer

    +[report issue]
    + +
    +
    +

    default_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* default_storage_constructor (storage_params const&
    +   , file_pool& p);
    +
    +

    the constructor function for the regular file storage. This is the +default value for add_torrent_params::storage.

    +[report issue]
    +
    +

    disabled_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* disabled_storage_constructor (storage_params const&, file_pool&);
    +
    +

    the constructor function for the disabled storage. This can be used for +testing and benchmarking. It will throw away any data written to +it and return garbage for anything read from it.

    +[report issue]
    +
    +

    zero_storage_constructor()

    +

    Declared in "libtorrent/storage_defs.hpp"

    +
    +storage_interface* zero_storage_constructor (storage_params const&, file_pool&);
    +
    +

    the constructor function for the "zero" storage. This will always read +zeros and ignore all writes.

    +[report issue]
    +
    +

    enum storage_mode_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    storage_mode_allocate0All pieces will be written to their final position, all files will be +allocated in full when the torrent is first started. This is done with +fallocate() and similar calls. This mode minimizes fragmentation.
    storage_mode_sparse1All pieces will be written to the place where they belong and sparse files +will be used. This is the recommended, and default mode.
    +[report issue]
    +
    +

    enum status_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_error0 
    fatal_disk_error1 
    need_full_check2 
    file_exist3 
    +[report issue]
    +
    +

    enum move_flags_t

    +

    Declared in "libtorrent/storage_defs.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    always_replace_files0replace any files in the destination when copying +or moving the storage
    fail_if_exist1if any files that we want to copy exist in the destination +exist, fail the whole operation and don't perform +any copy or move. There is an inherent race condition +in this mode. The files are checked for existence before +the operation starts. In between the check and performing +the copy, the destination files may be created, in which +case they are replaced.
    dont_replace2if any file exist in the target, take those files instead +of the ones we may have in the source.
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Custom Storage

    +

    libtorrent provides a customization point for storage of data. By default, +(default_storage) downloaded files are saved to disk according with the +general conventions of bittorrent clients, mimicking the original file layout +when the torrent was created. The libtorrent user may define a custom +storage to store piece data in a different way.

    +

    A custom storage implementation must derive from and implement the +storage_interface. You must also provide a function that constructs the +custom storage object and provide this function to the add_torrent() call +via add_torrent_params. Either passed in to the constructor or by setting +the add_torrent_params::storage field.

    +

    This is an example storage implementation that stores all pieces in a +std::map, i.e. in RAM. It's not necessarily very useful in practice, but +illustrates the basics of implementing a custom storage.

    +
    +struct temp_storage : lt::storage_interface
    +{
    +  explicit temp_storage(lt::file_storage const& fs) : lt::storage_interface(fs) {}
    +  void initialize(lt::storage_error&) override {}
    +  bool has_any_file(lt::storage_error&) override { return false; }
    +  void set_file_priority(lt::aux::vector<lt::download_priority_t, lt::file_index_t>&
    +    , lt::storage_error&) override {}
    +  int readv(lt::span<lt::iovec_t const> bufs, lt::piece_index_t piece
    +    , int offset, lt::open_mode_t, lt::storage_error&) override
    +  {
    +    auto const i = m_file_data.find(piece);
    +    if (i == m_file_data.end()) return 0;
    +    if (int(i->second.size()) <= offset) return 0;
    +    lt::iovec_t data{ i->second.data() + offset, int(i->second.size() - offset) };
    +    int ret = 0;
    +    for (lt::iovec_t const& b : bufs) {
    +      int const to_copy = std::min(int(b.size()), int(data.size()));
    +      memcpy(b.data(), data.data(), to_copy);
    +      data = data.subspan(to_copy);
    +      ret += to_copy;
    +      if (data.empty()) break;
    +    }
    +    return ret;
    +  }
    +  int writev(lt::span<lt::iovec_t const> bufs
    +    , lt::piece_index_t const piece, int offset, lt::open_mode_t, lt::storage_error&) override
    +  {
    +    auto& data = m_file_data[piece];
    +    int ret = 0;
    +    for (auto& b : bufs) {
    +      if (int(data.size()) < offset + b.size()) data.resize(offset + b.size());
    +      std::memcpy(data.data() + offset, b.data(), b.size());
    +      offset += int(b.size());
    +      ret += int(b.size());
    +    }
    +    return ret;
    +  }
    +  void rename_file(lt::file_index_t, std::string const&, lt::storage_error&) override
    +  { assert(false); }
    +  lt::status_t move_storage(std::string const&
    +    , lt::move_flags_t, lt::storage_error&) override { return lt::status_t::no_error; }
    +  bool verify_resume_data(lt::add_torrent_params const&
    +    , lt::aux::vector<std::string, lt::file_index_t> const&
    +    , lt::storage_error&) override
    +  { return false; }
    +  void release_files(lt::storage_error&) override {}
    +  void delete_files(lt::remove_flags_t, lt::storage_error&) override {}
    +
    +  std::map<lt::piece_index_t, std::vector<char>> m_file_data;
    +};
    +
    +lt::storage_interface* temp_storage_constructor(lt::storage_params const& params, lt::file_pool&)
    +{
    +  return new temp_storage(params.files);
    +}
    +
    +[report issue]
    +

    storage_interface

    +

    Declared in "libtorrent/storage.hpp"

    +

    The storage interface is a pure virtual class that can be implemented to +customize how and where data for a torrent is stored. The default storage +implementation uses regular files in the filesystem, mapping the files in +the torrent in the way one would assume a torrent is saved to disk. +Implementing your own storage interface makes it possible to store all +data in RAM, or in some optimized order on disk (the order the pieces are +received for instance), or saving multi file torrents in a single file in +order to be able to take advantage of optimized disk-I/O.

    +

    It is also possible to write a thin class that uses the default storage +but modifies some particular behavior, for instance encrypting the data +before it's written to disk, and decrypting it when it's read again.

    +

    The storage interface is based on pieces. Every read and write operation +happens in the piece-space. Each piece fits piece_size number +of bytes. All access is done by writing and reading whole or partial +pieces.

    +

    libtorrent comes with two built-in storage implementations; +default_storage and disabled_storage. Their constructor functions +are called default_storage_constructor() and +disabled_storage_constructor respectively. The disabled storage does +just what it sounds like. It throws away data that's written, and it +reads garbage. It's useful mostly for benchmarking and profiling purpose.

    +
    +struct storage_interface: std::enable_shared_from_this<storage_interface>, aux::disk_job_fence, aux::storage_piece_set
    +{
    +   explicit storage_interface (file_storage const& fs);
    +   storage_interface (storage_interface const&) = delete;
    +   storage_interface& operator= (storage_interface const&) = delete;
    +   virtual void initialize (storage_error& ec) = 0;
    +   virtual int writev (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +   virtual int readv (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +   virtual bool has_any_file (storage_error& ec) = 0;
    +   virtual void set_file_priority (aux::vector<download_priority_t, file_index_t>& prio
    +      , storage_error& ec) = 0;
    +   virtual status_t move_storage (std::string const& save_path
    +      , move_flags_t flags, storage_error& ec) = 0;
    +   virtual bool verify_resume_data (add_torrent_params const& rd
    +      , aux::vector<std::string, file_index_t> const& links
    +      , storage_error& ec) = 0;
    +   virtual void release_files (storage_error& ec) = 0;
    +   virtual void rename_file (file_index_t index, std::string const& new_filename
    +      , storage_error& ec) = 0;
    +   virtual void delete_files (remove_flags_t options, storage_error& ec) = 0;
    +   virtual bool tick ();
    +   file_storage const& files () const;
    +   bool set_need_tick ();
    +   void do_tick ();
    +   void set_owner (std::shared_ptr<void> const& tor);
    +   aux::session_settings const& settings () const;
    +   void set_storage_index (storage_index_t st);
    +   storage_index_t storage_index () const;
    +   void inc_refcount ();
    +   int dec_refcount ();
    +
    +   aux::session_settings const* m_settings  = nullptr;
    +};
    +
    +[report issue]
    +

    initialize()

    +
    +virtual void initialize (storage_error& ec) = 0;
    +
    +

    This function is called when the storage on disk is to be +initialized. The default storage will create directories and empty +files at this point. If allocate_files is true, it will also +ftruncate all files to their target size.

    +

    This function may be called multiple time on a single instance. When a +torrent is force-rechecked, the storage is re-initialized to trigger +the re-check from scratch.

    +

    The function is not necessarily called before other member functions. +For instance has_any_files() and verify_resume_data() are +called early to determine whether we may have to check all files or +not. If we're doing a full check of the files every piece will be +hashed, causing readv() to be called as well.

    +

    Any required internals that need initialization should be done in the +constructor. This function is called before the torrent starts to +download.

    +

    If an error occurs, storage_error should be set to reflect it.

    + +[report issue]
    +
    +

    readv() writev()

    +
    +virtual int writev (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +virtual int readv (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0;
    +
    +

    These functions should read and write the data in or to the given +piece at the given offset. It should read or write +num_bufs buffers sequentially, where the size of each buffer is +specified in the buffer array bufs. The iovec_t type has the +following members:

    +
    +struct iovec_t { void* iov_base; size_t iov_len; };
    +
    +

    These functions may be called simultaneously from multiple threads. +Make sure they are thread safe. The file in libtorrent is thread +safe when it can fall back to pread, preadv or the windows +equivalents. On targets where read operations cannot be thread safe +(i.e one has to seek first and then read), only one disk thread is +used.

    +

    The offset is aligned to 16 kiB boundaries most of the time, but +there are rare exceptions when it's not. Specifically if the read +cache is disabled/or full and a peer requests unaligned data. Most +clients request aligned data.

    +

    The number of bytes read or written should be returned, or -1 on +error. If there's an error, the storage_error must be filled out +to represent the error that occurred.

    +

    For possible values of flags, see open_mode_t.

    +[report issue]
    +
    +

    has_any_file()

    +
    +virtual bool has_any_file (storage_error& ec) = 0;
    +
    +

    This function is called when first checking (or re-checking) the +storage for a torrent. It should return true if any of the files that +is used in this storage exists on disk. If so, the storage will be +checked for existing pieces before starting the download.

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    set_file_priority()

    +
    +virtual void set_file_priority (aux::vector<download_priority_t, file_index_t>& prio
    +      , storage_error& ec) = 0;
    +
    +

    change the priorities of files. This is a fenced job and is +guaranteed to be the only running function on this storage +when called

    +[report issue]
    +
    +

    move_storage()

    +
    +virtual status_t move_storage (std::string const& save_path
    +      , move_flags_t flags, storage_error& ec) = 0;
    +
    +

    This function should move all the files belonging to the storage to +the new save_path. The default storage moves the single file or the +directory of the torrent.

    +

    Before moving the files, any open file handles may have to be closed, +like release_files().

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    verify_resume_data()

    +
    +virtual bool verify_resume_data (add_torrent_params const& rd
    +      , aux::vector<std::string, file_index_t> const& links
    +      , storage_error& ec) = 0;
    +
    +

    This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

    +

    The default storage may compare file sizes and time stamps of the files.

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

    +

    If the links pointer is non-empty, it has the same number +of elements as there are files. Each element is either empty or contains +the absolute path to a file identical to the corresponding file in this +torrent. The storage must create hard links (or copy) those files. If +any file does not exist or is inaccessible, the disk job must fail.

    +[report issue]
    +
    +

    release_files()

    +
    +virtual void release_files (storage_error& ec) = 0;
    +
    +

    This function should release all the file handles that it keeps open +to files belonging to this storage. The default implementation just +calls file_pool::release_files().

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    rename_file()

    +
    +virtual void rename_file (file_index_t index, std::string const& new_filename
    +      , storage_error& ec) = 0;
    +
    +

    Rename the file with index file to name new_name.

    +

    If an error occurs, storage_error should be set to reflect it.

    +[report issue]
    +
    +

    delete_files()

    +
    +virtual void delete_files (remove_flags_t options, storage_error& ec) = 0;
    +
    +

    This function should delete some or all of the storage for this torrent. +The options parameter specifies whether to delete all files or just +the partfile. options are set to the same value as the options +passed to session::remove_torrent().

    +

    If an error occurs, storage_error should be set to reflect it.

    +

    The disk_buffer_pool is used to allocate and free disk buffers. It +has the following members:

    +
    +struct disk_buffer_pool
    +{
    +        char* allocate_buffer(char const* category);
    +        void free_buffer(char* buf);
    +
    +        char* allocate_buffers(int blocks, char const* category);
    +        void free_buffers(char* buf, int blocks);
    +
    +        int block_size() const { return m_block_size; }
    +
    +};
    +
    +[report issue]
    +
    +

    tick()

    +
    +virtual bool tick ();
    +
    +

    called periodically (useful for deferred flushing). When returning +false, it means no more ticks are necessary. Any disk job submitted +will re-enable ticking. The default will always turn ticking back +off again.

    +[report issue]
    +
    +

    settings()

    +
    +aux::session_settings const& settings () const;
    +
    +

    access global session_settings

    +[report issue]
    +
    m_settings
    +
    initialized in disk_io_thread::perform_async_job
    +
    +[report issue]
    +
    +
    +

    default_storage

    +

    Declared in "libtorrent/storage.hpp"

    +

    The default implementation of storage_interface. Behaves as a normal +bittorrent client. It is possible to derive from this class in order to +override some of its behavior, when implementing a custom storage.

    +
    +class default_storage : public storage_interface
    +{
    +   explicit default_storage (storage_params const& params, file_pool&);
    +   void delete_files (remove_flags_t options, storage_error& ec) override;
    +   status_t move_storage (std::string const& save_path
    +      , move_flags_t flags, storage_error& ec) override;
    +   bool tick () override;
    +   bool verify_resume_data (add_torrent_params const& rd
    +      , aux::vector<std::string, file_index_t> const& links
    +      , storage_error& error) override;
    +   void release_files (storage_error& ec) override;
    +   void initialize (storage_error& ec) override;
    +   void rename_file (file_index_t index, std::string const& new_filename
    +      , storage_error& ec) override;
    +   void set_file_priority (aux::vector<download_priority_t, file_index_t>& prio
    +      , storage_error& ec) override;
    +   bool has_any_file (storage_error& ec) override;
    +   int readv (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) override;
    +   int writev (span<iovec_t const> bufs
    +      , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) override;
    +   file_storage const& files () const;
    +};
    +
    +[report issue]
    +

    default_storage()

    +
    +explicit default_storage (storage_params const& params, file_pool&);
    +
    +

    constructs the default_storage based on the give file_storage (fs). +mapped is an optional argument (it may be nullptr). If non-nullptr it +represents the file mapping that have been made to the torrent before +adding it. That's where files are supposed to be saved and looked for +on disk. save_path is the root save folder for this torrent. +file_pool is the cache of file handles that the storage will use. +All files it opens will ask the file_pool to open them. file_prio +is a vector indicating the priority of files on startup. It may be +an empty vector. Any file whose index is not represented by the vector +(because the vector is too short) are assumed to have priority 1. +this is used to treat files with priority 0 slightly differently.

    +[report issue]
    +
    +

    files()

    +
    +file_storage const& files () const;
    +
    +

    if the files in this storage are mapped, returns the mapped +file_storage, otherwise returns the original file_storage object.

    +[report issue]
    +
    +
    +

    file_pool

    +

    Declared in "libtorrent/file_pool.hpp"

    +

    this is an internal cache of open file handles. It's primarily used by +storage_interface implementations. It provides semi weak guarantees of +not opening more file handles than specified. Given multiple threads, +each with the ability to lock a file handle (via smart pointer), there +may be windows where more file handles are open.

    +
    +struct file_pool : boost::noncopyable
    +{
    +   ~file_pool ();
    +   explicit file_pool (int size = 40);
    +   file_handle open_file (storage_index_t st, std::string const& p
    +      , file_index_t file_index, file_storage const& fs, open_mode_t m
    +      , error_code& ec);
    +   void release ();
    +   void release (storage_index_t st);
    +   void release (storage_index_t st, file_index_t file_index);
    +   void resize (int size);
    +   int size_limit () const;
    +   void close_oldest ();
    +};
    +
    + +[report issue]
    +

    file_pool() ~file_pool()

    +
    +~file_pool ();
    +explicit file_pool (int size = 40);
    +
    +

    size specifies the number of allowed files handles +to hold open at any given time.

    +[report issue]
    +
    +

    open_file()

    +
    +file_handle open_file (storage_index_t st, std::string const& p
    +      , file_index_t file_index, file_storage const& fs, open_mode_t m
    +      , error_code& ec);
    +
    +

    return an open file handle to file at file_index in the +file_storage fs opened at save path p. m is the +file open mode (see file::open_mode_t).

    +[report issue]
    +
    +

    release()

    +
    +void release ();
    +void release (storage_index_t st);
    +void release (storage_index_t st, file_index_t file_index);
    +
    +

    release all files belonging to the specified storage_interface (st) +the overload that takes file_index releases only the file with +that index in storage st.

    +[report issue]
    +
    +

    resize()

    +
    +void resize (int size);
    +
    +

    update the allowed number of open file handles to size.

    +[report issue]
    +
    +

    size_limit()

    +
    +int size_limit () const;
    +
    +

    returns the current limit of number of allowed open file handles held +by the file_pool.

    +[report issue]
    +
    +

    close_oldest()

    +
    +void close_oldest ();
    +
    +

    close the file that was opened least recently (i.e. not accessed +least recently). The purpose is to make the OS (really just windows) +clear and flush its disk cache associated with this file. We don't want +any file to stay open for too long, allowing the disk cache to accrue.

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +
    +

    Utility

    +[report issue]
    +

    bitfield

    +

    Declared in "libtorrent/bitfield.hpp"

    +

    The bitfield type stores any number of bits as a bitfield +in a heap allocated array.

    +
    +struct bitfield
    +{
    +   bitfield () noexcept = default;
    +   bitfield (bitfield const& rhs);
    +   explicit bitfield (int bits);
    +   bitfield (bitfield&& rhs) noexcept = default;
    +   bitfield (int bits, bool val);
    +   bitfield (char const* b, int bits);
    +   void assign (char const* b, int const bits);
    +   bool operator[] (int index) const noexcept;
    +   bool get_bit (int index) const noexcept;
    +   void set_bit (int index) noexcept;
    +   void clear_bit (int index) noexcept;
    +   bool all_set () const noexcept;
    +   bool none_set () const noexcept;
    +   int size () const noexcept;
    +   int num_words () const noexcept;
    +   bool empty () const noexcept;
    +   char* data () noexcept;
    +   char const* data () const noexcept;
    +   void swap (bitfield& rhs) noexcept;
    +   int count () const noexcept;
    +   int find_first_set () const noexcept;
    +   int find_last_clear () const noexcept;
    +};
    +
    +[report issue]
    +

    bitfield()

    +
    +bitfield () noexcept = default;
    +bitfield (bitfield const& rhs);
    +explicit bitfield (int bits);
    +bitfield (bitfield&& rhs) noexcept = default;
    +bitfield (int bits, bool val);
    +bitfield (char const* b, int bits);
    +
    +

    constructs a new bitfield. The default constructor creates an empty +bitfield. bits is the size of the bitfield (specified in bits). +val is the value to initialize the bits to. If not specified +all bits are initialized to 0.

    +

    The constructor taking a pointer b and bits copies a bitfield +from the specified buffer, and bits number of bits (rounded up to +the nearest byte boundary).

    +[report issue]
    +
    +

    assign()

    +
    +void assign (char const* b, int const bits);
    +
    +

    copy bitfield from buffer b of bits number of bits, rounded up to +the nearest byte boundary.

    + +[report issue]
    +
    +

    get_bit() operator[]()

    +
    +bool operator[] (int index) const noexcept;
    +bool get_bit (int index) const noexcept;
    +
    +

    query bit at index. Returns true if bit is 1, otherwise false.

    + +[report issue]
    +
    +

    set_bit() clear_bit()

    +
    +void set_bit (int index) noexcept;
    +void clear_bit (int index) noexcept;
    +
    +

    set bit at index to 0 (clear_bit) or 1 (set_bit).

    +[report issue]
    +
    +

    all_set()

    +
    +bool all_set () const noexcept;
    +
    +

    returns true if all bits in the bitfield are set

    +[report issue]
    +
    +

    none_set()

    +
    +bool none_set () const noexcept;
    +
    +

    returns true if no bit in the bitfield is set

    +[report issue]
    +
    +

    size()

    +
    +int size () const noexcept;
    +
    +

    returns the size of the bitfield in bits.

    +[report issue]
    +
    +

    num_words()

    +
    +int num_words () const noexcept;
    +
    +

    returns the number of 32 bit words are needed to represent all bits in +this bitfield.

    +[report issue]
    +
    +

    empty()

    +
    +bool empty () const noexcept;
    +
    +

    returns true if the bitfield has zero size.

    +[report issue]
    +
    +

    data()

    +
    +char* data () noexcept;
    +char const* data () const noexcept;
    +
    +

    returns a pointer to the internal buffer of the bitfield, or +nullptr if it's empty.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (bitfield& rhs) noexcept;
    +
    +

    swaps the bit-fields two variables refer to

    +[report issue]
    +
    +

    count()

    +
    +int count () const noexcept;
    +
    +

    count the number of bits in the bitfield that are set to 1.

    +[report issue]
    +
    +

    find_first_set()

    +
    +int find_first_set () const noexcept;
    +
    +

    returns the index of the first set bit in the bitfield, i.e. 1 bit.

    +[report issue]
    +
    +

    find_last_clear()

    +
    +int find_last_clear () const noexcept;
    +
    +

    returns the index to the last cleared bit in the bitfield, i.e. 0 bit.

    +[report issue]
    +
    +
    +

    hasher

    +

    Declared in "libtorrent/hasher.hpp"

    +

    this is a SHA-1 hash class.

    +

    You use it by first instantiating it, then call update() to feed it +with data. i.e. you don't have to keep the entire buffer of which you want to +create the hash in memory. You can feed the hasher parts of it at a time. When +You have fed the hasher with all the data, you call final() and it +will return the sha1-hash of the data.

    +

    The constructor that takes a char const* and an integer will construct the +sha1 context and feed it the data passed in.

    +

    If you want to reuse the hasher object once you have created a hash, you have to +call reset() to reinitialize it.

    +

    The built-in software version of sha1-algorithm was implemented +by Steve Reid and released as public domain. +For more info, see src/sha1.cpp.

    +
    +class hasher
    +{
    +   hasher ();
    +   hasher& operator= (hasher const&) &;
    +   hasher (hasher const&);
    +   explicit hasher (span<char const> data);
    +   hasher (char const* data, int len);
    +   hasher& update (span<char const> data);
    +   hasher& update (char const* data, int len);
    +   sha1_hash final ();
    +   void reset ();
    +};
    +
    + +[report issue]
    +

    hasher() operator=()

    +
    +hasher& operator= (hasher const&) &;
    +hasher (hasher const&);
    +explicit hasher (span<char const> data);
    +hasher (char const* data, int len);
    +
    +

    this is the same as default constructing followed by a call to +update(data, len).

    +[report issue]
    +
    +

    update()

    +
    +hasher& update (span<char const> data);
    +hasher& update (char const* data, int len);
    +
    +

    append the following bytes to what is being hashed

    +[report issue]
    +
    +

    final()

    +
    +sha1_hash final ();
    +
    +

    returns the SHA-1 digest of the buffers previously passed to +update() and the hasher constructor.

    +[report issue]
    +
    +

    reset()

    +
    +void reset ();
    +
    +

    restore the hasher state to be as if the hasher has just been +default constructed.

    +[report issue]
    +
    +
    +

    operator<<()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +std::ostream& operator<< (std::ostream& os, sha1_hash const& peer);
    +
    +

    print a sha1_hash object to an ostream as 40 hexadecimal digits

    +[report issue]
    +
    +

    operator>>()

    +

    Declared in "libtorrent/sha1_hash.hpp"

    +
    +std::istream& operator>> (std::istream& is, sha1_hash& peer);
    +
    +

    read 40 hexadecimal digits from an istream into a sha1_hash

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Bencoding

    +

    Bencoding is a common representation in bittorrent used for dictionary, +list, int and string hierarchies. It's used to encode .torrent files and +some messages in the network protocol. libtorrent also uses it to store +settings, resume data and other session state.

    +

    Strings in bencoded structures do not necessarily represent text. +Strings are raw byte buffers of a certain length. If a string is meant to be +interpreted as text, it is required to be UTF-8 encoded. See BEP 3.

    +

    The function for decoding bencoded data bdecode(), returning a bdecode_node. +This function builds a tree that points back into the original buffer. The +returned bdecode_node will not be valid once the buffer it was parsed out of +is discarded.

    +

    It's possible to construct an entry from a bdecode_node, if a structure needs +to be altered and re-encoded.

    +[report issue]
    +

    entry

    +

    Declared in "libtorrent/entry.hpp"

    +

    The entry class represents one node in a bencoded hierarchy. It works as a +variant type, it can be either a list, a dictionary (std::map), an integer +or a string.

    +
    +class entry
    +{
    +   data_type type () const;
    +   entry& operator= (list_type) &;
    +   entry (list_type); // NOLINT;
    +   entry (dictionary_type); // NOLINT;
    +   entry& operator= (integer_type) &;
    +   entry& operator= (preformatted_type) &;
    +   list_type& list ();
    +   preformatted_type& preformatted ();
    +   integer_type& integer ();
    +   string_type& string ();
    +   const string_type& string () const;
    +   const dictionary_type& dict () const;
    +   const preformatted_type& preformatted () const;
    +   const integer_type& integer () const;
    +   dictionary_type& dict ();
    +   const list_type& list () const;
    +   void swap (entry& e);
    +   entry& operator[] (string_view key);
    +   const entry& operator[] (string_view key) const;
    +   entry const* find_key (string_view key) const;
    +   entry* find_key (string_view key);
    +   std::string to_string (bool single_line = false) const;
    +
    +   enum data_type
    +   {
    +      int_t,
    +      string_t,
    +      list_t,
    +      dictionary_t,
    +      undefined_t,
    +      preformatted_t,
    +   };
    +
    +   mutable std::uint8_t m_type_queried:1;
    +};
    +
    +[report issue]
    +

    type()

    +
    +data_type type () const;
    +
    +

    returns the concrete type of the entry

    + +[report issue]
    +
    +

    operator=() entry()

    +
    +entry& operator= (list_type) &;
    +entry (list_type); // NOLINT;
    +entry (dictionary_type); // NOLINT;
    +entry& operator= (integer_type) &;
    +entry& operator= (preformatted_type) &;
    +
    +

    constructors directly from a specific type. +The content of the argument is copied into the +newly constructed entry

    + + + + +[report issue]
    +
    +

    string() dict() list() integer() preformatted()

    +
    +list_type& list ();
    +preformatted_type& preformatted ();
    +integer_type& integer ();
    +string_type& string ();
    +const string_type& string () const;
    +const dictionary_type& dict () const;
    +const preformatted_type& preformatted () const;
    +const integer_type& integer () const;
    +dictionary_type& dict ();
    +const list_type& list () const;
    +
    +

    The integer(), string(), list() and dict() functions +are accessors that return the respective type. If the entry object +isn't of the type you request, the accessor will throw +system_error. You can ask an entry for its type through the +type() function.

    +

    If you want to create an entry you give it the type you want it to +have in its constructor, and then use one of the non-const accessors +to get a reference which you then can assign the value you want it to +have.

    +

    The typical code to get info from a torrent file will then look like +this:

    +
    +entry torrent_file;
    +// ...
    +
    +// throws if this is not a dictionary
    +entry::dictionary_type const& dict = torrent_file.dict();
    +entry::dictionary_type::const_iterator i;
    +i = dict.find("announce");
    +if (i != dict.end())
    +{
    +        std::string tracker_url = i->second.string();
    +        std::cout << tracker_url << "\n";
    +}
    +
    +

    The following code is equivalent, but a little bit shorter:

    +
    +entry torrent_file;
    +// ...
    +
    +// throws if this is not a dictionary
    +if (entry* i = torrent_file.find_key("announce"))
    +{
    +        std::string tracker_url = i->string();
    +        std::cout << tracker_url << "\n";
    +}
    +
    +

    To make it easier to extract information from a torrent file, the +class torrent_info exists.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (entry& e);
    +
    +

    swaps the content of this with e.

    +[report issue]
    +
    +

    operator[]()

    +
    +entry& operator[] (string_view key);
    +const entry& operator[] (string_view key) const;
    +
    +

    All of these functions requires the entry to be a dictionary, if it +isn't they will throw system_error.

    +

    The non-const versions of the operator[] will return a reference +to either the existing element at the given key or, if there is no +element with the given key, a reference to a newly inserted element at +that key.

    +

    The const version of operator[] will only return a reference to an +existing element at the given key. If the key is not found, it will +throw system_error.

    +[report issue]
    +
    +

    find_key()

    +
    +entry const* find_key (string_view key) const;
    +entry* find_key (string_view key);
    +
    +

    These functions requires the entry to be a dictionary, if it isn't +they will throw system_error.

    +

    They will look for an element at the given key in the dictionary, if +the element cannot be found, they will return nullptr. If an element +with the given key is found, the return a pointer to it.

    +[report issue]
    +
    +

    to_string()

    +
    +std::string to_string (bool single_line = false) const;
    +
    +

    returns a pretty-printed string representation +of the bencoded structure, with JSON-style syntax

    +[report issue]
    +
    +

    enum data_type

    +

    Declared in "libtorrent/entry.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    int_t0 
    string_t1 
    list_t2 
    dictionary_t3 
    undefined_t4 
    preformatted_t5 
    +[report issue]
    +
    m_type_queried
    +
    in debug mode this is set to false by bdecode to indicate that the +program has not yet queried the type of this entry, and should not +assume that it has a certain type. This is asserted in the accessor +functions. This does not apply if exceptions are used.
    +
    +[report issue]
    +
    +
    +

    bencode()

    +

    Declared in "libtorrent/bencode.hpp"

    +
    +template<class OutIt> int bencode (OutIt out, const entry& e);
    +
    +

    This function will encode data to bencoded form.

    +

    The entry class is the internal representation of the bencoded data +and it can be used to retrieve information, an entry can also be build by +the program and given to bencode() to encode it into the OutIt +iterator.

    +

    OutIt is an OutputIterator. It's a template and usually +instantiated as ostream_iterator or back_insert_iterator. This +function assumes the value_type of the iterator is a char. +In order to encode entry e into a buffer, do:

    +
    +std::vector<char> buffer;
    +bencode(std::back_inserter(buf), e);
    +
    +[report issue]
    +
    +

    operator<<()

    +

    Declared in "libtorrent/entry.hpp"

    +
    +inline std::ostream& operator<< (std::ostream& os, const entry& e);
    +
    +

    prints the bencoded structure to the ostream as a JSON-style structure.

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Alerts

    +

    The pop_alerts() function on session is the main interface for retrieving +alerts (warnings, messages and errors from libtorrent). If no alerts have +been posted by libtorrent pop_alerts() will return an empty list.

    +

    By default, only errors are reported. settings_pack::alert_mask can be +used to specify which kinds of events should be reported. The alert mask is +a combination of the alert_category_t flags in the alert class.

    +

    Every alert belongs to one or more category. There is a cost associated with +posting alerts. Only alerts that belong to an enabled category are +posted. Setting the alert bitmask to 0 will disable all alerts (except those +that are non-discardable). Alerts that are responses to API calls such as +save_resume_data() and post_session_stats() are non-discardable and will be +posted even if their category is disabled.

    +

    There are other alert base classes that some alerts derive from, all the +alerts that are generated for a specific torrent are derived from +torrent_alert, and tracker events derive from tracker_alert.

    +

    Alerts returned by pop_alerts() are only valid until the next call to +pop_alerts(). You may not copy an alert object to access it after the next +call to pop_alerts(). Internal members of alerts also become invalid once +pop_alerts() is called again.

    +[report issue]
    +

    alert

    +

    Declared in "libtorrent/alert.hpp"

    +

    The alert class is the base class that specific messages are derived from. +alert types are not copyable, and cannot be constructed by the client. The +pointers returned by libtorrent are short lived (the details are described +under session_handle::pop_alerts())

    +
    +class alert
    +{
    +   time_point timestamp () const;
    +   virtual int type () const noexcept = 0;
    +   virtual char const* what () const noexcept = 0;
    +   virtual std::string message () const = 0;
    +   virtual alert_category_t category () const noexcept = 0;
    +
    +   static constexpr alert_category_t error_notification  = 0_bit;
    +   static constexpr alert_category_t peer_notification  = 1_bit;
    +   static constexpr alert_category_t port_mapping_notification  = 2_bit;
    +   static constexpr alert_category_t storage_notification  = 3_bit;
    +   static constexpr alert_category_t tracker_notification  = 4_bit;
    +   static constexpr alert_category_t connect_notification  = 5_bit;
    +   static constexpr alert_category_t status_notification  = 6_bit;
    +   static constexpr alert_category_t ip_block_notification  = 8_bit;
    +   static constexpr alert_category_t performance_warning  = 9_bit;
    +   static constexpr alert_category_t dht_notification  = 10_bit;
    +   static constexpr alert_category_t stats_notification  = 11_bit;
    +   static constexpr alert_category_t session_log_notification  = 13_bit;
    +   static constexpr alert_category_t torrent_log_notification  = 14_bit;
    +   static constexpr alert_category_t peer_log_notification  = 15_bit;
    +   static constexpr alert_category_t incoming_request_notification  = 16_bit;
    +   static constexpr alert_category_t dht_log_notification  = 17_bit;
    +   static constexpr alert_category_t dht_operation_notification  = 18_bit;
    +   static constexpr alert_category_t port_mapping_log_notification  = 19_bit;
    +   static constexpr alert_category_t picker_log_notification  = 20_bit;
    +   static constexpr alert_category_t file_progress_notification  = 21_bit;
    +   static constexpr alert_category_t piece_progress_notification  = 22_bit;
    +   static constexpr alert_category_t upload_notification  = 23_bit;
    +   static constexpr alert_category_t block_progress_notification  = 24_bit;
    +   static constexpr alert_category_t all_categories  = alert_category_t::all();
    +};
    +
    +[report issue]
    +

    timestamp()

    +
    +time_point timestamp () const;
    +
    +

    a timestamp is automatically created in the constructor

    +[report issue]
    +
    +

    type()

    +
    +virtual int type () const noexcept = 0;
    +
    +

    returns an integer that is unique to this alert type. It can be +compared against a specific alert by querying a static constant called alert_type +in the alert. It can be used to determine the run-time type of an alert* in +order to cast to that alert type and access specific members.

    +

    e.g:

    +
    +std::vector<alert*> alerts;
    +ses.pop_alerts(&alerts);
    +for (alert* a : alerts) {
    +        switch (a->type()) {
    +
    +                case read_piece_alert::alert_type:
    +                {
    +                        auto* p = static_cast<read_piece_alert*>(a);
    +                        if (p->ec) {
    +                                // read_piece failed
    +                                break;
    +                        }
    +                        // use p
    +                        break;
    +                }
    +                case file_renamed_alert::alert_type:
    +                {
    +                        // etc...
    +                }
    +        }
    +}
    +
    +[report issue]
    +
    +

    what()

    +
    +virtual char const* what () const noexcept = 0;
    +
    +

    returns a string literal describing the type of the alert. It does +not include any information that might be bundled with the alert.

    +[report issue]
    +
    +

    message()

    +
    +virtual std::string message () const = 0;
    +
    +

    generate a string describing the alert and the information bundled +with it. This is mainly intended for debug and development use. It is not suitable +to use this for applications that may be localized. Instead, handle each alert +type individually and extract and render the information from the alert depending +on the locale.

    +[report issue]
    +
    +

    category()

    +
    +virtual alert_category_t category () const noexcept = 0;
    +
    +

    returns a bitmask specifying which categories this alert belong to.

    +[report issue]
    +
    +
    +

    dht_routing_bucket

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    struct to hold information about a single DHT routing table bucket

    +
    +struct dht_routing_bucket
    +{
    +   int num_nodes;
    +   int num_replacements;
    +   int last_active;
    +};
    +
    + +[report issue]
    +
    num_nodes num_replacements
    +
    the total number of nodes and replacement nodes +in the routing table
    +
    +[report issue]
    +
    last_active
    +
    number of seconds since last activity
    +
    +[report issue]
    +
    +

    torrent_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is a base class for alerts that are associated with a +specific torrent. It contains a handle to the torrent.

    +
    +struct torrent_alert : alert
    +{
    +   std::string message () const override;
    +   char const* torrent_name () const;
    +
    +   torrent_handle handle;
    +};
    +
    +[report issue]
    +

    message()

    +
    +std::string message () const override;
    +
    +

    returns the message associated with this alert

    +[report issue]
    +
    handle
    +
    The torrent_handle pointing to the torrent this +alert is associated with.
    +
    +[report issue]
    +
    +
    +

    peer_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The peer alert is a base class for alerts that refer to a specific peer. It includes all +the information to identify the peer. i.e. ip and peer-id.

    +
    +struct peer_alert : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   aux::noexcept_movable<tcp::endpoint> endpoint;
    +   peer_id pid;
    +};
    +
    +[report issue]
    +
    endpoint
    +
    The peer's IP address and port.
    +
    +[report issue]
    +
    pid
    +
    the peer ID, if known.
    +
    +[report issue]
    +
    +

    tracker_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is a base class used for alerts that are associated with a +specific tracker. It derives from torrent_alert since a tracker +is also associated with a specific torrent.

    +
    +struct tracker_alert : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* tracker_url () const;
    +
    +   aux::noexcept_movable<tcp::endpoint> local_endpoint;
    +};
    +
    +[report issue]
    +

    tracker_url()

    +
    +char const* tracker_url () const;
    +
    +

    returns a 0-terminated string of the tracker's URL

    +[report issue]
    +
    local_endpoint
    +
    endpoint of the listen interface being announced
    +
    +[report issue]
    +
    +
    +

    torrent_removed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The torrent_removed_alert is posted whenever a torrent is removed. Since +the torrent handle in its base class will always be invalid (since the torrent +is already removed) it has the info hash as a member, to identify it. +It's posted when the status_notification bit is set in the alert_mask.

    +

    Even though the handle member doesn't point to an existing torrent anymore, +it is still useful for comparing to other handles, which may also no +longer point to existing torrents, but to the same non-existing torrents.

    +

    The torrent_handle acts as a weak_ptr, even though its object no +longer exists, it can still compare equal to another weak pointer which +points to the same non-existent object.

    +
    +struct torrent_removed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    read_piece_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the asynchronous read operation initiated by +a call to torrent_handle::read_piece() is completed. If the read failed, the torrent +is paused and an error state is set and the buffer member of the alert +is 0. If successful, buffer points to a buffer containing all the data +of the piece. piece is the piece index that was read. size is the +number of bytes that was read.

    +

    If the operation fails, error will indicate what went wrong.

    +
    +struct read_piece_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   error_code const error;
    +   boost::shared_array<char> const buffer;
    +   piece_index_t const piece;
    +   int const size;
    +};
    +
    +[report issue]
    +
    +

    file_completed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted whenever an individual file completes its download. i.e. +All pieces overlapping this file have passed their hash check.

    +
    +struct file_completed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   file_index_t const index;
    +};
    +
    +[report issue]
    +
    index
    +
    refers to the index of the file that completed.
    +
    +[report issue]
    +
    +

    file_renamed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation succeeds.

    +
    +struct file_renamed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* new_name () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   file_index_t const index;
    +};
    +
    +[report issue]
    +
    index
    +
    refers to the index of the file that was renamed,
    +
    +[report issue]
    +
    +

    file_rename_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted as a response to a torrent_handle::rename_file() call, if the rename +operation failed.

    +
    +struct file_rename_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   file_index_t const index;
    +   error_code const error;
    +};
    +
    + +[report issue]
    +
    index error
    +
    refers to the index of the file that was supposed to be renamed, +error is the error code returned from the filesystem.
    +
    +[report issue]
    +
    +

    performance_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a limit is reached that might have a negative impact on +upload or download rate performance.

    +
    +struct performance_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   enum performance_warning_t
    +   {
    +      outstanding_disk_buffer_limit_reached,
    +      outstanding_request_limit_reached,
    +      upload_limit_too_low,
    +      download_limit_too_low,
    +      send_buffer_watermark_too_low,
    +      too_many_optimistic_unchoke_slots,
    +      too_high_disk_queue_limit,
    +      aio_limit_reached,
    +      deprecated_bittyrant_with_no_uplimit,
    +      too_few_outgoing_ports,
    +      too_few_file_descriptors,
    +      num_warnings,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::performance_warning;
    +   performance_warning_t const warning_code;
    +};
    +
    +[report issue]
    +

    enum performance_warning_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    outstanding_disk_buffer_limit_reached0This warning means that the number of bytes queued to be written to disk +exceeds the max disk byte queue setting (settings_pack::max_queued_disk_bytes). +This might restrict the download rate, by not queuing up enough write jobs +to the disk I/O thread. When this alert is posted, peer connections are +temporarily stopped from downloading, until the queued disk bytes have fallen +below the limit again. Unless your max_queued_disk_bytes setting is already +high, you might want to increase it to get better performance.
    outstanding_request_limit_reached1This is posted when libtorrent would like to send more requests to a peer, +but it's limited by settings_pack::max_out_request_queue. The queue length +libtorrent is trying to achieve is determined by the download rate and the +assumed round-trip-time (settings_pack::request_queue_time). The assumed +round-trip-time is not limited to just the network RTT, but also the remote disk +access time and message handling time. It defaults to 3 seconds. The target number +of outstanding requests is set to fill the bandwidth-delay product (assumed RTT +times download rate divided by number of bytes per request). When this alert +is posted, there is a risk that the number of outstanding requests is too low +and limits the download rate. You might want to increase the max_out_request_queue +setting.
    upload_limit_too_low2This warning is posted when the amount of TCP/IP overhead is greater than the +upload rate limit. When this happens, the TCP/IP overhead is caused by a much +faster download rate, triggering TCP ACK packets. These packets eat into the +rate limit specified to libtorrent. When the overhead traffic is greater than +the rate limit, libtorrent will not be able to send any actual payload, such +as piece requests. This means the download rate will suffer, and new requests +can be sent again. There will be an equilibrium where the download rate, on +average, is about 20 times the upload rate limit. If you want to maximize the +download rate, increase the upload rate limit above 5% of your download capacity.
    download_limit_too_low3This is the same warning as upload_limit_too_low but referring to the download +limit instead of upload. This suggests that your download rate limit is much lower +than your upload capacity. Your upload rate will suffer. To maximize upload rate, +make sure your download rate limit is above 5% of your upload capacity.
    send_buffer_watermark_too_low4

    We're stalled on the disk. We want to write to the socket, and we can write +but our send buffer is empty, waiting to be refilled from the disk. +This either means the disk is slower than the network connection +or that our send buffer watermark is too small, because we can +send it all before the disk gets back to us. +The number of bytes that we keep outstanding, requested from the disk, is calculated +as follows:

    +
    +min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark))
    +
    +

    If you receive this alert, you might want to either increase your send_buffer_watermark +or send_buffer_watermark_factor.

    +
    too_many_optimistic_unchoke_slots5If the half (or more) of all upload slots are set as optimistic unchoke slots, this +warning is issued. You probably want more regular (rate based) unchoke slots.
    too_high_disk_queue_limit6If the disk write queue ever grows larger than half of the cache size, this warning +is posted. The disk write queue eats into the total disk cache and leaves very little +left for the actual cache. This causes the disk cache to oscillate in evicting large +portions of the cache before allowing peers to download any more, onto the disk write +queue. Either lower max_queued_disk_bytes or increase cache_size.
    aio_limit_reached7 
    deprecated_bittyrant_with_no_uplimit8 
    too_few_outgoing_ports9This is generated if outgoing peer connections are failing because of address in use +errors, indicating that settings_pack::outgoing_ports is set and is too small of +a range. Consider not using the outgoing_ports setting at all, or widen the range to +include more ports.
    too_few_file_descriptors10 
    num_warnings11 
    +[report issue]
    +
    +
    +

    state_changed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Generated whenever a torrent changes its state.

    +
    +struct state_changed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   torrent_status::state_t const state;
    +   torrent_status::state_t const prev_state;
    +};
    +
    +[report issue]
    +
    state
    +
    the new state of the torrent.
    +
    +[report issue]
    +
    prev_state
    +
    the previous state.
    +
    +[report issue]
    +
    +

    tracker_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated on tracker time outs, premature disconnects, +invalid response or a HTTP response other than "200 OK". From the alert +you can get the handle to the torrent the tracker belongs to.

    +

    The times_in_row member says how many times in a row this tracker has +failed. status_code is the code returned from the HTTP server. 401 +means the tracker needs authentication, 404 means not found etc. If the +tracker timed out, the code will be set to 0.

    +
    +struct tracker_error_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* error_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker | alert_category::error;
    +   int const times_in_row;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    error_message()

    +
    +char const* error_message () const;
    +
    +

    the message associated with this error

    +[report issue]
    +
    +
    +

    tracker_warning_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is triggered if the tracker reply contains a warning field. +Usually this means that the tracker announce was successful, but the +tracker has a message to the client.

    +
    +struct tracker_warning_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* warning_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker | alert_category::error;
    +};
    +
    +[report issue]
    +

    warning_message()

    +
    +char const* warning_message () const;
    +
    +

    the message associated with this warning

    +[report issue]
    +
    +
    +

    scrape_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a scrape request succeeds.

    +
    +struct scrape_reply_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker;
    +   int const incomplete;
    +   int const complete;
    +};
    +
    + +[report issue]
    +
    incomplete complete
    +
    the data returned in the scrape response. These numbers +may be -1 if the response was malformed.
    +
    +[report issue]
    +
    +

    scrape_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    If a scrape request fails, this alert is generated. This might be due +to the tracker timing out, refusing connection or returning an http response +code indicating an error.

    +
    +struct scrape_failed_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* error_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker | alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    error_message()

    +
    +char const* error_message () const;
    +
    +

    if the error indicates there is an associated message, this returns +that message. Otherwise and empty string.

    +[report issue]
    +
    error
    +
    the error itself. This may indicate that the tracker sent an error +message (error::tracker_failure), in which case it can be +retrieved by calling error_message().
    +
    +[report issue]
    +
    +
    +

    tracker_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is only for informational purpose. It is generated when a tracker announce +succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or +the DHT.

    +
    +struct tracker_reply_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker;
    +   int const num_peers;
    +};
    +
    +[report issue]
    +
    num_peers
    +
    tells how many peers the tracker returned in this response. This is +not expected to be greater than the num_want settings. These are not necessarily +all new peers, some of them may already be connected.
    +
    +[report issue]
    +
    +

    dht_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated each time the DHT receives peers from a node. num_peers +is the number of peers we received in this packet. Typically these packets are +received from multiple DHT nodes, and so the alerts are typically generated +a few at a time.

    +
    +struct dht_reply_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht | alert_category::tracker;
    +   int const num_peers;
    +};
    +
    +[report issue]
    +
    +

    tracker_announce_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated each time a tracker announce is sent (or attempted to be sent). +There are no extra data members in this alert. The url can be found in the base class +however.

    +
    +struct tracker_announce_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::tracker;
    +   int const event;
    +};
    +
    +[report issue]
    +
    event
    +

    specifies what event was sent to the tracker. It is defined as:

    +
      +
    1. None
    2. +
    3. Completed
    4. +
    5. Started
    6. +
    7. Stopped
    8. +
    +
    +
    +[report issue]
    +
    +

    hash_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a finished piece fails its hash check. You can get the handle +to the torrent which got the failed piece and the index of the piece itself from the alert.

    +
    +struct hash_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    peer_ban_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is banned because it has sent too many corrupt pieces +to us. ip is the endpoint to the peer that was banned.

    +
    +struct peer_ban_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    peer_unsnubbed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is un-snubbed. Essentially when it was snubbed for stalling +sending data, and now it started sending data again.

    +
    +struct peer_unsnubbed_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    peer_snubbed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is snubbed, when it stops sending data when we request +it.

    +
    +struct peer_snubbed_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    peer_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer +will be disconnected, but you get its ip address from the alert, to identify it.

    +
    +struct peer_error_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   operation_t op;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    op
    +
    a 0-terminated string of the low-level operation that failed, or nullptr if +there was no low level disk operation.
    +
    +[report issue]
    +
    error
    +
    tells you what error caused this alert.
    +
    +[report issue]
    +
    +

    peer_connect_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted every time an outgoing peer connect attempts succeeds.

    +
    +struct peer_connect_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::connect;
    +   int const socket_type;
    +};
    +
    +[report issue]
    +
    +

    peer_disconnected_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer is disconnected for any reason (other than the ones +covered by peer_error_alert ).

    +
    +struct peer_disconnected_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::connect;
    +   int const socket_type;
    +   operation_t const op;
    +   error_code const error;
    +   close_reason_t const reason;
    +};
    +
    +[report issue]
    +
    socket_type
    +
    the kind of socket this peer was connected over
    +
    +[report issue]
    +
    op
    +
    the operation or level where the error occurred. Specified as an +value from the operation_t enum. Defined in operations.hpp.
    +
    +[report issue]
    +
    error
    +
    tells you what error caused peer to disconnect.
    +
    +[report issue]
    +
    reason
    +
    the reason the peer disconnected (if specified)
    +
    +[report issue]
    +
    +

    invalid_request_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is a debug alert that is generated by an incoming invalid piece request. +ip is the address of the peer and the request is the actual incoming +request from the peer. See peer_request for more info.

    +
    +struct invalid_request_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   peer_request const request;
    +   bool const we_have;
    +   bool const peer_interested;
    +   bool const withheld;
    +};
    +
    +[report issue]
    +
    request
    +
    the request we received from the peer
    +
    +[report issue]
    +
    we_have
    +
    true if we have this piece
    +
    +[report issue]
    +
    peer_interested
    +
    true if the peer indicated that it was interested to download before +sending the request
    +
    +[report issue]
    +
    withheld
    +
    if this is true, the peer is not allowed to download this piece because +of super-seeding rules.
    +
    +[report issue]
    +
    +

    torrent_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a torrent switches from being a downloader to a seed. +It will only be generated once per torrent. It contains a torrent_handle to the +torrent in question.

    +
    +struct torrent_finished_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    piece_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted every time a piece completes downloading +and passes the hash check. This alert derives from torrent_alert +which contains the torrent_handle to the torrent the piece belongs to. +Note that being downloaded and passing the hash check may happen before +the piece is also fully flushed to disk. So torrent_handle::have_piece() +may still return false

    +
    +struct piece_finished_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    piece_index
    +
    the index of the piece that finished
    +
    +[report issue]
    +
    +

    request_dropped_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a peer rejects or ignores a piece request.

    +
    +struct request_dropped_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    block_timeout_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request times out.

    +
    +struct block_timeout_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    block_finished_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request receives a response.

    +
    +struct block_finished_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    block_downloading_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block request is sent to a peer.

    +
    +struct block_downloading_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    unwanted_block_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a block is received that was not requested or +whose request timed out.

    +
    +struct unwanted_block_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    storage_moved_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The storage_moved_alert is generated when all the disk IO has +completed and the files have been moved, as an effect of a call to +torrent_handle::move_storage. This is useful to synchronize with the +actual disk. The storage_path() member return the new path of the +storage.

    +
    +struct storage_moved_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* storage_path () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +};
    +
    +[report issue]
    +

    storage_path()

    +
    +char const* storage_path () const;
    +
    +

    the path the torrent was moved to

    +[report issue]
    +
    +
    +

    storage_moved_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The storage_moved_failed_alert is generated when an attempt to move the storage, +via torrent_handle::move_storage(), fails.

    +
    +struct storage_moved_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* file_path () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   error_code const error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +

    file_path()

    +
    +char const* file_path () const;
    +
    +

    If the error happened for a specific file, this returns its path.

    +[report issue]
    +
    op
    +
    this indicates what underlying operation caused the error
    +
    +[report issue]
    +
    +
    +

    torrent_deleted_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a request to delete the files of a torrent complete.

    +

    The info_hash is the info-hash of the torrent that was just deleted. Most of +the time the torrent_handle in the torrent_alert will be invalid by the time +this alert arrives, since the torrent is being deleted. The info_hash member +is hence the main way of identifying which torrent just completed the delete.

    +

    This alert is posted in the storage_notification category, and that bit +needs to be set in the alert_mask.

    +
    +struct torrent_deleted_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    torrent_delete_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a request to delete the files of a torrent fails. +Just removing a torrent from the session cannot fail

    +
    +struct torrent_delete_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage
    +   | alert_category::error;
    +   error_code const error;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    error
    +
    tells you why it failed.
    +
    +[report issue]
    +
    info_hash
    +
    the info hash of the torrent whose files failed to be deleted
    +
    +[report issue]
    +
    +

    save_resume_data_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::save_resume_data request. +It is generated once the disk IO thread is done writing the state for this torrent.

    +
    +struct save_resume_data_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +   add_torrent_params params;
    +};
    +
    +[report issue]
    +
    params
    +
    the params structure is populated with the fields to be passed to +add_torrent() or async_add_torrent() to resume the torrent. To +save the state to disk, you may pass it on to write_resume_data().
    +
    +[report issue]
    +
    +

    save_resume_data_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated instead of save_resume_data_alert if there was an error +generating the resume data. error describes what went wrong.

    +
    +struct save_resume_data_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::storage
    +   | alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    error
    +
    the error code from the resume_data failure
    +
    +[report issue]
    +
    +

    torrent_paused_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::pause request. It is +generated once all disk IO is complete and the files in the torrent have been closed. +This is useful for synchronizing with the disk.

    +
    +struct torrent_paused_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    torrent_resumed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated as a response to a torrent_handle::resume() request. It is +generated when a torrent goes from a paused state to an active state.

    +
    +struct torrent_resumed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    torrent_checked_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when a torrent completes checking. i.e. when it transitions +out of the checking files state into a state where it is ready to start downloading

    +
    +struct torrent_checked_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    url_seed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a HTTP seed name lookup fails.

    +
    +struct url_seed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* server_url () const;
    +   char const* error_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer | alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    server_url()

    +
    +char const* server_url () const;
    +
    +

    the URL the error is associated with

    +[report issue]
    +
    +

    error_message()

    +
    +char const* error_message () const;
    +
    +

    in case the web server sent an error message, this function returns +it.

    +[report issue]
    +
    error
    +
    the error the web seed encountered. If this is not set, the server +sent an error message, call error_message().
    +
    +[report issue]
    +
    +
    +

    file_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    If the storage fails to read or write files that it needs access to, this alert is +generated and the torrent is paused.

    +
    +struct file_error_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* filename () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status
    +   | alert_category::storage;
    +   error_code const error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +

    filename()

    +
    +char const* filename () const;
    +
    +

    the file that experienced the error

    +[report issue]
    +
    error
    +
    the error code describing the error.
    +
    +[report issue]
    +
    op
    +
    indicates which underlying operation caused the error
    +
    +[report issue]
    +
    +
    +

    metadata_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when the metadata has been completely received and the info-hash +failed to match it. i.e. the metadata that was received was corrupt. libtorrent will +automatically retry to fetch it in this case. This is only relevant when running a +torrent-less download, with the metadata extension provided by libtorrent.

    +
    +struct metadata_failed_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    error
    +
    indicates what failed when parsing the metadata. This error is +what's returned from lazy_bdecode().
    +
    +[report issue]
    +
    +

    metadata_received_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when the metadata has been completely received and the torrent +can start downloading. It is not generated on torrents that are started with metadata, but +only those that needs to download it from peers (when utilizing the libtorrent extension).

    +

    There are no additional data members in this alert.

    +

    Typically, when receiving this alert, you would want to save the torrent file in order +to load it back up again when the session is restarted. Here's an example snippet of +code to do that:

    +
    +torrent_handle h = alert->handle();
    +if (h.is_valid()) {
    +        std::shared_ptr<torrent_info const> ti = h.torrent_file();
    +        create_torrent ct(*ti);
    +        entry te = ct.generate();
    +        std::vector<char> buffer;
    +        bencode(std::back_inserter(buffer), te);
    +        FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+");
    +        if (f) {
    +                fwrite(&buffer[0], 1, buffer.size(), f);
    +                fclose(f);
    +        }
    +}
    +
    +
    +struct metadata_received_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    udp_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when there is an error on a UDP socket. The +UDP sockets are used for all uTP, DHT and UDP tracker traffic. They are +global to the session.

    +
    +struct udp_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +   operation_t operation;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    endpoint
    +
    the source address associated with the error (if any)
    +
    +[report issue]
    +
    operation
    +
    the operation that failed
    +
    +[report issue]
    +
    error
    +
    the error code describing the error
    +
    +[report issue]
    +
    +

    external_ip_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Whenever libtorrent learns about the machines external IP, this alert is +generated. The external IP address can be acquired from the tracker (if it +supports that) or from peers that supports the extension protocol. +The address can be accessed through the external_address member.

    +
    +struct external_ip_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   aux::noexcept_movable<address> external_address;
    +};
    +
    +[report issue]
    +
    external_address
    +
    the IP address that is believed to be our external IP
    +
    +[report issue]
    +
    +

    listen_failed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when none of the ports, given in the port range, to +session can be opened for listening. The listen_interface member is the +interface that failed, error is the error code describing the failure.

    +

    In the case an endpoint was created before generating the alert, it is +represented by address and port. The combinations of socket type +and operation in which such address and port are not valid are: +accept - i2p +accept - socks5 +enum_if - tcp

    +

    libtorrent may sometimes try to listen on port 0, if all other ports failed. +Port 0 asks the operating system to pick a port that's free). If that fails +you may see a listen_failed_alert with port 0 even if you didn't ask to +listen on it.

    +
    +struct listen_failed_alert final : alert
    +{
    +   listen_failed_alert (aux::stack_allocator& alloc, string_view iface
    +      , tcp::endpoint const& ep, operation_t op, error_code const& ec
    +      , lt::socket_type_t t);
    +   listen_failed_alert (aux::stack_allocator& alloc, string_view iface
    +      , udp::endpoint const& ep, operation_t op, error_code const& ec
    +      , lt::socket_type_t t);
    +   listen_failed_alert (aux::stack_allocator& alloc, string_view iface
    +      , operation_t op, error_code const& ec, lt::socket_type_t t);
    +   std::string message () const override;
    +   char const* listen_interface () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status | alert_category::error;
    +   error_code const error;
    +   operation_t op;
    +   lt::socket_type_t const socket_type;
    +   aux::noexcept_movable<lt::address> address;
    +   int const port;
    +};
    +
    +[report issue]
    +

    listen_interface()

    +
    +char const* listen_interface () const;
    +
    +

    the network device libtorrent attempted to listen on, or the IP address

    +[report issue]
    +
    error
    +
    the error the system returned
    +
    +[report issue]
    +
    op
    +
    the underlying operation that failed
    +
    +[report issue]
    +
    socket_type
    +
    the type of listen socket this alert refers to.
    +
    +[report issue]
    +
    address
    +
    the address libtorrent attempted to listen on +see alert documentation for validity of this value
    +
    +[report issue]
    +
    port
    +
    the port libtorrent attempted to listen on +see alert documentation for validity of this value
    +
    +[report issue]
    +
    +
    +

    listen_succeeded_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the listen port succeeds to be opened on a +particular interface. address and port is the endpoint that +successfully was opened for listening.

    +
    +struct listen_succeeded_alert final : alert
    +{
    +   listen_succeeded_alert (aux::stack_allocator& alloc
    +      , tcp::endpoint const& ep
    +      , lt::socket_type_t t);
    +   listen_succeeded_alert (aux::stack_allocator& alloc
    +      , udp::endpoint const& ep
    +      , lt::socket_type_t t);
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   aux::noexcept_movable<lt::address> address;
    +   int const port;
    +   lt::socket_type_t const socket_type;
    +};
    +
    +[report issue]
    +
    address
    +
    the address libtorrent ended up listening on. This address +refers to the local interface.
    +
    +[report issue]
    +
    port
    +
    the port libtorrent ended up listening on.
    +
    +[report issue]
    +
    socket_type
    +
    the type of listen socket this alert refers to.
    +
    +[report issue]
    +
    +

    portmap_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a NAT router was successfully found but some +part of the port mapping request failed. It contains a text message that +may help the user figure out what is wrong. This alert is not generated in +case it appears the client is not running on a NAT:ed network or if it +appears there is no NAT router that can be remote controlled to add port +mappings.

    +
    +struct portmap_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::port_mapping
    +   | alert_category::error;
    +   port_mapping_t const mapping;
    +   portmap_transport map_transport;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    mapping
    +
    refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().
    +
    +[report issue]
    +
    map_transport
    +
    UPnP or NAT-PMP
    +
    +[report issue]
    +
    error
    +
    tells you what failed.
    +
    +[report issue]
    +
    +

    portmap_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a NAT router was successfully found and +a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP +capable router, this is typically generated once when mapping the TCP +port and, if DHT is enabled, when the UDP port is mapped.

    +
    +struct portmap_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::port_mapping;
    +   port_mapping_t const mapping;
    +   int const external_port;
    +   portmap_protocol const map_protocol;
    +   portmap_transport const map_transport;
    +};
    +
    +[report issue]
    +
    mapping
    +
    refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().
    +
    +[report issue]
    +
    external_port
    +
    the external port allocated for the mapping.
    +
    +[report issue]
    +
    +

    portmap_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated to log informational events related to either +UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP +and 1 = UPnP). Displaying these messages to an end user is only useful +for debugging the UPnP or NAT-PMP implementation. This alert is only +posted if the alert_category::port_mapping_log flag is enabled in +the alert mask.

    +
    +struct portmap_log_alert final : alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::port_mapping_log;
    +   portmap_transport const map_transport;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    the message associated with this log line

    +[report issue]
    +
    +
    +

    fastresume_rejected_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a fast resume file has been passed to +add_torrent() but the files on disk did not match the fast resume file. +The error_code explains the reason why the resume file was rejected.

    +
    +struct fastresume_rejected_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* file_path () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status
    +   | alert_category::error;
    +   error_code error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +

    file_path()

    +
    +char const* file_path () const;
    +
    +

    If the error happened to a specific file, this returns the path to it.

    +[report issue]
    +
    op
    +
    the underlying operation that failed
    +
    +[report issue]
    +
    +
    +

    peer_blocked_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when an incoming peer connection, or a peer that's about to be added +to our peer list, is blocked for some reason. This could be any of:

    +
      +
    • the IP filter
    • +
    • i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm)
    • +
    • the port filter
    • +
    • the peer has a low port and no_connect_privileged_ports is enabled
    • +
    • the protocol of the peer is blocked (uTP/TCP blocking)
    • +
    +
    +struct peer_blocked_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   enum reason_t
    +   {
    +      ip_filter,
    +      port_filter,
    +      i2p_mixed,
    +      privileged_ports,
    +      utp_disabled,
    +      tcp_disabled,
    +      invalid_local_interface,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::ip_block;
    +   int const reason;
    +};
    +
    +[report issue]
    +

    enum reason_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    ip_filter0 
    port_filter1 
    i2p_mixed2 
    privileged_ports3 
    utp_disabled4 
    tcp_disabled5 
    invalid_local_interface6 
    +[report issue]
    +
    reason
    +
    the reason for the peer being blocked. Is one of the values from the +reason_t enum.
    +
    +[report issue]
    +
    +
    +

    dht_announce_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a DHT node announces to an info-hash on our +DHT node. It belongs to the dht_notification category.

    +
    +struct dht_announce_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   aux::noexcept_movable<address> ip;
    +   int port;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    dht_get_peers_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when a DHT node sends a get_peers message to +our DHT node. It belongs to the dht_notification category.

    +
    +struct dht_get_peers_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted approximately once every second, and it contains +byte counters of most statistics that's tracked for torrents. Each active +torrent posts these alerts regularly. +This alert has been superseded by calling post_torrent_updates() +regularly on the session object. This alert will be removed

    +
    +struct stats_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   enum stats_channel
    +   {
    +      upload_payload,
    +      upload_protocol,
    +      download_payload,
    +      download_protocol,
    +      upload_ip_protocol,
    +      deprecated1,
    +      deprecated2,
    +      download_ip_protocol,
    +      deprecated3,
    +      deprecated4,
    +      num_channels,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +   std::array<int, num_channels> const transferred;
    +   int const interval;
    +};
    +
    +[report issue]
    +

    enum stats_channel

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    upload_payload0 
    upload_protocol1 
    download_payload2 
    download_protocol3 
    upload_ip_protocol4 
    deprecated15 
    deprecated26 
    download_ip_protocol7 
    deprecated38 
    deprecated49 
    num_channels10 
    +[report issue]
    +
    transferred
    +
    an array of samples. The enum describes what each sample is a +measurement of. All of these are raw, and not smoothing is performed.
    +
    +[report issue]
    +
    interval
    +
    the number of milliseconds during which these stats were collected. +This is typically just above 1000, but if CPU is limited, it may be +higher than that.
    +
    +[report issue]
    +
    +
    +

    cache_flushed_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the disk cache has been flushed for a specific +torrent as a result of a call to torrent_handle::flush_cache(). This +alert belongs to the storage_notification category, which must be +enabled to let this alert through. The alert is also posted when removing +a torrent from the session, once the outstanding cache flush is complete +and the torrent does no longer have any files open.

    +
    +struct cache_flushed_alert final : torrent_alert
    +{
    +   static constexpr alert_category_t static_category  = alert_category::storage;
    +};
    +
    +[report issue]
    +
    +

    lsd_peer_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when we receive a local service discovery message +from a peer for a torrent we're currently participating in.

    +
    +struct lsd_peer_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +};
    +
    +[report issue]
    +
    +

    trackerid_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted whenever a tracker responds with a trackerid. +The tracker ID is like a cookie. libtorrent will store the tracker ID +for this tracker and repeat it in subsequent announces.

    +
    +struct trackerid_alert final : tracker_alert
    +{
    +   std::string message () const override;
    +   char const* tracker_id () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +

    tracker_id()

    +
    +char const* tracker_id () const;
    +
    +

    The tracker ID returned by the tracker

    +[report issue]
    +
    +
    +

    dht_bootstrap_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when the initial DHT bootstrap is done.

    +
    +struct dht_bootstrap_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +};
    +
    +[report issue]
    +
    +

    torrent_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted whenever a torrent is transitioned into the error state.

    +
    +struct torrent_error_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* filename () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error | alert_category::status;
    +   error_code const error;
    +};
    +
    +[report issue]
    +

    filename()

    +
    +char const* filename () const;
    +
    +

    the filename (or object) the error occurred on.

    +[report issue]
    +
    error
    +
    specifies which error the torrent encountered.
    +
    +[report issue]
    +
    +
    +

    torrent_need_cert_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is always posted for SSL torrents. This is a reminder to the client that +the torrent won't work unless torrent_handle::set_ssl_certificate() is called with +a valid certificate. Valid certificates MUST be signed by the SSL certificate +in the .torrent file.

    +
    +struct torrent_need_cert_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +};
    +
    +[report issue]
    +
    +

    incoming_connection_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The incoming connection alert is posted every time we successfully accept +an incoming connection, through any mean. The most straight-forward ways +of accepting incoming connections are through the TCP listen socket and +the UDP listen socket for uTP sockets. However, connections may also be +accepted through a Socks5 or i2p listen socket, or via an SSL listen +socket.

    +
    +struct incoming_connection_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer;
    +   int const socket_type;
    +   aux::noexcept_movable<tcp::endpoint> endpoint;
    +};
    +
    +[report issue]
    +
    socket_type
    +

    tells you what kind of socket the connection was accepted +as:

    +
      +
    1. none (no socket instantiated)
    2. +
    3. TCP
    4. +
    5. Socks5
    6. +
    7. HTTP
    8. +
    9. uTP
    10. +
    11. i2p
    12. +
    13. SSL/TCP
    14. +
    15. SSL/Socks5
    16. +
    17. HTTPS (SSL/HTTP)
    18. +
    19. SSL/uTP
    20. +
    +
    +
    +[report issue]
    +
    endpoint
    +
    is the IP address and port the connection came from.
    +
    +[report issue]
    +
    +

    add_torrent_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is always posted when a torrent was attempted to be added +and contains the return status of the add operation. The torrent handle of the new +torrent can be found as the handle member in the base class. If adding +the torrent failed, error contains the error code.

    +
    +struct add_torrent_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   add_torrent_params params;
    +   error_code error;
    +};
    +
    +[report issue]
    +
    params
    +
    a copy of the parameters used when adding the torrent, it can be used +to identify which invocation to async_add_torrent() caused this alert.
    +
    +[report issue]
    +
    error
    +
    set to the error, if one occurred while adding the torrent.
    +
    +[report issue]
    +
    +

    state_update_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is only posted when requested by the user, by calling +session::post_torrent_updates() on the session. It contains the torrent +status of all torrents that changed since last time this message was +posted. Its category is status_notification, but it's not subject to +filtering, since it's only manually posted anyway.

    +
    +struct state_update_alert final : alert
    +{
    +   state_update_alert (aux::stack_allocator& alloc
    +      , std::vector<torrent_status> st);
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::status;
    +   std::vector<torrent_status> status;
    +};
    +
    +[report issue]
    +
    status
    +
    contains the torrent status of all torrents that changed since last +time this message was posted. Note that you can map a torrent status +to a specific torrent via its handle member. The receiving end is +suggested to have all torrents sorted by the torrent_handle or hashed +by it, for efficient updates.
    +
    +[report issue]
    +
    +

    session_stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The session_stats_alert is posted when the user requests session statistics by +calling post_session_stats() on the session object. Its category is +status_notification, but it is not subject to filtering, since it's only +manually posted anyway.

    +

    the message() member function returns a string representation of the values that +properly match the line returned in session_stats_header_alert::message().

    +

    this specific output is parsed by tools/parse_session_stats.py +if this is changed, that parser should also be changed

    +
    +struct session_stats_alert final : alert
    +{
    +   std::string message () const override;
    +   span<std::int64_t const> counters () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +};
    +
    +[report issue]
    +

    counters()

    +
    +span<std::int64_t const> counters () const;
    +
    +

    An array are a mix of counters and gauges, which meanings can be +queries via the session_stats_metrics() function on the session. The +mapping from a specific metric to an index into this array is constant +for a specific version of libtorrent, but may differ for other +versions. The intended usage is to request the mapping, i.e. call +session_stats_metrics(), once on startup, and then use that mapping to +interpret these values throughout the process' runtime.

    +

    For more information, see the session statistics section.

    +[report issue]
    +
    +
    +

    dht_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted when something fails in the DHT. This is not necessarily a fatal +error, but it could prevent proper operation

    +
    +struct dht_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error | alert_category::dht;
    +   error_code error;
    +   operation_t op;
    +};
    +
    +[report issue]
    +
    error
    +
    the error code
    +
    +[report issue]
    +
    op
    +
    the operation that failed
    +
    +[report issue]
    +
    +

    dht_immutable_item_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up immutable items in the DHT.

    +
    +struct dht_immutable_item_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash target;
    +   entry item;
    +};
    +
    +[report issue]
    +
    target
    +
    the target hash of the immutable item. This must +match the SHA-1 hash of the bencoded form of item.
    +
    +[report issue]
    +
    item
    +
    the data for this item
    +
    +[report issue]
    +
    +

    dht_mutable_item_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted as a response to a call to session::get_item(), +specifically the overload for looking up mutable items in the DHT.

    +
    +struct dht_mutable_item_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   std::array<char, 32> key;
    +   std::array<char, 64> signature;
    +   std::int64_t seq;
    +   std::string salt;
    +   entry item;
    +   bool authoritative;
    +};
    +
    +[report issue]
    +
    key
    +
    the public key that was looked up
    +
    +[report issue]
    +
    signature
    +
    the signature of the data. This is not the signature of the +plain encoded form of the item, but it includes the sequence number +and possibly the hash as well. See the dht_store document for more +information. This is primarily useful for echoing back in a store +request.
    +
    +[report issue]
    +
    seq
    +
    the sequence number of this item
    +
    +[report issue]
    +
    salt
    +
    the salt, if any, used to lookup and store this item. If no +salt was used, this is an empty string
    +
    +[report issue]
    +
    item
    +
    the data for this item
    +
    +[report issue]
    +
    authoritative
    +
    the last response for mutable data is authoritative.
    +
    +[report issue]
    +
    +

    dht_put_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this is posted when a DHT put operation completes. This is useful if the +client is waiting for a put to complete before shutting down for instance.

    +
    +struct dht_put_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash target;
    +   std::array<char, 32> public_key;
    +   std::array<char, 64> signature;
    +   std::string salt;
    +   std::int64_t seq;
    +   int num_success;
    +};
    +
    +[report issue]
    +
    target
    +
    the target hash the item was stored under if this was an immutable +item.
    +
    + + + +[report issue]
    +
    public_key signature salt seq
    +
    if a mutable item was stored, these are the public key, signature, +salt and sequence number the item was stored under.
    +
    +[report issue]
    +
    num_success
    +
    DHT put operation usually writes item to k nodes, maybe the node +is stale so no response, or the node doesn't support 'put', or the +token for write is out of date, etc. num_success is the number of +successful responses we got from the puts.
    +
    +[report issue]
    +
    +

    i2p_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is used to report errors in the i2p SAM connection

    +
    +struct i2p_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code error;
    +};
    +
    +[report issue]
    +
    error
    +
    the error that occurred in the i2p SAM connection
    +
    +[report issue]
    +
    +

    dht_outgoing_get_peers_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is generated when we send a get_peers request +It belongs to the dht_notification category.

    +
    +struct dht_outgoing_get_peers_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash info_hash;
    +   sha1_hash obfuscated_info_hash;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +};
    +
    +[report issue]
    +
    info_hash
    +
    the info_hash of the torrent we're looking for peers for.
    +
    +[report issue]
    +
    obfuscated_info_hash
    +
    if this was an obfuscated lookup, this is the info-hash target +actually sent to the node.
    +
    +[report issue]
    +
    endpoint
    +
    the endpoint we're sending this query to
    +
    +[report issue]
    +
    +

    log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by some session wide event. Its main purpose is +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert_category::session_log bit. +Furthermore, it's by default disabled as a build configuration.

    +
    +struct log_alert final : alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::session_log;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    returns the log message

    +[report issue]
    +
    +
    +

    torrent_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by torrent wide events. It's meant to be used for +trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert_category::torrent_log bit. By +default it is disabled as a build configuration.

    +
    +struct torrent_log_alert final : torrent_alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::torrent_log;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    returns the log message

    +[report issue]
    +
    +
    +

    peer_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted by events specific to a peer. It's meant to be used +for trouble shooting and debugging. It's not enabled by the default alert +mask and is enabled by the alert_category::peer_log bit. By +default it is disabled as a build configuration.

    +
    +struct peer_log_alert final : peer_alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   enum direction_t
    +   {
    +      incoming_message,
    +      outgoing_message,
    +      incoming,
    +      outgoing,
    +      info,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::peer_log;
    +   char const* event_type;
    +   direction_t direction;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    returns the log message

    +[report issue]
    +
    +

    enum direction_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    incoming_message0 
    outgoing_message1 
    incoming2 
    outgoing3 
    info4 
    +[report issue]
    +
    event_type
    +
    string literal indicating the kind of event. For messages, this is the +message name.
    +
    +[report issue]
    +
    +
    +

    lsd_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted if the local service discovery socket fails to start properly. +it's categorized as error_notification.

    +
    +struct lsd_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code error;
    +};
    +
    +[report issue]
    +
    error
    +
    The error code
    +
    +[report issue]
    +
    +

    dht_lookup

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    holds statistics about a current dht_lookup operation. +a DHT lookup is the traversal of nodes, looking up a +set of target nodes in the DHT for retrieving and possibly +storing information in the DHT

    +
    +struct dht_lookup
    +{
    +   char const* type;
    +   int outstanding_requests;
    +   int timeouts;
    +   int responses;
    +   int branch_factor;
    +   int nodes_left;
    +   int last_sent;
    +   int first_timeout;
    +   sha1_hash target;
    +};
    +
    +[report issue]
    +
    type
    +
    string literal indicating which kind of lookup this is
    +
    +[report issue]
    +
    outstanding_requests
    +
    the number of outstanding request to individual nodes +this lookup has right now
    +
    +[report issue]
    +
    timeouts
    +
    the total number of requests that have timed out so far +for this lookup
    +
    +[report issue]
    +
    responses
    +
    the total number of responses we have received for this +lookup so far for this lookup
    +
    +[report issue]
    +
    branch_factor
    +
    the branch factor for this lookup. This is the number of +nodes we keep outstanding requests to in parallel by default. +when nodes time out we may increase this.
    +
    +[report issue]
    +
    nodes_left
    +
    the number of nodes left that could be queries for this +lookup. Many of these are likely to be part of the trail +while performing the lookup and would never end up actually +being queried.
    +
    +[report issue]
    +
    last_sent
    +
    the number of seconds ago the +last message was sent that's still +outstanding
    +
    +[report issue]
    +
    first_timeout
    +
    the number of outstanding requests +that have exceeded the short timeout +and are considered timed out in the +sense that they increased the branch +factor
    +
    +[report issue]
    +
    target
    +
    the node-id or info-hash target for this lookup
    +
    +[report issue]
    +
    +

    dht_stats_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    contains current DHT state. Posted in response to session::post_dht_stats().

    +
    +struct dht_stats_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +   std::vector<dht_lookup> active_requests;
    +   std::vector<dht_routing_bucket> routing_table;
    +};
    +
    +[report issue]
    +
    active_requests
    +
    a vector of the currently running DHT lookups.
    +
    +[report issue]
    +
    routing_table
    +
    contains information about every bucket in the DHT routing +table.
    +
    +[report issue]
    +
    +

    incoming_request_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted every time an incoming request from a peer is accepted and queued +up for being serviced. This alert is only posted if +the alert_category::incoming_request flag is enabled in the alert +mask.

    +
    +struct incoming_request_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::incoming_request;
    +   peer_request req;
    +};
    +
    +[report issue]
    +
    req
    +
    the request this peer sent to us
    +
    +[report issue]
    +
    +

    dht_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    debug logging of the DHT when dht_log_notification is set in the alert +mask.

    +
    +struct dht_log_alert final : alert
    +{
    +   std::string message () const override;
    +   char const* log_message () const;
    +
    +   enum dht_module_t
    +   {
    +      tracker,
    +      node,
    +      routing_table,
    +      rpc_manager,
    +      traversal,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_log;
    +   dht_module_t module;
    +};
    +
    +[report issue]
    +

    log_message()

    +
    +char const* log_message () const;
    +
    +

    the log message

    +[report issue]
    +
    +

    enum dht_module_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tracker0 
    node1 
    routing_table2 
    rpc_manager3 
    traversal4 
    +[report issue]
    +
    module
    +
    the module, or part, of the DHT that produced this log message.
    +
    +[report issue]
    +
    +
    +

    dht_pkt_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted every time a DHT message is sent or received. It is +only posted if the alert_category::dht_log alert category is +enabled. It contains a verbatim copy of the message.

    +
    +struct dht_pkt_alert final : alert
    +{
    +   std::string message () const override;
    +   span<char const> pkt_buf () const;
    +
    +   enum direction_t
    +   {
    +      incoming,
    +      outgoing,
    +   };
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_log;
    +   direction_t direction;
    +   aux::noexcept_movable<udp::endpoint> node;
    +};
    +
    +[report issue]
    +

    pkt_buf()

    +
    +span<char const> pkt_buf () const;
    +
    +

    returns a pointer to the packet buffer and size of the packet, +respectively. This buffer is only valid for as long as the alert itself +is valid, which is owned by libtorrent and reclaimed whenever +pop_alerts() is called on the session.

    +[report issue]
    +
    +

    enum direction_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    incoming0 
    outgoing1 
    +[report issue]
    +
    direction
    +
    whether this is an incoming or outgoing packet.
    +
    +[report issue]
    +
    node
    +
    the DHT node we received this packet from, or sent this packet to +(depending on direction).
    +
    +[report issue]
    +
    +
    +

    dht_get_peers_reply_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    Posted when we receive a response to a DHT get_peers request.

    +
    +struct dht_get_peers_reply_alert final : alert
    +{
    +   std::string message () const override;
    +   int num_peers () const;
    +   std::vector<tcp::endpoint> peers () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_operation;
    +   sha1_hash info_hash;
    +};
    +
    +[report issue]
    +
    +

    dht_direct_response_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This is posted exactly once for every call to session_handle::dht_direct_request. +If the request failed, response() will return a default constructed bdecode_node.

    +
    +struct dht_direct_response_alert final : alert
    +{
    +   dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr);
    +   std::string message () const override;
    +   bdecode_node response () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   void const* userdata;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +};
    +
    + +[report issue]
    +

    dht_direct_response_alert() message()

    +
    +dht_direct_response_alert (aux::stack_allocator& alloc, void* userdata
    +      , udp::endpoint const& addr);
    +std::string message () const override;
    +
    +

    for when there was a timeout so we don't have a response

    +[report issue]
    +
    +
    +

    picker_log_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this is posted when one or more blocks are picked by the piece picker, +assuming the verbose piece picker logging is enabled (see +picker_log_notification).

    +
    +struct picker_log_alert final : peer_alert
    +{
    +   std::string message () const override;
    +   std::vector<piece_block> blocks () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::picker_log;
    +   static constexpr picker_flags_t partial_ratio  = 0_bit;
    +   static constexpr picker_flags_t prioritize_partials  = 1_bit;
    +   static constexpr picker_flags_t rarest_first_partials  = 2_bit;
    +   static constexpr picker_flags_t rarest_first  = 3_bit;
    +   static constexpr picker_flags_t reverse_rarest_first  = 4_bit;
    +   static constexpr picker_flags_t suggested_pieces  = 5_bit;
    +   static constexpr picker_flags_t prio_sequential_pieces  = 6_bit;
    +   static constexpr picker_flags_t sequential_pieces  = 7_bit;
    +   static constexpr picker_flags_t reverse_pieces  = 8_bit;
    +   static constexpr picker_flags_t time_critical  = 9_bit;
    +   static constexpr picker_flags_t random_pieces  = 10_bit;
    +   static constexpr picker_flags_t prefer_contiguous  = 11_bit;
    +   static constexpr picker_flags_t reverse_sequential  = 12_bit;
    +   static constexpr picker_flags_t backup1  = 13_bit;
    +   static constexpr picker_flags_t backup2  = 14_bit;
    +   static constexpr picker_flags_t end_game  = 15_bit;
    +   static constexpr picker_flags_t extent_affinity  = 16_bit;
    +   picker_flags_t const picker_flags;
    +};
    +
    +[report issue]
    +
    picker_flags
    +
    this is a bitmask of which features were enabled for this particular +pick. The bits are defined in the picker_flags_t enum.
    +
    +[report issue]
    +
    +

    session_error_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted when the session encounters a serious error, +potentially fatal

    +
    +struct session_error_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code const error;
    +};
    +
    +[report issue]
    +
    error
    +
    The error code, if one is associated with this error
    +
    +[report issue]
    +
    +

    dht_live_nodes_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted in response to a call to session::dht_live_nodes(). It contains the +live nodes from the DHT routing table of one of the DHT nodes running +locally.

    +
    +struct dht_live_nodes_alert final : alert
    +{
    +   std::string message () const override;
    +   int num_nodes () const;
    +   std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht;
    +   sha1_hash node_id;
    +};
    +
    + +[report issue]
    +

    num_nodes() nodes()

    +
    +int num_nodes () const;
    +std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +

    the number of nodes in the routing table and the actual nodes.

    +[report issue]
    +
    node_id
    +
    the local DHT node's node-ID this routing table belongs to
    +
    +[report issue]
    +
    +
    +

    session_stats_header_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    The session_stats_header alert is posted the first time +post_session_stats() is called

    +

    the message() member function returns a string representation of the +header that properly match the stats values string returned in +session_stats_alert::message().

    +

    this specific output is parsed by tools/parse_session_stats.py +if this is changed, that parser should also be changed

    +
    +struct session_stats_header_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::stats;
    +};
    +
    +[report issue]
    +
    +

    dht_sample_infohashes_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    posted as a response to a call to session::dht_sample_infohashes() with +the information from the DHT response message.

    +
    +struct dht_sample_infohashes_alert final : alert
    +{
    +   std::string message () const override;
    +   int num_samples () const;
    +   std::vector<sha1_hash> samples () const;
    +   int num_nodes () const;
    +   std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +   static constexpr alert_category_t static_category  = alert_category::dht_operation;
    +   aux::noexcept_movable<udp::endpoint> endpoint;
    +   time_duration const interval;
    +   int const num_infohashes;
    +};
    +
    + +[report issue]
    +

    samples() num_samples()

    +
    +int num_samples () const;
    +std::vector<sha1_hash> samples () const;
    +
    +

    returns the number of info-hashes returned by the node, as well as the +actual info-hashes. num_samples() is more efficient than +samples().size().

    +[report issue]
    +
    +

    num_nodes()

    +
    +int num_nodes () const;
    +
    +

    The total number of nodes returned by nodes().

    +[report issue]
    +
    +

    nodes()

    +
    +std::vector<std::pair<sha1_hash, udp::endpoint>> nodes () const;
    +
    +

    This is the set of more DHT nodes returned by the request.

    +

    The information is included so that indexing nodes can perform a key +space traversal with a single RPC per node by adjusting the target +value for each RPC.

    +[report issue]
    +
    endpoint
    +
    the node the request was sent to (and this response was received from)
    +
    +[report issue]
    +
    interval
    +
    the interval to wait before making another request to this node
    +
    +[report issue]
    +
    num_infohashes
    +
    This field indicates how many info-hash keys are currently in the node's storage. +If the value is larger than the number of returned samples it indicates that the +indexer may obtain additional samples after waiting out the interval.
    +
    +[report issue]
    +
    +
    +

    block_uploaded_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    This alert is posted when a block intended to be sent to a peer is placed in the +send buffer. Note that if the connection is closed before the send buffer is sent, +the alert may be posted without the bytes having been sent to the peer. +It belongs to the upload_notification category.

    +
    +struct block_uploaded_alert final : peer_alert
    +{
    +   std::string message () const override;
    +
    +   int const block_index;
    +   piece_index_t const piece_index;
    +};
    +
    +[report issue]
    +
    +

    alerts_dropped_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted to indicate to the client that some alerts were +dropped. Dropped meaning that the alert failed to be delivered to the +client. The most common cause of such failure is that the internal alert +queue grew too big (controlled by alert_queue_size).

    +
    +struct alerts_dropped_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   std::bitset<num_alert_types> dropped_alerts;
    +};
    +
    +[report issue]
    +
    dropped_alerts
    +
    a bitmask indicating which alerts were dropped. Each bit represents the +alert type ID, where bit 0 represents whether any alert of type 0 has +been dropped, and so on.
    +
    +[report issue]
    +
    +

    socks5_alert

    +

    Declared in "libtorrent/alert_types.hpp"

    +

    this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is +configured. It's enabled with the error_notification alert category.

    +
    +struct socks5_alert final : alert
    +{
    +   std::string message () const override;
    +
    +   static constexpr alert_category_t static_category  = alert_category::error;
    +   error_code error;
    +   operation_t op;
    +   aux::noexcept_movable<tcp::endpoint> ip;
    +};
    +
    +[report issue]
    +
    error
    +
    the error
    +
    +[report issue]
    +
    op
    +
    the operation that failed
    +
    +[report issue]
    +
    ip
    +
    the endpoint configured as the proxy
    +
    +[report issue]
    +
    +

    alert_cast()

    +

    Declared in "libtorrent/alert.hpp"

    +
    +template <class T> T const* alert_cast (alert const* a);
    +template <class T> T* alert_cast (alert* a);
    +
    +

    When you get an alert, you can use alert_cast<> to attempt to cast the +pointer to a specific alert type, in order to query it for more +information.

    +
    +

    Note

    +

    alert_cast<> can only cast to an exact alert type, not a base class

    +
    +[report issue]
    +
    +

    operation_name()

    +

    Declared in "libtorrent/operations.hpp"

    +
    +char const* operation_name (operation_t op);
    +
    +

    maps an operation id (from peer_error_alert and peer_disconnected_alert) +to its name. See operation_t for the constants

    +[report issue]
    +
    +

    enum operation_t

    +

    Declared in "libtorrent/operations.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    unknown0the error was unexpected and it is unknown which operation caused it
    bittorrent1this is used when the bittorrent logic +determines to disconnect
    iocontrol2a call to iocontrol failed
    getpeername3a call to getpeername() failed (querying the remote IP of a +connection)
    getname4a call to getname failed (querying the local IP of a +connection)
    alloc_recvbuf5an attempt to allocate a receive buffer failed
    alloc_sndbuf6an attempt to allocate a send buffer failed
    file_write7writing to a file failed
    file_read8reading from a file failed
    file9a non-read and non-write file operation failed
    sock_write10a socket write operation failed
    sock_read11a socket read operation failed
    sock_open12a call to open(), to create a socket socket failed
    sock_bind13a call to bind() on a socket failed
    available14an attempt to query the number of bytes available to read from a socket +failed
    encryption15a call related to bittorrent protocol encryption failed
    connect16an attempt to connect a socket failed
    ssl_handshake17establishing an SSL connection failed
    get_interface18a connection failed to satisfy the bind interface setting
    sock_listen19a call to listen() on a socket
    sock_bind_to_device20a call to the ioctl to bind a socket to a specific network device or +adapter
    sock_accept21a call to accept() on a socket
    parse_address22convert a string into a valid network address
    enum_if23enumeration network devices or adapters
    file_stat24invoking stat() on a file
    file_copy25copying a file
    file_fallocate26allocating storage for a file
    file_hard_link27creating a hard link
    file_remove28removing a file
    file_rename29renaming a file
    file_open30opening a file
    mkdir31creating a directory
    check_resume32check fast resume data against files on disk
    exception33an unknown exception
    alloc_cache_piece34allocate space for a piece in the cache
    partfile_move35move a part-file
    partfile_read36read from a part file
    partfile_write37write to a part-file
    hostname_lookup38a hostname lookup
    symlink39create or read a symlink
    handshake40handshake with a peer or server
    sock_option41set socket option
    enum_route42enumeration network routes
    +[report issue]
    +
    +

    enum socket_type_t

    +

    Declared in "libtorrent/alert_types.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    tcp0 
    tcp_ssl1 
    udp2 
    i2p3 
    socks54 
    utp_ssl5 
    +[report issue]
    +
    +

    alert_category_t

    +

    Declared in "libtorrent/alert.hpp"

    +
    +
    error
    +

    Enables alerts that report an error. This includes:

    +
      +
    • tracker errors
    • +
    • tracker warnings
    • +
    • file errors
    • +
    • resume data failures
    • +
    • web seed errors
    • +
    • .torrent files errors
    • +
    • listen socket errors
    • +
    • port mapping errors
    • +
    +
    +
    +
    +
    peer
    +
    Enables alerts when peers send invalid requests, get banned or +snubbed.
    +
    +
    +
    port_mapping
    +
    Enables alerts for port mapping events. For NAT-PMP and UPnP.
    +
    +
    +
    storage
    +
    Enables alerts for events related to the storage. File errors and +synchronization events for moving the storage, renaming files etc.
    +
    +
    +
    tracker
    +
    Enables all tracker events. Includes announcing to trackers, +receiving responses, warnings and errors.
    +
    +
    +
    connect
    +
    Low level alerts for when peers are connected and disconnected.
    +
    +
    +
    status
    +
    Enables alerts for when a torrent or the session changes state.
    +
    +
    +
    ip_block
    +
    Alerts when a peer is blocked by the ip blocker or port blocker.
    +
    +
    +
    performance_warning
    +
    Alerts when some limit is reached that might limit the download +or upload rate.
    +
    +
    +
    dht
    +
    Alerts on events in the DHT node. For incoming searches or +bootstrapping being done etc.
    +
    +
    +
    stats
    +
    If you enable these alerts, you will receive a stats_alert +approximately once every second, for every active torrent. +These alerts contain all statistics counters for the interval since +the lasts stats alert.
    +
    +
    +
    session_log
    +
    Enables debug logging alerts. These are available unless libtorrent +was built with logging disabled (TORRENT_DISABLE_LOGGING). The +alerts being posted are log_alert and are session wide.
    +
    +
    +
    torrent_log
    +
    Enables debug logging alerts for torrents. These are available +unless libtorrent was built with logging disabled +(TORRENT_DISABLE_LOGGING). The alerts being posted are +torrent_log_alert and are torrent wide debug events.
    +
    +
    +
    peer_log
    +
    Enables debug logging alerts for peers. These are available unless +libtorrent was built with logging disabled +(TORRENT_DISABLE_LOGGING). The alerts being posted are +peer_log_alert and low-level peer events and messages.
    +
    +
    +
    incoming_request
    +
    enables the incoming_request_alert.
    +
    +
    +
    dht_log
    +
    enables dht_log_alert, debug logging for the DHT
    +
    +
    +
    dht_operation
    +
    enable events from pure dht operations not related to torrents
    +
    +
    +
    port_mapping_log
    +
    enables port mapping log events. This log is useful +for debugging the UPnP or NAT-PMP implementation
    +
    +
    +
    picker_log
    +
    enables verbose logging from the piece picker.
    +
    +
    +
    file_progress
    +
    alerts when files complete downloading
    +
    +
    +
    piece_progress
    +
    alerts when pieces complete downloading or fail hash check
    +
    +
    +
    upload
    +
    alerts when we upload blocks to other peers
    +
    +
    +
    block_progress
    +
    alerts on individual blocks being requested, downloading, finished, +rejected, time-out and cancelled. This is likely to post alerts at a +high rate.
    +
    +
    +
    all
    +

    The full bitmask, representing all available categories.

    +

    since the enum is signed, make sure this isn't +interpreted as -1. For instance, boost.python +does that and fails when assigning it to an +unsigned parameter.

    +
    +
    +[report issue]
    +
    +

    int

    +

    Declared in "libtorrent/alert_types.hpp"

    +
    +
    user_alert_id
    +
    user defined alerts should use IDs greater than this
    +
    +
    +
    num_alert_types
    +
    this constant represents "max_alert_index" + 1
    +
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Filter

    +[report issue]
    +

    ip_filter

    +

    Declared in "libtorrent/ip_filter.hpp"

    +

    The ip_filter class is a set of rules that uniquely categorizes all +ip addresses as allowed or disallowed. The default constructor creates +a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +the IPv4 range, and the equivalent range covering all addresses for the +IPv6 range).

    +

    A default constructed ip_filter does not filter any address.

    +
    +struct ip_filter
    +{
    +   void add_rule (address const& first, address const& last, std::uint32_t flags);
    +   std::uint32_t access (address const& addr) const;
    +   filter_tuple_t export_filter () const;
    +
    +   enum access_flags
    +   {
    +      blocked,
    +   };
    +};
    +
    +[report issue]
    +

    add_rule()

    +
    +void add_rule (address const& first, address const& last, std::uint32_t flags);
    +
    +

    Adds a rule to the filter. first and last defines a range of +ip addresses that will be marked with the given flags. The flags +can currently be 0, which means allowed, or ip_filter::blocked, which +means disallowed.

    +

    precondition: +first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()

    +

    postcondition: +access(x) == flags for every x in the range [first, last]

    +

    This means that in a case of overlapping ranges, the last one applied takes +precedence.

    +[report issue]
    +
    +

    access()

    +
    +std::uint32_t access (address const& addr) const;
    +
    +

    Returns the access permissions for the given address (addr). The permission +can currently be 0 or ip_filter::blocked. The complexity of this operation +is O(log n), where n is the minimum number of non-overlapping ranges to describe +the current filter.

    +[report issue]
    +
    +

    export_filter()

    +
    +filter_tuple_t export_filter () const;
    +
    +

    This function will return the current state of the filter in the minimum number of +ranges possible. They are sorted from ranges in low addresses to high addresses. Each +entry in the returned vector is a range with the access control specified in its +flags field.

    +

    The return value is a tuple containing two range-lists. One for IPv4 addresses +and one for IPv6 addresses.

    +[report issue]
    +
    +

    enum access_flags

    +

    Declared in "libtorrent/ip_filter.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    blocked1indicates that IPs in this range should not be connected +to nor accepted as incoming connections
    +[report issue]
    +
    +
    +

    port_filter

    +

    Declared in "libtorrent/ip_filter.hpp"

    +

    the port filter maps non-overlapping port ranges to flags. This +is primarily used to indicate whether a range of ports should +be connected to or not. The default is to have the full port +range (0-65535) set to flag 0.

    +
    +class port_filter
    +{
    +   void add_rule (std::uint16_t first, std::uint16_t last, std::uint32_t flags);
    +   std::uint32_t access (std::uint16_t port) const;
    +
    +   enum access_flags
    +   {
    +      blocked,
    +   };
    +};
    +
    +[report issue]
    +

    add_rule()

    +
    +void add_rule (std::uint16_t first, std::uint16_t last, std::uint32_t flags);
    +
    +

    set the flags for the specified port range (first, last) to +flags overwriting any existing rule for those ports. The range +is inclusive, i.e. the port last also has the flag set on it.

    +[report issue]
    +
    +

    access()

    +
    +std::uint32_t access (std::uint16_t port) const;
    +
    +

    test the specified port (port) for whether it is blocked +or not. The returned value is the flags set for this port. +see access_flags.

    +[report issue]
    +
    +

    enum access_flags

    +

    Declared in "libtorrent/ip_filter.hpp"

    + +++++ + + + + + + + + + + + + +
    namevaluedescription
    blocked1this flag indicates that destination ports in the +range should not be connected to
    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +
    +

    Settings

    +

    You have some control over session configuration through the session::apply_settings() +member function. To change one or more configuration options, create a settings_pack +object and fill it with the settings to be set and pass it in to session::apply_settings().

    +

    The settings_pack object is a collection of settings updates that are applied +to the session when passed to session::apply_settings(). It's empty when +constructed.

    +

    You have control over proxy and authorization settings and also the user-agent +that will be sent to the tracker. The user-agent will also be used to identify the +client with other peers.

    +

    Each configuration option is named with an enum value inside the +settings_pack class. These are the available settings:

    +[report issue]
    +

    settings_pack

    +

    Declared in "libtorrent/settings_pack.hpp"

    +

    The settings_pack struct, contains the names of all settings as +enum values. These values are passed in to the set_str(), +set_int(), set_bool() functions, to specify the setting to +change.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    user_agentstringlibtorrent/
    +

    this is the client identification to the tracker. The recommended +format of this string is: "client-name/client-version +libtorrent/libtorrent-version". This name will not only be used when +making HTTP requests, but also when sending extended headers to +peers that support that extension. It may not contain r or n

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_ipstringnullptr
    +

    announce_ip is the ip address passed along to trackers as the +&ip= parameter. If left as the default, that parameter is +omitted.

    +
    +

    Note

    +

    This setting is only meant for very special cases where a seed is +running on the same host as the tracker, and the tracker accepts +the IP parameter (which normal trackers don't). Do not set this +option unless you also control the tracker.

    +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    handshake_client_versionstringnullptr
    +

    this is the client name and version identifier sent to peers in the +handshake message. If this is an empty string, the user_agent is +used instead. This string must be a UTF-8 encoded unicode string.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    outgoing_interfacesstring 
    +

    This controls which IP address outgoing TCP peer connections are bound +to, in addition to controlling whether such connections are also +bound to a specific network interface/adapter (bind-to-device). +This string is a comma-separated list of IP addresses and +interface names. An empty string will not bind TCP sockets to a +device, and let the network stack assign the local address. A +list of names will be used to bind outgoing TCP sockets in a +round-robin fashion. An IP address will simply be used to bind() +the socket. An interface name will attempt to bind the socket to +that interface. If that fails, or is unsupported, one of the IP +addresses configured for that interface is used to bind() the +socket to. If the interface or adapter doesn't exist, the +outgoing peer connection will fail with an error message suggesting +the device cannot be found. Adapter names on Unix systems are of +the form "eth0", "eth1", "tun0", etc. This may be useful for +clients that are multi-homed. Binding an outgoing connection to a +local IP does not necessarily make the connection via the +associated NIC/Adapter.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_interfacesstring0.0.0.0:6881,[::]:6881
    +

    a comma-separated list of (IP or device name, port) pairs. These are +the listen ports that will be opened for accepting incoming uTP and +TCP peer connections. These are also used for outgoing uTP and UDP +tracker connections and DHT nodes.

    +

    It is possible to listen on multiple interfaces and +multiple ports. Binding to port 0 will make the operating system +pick the port.

    +
    +

    Note

    +

    There are reasons to stick to the same port across sessions, +which would mean only using port 0 on the first start, and +recording the port that was picked for subsequent startups. +Trackers, the DHT and other peers will remember the port they see +you use and hand that port out to other peers trying to connect +to you, as well as trying to connect to you themselves.

    +
    +

    A port that has an "s" suffix will accept SSL peer connections. (note +that SSL sockets are only available in builds with SSL support)

    +

    A port that has an "l" suffix will be considered a local network. +i.e. it's assumed to only be able to reach hosts in the same local +network as the IP address (based on the netmask associated with the +IP, queried from the operating system).

    +

    if binding fails, the listen_failed_alert is posted. Once a +socket binding succeeds (if it does), the listen_succeeded_alert +is posted. There may be multiple failures before a success.

    +

    If a device name that does not exist is configured, no listen +socket will be opened for that interface. If this is the only +interface configured, it will be as if no listen ports are +configured.

    +

    If no listen ports are configured (e.g. listen_interfaces is an +empty string), networking will be disabled. No DHT will start, no +outgoing uTP or tracker connections will be made. No incoming TCP +or uTP connections will be accepted. (outgoing TCP connections +will still be possible, depending on +settings_pack::outgoing_interfaces).

    +

    For example: +[::1]:8888 - will only accept connections on the IPv6 loopback +address on port 8888.

    +

    eth0:4444,eth1:4444 - will accept connections on port 4444 on +any IP address bound to device eth0 or eth1.

    +

    [::]:0s - will accept SSL connections on a port chosen by the +OS. And not accept non-SSL connections at all.

    +

    0.0.0.0:6881,[::]:6881 - binds to all interfaces on port 6881.

    +

    10.0.1.13:6881l - binds to the local IP address, port 6881, but +only allow talking to peers on the same local network. The netmask +is queried from the operating system. Interfaces marked l are +not announced to trackers, unless the tracker is also on the same +local network.

    +

    Windows OS network adapter device name must be specified with GUID. +It can be obtained from "netsh lan show interfaces" command output. +GUID must be uppercased string embraced in curly brackets. +{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777 - will accept +connections on port 7777 on adapter with this GUID.

    +

    For more information, see the Multi-homed hosts section.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_hostnamestring 
    +

    when using a proxy, this is the hostname where the proxy is running +see proxy_type. Note that when using a proxy, the +settings_pack::listen_interfaces setting is overridden and only a +single interface is created, just to contact the proxy. This +means a proxy cannot be combined with SSL torrents or multiple +listen interfaces. This proxy listen interface will not accept +incoming TCP connections, will not map ports with any gateway and +will not enable local service discovery. All traffic is supposed +to be channeled through the proxy.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    proxy_usernamestring 
    proxy_passwordstring 
    +

    when using a proxy, these are the credentials (if any) to use when +connecting to it. see proxy_type

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    i2p_hostnamestring 
    +

    sets the i2p SAM bridge to connect to. set the port with the +i2p_port setting.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_fingerprintstring-LT1290-
    +

    this is the fingerprint for the client. It will be used as the +prefix to the peer_id. If this is 20 bytes (or longer) it will be +truncated to 20 bytes and used as the entire peer-id

    +

    There is a utility function, generate_fingerprint() that can be used +to generate a standard client peer ID fingerprint prefix.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_bootstrap_nodesstringdht.libtorrent.org:25401
    +

    This is a comma-separated list of IP port-pairs. They will be added +to the DHT node (if it's enabled) as back-up nodes in case we don't +know of any.

    +

    Changing these after the DHT has been started may not have any +effect until the DHT is restarted.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_multiple_connections_per_ipboolfalse
    +

    determines if connections from the same IP address as existing +connections should be rejected or not. Rejecting multiple connections +from the same IP address will prevent abusive +behavior by peers. The logic for determining whether connections are +to the same peer is more complicated with this enabled, and more +likely to fail in some edge cases. It is not recommended to enable +this feature.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    send_redundant_havebooltrue
    +

    send_redundant_have controls if have messages will be sent to +peers that already have the piece. This is typically not necessary, +but it might be necessary for collecting statistics in some cases.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_dht_as_fallbackboolfalse
    +

    use_dht_as_fallback determines how the DHT is used. If this is +true, the DHT will only be used for torrents where all trackers in +its tracker list has failed. Either by an explicit error message or +a time out. If this is false, the DHT is used regardless of if the +trackers fail or not.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    upnp_ignore_nonroutersboolfalse
    +

    upnp_ignore_nonrouters indicates whether or not the UPnP +implementation should ignore any broadcast response from a device +whose address is not on our subnet. i.e. +it's a way to not talk to other people's routers by mistake.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_parole_modebooltrue
    +

    use_parole_mode specifies if parole mode should be used. Parole +mode means that peers that participate in pieces that fail the hash +check are put in a mode where they are only allowed to download +whole pieces. If the whole piece a peer in parole mode fails the +hash check, it is banned. If a peer participates in a piece that +passes the hash check, it is taken out of parole mode.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    use_read_cachebooltrue
    +

    enable and disable caching of blocks read from disk. the purpose of +the read cache is partly read-ahead of requests but also to avoid +reading blocks back from the disk multiple times for popular +pieces.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    coalesce_readsboolfalse
    coalesce_writesboolfalse
    +

    allocate separate, contiguous, buffers for read and write calls. +Only used where writev/readv cannot be used will use more RAM but +may improve performance

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_prefer_seedsboolfalse
    +

    if true, prefer seeding torrents when determining which torrents to give +active slots to. If false, give preference to downloading torrents

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dont_count_slow_torrentsbooltrue
    +

    if dont_count_slow_torrents is true, torrents without any +payload transfers are not subject to the active_seeds and +active_downloads limits. This is intended to make it more +likely to utilize all available bandwidth, and avoid having +torrents that don't transfer anything block the active slots.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    close_redundant_connectionsbooltrue
    +

    close_redundant_connections specifies whether libtorrent should +close connections where both ends have no utility in keeping the +connection open. For instance if both ends have completed their +downloads, there's no point in keeping it open.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prioritize_partial_piecesboolfalse
    +

    If prioritize_partial_pieces is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    rate_limit_ip_overheadbooltrue
    +

    if set to true, the estimated TCP/IP overhead is drained from the +rate limiters, to avoid exceeding the limits with the total traffic

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    announce_to_all_tiersboolfalse
    announce_to_all_trackersboolfalse
    +

    announce_to_all_trackers controls how multi tracker torrents +are treated. If this is set to true, all trackers in the same tier +are announced to in parallel. If all trackers in tier 0 fails, all +trackers in tier 1 are announced as well. If it's set to false, the +behavior is as defined by the multi tracker specification.

    +

    announce_to_all_tiers also controls how multi tracker torrents +are treated. When this is set to true, one tracker from each tier +is announced to. This is the uTorrent behavior. To be compliant +with the Multi-tracker specification, set it to false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prefer_udp_trackersbooltrue
    +

    prefer_udp_trackers: true means that trackers +may be rearranged in a way that udp trackers are always tried +before http trackers for the same hostname. Setting this to false +means that the tracker's tier is respected and there's no +preference of one protocol over another.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    disable_hash_checksboolfalse
    +

    when set to true, all data downloaded from peers will be assumed to +be correct, and not tested to match the hashes in the torrent this +is only useful for simulation and testing purposes (typically +combined with disabled_storage)

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_i2p_mixedboolfalse
    +

    if this is true, i2p torrents are allowed to also get peers from +other sources than the tracker, and connect to regular IPs, not +providing any anonymization. This may be useful if the user is not +interested in the anonymization of i2p, but still wants to be able +to connect to i2p peers.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    volatile_read_cacheboolfalse
    +

    volatile_read_cache, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first +place.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_atime_storagebooltrue
    +

    no_atime_storage this is a Linux-only option and passes in the +O_NOATIME to open() when opening files. This may lead to +some disk performance improvements.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    incoming_starts_queued_torrentsboolfalse
    +

    incoming_starts_queued_torrents. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. the +torrent is paused and auto managed, this feature affects whether or +not it is automatically started on an incoming connection. The main +reason to queue torrents, is not to make them unavailable, but to +save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_true_downloadedboolfalse
    +

    when set to true, the downloaded counter sent to trackers will +include the actual number of payload bytes downloaded including +redundant bytes. If set to false, it will not include any redundancy +bytes

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    strict_end_game_modebooltrue
    +

    strict_end_game_mode controls when a +block may be requested twice. If this is true, a block may only +be requested twice when there's at least one request to every piece +that's left to download in the torrent. This may slow down progress +on some pieces sometimes, but it may also avoid downloading a lot +of redundant bytes. If this is false, libtorrent attempts to +use each peer connection to its max, by always requesting +something, even if it means requesting something that has been +requested from another peer already.

    + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    enable_outgoing_utpbooltrue
    enable_incoming_utpbooltrue
    enable_outgoing_tcpbooltrue
    enable_incoming_tcpbooltrue
    +

    when set to true, libtorrent will try to make outgoing utp +connections controls whether libtorrent will accept incoming +connections or make outgoing connections of specific type.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_recheck_incomplete_resumeboolfalse
    +

    no_recheck_incomplete_resume determines if the storage should +check the whole files when resume data is incomplete or missing or +whether it should simply assume we don't have any of the data. If +false, any existing files will be checked. +By setting this setting to true, the files won't be checked, but +will go straight to download mode.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    anonymous_modeboolfalse
    +

    anonymous_mode: When set to true, the client +tries to hide its identity to a certain degree. The user-agent will be +reset to an empty string (except for private torrents). Trackers +will only be used if they are using a proxy server. +The listen sockets are closed, and incoming +connections will only be accepted through a SOCKS5 or I2P proxy (if +a peer proxy is set up and is run on the same machine as the +tracker proxy). Since no incoming connections are accepted, +NAT-PMP, UPnP, DHT and local peer discovery are all turned off when +this setting is enabled.

    +

    If you're using I2P, it might make sense to enable anonymous mode +as well.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_web_seed_downloadsbooltrue
    +

    specifies whether downloads from web seeds is reported to the +tracker or not. Turning it off also excludes web +seed traffic from other stats and download rate reporting via the +libtorrent API.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seeding_outgoing_connectionsbooltrue
    +

    seeding_outgoing_connections determines if seeding (and +finished) torrents should attempt to make outgoing connections or +not. It may be set to false in very +specific applications where the cost of making outgoing connections +is high, and there are no or small benefits of doing so. For +instance, if no nodes are behind a firewall or a NAT, seeds don't +need to make outgoing connections.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    no_connect_privileged_portsboolfalse
    +

    when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    smooth_connectsbooltrue
    +

    smooth_connects means the number of +connection attempts per second may be limited to below the +connection_speed, in case we're close to bump up against the +limit of number of connections. The intention of this setting is to +more evenly distribute our connection attempts over time, instead +of attempting to connect in batches, and timing them out in +batches.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    always_send_user_agentboolfalse
    +

    always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    apply_ip_filter_to_trackersbooltrue
    +

    apply_ip_filter_to_trackers determines +whether the IP filter applies to trackers as well as peers. If this +is set to false, trackers are exempt from the IP filter (if there +is one). If no IP filter is set, this setting is irrelevant.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    ban_web_seedsbooltrue
    +

    when true, web seeds sending bad data will be banned

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allow_partial_disk_writesbooltrue
    +

    when set to false, the write_cache_line_size will apply across +piece boundaries. this is a bad idea unless the piece picker also +is configured to have an affinity to pick pieces belonging to the +same write cache line as is configured in the disk cache.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    support_share_modebooltrue
    +

    if false, prevents libtorrent to advertise share-mode support

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    support_merkle_torrentsbooltrue
    +

    if this is false, don't advertise support for the Tribler merkle +tree piece message

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    report_redundant_bytesbooltrue
    +

    if this is true, the number of redundant bytes is sent to the +tracker

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_system_port_fallbackbooltrue
    +

    if this is true, libtorrent will fall back to listening on a port +chosen by the operating system (i.e. binding to port 0). If a +failure is preferred, set this to false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    announce_crypto_supportbooltrue
    +

    when this is true, and incoming encrypted connections are enabled, +&supportcrypt=1 is included in http tracker announces

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_upnpbooltrue
    +

    Starts and stops the UPnP service. When started, the listen port +and the DHT port are attempted to be forwarded on local UPnP router +devices.

    +

    The upnp object returned by start_upnp() can be used to add and +remove arbitrary port mappings. Mapping status is returned through +the portmap_alert and the portmap_error_alert. The object will be +valid until stop_upnp() is called. See upnp and nat pmp.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_natpmpbooltrue
    +

    Starts and stops the NAT-PMP service. When started, the listen port +and the DHT port are attempted to be forwarded on the router +through NAT-PMP.

    +

    The natpmp object returned by start_natpmp() can be used to add +and remove arbitrary port mappings. Mapping status is returned +through the portmap_alert and the portmap_error_alert. The object +will be valid until stop_natpmp() is called. See +upnp and nat pmp.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_lsdbooltrue
    +

    Starts and stops Local Service Discovery. This service will +broadcast the info-hashes of all the non-private torrents on the +local network to look for peers on the same swarm within multicast +reach.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_dhtbooltrue
    +

    starts the dht node and makes the trackerless service available to +torrents.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    prefer_rc4boolfalse
    +

    if the allowed encryption level is both, setting this to true will +prefer RC4 if both methods are offered, plain text otherwise

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_hostnamesbooltrue
    +

    if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_peer_connectionsbooltrue
    +

    if true, peer connections are made (and accepted) over the +configured proxy, if any. Web seeds as well as regular bittorrent +peer connections are considered "peer connections". Anything +transporting actual torrent payload (trackers and DHT traffic are +not considered peer connections).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_sequentialbooltrue
    +

    if this setting is true, torrents with a very high availability of +pieces (and seeds) are downloaded sequentially. This is more +efficient for the disk I/O. With many seeds, the download order is +unlikely to matter anyway

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_tracker_connectionsbooltrue
    +

    if true, tracker connections are made over the configured proxy, if +any.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    enable_ip_notifierbooltrue
    +

    Starts and stops the internal IP table route changes notifier.

    +

    The current implementation supports multiple platforms, and it is +recommended to have it enable, but you may want to disable it if +it's supported but unreliable, or if you have a better way to +detect the changes. In the later case, you should manually call +session_handle::reopen_network_sockets to ensure network +changes are taken in consideration.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_prefer_verified_node_idsbooltrue
    +

    when this is true, nodes whose IDs are derived from their source +IP according to BEP 42 are preferred in the routing table.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    piece_extent_affinityboolfalse
    +

    when this is true, create an affinity for downloading 4 MiB extents +of adjacent pieces. This is an attempt to achieve better disk I/O +throughput by downloading larger extents of bytes, for torrents with +small piece sizes

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    validate_https_trackersboolfalse
    +

    when set to true, the certificate of HTTPS trackers will be +validated against the system's certificate store (as defined by +OpenSSL). If the system does not have one, enabling this may cause +HTTPS trackers to fail.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_completion_timeoutint30
    +

    tracker_completion_timeout is the number of seconds the tracker +connection will wait from when it sent the request until it +considers the tracker to have timed-out.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_receive_timeoutint10
    +

    tracker_receive_timeout is the number of seconds to wait to +receive any data from the tracker. If no data is received for this +number of seconds, the tracker will be considered as having timed +out. If a tracker is down, this is the kind of timeout that will +occur.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    stop_tracker_timeoutint5
    +

    stop_tracker_timeout is the number of seconds to wait when +sending a stopped message before considering a tracker to have +timed out. This is usually shorter, to make the client quit faster. +If the value is set to 0, the connections to trackers with the +stopped event are suppressed.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_maximum_response_lengthint1024*1024
    +

    this is the maximum number of bytes in a tracker response. If a +response size passes this number of bytes it will be rejected and +the connection will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get 20 bytes of gzip +response that'll expand to 2 megabytes, it will be interrupted +before the entire response has been uncompressed (assuming the +limit is lower than 2 MiB).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    piece_timeoutint20
    +

    the number of seconds from a request is sent until it times out if +no piece response is returned.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    request_timeoutint60
    +

    the number of seconds one block (16 kiB) is expected to be received +within. If it's not, the block is requested from a different peer

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    request_queue_timeint3
    +

    the length of the request queue given in the number of seconds it +should take for the other end to send all the pieces. i.e. the +actual number of requests depends on the download rate and this +number.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_allowed_in_request_queueint500
    +

    the number of outstanding block requests a peer is allowed to queue +up in the client. If a peer sends more requests than this (before +the first one has been sent) the last request will be dropped. the +higher this is, the faster upload speeds the client can get to a +single peer.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_out_request_queueint500
    +

    max_out_request_queue is the maximum number of outstanding +requests to send to a peer. This limit takes precedence over +request_queue_time. i.e. no matter the download speed, the +number of outstanding requests will never exceed this limit.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    whole_pieces_thresholdint20
    +

    if a whole piece can be downloaded in this number of seconds, or +less, the peer_connection will prefer to request whole pieces at a +time from this peer. The benefit of this is to better utilize disk +caches by doing localized accesses and also to make it easier to +identify bad peers if a piece fails the hash check.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_timeoutint120
    +

    peer_timeout is the number of seconds the peer connection +should wait (for any activity on the peer connection) before +closing it due to time out. 120 seconds is +specified in the protocol specification. After half +the time out, a keep alive message is sent.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_timeoutint20
    +

    same as peer_timeout, but only applies to url-seeds. this is +usually set lower, because web servers are expected to be more +reliable.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_pipeline_sizeint5
    +

    controls the pipelining size of url and http seeds. i.e. the number of HTTP +request to keep outstanding before waiting for the first one to +complete. It's common for web servers to limit this to a relatively +low number, like 5

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_wait_retryint30
    +

    number of seconds until a new retry of a url-seed takes place. +Default retry value for http-seeds that don't provide +a valid retry-after header.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    file_pool_sizeint40
    +

    sets the upper limit on the total number of files this session will +keep open. The reason why files are left open at all is that some +anti virus software hooks on every file close, and scans the file +for viruses. deferring the closing of the files will be the +difference between a usable system and a completely hogged down +system. Most operating systems also has a limit on the total number +of file descriptors a process may have open.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_failcountint3
    +

    max_failcount is the maximum times we try to +connect to a peer before stop connecting again. If a +peer succeeds, the failure counter is reset. If a +peer is retrieved from a peer source (other than DHT) +the failcount is decremented by one, allowing another +try.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    min_reconnect_timeint60
    +

    the number of seconds to wait to reconnect to a peer. this time is +multiplied with the failcount.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_connect_timeoutint15
    +

    peer_connect_timeout the number of seconds to wait after a +connection attempt is initiated to a peer until it is considered as +having timed out. This setting is especially important in case the +number of half-open connections are limited, since stale half-open +connection may delay the connection of other peers considerably.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connection_speedint30
    +

    connection_speed is the number of connection attempts that are +made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    inactivity_timeoutint600
    +

    if a peer is uninteresting and uninterested for longer than this +number of seconds, it will be disconnected.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    unchoke_intervalint15
    +

    unchoke_interval is the number of seconds between +chokes/unchokes. On this interval, peers are re-evaluated for being +choked/unchoked. This is defined as 30 seconds in the protocol, and +it should be significantly longer than what it takes for TCP to +ramp up to it's max rate.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    optimistic_unchoke_intervalint30
    +

    optimistic_unchoke_interval is the number of seconds between +each optimistic unchoke. On this timer, the currently +optimistically unchoked peer will change.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    num_wantint200
    +

    num_want is the number of peers we want from each tracker +request. It defines what is sent as the &num_want= parameter to +the tracker.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    initial_picker_thresholdint4
    +

    initial_picker_threshold specifies the number of pieces we need +before we switch to rarest first picking. The first +initial_picker_threshold pieces in any torrent are picked at random +, the following pieces are picked in rarest first order.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allowed_fast_set_sizeint5
    +

    the number of allowed pieces to send to peers that supports the +fast extensions

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    suggest_modeintsettings_pack::no_piece_suggestions
    +

    suggest_mode controls whether or not libtorrent will send out +suggest messages to create a bias of its peers to request certain +pieces. The modes are:

    +
      +
    • no_piece_suggestions which will not send out suggest messages.
    • +
    • suggest_read_cache which will send out suggest messages for +the most recent pieces that are in the read cache.
    • +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_queued_disk_bytesint1024 * 1024
    +

    max_queued_disk_bytes is the maximum number of bytes, to +be written to disk, that can wait in the disk I/O thread queue. +This queue is only for waiting for the disk I/O thread to receive +the job and either write it to disk or insert it in the write +cache. When this limit is reached, the peer connections will stop +reading data from their sockets, until the disk thread catches up. +Setting this too low will severely limit your download rate.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    handshake_timeoutint10
    +

    the number of seconds to wait for a handshake response from a peer. +If no response is received within this time, the peer is +disconnected.

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    send_buffer_low_watermarkint10 * 1024
    send_buffer_watermarkint500 * 1024
    send_buffer_watermark_factorint50
    +

    send_buffer_low_watermark the minimum send buffer target size +(send buffer includes bytes pending being read from disk). For good +and snappy seeding performance, set this fairly high, to at least +fit a few blocks. This is essentially the initial window size which +will determine how fast we can ramp up the send rate

    +

    if the send buffer has fewer bytes than send_buffer_watermark, +we'll read another 16 kiB block onto it. If set too small, upload +rate capacity will suffer. If set too high, memory will be wasted. +The actual watermark may be lower than this in case the upload rate +is low, this is the upper limit.

    +

    the current upload rate to a peer is multiplied by this factor to +get the send buffer watermark. The factor is specified as a +percentage. i.e. 50 -> 0.5 This product is clamped to the +send_buffer_watermark setting to not exceed the max. For high +speed upload, this should be set to a greater value than 100. For +high capacity connections, setting this higher can improve upload +performance and disk throughput. Setting it too high may waste RAM +and create a bias towards read jobs over write jobs.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    choking_algorithmintsettings_pack::fixed_slots_choker
    seed_choking_algorithmintsettings_pack::round_robin
    +

    choking_algorithm specifies which algorithm to use to determine +how many peers to unchoke. The unchoking algorithm for +downloading torrents is always "tit-for-tat", i.e. the peers we +download the fastest from are unchoked.

    +

    The options for choking algorithms are defined in the +choking_algorithm_t enum.

    +

    seed_choking_algorithm controls the seeding unchoke behavior. +i.e. How we select which peers to unchoke for seeding torrents. +Since a seeding torrent isn't downloading anything, the +tit-for-tat mechanism cannot be used. The available options are +defined in the seed_choking_algorithm_t enum.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    cache_sizeint2048
    cache_expiryint300
    +

    cache_size is the disk write and read cache. It is specified +in units of 16 kiB blocks. Buffers that are part of a peer's send +or receive buffer also count against this limit. Send and receive +buffers will never be denied to be allocated, but they will cause +the actual cached blocks to be flushed or evicted. If this is set +to -1, the cache size is automatically set based on the amount of +physical RAM on the machine. If the amount of physical RAM cannot +be determined, it's set to 1024 (= 16 MiB).

    +

    cache_expiry is the number of seconds from the last cached write +to a piece in the write cache, to when it's forcefully flushed to +disk.

    +

    On 32 bit builds, the effective cache size will be limited to 3/4 of +2 GiB to avoid exceeding the virtual address space limit.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    disk_io_write_modeintsettings_pack::enable_os_cache
    disk_io_read_modeintsettings_pack::enable_os_cache
    +

    determines how files are opened when they're in read only mode +versus read and write mode. The options are:

    +
    +
    enable_os_cache
    +
    Files are opened normally, with the OS caching reads and writes.
    +
    disable_os_cache
    +
    This opens all files in no-cache mode. This corresponds to the +OS not letting blocks for the files linger in the cache. This +makes sense in order to avoid the bittorrent client to +potentially evict all other processes' cache by simply handling +high throughput and large files. If libtorrent's read cache is +disabled, enabling this may reduce performance.
    +
    +

    One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    outgoing_portint0
    num_outgoing_portsint0
    +

    this is the first port to use for binding outgoing connections to. +This is useful for users that have routers that allow QoS settings +based on local port. when binding outgoing connections to specific +ports, num_outgoing_ports is the size of the range. It should +be more than a few

    +
    +

    Warning

    +

    setting outgoing ports will limit the ability to keep +multiple connections to the same client, even for different +torrents. It is not recommended to change this setting. Its main +purpose is to use as an escape hatch for cheap routers with QoS +capability but can only classify flows based on port numbers.

    +
    +

    It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in TIME_WAIT state.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    peer_tosint0x20
    +

    peer_tos determines the TOS byte set in the IP header of every +packet sent to peers (including web seeds). 0x0 means no marking, +0x20 represents the QBone scavenger service. For more +details, see QBSS.

    + + + + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    active_downloadsint3
    active_seedsint5
    active_checkingint1
    active_dht_limitint88
    active_tracker_limitint1600
    active_lsd_limitint60
    active_limitint500
    +

    for auto managed torrents, these are the limits they are subject +to. If there are too many torrents some of the auto managed ones +will be paused until some slots free up. active_downloads and +active_seeds controls how many active seeding and downloading +torrents the queuing mechanism allows. The target number of active +torrents is min(active_downloads + active_seeds, active_limit). +active_downloads and active_seeds are upper limits on the +number of downloading torrents and seeding torrents respectively. +Setting the value to -1 means unlimited.

    +

    For example if there are 10 seeding torrents and 10 downloading +torrents, and active_downloads is 4 and active_seeds is 4, +there will be 4 seeds active and 4 downloading torrents. If the +settings are active_downloads = 2 and active_seeds = 4, +then there will be 2 downloading torrents and 4 seeding torrents +active. Torrents that are not auto managed are not counted against +these limits.

    +

    active_checking is the limit of number of simultaneous checking +torrents.

    +

    active_limit is a hard limit on the number of active (auto +managed) torrents. This limit also applies to slow torrents.

    +

    active_dht_limit is the max number of torrents to announce to +the DHT.

    +

    active_tracker_limit is the max number of torrents to announce +to their trackers.

    +

    active_lsd_limit is the max number of torrents to announce to +the local network over the local service discovery protocol.

    +

    You can have more torrents active, even though they are not +announced to the DHT, lsd or their tracker. If some peer knows +about you for any reason and tries to connect, it will still be +accepted, unless the torrent is paused, which means it won't accept +any connections.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_intervalint30
    +

    auto_manage_interval is the number of seconds between the +torrent queue is updated, and rotated.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seed_time_limitint24 * 60 * 60
    +

    this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed +limit criteria. See queuing.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    auto_scrape_intervalint1800
    auto_scrape_min_intervalint300
    +

    auto_scrape_interval is the number of seconds between scrapes +of queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to +determine which torrents to seed and which to pause.

    +

    auto_scrape_min_interval is the minimum number of seconds +between any automatic scrape (regardless of torrent). In case there +are a large number of paused auto managed torrents, this puts a +limit on how often a scrape request is sent.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    max_peerlist_sizeint3000
    max_paused_peerlist_sizeint1000
    +

    max_peerlist_size is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this +number should be much greater than the maximum number of connected +peers. Peers are evicted from the cache when the list grows passed +90% of this limit, and once the size hits the limit, peers are no +longer added to the list. If this limit is set to 0, there is no +limit on how many peers we'll keep in the peer list.

    +

    max_paused_peerlist_size is the max peer list size used for +torrents that are paused. This can be used to save memory for paused +torrents, since it's not as important for them to keep a large peer +list.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    min_announce_intervalint5 * 60
    +

    this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering mis-configured +trackers.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    auto_manage_startupint60
    +

    this is the number of seconds a torrent is considered active after +it was started, regardless of upload and download speed. This is so +that newly started torrents are not considered inactive until they +have a fair chance to start downloading.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    seeding_piece_quotaint20
    +

    seeding_piece_quota is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_rejectsint50
    +

    max_rejects is the number of piece requests we will reject in a +row while a peer is choked before the peer is considered abusive +and is disconnected.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    recv_socket_buffer_sizeint0
    send_socket_buffer_sizeint0
    +

    specifies the buffer sizes set on peer sockets. 0 means the OS +default (i.e. don't change the buffer sizes). +The socket buffer sizes are changed using setsockopt() with +SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_peer_recv_buffer_sizeint2 * 1024 * 1024
    +

    the max number of bytes a single peer connection's receive buffer is +allowed to grow to.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    read_cache_line_sizeint32
    write_cache_line_sizeint16
    +

    read_cache_line_size is the number of blocks to read into the +read cache when a read cache miss occurs. Setting this to 0 is +essentially the same thing as disabling read cache. The number of +blocks read into the read cache is always capped by the piece +boundary.

    +

    When a piece in the write cache has write_cache_line_size +contiguous blocks in it, they will be flushed. Setting this to 1 +effectively disables the write cache.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    optimistic_disk_retryint10 * 60
    +

    optimistic_disk_retry is the number of seconds from a disk +write errors occur on a torrent until libtorrent will take it out +of the upload mode, to test if the error condition has been fixed.

    +

    libtorrent will only do this automatically for auto managed +torrents.

    +

    You can explicitly take a torrent out of upload only mode using +set_upload_mode().

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_suggest_piecesint16
    +

    max_suggest_pieces is the max number of suggested piece indices +received from a peer that's remembered. If a peer floods suggest +messages, this limit prevents libtorrent from using too much RAM.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    local_service_announce_intervalint5 * 60
    +

    local_service_announce_interval is the time between local +network announces for a torrent. +This interval is specified in seconds.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_announce_intervalint15 * 60
    +

    dht_announce_interval is the number of seconds between +announcing torrents to the distributed hash table (DHT).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    udp_tracker_token_expiryint60
    +

    udp_tracker_token_expiry is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is +specified to be 60 seconds. The higher this +value is, the fewer packets have to be sent to the UDP tracker. In +order for higher values to work, the tracker needs to be configured +to match the expiration time for tokens.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    num_optimistic_unchoke_slotsint0
    +

    num_optimistic_unchoke_slots is the number of optimistic +unchoke slots to use. +Having a higher number of optimistic unchoke slots mean you will +find the good peers faster but with the trade-off to use up more +bandwidth. 0 means automatic, where libtorrent opens up 20% of your +allowed upload slots as optimistic unchoke slots.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_pex_peersint50
    +

    the max number of peers we accept from pex messages from a single +peer. this limits the number of concurrent peers any of our peers +claims to be connected to. If they claim to be connected to more +than this, we'll ignore any peer that exceeds this limit

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tick_intervalint500
    +

    tick_interval specifies the number of milliseconds between +internal ticks. This is the frequency with which bandwidth quota is +distributed to peers. It should not be more than one second (i.e. +1000 ms). Setting this to a low value (around 100) means higher +resolution bandwidth quota distribution, setting it to a higher +value saves CPU cycles.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    share_mode_targetint3
    +

    share_mode_target specifies the target share ratio for share +mode torrents. If set to 3, we'll try to upload 3 +times as much as we download. Setting this very high, will make it +very conservative and you might end up not downloading anything +ever (and not affecting your share ratio). It does not make any +sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a +single piece and upload it more than 3 times. If the +share_mode_target is set to more than 3, nothing is downloaded.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    upload_rate_limitint0
    download_rate_limitint0
    +

    upload_rate_limit and download_rate_limit sets +the session-global limits of upload and download rate limits, in +bytes per second. By default peers on the local network are not rate +limited.

    +

    A value of 0 means unlimited.

    +

    For fine grained control over rate limits, including making them apply +to local peers, see peer classes.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    dht_upload_rate_limitint8000
    +

    dht_upload_rate_limit sets the rate limit on the DHT. This is +specified in bytes per second. For busy boxes +with lots of torrents that requires more DHT traffic, this should +be raised.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    unchoke_slots_limitint8
    +

    unchoke_slots_limit is the max number of unchoked peers in the +session. The number of unchoke slots may be ignored depending on +what choking_algorithm is set to. Setting this limit to -1 +means unlimited, i.e. all peers will always be unchoked.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connections_limitint200
    +

    connections_limit sets a global limit on the number of +connections opened. The number of connections is set to a hard +minimum of at least two per torrent, so if you set a too low +connections limit, and open too many torrents, the limit will not +be met.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connections_slackint10
    +

    connections_slack is the number of incoming connections +exceeding the connection limit to accept in order to potentially +replace existing ones.

    + + + + + + + + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    utp_target_delayint100
    utp_gain_factorint3000
    utp_min_timeoutint500
    utp_syn_resendsint2
    utp_fin_resendsint2
    utp_num_resendsint3
    utp_connect_timeoutint3000
    utp_loss_multiplierint50
    +

    utp_target_delay is the target delay for uTP sockets in +milliseconds. A high value will make uTP connections more +aggressive and cause longer queues in the upload bottleneck. It +cannot be too low, since the noise in the measurements would cause +it to send too slow. +utp_gain_factor is the number of bytes the uTP congestion +window can increase at the most in one RTT. +If this is set too high, the congestion controller reacts +too hard to noise and will not be stable, if it's set too low, it +will react slow to congestion and not back off as fast.

    +

    utp_min_timeout is the shortest allowed uTP socket timeout, +specified in milliseconds. The +timeout depends on the RTT of the connection, but is never smaller +than this value. A connection times out when every packet in a +window is lost, or when a packet is lost twice in a row (i.e. the +resent packet is lost as well).

    +

    The shorter the timeout is, the faster the connection will recover +from this situation, assuming the RTT is low enough. +utp_syn_resends is the number of SYN packets that are sent (and +timed out) before giving up and closing the socket. +utp_num_resends is the number of times a packet is sent (and +lost or timed out) before giving up and closing the connection. +utp_connect_timeout is the number of milliseconds of timeout +for the initial SYN packet for uTP connections. For each timed out +packet (in a row), the timeout is doubled. utp_loss_multiplier +controls how the congestion window is changed when a packet loss is +experienced. It's specified as a percentage multiplier for +cwnd. Do not change this value unless you know what you're doing. +Never set it higher than 100.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    mixed_mode_algorithmintsettings_pack::peer_proportional
    +

    The mixed_mode_algorithm determines how to treat TCP +connections when there are uTP connections. Since uTP is designed +to yield to TCP, there's an inherent problem when using swarms that +have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP +connections. This mode is prefer_tcp. The peer_proportional +mode simply looks at the current throughput and rate limits all TCP +connections to their proportional share based on how many of the +connections are TCP. This works best if uTP connections are not +rate limited by the global rate limiter (which they aren't by +default).

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    listen_queue_sizeint5
    +

    listen_queue_size is the value passed in to listen() for the +listen socket. It is the number of outstanding incoming connections +to queue up while we're not actively waiting for a connection to be +accepted. 5 should be sufficient for any +normal client. If this is a high performance server which expects +to receive a lot of connections, or used in a simulator or test, it +might make sense to raise this number. It will not take affect +until the listen_interfaces settings is updated.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    torrent_connect_boostint30
    +

    torrent_connect_boost is the number of peers to try to connect +to immediately when the first tracker response is received for a +torrent. This is a boost to given to new torrents to accelerate +them starting up. The normal connect scheduler is run once every +second, this allows peers to be connected immediately instead of +waiting for the session tick to trigger connections. +This may not be set higher than 255.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    alert_queue_sizeint1000
    +

    alert_queue_size is the maximum number of alerts queued up +internally. If alerts are not popped, the queue will eventually +fill up to this level. Once the alert queue is full, additional +alerts will be dropped, and not delivered to the client. Once the +client drains the queue, new alerts may be delivered again. In order +to know that alerts have been dropped, see +session_handle::dropped_alerts().

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_metadata_sizeint3 * 1024 * 10240
    +

    max_metadata_size is the maximum allowed size (in bytes) to be +received by the metadata extension, i.e. magnet links.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    checking_mem_usageint1024
    +

    the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    predictive_piece_announceint0
    +

    if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The +intention is to gain 1.5 potential round trip times per downloaded +piece. When non-zero, this indicates how many milliseconds in +advance pieces should be announced, before they are expected to be +completed.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    aio_threadsint4
    +

    for some aio back-ends, aio_threads specifies the number of +io-threads to use.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    tracker_backoffint250
    +

    tracker_backoff determines how aggressively to back off from +retrying failing trackers. This value determines x in the +following formula, determining the number of seconds to wait until +the next retry:

    +
    +delay = 5 + 5 * x / 100 * fails^2
    +

    This setting may be useful to make libtorrent more or less +aggressive in hitting trackers.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    share_ratio_limitint200
    seed_time_ratio_limitint700
    +

    when a seeding torrent reaches either the share ratio (bytes up / +bytes down) or the seed time ratio (seconds as seed / seconds as +downloader) or the seed time limit (seconds as seed) it is +considered done, and it will leave room for other torrents. These +are specified as percentages. Torrents that are considered done will +still be allowed to be seeded, they just won't have priority anymore. +For more, see queuing.

    + + + +++++ + + + + + + + + + + + + + + + + + + + + +
    nametypedefault
    peer_turnoverint4
    peer_turnover_cutoffint90
    peer_turnover_intervalint300
    +

    peer_turnover is the percentage of peers to disconnect every +turnover peer_turnover_interval (if we're at the peer limit), this +is specified in percent when we are connected to more than limit * +peer_turnover_cutoff peers disconnect peer_turnover fraction of the +peers. It is specified in percent peer_turnover_interval is the +interval (in seconds) between optimistic disconnects if the +disconnects happen and how many peers are disconnected is +controlled by peer_turnover and peer_turnover_cutoff

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    connect_seed_every_n_downloadint10
    +

    this setting controls the priority of downloading torrents over +seeding or finished torrents when it comes to making peer +connections. Peer connections are throttled by the connection_speed +and the half-open connection limit. This makes peer connections a +limited resource. Torrents that still have pieces to download are +prioritized by default, to avoid having many seeding torrents use +most of the connection attempts and only give one peer every now +and then to the downloading torrent. libtorrent will loop over the +downloading torrents to connect a peer each, and every n:th +connection attempt, a finished torrent is picked to be allowed to +connect to a peer. This setting controls n.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_http_recv_buffer_sizeint4*1024*204
    +

    the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via the +url provided in add_torrent_params.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_retry_port_bindint10
    +

    if binding to a specific port fails, should the port be incremented +by one and tried again? This setting specifies how many times to +retry a failed port bind

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    alert_maskintint
    +

    a bitmask combining flags from alert_category_t defining which +kinds of alerts to receive

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    out_enc_policyintsettings_pack::pe_enabled
    in_enc_policyintsettings_pack::pe_enabled
    +

    control the settings for incoming and outgoing connections +respectively. see enc_policy enum for the available options. +Keep in mind that protocol encryption degrades performance in +several respects:

    +
      +
    1. It prevents "zero copy" disk buffers being sent to peers, since +each peer needs to mutate the data (i.e. encrypt it) the data +must be copied per peer connection rather than sending the same +buffer to multiple peers.
    2. +
    3. The encryption itself requires more CPU than plain bittorrent +protocol. The highest cost is the Diffie Hellman exchange on +connection setup.
    4. +
    5. The encryption handshake adds several round-trips to the +connection setup, and delays transferring data.
    6. +
    + +++++ + + + + + + + + + + + + +
    nametypedefault
    allowed_enc_levelintsettings_pack::pe_both
    +

    determines the encryption level of the connections. This setting +will adjust which encryption scheme is offered to the other peer, +as well as which encryption scheme is selected by the client. See +enc_level enum for options.

    + + +++++ + + + + + + + + + + + + + + + + +
    nametypedefault
    inactive_down_rateint2048
    inactive_up_rateint2048
    +

    the download and upload rate limits for a torrent to be considered +active by the queuing mechanism. A torrent whose download rate is +less than inactive_down_rate and whose upload rate is less than +inactive_up_rate for auto_manage_startup seconds, is +considered inactive, and another queued torrent may be started. +This logic is disabled if dont_count_slow_torrents is false.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_typeintsettings_pack::none
    +

    proxy to use. see proxy_type_t.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    proxy_portint0
    +

    the port of the proxy server

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    i2p_portint0
    +

    sets the i2p SAM bridge port to connect to. set the hostname with +the i2p_hostname setting.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    cache_size_volatileint256
    +

    this determines the max number of volatile disk cache blocks. If the +number of volatile blocks exceed this limit, other volatile blocks +will start to be evicted. A disk cache block is volatile if it has +low priority, and should be one of the first blocks to be evicted +under pressure. For instance, blocks pulled into the cache as the +result of calculating a piece hash are volatile. These blocks don't +represent potential interest among peers, so the value of keeping +them in the cache is limited.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    urlseed_max_request_bytesint16 * 1024 * 1024
    +

    The maximum request range of an url seed in bytes. This value +defines the largest possible sequential web seed request. Lower values +are possible but will be ignored if they are lower then piece size. +This value should be related to your download speed to prevent +libtorrent from creating too many expensive http requests per +second. You can select a value as high as you want but keep in mind +that libtorrent can't create parallel requests if the first request +did already select the whole file. +If you combine bittorrent seeds with web seeds and pick strategies +like rarest first you may find your web seed requests split into +smaller parts because we don't download already picked pieces +twice.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    web_seed_name_lookup_retryint1800
    +

    time to wait until a new retry of a web seed name lookup

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    close_file_intervalintCLOSE_FILE_INTERVAL
    +

    the number of seconds between closing the file opened the longest +ago. 0 means to disable the feature. The purpose of this is to +periodically close files to trigger the operating system flushing +disk cache. Specifically it has been observed to be required on +windows to not have the disk cache grow indefinitely. +This defaults to 120 seconds on windows, and disabled on other +systems.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    utp_cwnd_reduce_timerint100
    +

    When uTP experiences packet loss, it will reduce the congestion +window, and not reduce it again for this many milliseconds, even if +experiencing another lost packet.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_web_seed_connectionsint3
    +

    the max number of web seeds to have connected per torrent at any +given time.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    resolver_cache_timeoutint1200
    +

    the number of seconds before the internal host name resolver +considers a cache value timed out, negative values are interpreted +as zero.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    send_not_sent_low_watermarkint16384
    +

    specify the not-sent low watermark for socket send buffers. This +corresponds to the, Linux-specific, TCP_NOTSENT_LOWAT TCP socket +option.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    rate_choker_initial_thresholdint1024
    +

    the rate based choker compares the upload rate to peers against a +threshold that increases proportionally by its size for every +peer it visits, visiting peers in decreasing upload rate. The +number of upload slots is determined by the number of peers whose +upload rate exceeds the threshold. This option sets the start +value for this threshold. A higher value leads to fewer unchoke +slots, a lower value leads to more.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    upnp_lease_durationint3600
    +

    The expiration time of UPnP port-mappings, specified in seconds. 0 +means permanent lease. Some routers do not support expiration times +on port-maps (nor correctly returning an error indicating lack of +support). In those cases, set this to 0. Otherwise, don't set it any +lower than 5 minutes.

    + +++++ + + + + + + + + + + + + +
    nametypedefault
    max_concurrent_http_announcesint50
    +

    limits the number of concurrent HTTP tracker announces. Once the +limit is hit, tracker requests are queued and issued when an +outstanding announce completes.

    +
    +struct settings_pack
    +{
    +   void set_str (int name, std::string val);
    +   void set_int (int name, flags::bitfield_flag<Type, Tag> const val);
    +   void set_bool (int name, bool val);
    +   void set_int (int name, int val);
    +   bool has_val (int name) const;
    +   void clear ();
    +   void clear (int name);
    +   bool get_bool (int name) const;
    +   std::string const& get_str (int name) const;
    +   int get_int (int name) const;
    +
    +   enum type_bases
    +   {
    +      string_type_base,
    +      int_type_base,
    +      bool_type_base,
    +      type_mask,
    +      index_mask,
    +   };
    +
    +   enum suggest_mode_t
    +   {
    +      no_piece_suggestions,
    +      suggest_read_cache,
    +   };
    +
    +   enum choking_algorithm_t
    +   {
    +      fixed_slots_choker,
    +      rate_based_choker,
    +      deprecated_bittyrant_choker,
    +   };
    +
    +   enum seed_choking_algorithm_t
    +   {
    +      round_robin,
    +      fastest_upload,
    +      anti_leech,
    +   };
    +
    +   enum io_buffer_mode_t
    +   {
    +      enable_os_cache,
    +      deprecated_disable_os_cache_for_aligned_files,
    +      disable_os_cache,
    +   };
    +
    +   enum bandwidth_mixed_algo_t
    +   {
    +      prefer_tcp,
    +      peer_proportional,
    +   };
    +
    +   enum enc_policy
    +   {
    +      pe_forced,
    +      pe_enabled,
    +      pe_disabled,
    +   };
    +
    +   enum enc_level
    +   {
    +      pe_plaintext,
    +      pe_rc4,
    +      pe_both,
    +   };
    +
    +   enum proxy_type_t
    +   {
    +      none,
    +      socks4,
    +      socks5,
    +      socks5_pw,
    +      http,
    +      http_pw,
    +      i2p_proxy,
    +   };
    +};
    +
    + + +[report issue]
    +

    set_str() set_bool() set_int()

    +
    +void set_str (int name, std::string val);
    +void set_int (int name, flags::bitfield_flag<Type, Tag> const val);
    +void set_bool (int name, bool val);
    +void set_int (int name, int val);
    +
    +

    set a configuration option in the settings_pack. name is one of +the enum values from string_types, int_types or bool_types. They must +match the respective type of the set_* function.

    +[report issue]
    +
    +

    has_val()

    +
    +bool has_val (int name) const;
    +
    +

    queries whether the specified configuration option has a value set in +this pack. name can be any enumeration value from string_types, +int_types or bool_types.

    +[report issue]
    +
    +

    clear()

    +
    +void clear ();
    +
    +

    clear the settings pack from all settings

    +[report issue]
    +
    +

    clear()

    +
    +void clear (int name);
    +
    +

    clear a specific setting from the pack

    + + +[report issue]
    +
    +

    get_str() get_int() get_bool()

    +
    +bool get_bool (int name) const;
    +std::string const& get_str (int name) const;
    +int get_int (int name) const;
    +
    +

    queries the current configuration option from the settings_pack. +name is one of the enumeration values from string_types, int_types +or bool_types. The enum value must match the type of the get_* +function.

    +[report issue]
    +
    +

    enum type_bases

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    string_type_base0 
    int_type_base16384 
    bool_type_base32768 
    type_mask49152 
    index_mask16383 
    +[report issue]
    +
    +

    enum suggest_mode_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    no_piece_suggestions0 
    suggest_read_cache1 
    +[report issue]
    +
    +

    enum choking_algorithm_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    fixed_slots_choker0This is the traditional choker with a fixed number of unchoke +slots (as specified by settings_pack::unchoke_slots_limit).
    rate_based_choker2

    This opens up unchoke slots based on the upload rate achieved to +peers. The more slots that are opened, the marginal upload rate +required to open up another slot increases. Configure the initial +threshold with settings_pack::rate_choker_initial_threshold.

    +

    For more information, see rate based choking.

    +
    deprecated_bittyrant_choker3 
    +[report issue]
    +
    +

    enum seed_choking_algorithm_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    round_robin0which round-robins the peers that are unchoked +when seeding. This distributes the upload bandwidth uniformly and +fairly. It minimizes the ability for a peer to download everything +without redistributing it.
    fastest_upload1unchokes the peers we can send to the fastest. This might be a +bit more reliable in utilizing all available capacity.
    anti_leech2prioritizes peers who have just started or are +just about to finish the download. The intention is to force +peers in the middle of the download to trade with each other. +This does not just take into account the pieces a peer is +reporting having downloaded, but also the pieces we have sent +to it.
    +[report issue]
    +
    +

    enum io_buffer_mode_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    enable_os_cache0 
    deprecated_disable_os_cache_for_aligned_files1 
    disable_os_cache2 
    +[report issue]
    +
    +

    enum bandwidth_mixed_algo_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + +
    namevaluedescription
    prefer_tcp0disables the mixed mode bandwidth balancing
    peer_proportional1does not throttle uTP, throttles TCP to the same proportion +of throughput as there are TCP connections
    +[report issue]
    +
    +

    enum enc_policy

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pe_forced0Only encrypted connections are allowed. Incoming connections that +are not encrypted are closed and if the encrypted outgoing +connection fails, a non-encrypted retry will not be made.
    pe_enabled1encrypted connections are enabled, but non-encrypted connections +are allowed. An incoming non-encrypted connection will be accepted, +and if an outgoing encrypted connection fails, a non- encrypted +connection will be tried.
    pe_disabled2only non-encrypted connections are allowed.
    +[report issue]
    +
    +

    enum enc_level

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    pe_plaintext1use only plain text encryption
    pe_rc42use only RC4 encryption
    pe_both3allow both
    +[report issue]
    +
    +

    enum proxy_type_t

    +

    Declared in "libtorrent/settings_pack.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none0No proxy server is used and all other fields are ignored.
    socks41The server is assumed to be a SOCKS4 server that requires a +username.
    socks52The server is assumed to be a SOCKS5 server (RFC 1928) that does +not require any authentication. The username and password are +ignored.
    socks5_pw3The server is assumed to be a SOCKS5 server that supports plain +text username and password authentication (RFC 1929). The +username and password specified may be sent to the proxy if it +requires.
    http4The server is assumed to be an HTTP proxy. If the transport used +for the connection is non-HTTP, the server is assumed to support +the CONNECT method. i.e. for web seeds and HTTP trackers, a plain +proxy will suffice. The proxy is assumed to not require +authorization. The username and password will not be used.
    http_pw5The server is assumed to be an HTTP proxy that requires user +authorization. The username and password will be sent to the proxy.
    i2p_proxy6route through a i2p SAM proxy
    + +[report issue]
    +
    +
    +

    high_performance_seed() min_memory_usage()

    +

    Declared in "libtorrent/session.hpp"

    +
    +settings_pack high_performance_seed ();
    +settings_pack min_memory_usage ();
    +
    +

    The default values of the session settings are set for a regular +bittorrent client running on a desktop system. There are functions that +can set the session settings to pre set settings for other environments. +These can be used for the basis, and should be tweaked to fit your needs +better.

    +

    min_memory_usage returns settings that will use the minimal amount of +RAM, at the potential expense of upload and download performance. It +adjusts the socket buffer sizes, disables the disk cache, lowers the send +buffer watermarks so that each connection only has at most one block in +use at any one time. It lowers the outstanding blocks send to the disk +I/O thread so that connections only have one block waiting to be flushed +to disk at any given time. It lowers the max number of peers in the peer +list for torrents. It performs multiple smaller reads when it hashes +pieces, instead of reading it all into memory before hashing.

    +

    This configuration is intended to be the starting point for embedded +devices. It will significantly reduce memory usage.

    +

    high_performance_seed returns settings optimized for a seed box, +serving many peers and that doesn't do any downloading. It has a 128 MB +disk cache and has a limit of 400 files in its file pool. It support fast +upload rates by allowing large send buffers.

    + +[report issue]
    +
    +

    setting_by_name() name_for_setting()

    +

    Declared in "libtorrent/settings_pack.hpp"

    +
    +char const* name_for_setting (int s);
    +int setting_by_name (string_view name);
    +
    +

    converts a setting integer (from the enums string_types, int_types or +bool_types) to a string, and vice versa.

    +[report issue]
    +
    +

    default_settings()

    +

    Declared in "libtorrent/settings_pack.hpp"

    +
    +settings_pack default_settings ();
    +
    +

    returns a settings_pack with every setting set to its default value

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    Bdecoding

    +[report issue]
    +

    bdecode_node

    +

    Declared in "libtorrent/bdecode.hpp"

    +

    Sometimes it's important to get a non-owning reference to the root node ( +to be able to copy it as a reference for instance). For that, use the +non_owning() member function.

    +

    There are 5 different types of nodes, see type_t.

    +
    +struct bdecode_node
    +{
    +   bdecode_node () = default;
    +   bdecode_node (bdecode_node&&) noexcept;
    +   bdecode_node& operator= (bdecode_node&&) = default;
    +   bdecode_node& operator= (bdecode_node const&);
    +   bdecode_node (bdecode_node const&);
    +   type_t type () const noexcept;
    +   explicit operator bool () const noexcept;
    +   bdecode_node non_owning () const;
    +   span<char const> data_section () const noexcept;
    +   int list_size () const;
    +   std::int64_t list_int_value_at (int i
    +      , std::int64_t default_val = 0) const;
    +   bdecode_node list_at (int i) const;
    +   string_view list_string_value_at (int i
    +      , string_view default_val = string_view()) const;
    +   std::pair<string_view, bdecode_node> dict_at (int i) const;
    +   string_view dict_find_string_value (string_view key
    +      , string_view default_value = string_view()) const;
    +   bdecode_node dict_find_int (string_view key) const;
    +   bdecode_node dict_find (string_view key) const;
    +   bdecode_node dict_find_list (string_view key) const;
    +   int dict_size () const;
    +   std::int64_t dict_find_int_value (string_view key
    +      , std::int64_t default_val = 0) const;
    +   bdecode_node dict_find_dict (string_view key) const;
    +   bdecode_node dict_find_string (string_view key) const;
    +   std::int64_t int_value () const;
    +   char const* string_ptr () const;
    +   int string_length () const;
    +   string_view string_value () const;
    +   void clear ();
    +   void swap (bdecode_node& n);
    +   void reserve (int tokens);
    +   void switch_underlying_buffer (char const* buf) noexcept;
    +   bool has_soft_error (span<char> error) const;
    +
    +   enum type_t
    +   {
    +      none_t,
    +      dict_t,
    +      list_t,
    +      string_t,
    +      int_t,
    +   };
    +};
    +
    +[report issue]
    +

    bdecode_node()

    +
    +bdecode_node () = default;
    +
    +

    creates a default constructed node, it will have the type none_t.

    + +[report issue]
    +
    +

    bdecode_node() operator=()

    +
    +bdecode_node (bdecode_node&&) noexcept;
    +bdecode_node& operator= (bdecode_node&&) = default;
    +bdecode_node& operator= (bdecode_node const&);
    +bdecode_node (bdecode_node const&);
    +
    +

    For owning nodes, the copy will create a copy of the tree, but the +underlying buffer remains the same.

    +[report issue]
    +
    +

    type()

    +
    +type_t type () const noexcept;
    +
    +

    the type of this node. See type_t.

    +[report issue]
    +
    +

    bool()

    +
    +explicit operator bool () const noexcept;
    +
    +

    returns true if type() != none_t.

    +[report issue]
    +
    +

    non_owning()

    +
    +bdecode_node non_owning () const;
    +
    +

    return a non-owning reference to this node. This is useful to refer to +the root node without copying it in assignments.

    +[report issue]
    +
    +

    data_section()

    +
    +span<char const> data_section () const noexcept;
    +
    +

    returns the buffer and length of the section in the original bencoded +buffer where this node is defined. For a dictionary for instance, this +starts with d and ends with e, and has all the content of the +dictionary in between.

    + + + +[report issue]
    +
    +

    list_at() list_string_value_at() list_int_value_at() list_size()

    +
    +int list_size () const;
    +std::int64_t list_int_value_at (int i
    +      , std::int64_t default_val = 0) const;
    +bdecode_node list_at (int i) const;
    +string_view list_string_value_at (int i
    +      , string_view default_val = string_view()) const;
    +
    +

    functions with the list_ prefix operate on lists. These functions are +only valid if type() == list_t. list_at() returns the item +in the list at index i. i may not be greater than or equal to the +size of the list. size() returns the size of the list.

    + + + + + + + + +[report issue]
    +
    +

    dict_find_dict() dict_find_list() dict_at() dict_find_string() dict_find() dict_size() dict_find_int() dict_find_int_value() dict_find_string_value()

    +
    +std::pair<string_view, bdecode_node> dict_at (int i) const;
    +string_view dict_find_string_value (string_view key
    +      , string_view default_value = string_view()) const;
    +bdecode_node dict_find_int (string_view key) const;
    +bdecode_node dict_find (string_view key) const;
    +bdecode_node dict_find_list (string_view key) const;
    +int dict_size () const;
    +std::int64_t dict_find_int_value (string_view key
    +      , std::int64_t default_val = 0) const;
    +bdecode_node dict_find_dict (string_view key) const;
    +bdecode_node dict_find_string (string_view key) const;
    +
    +

    Functions with the dict_ prefix operates on dictionaries. They are +only valid if type() == dict_t. In case a key you're looking up +contains a 0 byte, you cannot use the 0-terminated string overloads, +but have to use std::string instead. dict_find_list will return a +valid bdecode_node if the key is found _and_ it is a list. Otherwise +it will return a default-constructed bdecode_node.

    +

    Functions with the _value suffix return the value of the node +directly, rather than the nodes. In case the node is not found, or it has +a different type, a default value is returned (which can be specified).

    +[report issue]
    +
    +

    int_value()

    +
    +std::int64_t int_value () const;
    +
    +

    this function is only valid if type() == int_t. It returns the +value of the integer.

    + + +[report issue]
    +
    +

    string_length() string_ptr() string_value()

    +
    +char const* string_ptr () const;
    +int string_length () const;
    +string_view string_value () const;
    +
    +

    these functions are only valid if type() == string_t. They return +the string values. Note that string_ptr() is not 0-terminated. +string_length() returns the number of bytes in the string.

    +[report issue]
    +
    +

    clear()

    +
    +void clear ();
    +
    +

    resets the bdecoded_node to a default constructed state. If this is +an owning node, the tree is freed and all child nodes are invalidated.

    +[report issue]
    +
    +

    swap()

    +
    +void swap (bdecode_node& n);
    +
    +

    Swap contents.

    +[report issue]
    +
    +

    reserve()

    +
    +void reserve (int tokens);
    +
    +

    preallocate memory for the specified numbers of tokens. This is +useful if you know approximately how many tokens are in the file +you are about to parse. Doing so will save realloc operations +while parsing. You should only call this on the root node, before +passing it in to bdecode().

    +[report issue]
    +
    +

    switch_underlying_buffer()

    +
    +void switch_underlying_buffer (char const* buf) noexcept;
    +
    +

    this buffer MUST be identical to the one originally parsed. This +operation is only defined on owning root nodes, i.e. the one passed in to +decode().

    +[report issue]
    +
    +

    has_soft_error()

    +
    +bool has_soft_error (span<char> error) const;
    +
    +

    returns true if there is a non-fatal error in the bencoding of this node +or its children

    +[report issue]
    +
    +

    enum type_t

    +

    Declared in "libtorrent/bdecode.hpp"

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    namevaluedescription
    none_t0uninitialized or default constructed. This is also used +to indicate that a node was not found in some cases.
    dict_t1a dictionary node. The dict_find_ functions are valid.
    list_t2a list node. The list_ functions are valid.
    string_t3a string node, the string_ functions are valid.
    int_t4an integer node. The int_ functions are valid.
    +[report issue]
    +
    +
    +

    print_entry()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +std::string print_entry (bdecode_node const& e
    +   , bool single_line = false, int indent = 0);
    +
    +

    print the bencoded structure in a human-readable format to a string +that's returned.

    +[report issue]
    +
    +

    bdecode()

    +

    Declared in "libtorrent/bdecode.hpp"

    +
    +int bdecode (char const* start, char const* end, bdecode_node& ret
    +   , error_code& ec, int* error_pos = nullptr, int depth_limit = 100
    +   , int token_limit = 2000000);
    +bdecode_node bdecode (span<char const> buffer
    +   , error_code& ec, int* error_pos = nullptr, int depth_limit = 100
    +   , int token_limit = 2000000);
    +bdecode_node bdecode (span<char const> buffer
    +   , int depth_limit = 100, int token_limit = 2000000);
    +
    +

    This function decodes/parses bdecoded data (for example a .torrent file). +The data structure is returned in the ret argument. the buffer to parse +is specified by the start of the buffer as well as the end, i.e. one +byte past the end. If the buffer fails to parse, the function returns a +non-zero value and fills in ec with the error code. The optional +argument error_pos, if set to non-nullptr, will be set to the byte offset +into the buffer where the parse failure occurred.

    +

    depth_limit specifies the max number of nested lists or dictionaries are +allowed in the data structure. (This affects the stack usage of the +function, be careful not to set it too high).

    +

    token_limit is the max number of tokens allowed to be parsed from the +buffer. This is simply a sanity check to not have unbounded memory usage.

    +

    The resulting bdecode_node is an owning node. That means it will +be holding the whole parsed tree. When iterating lists and dictionaries, +those bdecode_node objects will simply have references to the root or +owning bdecode_node. If the root node is destructed, all other nodes +that refer to anything in that tree become invalid.

    +

    However, the underlying buffer passed in to this function (start, end) +must also remain valid while the bdecoded tree is used. The parsed tree +produced by this function does not copy any data out of the buffer, but +simply produces references back into it.

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    home

    +
    +
    +
    +

    ed25519

    +[report issue]
    +

    ed25519_create_seed()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +std::array<char, 32> ed25519_create_seed ();
    +
    +

    See documentation of internal random_bytes

    +[report issue]
    +
    +

    ed25519_create_keypair()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +std::tuple<public_key, secret_key> ed25519_create_keypair (
    +   std::array<char, 32> const& seed);
    +
    +

    Creates a new key pair from the given seed.

    +

    It's important to clarify that the seed completely determines +the key pair. Then it's enough to save the seed and the +public key as the key-pair in a buffer of 64 bytes. The standard +is (32 bytes seed, 32 bytes public key).

    +

    This function does work with a given seed, giving you a pair of +(64 bytes private key, 32 bytes public key). It's a trade-off between +space and CPU, saving in one format or another.

    +

    The smaller format is not weaker by any means, in fact, it is only +the seed (32 bytes) that determines the point in the curve.

    +[report issue]
    +
    +

    ed25519_sign()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +signature ed25519_sign (span<char const> msg
    +   , public_key const& pk, secret_key const& sk);
    +
    +

    Creates a signature of the given message with the given key pair.

    +[report issue]
    +
    +

    ed25519_verify()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +bool ed25519_verify (signature const& sig
    +   , span<char const> msg, public_key const& pk);
    +
    +

    Verifies the signature on the given message using pk

    +[report issue]
    +
    +

    ed25519_add_scalar()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +secret_key ed25519_add_scalar (secret_key const& sk
    +   , std::array<char, 32> const& scalar);
    +public_key ed25519_add_scalar (public_key const& pk
    +   , std::array<char, 32> const& scalar);
    +
    +

    Adds a scalar to the given key pair where scalar is a 32 byte buffer +(possibly generated with ed25519_create_seed), generating a new key pair.

    +

    You can calculate the public key sum without knowing the private key and +vice versa by passing in null for the key you don't know. This is useful +when a third party (an authoritative server for example) needs to enforce +randomness on a key pair while only knowing the public key of the other +side.

    +

    Warning: the last bit of the scalar is ignored - if comparing scalars make +sure to clear it with scalar[31] &= 127.

    +

    see http://crypto.stackexchange.com/a/6215/4697 +see test_ed25519 for a practical example

    +[report issue]
    +
    +

    ed25519_key_exchange()

    +

    Declared in "libtorrent/kademlia/ed25519.hpp"

    +
    +std::array<char, 32> ed25519_key_exchange (
    +   public_key const& pk, secret_key const& sk);
    +
    +

    Performs a key exchange on the given public key and private key, producing a +shared secret. It is recommended to hash the shared secret before using it.

    +

    This is useful when two parties want to share a secret but both only knows +their respective public keys. +see test_ed25519 for a practical example

    +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/stats_counters.rst b/docs/stats_counters.rst new file mode 100644 index 0000000..7f4302a --- /dev/null +++ b/docs/stats_counters.rst @@ -0,0 +1,2123 @@ +.. _peer.error_peers: + +.. _peer.disconnected_peers: + +.. raw:: html + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| peer.error_peers | counter | ++-------------------------+---------+ +| peer.disconnected_peers | counter | ++-------------------------+---------+ + + +``error_peers`` is the total number of peer disconnects +caused by an error (not initiated by this client) and +disconnected initiated by this client (``disconnected_peers``). + +.. _peer.eof_peers: + +.. _peer.connreset_peers: + +.. _peer.connrefused_peers: + +.. _peer.connaborted_peers: + +.. _peer.notconnected_peers: + +.. _peer.perm_peers: + +.. _peer.buffer_peers: + +.. _peer.unreachable_peers: + +.. _peer.broken_pipe_peers: + +.. _peer.addrinuse_peers: + +.. _peer.no_access_peers: + +.. _peer.invalid_arg_peers: + +.. _peer.aborted_peers: + +.. raw:: html + + + + + + + + + + + + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| peer.eof_peers | counter | ++-------------------------+---------+ +| peer.connreset_peers | counter | ++-------------------------+---------+ +| peer.connrefused_peers | counter | ++-------------------------+---------+ +| peer.connaborted_peers | counter | ++-------------------------+---------+ +| peer.notconnected_peers | counter | ++-------------------------+---------+ +| peer.perm_peers | counter | ++-------------------------+---------+ +| peer.buffer_peers | counter | ++-------------------------+---------+ +| peer.unreachable_peers | counter | ++-------------------------+---------+ +| peer.broken_pipe_peers | counter | ++-------------------------+---------+ +| peer.addrinuse_peers | counter | ++-------------------------+---------+ +| peer.no_access_peers | counter | ++-------------------------+---------+ +| peer.invalid_arg_peers | counter | ++-------------------------+---------+ +| peer.aborted_peers | counter | ++-------------------------+---------+ + + +these counters break down the peer errors into more specific +categories. These errors are what the underlying transport +reported (i.e. TCP or uTP) + +.. _peer.piece_requests: + +.. _peer.max_piece_requests: + +.. _peer.invalid_piece_requests: + +.. _peer.choked_piece_requests: + +.. _peer.cancelled_piece_requests: + +.. _peer.piece_rejects: + +.. raw:: html + + + + + + + + ++-------------------------------+---------+ +| name | type | ++===============================+=========+ +| peer.piece_requests | counter | ++-------------------------------+---------+ +| peer.max_piece_requests | counter | ++-------------------------------+---------+ +| peer.invalid_piece_requests | counter | ++-------------------------------+---------+ +| peer.choked_piece_requests | counter | ++-------------------------------+---------+ +| peer.cancelled_piece_requests | counter | ++-------------------------------+---------+ +| peer.piece_rejects | counter | ++-------------------------------+---------+ + + +the total number of incoming piece requests we've received followed +by the number of rejected piece requests for various reasons. +max_piece_requests mean we already had too many outstanding requests +from this peer, so we rejected it. cancelled_piece_requests are ones +where the other end explicitly asked for the piece to be rejected. + +.. _peer.error_incoming_peers: + +.. _peer.error_outgoing_peers: + +.. raw:: html + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| peer.error_incoming_peers | counter | ++---------------------------+---------+ +| peer.error_outgoing_peers | counter | ++---------------------------+---------+ + + +these counters break down the peer errors into +whether they happen on incoming or outgoing peers. + +.. _peer.error_rc4_peers: + +.. _peer.error_encrypted_peers: + +.. raw:: html + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| peer.error_rc4_peers | counter | ++----------------------------+---------+ +| peer.error_encrypted_peers | counter | ++----------------------------+---------+ + + +these counters break down the peer errors into +whether they happen on encrypted peers (just +encrypted handshake) and rc4 peers (full stream +encryption). These can indicate whether encrypted +peers are more or less likely to fail + +.. _peer.error_tcp_peers: + +.. _peer.error_utp_peers: + +.. raw:: html + + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| peer.error_tcp_peers | counter | ++----------------------+---------+ +| peer.error_utp_peers | counter | ++----------------------+---------+ + + +these counters break down the peer errors into +whether they happen on uTP peers or TCP peers. +these may indicate whether one protocol is +more error prone + +.. _peer.connect_timeouts: + +.. _peer.uninteresting_peers: + +.. _peer.timeout_peers: + +.. _peer.no_memory_peers: + +.. _peer.too_many_peers: + +.. _peer.transport_timeout_peers: + +.. _peer.num_banned_peers: + +.. _peer.banned_for_hash_failure: + +.. _peer.connection_attempts: + +.. _peer.connection_attempt_loops: + +.. _peer.boost_connection_attempts: + +.. _peer.missed_connection_attempts: + +.. _peer.no_peer_connection_attempts: + +.. _peer.incoming_connections: + +.. raw:: html + + + + + + + + + + + + + + + + ++----------------------------------+---------+ +| name | type | ++==================================+=========+ +| peer.connect_timeouts | counter | ++----------------------------------+---------+ +| peer.uninteresting_peers | counter | ++----------------------------------+---------+ +| peer.timeout_peers | counter | ++----------------------------------+---------+ +| peer.no_memory_peers | counter | ++----------------------------------+---------+ +| peer.too_many_peers | counter | ++----------------------------------+---------+ +| peer.transport_timeout_peers | counter | ++----------------------------------+---------+ +| peer.num_banned_peers | counter | ++----------------------------------+---------+ +| peer.banned_for_hash_failure | counter | ++----------------------------------+---------+ +| peer.connection_attempts | counter | ++----------------------------------+---------+ +| peer.connection_attempt_loops | counter | ++----------------------------------+---------+ +| peer.boost_connection_attempts | counter | ++----------------------------------+---------+ +| peer.missed_connection_attempts | counter | ++----------------------------------+---------+ +| peer.no_peer_connection_attempts | counter | ++----------------------------------+---------+ +| peer.incoming_connections | counter | ++----------------------------------+---------+ + + +these counters break down the reasons to +disconnect peers. + +.. _peer.num_tcp_peers: + +.. _peer.num_socks5_peers: + +.. _peer.num_http_proxy_peers: + +.. _peer.num_utp_peers: + +.. _peer.num_i2p_peers: + +.. _peer.num_ssl_peers: + +.. _peer.num_ssl_socks5_peers: + +.. _peer.num_ssl_http_proxy_peers: + +.. _peer.num_ssl_utp_peers: + +.. _peer.num_peers_half_open: + +.. _peer.num_peers_connected: + +.. _peer.num_peers_up_interested: + +.. _peer.num_peers_down_interested: + +.. _peer.num_peers_up_unchoked_all: + +.. _peer.num_peers_up_unchoked_optimistic: + +.. _peer.num_peers_up_unchoked: + +.. _peer.num_peers_down_unchoked: + +.. _peer.num_peers_up_requests: + +.. _peer.num_peers_down_requests: + +.. _peer.num_peers_end_game: + +.. _peer.num_peers_up_disk: + +.. _peer.num_peers_down_disk: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + ++---------------------------------------+-------+ +| name | type | ++=======================================+=======+ +| peer.num_tcp_peers | gauge | ++---------------------------------------+-------+ +| peer.num_socks5_peers | gauge | ++---------------------------------------+-------+ +| peer.num_http_proxy_peers | gauge | ++---------------------------------------+-------+ +| peer.num_utp_peers | gauge | ++---------------------------------------+-------+ +| peer.num_i2p_peers | gauge | ++---------------------------------------+-------+ +| peer.num_ssl_peers | gauge | ++---------------------------------------+-------+ +| peer.num_ssl_socks5_peers | gauge | ++---------------------------------------+-------+ +| peer.num_ssl_http_proxy_peers | gauge | ++---------------------------------------+-------+ +| peer.num_ssl_utp_peers | gauge | ++---------------------------------------+-------+ +| peer.num_peers_half_open | gauge | ++---------------------------------------+-------+ +| peer.num_peers_connected | gauge | ++---------------------------------------+-------+ +| peer.num_peers_up_interested | gauge | ++---------------------------------------+-------+ +| peer.num_peers_down_interested | gauge | ++---------------------------------------+-------+ +| peer.num_peers_up_unchoked_all | gauge | ++---------------------------------------+-------+ +| peer.num_peers_up_unchoked_optimistic | gauge | ++---------------------------------------+-------+ +| peer.num_peers_up_unchoked | gauge | ++---------------------------------------+-------+ +| peer.num_peers_down_unchoked | gauge | ++---------------------------------------+-------+ +| peer.num_peers_up_requests | gauge | ++---------------------------------------+-------+ +| peer.num_peers_down_requests | gauge | ++---------------------------------------+-------+ +| peer.num_peers_end_game | gauge | ++---------------------------------------+-------+ +| peer.num_peers_up_disk | gauge | ++---------------------------------------+-------+ +| peer.num_peers_down_disk | gauge | ++---------------------------------------+-------+ + + +the number of peer connections for each kind of socket. +``num_peers_half_open`` counts half-open (connecting) peers, no other +count includes those peers. +``num_peers_up_unchoked_all`` is the total number of unchoked peers, +whereas ``num_peers_up_unchoked`` only are unchoked peers that count +against the limit (i.e. excluding peers that are unchoked because the +limit doesn't apply to them). ``num_peers_up_unchoked_optimistic`` is +the number of optimistically unchoked peers. + +.. _net.on_read_counter: + +.. _net.on_write_counter: + +.. _net.on_tick_counter: + +.. _net.on_lsd_counter: + +.. _net.on_lsd_peer_counter: + +.. _net.on_udp_counter: + +.. _net.on_accept_counter: + +.. _net.on_disk_queue_counter: + +.. _net.on_disk_counter: + +.. raw:: html + + + + + + + + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| net.on_read_counter | counter | ++---------------------------+---------+ +| net.on_write_counter | counter | ++---------------------------+---------+ +| net.on_tick_counter | counter | ++---------------------------+---------+ +| net.on_lsd_counter | counter | ++---------------------------+---------+ +| net.on_lsd_peer_counter | counter | ++---------------------------+---------+ +| net.on_udp_counter | counter | ++---------------------------+---------+ +| net.on_accept_counter | counter | ++---------------------------+---------+ +| net.on_disk_queue_counter | counter | ++---------------------------+---------+ +| net.on_disk_counter | counter | ++---------------------------+---------+ + + +These counters count the number of times the +network thread wakes up for each respective +reason. If these counters are very large, it +may indicate a performance issue, causing the +network thread to wake up too ofte, wasting CPU. +mitigate it by increasing buffers and limits +for the specific trigger that wakes up the +thread. + +.. _net.sent_payload_bytes: + +.. _net.sent_bytes: + +.. _net.sent_ip_overhead_bytes: + +.. _net.sent_tracker_bytes: + +.. _net.recv_payload_bytes: + +.. _net.recv_bytes: + +.. _net.recv_ip_overhead_bytes: + +.. _net.recv_tracker_bytes: + +.. raw:: html + + + + + + + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| net.sent_payload_bytes | counter | ++----------------------------+---------+ +| net.sent_bytes | counter | ++----------------------------+---------+ +| net.sent_ip_overhead_bytes | counter | ++----------------------------+---------+ +| net.sent_tracker_bytes | counter | ++----------------------------+---------+ +| net.recv_payload_bytes | counter | ++----------------------------+---------+ +| net.recv_bytes | counter | ++----------------------------+---------+ +| net.recv_ip_overhead_bytes | counter | ++----------------------------+---------+ +| net.recv_tracker_bytes | counter | ++----------------------------+---------+ + + +total number of bytes sent and received by the session + +.. _net.limiter_up_queue: + +.. _net.limiter_down_queue: + +.. raw:: html + + + + ++------------------------+-------+ +| name | type | ++========================+=======+ +| net.limiter_up_queue | gauge | ++------------------------+-------+ +| net.limiter_down_queue | gauge | ++------------------------+-------+ + + +the number of sockets currently waiting for upload and download +bandwidth from the rate limiter. + +.. _net.limiter_up_bytes: + +.. _net.limiter_down_bytes: + +.. raw:: html + + + + ++------------------------+-------+ +| name | type | ++========================+=======+ +| net.limiter_up_bytes | gauge | ++------------------------+-------+ +| net.limiter_down_bytes | gauge | ++------------------------+-------+ + + +the number of upload and download bytes waiting to be handed out from +the rate limiter. + +.. _net.recv_failed_bytes: + +.. raw:: html + + + ++-----------------------+---------+ +| name | type | ++=======================+=========+ +| net.recv_failed_bytes | counter | ++-----------------------+---------+ + + +the number of bytes downloaded that had to be discarded because they +failed the hash check + +.. _net.recv_redundant_bytes: + +.. raw:: html + + + ++--------------------------+---------+ +| name | type | ++==========================+=========+ +| net.recv_redundant_bytes | counter | ++--------------------------+---------+ + + +the number of downloaded bytes that were discarded because they +were downloaded multiple times (from different peers) + +.. _net.has_incoming_connections: + +.. raw:: html + + + ++------------------------------+-------+ +| name | type | ++==============================+=======+ +| net.has_incoming_connections | gauge | ++------------------------------+-------+ + + +is false by default and set to true when +the first incoming connection is established +this is used to know if the client is behind +NAT or not. + +.. _ses.num_checking_torrents: + +.. _ses.num_stopped_torrents: + +.. _ses.num_upload_only_torrents: + +.. _ses.num_downloading_torrents: + +.. _ses.num_seeding_torrents: + +.. _ses.num_queued_seeding_torrents: + +.. _ses.num_queued_download_torrents: + +.. _ses.num_error_torrents: + +.. raw:: html + + + + + + + + + + ++----------------------------------+-------+ +| name | type | ++==================================+=======+ +| ses.num_checking_torrents | gauge | ++----------------------------------+-------+ +| ses.num_stopped_torrents | gauge | ++----------------------------------+-------+ +| ses.num_upload_only_torrents | gauge | ++----------------------------------+-------+ +| ses.num_downloading_torrents | gauge | ++----------------------------------+-------+ +| ses.num_seeding_torrents | gauge | ++----------------------------------+-------+ +| ses.num_queued_seeding_torrents | gauge | ++----------------------------------+-------+ +| ses.num_queued_download_torrents | gauge | ++----------------------------------+-------+ +| ses.num_error_torrents | gauge | ++----------------------------------+-------+ + + +these gauges count the number of torrents in +different states. Each torrent only belongs to +one of these states. For torrents that could +belong to multiple of these, the most prominent +in picked. For instance, a torrent with an error +counts as an error-torrent, regardless of its other +state. + +.. _ses.non_filter_torrents: + +.. raw:: html + + + ++-------------------------+-------+ +| name | type | ++=========================+=======+ +| ses.non_filter_torrents | gauge | ++-------------------------+-------+ + + +the number of torrents that don't have the +IP filter applied to them. + +.. _ses.num_piece_passed: + +.. _ses.num_piece_failed: + +.. _ses.num_have_pieces: + +.. _ses.num_total_pieces_added: + +.. raw:: html + + + + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| ses.num_piece_passed | counter | ++----------------------------+---------+ +| ses.num_piece_failed | counter | ++----------------------------+---------+ +| ses.num_have_pieces | counter | ++----------------------------+---------+ +| ses.num_total_pieces_added | counter | ++----------------------------+---------+ + + +these count the number of times a piece has passed the +hash check, the number of times a piece was successfully +written to disk and the number of total possible pieces +added by adding torrents. e.g. when adding a torrent with +1000 piece, num_total_pieces_added is incremented by 1000. + +.. _ses.num_unchoke_slots: + +.. raw:: html + + + ++-----------------------+-------+ +| name | type | ++=======================+=======+ +| ses.num_unchoke_slots | gauge | ++-----------------------+-------+ + + +the number of allowed unchoked peers + +.. _ses.num_outstanding_accept: + +.. raw:: html + + + ++----------------------------+-------+ +| name | type | ++============================+=======+ +| ses.num_outstanding_accept | gauge | ++----------------------------+-------+ + + +the number of listen sockets that are currently accepting incoming +connections + +.. _ses.num_incoming_choke: + +.. _ses.num_incoming_unchoke: + +.. _ses.num_incoming_interested: + +.. _ses.num_incoming_not_interested: + +.. _ses.num_incoming_have: + +.. _ses.num_incoming_bitfield: + +.. _ses.num_incoming_request: + +.. _ses.num_incoming_piece: + +.. _ses.num_incoming_cancel: + +.. _ses.num_incoming_dht_port: + +.. _ses.num_incoming_suggest: + +.. _ses.num_incoming_have_all: + +.. _ses.num_incoming_have_none: + +.. _ses.num_incoming_reject: + +.. _ses.num_incoming_allowed_fast: + +.. _ses.num_incoming_ext_handshake: + +.. _ses.num_incoming_pex: + +.. _ses.num_incoming_metadata: + +.. _ses.num_incoming_extended: + +.. _ses.num_outgoing_choke: + +.. _ses.num_outgoing_unchoke: + +.. _ses.num_outgoing_interested: + +.. _ses.num_outgoing_not_interested: + +.. _ses.num_outgoing_have: + +.. _ses.num_outgoing_bitfield: + +.. _ses.num_outgoing_request: + +.. _ses.num_outgoing_piece: + +.. _ses.num_outgoing_cancel: + +.. _ses.num_outgoing_dht_port: + +.. _ses.num_outgoing_suggest: + +.. _ses.num_outgoing_have_all: + +.. _ses.num_outgoing_have_none: + +.. _ses.num_outgoing_reject: + +.. _ses.num_outgoing_allowed_fast: + +.. _ses.num_outgoing_ext_handshake: + +.. _ses.num_outgoing_pex: + +.. _ses.num_outgoing_metadata: + +.. _ses.num_outgoing_extended: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++---------------------------------+---------+ +| name | type | ++=================================+=========+ +| ses.num_incoming_choke | counter | ++---------------------------------+---------+ +| ses.num_incoming_unchoke | counter | ++---------------------------------+---------+ +| ses.num_incoming_interested | counter | ++---------------------------------+---------+ +| ses.num_incoming_not_interested | counter | ++---------------------------------+---------+ +| ses.num_incoming_have | counter | ++---------------------------------+---------+ +| ses.num_incoming_bitfield | counter | ++---------------------------------+---------+ +| ses.num_incoming_request | counter | ++---------------------------------+---------+ +| ses.num_incoming_piece | counter | ++---------------------------------+---------+ +| ses.num_incoming_cancel | counter | ++---------------------------------+---------+ +| ses.num_incoming_dht_port | counter | ++---------------------------------+---------+ +| ses.num_incoming_suggest | counter | ++---------------------------------+---------+ +| ses.num_incoming_have_all | counter | ++---------------------------------+---------+ +| ses.num_incoming_have_none | counter | ++---------------------------------+---------+ +| ses.num_incoming_reject | counter | ++---------------------------------+---------+ +| ses.num_incoming_allowed_fast | counter | ++---------------------------------+---------+ +| ses.num_incoming_ext_handshake | counter | ++---------------------------------+---------+ +| ses.num_incoming_pex | counter | ++---------------------------------+---------+ +| ses.num_incoming_metadata | counter | ++---------------------------------+---------+ +| ses.num_incoming_extended | counter | ++---------------------------------+---------+ +| ses.num_outgoing_choke | counter | ++---------------------------------+---------+ +| ses.num_outgoing_unchoke | counter | ++---------------------------------+---------+ +| ses.num_outgoing_interested | counter | ++---------------------------------+---------+ +| ses.num_outgoing_not_interested | counter | ++---------------------------------+---------+ +| ses.num_outgoing_have | counter | ++---------------------------------+---------+ +| ses.num_outgoing_bitfield | counter | ++---------------------------------+---------+ +| ses.num_outgoing_request | counter | ++---------------------------------+---------+ +| ses.num_outgoing_piece | counter | ++---------------------------------+---------+ +| ses.num_outgoing_cancel | counter | ++---------------------------------+---------+ +| ses.num_outgoing_dht_port | counter | ++---------------------------------+---------+ +| ses.num_outgoing_suggest | counter | ++---------------------------------+---------+ +| ses.num_outgoing_have_all | counter | ++---------------------------------+---------+ +| ses.num_outgoing_have_none | counter | ++---------------------------------+---------+ +| ses.num_outgoing_reject | counter | ++---------------------------------+---------+ +| ses.num_outgoing_allowed_fast | counter | ++---------------------------------+---------+ +| ses.num_outgoing_ext_handshake | counter | ++---------------------------------+---------+ +| ses.num_outgoing_pex | counter | ++---------------------------------+---------+ +| ses.num_outgoing_metadata | counter | ++---------------------------------+---------+ +| ses.num_outgoing_extended | counter | ++---------------------------------+---------+ + + +bittorrent message counters. These counters are incremented +every time a message of the corresponding type is received from +or sent to a bittorrent peer. + +.. _ses.waste_piece_timed_out: + +.. _ses.waste_piece_cancelled: + +.. _ses.waste_piece_unknown: + +.. _ses.waste_piece_seed: + +.. _ses.waste_piece_end_game: + +.. _ses.waste_piece_closing: + +.. raw:: html + + + + + + + + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| ses.waste_piece_timed_out | counter | ++---------------------------+---------+ +| ses.waste_piece_cancelled | counter | ++---------------------------+---------+ +| ses.waste_piece_unknown | counter | ++---------------------------+---------+ +| ses.waste_piece_seed | counter | ++---------------------------+---------+ +| ses.waste_piece_end_game | counter | ++---------------------------+---------+ +| ses.waste_piece_closing | counter | ++---------------------------+---------+ + + +the number of wasted downloaded bytes by reason of the bytes being +wasted. + +.. _picker.piece_picker_partial_loops: + +.. _picker.piece_picker_suggest_loops: + +.. _picker.piece_picker_sequential_loops: + +.. _picker.piece_picker_reverse_rare_loops: + +.. _picker.piece_picker_rare_loops: + +.. _picker.piece_picker_rand_start_loops: + +.. _picker.piece_picker_rand_loops: + +.. _picker.piece_picker_busy_loops: + +.. raw:: html + + + + + + + + + + ++----------------------------------------+---------+ +| name | type | ++========================================+=========+ +| picker.piece_picker_partial_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_suggest_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_sequential_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_reverse_rare_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_rare_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_rand_start_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_rand_loops | counter | ++----------------------------------------+---------+ +| picker.piece_picker_busy_loops | counter | ++----------------------------------------+---------+ + + +the number of pieces considered while picking pieces + +.. _picker.reject_piece_picks: + +.. _picker.unchoke_piece_picks: + +.. _picker.incoming_redundant_piece_picks: + +.. _picker.incoming_piece_picks: + +.. _picker.end_game_piece_picks: + +.. _picker.snubbed_piece_picks: + +.. _picker.interesting_piece_picks: + +.. _picker.hash_fail_piece_picks: + +.. raw:: html + + + + + + + + + + ++---------------------------------------+---------+ +| name | type | ++=======================================+=========+ +| picker.reject_piece_picks | counter | ++---------------------------------------+---------+ +| picker.unchoke_piece_picks | counter | ++---------------------------------------+---------+ +| picker.incoming_redundant_piece_picks | counter | ++---------------------------------------+---------+ +| picker.incoming_piece_picks | counter | ++---------------------------------------+---------+ +| picker.end_game_piece_picks | counter | ++---------------------------------------+---------+ +| picker.snubbed_piece_picks | counter | ++---------------------------------------+---------+ +| picker.interesting_piece_picks | counter | ++---------------------------------------+---------+ +| picker.hash_fail_piece_picks | counter | ++---------------------------------------+---------+ + + +This breaks down the piece picks into the event that +triggered it + +.. _disk.write_cache_blocks: + +.. _disk.read_cache_blocks: + +.. raw:: html + + + + ++-------------------------+-------+ +| name | type | ++=========================+=======+ +| disk.write_cache_blocks | gauge | ++-------------------------+-------+ +| disk.read_cache_blocks | gauge | ++-------------------------+-------+ + + +These gauges indicate how many blocks are currently in use as dirty +disk blocks (``write_cache_blocks``) and read cache blocks, +respectively. deprecates ``cache_status::read_cache_size``. +The sum of these gauges deprecates ``cache_status::cache_size``. + +.. _disk.request_latency: + +.. raw:: html + + + ++----------------------+-------+ +| name | type | ++======================+=======+ +| disk.request_latency | gauge | ++----------------------+-------+ + + +the number of microseconds it takes from receiving a request from a +peer until we're sending the response back on the socket. + +.. _disk.pinned_blocks: + +.. _disk.disk_blocks_in_use: + +.. raw:: html + + + + ++-------------------------+-------+ +| name | type | ++=========================+=======+ +| disk.pinned_blocks | gauge | ++-------------------------+-------+ +| disk.disk_blocks_in_use | gauge | ++-------------------------+-------+ + + +``disk_blocks_in_use`` indicates how many disk blocks are currently in +use, either as dirty blocks waiting to be written or blocks kept around +in the hope that a peer will request it or in a peer send buffer. This +gauge deprecates ``cache_status::total_used_buffers``. + +.. _disk.queued_disk_jobs: + +.. _disk.num_running_disk_jobs: + +.. _disk.num_read_jobs: + +.. _disk.num_write_jobs: + +.. _disk.num_jobs: + +.. _disk.blocked_disk_jobs: + +.. _disk.num_writing_threads: + +.. _disk.num_running_threads: + +.. raw:: html + + + + + + + + + + ++----------------------------+-------+ +| name | type | ++============================+=======+ +| disk.queued_disk_jobs | gauge | ++----------------------------+-------+ +| disk.num_running_disk_jobs | gauge | ++----------------------------+-------+ +| disk.num_read_jobs | gauge | ++----------------------------+-------+ +| disk.num_write_jobs | gauge | ++----------------------------+-------+ +| disk.num_jobs | gauge | ++----------------------------+-------+ +| disk.blocked_disk_jobs | gauge | ++----------------------------+-------+ +| disk.num_writing_threads | gauge | ++----------------------------+-------+ +| disk.num_running_threads | gauge | ++----------------------------+-------+ + + +``queued_disk_jobs`` is the number of disk jobs currently queued, +waiting to be executed by a disk thread. Deprecates +``cache_status::job_queue_length``. + +.. _disk.queued_write_bytes: + +.. _disk.arc_mru_size: + +.. _disk.arc_mru_ghost_size: + +.. _disk.arc_mfu_size: + +.. _disk.arc_mfu_ghost_size: + +.. _disk.arc_write_size: + +.. _disk.arc_volatile_size: + +.. raw:: html + + + + + + + + + ++-------------------------+-------+ +| name | type | ++=========================+=======+ +| disk.queued_write_bytes | gauge | ++-------------------------+-------+ +| disk.arc_mru_size | gauge | ++-------------------------+-------+ +| disk.arc_mru_ghost_size | gauge | ++-------------------------+-------+ +| disk.arc_mfu_size | gauge | ++-------------------------+-------+ +| disk.arc_mfu_ghost_size | gauge | ++-------------------------+-------+ +| disk.arc_write_size | gauge | ++-------------------------+-------+ +| disk.arc_volatile_size | gauge | ++-------------------------+-------+ + + +the number of bytes we have sent to the disk I/O +thread for writing. Every time we hear back from +the disk I/O thread with a completed write job, this +is updated to the number of bytes the disk I/O thread +is actually waiting for to be written (as opposed to +bytes just hanging out in the cache) + +.. _disk.num_blocks_written: + +.. _disk.num_blocks_read: + +.. raw:: html + + + + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| disk.num_blocks_written | counter | ++-------------------------+---------+ +| disk.num_blocks_read | counter | ++-------------------------+---------+ + + +the number of blocks written and read from disk in total. A block is 16 +kiB. ``num_blocks_written`` and ``num_blocks_read`` deprecates +``cache_status::blocks_written`` and ``cache_status::blocks_read`` respectively. + +.. _disk.num_blocks_hashed: + +.. raw:: html + + + ++------------------------+---------+ +| name | type | ++========================+=========+ +| disk.num_blocks_hashed | counter | ++------------------------+---------+ + + +the total number of blocks run through SHA-1 hashing + +.. _disk.num_blocks_cache_hits: + +.. raw:: html + + + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| disk.num_blocks_cache_hits | counter | ++----------------------------+---------+ + + +the number of blocks read from the disk cache +Deprecates ``cache_info::blocks_read_hit``. + +.. _disk.num_write_ops: + +.. _disk.num_read_ops: + +.. raw:: html + + + + ++--------------------+---------+ +| name | type | ++====================+=========+ +| disk.num_write_ops | counter | ++--------------------+---------+ +| disk.num_read_ops | counter | ++--------------------+---------+ + + +the number of disk I/O operation for reads and writes. One disk +operation may transfer more then one block. +These counters deprecates ``cache_status::writes`` and +``cache_status::reads``. + +.. _disk.num_read_back: + +.. raw:: html + + + ++--------------------+---------+ +| name | type | ++====================+=========+ +| disk.num_read_back | counter | ++--------------------+---------+ + + +the number of blocks that had to be read back from disk in order to +hash a piece (when verifying against the piece hash) + +.. _disk.disk_read_time: + +.. _disk.disk_write_time: + +.. _disk.disk_hash_time: + +.. _disk.disk_job_time: + +.. raw:: html + + + + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| disk.disk_read_time | counter | ++----------------------+---------+ +| disk.disk_write_time | counter | ++----------------------+---------+ +| disk.disk_hash_time | counter | ++----------------------+---------+ +| disk.disk_job_time | counter | ++----------------------+---------+ + + +cumulative time spent in various disk jobs, as well +as total for all disk jobs. Measured in microseconds + +.. _disk.num_fenced_read: + +.. _disk.num_fenced_write: + +.. _disk.num_fenced_hash: + +.. _disk.num_fenced_move_storage: + +.. _disk.num_fenced_release_files: + +.. _disk.num_fenced_delete_files: + +.. _disk.num_fenced_check_fastresume: + +.. _disk.num_fenced_save_resume_data: + +.. _disk.num_fenced_rename_file: + +.. _disk.num_fenced_stop_torrent: + +.. _disk.num_fenced_flush_piece: + +.. _disk.num_fenced_flush_hashed: + +.. _disk.num_fenced_flush_storage: + +.. _disk.num_fenced_trim_cache: + +.. _disk.num_fenced_file_priority: + +.. _disk.num_fenced_load_torrent: + +.. _disk.num_fenced_clear_piece: + +.. _disk.num_fenced_tick_storage: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + ++----------------------------------+-------+ +| name | type | ++==================================+=======+ +| disk.num_fenced_read | gauge | ++----------------------------------+-------+ +| disk.num_fenced_write | gauge | ++----------------------------------+-------+ +| disk.num_fenced_hash | gauge | ++----------------------------------+-------+ +| disk.num_fenced_move_storage | gauge | ++----------------------------------+-------+ +| disk.num_fenced_release_files | gauge | ++----------------------------------+-------+ +| disk.num_fenced_delete_files | gauge | ++----------------------------------+-------+ +| disk.num_fenced_check_fastresume | gauge | ++----------------------------------+-------+ +| disk.num_fenced_save_resume_data | gauge | ++----------------------------------+-------+ +| disk.num_fenced_rename_file | gauge | ++----------------------------------+-------+ +| disk.num_fenced_stop_torrent | gauge | ++----------------------------------+-------+ +| disk.num_fenced_flush_piece | gauge | ++----------------------------------+-------+ +| disk.num_fenced_flush_hashed | gauge | ++----------------------------------+-------+ +| disk.num_fenced_flush_storage | gauge | ++----------------------------------+-------+ +| disk.num_fenced_trim_cache | gauge | ++----------------------------------+-------+ +| disk.num_fenced_file_priority | gauge | ++----------------------------------+-------+ +| disk.num_fenced_load_torrent | gauge | ++----------------------------------+-------+ +| disk.num_fenced_clear_piece | gauge | ++----------------------------------+-------+ +| disk.num_fenced_tick_storage | gauge | ++----------------------------------+-------+ + + +for each kind of disk job, a counter of how many jobs of that kind +are currently blocked by a disk fence + +.. _dht.dht_nodes: + +.. raw:: html + + + ++---------------+-------+ +| name | type | ++===============+=======+ +| dht.dht_nodes | gauge | ++---------------+-------+ + + +The number of nodes in the DHT routing table + +.. _dht.dht_node_cache: + +.. raw:: html + + + ++--------------------+-------+ +| name | type | ++====================+=======+ +| dht.dht_node_cache | gauge | ++--------------------+-------+ + + +The number of replacement nodes in the DHT routing table + +.. _dht.dht_torrents: + +.. raw:: html + + + ++------------------+-------+ +| name | type | ++==================+=======+ +| dht.dht_torrents | gauge | ++------------------+-------+ + + +the number of torrents currently tracked by our DHT node + +.. _dht.dht_peers: + +.. raw:: html + + + ++---------------+-------+ +| name | type | ++===============+=======+ +| dht.dht_peers | gauge | ++---------------+-------+ + + +the number of peers currently tracked by our DHT node + +.. _dht.dht_immutable_data: + +.. raw:: html + + + ++------------------------+-------+ +| name | type | ++========================+=======+ +| dht.dht_immutable_data | gauge | ++------------------------+-------+ + + +the number of immutable data items tracked by our DHT node + +.. _dht.dht_mutable_data: + +.. raw:: html + + + ++----------------------+-------+ +| name | type | ++======================+=======+ +| dht.dht_mutable_data | gauge | ++----------------------+-------+ + + +the number of mutable data items tracked by our DHT node + +.. _dht.dht_allocated_observers: + +.. raw:: html + + + ++-----------------------------+-------+ +| name | type | ++=============================+=======+ +| dht.dht_allocated_observers | gauge | ++-----------------------------+-------+ + + +the number of RPC observers currently allocated + +.. _dht.dht_messages_in: + +.. _dht.dht_messages_out: + +.. raw:: html + + + + ++----------------------+---------+ +| name | type | ++======================+=========+ +| dht.dht_messages_in | counter | ++----------------------+---------+ +| dht.dht_messages_out | counter | ++----------------------+---------+ + + +the total number of DHT messages sent and received + +.. _dht.dht_messages_in_dropped: + +.. raw:: html + + + ++-----------------------------+---------+ +| name | type | ++=============================+=========+ +| dht.dht_messages_in_dropped | counter | ++-----------------------------+---------+ + + +the number of incoming DHT requests that were dropped. There are a few +different reasons why incoming DHT packets may be dropped: + +1. there wasn't enough send quota to respond to them. +2. the Denial of service logic kicked in, blocking the peer +3. ignore_dark_internet is enabled, and the packet came from a + non-public IP address +4. the bencoding of the message was invalid + +.. _dht.dht_messages_out_dropped: + +.. raw:: html + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| dht.dht_messages_out_dropped | counter | ++------------------------------+---------+ + + +the number of outgoing messages that failed to be +sent + +.. _dht.dht_bytes_in: + +.. _dht.dht_bytes_out: + +.. raw:: html + + + + ++-------------------+---------+ +| name | type | ++===================+=========+ +| dht.dht_bytes_in | counter | ++-------------------+---------+ +| dht.dht_bytes_out | counter | ++-------------------+---------+ + + +the total number of bytes sent and received by the DHT + +.. _dht.dht_ping_in: + +.. _dht.dht_ping_out: + +.. _dht.dht_find_node_in: + +.. _dht.dht_find_node_out: + +.. _dht.dht_get_peers_in: + +.. _dht.dht_get_peers_out: + +.. _dht.dht_announce_peer_in: + +.. _dht.dht_announce_peer_out: + +.. _dht.dht_get_in: + +.. _dht.dht_get_out: + +.. _dht.dht_put_in: + +.. _dht.dht_put_out: + +.. _dht.dht_sample_infohashes_in: + +.. _dht.dht_sample_infohashes_out: + +.. raw:: html + + + + + + + + + + + + + + + + ++-------------------------------+---------+ +| name | type | ++===============================+=========+ +| dht.dht_ping_in | counter | ++-------------------------------+---------+ +| dht.dht_ping_out | counter | ++-------------------------------+---------+ +| dht.dht_find_node_in | counter | ++-------------------------------+---------+ +| dht.dht_find_node_out | counter | ++-------------------------------+---------+ +| dht.dht_get_peers_in | counter | ++-------------------------------+---------+ +| dht.dht_get_peers_out | counter | ++-------------------------------+---------+ +| dht.dht_announce_peer_in | counter | ++-------------------------------+---------+ +| dht.dht_announce_peer_out | counter | ++-------------------------------+---------+ +| dht.dht_get_in | counter | ++-------------------------------+---------+ +| dht.dht_get_out | counter | ++-------------------------------+---------+ +| dht.dht_put_in | counter | ++-------------------------------+---------+ +| dht.dht_put_out | counter | ++-------------------------------+---------+ +| dht.dht_sample_infohashes_in | counter | ++-------------------------------+---------+ +| dht.dht_sample_infohashes_out | counter | ++-------------------------------+---------+ + + +the number of DHT messages we've sent and received +by kind. + +.. _dht.dht_invalid_announce: + +.. _dht.dht_invalid_get_peers: + +.. _dht.dht_invalid_find_node: + +.. _dht.dht_invalid_put: + +.. _dht.dht_invalid_get: + +.. _dht.dht_invalid_sample_infohashes: + +.. raw:: html + + + + + + + + ++-----------------------------------+---------+ +| name | type | ++===================================+=========+ +| dht.dht_invalid_announce | counter | ++-----------------------------------+---------+ +| dht.dht_invalid_get_peers | counter | ++-----------------------------------+---------+ +| dht.dht_invalid_find_node | counter | ++-----------------------------------+---------+ +| dht.dht_invalid_put | counter | ++-----------------------------------+---------+ +| dht.dht_invalid_get | counter | ++-----------------------------------+---------+ +| dht.dht_invalid_sample_infohashes | counter | ++-----------------------------------+---------+ + + +the number of failed incoming DHT requests by kind of request + +.. _utp.utp_packet_loss: + +.. _utp.utp_timeout: + +.. _utp.utp_packets_in: + +.. _utp.utp_packets_out: + +.. _utp.utp_fast_retransmit: + +.. _utp.utp_packet_resend: + +.. _utp.utp_samples_above_target: + +.. _utp.utp_samples_below_target: + +.. _utp.utp_payload_pkts_in: + +.. _utp.utp_payload_pkts_out: + +.. _utp.utp_invalid_pkts_in: + +.. _utp.utp_redundant_pkts_in: + +.. raw:: html + + + + + + + + + + + + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| utp.utp_packet_loss | counter | ++------------------------------+---------+ +| utp.utp_timeout | counter | ++------------------------------+---------+ +| utp.utp_packets_in | counter | ++------------------------------+---------+ +| utp.utp_packets_out | counter | ++------------------------------+---------+ +| utp.utp_fast_retransmit | counter | ++------------------------------+---------+ +| utp.utp_packet_resend | counter | ++------------------------------+---------+ +| utp.utp_samples_above_target | counter | ++------------------------------+---------+ +| utp.utp_samples_below_target | counter | ++------------------------------+---------+ +| utp.utp_payload_pkts_in | counter | ++------------------------------+---------+ +| utp.utp_payload_pkts_out | counter | ++------------------------------+---------+ +| utp.utp_invalid_pkts_in | counter | ++------------------------------+---------+ +| utp.utp_redundant_pkts_in | counter | ++------------------------------+---------+ + + +uTP counters. Each counter represents the number of time each event +has occurred. + +.. _utp.num_utp_idle: + +.. _utp.num_utp_syn_sent: + +.. _utp.num_utp_connected: + +.. _utp.num_utp_fin_sent: + +.. _utp.num_utp_close_wait: + +.. _utp.num_utp_deleted: + +.. raw:: html + + + + + + + + ++------------------------+-------+ +| name | type | ++========================+=======+ +| utp.num_utp_idle | gauge | ++------------------------+-------+ +| utp.num_utp_syn_sent | gauge | ++------------------------+-------+ +| utp.num_utp_connected | gauge | ++------------------------+-------+ +| utp.num_utp_fin_sent | gauge | ++------------------------+-------+ +| utp.num_utp_close_wait | gauge | ++------------------------+-------+ +| utp.num_utp_deleted | gauge | ++------------------------+-------+ + + +the number of uTP sockets in each respective state + +.. _sock_bufs.socket_send_size3: + +.. _sock_bufs.socket_send_size4: + +.. _sock_bufs.socket_send_size5: + +.. _sock_bufs.socket_send_size6: + +.. _sock_bufs.socket_send_size7: + +.. _sock_bufs.socket_send_size8: + +.. _sock_bufs.socket_send_size9: + +.. _sock_bufs.socket_send_size10: + +.. _sock_bufs.socket_send_size11: + +.. _sock_bufs.socket_send_size12: + +.. _sock_bufs.socket_send_size13: + +.. _sock_bufs.socket_send_size14: + +.. _sock_bufs.socket_send_size15: + +.. _sock_bufs.socket_send_size16: + +.. _sock_bufs.socket_send_size17: + +.. _sock_bufs.socket_send_size18: + +.. _sock_bufs.socket_send_size19: + +.. _sock_bufs.socket_send_size20: + +.. _sock_bufs.socket_recv_size3: + +.. _sock_bufs.socket_recv_size4: + +.. _sock_bufs.socket_recv_size5: + +.. _sock_bufs.socket_recv_size6: + +.. _sock_bufs.socket_recv_size7: + +.. _sock_bufs.socket_recv_size8: + +.. _sock_bufs.socket_recv_size9: + +.. _sock_bufs.socket_recv_size10: + +.. _sock_bufs.socket_recv_size11: + +.. _sock_bufs.socket_recv_size12: + +.. _sock_bufs.socket_recv_size13: + +.. _sock_bufs.socket_recv_size14: + +.. _sock_bufs.socket_recv_size15: + +.. _sock_bufs.socket_recv_size16: + +.. _sock_bufs.socket_recv_size17: + +.. _sock_bufs.socket_recv_size18: + +.. _sock_bufs.socket_recv_size19: + +.. _sock_bufs.socket_recv_size20: + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| sock_bufs.socket_send_size3 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size4 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size5 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size6 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size7 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size8 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size9 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size10 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size11 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size12 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size13 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size14 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size15 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size16 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size17 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size18 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size19 | counter | ++------------------------------+---------+ +| sock_bufs.socket_send_size20 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size3 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size4 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size5 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size6 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size7 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size8 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size9 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size10 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size11 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size12 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size13 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size14 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size15 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size16 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size17 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size18 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size19 | counter | ++------------------------------+---------+ +| sock_bufs.socket_recv_size20 | counter | ++------------------------------+---------+ + + +the buffer sizes accepted by +socket send and receive calls respectively. +The larger the buffers are, the more efficient, +because it reqire fewer system calls per byte. +The size is 1 << n, where n is the number +at the end of the counter name. i.e. +8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, +16384, 32768, 65536, 131072, 262144, 524288, 1048576 +bytes + +.. _tracker.num_queued_tracker_announces: + +.. raw:: html + + + ++--------------------------------------+-------+ +| name | type | ++======================================+=======+ +| tracker.num_queued_tracker_announces | gauge | ++--------------------------------------+-------+ + + +if the outstanding tracker announce limit is reached, tracker +announces are queued, to be issued when an announce slot opens up. +this measure the number of tracker announces currently in the +queue + diff --git a/docs/storage.png b/docs/storage.png new file mode 100644 index 0000000000000000000000000000000000000000..61ae82e1ef1c8953f3b9052611cd836003797036 GIT binary patch literal 12360 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 literal 0 HcmV?d00001 diff --git a/docs/streaming.html b/docs/streaming.html new file mode 100644 index 0000000..3185903 --- /dev/null +++ b/docs/streaming.html @@ -0,0 +1,154 @@ + + + + + + + +Streaming implementation + + + + + +
    +
    + + libtorrent logo + +
    +

    Streaming implementation

    + +++ + + + +
    Version:2.0.0
    +

    This documents describes the algorithm libtorrent uses to satisfy time critical +piece requests, i.e. streaming.

    +
    +

    piece picking

    +

    The standard bittorrent piece picker is peer-centric. A peer unchokes us or we +complete a block from a peer and we want to make another request to that peer. +The piece picker answers the question: which block should we request from this +peer.

    +

    When streaming, we have a number of time critical pieces, the ones the video +or audio player will need next to keep up with the stream. To keep the deadlines +of these pieces, we need a mechanism to answer the question: I want to request +blocks from this piece, which peer is the most likely to be able to deliver it +to me the soonest.

    +

    This question is answered by torrent::request_time_critical_pieces() in +libtorrent.

    +

    At a high level, this algorithm keeps a list of peers, sorted by the estimated +download queue time. That is, the estimated time for a new request to this +peer to be received. The bottom 10th percentile of the peers (the 10% slowest +peers) are ignored and not included in the peer list. Peers that have choked +us, are not interesting, is on parole, disconnecting, have too many outstanding +block requests or is snubbed are also excluded from the peer list.

    +

    The time critical pieces are also kept sorted by their deadline. Pieces with +an earlier deadline first. This list of pieces is iterated, starting at the +top, and blocks are requested from a piece until we cannot make any more +requests from it. We then move on to the next piece and request blocks from it +until we cannot make any more. The peer each request is sent to is the one +with the lowest download queue time. Each time a request is made, this +estimate is updated and the peer is resorted in this list.

    +

    Any peer that doesn't have the piece is ignored until we move on to the next +piece.

    +

    If the top peer's download queue time is more than 2 seconds, the loop is +terminated. This is to not over-request. request_time_critical_pieces() +is called once per second, so this will keep the queue full with margin.

    +
    +
    +

    download queue time

    +

    Each peer maintains the number of bytes that have been requested from it but +not yet been received. This is referred to as outstanding_bytes. This number +is incremented by the size of each outgoing request and decremented for each +payload byte received.

    +

    This counter is divided by an estimated download rate from the peer to form +the estimated download queue time. That is, the estimated time it will take +any new request to this peer to begin being received.

    +

    The estimated download rate of a peer is not trivial. There may not be any +outstanding requests to the peer, in which case the payload download rate +will be zero. That would not be a reasonable estimate of the rate we would see +once we make a request.

    +

    If we have not received any payload from a peer in the last 30 seconds, we +must use an alternative estimate of the download rate. If we have received +payload from this peer previously, we can use the peak download rate.

    +

    If we have received less than 2 blocks (32 kiB) and we have been unchoked for +less than 5 seconds ago, use the average download rate of all peers (that have +outstanding requests).

    +
    +
    +

    timeouts

    +

    An observation that is useful to keep in mind when streaming is that your +download capacity is likely to be saturated by your peers. In this case, if the +swarm is well seeded, most peers will send data to you at close to the same +rate. This makes it important to support streaming from many slow peers. For +instance, this means you can't make assumptions about the download time of a +block being less than some absolute time. You may be downloading at well above +the bit rate of the video, but each individual peer only transfers at 5 kiB/s.

    +

    In this state, your download rate is a zero-sum-game. Any block you request +that is not urgent, will take away from the bandwidth you get for peers that +are urgent. Make sure to limit requests to useful blocks only.

    +

    Some requests will stall. It appears to be very hard to have enough accuracy in +the prediction of download queue time such that all requests come back within a +reasonable amount of time.

    +

    To support adaptive timeouts, each torrent maintains a running average of how +long it takes to complete a piece. There is also a running average of the +deviation from the mean download time.

    +

    This download time is used as the benchmark to determine when blocks have +timed out, and should be re-requested from another peer.

    +

    If any time-critical piece has taken more than the average piece download +time + a half average deviation form that, the piece is considered to have +timed out. This means we are allowed to double-request blocks. Subsequent +passes over this piece will make sure that any blocks we don't already have +are requested one more time.

    +

    In fact, this scales to multiple time-outs. The time since a download was +started is divided by average download time + average deviation time / 2. +The resulting integer is the number if times the piece has timed out.

    +

    Each time a piece times out, another busy request is allowed to try to make +it complete sooner. A busy request is where a block is requested from a peer +even though it has already been requested from another peer.

    +

    This has the effect of getting more and more aggressive in requesting blocks +the longer it takes to complete the piece. If this mechanism is too aggressive, +a significant amount of bandwidth may be lost in redundant download (keep in +mind the zero-sum game).

    +

    It never makes sense to request a block twice from the same peer. There is logic +in place to prevent this.

    +
    +
    +

    optimizations

    +

    One optimization is to buffer all piece requests while looping over the time- +critical pieces and not send them until one round is complete. This increases +the chances that the request messages are coalesced into the same packet. +This in turn lowers the number of system calls and network overhead.

    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/streaming.rst b/docs/streaming.rst new file mode 100644 index 0000000..6ee4bea --- /dev/null +++ b/docs/streaming.rst @@ -0,0 +1,128 @@ +Streaming implementation +======================== + +.. include:: header.rst + +This documents describes the algorithm libtorrent uses to satisfy time critical +piece requests, i.e. streaming. + +piece picking +------------- + +The standard bittorrent piece picker is peer-centric. A peer unchokes us or we +complete a block from a peer and we want to make another request to that peer. +The piece picker answers the question: which block should we request from this +peer. + +When streaming, we have a number of *time critical* pieces, the ones the video +or audio player will need next to keep up with the stream. To keep the deadlines +of these pieces, we need a mechanism to answer the question: I want to request +blocks from this piece, which peer is the most likely to be able to deliver it +to me the soonest. + +This question is answered by ``torrent::request_time_critical_pieces()`` in +libtorrent. + +At a high level, this algorithm keeps a list of peers, sorted by the estimated +download queue time. That is, the estimated time for a new request to this +peer to be received. The bottom 10th percentile of the peers (the 10% slowest +peers) are ignored and not included in the peer list. Peers that have choked +us, are not interesting, is on parole, disconnecting, have too many outstanding +block requests or is snubbed are also excluded from the peer list. + +The time critical pieces are also kept sorted by their deadline. Pieces with +an earlier deadline first. This list of pieces is iterated, starting at the +top, and blocks are requested from a piece until we cannot make any more +requests from it. We then move on to the next piece and request blocks from it +until we cannot make any more. The peer each request is sent to is the one +with the lowest `download queue time`_. Each time a request is made, this +estimate is updated and the peer is resorted in this list. + +Any peer that doesn't have the piece is ignored until we move on to the next +piece. + +If the top peer's download queue time is more than 2 seconds, the loop is +terminated. This is to not over-request. ``request_time_critical_pieces()`` +is called once per second, so this will keep the queue full with margin. + +download queue time +------------------- + +Each peer maintains the number of bytes that have been requested from it but +not yet been received. This is referred to as ``outstanding_bytes``. This number +is incremented by the size of each outgoing request and decremented for each +*payload* byte received. + +This counter is divided by an estimated download rate from the peer to form +the estimated *download queue time*. That is, the estimated time it will take +any new request to this peer to begin being received. + +The estimated download rate of a peer is not trivial. There may not be any +outstanding requests to the peer, in which case the payload download rate +will be zero. That would not be a reasonable estimate of the rate we would see +once we make a request. + +If we have not received any payload from a peer in the last 30 seconds, we +must use an alternative estimate of the download rate. If we have received +payload from this peer previously, we can use the peak download rate. + +If we have received less than 2 blocks (32 kiB) and we have been unchoked for +less than 5 seconds ago, use the average download rate of all peers (that have +outstanding requests). + +timeouts +-------- + +An observation that is useful to keep in mind when streaming is that your +download capacity is likely to be saturated by your peers. In this case, if the +swarm is well seeded, most peers will send data to you at close to the same +rate. This makes it important to support streaming from many slow peers. For +instance, this means you can't make assumptions about the download time of a +block being less than some absolute time. You may be downloading at well above +the bit rate of the video, but each individual peer only transfers at 5 kiB/s. + +In this state, your download rate is a zero-sum-game. Any block you request +that is not urgent, will take away from the bandwidth you get for peers that +are urgent. Make sure to limit requests to useful blocks only. + +Some requests will stall. It appears to be very hard to have enough accuracy in +the prediction of download queue time such that all requests come back within a +reasonable amount of time. + +To support adaptive timeouts, each torrent maintains a running average of how +long it takes to complete a piece. There is also a running average of the +deviation from the mean download time. + +This download time is used as the benchmark to determine when blocks have +timed out, and should be re-requested from another peer. + +If any time-critical piece has taken more than the average piece download +time + a half average deviation form that, the piece is considered to have +timed out. This means we are allowed to double-request blocks. Subsequent +passes over this piece will make sure that any blocks we don't already have +are requested one more time. + +In fact, this scales to multiple time-outs. The time since a download was +started is divided by average download time + average deviation time / 2. +The resulting integer is the number if *times* the piece has timed out. + +Each time a piece times out, another *busy request* is allowed to try to make +it complete sooner. A busy request is where a block is requested from a peer +even though it has already been requested from another peer. + +This has the effect of getting more and more aggressive in requesting blocks +the longer it takes to complete the piece. If this mechanism is too aggressive, +a significant amount of bandwidth may be lost in redundant download (keep in +mind the zero-sum game). + +It never makes sense to request a block twice from the same peer. There is logic +in place to prevent this. + +optimizations +------------- + +One optimization is to buffer all piece requests while looping over the time- +critical pieces and not send them until one round is complete. This increases +the chances that the request messages are coalesced into the same packet. +This in turn lowers the number of system calls and network overhead. + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..ce03d34 --- /dev/null +++ b/docs/style.css @@ -0,0 +1,154 @@ +.entry { min-height: 135px; } + +#main { + font-family: Verdana; + text-align: left; + margin-top: 10px; +} + +/* Base elements */ + +* {margin: 0; padding: 0;} +body, table { font: 0.9em Verdana, sans-serif;} + +h1, h2, h3 { + font-family: Georgia "Times New Roman", serif; + padding-bottom: 0.5em; + font-weight: bold; +} + +div.sidebar { + background: #f8f8e8; + float: right; + width: 20em; + margin-right: 1em; + border: solid 1px #e5e5d5; + padding: 1.3em; +} + +div.sidebar p.sidebar-title { + font: 1.3em Georgia; + border-bottom: solid 1px #e5e5d5; + padding-bottom: 0.5em; + margin: 0 0 0.5em 0; +} + +a { + text-decoration: none; + color: #8D370A; + border-bottom: dotted 1px #8D370A; +} + +ul, ol { line-height: 1.8em; } +ul { list-style: square; } +li { margin-left: 2.8em; } + +p, ul, ol, img {margin-bottom: 1em;} + +.align-right { + float: right; +} + +.document { + margin: 0px; +} + +div.section { + margin-bottom: 3em; +} + +div.section div.section div.section { + margin-bottom: 2em; +} + +div.section p, div.section ul, div.section dl { +} + +#container { + text-align: left; + max-width: 65em; + margin: 5px auto; + position: relative; + padding: 3px ; +} + +#header { + height: 116px; + width: 100%; + border: none; + margin-top: 1em; + margin-bottom: 1em; +} + +#header tr td { + border: none; +} + +#orange { + margin: 0; + padding: 0; + width: 159px; + height: 116px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + background: url('img/orange.png') no-repeat top left; +} + +#logo { + margin: 0; + padding: 0; + text-align: center; + color: white; + font-size: 50pt; + height: 116px; + font-family: Georgia; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + background: url('img/bg.png'); +} + +#gradient { + width: 100%; + clear: both; + height: 30px; + background: linear-gradient(#bbb, #ddd); +} + +#footer { + margin: 0px; + padding: 0px; + text-align: center; + background: #ddd; + width: 100%; + color: #777; + overflow: hidden; +} + +#footer table { + margin-left: auto; + margin-right: auto; + font-size: 1em; +} + +#footer tr td { + border: none; + background: none; + color: #777; + text-align: left; +} + +#footer a { + color: #777; +} + +#footer a:hover { + color: #000; +} + +#filler { + height: 200px; + background: linear-gradient(#ddd, #fff); +} + +li p, li li { font-size: 100%; } + diff --git a/docs/todo.html b/docs/todo.html new file mode 100644 index 0000000..1523a02 --- /dev/null +++ b/docs/todo.html @@ -0,0 +1,11816 @@ + + + + +

    libtorrent todo-list

    +3 urgent +43 important +55 relevant +4 feasible +184 notes +
    relevance 4../test/test_dht.cpp:1281pass in the actual salt as a parameter
    relevance 4../test/test_dht.cpp:2172pass in th actual salt as the argument
    relevance 4../src/disk_io_thread.cpp:1173instead of doing this. pass in the settings to each storage_interface call. Each disk thread could hold its most recent understanding of the settings in a shared_ptr, and update it every time it wakes up from a job. That way each access to the settings won't require a std::mutex to be held.
    relevance 3../test/test_dht.cpp:111make the mock_socket hold a reference to the list of where to record packets instead of having a global variable
    relevance 3../test/test_dht.cpp:120ideally the mock_socket would contain this queue of packets, to make tests independent
    relevance 3../test/test_dht.cpp:1238split this up into smaller tests
    relevance 3../test/test_dht.cpp:2641use dht_test_setup class to simplify the node setup
    relevance 3../test/test_dht.cpp:3186use dht_test_setup class to simplify the node setup
    relevance 3../test/test_dht.cpp:3285use dht_test_setup class to simplify the node setup
    relevance 3../test/test_dht.cpp:3378use dht_test_setup class to simplify the node setup
    relevance 3../src/ut_metadata.cpp:272use the aux::write_* functions and the span here instead, it will fit better with send_buffer()
    relevance 3../src/session_handle.cpp:625expose the sequence_number, public_key, secret_key and signature types to the client
    relevance 3../src/peer_connection.cpp:3059instead of having to ask the torrent whether it's in graceful pause mode or not, the peers should keep that state (and the torrent should update them when it enters graceful pause). When a peer enters graceful pause mode, it should cancel all outstanding requests and clear its request queue.
    relevance 3../src/peer_connection.cpp:3946once peers are properly put in graceful pause mode, they can cancel all outstanding requests and this test can be removed.
    relevance 3../src/peer_connection.cpp:4630new_piece should be an optional. piece index -1 should not be allowed
    relevance 3../src/upnp.cpp:99bind the broadcast socket. it would probably have to be changed to a vector of interfaces to bind to, since the broadcast socket opens one socket per local interface by default
    relevance 3../src/session_impl.cpp:981closing the udp sockets here means that the uTP connections cannot be closed gracefully
    relevance 3../src/session_impl.cpp:1430the logic in this if-block should be factored out into a separate function. At least most of it
    relevance 3../src/session_impl.cpp:2394it would be neat if the utp socket manager would handle ICMP errors too
    relevance 3../src/session_impl.cpp:3907it would probably make sense to have a separate list of peers that are eligible for optimistic unchoke, similar to the torrents perhaps this could even iterate over the pool allocators of torrent_peer objects. It could probably be done in a single pass and collect the n best candidates. maybe just a queue of peers would make even more sense, just pick the next peer in the queue for unchoking. It would be O(1).
    relevance 3../src/session_impl.cpp:3930peers should know whether their torrent is paused or not, instead of having to ask it over and over again
    relevance 3../src/session_impl.cpp:4176there should be a pre-calculated list of all peers eligible for unchoking
    relevance 3../src/session_impl.cpp:4751perhaps params could be moved into the torrent object, instead of it being copied by the torrent constructor
    relevance 3../src/session_impl.cpp:6019use public_key here instead of std::array
    relevance 3../src/torrent.cpp:239we could probably get away with just saving a few fields here
    relevance 3../src/torrent.cpp:837assert there are no outstanding async operations on this torrent
    relevance 3../src/torrent.cpp:1380there's some duplication between this function and peer_connection::incoming_piece(). is there a way to merge something?
    relevance 3../src/torrent.cpp:3746this could probably be pulled out into a free function
    relevance 3../src/torrent.cpp:4503should this alert have an error code in it?
    relevance 3../src/torrent.cpp:4570this should return optional<>. piece index -1 should not be allowed
    relevance 3../src/web_peer_connection.cpp:187this should be an optional, piece index -1 should not be allowed
    relevance 3../src/web_peer_connection.cpp:401do we really need a special case here? wouldn't the multi-file case handle single file torrents correctly too?
    relevance 3../src/web_peer_connection.cpp:486file_index_t should not allow negative values
    relevance 3../src/web_peer_connection.cpp:665this could be made more efficient for the case when we use an HTTP proxy. Then we wouldn't need to add new web seeds to the torrent, we could just make the redirect table contain full URLs.
    relevance 3../src/file_storage.cpp:671this is a hack to retain ABI compatibility with 1.2.1 in next major release, make this return by value
    relevance 3../src/kademlia/rpc_manager.cpp:65move this into it's own .cpp file
    relevance 3../include/libtorrent/enum_net.hpp:152use string_view for device_name
    relevance 3../include/libtorrent/stat.hpp:255everything but payload counters and rates could probably be removed from here
    relevance 3../include/libtorrent/torrent_handle.hpp:494unify url_seed and http_seed with just web_seed, using the web_seed_entry.
    relevance 3../include/libtorrent/tracker_manager.hpp:388make sure the udp_socket supports passing on string-hostnames too, and that this function is used
    relevance 3../include/libtorrent/pe_crypto.hpp:71dh_key_exchange should probably move into its own file
    relevance 3../include/libtorrent/torrent.hpp:1362factor out predictive pieces and all operations on it into a separate class (to use as memeber here instead)
    relevance 3../include/libtorrent/torrent.hpp:1421factor out the links (as well as update_list() to a separate class that torrent can inherit)
    relevance 3../include/libtorrent/web_peer_connection.hpp:117if we make this be a disk_buffer_holder instead we would save a copy use allocate_disk_receive_buffer and release_disk_receive_buffer
    relevance 3../include/libtorrent/kademlia/routing_table.hpp:141to improve memory locality and scanning performance, turn the routing table into a single vector with boundaries for the nodes instead. Perhaps replacement nodes should be in a separate vector.
    relevance 3../include/libtorrent/aux_/storage_utils.hpp:52remove this typedef, and use span for disk write operations
    relevance 2../test/test_piece_picker.cpp:2367test picking with partial pieces and other peers present so that both backup_pieces and backup_pieces2 are used
    relevance 2../test/test_storage.cpp:533split this test up into smaller parts
    relevance 2../test/test_dht.cpp:1674test num_global_nodes
    relevance 2../test/test_dht.cpp:1675test need_refresh
    relevance 2../test/test_dht.cpp:2862split this up into smaller test cases
    relevance 2../src/ut_metadata.cpp:103if we were to initialize m_metadata_size lazily instead, we would probably be more efficient initialize m_metadata_size
    relevance 2../src/instantiate_connection.cpp:40peer_connection and tracker_connection should probably be flags
    relevance 2../src/bdecode.cpp:785attempt to simplify this implementation by embracing the span
    relevance 2../src/peer_connection.cpp:2500this should probably be based on time instead of number of request messages. For a very high throughput connection, 300 may be a legitimate number of requests to have in flight when getting choked
    relevance 2../src/peer_connection.cpp:3236since we throw away the queue entry once we issue the disk job, this may happen. Instead, we should keep the queue entry around, mark it as having been requested from disk and once the disk job comes back, discard it if it has been cancelled. Maybe even be able to cancel disk jobs?
    relevance 2../src/peer_connection.cpp:4872use a deadline_timer for timeouts. Don't rely on second_tick()! Hook this up to connect timeout as well. This would improve performance because of less work in second_tick(), and might let use remove ticking entirely eventually
    relevance 2../src/http_tracker_connection.cpp:392returning a bool here is redundant. Instead this function should return the peer_entry
    relevance 2../src/session_impl.cpp:547is there a reason not to move all of this into init()? and just post it to the io_service?
    relevance 2../src/session_impl.cpp:669the ip filter should probably be saved here too
    relevance 2../src/session_impl.cpp:3633make a list for torrents that want to be announced on the DHT so we don't have to loop over all torrents, just to find the ones that want to announce
    relevance 2../src/session_impl.cpp:5420this function should be removed and users need to deal with the more generic case of having multiple listen ports
    relevance 2../src/session_impl.cpp:5460this function should be removed and users need to deal with the more generic case of having multiple ssl ports
    relevance 2../src/session_impl.cpp:6294this should be factored into the udp socket, so we only have the code once
    relevance 2../src/torrent.cpp:240p should probably be moved in here
    relevance 2../src/torrent.cpp:354set_merkle_tree should probably take the vector as &&
    relevance 2../src/torrent.cpp:595post alert
    relevance 2../src/torrent.cpp:1868add a unit test where we don't have metadata, connect to a peer that sends a bitfield that's too large, then we get the metadata
    relevance 2../src/torrent.cpp:4118use chrono type for time duration
    relevance 2../src/torrent.cpp:4514abort lookups this torrent has made via the session host resolver interface
    relevance 2../src/torrent.cpp:7148if peer is a really good peer, maybe we shouldn't disconnect it perhaps this logic should be disabled if we have too many idle peers (with some definition of idle)
    relevance 2../src/utp_stream.cpp:294it would be nice if not everything would have to be public here
    relevance 2../src/udp_tracker_connection.cpp:77support authentication here. tracker_req().auth
    relevance 2../src/alert_manager.cpp:75keep a count of the number of threads waiting. Only if it's > 0 notify them
    relevance 2../src/path.cpp:349test this on a FAT volume to see what error we get!
    relevance 2../src/peer_list.cpp:471it would be nice if there was a way to iterate over these torrent_peer objects in the order they are allocated in the pool instead. It would probably be more efficient
    relevance 2../src/piece_picker.cpp:1946make the 2048 limit configurable
    relevance 2../src/piece_picker.cpp:2589the first_block returned here is the largest free range, not the first-fit range, which would be better
    relevance 2../src/piece_picker.cpp:3407it would be nice if this could be folded into lock_piece() the main distinction is that this also maintains the m_num_passed counter and the passed_hash_check member Is there ever a case where we call write filed without also locking the piece? Perhaps write_failed() should imply locking it.
    relevance 2../src/storage_utils.cpp:350technically, this is where the transaction of moving the files is completed. This is where the new save_path should be committed. If there is an error in the code below, that should not prevent the new save path to be set. Maybe it would make sense to make the save_path an in-out parameter
    relevance 2../src/storage_utils.cpp:522is this risky? The upper layer will assume we have the whole file. Perhaps we should verify that at least the size of the file is correct
    relevance 2../src/web_peer_connection.cpp:615just make this peer not have the pieces associated with the file we just requested. Only when it doesn't have any of the file do the following pad files will make it complicated
    relevance 2../src/escape_string.cpp:203this should probably be moved into string_util.cpp
    relevance 2../src/tracker_manager.cpp:376implement
    relevance 2../src/alert.cpp:1695the salt here is allocated on the heap. It would be nice to allocate in the stack_allocator
    relevance 2../src/block_cache.cpp:1619turn these return values into enums returns -1: block not in cache -2: out of memory
    relevance 2../src/kademlia/node.cpp:681it would be nice to have a bias towards node-id prefixes that are missing in the bucket
    relevance 2../src/kademlia/node.cpp:750use the non deprecated function instead of this one
    relevance 2../src/kademlia/routing_table.cpp:297use the non deprecated function instead of this one
    relevance 2../src/kademlia/routing_table.cpp:932move the lowest priority nodes to the replacement bucket
    relevance 2../src/kademlia/dht_storage.cpp:82make this configurable in dht_settings
    relevance 2../include/libtorrent/chained_buffer.hpp:59this type should probably be renamed to send_buffer
    relevance 2../include/libtorrent/peer_connection.hpp:955this should really be a circular buffer
    relevance 2../include/libtorrent/peer_connection.hpp:1053rename this target queue size
    relevance 2../include/libtorrent/piece_picker.hpp:611having 8 priority levels is probably excessive. It should probably be changed to 3 levels + dont-download
    relevance 2../include/libtorrent/enum_net.hpp:184this could be done more efficiently by just looking up the interface with the given name, maybe even with if_nametoindex()
    relevance 2../include/libtorrent/proxy_base.hpp:274use the resolver interface that has a built-in cache
    relevance 2../include/libtorrent/broadcast_socket.hpp:50factor these functions out
    relevance 2../include/libtorrent/socks5_stream.hpp:137add async_connect() that takes a hostname and port as well
    relevance 2../include/libtorrent/aux_/session_interface.hpp:130make this interface a lot smaller. It could be split up into several smaller interfaces. Each subsystem could then limit the size of the mock object to test it.
    relevance 2../include/libtorrent/aux_/session_interface.hpp:139the IP voting mechanism should be factored out to its own class, not part of the session and these constants should move too
    relevance 1../src/session_impl.cpp:5636report the proper address of the router as the source IP of this vote of our external address, instead of the empty address
    relevance 1../src/torrent.cpp:1221make this depend on the error and on the filesystem the files are being downloaded to. If the error is no_space_left_on_device and the filesystem doesn't support sparse files, only zero the priorities of the pieces that are at the tails of all files, leaving everything up to the highest written piece in each file
    relevance 1../src/torrent.cpp:7507should disconnect all peers that have the pieces we have not just seeds. It would be pretty expensive to check all pieces for all peers though
    relevance 1../include/libtorrent/ip_voter.hpp:128have one instance per possible subnet, 192.168.x.x, 10.x.x.x, etc.
    relevance 0../test/test_resume.cpp:548test what happens when loading a resume file with both piece priorities and file priorities (file prio should take precedence)
    relevance 0../test/test_resume.cpp:551make sure a resume file only ever contain file priorities OR piece priorities. Never both.
    relevance 0../test/test_resume.cpp:554generally save
    relevance 0../test/test_resume.cpp:874test all other resume flags here too. This would require returning more than just the torrent_status from test_resume_flags. Also http seeds and trackers for instance
    relevance 0../test/test_resume.cpp:1575test all other resume flags here too. This would require returning more than just the torrent_status from test_resume_flags. Also http seeds and trackers for instance
    relevance 0../test/test_ssl.cpp:411test using a signed certificate with the wrong info-hash in DN
    relevance 0../test/test_ssl.cpp:509also test using a hash that refers to a valid torrent but that differs from the SNI hash
    relevance 0../test/test_peer_list.cpp:968test erasing peers
    relevance 0../test/test_peer_list.cpp:969test update_peer_port with allow_multiple_connections_per_ip and without
    relevance 0../test/test_peer_list.cpp:970test add i2p peers
    relevance 0../test/test_peer_list.cpp:971test allow_i2p_mixed
    relevance 0../test/test_peer_list.cpp:972test insert_peer failing with all error conditions
    relevance 0../test/test_peer_list.cpp:973test IPv6
    relevance 0../test/test_peer_list.cpp:974test connect_to_peer() failing
    relevance 0../test/test_peer_list.cpp:975test connection_closed
    relevance 0../test/test_peer_list.cpp:976connect candidates recalculation when incrementing failcount
    relevance 0../test/test_tracker.cpp:55test scrape requests
    relevance 0../test/test_tracker.cpp:56test parse peers6
    relevance 0../test/test_tracker.cpp:57test parse tracker-id
    relevance 0../test/test_tracker.cpp:58test parse failure-reason
    relevance 0../test/test_tracker.cpp:59test all failure paths, including invalid bencoding not a dictionary no files entry in scrape response no info-hash entry in scrape response malformed peers in peer list of dictionaries uneven number of bytes in peers and peers6 string responses
    relevance 0../test/test_timestamp_history.cpp:54test the case where we have > 120 samples (and have the base delay actually be updated)
    relevance 0../test/test_timestamp_history.cpp:55test the case where a sample is lower than the history entry but not lower than the base
    relevance 0../test/test_flags.cpp:140change to a different test setup. currently always paused. test_set_after_add(torrent_flags::paused); test_unset_after_add(torrent_flags::paused);
    relevance 0../test/test_flags.cpp:176change to a different test setup. currently always paused. test_set_after_add(torrent_flags::stop_when_ready);
    relevance 0../test/test_upnp.cpp:151store the log and verify that some key messages are there
    relevance 0../test/test_file_storage.cpp:778test file attributes
    relevance 0../test/test_file_storage.cpp:779test symlinks
    relevance 0../test/test_file_storage.cpp:780test reorder_file (make sure internal_file_entry::swap() is used)
    relevance 0../test/test_storage.cpp:1002this should take a span of iovec_ts
    relevance 0../test/test_storage.cpp:1027this should take a span
    relevance 0../test/test_torrent_info.cpp:173test remap_files
    relevance 0../test/test_torrent_info.cpp:174merkle torrents. specifically torrent_info::add_merkle_nodes and torrent with "root hash"
    relevance 0../test/test_torrent_info.cpp:175torrent with 'p' (padfile) attribute
    relevance 0../test/test_torrent_info.cpp:176torrent with 'h' (hidden) attribute
    relevance 0../test/test_torrent_info.cpp:177torrent with 'x' (executable) attribute
    relevance 0../test/test_torrent_info.cpp:178torrent with 'l' (symlink) attribute
    relevance 0../test/test_torrent_info.cpp:179creating a merkle torrent (torrent_info::build_merkle_list)
    relevance 0../test/test_torrent_info.cpp:180torrent with multiple trackers in multiple tiers, making sure we shuffle them (how do you test shuffling?, load it multiple times and make sure it's in different order at least once)
    relevance 0../test/test_torrent_info.cpp:183torrents with a zero-length name
    relevance 0../test/test_torrent_info.cpp:184torrents with a merkle tree and add_merkle_nodes
    relevance 0../test/test_torrent_info.cpp:185torrent with a non-dictionary info-section
    relevance 0../test/test_torrent_info.cpp:186torrents with DHT nodes
    relevance 0../test/test_torrent_info.cpp:187torrent with url-list as a single string
    relevance 0../test/test_torrent_info.cpp:188torrent with http seed as a single string
    relevance 0../test/test_torrent_info.cpp:189torrent with a comment
    relevance 0../test/test_torrent_info.cpp:190torrent with an SSL cert
    relevance 0../test/test_torrent_info.cpp:191torrent with attributes (executable and hidden)
    relevance 0../test/test_torrent_info.cpp:192torrent_info::add_tracker
    relevance 0../test/test_torrent_info.cpp:193torrent_info constructor that takes an invalid bencoded buffer
    relevance 0../test/test_torrent_info.cpp:194verify_encoding with a string that triggers character replacement
    relevance 0../test/test_block_cache.cpp:484test try_evict_blocks
    relevance 0../test/test_block_cache.cpp:485test evicting volatile pieces, to see them be removed
    relevance 0../test/test_block_cache.cpp:486test evicting dirty pieces
    relevance 0../test/test_block_cache.cpp:487test free_piece
    relevance 0../test/test_block_cache.cpp:488test abort_dirty
    relevance 0../test/test_block_cache.cpp:489test unaligned reads
    relevance 0../test/test_fast_extension.cpp:1128test sending invalid requests (out of bound piece index, offsets and sizes)
    relevance 0../test/test_bloom_filter.cpp:134test size()
    relevance 0../test/test_bloom_filter.cpp:135test clear()
    relevance 0../test/test_dht.cpp:465check to make sure the "best" items are stored
    relevance 0../test/test_dht.cpp:3257this won't work because the second node isn't pinged so it wont be added to the routing table
    relevance 0../test/test_dht.cpp:4047test obfuscated_get_peers
    relevance 0../test/test_resolve_links.cpp:86test files with different piece size (negative test)
    relevance 0../test/test_resolve_links.cpp:89it would be nice to test resolving of more than just 2 files as well. like 3 single file torrents merged into one, resolving all 3 files.
    relevance 0../test/test_transfer.cpp:218these settings_pack tests belong in their own test
    relevance 0../test/test_transfer.cpp:317factor out the disk-full test into its own unit test
    relevance 0../src/ut_metadata.cpp:285we really need to increment the refcounter on the torrent while this buffer is still in the peer's send buffer
    relevance 0../src/ut_metadata.cpp:338make this an enum class
    relevance 0../src/disk_buffer_pool.cpp:207perhaps we should sort the buffers here?
    relevance 0../src/socks5_stream.cpp:93we could bind the socket here, since we know what the target endpoint is of the proxy
    relevance 0../src/session_handle.cpp:415in C++14, use unique_ptr and move it into the lambda
    relevance 0../src/peer_connection.cpp:1102this should be the global download rate
    relevance 0../src/peer_connection.cpp:3461sort the allowed fast set in priority order
    relevance 0../src/torrent_info.cpp:86remove this limit and the overloads that imply it, in favour of using load_torrent_limits
    relevance 0../src/torrent_info.cpp:739this should be considered a failure, and the .torrent file rejected
    relevance 0../src/magnet_uri.cpp:271what's the right number here?
    relevance 0../src/part_file.cpp:238what do we do if someone is currently reading from the disk from this piece? does it matter? Since we won't actively erase the data from disk, but it may be overwritten soon, it's probably not that big of a deal
    relevance 0../src/part_file.cpp:348instead of rebuilding the whole file header and flushing it, update the slot entries as we go
    relevance 0../src/packet_buffer.cpp:155use compare_less_wrap for this comparison as well
    relevance 0../src/session_impl.cpp:1338it would be nice to reserve() these vectors up front
    relevance 0../src/session_impl.cpp:1789could this function be merged with expand_unspecified_addresses? right now both listen_endpoint_t and listen_interface_t are almost identical, maybe the latter could be removed too
    relevance 0../src/session_impl.cpp:2087it would probably be better to do this by having a listen-socket "version" number that gets bumped. And instead of setting a bool to disable a tracker, we set the version number that it was disabled at. This change would affect the ABI in 1.2, so should be done in 2.0 or later
    relevance 0../src/session_impl.cpp:3327have a separate list for these connections, instead of having to loop through all of them
    relevance 0../src/session_impl.cpp:3360this should apply to all bandwidth channels
    relevance 0../src/session_impl.cpp:4066use a lower limit than m_settings.connections_limit to allocate the to 10% or so of connection slots for incoming connections cap this at max - 1, since we may add one below
    relevance 0../src/session_impl.cpp:4211post a message to have this happen immediately instead of waiting for the next tick
    relevance 0../src/session_impl.cpp:4595it might be a nice feature here to limit the number of torrents to send in a single update. By just posting the first n torrents, they would nicely be round-robined because the torrent lists are always pushed back. Perhaps the status_update_alert could even have a fixed array of n entries rather than a vector, to further improve memory locality.
    relevance 0../src/session_impl.cpp:5011factor out this logic into a separate function for unit testing
    relevance 0../src/session_impl.cpp:5826refactor, move the storage to dht_tracker
    relevance 0../src/session_impl.cpp:6156asserts that no outstanding async operations are still in flight
    relevance 0../src/ip_notifier.cpp:37simulator support
    relevance 0../src/cpuid.cpp:128enable when aarch64 is really tested
    relevance 0../src/add_torrent_params.cpp:76pre C++17, GCC and msvc does not make std::string nothrow move assignable, which means no type containing a string will be nothrow move assignable by default either static_assert(std::is_nothrow_move_assignable::value , "should be nothrow move assignable");
    relevance 0../src/add_torrent_params.cpp:82it would be nice if this was nothrow default constructible static_assert(std::is_nothrow_default_constructible::value , "should be nothrow default constructible");
    relevance 0../src/torrent.cpp:98factor out cache_status to its own header
    relevance 0../src/torrent.cpp:360if this is a merkle torrent and we can't restore the tree, we need to wipe all the bits in the have array, but not necessarily we might want to do a full check to see if we have all the pieces. This is low priority since almost no one uses merkle torrents
    relevance 0../src/torrent.cpp:489if the existing torrent doesn't have metadata, insert the metadata we just downloaded into it.
    relevance 0../src/torrent.cpp:1560is verify_peer_cert called once per certificate in the chain, and this function just tells us which depth we're at right now? If so, the comment makes sense. any certificate that isn't the leaf (i.e. the one presented by the peer) should be accepted automatically, given preverified is true. The leaf certificate need to be verified to make sure its DN matches the info-hash
    relevance 0../src/torrent.cpp:1991this could be optimized by looking up which files are complete and just look at those
    relevance 0../src/torrent.cpp:2004this could be optimized by looking up which files are complete and just look at those
    relevance 0../src/torrent.cpp:2659this pattern is repeated in a few places. Factor this into a function and generalize the concept of a torrent having a dedicated listen port
    relevance 0../src/torrent.cpp:3662add one peer per IP the hostname resolves to
    relevance 0../src/torrent.cpp:8534add a flag to ignore stats, and only care about resume data for content. For unchanged files, don't trigger a load of the metadata just to save an empty resume data file
    relevance 0../src/torrent.cpp:10075instead of resorting the whole list, insert the peers directly into the right place
    relevance 0../src/choker.cpp:272use an incremental partial_sort() here
    relevance 0../src/choker.cpp:335make configurable
    relevance 0../src/web_connection_base.cpp:68introduce a web-seed default class which has a low download priority
    relevance 0../src/broadcast_socket.cpp:111this function is pointless
    relevance 0../src/utp_stream.cpp:1727this loop is not very efficient. It could be fixed by having a separate list of sequence numbers that need resending
    relevance 0../src/udp_tracker_connection.cpp:629why is this a linked list?
    relevance 0../src/torrent_peer.cpp:174how do we deal with our external address changing?
    relevance 0../src/piece_picker.cpp:107find a better place for this
    relevance 0../src/piece_picker.cpp:2022this could probably be optimized by incrementally calling partial_sort to sort one more element in the list. Because chances are that we'll just need a single piece, and once we've picked from it we're done. Sorting the rest of the list in that case is a waste of time.
    relevance 0../src/piece_picker.cpp:2172Is it a good idea that this affinity takes precedence over piece priority?
    relevance 0../src/piece_picker.cpp:2561when expanding pieces for cache stripe reasons, the !downloading condition doesn't make much sense
    relevance 0../src/piece_picker.cpp:3117should 5 be configurable?
    relevance 0../src/torrent_handle.cpp:534support moving files into this call
    relevance 0../src/storage_utils.cpp:285ideally, if we end up copying files because of a move across volumes, the source should not be deleted until they've all been copied. That would let us rollback with higher confidence.
    relevance 0../src/storage_utils.cpp:501this should probably be moved to default_storage::initialize
    relevance 0../src/disk_io_thread.cpp:492in this case, the piece should probably not be flushed yet. are there any more cases where it should?
    relevance 0../src/disk_io_thread.cpp:943it would be nice to optimize this by having the cache pieces also ordered by
    relevance 0../src/disk_io_thread.cpp:985instead of doing a lookup each time through the loop, save cached_piece_entry pointers with piece_refcount incremented to pin them
    relevance 0../src/disk_io_thread.cpp:1185in the future, propagate exceptions back to the handlers
    relevance 0../src/disk_io_thread.cpp:1243a potentially more efficient solution would be to have a special queue for retry jobs, that's only ever run when a job completes, in any thread. It would only work if counters::num_running_disk_jobs > 0
    relevance 0../src/disk_io_thread.cpp:2003this is potentially very expensive. One way to solve it would be to have a fence for just this one piece.
    relevance 0../src/disk_io_thread.cpp:2265we should probably just hang the job on the piece and make sure the hasher gets kicked
    relevance 0../src/settings_pack.cpp:276deprecate this
    relevance 0../src/settings_pack.cpp:517it would be nice to reserve() these vectors up front
    relevance 0../src/http_parser.cpp:138remove to_string() if we're in C++14
    relevance 0../src/http_parser.cpp:146remove to_string() if we're in C++14
    relevance 0../src/enum_net.cpp:254if we get here, the caller still assumes the error code is reported via errno
    relevance 0../src/enum_net.cpp:264if we get here, the caller still assumes the error code is reported via errno
    relevance 0../src/pe_crypto.cpp:62it would be nice to get the literal working
    relevance 0../src/pe_crypto.cpp:73it would be nice to be able to export to a fixed width field, so we wouldn't have to shift it later
    relevance 0../src/udp_socket.cpp:528use the system resolver_interface here
    relevance 0../src/udp_socket.cpp:644perhaps an attempt should be made to bind m_socks5_sock to the device of m_listen_socket
    relevance 0../src/file_storage.cpp:1002padfiles should be removed
    relevance 0../src/file_storage.cpp:1155in C++17 this could be string_view
    relevance 0../src/block_cache.cpp:1009it's somewhat expensive to iterate over this linked list. Presumably because of the random access of memory. It would be nice if pieces with no evictable blocks weren't in this list
    relevance 0../src/block_cache.cpp:1080this should probably only be done every n:th time
    relevance 0../src/block_cache.cpp:1714create a holder for refcounts that automatically decrement
    relevance 0../src/kademlia/item.cpp:146implement ctor for entry from bdecode_node?
    relevance 0../src/kademlia/node.cpp:1170keep the returned value to pass as a limit to write_nodes_entries when implemented
    relevance 0../src/kademlia/node.cpp:1198limit number of entries in the result
    relevance 0../src/kademlia/routing_table.cpp:281This is temporary. For now, only report the largest routing table (of potentially multiple ones, for multi-homed systems) in next major version, break the ABI and support reporting all of them in the dht_stats_alert
    relevance 0../src/kademlia/routing_table.cpp:306arvidn note when it's across IPv4 and IPv6, adding (dht_global_nodes) would make sense. in the future though, where we may have one DHT node per external interface (which may be multiple of the same address family), then it becomes a bit trickier
    relevance 0../src/kademlia/routing_table.cpp:510this need to take bucket "prefix" into account. It should be unified with add_node_impl()
    relevance 0../src/kademlia/put_data.cpp:88what if o is not an instance of put_data_observer? This need to be redesigned for better type safety.
    relevance 0../src/kademlia/node_id.cpp:64it's a little bit weird to return 159 - leading zeroes. It should probably be 160 - leading zeroes, but all other code in here is tuned to this expectation now, and it doesn't really matter (other than complexity)
    relevance 0../src/kademlia/dht_tracker.cpp:303pick the closest node rather than the first
    relevance 0../include/libtorrent/config.hpp:47don't include that here. Make each header that use the export macros include it instead. and move it to aux_
    relevance 0../include/libtorrent/performance_counters.hpp:47move this out of counters
    relevance 0../include/libtorrent/performance_counters.hpp:151should keepalives be in here too? how about dont-have, share-mode, upload-only
    relevance 0../include/libtorrent/performance_counters.hpp:495some space could be saved here by making gauges 32 bits
    relevance 0../include/libtorrent/performance_counters.hpp:496restore these to regular integers. Instead have one copy of the counters per thread and collect them at convenient synchronization points
    relevance 0../include/libtorrent/peer_connection.hpp:186make this a raw pointer (to save size in the first cache line) and make the constructor take a raw pointer. torrent objects should always outlive their peers
    relevance 0../include/libtorrent/peer_connection.hpp:985factor this out into its own class with a virtual interface torrent and session should implement this interface
    relevance 0../include/libtorrent/string_view.hpp:40replace this by the standard string_view in C++17
    relevance 0../include/libtorrent/piece_picker.hpp:766should this be allocated lazily?
    relevance 0../include/libtorrent/piece_picker.hpp:773this could be a much more efficient data structure
    relevance 0../include/libtorrent/piece_picker.hpp:846it would be more intuitive to account "wanted" pieces instead of filtered
    relevance 0../include/libtorrent/upnp.hpp:146support using the windows API for UPnP operations as well
    relevance 0../include/libtorrent/block_cache.hpp:225make this 32 bits and to count seconds since the block cache was created
    relevance 0../include/libtorrent/sha1_hash.hpp:58find a better place for these functions
    relevance 0../include/libtorrent/proxy_base.hpp:188it would be nice to remember the bind port and bind once we know where the proxy is m_sock.bind(endpoint, ec);
    relevance 0../include/libtorrent/peer_connection_interface.hpp:50make this interface smaller!
    relevance 0../include/libtorrent/announce_entry.hpp:86include the number of peers received from this tracker, at last announce
    relevance 0../include/libtorrent/broadcast_socket.hpp:60refactor these out too
    relevance 0../include/libtorrent/identify_client.hpp:47hide this declaration when deprecated functions are disabled, and remove its internal use
    relevance 0../include/libtorrent/utp_stream.hpp:426implement blocking write. Low priority since it's not used (yet)
    relevance 0../include/libtorrent/torrent_info.hpp:607there may be some opportunities to optimize the size if torrent_info. specifically to turn some std::string and std::vector into pointers
    relevance 0../include/libtorrent/torrent_info.hpp:672change the type to std::shared_ptr in C++17
    relevance 0../include/libtorrent/torrent.hpp:246make this a raw pointer. perhaps keep the shared_ptr around further down the object to maintain an owner
    relevance 0../include/libtorrent/torrent.hpp:424make graceful pause also finish all sending blocks before disconnecting
    relevance 0../include/libtorrent/torrent.hpp:1304this wastes 5 bits per file
    relevance 0../include/libtorrent/kademlia/msg.hpp:85move this to its own .hpp/.cpp pair?
    relevance 0../include/libtorrent/kademlia/item.hpp:58since this is a public function, it should probably be moved out of this header and into one with other public functions.
    relevance 0../include/libtorrent/aux_/deprecated.hpp:41figure out which version of clang this is supported in
    relevance 0../include/libtorrent/aux_/session_impl.hpp:241make these direct members and generate shared_ptrs to them which alias the listen_socket_t shared_ptr
    relevance 0../include/libtorrent/aux_/session_impl.hpp:1048replace this by a proper asio timer
    relevance 0../include/libtorrent/aux_/session_impl.hpp:1053replace this by a proper asio timer
    relevance 0../include/libtorrent/aux_/session_impl.hpp:1060replace this by a proper asio timer
    relevance 0../include/libtorrent/aux_/session_interface.hpp:221it would be nice to not have this be part of session_interface
    \ No newline at end of file diff --git a/docs/troubleshooting.dot b/docs/troubleshooting.dot new file mode 100644 index 0000000..c26931e --- /dev/null +++ b/docs/troubleshooting.dot @@ -0,0 +1,108 @@ +digraph no_download { + + node [shape=box]; + + node_peers [label="Do you have any peers?\n(torrent_status::num_peers)"]; + node_unchoked [label="Have any of the peers unchoked you?\n(peer_info::flags & peer_info::remote_unchoked)"]; + node_error [label="Does the torrent have an error state?\n(torrent_status::error)"]; + node_paused [label="Is the torrent paused?\n(torrent_status::paused)"]; + node_end_error [label="The error string in the torrent describes what\nwent wrong in the torrent. This is typically\nindicative of a fatal error that, once resolved,\nrequires you to call torrent_handle::clear_error()\nto clear before resuming"]; + node_tracker [label="Do you have any trackers in the torrent?\n(torrent_handle::trackers())"]; + node_auto [label="Is the torrent auto managed?\n(torrent_status::auto_managed)"]; + node_outstanding_reqs [label="Do you have any outstanding requests to any peer?\n(peer_info::download_queue_length)"]; + node_interested [label="Are you interested in any peers?\n(peer_info::flags & peer_info::interesting)"]; + node_tracker_peers [label="Did any tracker return any peers?\n(tracker_reply_alert::num_peers)"]; + + node_dht_enabled [label="Is DHT enabled?\n(session::is_dht_running())"]; + + node_dht_nodes [label="Do you see more than 5 DHT nodes?\n(session_status::dht_nodes)"] + + node_peers_for_sure [label="Do you know for sure the torrent has peers?"]; + + node_peers_connected [label="Were any of the peers connected to?\n(peer_info::flags & peer_info::connecting)"]; + + node_end_wireshark_tracker [label="Wireshark the tracker announce\nfrom all your peers and make sure\nthey all send identical info-hashes."]; + + node_connect_speed [label="connect_speed, connection_limit, ip-filter"]; + + node_upload_mode [label="Is the torrent in upload mode?\n(torrent_status::upload_mode)"]; + node_bwstate [label="What is the peer read_state set to?\n(peer_info::read_state)"]; + + node_dl_limit [label="There is a download rate limit in affect on your peers.\nDo you have a download rate limit set?\n(settings_pack::download_rate_limit)"]; + + node_dl_disk [label="Peers are blocked waiting on the disk.\nThis typically means your disk is overloaded"]; + + node_dl_socket [label="The peer is listening on the socket,\nbut no data is coming down"]; + +// end states + + node_end_queued [label="This means the torrent is 'queued'. i.e. it will\nbe started once the torrents in front of it\ncompletes downloading. To know the queue\norder, see torrent_status::queue_position. To\nconfigure the number of simultaneous downloads,\nsee settings_pack::active_limit and\nsettings_pack::active_downloads."]; + + node_end_stopped [label="This means the torrent is\n'stopped'. To start it call\ntorrent_handle::resume()."]; + + node_end_no_peer_source [label="You don't have any peers and you don't\nhave a way to acquire peers. You either\nneed a torrent with a tracker or you need\nDHT to be enabled. The DHT will not help\nfor private torrents"]; + + node_end_dht_broken [label="The DHT is probably not working correctly.\nYou might want to add a DHT bootstrap node\nthrough session::add_dht_router(), or have a\ntorrent with DHT nodes in it, or peer\nconnections of peers that are part of the DHT\nnetwork."]; + + node_end_no_peers [label="The torrent you are trying to\ndownload may not have any peers,\n and all peers you may see are stale\nand not responding anymore."]; + + node_end_supply_demand [label="There might be a much higher demand\nthan supply in this torrent. Waiting\naround will probably get you unchoked\neventually."]; + + node_end_flash_crowd [label="The torrent might have a very large number\nof peers and only very few seeds. This sometimes\nhappens with torrents that gain popularity\nvery fast, much faster than the initial seed\nand early peers can keep up distributing\nto. Typically when this happens all your peers,\nand you, will get pieces in lock-step."]; + + node_end_no_download [label="This means the torrent is configured to not\ndownload anything. If you want to download,\ntake the torrent out of upload only mode. If\nthe disk the torrent is downloading to is full,\nor if writing to the disk failed in some way, the\ntorrent may have switched into upload mode\nautomatically"]; + + node_peers -> node_error [label="no"]; + node_peers -> node_unchoked [label="yes"]; + + node_error -> node_end_error [label="yes"]; + node_error -> node_paused [label="no"]; + + node_paused -> node_auto [label="yes"]; + node_paused -> node_tracker [label="no"]; + + node_auto -> node_end_queued [label="yes"]; + node_auto -> node_end_stopped [label="no"]; + + node_tracker -> node_tracker_peers [label="yes"]; + node_tracker -> node_dht_enabled [label="no"]; + + node_tracker_peers -> node_peers_for_sure [label="no"]; + node_tracker_peers -> node_peers_connected [label="yes"]; + + node_peers_for_sure -> node_end_wireshark_tracker [label="yes"]; + node_peers_for_sure -> node_dht_enabled [label="no"]; + + node_peers_connected -> node_connect_speed [label="no"]; + node_peers_connected -> node_end_no_peers [label="yes"]; + + node_dht_enabled -> node_end_no_peer_source [label="no"]; + node_dht_enabled -> node_dht_nodes [label="yes"]; + + node_dht_nodes -> node_end_dht_broken [label="no"]; + node_dht_nodes -> node_end_no_peers [label="yes"]; + + node_unchoked -> node_outstanding_reqs [label="yes"]; + node_unchoked -> node_interested [label="no"]; + + node_outstanding_reqs -> node_bwstate [label="yes"]; + node_outstanding_reqs -> node_upload_mode [label="no"]; + + node_upload_mode -> node_end_flash_crowd [label="no"]; + node_upload_mode -> node_end_no_download [label="yes"]; + + node_interested -> node_upload_mode [label="no"]; + node_interested -> node_end_supply_demand [label="yes"]; + + node_bwstate -> "?" [label="peer_info::bw_idle"]; + node_bwstate -> node_dl_limit [label="peer_info::bw_limit"]; + node_bwstate -> node_dl_socket [label="peer_info::bw_network"]; + node_bwstate -> node_dl_disk [label="peer_info::bw_disk"]; + + node_dl_limit -> "Your download rate limit may be set too low.\nKeep in mind that it is specified in bytes\nper second." [label="yes"]; + node_dl_limit -> "mixed mode?" [label="no"]; + + node_dl_disk -> "e" [label="yes"]; + node_dl_disk -> "f" [label="no"]; +} + diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html new file mode 100644 index 0000000..e9275a5 --- /dev/null +++ b/docs/troubleshooting.html @@ -0,0 +1,79 @@ + + + + + + +libtorrent manual + + + + + + + +
    +
    + + + + +
    +

    libtorrent manual

    + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    +

    The following troubleshooting chart may help in finding out why torrents fail +to download. It is not complete, please submit suggestions via pull requests at +https://github.com/arvidn/libtorrent or to the mailing list. Ideally in the +form of patches against docs/troubleshooting.dot.

    +

    thumb

    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/troubleshooting.png b/docs/troubleshooting.png new file mode 100644 index 0000000000000000000000000000000000000000..7ff9394a9c108770d747a5f26daf632ccc62e2ca GIT binary patch literal 350876 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

    OSuo*ywnB#ymu%83WYe3lK*(D7lh@g*3}X?LA+ zz6K!jLt9&$!`s`J2+&iFKZYBj>wsQMBTe5@Id<$=*L`~X2P%u9B4~DfOaGYrik_sZ z5(pWZT%QC88Lg1VN~p1^ZhBEMM?ERZcbR8NG)Pj<}%>Lj#w-0yyM(^`r z<7c{wnxN~{7}-bE47+M#0;nW|_NH-Nm-YKA3-G0$dLhY#Xg}>B|3&2Yz;tQ#GYKxh zMfs7dNNj~CE=#5BCVk4gp!wpAw;(nNDF^E$?JVrj*y!(b)vil3IvkRr*X7HXJ=nwr z`*S9(0M>jlnQW*aAEtb+NzJHT_#_&zn6*-41v!FjbbWU0+Uzr0)?l<=602pu1%W2J z;V8a`y3t1JINEJRoPO?5cdY=%Y5Rv2a&R7(eQ;-HqBggkT<+}A<^Wy0y3HoR4#rpy zN4hkp-2cl$-sHJ!BfOw(AKue7ZQh%{>)oOp97xhEI^lu(RO=90)R)+74x%GO-2E~6 z!jd)Z@AEm8_wCz9Z4cNg45$fWw=#-s#r-39RSx_MqxtgEzE4^(OOY7rijGae=5a!D z#9uoCZ9QM>)_hG~#?74uG$`odyx19?a7NK|ec|vC4hZ$6uJLd}dEVO{a)M!LTfT2i z8(C%vFnm$mH;BLfZ4&`#l>_Hl17KF>d9nvw-*liIBGEyBwaRR#QB=JS_XW!Ud5(S9 z#1CBOdAAQW!jyvid1BKxiZz`*iV|{T!`_RnfKKYI&m0Z#?{rKyT`3eB6I7uB(upwd zJ@D$91qshR?7H_!@_Biw@69#YQg01CKNx@E072s5#H;!fnghUTv4w*v5a0l7YVZx` z>@H&T)W_NF8<8Hso3Ut)ZvPa>7`yE+?PN#lvkvqCV(v=fZC)g>lB*=ozO*J1Y%285 zosF9gcovgKL*)=M-T@8=!xSt_mHfHzMAK(iXiQ)b_Ub@p%QXLRS9KBkU*H!D7T$B6 zo(T0^xEbrx8>+Vq{A5xlpo!ll>e|_mYE3c{I=rvMM!MGSWuICDi|k793{$JLR~Ko{ zBfy*qEd_!Qlf-j~eQA!p5AQK1p}y8<*NKV`!p{nY8HjaMMurekv=49GVm+^D)!38k z$*9%!JtUAV3~)2&7i03#T+kAZ2R%6b>X8)4GnEvo@|zF&5UBs3%X9L;r}gx3qHC~B zwhWS5;358Wt`L`zW8_-30#++y;oy2}j{Ps|6xtclcW^ra$YFdRHd7Agyv-DCO~zFt zx|}#Tt)Sb?6q{}FPSThwpdw(M4i`42+X~tByr4>=EcN~;e^E<+@a=HS+ak8pyP@*S zvww%-b+4GmHKQ30_l8@V}e!i)ztAW|ZWC)OG0;RyV>d1k%K-*G?_ZBK$r{0|xhWJF84b*J= zW~k*;5bu;Af6)&bfd=w2q-ZVF%wG%Lk#Se-FD+yD-+nag9?+ZkXrpanwYs*q1naxm z063Dcn*{-J75oG@Lo_>th!Tw|Y|xC4yTEq-h?;{U(#EE$Wa}^EoDH>7jN;S{LcCm~irW z24M?qe|cf}$-?y(e?S~JeDd3OWTel$yClmc=LubhOcfxBQ(!2!oyXgJH!G30zFTqL z^^S3{%DOs}7zE%igb+pna#(+Wjz~%NCLN8q`-S;Is3Fvoq=ugF^+QaAbUE_hD0hW{ zjmp)=KtFWa$6(+O^-*$H=K^AI<~PfMoBNu{ zw*Ijh9GFU>>Qlm*+1p zI>ARJl7uKI|NQ;Av#wAp1GBKY$Kz%v;%McTKKyyXV54mrM2pyD1xlfHjn7HZAdIG{ z_Wokl!Ptvn9nHs#8%cXb9K-^ESA^qGcG%!;9w?s+>i7ZqvhT z80_(DX!c~Ck?%&K08l$~zZ@t#iioN~$@4`7qpja<*6AWP3DcwIlG|KM+2`@jpGstsms(LZoY{2qhZve|LjI)G@KM_+Gko#>8>vx0T)OwEQ0 z2t-;r=3P$-Znvp7oVGMV&^+S$$Rf9hf4X{n)NOW@xSSj_CZ(R6G-3ctUw`Y&-jpIm zv8JTPM<{@1GGc(ud{`9Q=FcM`m9Sop3lLJfq zYDo(?ZRiYD_ZwVr72>RDG?-klv$Z3wNl8Qo2jbQma#(6K_v8NDRpw!+2dgosPILM- z8e<34ztY*MvRR7QP-zEW=ZJXyI z8*_9~=RfeyGP5Jrvp!TDNtdV1PCz}`EN05BkO9Nw9xf)mFQYr*RQVkp+yJFF{g8%G z8;wADr#7FEkN`P)*VB_{(nBz2l4PM*q*}L7(->?oDSBIe7Y0Qo^h+H^wOUlxnuq>J zfo>@Pv2N$dpD0-Y9qI>d%Vf2g+Sv(x)4BwszqlZ@G;^s#|4H}VvO@R zOcJ6eJCFmjBxOhkcjV9=lUjiQR8_35lXsjx@O|LvM_*G%jfETz)r1{kiB3B!$D}*Z~l?lRWRNy*INzoU_&v)HvqR1jm4U;RM%~nAazBrXm+J6b> z-oF;{c{Qi;8U?e{E~5k+2!aXnlG+A?1ZusQpNN*b$V)=qNErnqjw`xc>fLUNy-;?G zY%w4z^=_T5fv5L$)y9Ruwwl`t01t@@A#;J0|0DVlYBLSeRf;TBFon86x+&M9f{8-7 zvf7_rQ~5dq@XZ~kNB1mum(gEt(ZZQiRUyv>Lyjg+S?_(casqqGbL zX4?o^a{O8E9#Mvea-aZ=i3pdrBWzcK2+h%^e!No=l?F2K9Kf>@|6I7AMBf35>IiyIWGkcT!{pR5n*!_hLv!aF)Om}- zMfj~Xs^c{|g{ZUSnp5BwMbD>YW+ZhcANPv&89hHuWVQpDQDQ#n6J1fb@Ss4@dBO&m z#$5IGT!`GZ$eVS9`s!)%41UoaLM|q`{x+}!+bC2=dL1Bp2UH|^^u-UU?&G?5KM}U$ zYR*UjP>r!I*z9``wS!XB6q7|d;ap7n3lIp8G6U3iFFWiC&mTcn{wHN-=K`t znMK9O&*jFC>&}yjjvJAaNA59y#@jnoM&}V2!WMo>Q7M7Cw1OsC(7D{76i6igv zpE)nWNR)z@M*K$CzR8>1enMvinV~CF)%k$z-?_{~_P4eWpOQs@1Q^J60HCQ&EO%T{ z3BM_9USko{oqHgqT~G=T6TjF*4NbdQO-$bxr|6VOV?x1y>;Avnq^qCaU9NyleK z4~zXlo6xyz9+uSnj>4$a#we3R0FOXu;F-L2&3nF{`hgEt6fXVTeor@#t(Gr*2;P4lvf!n7lKm`T^_CY!6^M{Zo*8WP=RsgV6536iFo=6 zA^>D?68a-;g49N|A(BSsG{I=Tc^Gh}uJ&mrvUDZTo(Lm=Nr~5CbAwMF4;nxtY5jQ- z2kDG+aaA%>fN%S=pFIZKTL zfSo)EGr(3^XuowQukcl5+V@X&Y2;E$Oyh$jYW~Z-atZmmjokC7xE%kvH`F=(^BqBV z5kpvpq$lq8SbH6*t9u~v#G+w2Cd{!n4%(w#^Aav&uh5NkN_V~SAOJ?Q)9%;b8lmr4zf^Y%Pc2#XhwV?0<(308*u`lY*9rcJ6EDvY%n}hPzbT`j%y6LaJ zuG_f#p^a(s&a=f8kB%(R$&qLMS$n@azoJlt-wpK|`#jO3Tkq!Y(ogj(6kHSd-B*<= ziCezi8k1D`ZsNk(aHHC*=Y%iYp8XU-fscP{Pw@4u&P{&JbI-IRdFTZRAe$F~E#Wqkl{m+NSXiq5ed+VWlrZGPp2LQQ_s|jUzJ7hu1z@B;!RQLw;;WuhEYI(y zXJl{-3aS+O3e>_cx(9f&eb+8cQa8U`uwWHCdrVuAZ#QDJrJi%yD3;?w%RXUCs+(zD z(~~W;>Sxa;0e$eVTDcPXm5hp+StLmsyChjzUM>y}4!-Q^S%XCkg+tc#{p+X9dr*mf z+OT26w$qQ>Fro=q&qr_i_m_?l zh12Q0fmWtn3{>(+_~zUB`Ah5KaT3gC?A-~E}#k>9d`QfKeo^0diS9P5Uk+w0dV%jLdcS^5;UXb4W)squj}U=bB023D?F6An~)7&)Q8KPTz*KR@TFj!yK8 z7n)x!Txf;~^RAI}9($nY*tvNcVLuII-bHNN6{L|@wP7$~@{?HHFNWQ__Y*C)lPB+} z$LdhhJ_5w{@|7!E&=lc*VrTwuUZb5mvo$;lc#inD@<__i5S6%IX7vsyI7m z0her+m6Z)>m>6lW9tI9MS|&BlpCInL=yfxB*h zzIkHebhFOI*Qd~6U`?5hy?a$yQWA4%Xzmhj?R?|=kFB~EA>`+Xshu6K_1DqSNixV^ z29PX=GZp@j24ayzRDtbv1U)C{=4zJbH-n^|)Ypqz(143tH}hIsTQ{LH(10V~)!Nsm z*C$P>m1UeVG-iyqh=_0Bo`LK^&>vJxSy+U>{O7`b{=*bPpH;{$Gukx#8=IOKu-55N zf&Wf}i>{LmErpepm2%s^V9~gXR5;JhokxEE{rCEKgG*n2`K7Sovu~f~=ksjbxCg@r z!smy0`Lb@O#>PfmOf1^+0r*=XD=MjZv$!w3y!Y>Oh4cbTpumO=KVsFy3^A@ey%Qv9 z--X$W%U5M_4|@yPR|^F*$)-)dwPR&xdI>ejn!L<=;lYmbumlq@ ze1WUxGD$4LQvcYTFLj8Oionyc$$jV~Zf|eD@4$g>&e^d8(pwg=2s5y@kJ1{B*1Qb@ z9C_>3tw)Zrv9Xtb`|UX{Vdb~q9xlTG;A{7SryKO>FMofP4x(vu%gE#uF31YyU412I z?DY~XiWm+YntP|u=`wVE z>nZ0Iaqr%}#<+JGX=$`{t*oqS1hF)95triLzP%hPy-Sjx<<{3ccwba&xOu9UP3Rh^ z>Zc%_BZGYh%d30ChPf*|b)UDraP~{mqW=2w_rlaDc6N4_dVX>1>w|baXc=ch9-q*_s&n*RNmiDqb*h;_6R7{`m8r zy?f24W;{H;VRTmP!-OwW`07^t@g~af`ip*Ev0z*r!otT*;u0Rl$-lAbdCc;%D=&k5 zIAKfv()nXD@9K0ccYTTv%zK@MW!Kt&H15B@yXeig0)PKw{$E}m|9qAeeXd=*h9c*e zi-3b~#0pkct=Lk;CIY?EOtrHelPN)_tgPIv38_WQF7<|&m#VSUlEsU=e&7p?;m>zK zcC5y3@7}%aHC9Hz$41$Ds5ISEj-CWPeDxgV;L8V0O--#_x$+*GiWwA|QqP6LTB~~X zvE8sQZasLg15nan0=+JXz4C-+>U#dXg&IO5JLt06E_}A%Zxs|46;C z^oM-xCxKLMHFzVZTm{(1W&;l*td3>%RiUAwrbBPbS$7p2KgWaM!~Q}gEIhS+Bix(q#z{PfdL**ay{5oo%F(-69D6O|!!L#Qv@ z!osRMckc8Tw+%~t^Tkz_eUrX$v)Ff?-i_K{10(k=SZF6`bp?U}L4heF|MiAgPQCkn z#R^;w(0A*bH+QiHyMeWF4A@nz(+R75B?pJzwe4T18e*VX1r2}8%FeF7y>}5E#aR6H z*X2{=gN)ZtzBb8U5W5(wZ}B(ZRN=-Doa5|-cwK>Tc__d^(b_jX=R{3^>$clltm^Bp z^{yWC>>m=(}cs4cX7__C-fAmbi$59r%Vof>AwGh^jujv@apr1_57u7#@T|p?^t#n zHZ~69ih$p!r1Ygu5bB;Q3hW^!xy2IVOIdWJUp%I3d9dd5HEwo4Elm@0le(&^*Dt^P zf+`RJLd%UVuzB-l<~>Y8;mLTfaVN0WnEu=>DN)gydOIa0PZ-A0NUq&SQw0dRdh+DS zB$G0ZOGYGd9R zrd!aZfCtG#`tG~$2G_qU`6~-BFg)yyxC_jHxTK^cE!|3=3=L2F{rfKx!o$`&R-_ax z74hiN9%(RZ1eXF(qe6Z7w7DfDv@rPhM|KZG8aidkBPG?SmE$mIZ0>+2!c(GJ&6d^J zHkgmnef#$9%N`z8v?{PAZH6t`O|;vMjG7^Y*o0i~>9GjN53j-!7Iq#xJw7>E)X<=f z)XfnyGqdDcU$YtU(^Jf2zfBTw%$a0U+?DTHVBc%ABW}rF||T2 z6K6LSShVCch2^N*%{mReJwbFfwlP>-Q^E~9Ndb&X9IUXQv{V!an|`@&h+`jzjWWHj zCT0%`^70n{`s=Tnv${Gu77#Uz@7%dl3`!i+HsVnRqtyhbv`*~Vv!@si%qbgN+Y*>x zMo2CmmpJ?OxRjKXMaJxivr=zwYPNy48CZ-ld>@*cfBoxUH_K!ui^Fnsu5$UI{YpwM zWOeuNdn`XHfcRikl9H=y4r943Cdt6VCB)t-SeKD*Rz}D|X0Pj_&w~0a7l0)>(wl zu)U6%`L>wOPslHSKhj+rr!x(cffY#47AYR{I!Faqxdx%!dq)KL`KCAjwsJ193SM zg$D&soH!wp5tcnJKS@WkHEz;Er6c^j8m>5fQZ>FoPrLni=TyYo9Z!Jlxuu?%7wu}0 z6cCVo;?!Qe4hrFJA)~^TAZ$AA(<2_rz`oZFYhrb^!}@UBbR#8~&h?H5);IL~bG&9S z7$T@yLa>5F^F7%UBRDm9mJQxg)#Z4deeV6q^u1Xl&dt3JcGYbeM+(<<+o}1PW%x!| z^d^TYDb_k^*VJ@PXs>e03ybIr;d%2`I*^aib+S+WNpQy=20mq-Z)AOBr&L5Q$3+H3 zCQnt=q`eq0&;D!E)1TWYuv(wv;*>rB#DF?O=g(^re*5j;jQ(a_*fTzG-HiA`V248T z#O_|;dPNF$fSeToG_m*8z^gXwszy@d)n7rU%$qn7CLhb#=- z<)a)5XV}==#|Z{-5s_ztrTih-#=*e>JbxG~63MyqW({Y4<(Xvr{`-v)79s3Aw}et_ z#!m|x0iLU!KK)>%uPF*jWoF#5N@t4XUs#7T)c3%ji3!%H+lC`n#U)qx?Ac*@szjHG zVx8ckLmg!}RaKPj2A=MZM0Iknx^SsTkng&6>oOpKx4wRThwunmy^&t3i8C=cIaVm? zei?uM`DX^4szTjNLF}~d!ND(Kx_czGr`_la5&^BZLi4VjO4 zao(hApsk4XA1tNu@o`g}A*$KS$%x1h*KC@KPi`vAt7~>}z?NCcDWii9N{K7#n1XQ) zl0NC_>1NGY=>TelM%#imr6jc3)<2iI#=^E$L`02A+#@|RvtigF)!s6qjBz)ow)b*X zd)=|RdTrgH`k|tlXTl6S+qN=HRpOVo2J5KEJ z6L{`)?%aJCLMYIbx$)S!YG$~A4+DZS&yF1r6!bZq-~EUwRGrjYzI-_*BZ&eYxpIYypG_MAoyp<{_C*HBGODeN1}h-p(62zRr&XkQ zbH|Tpdfb(<(gvN~-BG{%@+CxA@1oNaL#CM%hD9AoTc%^9;=g*i3$PCQ4O1_wW^`p`$OVXISsVRMY_%V>IZf1oAuR6 z@~gR6bx*p|P`f|*jGMc&rXy!kQBgtc1*g`ckcX%d@aX91&=})Dg;Deq+(&v-$n2B} z9nL{@0)9Hm}CB_06QsO!_InkQ&dmziik55DDi{mmy z?82yU$&w|>2pu{&I-220A<2br+uGFHZ@t19@cT%JRo#!N_bg~zym)aT+kn@-xZD}7 z{A-*Vpe+b?nv9CD`zcSf)&8=aZPyQSs9hSs$4n;EFkZ9p>MikNk;8TD1Sw>IOO-${$!K3C!P43U+yepv9w*k(IlCL^!>Bo-&z>CwrDTZajv^p= z1f5TW(WM%C@7^{sF%1ZgRS1O=FZ>`j)^NAb(@5B*&K*AB$#%riF+)&Ds2ZLMYqDCy z&h$XY^uZ48>q5iBNgJ=vc1nfE7=@2PnZCWh_N-x!3ZCe4A8{p~RjXHbAs^u{X&>vc zGXpsSOUGH9BRmTnG!2jb2>cg1hJ&yP5iz^A`v*Ix959DTK(KA??OhO09T9!NwW+3 zYTSjhp$g_rNSS$M_HoXkEAACPEKwoXljier$g&^0eOF}ZIB618ER;b&u&D( zh<*zBy^NCdIAdmUS9Men`+}vr6-Q1$+yNneKRPM~C?rB2cL`vZUMCKOW;IOo*gVN7 z|F*WbP_WKc!19=rrEZ92uZCj(F~)SZiel{JnRMfHcIj(uBKA>#q4&TDD~<+9Ww!WB zCq77PNg}HaJScMd*Vm~8$Z}kA(ASide#v`_lx!9DShCZomB|6ql~A$5P@HL7lzRTM z<<~OP#|H75Nshy2SJ&=*RCna1;hYqg|3Qi~{hhy5>904YKi%`p%kS)?$V8$6%l$7y zj(+^+>1CfQd=F_ARl>cALNXRg!a>-FuhxXRFT7Z`{o}6)fAhKi=B|q(O)}W*WbQ1T z+J$1!-g9tiGn5@8T%vuy9+sN|Sqpf+nH{gr)3LyhYOnj?dwt{iy3O%K@IG^#AuRPQ ze+$y9i#2F(_l@v(&PpGD?AD2wpUW{HuL^(sLovMXjR##Ffp~naG(@()XWP|Bm|wtK zF?1&gEClZm+d5OoI5Z0hw0mlT@v3EZCiTwUyQ|pPc;TZoA+IHW@ZfWl*dX@a$kyoe zD|wr@Z;vP*483*h;R(yK5ZPS45N&8J33_qZ)JBvC3{#KO)9qNtQnhSS^_E$*30>ad z(a+^iGYe#SGZ1U9j9wK9kOQ$0T1y+=#S|Iy>eKu&k?)5!Ci7J6+by8;2)?I!u=-^O0V^_swa5zIS< z6VS`7o#2HE=2bP=(t`p8^R{i=c&E{v02s{47})!BK^pCy>|1$Zh@)ODqS{fSwpQNn+WUR$h06Wy-DR=ogdm!L-_U zH@*4{GdyTOtY#RKwVjtYT4DUd_5wOu$;ujuDzzO3R8CF~L>(2xk-U9;)MK*)1KAXV zrH-L>9>(Uwapm=}aZh3Oi9`l?c^Q@f2YX$=ejCS{?jzAslYk1N-cZKV02lfGgDEH> z&mrYj-`Te$*{oU~W5+kRN>PFX!BaR4g6I4*J{D=Vui!RQbcp*iFgJNA{mKtw3`RA23O4mCW8+U2@^_MBl4di;NvNu-;*M*=QkSbBOWIP2W%zD}iy5QLNIUpI zO7u}w;i$^w=!}k(nCTc#J|{8lSflQlgp8CP-F}PSy^7*yZYE{UXR6q;os|ndZq{d1 zYd(ItZ{dS%{3I{0emEEoWd1=xD2+B3;x1jdunvZ4C1@SgmSs15eNEuV8R3kBI*ytO z4dTR+b~gv41RZ!|TXyVtZh>`)g6@yUIOZYSUyDPFjR047_cN~UG)7t3&+KeSjjPLX z$xJ`}>X$lb0%st!h>MGhj;1CBVO|y&U4q=$+wn_39W`$Bm*P&lP%Y-nvygfAoJ9NRcQgO*&Qn z2s+HnrFX6xi!rl-c_6%`d_A(>W_vy@$;Y9++r?r+u! zJ~@~$gY&H}qRAzVmN#uv6DAK=PO`38VPMzaY)LXI2>4YlB-oA%S-jXWh%~N$W1~*u zsq&4R3UDH4MRHdnCZ6&*+-#gqgAI2Qx`*GEKgQwlNS$qKv}nD zLlYA^YWL?sLBci(O@PPiWMy;Z869JMC(Dp;*iDqaUNWW01BDS=cA4S+uYY;Ojbn(7mdhVvp5#lZN`_#SQ;0Z zoASVCn!RoYv>M-{xfjt^v& zGsaWSR^@Q4UTw_fM~1Y*ILWA7eyF78b%;Hk;_^e-!7C!b+uI1DssbK-W2RbrbJo># z8me|`l$%Cm-w%eLB*(@RMTPb2kUqX{h&;iuuq<@me=Sdg(f^OW-Xo;XS1jUE$9@bw`?q1 za8gQ?{dGw}!OoeP8Dr4ayqo%c<(O?tE4=qq+iP z$N}o%u?Vm40H*jvtcRw6k$EmTaC&4_^^@z6tyhHI zsYKl19;OsjZ!R%_Ma^ki;xwt_?cW{;Z|6cq4j$f<);KAKXaS5h^#-eh2M>n!);lQ+2@0OYQHf_y zpY8+9aZE4EkRDA}IHS%8T2 zlp^q}RapOL743Xu(8 z$XSwkK#pA(mI7LP^yt{%vS>}uJozwr1u`PB7N`Ie41#a~V!Z)4nli_ZKirmp+Tbz>p+J9PQ9IMXh&4HV@f@&6~NnP67JcjfTw4#Jz;c^K-JREiBcv#HMc@QKQE5lRYQ;M8m@4$<#O3B54?MK81Z>D%`n*N>kPQ3x?6H{ zbs6hd{0BKwZ+J5dP%^B@tj7}qBcV+IMruCT&5VB2_3G6IeSLjw&26}U9m!Aw7jZbS z|4E-z3y1-dW8TBbo)iTKz=&bU8j;}qj&KM`%|#G}2Qk0C86kViH$Cay9+O%(WVCsG z983DKuW2jmjDwQ*nuXuVQX9^Wt&g$bz$BZ(iPg$=&8DOv&XbDF?IHu4^>4IXXCu#f7}qOw$ivZT>4ZL*KGNal(9 zM-*-UIX~(dK`>aJc^1J3`ECf8UgC#0%jvsKC!>_aqLXy5d17$#q$+}Xu$gTvvyfEJ zz>zGxU}4{sM`yXbxqzqR6&xVULouiL2UQNe1f7hta>&&{fvmvkplaaZ?hTg#Qf!7P zdnZ>9x72R{R%3TuyIw40W(mX@xeA(^bkS_Fo0v~eZm*ra?A09g)soe3ap=HE4=^?; z#i}V1!EYK9nA6eaf39ke?G2Q+4?3gY+nea?tKqkIKOQW27iV!*OvW4jA!K5tH-@_4 ziGHI+*)`ct_R@qG%xZ%jTESNkcO4%aI|ZamdM1`O&f*%y7?e%N^c=H;W{4bvs(<#7 zo^R|GmcTe5NVPH}lW22BwvSr1zV?em`=}nRk&oSghTVwu@qZrn)vH&BVYtlj7;*0! zXdktBcEjDFp|iSar+-9ou0g)C8Xyt-RI{7Hm;clvr_XF%{Z|&?f9jG=jP*D0@YQ_~ z1FDd$^JX&d03(sqhzF4!f!z#Oapl^zr()btrRZU_FqlmI?6`SSFGWQ~p?HU5V(?IO zWr$d+aIjLWTWE^Bqh@g~BMS7=UxE>tVo`So%7qJ}J~-HNiFS-qDT6(T4(nB!FMLrW{(^oM>_lJx$X6;_W%w&{i*X&FoHWZ9?RPIgj?JmA7tD z^K>X*dN0w1^U_Y(`r)PLj0yroG*m4vY3BW1~T& zJUPsh6g52sklto&axwt<4S(uo zMNpnO(NvBjtZ>B0L9I3j=Nm#@o&l;MfG-H!Qw%7NSPIT^mE!SGm0GC?Qq^=D8yen( z=)WG#hcrh3Jta~=Z{keQI%*>8M*k00k^}5`<)G~UW9&`9a?acL|JxYLFvd0vSqIre zlwHio5<;j*3nD2=QnaZVGq#jn6p4sRC5lingBnYVbeD=OsYc0`qLluxD?Fq5KF{y? zcg!)KXH@rne?ITewVc;^o>xujSaD!XNJ>hQw&EVTlmckAXRaqDX+rrOWNK=?ek z9Gi=`?r%_EQ*yq!W?TepfRkAafX=_>xLx6AxY-J2GddwJIsPFs&ALj5Iqh(XVDVG6rGTzdt)fS{=2m_~x5}R{J zTrRB2zp=^&txLe4*13cb#|IB?9$F$;EVHSes;bvufz8cv!*J(DY%ij|G)9E@tZ=)X zivg-^GF?_Wggn*yRzKA0kkNhnti4iB&T$J{k}#**H7xv+GV)_jaNo?pCnuvUIallM z1;al1C#nyQDZQp=|7Mk@Ny>r_8E%}V0G;{f>Iv<>K=V9$l4z#y+`Vs~W#)UCk4mj5 zAi~?GMqtf%7)J^6u`ype&mQ5t5SgjS0?!2A-Fm+1Wdmk9`q-0~5za-W?HHH2DAE!J zq6+euPRGWZ>)ml{7h<5)YBO$L^t^xG#lOjGYvrjOAGLyzgan`ZcX-|zfgP!0{zebLqJmjFQgw_+h@YW(*5pz zEj3f&5$bcTo%w-(%#irY;WLQP~*opA9Iw3PR<`+m(^R{KX6d~*MGje z9?(7Q_V*7b@&U0}gCSZG${}t?0bl~R_3PU8a+bREjt&1vNtuDj1QAOnVKy3eIwm;U zwz01^K&CtL?%mok=uU;B_(`8*^~bfk^vu-Z2esUc7u;iG!;BP;b^y?I07dVtvv^{!qRQ+0Rr22qQzw-P=!S6Q^zXp#(#O%!J# z#T<{iPo&UYrA7n8B^7;f9m{ZlETU-_JQvUw0O4RKvER1biQMWilVzFzz!956!&-)C zdvBChgh&xfovD@cDF)=#eO?tO+-d2AVZ`Z=f}$$yWFY%AI=V1QJ+X9!Rte>622S$dM70(AZ*Lh;>1SnPwWgX;~!J6a&x zt5>y~wHq|Crd-o31UdM52oZ0c5(vi+@PJ9}+QC;@y(L zXzUaZ`_7_G64Ez^kq7M#_g#6x!5Y$xnVhp)o~%ru#!id8^0!QfgdLoBvZ?L~kFCx1GRdjC3)DkqrSsF}qZ zvj=1*oG`emQ4YlhG806)9(?-D8ToxYB6c3~ppEl_g({r9DZ08!u8D@lt#)zfQYGm- zK;~ceSuR<79mfU!izC!fRC&%+0uojk&)^oSBS?zKp%4O6sLsImLj;+|#_x)1gIyxH zji(UjDchG3)r$DP7{Y!dvN^_dQ>IThu(ON&c;ChaKKj?wtve6h$*kG4_Z&EIlKT27 zL#8jUBm6bka1I|IZrx1;<8Ix%w`t!#m?nC>L{x100!8ac93deb>da{WK7!v*a>r&W zb*eN)(L83TA_O9{+2qE~1I>`P3$-8+7_$iKHGRF^$5#{NMihCZ9ud6d-`L}f$fyiF z_XM4VGZr1IY1Bh`)1P0=+w;tq?UX{Jft)*GO}%`%QuKR}7?miD-u&5g;NN^uv3ipW z$KQTTZkF3u#J-wXE&4Ow&Kr0~A@WkxjRIrwG?EypRR8*TCIkNX*ZjL^9KA3Euiacr zq5evY!2L9oCd!J5unL4HrmCM}(X#?D9$p#L(;C|zAdNJ+mei_!(TzfLl4QfU_4%_} zr@@DF>Za0_+gtbg(8c9#XZ%{o%#$ppt1bUPHpV)Roa`m z^B!6aPFGe%-E_6zGvukIYI#`u+xx#w?C_@3?eT#PExgtS?>HSl%Sy+oK}+@Vxz4}4ZeY!s(L zA}}(q>e53sbCxX&mZ(JwCSjJ3kU!ueZ%i?>DWQ@$C20(9UMBCZEc4O_eF_Znt>{8% z`(gase7uS)lk9rcNdRW2vX&#aO@q=Vj6(+2+=zL7)zp-DN*$J90k~vzYQZne6y{y7 z{a|IONY;|}51E8{SsFheryg8K>{~!TW>d;?OrL$M?ZVr)t)s%8jC^m0(n_BKiUvzw zaZf+PzJmtM>@aM_9KgSm2BC}0&GYl}%wNRF$4d?2i4>)cd^Wo6{Gy@|2G`f2408BM z!@JcJa;3&4_7Be*B|q}{%Og${MUozPJ*s}D_?j4ZVoNn!B$9CKu9-O278l=$ww&t+ zW;oL6^--zcMIK3YA$$%%9eiKZ(RAnJek?}*h-v+0vt&7B`A|OqA+d+0+ziIcO>7W) z2vk0DT~&;bvVrOQ3>vh(L9LQBb*reX2wLQpWt8pIvgz_vgLX)Slr>?8y^%RhhnhPY}L zUG1}H&(ljEiZ2PXMwGuI!RPMKpU;^$FDt51qs)VcQWCqYd-oFxI2mXNuZn=!Ny{}R zVd$r%r^MBK#Hdj>(HmiF;{njPVk2#n$Jq_VKWO|W>8M*b3AdjpQjGiMpz?M!A)Mqt zd7?4zy{nd#&?`0P*6Hbt#`q-#Q3MH!Nnch35=610rekzrb+8DjQGmk5c0vOe`}hM$ zdtdXd$bHo&O`0T)k282d51r}Lr$gw$hSLabBbbky&a4$H!bQ(TQ4)v4uJoJuC7tBv zHETau8+al-yGD>)`a9t$q)9T8S_{sg92J?vKWzNS*Uvp?D*Nd*M>1f*&yMSwN~0IBI){_K38k8IO8bD(8^0C!Fe4s%oAzkQL( zk4ujz+f#^=pSrd1Wq$wTA{|3{E648IHF++%PCWm!Vj!rP7uL}fiL1p6*P&p%SQ3lWM1&%8_Be{B{etm~?*x3P&Fe?Mj(*??t zHBD&O9>lD#S2Y$v;L#1=wm-tv5xpdlOL?iL+LkfQ9HltOmNBjt7YblwLpt;HOYZJc z+R_lDL+TXgoUBMnvjDY6l?}Ywe0pTK5K^u4xJok%Fx-d15A({w3C3s2-?@qbYk>N{ zj*J(cdZyCfZQ8UQ{8b7^P7oJIA}J{GZMH{!&!BDT(!$Ucwg6?KvJk=eL2hD*tFZnc zg~`*Qzt`8dFK3D5ZKIPxj3@wQzlpWe`D>MTn|<(}|!|<+EA0?nPn$u6elG%4lJ11BC!4 z^uy4EQvNpS2HtKEsXz~^kWwTi%958AE%@By%um&EVu+f{q~nWBBFbC1q&ts-oJdvhx{{BwTZ`Q+il0NN6X$%3m=Z==$M4*Z5{R>SE~s^fX&@XWS;7+^e; zT;-LCFh%w0*fou*XDz>pS@)AU-kUb!&Ilg^WM}|-yg01drDB04xg^?1Ur+CLfZOYj zH*kDpof~-9gsPjyM~1+J1L1KIC?%6g0=u%Yxivm&9O@Gnh8WuO2?TEqqwtBNXOYnM zI4{pS-49|GsWghyq7s3b(M;LTrTpP4Y~T=@<;goa*AWaVAPnC4Wuo{yym|)qA(G&D zV(&Fl%q?W3#h${SVK!tyo)MB83GgX2{oCM$+|4=q?f?-?H)XR%5Z4B`7WbX%69F`q zCx;(yRy)!KIb&bZY^It*m7s$V|91%VR5^#|Jzs?}f1N|Arlu>`i>^^RMpQ@l znLQwijEghRkO(3G2xOQevI8nQ1G-|mRL0z-B8mpdx>9>Sz;>ibvu0r-Z=p*S$yi0` zpwbIst)fpIqlsjhDZcJ^-+h;)REt`K3>92HSnmU1foy!yEV#|DgXq{(N1E({ltU57 z5sSF@fKlt8<2he_sHW6?Zf`z=@3*9-%@kH3SI3wwE<*}bokY%1H4WKm0w<|DHlZ@h4LpLL4NMNDNP{dV@nH4$-vik%;7 zy0we{z++_8G~6sTts2BqI&*RI5OvJjwSSA&0EC4KG9bkH<3*o2qtEp6I;8FJF2q0) zS$u$i4q$c|ofYogdx9bsypGpd%i8?c$>SrcGf^AD}b9iBQl zn!?Vdcj7|Z@H!cLuj`>luy}NdG4^@W1Y!>IH(^<`hrNUNXRUA3HP4Upj83;*90y>W@>}sYvamD(NvZmp^Bs0Mx zUxQ%4{SFV~NB+$brY;mLIk~UoC8ECV<6ZYg8wFus)Ek73TO*S(3=mL09f5sT=b|sH z?K~9$x(F_mAX}?nKK!ZV{zXGjaH(CG&`NeCrNPI(TZ`x^Mw!>C!B6Z&J;TE;MGw+= z0bz;K{_Y=~1Yq6Ryo=C%($m_;~$l|DA2+GXg88t_mIPpXQv}vZYB$)|pAfD0vJ7f`sQFXq!kV0d}r` z?3@%Q8X<2NZWv7fC=Ev~16v~bPze6@w~c2CeP9gh1~k3=SdJ@oA|&jE1|!IDq}gbM&-Mt$6yP*17;scE|G(7kKdanL!16|jdg ztV@?JWAJ?xFBpO{771I`%e@7`4fy7z;jk|*WcYYV&miwKX=HF2n99&m`VtwDkk&$( z41N4r#W=r5G(-d^uHgwDV@k((Nx^N~_GA<;upTzbyU9CCKeq4TEuVkcpIml$OiYq$ z8&@-U*57mC0Ss!{b&k(cImFm#2pZhBb}FM-FiF}gFE1svn9Ne-1%Ua}X)aCzBYwiI zKK9ebs&#^D>J|Z1po-BD?EQ#etVYW$tP%mR1mIlo$5#y+HM)6wM?@{Mtw>E1kP{4M zq}}7+((c@WEgm7xJsUc#n92L)`p|Eb)Zzr=y8o+rKXHjkdvE9&2BG=_AfgXX51iE8 ztNjoGqXm$KFSq<7dZGyUTb%l%F(pE7KPk zOipSYwB@qHa>D1oiaNGe)hX&0e!LP^f7E@v>OqQ$#v+5}teQ;I-Nuj}p~0 ztmCC0yMKkk@$uh(pP>HdD%Q50Bw17heF@-#COET$vFvK=83LM@*Lv(dtf>ooT8Q7RAFYg4YUQ#Zt~N zs40iJ^ht-tn!)|2o}D>m$`-Jp4zUk}|NKpwloU2iJ?q4&pJ|pvHP4g;E!;605fd4n zVlEm=%iRwm1R^{xsSm1!xIFm{3J-%D|KNX+0pwE3bQ%}~S)%~}nC$*>F0mGA5o$~R zKBVV#u?07NP!|pKC{E%@mT`iZ3o()tBSn##I=x;jxUlfYm&jBBOQ-fyOH_U$Tia3e*Q8V0uI6^#GmPX zsX}?&e5jCRzAC{VKoW*1A?@Kv!~rJ}d%h~eQz6x8W%XBg#g|0xEgQ_~j%2Y63<|W;9WJSsMty&!+m5G9Vh!(G&ntk-8*`v>IL;gc!$`alJBl2C~ zxtNH&DI8lfuU@@+*)ev3z&dm}jQVeWN*k%&zGlEdpMb~mq~T51019l zRD&{#KVsc0iUe1Hz<*#`#bt*Vw(jr!NE*fZZ=r46<`u(gDh}1`xOmZ}buTH3je6Bd zTG<^x9b2*pwT#$Gf?3P4RWrFT2QExe)|ulU>q?#-KVTZFs(@TL?fuF?GmAliGuVTt z#C*KxG#R5sO|O$VqP_vv5$J;HfT&i)2UgsRxU3>`W`ETbpwUmcUZK*J$tsV3x%X`g zeFFm-z>C#3{fELxVZJr4yJDQ@m46IqnV&ioLvS>t10+|nQgOWe6m}~@Kj5$l_wvOi z-WAA(%3P?vaowYwYqfoI#tc+b(-%l6_{fo;IWZ0yXR?+93e=jFO`4Qg(CjNAg?Yba@{HV)@*!_LtvWuS4)f)@i6UORn#Zy2vw zLfA3XGzHu$_qJE!`E0B_WE$t3H7Z&2T1A6@CoM+_Xt!=n6aFmWQH~l51KQBcT3*}I zhVh{ksbI(5L5W;yXYJ%YNrqe1^|ztH(MjXzhwe#4e@Ly3{B-c(wB+F1?Vu}Vs-3$h zQ%RReI~G3}eJejh*7WXBpNQ!}xp&%v=%g~#snZkM!QoEzzV6$w@CQYdTAe3HBNSj5 zQv*tX6ATl?S&y-c2qU2{6P)_+sL)&0cFV>eZ%kAx^z*Y;u3Q;-Zdk%!7H6(ooH4mF z$1Ag=Q+WA4&ENO#3+@vg{UIah*g==f`UPziQ5~n(xfd}Xxb&PM0Yg}+D%gTR4sv;# zk9o5lOYWgApNwBmECeYj3%^Je*K^vh1*sHC%p30Js?*<&9yfboczs3q(mJ2V*#3*o zA43G`&ZIE}9Em0Y3-<6iN6V+V?2vRjSGxJnUzr>KUk&BQ)`~9vQ$wk+Y>kHP`_`=k zQQ3wt;sVr;C|j`$@mPj{*_8L3%y}sCQ-KdvR`+^X`K&wcFaR_9+hC)Le;e;EwD0`L zMc3L0V}EjG%8T{F!(aw=ogd{Hk=c0^6RL*MPRrb*S-*0}y*kr<8O5leHOzy=hJm0L zeERgPol!8aMhqV=MtSIZPG(1dzy!W`6KTo0YOs?c%^)e3W9;(8Vin5t-^5k}5)!>@ zXnEO_T{AMWUYMPAiFR7|bV}kgA7AqUnZxcx`>tuWzu0TUrEra*%{r|&tE}i)jFeFz zj$5~GjV#Vwv%JVYSvj;VQC8|0{c%x2ZfmD`n8VSSN-TSQl)byb!d9xBCX`)9nk1ZtwZq*DxHS*fsjNNM_I;f|*D z6uur`+`NDtIM*(RytIhd_X2yeTeWI16IxB$$3cARQ)@;mV#34BF6F+gEsK0tR{t8b zV4D-=(9$0J87*bb>&3G*c`)P?3t-^_@EHrzxxA%?+-pbdVm}I#Rhgz3%C&YiO*2WEcB+;9)6>vnYja!`2eVIMc5SiK; z7u|UQ%EjHS*cWvCc!y!b&Z(GJ!YlLTNUETJk>6n8H3`sGSkd6c!9&bV_DA$g$xnZ~ zPczdvepXP>zBPs+ckY&k#+%z8Q$L|=WB&{q#$dKq4rZ_xuAZopwz1OR-Y4NtpcvkB zDTwoaW_YCltSUMMR29q9hgYo%$A2bOY&cLw?<3%eMcN0osoU!<<{%x>cHF z9?3HEeoD$THlG%F<-&EUkHS%5>)pDf}4`Io1y%gBeVDVLt*zd<1< zj~>0ccJ11IxHn|QoIk%`#*jtP^lE;>Gt1D*ER^eRLD{RhYMTG(EU{HN06=dvf>a)G zZL(u*NY1<3rs`w340mDd4NxRV3}ik{6uhJo>1wCUm|+P04A2ZC3nI;mPneiOErHos z=$N+{UiQF8=yXVb!lAdfw**duOprA)qztckQv-9Ro4>V_YZ=+xL7DZRx!DawZPK2k zrOmQ|aj0kW;U3o^3YIWvfE8U_$jOttdx$)SS&40Gyo%qW!gV8pXfk((Y8n?I!0FcMwv)RQxg?vY zcp@GPnCK!+HP_X8_povtmK~H3Q)h=g^B(nlJ76Yo6?k zA{hytyb^pfl(#N8!1f*hOhuyNBs*B$H*`ymAeoE$KjAF*Z;q4GdGMTweeD$sy41~~ zmQECri|Kr*cY&nm?p&QR=I!6uHDm(fTUU2$rwDlZq_D79Sf2*vN<%Z9oj@IP!|vS? zZBE&oGfSw_Pc+LMc-g(`_1Mw&tE*DnVo1b2fXhCo}`8BckS<; z(@hI!`F)=0Wy(x1*lOYY@i|rJnMlrOk=$*ur)R3IlLititWxgdFiSJ9q?h)WMkgdE zCvyvyVLxVUV!~kgxJnMLWEvMr5}qszar9j@z)WX$%Eg;W4#AJAtwJKtr**uDt)1wy zt;#wOf)3als(4<|D8C0E(~ps))o#%s3sr=^KL6=cr=<5zs#Gh%!NEpnEW9L*GO@Uh z`T?T1Gl4@~Uvv+S)JM^v&w~(_r$0Z}%-?LM>}r+qSvH4E=#7vdBeRFn;FjI^R$;lW zPK6;B+YGyXBG1IVnarmR*4QvUP`Rf#3+Q1ZW0zA$DC5EKxDoJ2xQ$q`({eYx!I?)O+w8b z)e0Bl|7V|D9h(%SbxCDpLqlJ4&~444VMm*njVKJ)Xq%aR?ArNDtDm}xxuEaSLxzJL zpUuUA0)Rwc@HNs}APu@1uHY(~R=cG!5;&K}`VQvki5u2bEfo?x(O3kkkSt{^!uuAA zHdCaj3Hwy4>jUc7E!9tL!|#ntFB5*y0m45`uaaXO%`cMAkMRX<{30OzyZcGy@A`#{(M#l^R zgruvJhKn>zonZOF^XJd2uoxxqy&3<3!@~q+zG$l;mFq=MK8N-*!^Y@F%6Nt1%N8v_ z7x&T6JU);Ra^F*ktj_s+3|B)4`G?21H?X_=tcD(Y7jsLM@vieaK>EY;A!>gsiZSEP za-3x&x->e%Y!&7xOoG|4$_TzeqGZIEHvVxZ)rf0aD!VQUO$){aMGO$kQ6U9#`&fLS z&qRe@EnH_lyroRs5RRQbJs7LE;u-FhyRg)1dyQ2CCZZ6e7z&cHQ?Fi=(0g9&*jIDN3Jm2%Qn7?eX(-A!m!WPS!+dRlk`nh#IUCUoskS z8qH+t(dyWpa0n*vq@WvskX&C2)|U84%ZL|hkhA_JTU!({xNe1iqC=5Ph3|*Xu;c(+Sx1H z%!UuN6q?*Eww+mUyB2U?k_ZykT$vTiht7|_Ky5yV8#e^pU4t}TK=m5FhUQ{u`r2v$ z3!69lI*(pkD^+AVdyRbXhb-kTV63c8iXX-k8MEk8goFPDjVDskhWERpnrW4*xp>9U z@~V&&^^3FIi(i+Hz+B4$w%2kF8Xjzu^+z#{v!2;y>$1Be-rkJ-8w85&*pqHiRTsH! zE%q&j&>Z6Yytvq!tz$#j>DTxUR47q-N@>4q|Ng8Kkye&tBc6zbg|I8UN`#)+{W>lr zhW%`B8INF?vDwpe2%jj3ODOgJXbd1_p|QS0{rdGovn`*+TAeI#9ox{upl8+}?aV=L zK7U!Nqog_l17Z&ALcFp+FkZ@zbvh&S>?Jqbau}~Oj~3Jg@hud$MCO12dz>Hr{)04t z@Le)1E5+^hr}Ygr?NJx`FI~5}7TneDt%-mIx?x691W(AjYM}{9;}bb{EE0v9Us&wA)_h z_1kVN{y||Qk_7I)byd}Sx{}ol{Fn`=O~1}-rrvvZv;RM@9i<@r$g~K)3?$W)AXA-i zj7_fmX8-_ibu}eQG4@l21VaI~i4Oh7jEO69Ao4$=9!rI_XB2pkJ{qm?+|owWt}$PQ zn<=97g@plnCy|5hO0%Uh zO0wJ=E8d;&tj{z1$MqDay5w%F7cl)_vN9066Qby9SuM>uAZ%5g$(^OOH~_;R zf}a(b(ID0bj!zLiXgVX^GMPajPN~J+clKbM&1crBOnEKLN_Oc|Ql=4L%bW*szp+z@u#r3%jpz!37! ziP+ex(`8_zZ@ALKW{-r?(5h1>HNT{ujT$wQYh6o4=hR{Q0$@Bz(yst@RCo{KoDVcNC>|Bi&1qG%1R_4sHKi#MB zLY%j){eeql{KsERW&+A4GxUY39cRu^eb&sGT_;WY;BIl(Y_?j@!pg5=oMk{GMtd%a z3M17IXTHC@F6O)CIWg;&`K^6$tt^PgWF&i|;>-i~+$DCz6^GA17p`Zcp*#@Vxv;YA zZZE~So7euq=2I5{5S;_^w$$XaIpDns{3o{4vYm!nPlUuW>?3OE*Jx2(4=|1ipo}12 z%e8N8r|^Lu8(^{MqmP76SJJ70rGsyAb+gNR>^j{& zyRtfM=&N`+n*qAD{(1Z_g`!NOAa^w8OcMMD$-)ya+u4(ZP#Afsv!4D+q(gcS>@r3Kja)av!A zZ-&7)9cJeGYJ&1CqxchNOJq7meDs2#i5OA?s;wr@*(Uncr~vds-JjkD0_s^ZoA4A} zC^?n^SOP9CA>um%O^wmjE!MqEBdq8mz0n2oN}`j##h^I-?{#Lcd8YOO%-=yCB56-@ zCqV@KEjT+9R#nmH?=CH;n0yoRx@wkNVqx0a^m7L*ZygIwO5bM}YSX#DS$u4+{V~;p z-J7-i2VpxNZ*H70jVrGF)=Y$3`kXeEB+x$m2Oj{PO3VxnFPBlrM0Y!3cWE3PN6&?hCyW`PElTfBjF#;#%tq97L`Ae!U6@imgquiFmA<)kFF9Fm7%%w_ zw_Bt?pH83efe;wTK5mHaTh$RcS*B@;bXd6x$m*#$Fw2yaB`0FGvs1i%A=t>qCz*i| zrKM&SmOmHAD00z+z)H1R&voH#%Q|U-f+6B48A;N*{ms?i33;D+giU&ZPf3Qp>7mil z%l>{>6?+hdTZx$3jVe1h)o@pEWS&9J=o2#zZg>-VK6yvbs$=Tm*3+k?{l7`{19Dc( zE_u5u@f-~>9CIp1l56^B_S;Ub94_MW)&L`ld212bnJh*x12JrBmo0OgViyX^dg)v*I(4 z9j@e_*STEc9_^ zz3`id>zj+0P?N@ug{ZQ4>umrzE1S1?Youf9ksfJ2NjNjm!YkiistKb5zWDZOp{U)M zO^NsB1qL~D^z;rtzkg9&IWu|mBQOS+kw0*Qpj09+D0`MQTN*v;4&{k;m!KR=Eg`e5 zQ>RXam|x&qhylD1uRsZg~hqcU&eaPK? zAYb7nhQ%WQw2w$yK{d_vgMzYOsL~qjh1DsX^e|T%JfR;ENv)R4B0#JYj~C4qRSagQ z7YCTeo_P1}UB9zV%f9Oc#fx`MTe_EDDGLH269pOF=Yz^8^I`jh*p^brSkeb_UQ?-p z(@)9`S^i&d$XaLTB6gL|VLZz)riYF&4@D|2TWO`Al7(nGAs5#F4agR9b_U8DU}TbS zXh~hX(K>2W9j<?*v=FQuWOGz&(9>6N5Pjg&c5qf;I(wgW;H>mUtbB(g$=}PawK=WwFaEk%TOy$=3 zHl+bt4o0s>tO_$(wsl}34Wkgvz(fTY9$@leAX3Y=OZL|^uNu?%+biwA)zjNG^NiY+ z!D`KoJI_pQQ@_uh(MJn{BQAzMnPq*xc-^I^+4@#tZfC;&HX616k)!H{f_WhWPlTNh zUN-iRs9jB(Tr4mw`A-)Tepht(Gbgil4k6+iavqw{0mep@>HQ9C|Y z#Rh#tr;vL=$k0~iB{UNpMBzb)!IY>Ak?Ne&Iv6)edEi)IDe%;~zQQEAVC8Izmbvk+ zBj6Wqgd|vcz@;eoxI%$L;>zrylhV)nZQA78!S=B#XxitmZA{LiD^q_vbf^o@-1dF@ zHc{<7=7G-r?oK}*gZ`BeSQ2u6Y&q{8jE@8BH&3e%}MO#s_F zh6n`lw9(&_mH_Q*T1lkKMc1CyY+k(fNZQf$Ve(L7yJk?Yqety7n`wu(%!y*|7JqV z_0as0#Sf4v{y21K+lctezE;-Ok&Y!sVtDkt*;}81W*aum?PME!wu5a!^tG&xE3A;i z&5c~tfPwo)ZrZ+SJvTKL0*T^O`}HbTIl9`~@m|ATwW9C-^tEG>zM)|r@|A`P>{iEX z&ongLNSJ@bUhga5+bb!@76Oy5sEJ4q)15f6q4w8+C%NDK{#Ov#&^YbwSh$wWr4WJ8 z;{`Edp!P)V+7r15jMc%yC_4}Q%-hOS8qu&v}-pbuj2=l17V#n zk~Lp4Nj-A-a6OuEIqc-H-yp`7CjB)r@U~N6o0&N)x^;VP6T8`mGx+(-$QXK3iw+$; zLE7ZWB?D>XviVF_cCV;ON!QM8rJ}M0A$`TP5&k{94cb4Af4Hr_FW*!xQ!CxIE|N^U zl_!My3Sg-hRiFn<4vOuB!g&3lF?{!023vNNa*B5;&EgjO?R5%LJ82T1(eQcw?4AY9 zx^-LPsy48GeEVI!qR*8(H?eoW>8EbqP|=lz2ao-h;g{K>ZQBjdaae!ZY~8xOU0%5p zad&Lr?!kn!BMw{RmMl>XH$ML>z*K!+r6ZK*`tVD9@WW1TbDEeXYJB{N?<5l19>Xn#ur{r{^o=XiQ;6Om$P20955~QN6c1{v! z-{I@3hVwkyVfhjZjkZNgpB1MwPaCV+yFykN)b87N49GTdR0EMSJKpr%wCOSsh$q*LzTQ zT~cfZV7PpL(ENj(j(S}=MmdMKcVKY6m_e1+Z125HJtstDx{UgE*5x~;i9Zzf3MM|a ztJjs*?f1upF}^i!$6U>*CcZllhbtc|jIYL_wLTUeo}9BVx~a{=606Na2R+!O7XHL5 z<4D|-F5S!ut{10fx#&G#xz{J|mDY^;cVC>iots@eK=b|R8Tsqt!>hIt{iedyy}Eet zypD%}tQFgN(z9K2S;oBtYS}8hEOgjj$QHWEFpGfo6J3*O2^x4@OF;&yt$osaTbHr9 zRj~vw9Wp6)XOo%eE&(a#`3@Ui;$_ANWidXsFmGX#PezF>5DMbLdn>4kA{Q@SJQ<$sbl!bS)zIAPNl}dEp+;5(Th4{= z02QAK-cRj6o}~j3G0lYxaK?tc_X85GZKrS9)DetP)*OjHJg+hpJZCgT4%gb;p=4-b z@Txs;Y-dBdY^``r6`7+ZtA+ucpFk1bLnQj9Y17v0|L97G=mX(tf=#PP|0~3b9RkrK zU{_6sHTWX0PxFdT4m5vW=)6JD`1)hYUDw3sdOSoWs#h|Ko7Y zyzZhGzUqG*uA>=8!7B%;c)g>Si0jm(rDh};(n+&Roy8pTzCFs{Z9Gy!6ll=Bw7+Ue zd7E2_+S-oQS5k6Xe)&w*yUb8np&+#WY+`b-;vHu*l2Krf9zD8}nE)r+4O`I>P@?1B zH6-%CSC8~Ogek#SilX9THSe)>w;@II&#yXGao)?W{$&czq(ob!1WLD=RJ=DY11uCK>wzC!o4VDSEXRxVCXBP17qFLdM8!Pfq| zb?Ykerp>j89zzvul{m8rjC$RvoE3&Si?;PjSVJkC`gC>7y6Ta%dAAD1Z!sk}EbJO) z8MlF|IF#K-j#OdVc$xn5S2F6g{Iy56`9XO89=wMO@e4pe4^`Eys&UIpUw0V$b;yJ` zK(=-omMxH4Zs4W;Zi*c8zMYM!t(~2^3^v)gdfOFz>MM^6PT?8@3^Ttz7HO&JzI(U% zwwww$CB! z{(RCT8a-7*K5^@OIqt1_lxtW}k8`m^ zHs9x)M@L^elR3lc^|`d_P5oRJXqKK*eM=>h?vHx2ubUQLMasGpP(=cTG{1r9>4s^`x!rx_HrN#6U$0 z#VX)jg14I59(-svdwK0a`w6%sa^dT@Zhe1ndM`%B(~GC;>b7BcRzY`nvU3NFUc{`w z>SA`~rzmca{~ZQaa322FI6IMwt>D85{3qua8r~$}^`LtdUr)OgKqQLo(j0M2j{+wO zt@BlB=`My9p7wCwicghv!Kl09ExG6~MiG4}k59u13l0g{Ny+pVp;h`oQ4<@}0VpnY+hG(s~!kf2j*(u&STe7qNQdYg_m;e6P zn(7BNd+^Qib!W!x(S=@R_wQE~^nZ{O zsC;+#PuGC^2Ue@rT=W}eJ6UQBSRbl9=!!ds`PyO|?AbxKU9U$<@+ z_BPb$`HPNvyDll8PfCCG%bw&bd$S9pwVxLjPkF6L6ZF+MF_0Kr{brZ(rFAAk+QDId zyY{3>H+eu}J9X2$=!24yY0NBreN{31I=bdX?kq#Y!j~h6tF3!&KvrD4t6#L zMdZZ)d~I=h-7^;;V($H`qfiFa>&iCr)5FgN1x*xliiYcdg5%UNBM#0|y{;S%YU8?plwVC0o_>)KT$Ui&FjykHeMnG8PoGY6UCzkaF((&2 zmgm^Y6!1dD-1|{8jg0y_$CHYTU zok?Z7vHnGQP~W~$R6wBbpF9SX{O>YnpB0ZCj^~p9bjaaThqZQN@IADP;gA%Q%~O*O z=(YAVD4nD`qI`H?Gf&N&%*>WM%(NOe92d`;9Zz8DE{v%Vj@yd++h#0kvkY0d-jz2Y==b7*O_amur0x-LD|yxtnPRy*oa zd2D37^^OM}^Icq`RJzRH*F4H^SNIe6HKh}+Z+Cv(d`CoS0-s`?NslAX-8!q4J0vff zbe4Hf$~{S}_H zuIm?7!0UP(FNFsuTO89A9$=0Ng_zXZg_}l9f|9y*Vt1xe`KRsyp1vv0Y)&UNbbwj> za;7WVAf^9T#)Wh+^i zsne$?fOnanDWP(w#U9p$N%MMu0fxo>Z?NQLh_LkW;Q1Pm&c59x-@Cq6xz7m|^-nFY zjWd3EGz@b)4qvGNp-7Z&Gc}c^s{;lkY--v4d!M}PTDj-#dnvkVYAPS!W2A6|Vt$Kx zdPpvPlx#LR=FD=_#icR6PR}>T7<)|!tq9Z~QEqAHRZ%%I4Tp%Yk%^ug4r6ib>XPg> z#Kj?n8k%{}ibgKCjURRn`Qyp?=e~nsu77WOS^|VIJN7(|hN#^(ktuC61!C0o_Y18e z{j=`)mQOD4eZKtQ?aJ)Zgv#&<>+E|y$jM87H>iu{uwEtymrCnvKseBwJtepB_9C;w zt)VU>R3kK_awDuFFWFCA_QBC%?2+HnobK*?Ke}L@)i%nXFwp&m`u=GJ=5-MwY9sy`jTM2yL!!*?S2DE@DWSt?P>_7k zTI+aZNzYy1(8m-E?%*!acs&K=SnGE@0_Rj-p;Crf9|&034QpWgiW;zGL! zJihI)$LD7;%z}WFN z#9Ys_DSpFJ9@p5cp`(fGK$L@H*GpzFtgCmu+EoOJs->lv|{RZ1hA)IpT&|T z^;TBd#3n5pVV0fN`FhJq);@bP{fA|&Q7iR3{yMH7(f4K}qbKg&j-0)^E5b89ZPop^ zZ>%*njUJbIjWQ4D;aoeqra%gDvhWenB7PnfbsM{BB-VqbA&s-0wEKdPEqHzx9K*c39JY-9(py{YzhB zebJhf;NFPL;z)Qaz^&gf5W2=Q7YRuw zv@|^z9?4`6dMIO{=ZoI&|!~{MEuFy3kHItM=r$ z<>zArSDPLf0VPz`3n+MXw_oBNx6$y!6w=nHc^l4|6A%+~x8$`lWBy0XOt*N}90L`= zx{q9df3k4f(n&Uwg&*A4yr_AyyO))fm2?4~<_}y;n~^i<__ZNTHGlx*xTcfi(fIhz zaFHCIo`2#`X)Fn<9ka)z#KK$q>nrXu!U}N;SGq3i=h9XssA177(3Ru&;&UrY}cIn{#D?yT$-@nj?@#ENARi5zMrU4`t*I1wJ6Ky?T?WKrobN6Rok z?{?~oz9ws&B6GuDylkm)Bp^URTXNxkAJ!pq zR^>OzU7OczG|Lv}p0Q{qV&ZMOYN|x$^v#|;zf&d8yt@6X={J&?^ZrfUYVspiSRFf zM~2azUKuQK$f4sF2kx6}Ea77RGHx>eRF2jCBwMu|FO&3uYLW`u}X5x%1dt7`}%|M zXOq&O|FY-SmA!Wh)!$y6s7-+7pX}9fe57_L)@EEivd6tDuS<)T{Y_)r@gJf0T}~NW zv-RTh3!(by#g^0la>#vV=-sDfOTAG)evzrQn-Gt-WRD#P-ql#)*@ga)JC;VJQV~&8 zIdBA=sQZOHjg!$%UrkJ$N?pcJPsV=v8JbLos{i0k*0Uav3YI|a6EjWqcHa(03o!yB zi!64ymoc{LQFF)Qk!xQD=s)Ps2wMAU(L2bb6SSpk6jTd#hqOY{vCcbVah+e1R7eYY zIVAkJzw;EuU!aB066`BpFA+|`Jq_a8_XjxNOgj>PST$=t>>-@ao#sxt?q)2$W)l^ z5HUReSg;*N#0{{C9_&|h{eW-EVoG0wd@`f%F6Z^@*Dt|LPtdlT)QLTY55M~~COafE z*XWpqe~zat4tV1nr>w9l+?qBw%6F{QjmSj3if+#%vceX2(wR1GH-LBr)G$>9Aozh? zT`)-c;bSbdEOOPfpML$kRl&tge*wP^pk0D`sDCbcN56^(J~!TOkVOoOT!n@iY9C+o z!Nt$~>E^Oq?agw!FFRO!zRXh1tGOKTmu$uo~u}DK@{*pK&Z=F2f$;t0E$BemeUK*rky0qkX z(pUWUfhy70T&HOt-H|l1WgS4Q&nc%;3xqc9!^%>cLg~2-vi%CNhSR9;SJh99to}Ra z+(4Rzbycq>*=}64kmQ0vs=n-AZ-+lWW4<+;6uX zbZ(Z*HP_U5^_mt=T_VO?ExYv~_e$oTqKZG?&-e#ljBC3-16=A9b_Sr~UpHtFeZe`p zW9#tEHIGi3jTvE^{n#PH^xX~3CrvAk=*<^IPo-kxp>y58Y;~;q5b8#vxO-eO=r7%l ziD!?UJoznC!4sjOXNG^M1MCEz-CEpuhpK7obW^+Mt$E6J7@?Q~P%Ai;;1}fEr3SEV z5Jn2x6DJ;_0G&K_>J>um$s42o4}2E8KgBf8i-u_{1c1)6uR)H~MzLT}yMQ}Sfu@@e?>~Rpp{%@< zreO)%o^ucC4K_$BocJZM_Y6#El{3mf_92^>2>NxjktHekfC~s{PgArO+Kp}d< zX?%?`{06(?dLYICRLv`C5k|c5G>v%c*zV}Bdy6MSkR!1z>v-6YoA;h)Jk&zuvFyG;6B)hjRb3?=B3+1xvB%wq=wP3}6#5udWCtPeW0XXu0y?I8S@p zN;Rg$U@N^~;_YRQGXmFEILMO`b=kz%EDp^@IJFsf52~c8z$3;N9d${*iz&Uo8Ygqw zmMyWZ`ayA;9C)6Jn6lz#zlhjwBK>0Lp}rx^4LEmm^t)_jQ4jp#ClnoLM!ExNy@o-u z&;1gcIPW3rXw{)ZznpWnKJpgu%=wl*@)%q^fpz3qydt+8q=47)L7LBY*1e2MZ$Dj4 zCz+>VF)6)#|CsV3<*I4vdyiS@&KYIkRU!_l44eKU2yhSKRkf$%)7H%`=ZKLR^A4VY3Vj3I=_= zJ>DxGE3M@I6!bKWHdo%w6@Jl*{mIF84$C_!mu*WspIddopOsijhp;k-pbepJL5t&N zT}ZaHGa2TUl)E-0xEHiD_-KptC*u6kf5l_{od0_V*4?;t=F5@t35Wfo)(p`8+QGqL zxZ|^SqIkF6s6F7}@Uw2zP*Nhjg4z}cT0j6;i&Z+q+=nHp{gHMw=(gxpDJ)5+QT=C) z^K`&d*FP57}Z~OSTv68xcbA?~*!dk=#sIrqns@Bp-cvfF2XoJ*}nVt6& zhIdT(M?}i2!_u<55a74(+0#HlypM4Ln6f^1TdBn+mKWb>C7gdO_!JHYC)f_tpa=$@7*;r^`U{D)$Ngyz72<;Nx(`V19 zrvQyVd}8|2b+A220sjCwOtS|L;&}~?(e2q9VDMI?#)m5S{9h>!Xt`yt7?YgcxkHDh z3M??}&#U=AAEI{i?VOX(x9Ss}?!2}+n3sIFZ}1)cqzLP=m&{fqE{hHSW$(pgU;E=J zyH6TU$!M<@UR=;Isrj)o8Nsn(WdL3>o_O_8vzgOZ6!=ze7TT4{XS5dufq8oM^wcFswVZ(j^qTBe#fgZ*t*#G-qlpI#moV&~=F2Gt5Srlbsoc zk5en=VdEecx9r^6`^WJc7LcfEdbXS!Z@tTPValZV-`^{`J=WSjdA@gU;KE#`KM3jE zx$Wp;XPTJUl*OcFwjsMyEieN8Nnd}@75iS(_Ia=fcLCNrz5Dd}<@(}ZH$5atHB@Zc zbgg3K!RZUF`?I*aJ#jJ-+A4DgBR1H!rBFW;qdq+%Q-*{DK`Brw&pt~y#Cg4NublD2 zSMT!YtC<`-n=;xYK2ZJDoRdad_w;jcc3$qc${{=_ZC2(yd;e!cjulUvn$#w`4D^ld zBlQt}UZd{c;p5Y&t44A_lo&Ud$sCw^3Y2HTN^{!S=0dp8(9mFMP-9|G0(gH5R4(yOsg@eq z{f7-7-foy}u0{>pZqMaGFkh{*?b4;O@Oid=qW1`0?f;3bd{=C>9JYFnns+AU5ZraD zyUDt`4k~BVD434lJU6VTzqh_;(|e!X!F*s zSDC9Q6ll%c;b2!WcK7mZL!+`oy)wpyy=H#sR2N<7_PQk}_zLvVm#O2MKRS1*I_%t) zSM6HL;k&+g)y0$ltM{C`e)8tOTEjP2ryJD00hjeM3dom;`x=OZ{OB~RG5dxoOAT_D zdsb9Z3;xW+`U9I0yTQT%A%#gW7|1cNB-M4I^`VPK2@y-4m$sc`lX^NL{h^k}iRx8R z&6hW|4p&jjGF2tDNl!llnLGx%tk@b$MSRJwqF|+T0Li)8yuCAly zHo@5H1jB;-)~Oo2XL|qr`t>_WE#`FSIvtzNu+=a4b6oMQ*sZ^!lT73; zz@l_4!R}G|xVP0YC*9F8Ngt+yLBa86W*y;92V>1Fhmtt49(%G5OPBuZ zrMg8-o9@S6-HSE*#<&LKJfw;G@1{>(YR*gjg2hwvcATxYTo4}>SB7)lNm>cQ+;XsP zVSe?~`tR}hHh0VotxL3SGVgq6Woess?UEE^EYB#f!40m*%i5f(CyEQ`4!M zQ)0L5*wJ5c+D^xNG=||A$#+V%et3Vd>ANb{d0qB^S6Q8FFD%!Sf}6@P=@p{nW0h6j zD44Gx-2-Ch^qQbNn^SvJ^<;=%i{+izV`|VQJvsb~?S(3fu1+7U?tg zLv^z%@g#ku6pcv^_eR^?(JHB!Y-iUG(TuZa+|s+pZY0{bXZq?_z!Xv;hpvPEs;<2m z$RU|m5|>gDbtCUjrvi+8_k6CeuL(AT05Gi?I4As%g!{F*Zb7dso7gz5jeVs!aiY(Y zvD5E0S>=`bf|jA7|}b1F=%tTs*R;U!DWbb7`{MY$AamEP4^-Q~~P z$218VAy_l~;fLO+F4iN3g>5<;vztfTSOJeuUEX7Paii){Xm-ysqV*d}M~amR6DQ8c z6N}*Fq7gYPa$4}WXl%L9pMLfkv#Y(=^&DgNnb27id$#H}{Nii61Ft}Dzu~(wHi&h= zu5TH23Xf?UJfqMD*8W^(<{9Rng!%8%s+n|VVLI1Jgnzir&T^59%V6BMf8r7e6I1&3 zcksNB1&4e*A6&N|yP7G@*?|dnG#>v@VCiyK&q8nAwr4&i-pkNe@Pu~stbYFdNY|xk zP(hVz8Ox*NGI;=HcrJ9^sbAA0QMd-6L1!FH4?2PU0+;7fO zddUnwclKoH?GR2lKl`U=tmfdHq@LPUxNuMQtk+J#70i(hf){Tzw zAy$6YqleSdHXGM7^V&jTgyC?ZS@@5U)oEIHhdEpJcz&0VVcqUpRnYO|Gue7N|D80L zeP(e6D*ZmhML`@puOThyOKn=Z6s3Z;;^H{5VhcSu%5!e|b+c}1Vb>#Tu158ro>>#3 z<8wU6BL1G%1go3PAJq)p^4x1d&vLuenY%`8yEA@Z{@s0d?YG!&yF8$CR6$z*4RbRc z9CRH`j(0XWG$JRa?RERGowcQvD|Alm^xToZAu3L!S||=KFy)q_*&=BuenaYr^q+c~ z$D0%WsHU!Jp_qfgVrF{#_U)Vcu4I=(CpsbH1}V@cWv^TUS{m;B(Lh+NRBLUXrAvj? zmb#U{)7Ts55gnYFap3tEb+o-Umaw|^PsN|d>`z;d@*9!rRG4`F_Hs%Y^a*CInyp2h zwqr8_Co2lR{bR>CRK1|X_>u2QM({e7{lWOI5Zmjzt4_PY$cuD@+H~iofl&c!kEfSXYSvoo6t*5!{jdsksWysN$-qaB*w_xqnS9f~lJUOae zW<06p*Q;q8@{6u7aMlz9T#BvPfkg=YCXRT_pcVQA5$yhYPJ5M(GPhr&&oOh~LJeJ= zOu+64J|O;0%WyoMd`+)3zVLvBZ&mt(DIkE1xa0tXVVK(RA3&>t37BcCH>&4f(z0`H-IN z+Z7}gv~5_B-tmB^QFla-m(hp`A!&9>msuDBgW9yysc0mNIEhyKEG2ZP>9#gk#NFxpHxZ->A!O^<6uO9EZ z`MQ~5SUYTjhA4_LT7Q8X2Qw%3huU7R7_U1dG9qGcNXW^UO@zN(mFgA>fKprKt7)IC zSa|mI=@8Vkg8~b-#pbU+l93nMqtDWFTi@JFc}qVe_T?@Nuh(VUxyMeE+F)R<+YR@h zJ0+jFp{MiTB|5v$9!8K$L|DKKqGEML7+G@xelk)@A+(?V`=vL0zQNQWVx8d?wyXHM zqrT*eM)&0%Kd^4AT<&f2*DA$~S9^cxX}7sBC3~{b*xUv4oThB-9`=)YvO(rsV+Unn z@dayxiW?WVt_n%?QKOSv_apH3>HxaQUS`q*UdioY`8$W_aAh@Cd0j_fQwTR1e5 z=MdgswXuE{UcQPK)6*0NfIu8%*3hDCi6-?0LlT?0k0OQQNShU{&ML|q=2^6%r;GOV zYZ~2>?k2C$kGfT{s_FAa6YhW7CJ$5QdM&`ZfK?$Nht_>+O*VQY_xD`7X(qQZ;?;=h zkKCb=M>H98t@)I~sAWFoSu1od9i7h^}I~mQjxZ@_|hK;M8-1?Fk?SKrz z{Qr)MRSq8+HoWw|>1j@mS*&^%d}X#~V8L=nowBmBb=1+8nrYrOF=n;Ogm%+|?zfnp zSE8(|a@SLNj9PlXPsSK5STf?bN3M zIak4-A1hw?)IF@5(|xnQ|5=1`u1rtdV&(YXc-OT9TB4SWb{ba}KlH>%&-(|qze?=E z`-x8OF=k8JuJi{NPBfP4Jk|AZTld7$!|pfiHgv!D;N^a^K_&IS1;wRMbcrR`BRrYaQ3{|>D{ z`rKMy@MZy;#O(yJAC-Hn2z4m57$CSlWo^0dr+t_y)@=&T3@M zL%8rj?{>fE_WHw>7iKNhU*5}SMA^UIvbEQj&dc?hA3r|UVN1*02RFB!jw?+{I{s?( z;%)abtty&Khmc*lv_=5+!|3>UL)sZzf`Ur#n?(JLt=9Mnyo0Fnk@jDG(7G(h|A+HC zJn}(C^{MT)4%7cl9X40|i|QMthahX+zlIar)vDD$gkEX|-u(YV(eYg7zi6kT-=ITr zbq%r6D%q_m{`$?td6Y&?GnmtuOOD2}_5?YEXiWYabECUw9593Ng?5a49PF2#_ZXc# zl79`EE<3ciEy|$BOzke9A@OnR)*hof6>s0=(MUbsc5T^zj<@|$yL8W$-$%Qb`!Z7$ zLW`MF){w3#y>Z4x9=)tem>U>-_~Euc%}T+MgXv!$zZtSEAmB$ZKr#LFiKt-cUam#k zw<}FQ(#s|)*>KF9!?9!LY}R_?!vt$+c=$)->Ru0P(__qfCfR!R?|(kH?`_~Y+0;kb z^Y;-+ch9Fp2a$*i^q!JYizy4D82w-|;3Gikw-Y+NDeJ;)*tnoU^L_;tv}0iW6nyHh zlnc@t5UCRw)QroFzaeU*Xn&R_QK zrCj^dKg~n_d-7hI$G7%$9#h8h$d0srM#)vQt(U=nCA%EJO;b>}QZ;v#W@o2Pmcc&% zRjlG#aUHU%g(9|R58mCIc*f9(8?so6I9M$xDdcBhahE8 zff3XD1fV%*Sk%*-i6J)}UnZvGP0@#4ZDiRq^@ZO;j%5I|wf+lv?Ssx-iJ~EP1(`g& z529kJ+wOqLI;E?g-68?p|0AJq>HlT^Yc%s;+h0xFuy0|r2IFmXTo|q}FSAMYHbZl( z#WYzva5QgzD^q3}zKxn(9D?k}XYihcQtu}}6!?$qCSZQj(sOg_Hsgd0c@nokpKS=R zVn(!nWbzEy@X-8CuU@Pd3IGl!dsXj?S^ee0F~N272JZ37d0xFQ%v&>VE=)v zEo7;STe_yF%-Km5+obInyzNJskILTyO>sWK=Z!=1-JrRr@d`tayAWpgAmh){{0At@ zZ<_duw7xqiBf0AM()_>|-3l^R{h`qUeHF9Et1 zRUQSiMxE_C&~457mkuPwrkVJVuAPplJHW*8vZY zvu3nBb^pHK8M(VxxW9e7M*pNW{(DPf^8Z<0Qz-D`{Pb&!1T7g?S`dAf|~Ax|Cv+m6)p+-Y08b zuWR?_U3zfuJty1y%4g3P1+5&V{(22mHJZN;c$U{Y$-&IQF)cRH&KW|A0lXIcEEKS< z!*cvnpZ~pT)b?7pC5}7(ArrS3RV{$S{aw#v0HgcAvCZP>l2Y4Vy^g=VRd)a51~+ki zd1t=$xl5PQA+CNxP_SZj_g=js(Le~}f9#mv#uDb_nnsH-?7Co^+PsS*A~tSHC&dUC(_oR*s)@I%MQd2ATUX zGRye&AR0<;GHOO5ps{`)+VA2*S?x#w>VvNtHb;(QJ-LjZ(G{%Lj}dbuJ@+Wp<3>%I zP3FPeKkJ~Qp`Yjf->2SA+V9y1lYt0-B~lji&0?v<&Skr|sc1lO>yi(`xD;-oGue32 zhuu}~dcl|o9aY&pLunoOd-uR>C|*(UK!+Pa$nKwg;9Dj>wbQ=t)eD+XOVhB%8Qv4oWQ;Ol=Hbfg zEUc#~KUWpK%u1D*o~O5zb?EfUw!lypS}k6Z**0m_Sore|{{BH^L_Pg@_qPu1cEzr` zZ+Akkq;jQ4nMU@@V=KWY7vC(J&iV|+Z(=6+=F0knLlO)xs?amM|J9+Sr_J0Oq_cN3 z-6@(u#+fCFHPu++6OXnv3~ICfrD`y1exFgd&D<7RCLXa3X$5 zN_GX{CaLxXLLq**Uu3Ve>*4$Is}^E~Q)){t`%Li!8_zkrS3m%uJayW#Yk86#O~{m1 zU5B2I*~D79e#bU=f=n74(1)~hO<@C@%x;U%gzce4nV%`lN374!&7I6YLWZBrE2~{T z_4?`9;e9796z!1y@c*eDI$1d`+HS+H<*LIs3$D*{^WRxpkeJet{OdX^W7qOs`J?DK z#_Fm3V|PBgI(IR{=h{rqJvL5JzFLV9`ez+GF7#L%6IU{&EUnXl|iaV4t@u$Gm+}(q*K}=Uio9Mr0=rL2MVoR@UWo=O5!PxECd+!WV@@2@bNF zj2&Jqb1FF4R55t<>*X7VK2=M59>cTD=fy0CeChCf&GE$b(FZo+YB)##Fct^ZX)J8n zfA%8<*Znc0RV5_rE)3jGz3&njd;iFY=i7FNEm}QXR|J4JQT?@19Cf+XSXRVEK0X@i z70gVexyLcR_$h_9&cPM+3Z}J~h)le1a;;4K-!k2nXjQZ`ro-_$Z5)Bekx)v&4>Z(c*l zzJ1L|Ddr2M8+lUurc=S>*tak1?iG1i1$w-#t1Fvv-NI%3#97%^869?KPoM4YX4Usx z#B7~~L#vC^qfMN<=Dyg`W&4i@?_On@b16_*faYjOVXnKUYIF|Zl

  1. IvgG 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 z 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
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +
    +

    tuning libtorrent

    +

    libtorrent expose most parameters used in the bittorrent engine for +customization through the settings_pack. This makes it possible to +test and tweak the parameters for certain algorithms to make a client +that fits a wide range of needs. From low memory embedded devices to +servers seeding thousands of torrents. The default settings in libtorrent +are tuned for an end-user bittorrent client running on a normal desktop +computer.

    +

    This document describes techniques to benchmark libtorrent performance +and how parameters are likely to affect it.

    +
    +
    +

    profiling

    +

    libtorrent is instrumented with a number of counters and gauges you can have +access to via the session_stats_alert. First, enable these alerts in the +alert mask:

    +
    +settings_pack p;
    +p.set_int(settings_mask::alert_mask, alert::stats_notification);
    +ses.apply_settings(p);
    +
    +

    Then print alerts to a file:

    +
    +std::vector<alert*> alerts;
    +ses.pop_alerts(&alerts);
    +
    +for (auto* a : alerts) {
    +        std::cout << a->message() << "\n";
    +}
    +
    +

    If you want to separate generic alerts from session stats, you can filter on the +alert category in the alert, alert::category().

    +

    The alerts with data will have the type session_stats_alert and there is one +session_log_alert that will be posted on startup containing the column names +for all metrics. Logging this line will greatly simplify interpreting the output.

    +

    The python scrip in tools/parse_session_stats.py can parse the resulting +file and produce graphs of relevant stats. It requires gnuplot.

    +
    +
    +

    reducing memory footprint

    +

    These are things you can do to reduce the memory footprint of libtorrent. You get +some of this by basing your default settings_pack on the min_memory_usage() +setting preset function.

    +

    Keep in mind that lowering memory usage will affect performance, always profile +and benchmark your settings to determine if it's worth the trade-off.

    +

    The typical buffer usage of libtorrent, for a single download, with the cache +size set to 256 blocks (256 * 16 kiB = 4 MiB) is:

    +
    +read cache:      128.6 (2058 kiB)
    +write cache:     103.5 (1656 kiB)
    +receive buffers: 7.3   (117 kiB)
    +send buffers:    4.8   (77 kiB)
    +hash temp:       0.001 (19 Bytes)
    +
    +

    The receive buffers is proportional to the number of connections we make, and is +limited by the total number of connections in the session (default is 200).

    +

    The send buffers is proportional to the number of upload slots that are allowed +in the session. The default is auto configured based on the observed upload rate.

    +

    The read and write cache can be controlled (see section below).

    +

    The "hash temp" entry size depends on whether or not hashing is optimized for +speed or memory usage. In this test run it was optimized for memory usage.

    +
    +

    disable disk cache

    +

    The bulk of the memory libtorrent will use is used for the disk cache. To save +the absolute most amount of memory, you can disable the cache by setting +settings_pack::cache_size to 0. You might want to consider using the cache +but just disable caching read operations. You do this by settings +settings_pack::use_read_cache to false. This is the main factor in how much +memory will be used by the client. Keep in mind that you will degrade performance +by disabling the cache. You should benchmark the disk access in order to make an +informed trade-off.

    +
    +
    +

    remove torrents

    +

    Torrents that have been added to libtorrent will inevitably use up memory, even +when it's paused. A paused torrent will not use any peer connection objects or +any send or receive buffers though. Any added torrent holds the entire .torrent +file in memory, it also remembers the entire list of peers that it's heard about +(which can be fairly long unless it's capped). It also retains information about +which blocks and pieces we have on disk, which can be significant for torrents +with many pieces.

    +

    If you need to minimize the memory footprint, consider removing torrents from +the session rather than pausing them. This will likely only make a difference +when you have a very large number of torrents in a session.

    +

    The downside of removing them is that they will no longer be auto-managed. Paused +auto managed torrents are scraped periodically, to determine which torrents are +in the greatest need of seeding, and libtorrent will prioritize to seed those.

    +
    +
    +

    socket buffer sizes

    +

    You can make libtorrent explicitly set the kernel buffer sizes of all its peer +sockets. If you set this to a low number, you may see reduced throughput, especially +for high latency connections. It is however an opportunity to save memory per +connection, and might be worth considering if you have a very large number of +peer connections. This memory will not be visible in your process, this sets +the amount of kernel memory is used for your sockets.

    +

    Change this by setting settings_pack::recv_socket_buffer_size and +settings_pack::send_socket_buffer_size.

    +
    +
    +

    peer list size

    +

    The default maximum for the peer list is 4000 peers. For IPv4 peers, each peer +entry uses 32 bytes, which ends up using 128 kB per torrent. If seeding 4 popular +torrents, the peer lists alone uses about half a megabyte.

    +

    The default limit is the same for paused torrents as well, so if you have a +large number of paused torrents (that are popular) it will be even more +significant.

    +

    If you're short of memory, you should consider lowering the limit. 500 is probably +enough. You can do this by setting settings_pack::max_peerlist_size to +the max number of peers you want in a torrent's peer list. This limit applies per +torrent. For 5 torrents, the total number of peers in peer lists will be 5 times +the setting.

    +

    You should also lower the same limit but for paused torrents. It might even make sense +to set that even lower, since you only need a few peers to start up while waiting +for the tracker and DHT to give you fresh ones. The max peer list size for paused +torrents is set by settings_pack::max_paused_peerlist_size.

    +

    The drawback of lowering this number is that if you end up in a position where +the tracker is down for an extended period of time, your only hope of finding live +peers is to go through your list of all peers you've ever seen. Having a large +peer list will also help increase performance when starting up, since the torrent +can start connecting to peers in parallel with connecting to the tracker.

    +
    +
    +

    send buffer watermark

    +

    The send buffer watermark controls when libtorrent will ask the disk I/O thread +to read blocks from disk, and append it to a peer's send buffer.

    +

    When the send buffer has fewer than or equal number of bytes as +settings_pack::send_buffer_watermark, the peer will ask the disk I/O thread +for more data to send. The trade-off here is between wasting memory by having too +much data in the send buffer, and hurting send rate by starving out the socket, +waiting for the disk read operation to complete.

    +

    If your main objective is memory usage and you're not concerned about being able +to achieve high send rates, you can set the watermark to 9 bytes. This will guarantee +that no more than a single (16 kiB) block will be on the send buffer at a time, for +all peers. This is the least amount of memory possible for the send buffer.

    +

    You should benchmark your max send rate when adjusting this setting. If you have +a very fast disk, you are less likely see a performance hit.

    +
    +
    +

    reduce executable size

    +

    Compilers generally add a significant number of bytes to executables that make use +of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can +reduce the executable size with up to 45%. In order to build without exception +support, you need to patch parts of boost.

    +

    Also make sure to optimize for size when compiling.

    +

    Another way of reducing the executable size is to disable code that isn't used. +There are a number of TORRENT_* macros that control which features are included +in libtorrent. If these macros are used to strip down libtorrent, make sure the same +macros are defined when building libtorrent as when linking against it. If these +are different the structures will look different from the libtorrent side and from +the client side and memory corruption will follow.

    +

    One, probably, safe macro to define is TORRENT_NO_DEPRECATE which removes all +deprecated functions and struct members. As long as no deprecated functions are +relied upon, this should be a simple way to eliminate a little bit of code.

    +

    For all available options, see the building libtorrent section.

    +
    +
    +
    +

    high performance seeding

    +

    In the case of a high volume seed, there are two main concerns. Performance and scalability. +This translates into high send rates, and low memory and CPU usage per peer connection.

    +
    +

    file pool

    +

    libtorrent keeps an LRU file cache. Each file that is opened, is stuck in the cache. The main +purpose of this is because of anti-virus software that hooks on file-open and file close to +scan the file. Anti-virus software that does that will significantly increase the cost of +opening and closing files. However, for a high performance seed, the file open/close might +be so frequent that it becomes a significant cost. It might therefore be a good idea to allow +a large file descriptor cache. Adjust this though settings_pack::file_pool_size.

    +

    Don't forget to set a high rlimit for file descriptors in your process as well. This limit +must be high enough to keep all connections and files open.

    +
    +
    +

    disk cache

    +

    You typically want to set the cache size to as high as possible. The +settings_pack::cache_size is specified in 16 kiB blocks. Since you're seeding, +the cache would be useless unless you also set settings_pack::use_read_cache +to true.

    +

    In order to increase the possibility of read cache hits, set the +settings_pack::cache_expiry to a large number. This won't degrade anything as +long as the client is only seeding, and not downloading any torrents.

    +

    There's a guided cache mode. This means the size of the read cache line that's +stored in the cache is determined based on the upload rate to the peer that +triggered the read operation. The idea being that slow peers don't use up a +disproportional amount of space in the cache. This is enabled through +settings_pack::guided_read_cache.

    +

    In cases where the assumption is that the cache is only used as a read-ahead, and that no +other peer will ever request the same block while it's still in the cache, the read +cache can be set to be volatile. This means that every block that is requested out of +the read cache is removed immediately. This saves a significant amount of cache space +which can be used as read-ahead for other peers. To enable volatile read cache, set +settings_pack::volatile_read_cache to true.

    +
    +
    +

    uTP-TCP mixed mode

    +

    libtorrent supports uTP, which has a delay based congestion controller. In order to +avoid having a single TCP bittorrent connection completely starve out any uTP connection, +there is a mixed mode algorithm. This attempts to detect congestion on the uTP peers and +throttle TCP to avoid it taking over all bandwidth. This balances the bandwidth resources +between the two protocols. When running on a network where the bandwidth is in such an +abundance that it's virtually infinite, this algorithm is no longer necessary, and might +even be harmful to throughput. It is advised to experiment with the +settings_pack::mixed_mode_algorithm, setting it to settings_pack::prefer_tcp. +This setting entirely disables the balancing and un-throttles all connections. On a typical +home connection, this would mean that none of the benefits of uTP would be preserved +(the modem's send buffer would be full at all times) and uTP connections would for the most +part be squashed by the TCP traffic.

    +
    +
    +

    send buffer low watermark

    +

    libtorrent uses a low watermark for send buffers to determine when a new piece should +be requested from the disk I/O subsystem, to be appended to the send buffer. The low +watermark is determined based on the send rate of the socket. It needs to be large +enough to not draining the socket's send buffer before the disk operation completes.

    +

    The watermark is bound to a max value, to avoid buffer sizes growing out of control. +The default max send buffer size might not be enough to sustain very high upload rates, +and you might have to increase it. It's specified in bytes in +settings_pack::send_buffer_watermark.

    +
    +
    +

    peers

    +

    First of all, in order to allow many connections, set the global connection limit +high, settings_pack::connections_limit. Also set the upload rate limit to +infinite, settings_pack::upload_rate_limit, 0 means infinite.

    +

    When dealing with a large number of peers, it might be a good idea to have slightly +stricter timeouts, to get rid of lingering connections as soon as possible.

    +

    There are a couple of relevant settings: settings_pack::request_timeout, +settings_pack::peer_timeout and settings_pack::inactivity_timeout.

    +

    For seeds that are critical for a delivery system, you most likely want to allow +multiple connections from the same IP. That way two people from behind the same NAT +can use the service simultaneously. This is controlled by +settings_pack::allow_multiple_connections_per_ip.

    +

    In order to always unchoke peers, turn off automatic unchoke by setting +settings_pack::choking_algorithm to settings_pack::fixed_slot_choker and set the number +of upload slots to a large number via settings_pack::unchoke_slots_limit, +or use -1 (which means infinite).

    +
    +
    +

    torrent limits

    +

    To seed thousands of torrents, you need to increase the settings_pack::active_limit +and settings_pack::active_seeds.

    +
    +
    +

    SHA-1 hashing

    +

    When downloading at very high rates, it is possible to have the CPU be the +bottleneck for passing every downloaded byte through SHA-1. In order to enable +calculating SHA-1 hashes in parallel, on multi-core systems, set +settings_pack::aio_threads to the number of threads libtorrent should +perform I/O and do SHA-1 hashing in. Only if that thread is close to saturating +one core does it make sense to increase the number of threads.

    +
    +
    +
    +

    scalability

    +

    In order to make more efficient use of the libtorrent interface when running a large +number of torrents simultaneously, one can use the session::get_torrent_status() call +together with session::post_torrent_updates(). Keep in mind that every call into +libtorrent that return some value have to block your thread while posting a message to +the main network thread and then wait for a response. Calls that don't return any data +will simply post the message and then immediately return, performing the work +asynchronously. The time this takes might become significant once you reach a +few hundred torrents, depending on how many calls you make to each torrent and how often. +session::get_torrent_status() lets you query the status of all torrents in a single call. +This will actually loop through all torrents and run a provided predicate function to +determine whether or not to include it in the returned vector.

    +

    To use session::post_torrent_updates() torrents need to have the flag_update_subscribe +flag set. When post_torrent_updates() is called, a state_update_alert alert +is posted, with all the torrents that have updated since the last time this +function was called. The client have to keep its own state of all torrents, and +update it based on this alert.

    +
    +
    +

    benchmarking

    +

    There is a bunch of built-in instrumentation of libtorrent that can be used to get an insight +into what it's doing and how well it performs. This instrumentation is enabled by defining +preprocessor symbols when building.

    +

    There are also a number of scripts that parses the log files and generates graphs (requires +gnuplot and python).

    +
    +
    +

    understanding the disk threads

    +

    This section is somewhat outdated, there are potentially more than one disk +thread

    +

    All disk operations are funneled through a separate thread, referred to as the +disk thread. The main interface to the disk thread is a queue where disk jobs +are posted, and the results of these jobs are then posted back on the main +thread's io_service.

    +

    A disk job is essentially one of:

    +
      +
    1. +
      write this block to disk, i.e. a write job. For the most part this is just a
      +
      matter of sticking the block in the disk cache, but if we've run out of +cache space or completed a whole piece, we'll also flush blocks to disk. +This is typically very fast, since the OS just sticks these buffers in its +write cache which will be flushed at a later time, presumably when the drive +head will pass the place on the platter where the blocks go.
      +
      +
    2. +
    3. +
      read this block from disk. The first thing that happens is we look in the
      +
      cache to see if the block is already in RAM. If it is, we'll return +immediately with this block. If it's a cache miss, we'll have to hit the +disk. Here we decide to defer this job. We find the physical offset on the +drive for this block and insert the job in an ordered queue, sorted by the +physical location. At a later time, once we don't have any more non-read +jobs left in the queue, we pick one read job out of the ordered queue and +service it. The order we pick jobs out of the queue is according to an +elevator cursor moving up and down along the ordered queue of read jobs. If +we have enough space in the cache we'll read read_cache_line_size number of +blocks and stick those in the cache. This defaults to 32 blocks. If the +system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for +instance), jobs will be issued immediately to the OS. This especially +increases read throughput, since the OS has a much greater flexibility to +reorder the read jobs.
      +
      +
    4. +
    +

    Other disk job consist of operations that needs to be synchronized with the +disk I/O, like renaming files, closing files, flushing the cache, updating the +settings etc. These are relatively rare though.

    +
    +
    +

    contributions

    +

    If you have added instrumentation for some part of libtorrent that is not +covered here, or if you have improved any of the parser scrips, please consider +contributing it back to the project.

    +

    If you have run tests and found that some algorithm or default value in +libtorrent are suboptimal, please contribute that knowledge back as well, to +allow us to improve the library.

    +

    If you have additional suggestions on how to tune libtorrent for any specific +use case, please let us know and we'll update this document.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/tuning.rst b/docs/tuning.rst new file mode 100644 index 0000000..430aae0 --- /dev/null +++ b/docs/tuning.rst @@ -0,0 +1,398 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +tuning libtorrent +================= + +libtorrent expose most parameters used in the bittorrent engine for +customization through the settings_pack. This makes it possible to +test and tweak the parameters for certain algorithms to make a client +that fits a wide range of needs. From low memory embedded devices to +servers seeding thousands of torrents. The default settings in libtorrent +are tuned for an end-user bittorrent client running on a normal desktop +computer. + +This document describes techniques to benchmark libtorrent performance +and how parameters are likely to affect it. + +profiling +========= + +libtorrent is instrumented with a number of counters and gauges you can have +access to via the ``session_stats_alert``. First, enable these alerts in the +alert mask:: + + settings_pack p; + p.set_int(settings_mask::alert_mask, alert::stats_notification); + ses.apply_settings(p); + +Then print alerts to a file:: + + std::vector alerts; + ses.pop_alerts(&alerts); + + for (auto* a : alerts) { + std::cout << a->message() << "\n"; + } + +If you want to separate generic alerts from session stats, you can filter on the +alert category in the alert, ``alert::category()``. + +The alerts with data will have the type session_stats_alert and there is one +session_log_alert that will be posted on startup containing the column names +for all metrics. Logging this line will greatly simplify interpreting the output. + +The python scrip in ``tools/parse_session_stats.py`` can parse the resulting +file and produce graphs of relevant stats. It requires gnuplot_. + +.. _gnuplot: http://www.gnuplot.info + +reducing memory footprint +========================= + +These are things you can do to reduce the memory footprint of libtorrent. You get +some of this by basing your default settings_pack on the min_memory_usage() +setting preset function. + +Keep in mind that lowering memory usage will affect performance, always profile +and benchmark your settings to determine if it's worth the trade-off. + +The typical buffer usage of libtorrent, for a single download, with the cache +size set to 256 blocks (256 * 16 kiB = 4 MiB) is:: + + read cache: 128.6 (2058 kiB) + write cache: 103.5 (1656 kiB) + receive buffers: 7.3 (117 kiB) + send buffers: 4.8 (77 kiB) + hash temp: 0.001 (19 Bytes) + +The receive buffers is proportional to the number of connections we make, and is +limited by the total number of connections in the session (default is 200). + +The send buffers is proportional to the number of upload slots that are allowed +in the session. The default is auto configured based on the observed upload rate. + +The read and write cache can be controlled (see section below). + +The "hash temp" entry size depends on whether or not hashing is optimized for +speed or memory usage. In this test run it was optimized for memory usage. + +disable disk cache +------------------ + +The bulk of the memory libtorrent will use is used for the disk cache. To save +the absolute most amount of memory, you can disable the cache by setting +settings_pack::cache_size to 0. You might want to consider using the cache +but just disable caching read operations. You do this by settings +settings_pack::use_read_cache to false. This is the main factor in how much +memory will be used by the client. Keep in mind that you will degrade performance +by disabling the cache. You should benchmark the disk access in order to make an +informed trade-off. + +remove torrents +--------------- + +Torrents that have been added to libtorrent will inevitably use up memory, even +when it's paused. A paused torrent will not use any peer connection objects or +any send or receive buffers though. Any added torrent holds the entire .torrent +file in memory, it also remembers the entire list of peers that it's heard about +(which can be fairly long unless it's capped). It also retains information about +which blocks and pieces we have on disk, which can be significant for torrents +with many pieces. + +If you need to minimize the memory footprint, consider removing torrents from +the session rather than pausing them. This will likely only make a difference +when you have a very large number of torrents in a session. + +The downside of removing them is that they will no longer be auto-managed. Paused +auto managed torrents are scraped periodically, to determine which torrents are +in the greatest need of seeding, and libtorrent will prioritize to seed those. + +socket buffer sizes +------------------- + +You can make libtorrent explicitly set the kernel buffer sizes of all its peer +sockets. If you set this to a low number, you may see reduced throughput, especially +for high latency connections. It is however an opportunity to save memory per +connection, and might be worth considering if you have a very large number of +peer connections. This memory will not be visible in your process, this sets +the amount of kernel memory is used for your sockets. + +Change this by setting settings_pack::recv_socket_buffer_size and +settings_pack::send_socket_buffer_size. + +peer list size +-------------- + +The default maximum for the peer list is 4000 peers. For IPv4 peers, each peer +entry uses 32 bytes, which ends up using 128 kB per torrent. If seeding 4 popular +torrents, the peer lists alone uses about half a megabyte. + +The default limit is the same for paused torrents as well, so if you have a +large number of paused torrents (that are popular) it will be even more +significant. + +If you're short of memory, you should consider lowering the limit. 500 is probably +enough. You can do this by setting settings_pack::max_peerlist_size to +the max number of peers you want in a torrent's peer list. This limit applies per +torrent. For 5 torrents, the total number of peers in peer lists will be 5 times +the setting. + +You should also lower the same limit but for paused torrents. It might even make sense +to set that even lower, since you only need a few peers to start up while waiting +for the tracker and DHT to give you fresh ones. The max peer list size for paused +torrents is set by settings_pack::max_paused_peerlist_size. + +The drawback of lowering this number is that if you end up in a position where +the tracker is down for an extended period of time, your only hope of finding live +peers is to go through your list of all peers you've ever seen. Having a large +peer list will also help increase performance when starting up, since the torrent +can start connecting to peers in parallel with connecting to the tracker. + +send buffer watermark +--------------------- + +The send buffer watermark controls when libtorrent will ask the disk I/O thread +to read blocks from disk, and append it to a peer's send buffer. + +When the send buffer has fewer than or equal number of bytes as +settings_pack::send_buffer_watermark, the peer will ask the disk I/O thread +for more data to send. The trade-off here is between wasting memory by having too +much data in the send buffer, and hurting send rate by starving out the socket, +waiting for the disk read operation to complete. + +If your main objective is memory usage and you're not concerned about being able +to achieve high send rates, you can set the watermark to 9 bytes. This will guarantee +that no more than a single (16 kiB) block will be on the send buffer at a time, for +all peers. This is the least amount of memory possible for the send buffer. + +You should benchmark your max send rate when adjusting this setting. If you have +a very fast disk, you are less likely see a performance hit. + +reduce executable size +---------------------- + +Compilers generally add a significant number of bytes to executables that make use +of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can +reduce the executable size with up to 45%. In order to build without exception +support, you need to patch parts of boost. + +Also make sure to optimize for size when compiling. + +Another way of reducing the executable size is to disable code that isn't used. +There are a number of ``TORRENT_*`` macros that control which features are included +in libtorrent. If these macros are used to strip down libtorrent, make sure the same +macros are defined when building libtorrent as when linking against it. If these +are different the structures will look different from the libtorrent side and from +the client side and memory corruption will follow. + +One, probably, safe macro to define is ``TORRENT_NO_DEPRECATE`` which removes all +deprecated functions and struct members. As long as no deprecated functions are +relied upon, this should be a simple way to eliminate a little bit of code. + +For all available options, see the `building libtorrent`_ section. + +.. _`building libtorrent`: building.html + +high performance seeding +======================== + +In the case of a high volume seed, there are two main concerns. Performance and scalability. +This translates into high send rates, and low memory and CPU usage per peer connection. + +file pool +--------- + +libtorrent keeps an LRU file cache. Each file that is opened, is stuck in the cache. The main +purpose of this is because of anti-virus software that hooks on file-open and file close to +scan the file. Anti-virus software that does that will significantly increase the cost of +opening and closing files. However, for a high performance seed, the file open/close might +be so frequent that it becomes a significant cost. It might therefore be a good idea to allow +a large file descriptor cache. Adjust this though settings_pack::file_pool_size. + +Don't forget to set a high rlimit for file descriptors in your process as well. This limit +must be high enough to keep all connections and files open. + +disk cache +---------- + +You typically want to set the cache size to as high as possible. The +settings_pack::cache_size is specified in 16 kiB blocks. Since you're seeding, +the cache would be useless unless you also set settings_pack::use_read_cache +to true. + +In order to increase the possibility of read cache hits, set the +settings_pack::cache_expiry to a large number. This won't degrade anything as +long as the client is only seeding, and not downloading any torrents. + +There's a *guided cache* mode. This means the size of the read cache line that's +stored in the cache is determined based on the upload rate to the peer that +triggered the read operation. The idea being that slow peers don't use up a +disproportional amount of space in the cache. This is enabled through +settings_pack::guided_read_cache. + +In cases where the assumption is that the cache is only used as a read-ahead, and that no +other peer will ever request the same block while it's still in the cache, the read +cache can be set to be *volatile*. This means that every block that is requested out of +the read cache is removed immediately. This saves a significant amount of cache space +which can be used as read-ahead for other peers. To enable volatile read cache, set +settings_pack::volatile_read_cache to true. + +uTP-TCP mixed mode +------------------ + +libtorrent supports uTP_, which has a delay based congestion controller. In order to +avoid having a single TCP bittorrent connection completely starve out any uTP connection, +there is a mixed mode algorithm. This attempts to detect congestion on the uTP peers and +throttle TCP to avoid it taking over all bandwidth. This balances the bandwidth resources +between the two protocols. When running on a network where the bandwidth is in such an +abundance that it's virtually infinite, this algorithm is no longer necessary, and might +even be harmful to throughput. It is advised to experiment with the +settings_pack::mixed_mode_algorithm, setting it to settings_pack::prefer_tcp. +This setting entirely disables the balancing and un-throttles all connections. On a typical +home connection, this would mean that none of the benefits of uTP would be preserved +(the modem's send buffer would be full at all times) and uTP connections would for the most +part be squashed by the TCP traffic. + +.. _`uTP`: utp.html + +send buffer low watermark +------------------------- + +libtorrent uses a low watermark for send buffers to determine when a new piece should +be requested from the disk I/O subsystem, to be appended to the send buffer. The low +watermark is determined based on the send rate of the socket. It needs to be large +enough to not draining the socket's send buffer before the disk operation completes. + +The watermark is bound to a max value, to avoid buffer sizes growing out of control. +The default max send buffer size might not be enough to sustain very high upload rates, +and you might have to increase it. It's specified in bytes in +settings_pack::send_buffer_watermark. + +peers +----- + +First of all, in order to allow many connections, set the global connection limit +high, settings_pack::connections_limit. Also set the upload rate limit to +infinite, settings_pack::upload_rate_limit, 0 means infinite. + +When dealing with a large number of peers, it might be a good idea to have slightly +stricter timeouts, to get rid of lingering connections as soon as possible. + +There are a couple of relevant settings: settings_pack::request_timeout, +settings_pack::peer_timeout and settings_pack::inactivity_timeout. + +For seeds that are critical for a delivery system, you most likely want to allow +multiple connections from the same IP. That way two people from behind the same NAT +can use the service simultaneously. This is controlled by +settings_pack::allow_multiple_connections_per_ip. + +In order to always unchoke peers, turn off automatic unchoke by setting +settings_pack::choking_algorithm to settings_pack::fixed_slot_choker and set the number +of upload slots to a large number via settings_pack::unchoke_slots_limit, +or use -1 (which means infinite). + +torrent limits +-------------- + +To seed thousands of torrents, you need to increase the settings_pack::active_limit +and settings_pack::active_seeds. + +SHA-1 hashing +------------- + +When downloading at very high rates, it is possible to have the CPU be the +bottleneck for passing every downloaded byte through SHA-1. In order to enable +calculating SHA-1 hashes in parallel, on multi-core systems, set +settings_pack::aio_threads to the number of threads libtorrent should +perform I/O and do SHA-1 hashing in. Only if that thread is close to saturating +one core does it make sense to increase the number of threads. + +scalability +=========== + +In order to make more efficient use of the libtorrent interface when running a large +number of torrents simultaneously, one can use the ``session::get_torrent_status()`` call +together with ``session::post_torrent_updates()``. Keep in mind that every call into +libtorrent that return some value have to block your thread while posting a message to +the main network thread and then wait for a response. Calls that don't return any data +will simply post the message and then immediately return, performing the work +asynchronously. The time this takes might become significant once you reach a +few hundred torrents, depending on how many calls you make to each torrent and how often. +``session::get_torrent_status()`` lets you query the status of all torrents in a single call. +This will actually loop through all torrents and run a provided predicate function to +determine whether or not to include it in the returned vector. + +To use ``session::post_torrent_updates()`` torrents need to have the ``flag_update_subscribe`` +flag set. When post_torrent_updates() is called, a state_update_alert alert +is posted, with all the torrents that have updated since the last time this +function was called. The client have to keep its own state of all torrents, and +update it based on this alert. + +benchmarking +============ + +There is a bunch of built-in instrumentation of libtorrent that can be used to get an insight +into what it's doing and how well it performs. This instrumentation is enabled by defining +preprocessor symbols when building. + +There are also a number of scripts that parses the log files and generates graphs (requires +gnuplot and python). + +understanding the disk threads +============================== + +*This section is somewhat outdated, there are potentially more than one disk +thread* + +All disk operations are funneled through a separate thread, referred to as the +disk thread. The main interface to the disk thread is a queue where disk jobs +are posted, and the results of these jobs are then posted back on the main +thread's io_service. + +A disk job is essentially one of: + +1. write this block to disk, i.e. a write job. For the most part this is just a + matter of sticking the block in the disk cache, but if we've run out of + cache space or completed a whole piece, we'll also flush blocks to disk. + This is typically very fast, since the OS just sticks these buffers in its + write cache which will be flushed at a later time, presumably when the drive + head will pass the place on the platter where the blocks go. + +2. read this block from disk. The first thing that happens is we look in the + cache to see if the block is already in RAM. If it is, we'll return + immediately with this block. If it's a cache miss, we'll have to hit the + disk. Here we decide to defer this job. We find the physical offset on the + drive for this block and insert the job in an ordered queue, sorted by the + physical location. At a later time, once we don't have any more non-read + jobs left in the queue, we pick one read job out of the ordered queue and + service it. The order we pick jobs out of the queue is according to an + elevator cursor moving up and down along the ordered queue of read jobs. If + we have enough space in the cache we'll read read_cache_line_size number of + blocks and stick those in the cache. This defaults to 32 blocks. If the + system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for + instance), jobs will be issued immediately to the OS. This especially + increases read throughput, since the OS has a much greater flexibility to + reorder the read jobs. + +Other disk job consist of operations that needs to be synchronized with the +disk I/O, like renaming files, closing files, flushing the cache, updating the +settings etc. These are relatively rare though. + +contributions +============= + +If you have added instrumentation for some part of libtorrent that is not +covered here, or if you have improved any of the parser scrips, please consider +contributing it back to the project. + +If you have run tests and found that some algorithm or default value in +libtorrent are suboptimal, please contribute that knowledge back as well, to +allow us to improve the library. + +If you have additional suggestions on how to tune libtorrent for any specific +use case, please let us know and we'll update this document. + diff --git a/docs/tutorial.html b/docs/tutorial.html new file mode 100644 index 0000000..9cfb402 --- /dev/null +++ b/docs/tutorial.html @@ -0,0 +1,477 @@ + + + + + + +tutorial.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +
    +

    tutorial

    +

    The fundamental feature of starting and downloading torrents in libtorrent is +achieved by creating a session, which provides the context and a container for +torrents. This is done with via the session class, most of its interface is +documented under session_handle though.

    +

    To add a torrent to the session, you fill in an add_torrent_params object and +pass it either to add_torrent() or async_add_torrent().

    +

    add_torrent() is a blocking call which returns a torrent_handle.

    +

    For example:

    +
    +#include <libtorrent/session.hpp>
    +#include <libtorrent/add_torrent_params.hpp>
    +#include <libtorrent/torrent_handle.hpp>
    +#include <libtorrent/magnet_uri.hpp>
    +
    +int main(int argc, char const* argv[])
    +{
    +        if (argc != 2) {
    +                fprintf(stderr, "usage: %s <magnet-url>\n");
    +                return 1;
    +        }
    +        lt::session ses;
    +
    +        lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]);
    +        atp.save_path = "."; // save in current dir
    +        lt::torrent_handle h = ses.add_torrent(atp);
    +
    +        // ...
    +}
    +
    +

    Once you have a torrent_handle, you can affect it as well as querying status. +First, let's extend the example to print out messages from the bittorrent engine +about progress and events happening under the hood. libtorrent has a mechanism +referred to as alerts to communicate back information to the client application.

    +

    Clients can poll a session for new alerts via the pop_alerts() call. This +function fills in a vector of alert pointers with all new alerts since the last +call to this function. The pointers are owned by the session object at will +become invalidated by the next call to pop_alerts().

    +

    The alerts form a class hierarchy with alert as the root class. Each specific +kind of alert may include additional state, specific to the kind of message. All +alerts implement a message() function that prints out pertinent information +of the alert message. This can be convenient for simply logging events.

    +

    For programmatically react to certain events, use alert_cast<> to attempt +a down cast of an alert object to a more specific type.

    +

    In order to print out events from libtorrent as well as exiting when the torrent +completes downloading, we can poll the session for alerts periodically and print +them out, as well as listening for the torrent_finished_alert, which is posted +when a torrent completes.

    +
    +#include <iostream>
    +#include <thread>
    +#include <chrono>
    +
    +#include <libtorrent/session.hpp>
    +#include <libtorrent/add_torrent_params.hpp>
    +#include <libtorrent/torrent_handle.hpp>
    +#include <libtorrent/alert_types.hpp>
    +#include <libtorrent/magnet_uri.hpp>
    +
    +int main(int argc, char const* argv[]) try
    +{
    +  if (argc != 2) {
    +    std::cerr << "usage: " << argv[0] << " <magnet-url>" << std::endl;
    +    return 1;
    +  }
    +  lt::settings_pack p;
    +  p.set_int(lt::settings_pack::alert_mask, lt::alert_category::status
    +    | lt::alert_category::error);
    +  lt::session ses(p);
    +
    +  lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]);
    +  atp.save_path = "."; // save in current dir
    +  lt::torrent_handle h = ses.add_torrent(std::move(atp));
    +
    +  for (;;) {
    +    std::vector<lt::alert*> alerts;
    +    ses.pop_alerts(&alerts);
    +
    +    for (lt::alert const* a : alerts) {
    +      std::cout << a->message() << std::endl;
    +      // if we receive the finished alert or an error, we're done
    +      if (lt::alert_cast<lt::torrent_finished_alert>(a)) {
    +        goto done;
    +      }
    +      if (lt::alert_cast<lt::torrent_error_alert>(a)) {
    +        goto done;
    +      }
    +    }
    +    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    +  }
    +  done:
    +  std::cout << "done, shutting down" << std::endl;
    +}
    +catch (std::exception& e)
    +{
    +  std::cerr << "Error: " << e.what() << std::endl;
    +}
    +
    +
    +

    alert masks

    +

    The output from this program will be quite verbose, which is probably a good +starting point to get some understanding of what's going on. Alerts are +categorized into alert categories. Each category can be enabled and disabled +independently via the alert mask.

    +

    The alert mask is a configuration option offered by libtorrent. There are many +configuration options, see settings_pack. The alert_mask setting is an integer +of the category flags ORed together.

    +

    For instance, to only see the most pertinent alerts, the session can be +constructed like this:

    +
    +lt::settings_pack pack;
    +pack.set_int(lt::settings_pack::alert_mask
    +        , lt::alert::error_notification
    +        | lt::alert::storage_notification
    +        | lt::alert::status_notification);
    +
    +lt::session ses(pack);
    +
    +

    Configuration options can be updated after the session is started by calling +apply_settings(). Some settings are best set before starting the session +though, like listen_interfaces, to avoid race conditions. If you start the +session with the default settings and then immediately change them, there will +still be a window where the default settings apply.

    +

    Changing the settings may trigger listen sockets to close and re-open and +NAT-PMP, UPnP updates to be sent. For this reason, it's typically a good idea +to batch settings updates into a single call.

    +
    +
    +

    session destruction

    +

    The session destructor is blocking by default. When shutting down, trackers +will need to be contacted to stop torrents and other outstanding operations +need to be cancelled. Shutting down can sometimes take several seconds, +primarily because of trackers that are unresponsive (and time out) and also +DNS servers that are unresponsive. DNS lookups are especially difficult to +abort when stalled.

    +

    In order to be able to start destruction an wait for it asynchronously, one +can call session::abort().

    +

    This call returns a session_proxy object, which is a handle keeping the session +state alive while destructing it. It deliberately does not provide any of the +session operations, since it's shutting down.

    +

    After having a session_proxy object, the session destructor does not block. +However, the session_proxy destructor will.

    +

    This can be used to shut down multiple sessions or other parts of the +application in parallel.

    +
    +
    +

    asynchronous operations

    +

    Essentially any call to a member function of session or torrent_handle that +returns a value is a blocking synchronous call. Meaning it will post a message +to the main libtorrent thread and wait for a response. Such calls may be +expensive, and in applications where stalls should be avoided (such as user +interface threads), blocking calls should be avoided.

    +

    In the example above, session::add_torrent() returns a torrent_handle and is +thus blocking. For higher efficiency, async_add_torrent() will post a message +to the main thread to add a torrent, and post the resulting torrent_handle back +in an alert (add_torrent_alert). This is especially useful when adding a lot +of torrents in quick succession, as there's no stall in between calls.

    +

    In the example above, we don't actually use the torrent_handle for anything, so +converting it to use async_add_torrent() is just a matter of replacing the +add_torrent() call with async_add_torrent().

    +
    +
    +

    torrent_status_updates

    +

    To get updates to the status of torrents, call post_torrent_updates() on the +session object. This will cause libtorrent to post a state_update_alert +containing torrent_status objects for all torrents whose status has changed +since the last call to post_torrent_updates().

    +

    The state_update_alert looks something like this:

    +
    +struct state_update_alert : alert
    +{
    +        virtual std::string message() const;
    +        std::vector<torrent_status> status;
    +};
    +
    +

    The status field only contains the torrent_status for torrents with +updates since the last call. It may be empty if no torrent has updated its +state. This feature is critical for scalability.

    +

    See the torrent_status object for more information on what is in there. +Perhaps the most interesting fields are total_payload_download, +total_payload_upload, num_peers and state.

    +
    +
    +

    resuming torrents

    +

    Since bittorrent downloads pieces of files in random order, it's not trivial to +resume a partial download. When resuming a download, the bittorrent engine must +restore the state of the downloading torrent, specifically which parts of the +file(s) are downloaded. There are two approaches to doing this:

    +
      +
    1. read every piece of the downloaded files from disk and compare it against its +expected hash.
    2. +
    3. save, to disk, the state of which pieces (and partial pieces) are downloaded, +and load it back in again when resuming.
    4. +
    +

    If no resume data is provided with a torrent that's added, libtorrent will +employ (1) by default.

    +

    To save resume data, call save_resume_data() on the torrent_handle object. +This will ask libtorrent to generate the resume data and post it back in +a save_resume_data_alert. If generating the resume data fails for any reason, +a save_resume_data_failed_alert is posted instead.

    +

    Exactly one of those alerts will be posted for every call to +save_resume_data(). This is an important property when shutting down a +session with multiple torrents, every resume alert must be handled before +resuming with shut down. Any torrent may fail to save resume data, so the client +would need to keep a count of the outstanding resume files, decremented on +either save_resume_data_alert or save_resume_data_failed_alert.

    +

    The save_resume_data_alert looks something like this:

    +
    +struct save_resume_data_alert : torrent_alert
    +{
    +        virtual std::string message() const;
    +
    +        // the resume data
    +        add_torrent_params params;
    +};
    +
    +

    The params field is an add_torrent_params object containing all the state +to add the torrent back to the session again. This object can be serialized +using write_resume_data() or write_resume_data_buf(), and de-serialized +with read_resume_data().

    +
    +
    +

    example

    +

    Here's an updated version of the above example with the following updates:

    +
      +
    1. not using blocking calls
    2. +
    3. printing torrent status updates rather than the raw log
    4. +
    5. saving and loading resume files
    6. +
    +
    +#include <iostream>
    +#include <thread>
    +#include <chrono>
    +#include <fstream>
    +
    +#include <libtorrent/session.hpp>
    +#include <libtorrent/add_torrent_params.hpp>
    +#include <libtorrent/torrent_handle.hpp>
    +#include <libtorrent/alert_types.hpp>
    +#include <libtorrent/bencode.hpp>
    +#include <libtorrent/torrent_status.hpp>
    +#include <libtorrent/read_resume_data.hpp>
    +#include <libtorrent/write_resume_data.hpp>
    +#include <libtorrent/error_code.hpp>
    +#include <libtorrent/magnet_uri.hpp>
    +
    +using clk = std::chrono::steady_clock;
    +
    +// return the name of a torrent status enum
    +char const* state(lt::torrent_status::state_t s)
    +{
    +  switch(s) {
    +    case lt::torrent_status::checking_files: return "checking";
    +    case lt::torrent_status::downloading_metadata: return "dl metadata";
    +    case lt::torrent_status::downloading: return "downloading";
    +    case lt::torrent_status::finished: return "finished";
    +    case lt::torrent_status::seeding: return "seeding";
    +    case lt::torrent_status::allocating: return "allocating";
    +    case lt::torrent_status::checking_resume_data: return "checking resume";
    +    default: return "<>";
    +  }
    +}
    +
    +int main(int argc, char const* argv[]) try
    +{
    +  if (argc != 2) {
    +    std::cerr << "usage: " << argv[0] << " <magnet-url>" << std::endl;
    +    return 1;
    +  }
    +
    +  lt::settings_pack pack;
    +  pack.set_int(lt::settings_pack::alert_mask
    +    , lt::alert_category::error
    +    | lt::alert_category::storage
    +    | lt::alert_category::status);
    +
    +  lt::session ses(pack);
    +  clk::time_point last_save_resume = clk::now();
    +
    +  // load resume data from disk and pass it in as we add the magnet link
    +  std::ifstream ifs(".resume_file", std::ios_base::binary);
    +  ifs.unsetf(std::ios_base::skipws);
    +  std::vector<char> buf{std::istream_iterator<char>(ifs)
    +    , std::istream_iterator<char>()};
    +
    +  lt::add_torrent_params atp = lt::read_resume_data(buf);
    +  lt::add_torrent_params magnet = lt::parse_magnet_uri(argv[1]);
    +  if (atp.info_hash != magnet.info_hash) {
    +    atp = std::move(magnet);
    +  }
    +  atp.save_path = "."; // save in current dir
    +  ses.async_add_torrent(std::move(atp));
    +
    +  // this is the handle we'll set once we get the notification of it being
    +  // added
    +  lt::torrent_handle h;
    +  for (;;) {
    +    std::vector<lt::alert*> alerts;
    +    ses.pop_alerts(&alerts);
    +
    +    for (lt::alert const* a : alerts) {
    +      if (auto at = lt::alert_cast<lt::add_torrent_alert>(a)) {
    +        h = at->handle;
    +      }
    +      // if we receive the finished alert or an error, we're done
    +      if (lt::alert_cast<lt::torrent_finished_alert>(a)) {
    +        h.save_resume_data();
    +        goto done;
    +      }
    +      if (lt::alert_cast<lt::torrent_error_alert>(a)) {
    +        std::cout << a->message() << std::endl;
    +        goto done;
    +      }
    +
    +      // when resume data is ready, save it
    +      if (auto rd = lt::alert_cast<lt::save_resume_data_alert>(a)) {
    +        std::ofstream of(".resume_file", std::ios_base::binary);
    +        of.unsetf(std::ios_base::skipws);
    +        auto const b = write_resume_data_buf(rd->params);
    +        of.write(b.data(), b.size());
    +      }
    +
    +      if (auto st = lt::alert_cast<lt::state_update_alert>(a)) {
    +        if (st->status.empty()) continue;
    +
    +        // we only have a single torrent, so we know which one
    +        // the status is for
    +        lt::torrent_status const& s = st->status[0];
    +        std::cout << "\r" << state(s.state) << " "
    +          << (s.download_payload_rate / 1000) << " kB/s "
    +          << (s.total_done / 1000) << " kB ("
    +          << (s.progress_ppm / 10000) << "%) downloaded\x1b[K";
    +        std::cout.flush();
    +      }
    +    }
    +    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    +
    +    // ask the session to post a state_update_alert, to update our
    +    // state output for the torrent
    +    ses.post_torrent_updates();
    +
    +    // save resume data once every 30 seconds
    +    if (clk::now() - last_save_resume > std::chrono::seconds(30)) {
    +      h.save_resume_data();
    +      last_save_resume = clk::now();
    +    }
    +  }
    +
    +  // TODO: ideally we should save resume data here
    +
    +done:
    +  std::cout << "\ndone, shutting down" << std::endl;
    +}
    +catch (std::exception& e)
    +{
    +  std::cerr << "Error: " << e.what() << std::endl;
    +}
    +
    +
    +
    +

    torrent files

    +

    To add torrent files to a session (as opposed to a magnet link), it must first +be loaded into a torrent_info object.

    +

    The torrent_info object can be created either by filename a buffer or a +bencoded structure. When adding by filename, there's a sanity check limit on the +size of the file, for adding arbitrarily large torrents, load the file outside +of the constructor.

    +

    The torrent_info object provides an opportunity to query information about the +.torrent file as well as mutating it before adding it to the session.

    +
    +
    +

    bencoding

    +

    bencoded structures is the default data storage format used by bittorrent, such +as .torrent files, tracker announce and scrape responses and some wire protocol +extensions. libtorrent provides an efficient framework for decoding bencoded +data through bdecode() function.

    +

    There are two separate mechanisms for encoding and decoding. When decoding, +use the bdecode() function that returns a bdecode_node. When encoding, use +bencode() taking an entry object.

    +

    The key property of bdecode() is that it does not copy any data out of the +buffer that was parsed. It builds the tree structures of references pointing +into the buffer. The buffer must stay alive and valid for as long as the +bdecode_node is in use.

    +

    For performance details on bdecode(), see the blog post about it.

    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/tutorial.rst b/docs/tutorial.rst new file mode 100644 index 0000000..f35db3b --- /dev/null +++ b/docs/tutorial.rst @@ -0,0 +1,304 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +tutorial +======== + +The fundamental feature of starting and downloading torrents in libtorrent is +achieved by creating a *session*, which provides the context and a container for +torrents. This is done with via the session_ class, most of its interface is +documented under session_handle_ though. + +To add a torrent to the session, you fill in an add_torrent_params_ object and +pass it either to `add_torrent()`_ or `async_add_torrent()`_. + +``add_torrent()`` is a blocking call which returns a torrent_handle_. + +For example: + +.. code:: c++ + + #include + #include + #include + #include + + int main(int argc, char const* argv[]) + { + if (argc != 2) { + fprintf(stderr, "usage: %s \n"); + return 1; + } + lt::session ses; + + lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]); + atp.save_path = "."; // save in current dir + lt::torrent_handle h = ses.add_torrent(atp); + + // ... + } + +Once you have a torrent_handle_, you can affect it as well as querying status. +First, let's extend the example to print out messages from the bittorrent engine +about progress and events happening under the hood. libtorrent has a mechanism +referred to as *alerts* to communicate back information to the client application. + +Clients can poll a session for new alerts via the `pop_alerts()`_ call. This +function fills in a vector of alert pointers with all new alerts since the last +call to this function. The pointers are owned by the session object at will +become invalidated by the next call to `pop_alerts()`_. + +The alerts form a class hierarchy with alert_ as the root class. Each specific +kind of alert may include additional state, specific to the kind of message. All +alerts implement a message() function that prints out pertinent information +of the alert message. This can be convenient for simply logging events. + +For programmatically react to certain events, use `alert_cast<>`_ to attempt +a down cast of an alert object to a more specific type. + +In order to print out events from libtorrent as well as exiting when the torrent +completes downloading, we can poll the session for alerts periodically and print +them out, as well as listening for the torrent_finished_alert_, which is posted +when a torrent completes. + +.. include:: ../examples/bt-get.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +alert masks +----------- + +The output from this program will be quite verbose, which is probably a good +starting point to get some understanding of what's going on. Alerts are +categorized into alert categories. Each category can be enabled and disabled +independently via the *alert mask*. + +The alert mask is a configuration option offered by libtorrent. There are many +configuration options, see settings_pack_. The alert_mask_ setting is an integer +of the `category flags`_ ORed together. + +For instance, to only see the most pertinent alerts, the session can be +constructed like this: + +.. code:: c++ + + lt::settings_pack pack; + pack.set_int(lt::settings_pack::alert_mask + , lt::alert::error_notification + | lt::alert::storage_notification + | lt::alert::status_notification); + + lt::session ses(pack); + +Configuration options can be updated after the session is started by calling +`apply_settings()`_. Some settings are best set before starting the session +though, like listen_interfaces_, to avoid race conditions. If you start the +session with the default settings and then immediately change them, there will +still be a window where the default settings apply. + +Changing the settings may trigger listen sockets to close and re-open and +NAT-PMP, UPnP updates to be sent. For this reason, it's typically a good idea +to batch settings updates into a single call. + +session destruction +------------------- + +The session destructor is blocking by default. When shutting down, trackers +will need to be contacted to stop torrents and other outstanding operations +need to be cancelled. Shutting down can sometimes take several seconds, +primarily because of trackers that are unresponsive (and time out) and also +DNS servers that are unresponsive. DNS lookups are especially difficult to +abort when stalled. + +In order to be able to start destruction an wait for it asynchronously, one +can call `session::abort()`_. + +This call returns a session_proxy_ object, which is a handle keeping the session +state alive while destructing it. It deliberately does not provide any of the +session operations, since it's shutting down. + +After having a session_proxy_ object, the session destructor does not block. +However, the session_proxy_ destructor *will*. + +This can be used to shut down multiple sessions or other parts of the +application in parallel. + +asynchronous operations +----------------------- + +Essentially any call to a member function of session_ or torrent_handle_ that +returns a value is a blocking synchronous call. Meaning it will post a message +to the main libtorrent thread and wait for a response. Such calls may be +expensive, and in applications where stalls should be avoided (such as user +interface threads), blocking calls should be avoided. + +In the example above, session::add_torrent() returns a torrent_handle_ and is +thus blocking. For higher efficiency, `async_add_torrent()`_ will post a message +to the main thread to add a torrent, and post the resulting torrent_handle_ back +in an alert (add_torrent_alert_). This is especially useful when adding a lot +of torrents in quick succession, as there's no stall in between calls. + +In the example above, we don't actually use the torrent_handle_ for anything, so +converting it to use `async_add_torrent()`_ is just a matter of replacing the +`add_torrent()`_ call with `async_add_torrent()`_. + +torrent_status_updates +---------------------- + +To get updates to the status of torrents, call `post_torrent_updates()`_ on the +session object. This will cause libtorrent to post a state_update_alert_ +containing torrent_status_ objects for all torrents whose status has *changed* +since the last call to `post_torrent_updates()`_. + +The state_update_alert_ looks something like this: + +.. code:: c++ + + struct state_update_alert : alert + { + virtual std::string message() const; + std::vector status; + }; + +The ``status`` field only contains the torrent_status_ for torrents with +updates since the last call. It may be empty if no torrent has updated its +state. This feature is critical for scalability_. + +See the torrent_status_ object for more information on what is in there. +Perhaps the most interesting fields are ``total_payload_download``, +``total_payload_upload``, ``num_peers`` and ``state``. + +resuming torrents +----------------- + +Since bittorrent downloads pieces of files in random order, it's not trivial to +resume a partial download. When resuming a download, the bittorrent engine must +restore the state of the downloading torrent, specifically which parts of the +file(s) are downloaded. There are two approaches to doing this: + +1. read every piece of the downloaded files from disk and compare it against its + expected hash. +2. save, to disk, the state of which pieces (and partial pieces) are downloaded, + and load it back in again when resuming. + +If no resume data is provided with a torrent that's added, libtorrent will +employ (1) by default. + +To save resume data, call `save_resume_data()`_ on the torrent_handle_ object. +This will ask libtorrent to generate the resume data and post it back in +a save_resume_data_alert_. If generating the resume data fails for any reason, +a save_resume_data_failed_alert_ is posted instead. + +Exactly one of those alerts will be posted for every call to +`save_resume_data()`_. This is an important property when shutting down a +session with multiple torrents, every resume alert must be handled before +resuming with shut down. Any torrent may fail to save resume data, so the client +would need to keep a count of the outstanding resume files, decremented on +either save_resume_data_alert_ or save_resume_data_failed_alert_. + +The save_resume_data_alert_ looks something like this: + +.. code:: c++ + + struct save_resume_data_alert : torrent_alert + { + virtual std::string message() const; + + // the resume data + add_torrent_params params; + }; + +The ``params`` field is an add_torrent_params_ object containing all the state +to add the torrent back to the session again. This object can be serialized +using `write_resume_data()`_ or `write_resume_data_buf()`_, and de-serialized +with `read_resume_data()`_. + +example +------- + +Here's an updated version of the above example with the following updates: + +1. not using blocking calls +2. printing torrent status updates rather than the raw log +3. saving and loading resume files + +.. include:: ../examples/bt-get2.cpp + :code: c++ + :tab-width: 2 + :start-after: */ + +torrent files +------------- + +To add torrent files to a session (as opposed to a magnet link), it must first +be loaded into a torrent_info_ object. + +The torrent_info_ object can be created either by filename a buffer or a +bencoded structure. When adding by filename, there's a sanity check limit on the +size of the file, for adding arbitrarily large torrents, load the file outside +of the constructor. + +The torrent_info_ object provides an opportunity to query information about the +.torrent file as well as mutating it before adding it to the session. + +bencoding +--------- + +bencoded_ structures is the default data storage format used by bittorrent, such +as .torrent files, tracker announce and scrape responses and some wire protocol +extensions. libtorrent provides an efficient framework for decoding bencoded +data through `bdecode()`_ function. + +There are two separate mechanisms for *encoding* and *decoding*. When decoding, +use the `bdecode()`_ function that returns a bdecode_node_. When encoding, use +`bencode()`_ taking an entry_ object. + +The key property of `bdecode()`_ is that it does not copy any data out of the +buffer that was parsed. It builds the tree structures of references pointing +into the buffer. The buffer must stay alive and valid for as long as the +bdecode_node_ is in use. + +For performance details on `bdecode()`_, see the `blog post about it`__. + +__ https://blog.libtorrent.org/2015/03/bdecode-parsers/ + +.. _session: reference-Core.html#session +.. _session_handle: reference-Core.html#session_handle +.. _add_torrent_params: reference-Core.html#add_torrent_params +.. _`add_torrent()`: reference-Core.html#add_torrent() +.. _`async_add_torrent()`: reference-Core.html#add_torrent() +.. _torrent_handle: reference-Core.html#torrent_handle +.. _`pop_alerts()`: reference-Core.html#pop_alerts() +.. _`alert`: reference-Alerts.html#alert +.. _`alert_cast<>`: reference-Alerts.html#alert_cast() +.. _torrent_finished_alert: reference-Alerts.html#torrent-finished-alert +.. _listen_interfaces: reference-Settings.html#listen_interfaces +.. _`add_torrent_alert`: reference-Alerts.html#add-torrent-alert +.. _settings_pack: reference-Settings.html#settings_pack +.. _alert_mask: reference-Settings.html#alert_mask +.. _`category flags`: reference-Alerts.html#category_t +.. _`apply_settings()`: reference-Core.html#apply_settings() +.. _`session::abort()`: reference-Core.html#abort() +.. _session_proxy: reference-Core.html#session_proxy +.. _`post_torrent_updates()`: reference-Core.html#post_torrent_updates() +.. _torrent_status: reference-Core.html#torrent_status +.. _state_update_alert: reference-Alerts.html#state_update_alert +.. _scalability: https://blog.libtorrent.org/2011/11/scalable-interfaces/ +.. _`save_resume_data()`: reference-Core.html#save_resume_data() +.. _save_resume_data_alert: reference-Alerts.html#save_resume_data_alert +.. _save_resume_data_failed_alert: reference-Alerts.html#save_resume_data_failed_alert +.. _bencoded: https://en.wikipedia.org/wiki/Bencode +.. _entry: reference-Bencoding.html#entry +.. _`bencode()`: reference-Bencoding.html#bencode() +.. _torrent_info: reference-Core.html#torrent_info +.. _`add_torrent_params::resume_data`: reference-Core.html#resume_data +.. _`bdecode()`: reference-Bdecoding.html#bdecode() +.. _bdecode_node: reference-Bdecoding.html#bdecode-node +.. _`write_resume_data()`: http://libtorrent.org/reference-Core.html#write_resume_data() +.. _`write_resume_data_buf()`: http://libtorrent.org/reference-Core.html#write_resume_data_buf() +.. _`read_resume_data()`: reference-Core.html#read_resume_data() + diff --git a/docs/udp_tracker_protocol.html b/docs/udp_tracker_protocol.html new file mode 100644 index 0000000..61e6134 --- /dev/null +++ b/docs/udp_tracker_protocol.html @@ -0,0 +1,578 @@ + + + + + + + +Bittorrent UDP-tracker protocol extension + + + + + + +
    +
    + + libtorrent logo + +
    +

    Bittorrent UDP-tracker protocol extension

    + +++ + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    + +
    +

    introduction

    +

    A tracker with the protocol "udp://" in its URI +is supposed to be contacted using this protocol.

    +

    This protocol is supported by +xbt-tracker.

    +

    For additional information and descriptions of +the terminology used in this document, see +the protocol specification

    +

    All values are sent in network byte order (big-endian). The sizes +are specified with ANSI-C standard types.

    +

    If no response to a request is received within 15 seconds, resend +the request. If no reply has been received after 60 seconds, stop +retrying.

    +
    +
    +

    connecting

    +

    Client sends packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int64_tconnection_idMust be initialized to 0x41727101980 +in network byte order. This will +identify the protocol.
    int32_taction0 for a connection request
    int32_ttransaction_idRandomized by client.
    +

    Server replies with packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionDescribes the type of packet, in this +case it should be 0, for connect. +If 3 (for error) see errors.
    int32_ttransaction_idMust match the transaction_id sent +from the client.
    int64_tconnection_idA connection id, this is used when +further information is exchanged with +the tracker, to identify you. +This connection id can be reused for +multiple requests, but if it's cached +for too long, it will not be valid +anymore.
    +
    +
    +

    announcing

    +

    Client sends packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int64_tconnection_idThe connection id acquired from +establishing the connection.
    int32_tactionAction. in this case, 1 for announce. +See actions.
    int32_ttransaction_idRandomized by client.
    int8_t[20]info_hashThe info-hash of the torrent you want +announce yourself in.
    int8_t[20]peer_idYour peer id.
    int64_tdownloadedThe number of byte you've downloaded +in this session.
    int64_tleftThe number of bytes you have left to +download until you're finished.
    int64_tuploadedThe number of bytes you have uploaded +in this session.
    int32_tevent

    The event, one of

    +
    +
      +
    • none = 0
    • +
    • completed = 1
    • +
    • started = 2
    • +
    • stopped = 3
    • +
    +
    +
    uint32_tipYour ip address. Set to 0 if you want +the tracker to use the sender of +this UDP packet.
    uint32_tkeyA unique key that is randomized by the +client.
    int32_tnum_wantThe maximum number of peers you want +in the reply. Use -1 for default.
    uint16_tportThe port you're listening on.
    uint16_textensionsSee extensions
    +

    Server replies with packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionThe action this is a reply to. Should +in this case be 1 for announce. +If 3 (for error) see errors. +See actions.
    int32_ttransaction_idMust match the transaction_id sent +in the announce request.
    int32_tintervalthe number of seconds you should wait +until re-announcing yourself.
    int32_tleechersThe number of peers in the swarm that +has not finished downloading.
    int32_tseedersThe number of peers in the swarm that +has finished downloading and are +seeding.
    +

    The rest of the server reply is a variable number of the following structure:

    + +++++ + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tipThe ip of a peer in the swarm.
    uint16_tportThe peer's listen port.
    +
    +
    +

    scraping

    +

    Client sends packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int64_tconnection_idThe connection id retrieved from the +establishing of the connection.
    int32_tactionThe action, in this case, 2 for +scrape. See actions.
    int32_ttransaction_idRandomized by client.
    +

    The following structure is repeated for each info-hash to scrape, but limited by +the MTU.

    + +++++ + + + + + + + + + + + + +
    sizenamedescription
    int8_t[20]info_hashThe info hash that is to be scraped.
    +

    Server replies with packet:

    + +++++ + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionThe action, should in this case be +2 for scrape. +If 3 (for error) see errors.
    int32_ttransaction_idMust match the sent transaction id.
    +

    The rest of the packet contains the following structures once for each info-hash +you asked in the scrape request.

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tcompleteThe current number of connected seeds.
    int32_tdownloadedThe number of times this torrent has +been downloaded.
    int32_tincompleteThe current number of connected +leechers.
    +
    +
    +

    errors

    +

    In case of a tracker error,

    +

    server replies packet:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int32_tactionThe action, in this case 3, for error. +See actions.
    int32_ttransaction_idMust match the transaction_id sent +from the client.
    int8_t[]error_stringThe rest of the packet is a string +describing the error.
    +
    +
    +

    actions

    +

    The action fields has the following encoding:

    +
    +
      +
    • connect = 0
    • +
    • announce = 1
    • +
    • scrape = 2
    • +
    • error = 3 (only in server replies)
    • +
    +
    +
    +
    +

    extensions

    +

    The extensions field is a bitmask. The following +bits are assigned:

    +
    + +
    +

    If multiple bits are present in the extension field, the extension +bodies are appended to the packet in the order of least significant +bit first. For instance, if both bit 1 and 2 are set, the extension +represented by bit 1 comes first, followed by the extension represented +by bit 2.

    +
    +

    authentication

    +

    The packet will have an authentication part +appended to it. It has the following format:

    + +++++ + + + + + + + + + + + + + + + + + + + + +
    sizenamedescription
    int8_tusername_lengthThe number of characters in the +username.
    int8_t[]usernameThe username, the number of characters +as specified in the previous field.
    uint8_t[8]passwd_hashsha1(packet + sha1(password)) +The packet in this case means the +entire packet except these 8 bytes +that are the password hash. These are +the 8 first bytes (most significant) +from the 20 bytes hash calculated.
    +
    +
    +
    +

    request string

    +

    The request string extension is meant to allow torrent creators pass along +cookies back to the tracker. This can be useful for authenticating that a +torrent is allowed to be tracked by a tracker for instance. It could also +be used to authenticate users by generating torrents with unique tokens +in the tracker URL for each user. The extension body has the following format:

    + +++++ + + + + + + + + + + + + + + + + +
    sizenamedescription
    int8_trequest lengthThe number of bytes in the request +string.
    int8_t[]request stringThe string that comes after the host- +name and port in the UDP tracker URL. +Typically this starts with "/announce" +The bittorrent client is not expected +to append query string arguments for +stats reporting, like "uploaded" and +"downloaded" since this is already +reported in the UDP tracker protocol. +However, the client is free to add +arguments as extensions.
    +
    +
    +

    credits

    +

    Protocol designed by Olaf van der Spek and extended by Arvid Norberg

    +
    + +
    +
    +
    + + +
    + + diff --git a/docs/udp_tracker_protocol.rst b/docs/udp_tracker_protocol.rst new file mode 100644 index 0000000..5f78559 --- /dev/null +++ b/docs/udp_tracker_protocol.rst @@ -0,0 +1,320 @@ +Bittorrent UDP-tracker protocol extension +========================================= + +:Author: Arvid Norberg, arvid@libtorrent.org + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + + +introduction +------------ + +A tracker with the protocol "udp://" in its URI +is supposed to be contacted using this protocol. + +This protocol is supported by +xbt-tracker_. + + +.. _xbt-tracker: http://xbtt.sourceforge.net + +For additional information and descriptions of +the terminology used in this document, see +the `protocol specification`__ + +__ http://wiki.theory.org/index.php/BitTorrentSpecification + +All values are sent in network byte order (big-endian). The sizes +are specified with ANSI-C standard types. + +If no response to a request is received within 15 seconds, resend +the request. If no reply has been received after 60 seconds, stop +retrying. + + +connecting +---------- + +Client sends packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int64_t | connection_id | Must be initialized to 0x41727101980 | +| | | in network byte order. This will | +| | | identify the protocol. | ++-------------+---------------------+----------------------------------------+ +| int32_t | action | 0 for a connection request | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Randomized by client. | ++-------------+---------------------+----------------------------------------+ + +Server replies with packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | Describes the type of packet, in this | +| | | case it should be 0, for connect. | +| | | If 3 (for error) see errors_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the transaction_id sent | +| | | from the client. | ++-------------+---------------------+----------------------------------------+ +| int64_t | connection_id | A connection id, this is used when | +| | | further information is exchanged with | +| | | the tracker, to identify you. | +| | | This connection id can be reused for | +| | | multiple requests, but if it's cached | +| | | for too long, it will not be valid | +| | | anymore. | ++-------------+---------------------+----------------------------------------+ + + +announcing +---------- + +Client sends packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int64_t | connection_id | The connection id acquired from | +| | | establishing the connection. | ++-------------+---------------------+----------------------------------------+ +| int32_t | action | Action. in this case, 1 for announce. | +| | | See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Randomized by client. | ++-------------+---------------------+----------------------------------------+ +| int8_t[20] | info_hash | The info-hash of the torrent you want | +| | | announce yourself in. | ++-------------+---------------------+----------------------------------------+ +| int8_t[20] | peer_id | Your peer id. | ++-------------+---------------------+----------------------------------------+ +| int64_t | downloaded | The number of byte you've downloaded | +| | | in this session. | ++-------------+---------------------+----------------------------------------+ +| int64_t | left | The number of bytes you have left to | +| | | download until you're finished. | ++-------------+---------------------+----------------------------------------+ +| int64_t | uploaded | The number of bytes you have uploaded | +| | | in this session. | ++-------------+---------------------+----------------------------------------+ +| int32_t | event | The event, one of | +| | | | +| | | * none = 0 | +| | | * completed = 1 | +| | | * started = 2 | +| | | * stopped = 3 | ++-------------+---------------------+----------------------------------------+ +| uint32_t | ip | Your ip address. Set to 0 if you want | +| | | the tracker to use the ``sender`` of | +| | | this UDP packet. | ++-------------+---------------------+----------------------------------------+ +| uint32_t | key | A unique key that is randomized by the | +| | | client. | ++-------------+---------------------+----------------------------------------+ +| int32_t | num_want | The maximum number of peers you want | +| | | in the reply. Use -1 for default. | ++-------------+---------------------+----------------------------------------+ +| uint16_t | port | The port you're listening on. | ++-------------+---------------------+----------------------------------------+ +| uint16_t | extensions | See extensions_ | ++-------------+---------------------+----------------------------------------+ + + +Server replies with packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | The action this is a reply to. Should | +| | | in this case be 1 for announce. | +| | | If 3 (for error) see errors_. | +| | | See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the transaction_id sent | +| | | in the announce request. | ++-------------+---------------------+----------------------------------------+ +| int32_t | interval | the number of seconds you should wait | +| | | until re-announcing yourself. | ++-------------+---------------------+----------------------------------------+ +| int32_t | leechers | The number of peers in the swarm that | +| | | has not finished downloading. | ++-------------+---------------------+----------------------------------------+ +| int32_t | seeders | The number of peers in the swarm that | +| | | has finished downloading and are | +| | | seeding. | ++-------------+---------------------+----------------------------------------+ + +The rest of the server reply is a variable number of the following structure: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | ip | The ip of a peer in the swarm. | ++-------------+---------------------+----------------------------------------+ +| uint16_t | port | The peer's listen port. | ++-------------+---------------------+----------------------------------------+ + + +scraping +-------- + +Client sends packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int64_t | connection_id | The connection id retrieved from the | +| | | establishing of the connection. | ++-------------+---------------------+----------------------------------------+ +| int32_t | action | The action, in this case, 2 for | +| | | scrape. See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Randomized by client. | ++-------------+---------------------+----------------------------------------+ + +The following structure is repeated for each info-hash to scrape, but limited by +the MTU. + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int8_t[20] | info_hash | The info hash that is to be scraped. | ++-------------+---------------------+----------------------------------------+ + + +Server replies with packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | The action, should in this case be | +| | | 2 for scrape. | +| | | If 3 (for error) see errors_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the sent transaction id. | ++-------------+---------------------+----------------------------------------+ + +The rest of the packet contains the following structures once for each info-hash +you asked in the scrape request. + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | complete | The current number of connected seeds. | ++-------------+---------------------+----------------------------------------+ +| int32_t | downloaded | The number of times this torrent has | +| | | been downloaded. | ++-------------+---------------------+----------------------------------------+ +| int32_t | incomplete | The current number of connected | +| | | leechers. | ++-------------+---------------------+----------------------------------------+ + + +errors +------ + +In case of a tracker error, + +server replies packet: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int32_t | action | The action, in this case 3, for error. | +| | | See actions_. | ++-------------+---------------------+----------------------------------------+ +| int32_t | transaction_id | Must match the transaction_id sent | +| | | from the client. | ++-------------+---------------------+----------------------------------------+ +| int8_t[] | error_string | The rest of the packet is a string | +| | | describing the error. | ++-------------+---------------------+----------------------------------------+ + + +actions +------- + +The action fields has the following encoding: + + * connect = 0 + * announce = 1 + * scrape = 2 + * error = 3 (only in server replies) + + +extensions +---------- + +The extensions field is a bitmask. The following +bits are assigned: + + * 1 = authentication_. + * 2 = `request string`_. + +If multiple bits are present in the extension field, the extension +bodies are appended to the packet in the order of least significant +bit first. For instance, if both bit 1 and 2 are set, the extension +represented by bit 1 comes first, followed by the extension represented +by bit 2. + +authentication +~~~~~~~~~~~~~~ + +The packet will have an authentication part +appended to it. It has the following format: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int8_t | username_length | The number of characters in the | +| | | username. | ++-------------+---------------------+----------------------------------------+ +| int8_t[] | username | The username, the number of characters | +| | | as specified in the previous field. | ++-------------+---------------------+----------------------------------------+ +| uint8_t[8] | passwd_hash | sha1(packet + sha1(password)) | +| | | The packet in this case means the | +| | | entire packet except these 8 bytes | +| | | that are the password hash. These are | +| | | the 8 first bytes (most significant) | +| | | from the 20 bytes hash calculated. | ++-------------+---------------------+----------------------------------------+ + +request string +-------------- + +The request string extension is meant to allow torrent creators pass along +cookies back to the tracker. This can be useful for authenticating that a +torrent is allowed to be tracked by a tracker for instance. It could also +be used to authenticate users by generating torrents with unique tokens +in the tracker URL for each user. The extension body has the following format: + ++-------------+---------------------+----------------------------------------+ +| size | name | description | ++=============+=====================+========================================+ +| int8_t | request length | The number of bytes in the request | +| | | string. | ++-------------+---------------------+----------------------------------------+ +| int8_t[] | request string | The string that comes after the host- | +| | | name and port in the UDP tracker URL. | +| | | Typically this starts with "/announce" | +| | | The bittorrent client is not expected | +| | | to append query string arguments for | +| | | stats reporting, like "uploaded" and | +| | | "downloaded" since this is already | +| | | reported in the UDP tracker protocol. | +| | | However, the client is free to add | +| | | arguments as extensions. | ++-------------+---------------------+----------------------------------------+ + +credits +------- + +Protocol designed by Olaf van der Spek and extended by Arvid Norberg + diff --git a/docs/upgrade_to_1.2-ref.html b/docs/upgrade_to_1.2-ref.html new file mode 100644 index 0000000..dcd78d1 --- /dev/null +++ b/docs/upgrade_to_1.2-ref.html @@ -0,0 +1,220 @@ + + + + + + +Upgrading to libtorrent 1.2 + + + + + + + +
    +
    + + + + +
    +

    Upgrading to libtorrent 1.2

    + +++ + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    + +

    libtorrent version 1.2 comes with some significant updates in the API. +This document summarizes the changes affecting library users.

    +
    +

    C++98 no longer supported

    +

    With libtorrent 1.2, C++98 is no longer supported, you need a compiler capable +of at least C++11 to build libtorrent.

    +

    This also means libtorrent types now support move.

    +
    +
    +

    listen interfaces

    +

    There's a subtle change in how the listen_interfaces setting is interpreted +in 1.2 compared to 1.1.

    +

    In libtorrent-1.1, if you listen to 0.0.0.0:6881 (which was the default), +not only would an IPv4 listen socket be opened (bound to INADDR_ANY) but also an +IPv6 socket would be opened.

    +

    In libtorrent-1.2, if you listen to 0.0.0.0:6881 only the IPv4 INADDR_ANY is +opened as a listen socket. If you want to listen to both IPv4 and IPv6, you need +to listen to 0.0.0.0:6881,[::]:6881.

    +
    +
    +

    forward declaring libtorrent types deprecated

    +

    Clients are discouraged from forward declaring types from libtorrent. +Instead, include the <libtorrent/fwd.hpp> header.

    +

    A future release will introduce ABI versioning using an inline namespace, which will break any forward declarations by clients.

    +

    There is a new namespace alias, lt which is shorthand for libtorrent. +In the future, libtorrent will be the alias and lt the namespace name. +With no forward declarations inside libtorrent's namespace though, there should not be any reason for clients to re-open the namespace.

    +
    +
    +

    resume data handling

    +

    To significantly simplify handling of resume data, the previous way of handling it is deprecated. +resume data is no longer passed in as a flat buffer in the add_torrent_params. +The add_torrent_params structure itself is the resume data now.

    +

    In order to parse the bencoded fast resume file (which is still the same format, and backwards compatible) use the read_resume_data() function.

    +

    Similarly, when saving resume data, the save_resume_data_alert now has a params field of type add_torrent_params which contains the resume data. +This object can be serialized into the bencoded form using write_resume_data().

    +

    This give the client full control over which properties should be loaded from the resume data and which should be controlled by the client directly. +The flags flag_override_resume_data, flag_merge_resume_trackers, flag_use_resume_save_path and flag_merge_resume_http_seeds have all been deprecated, since they are no longer needed.

    +

    The old API is still supported as long as libtorrent is built with deprecated functions enabled (which is the default). +It will be performing slightly better without deprecated functions present.

    +
    +
    +

    rate_limit_utp changed defaults

    +

    The setting rate_limit_utp was deprecated in libtorrent 1.1. +When building without deprecated features (deprecated-functions=off) the default behavior also changed to have rate limits apply to utp sockets too. +In order to be more consistent between the two build configurations, the default value has changed to true. +The new mechanism provided to apply special rate limiting rules is peer classes. +In order to implement the old behavior of not rate limiting uTP peers, one can set up a peer class for all uTP peers, to make the normal peer classes not apply to them (which is where the rate limits are set).

    +
    +
    +

    announce entry multi-home support

    +

    The announce_entry type now captures status on individual endpoints, as opposed to treating every tracker behind the same name as a single tracker. +This means some properties has moved into the announce_endpoint structure, and an announce entry has 0 or more endpoints.

    +
    +
    +

    alerts no longer cloneable

    +

    As part of the transition to a more efficient handling of alerts, 1.1 allocated them in a contiguous, heterogeneous, vector. +This means they are no longer heap allocated nor held by a smart pointer. +The clone() member on alerts was deprecated in 1.1 and removed in 1.2. +To pass alerts across threads, instead pull out the relevant information from the alerts and pass that across.

    +
    +
    +

    progress alert category

    +

    The alert::progress_notification category has been deprecated. +Alerts posted in this category are now also posted in one of these new categories:

    +
      +
    • alert::block_progress_notification
    • +
    • alert::piece_progress_notification
    • +
    • alert::file_progress_notification
    • +
    • alert::upload_notification
    • +
    +
    +
    +

    boost replaced by std

    +

    boost::shared_ptr has been replaced by std::shared_ptr in the libtorrent API. +The same goes for <cstdint> types, instead of boost::int64_t, libtorrent now uses std::int64_t. +Instead of boost::array, std::array is used, and boost::function has been replaced by std::function.

    +
    +
    +

    strong typedefs

    +

    In order to strengthen type-safety, libtorrent now uses special types to represent certain indexes and ID types. +Any integer referring to a piece index, now has the type piece_index_t, and indices to files in a torrent, use file_index_t. +Similarly, time points and duration now use time_point and duration from the <chrono> standard library.

    +

    The specific types have typedefs at lt::time_point and lt::duration, and the clock used by libtorrent is lt::clock_type.`

    +
    +
    +

    strongly typed flags

    +

    Enum flags have been replaced by strongly typed flags. +This means their implicit conversion to and from int is deprecated. +For example, the following expressions are deprecated:

    +
    +if ((atp.flags & add_torrent_params::flag_paused) == 0)
    +
    +atp.flags = 0;
    +
    +

    Insted say:

    +
    +if (!(atp.flags & torrent_flags::paused))
    +
    +atp.flags = {};
    +
    +

    (Also note that in this specific example, the flags moved out of the add_torrent_params structure, but this is unrelated to them also having stronger types).

    +
    +
    +

    span<> and string_view

    +

    The interface has adopted string_view (from boost for now) and span<> (custom implementation for now). +This means some function calls that previously took char const* or std::string may now take an lt::string_view. +Similarly, functions that previously would take a pointer and length pair will now take a span<>.

    +
    +
    +

    periphery utility functions no longer exported

    +

    Historically, libtorrent has exported functions not essential to its core bittorrent functionality. +Such as filesystem functions like directory, file classes and remove, create_directory functions. +Path manipulation functions like combine_path, extension, split_path etc. +String manipulation functions like from_hex and to_hex. +Time functions like time_now. These functions are no longer available to clients, and some have been removed from the library. +Instead, it is recommended to use boost.filesystem or the experimental filesystem TS.

    +
    +
    +

    plugins

    +

    libtorrent session plugins no longer have all callbacks called unconditionally. +The plugin has to register which callbacks it's interested in receiving by returning a bitmask from feature_flags_t implemented_features(). +The return value is documented in the plugin class.

    +
    +
    +

    RSS functions removed

    +

    The deprecated RSS functions have been removed from the library interface.

    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/utp.html b/docs/utp.html new file mode 100644 index 0000000..dd3c0c6 --- /dev/null +++ b/docs/utp.html @@ -0,0 +1,359 @@ + + + + + + +utp.rst + + + + + + + +
    +
    + + + + +
    + + +++ + + + + + +
    Author:Arvid Norberg, arvid@libtorrent.org
    Version:1.2.9
    + +
    +

    uTP

    +

    uTP (uTorrent transport protocol) is a transport protocol which uses one-way +delay measurements for its congestion controller. This article is about uTP +in general and specifically about libtorrent's implementation of it.

    +
    +

    rationale

    +

    One of the most common problems users are experiencing using bittorrent is +that their internet "stops working". This can be caused by a number of things, +for example:

    +
      +
    1. a home router that crashes or slows down when its NAT pin-hole +table overflows, triggered by DHT or simply many TCP connections.
    2. +
    3. a home router that crashes or slows down by UDP traffic (caused by +the DHT)
    4. +
    5. a home DSL or cable modem having its send buffer filled up by outgoing +data, and the buffer fits seconds worth of bytes. This adds seconds +of delay on interactive traffic. For a web site that needs 10 round +trips to load this may mean 10s of seconds of delay to load compared +to without bittorrent. Skype or other delay sensitive applications +would be affected even more.
    6. +
    +

    This document will cover (3).

    +

    Typically this is solved by asking the user to enter a number of bytes +that the client is allowed to send per second (i.e. setting an upload +rate limit). The common recommendation is to set this limit to 80% of the +uplink's capacity. This is to leave some headroom for things like TCP +ACKs as well as the user's interactive use of the connection such as +browsing the web or checking email.

    +

    There are two major drawbacks with this technique:

    +
      +
    1. The user needs to actively make this setting (very few protocols +require the user to provide this sort of information). This also +means the user needs to figure out what its up-link capacity is. +This is unfortunately a number that many ISPs are not advertising +(because it's often much lower than the download capacity) which +might make it hard to find.
    2. +
    3. The 20% headroom is wasted most of the time. Whenever the user +is not using the internet connection for anything, those extra 20% +could have been used by bittorrent to upload, but they're already +allocated for interactive traffic. On top of that, 20% of the up-link +is often not enough to give a good and responsive browsing experience.
    4. +
    +

    The ideal bandwidth allocation would be to use 100% for bittorrent when +there is no interactive cross traffic, and 100% for interactive traffic +whenever there is any. This would not waste any bandwidth while the user +is idling, and it would make for a much better experience when the user +is using the internet connection for other things.

    +

    This is what uTP does.

    +
    +
    +

    TCP

    +

    The reason TCP will fill the send buffer, and cause the delay on all traffic, +is because its congestion control is only based on packet loss (and timeout).

    +

    Since the modem is buffering, packets won't get dropped until the entire queue +is full, and no more packets will fit. The packets will be dropped, TCP will +detect this within an RTT or so. When TCP notices a packet loss, it will slow +down its send rate and the queue will start to drain again. However, TCP will +immediately start to ramp up its send rate again until the buffer is full and +it detects packet loss again.

    +

    TCP is designed to fully utilize the link capacity, without causing congestion. +Whenever it sense congestion (through packet loss) it backs off. TCP is not +designed to keep delays low. When you get the first packet loss (assuming the +kind of queue described above, tail-queue) it is already too late. Your queue +is full and you have the maximum amount of delay your modem can provide.

    +

    TCP controls its send rate by limiting the number of bytes in-flight at any +given time. This limit is called congestion window (cwnd for short). During +steady state, the congestion window is constantly increasing linearly. Each +packet that is successfully transferred will increase cwnd.

    +
    +            cwnd
    +send_rate = ----
    +            RTT
    +
    +

    Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause +the send rate to be lower and a larger cwnd will cause the send rate to be +higher.

    +

    Using a congestion window instead of controlling the rate directly is simple +because it also introduces an upper bound for memory usage for packets that +haven't been ACKed yet and needs to be kept around.

    +

    The behavior of TCP, where it bumps up against the ceiling, backs off and then +starts increasing again until it hits the ceiling again, forms a saw tooth shape. +If the modem wouldn't have any send buffer at all, a single TCP stream would +not be able to fully utilize the link because of this behavior, since it would +only fully utilize the link right before the packet loss and the back-off.

    +
    +
    +

    LEDBAT congestion controller

    +

    The congestion controller in uTP is called LEDBAT, which also is an IETF working +group attempting to standardize it. The congestion controller, on top of reacting +to packet loss the same way TCP does, also reacts to changes in delays.

    +

    For any uTP (or LEDBAT) implementation, there is a target delay. This is the +amount of delay that is acceptable, and is in fact targeted for the connection. +The target delay is defined to 25 ms in LEDBAT, uTorrent uses 100 ms and +libtorrent uses 75 ms. Whenever a delay measurement is lower than the target, +cwnd is increased proportional to (target_delay - delay). Whenever the measurement +is higher than the target, cwnd is decreased proportional to (delay - target_delay).

    +

    It can simply be expressed as:

    +
    +cwnd += gain * (target_delay - delay)
    +
    +cwnd_thumb.png +

    Similarly to TCP, this is scaled so that the increase is evened out over one RTT.

    +

    The linear controller will adjust the cwnd more for delays that are far off the +target, and less for delays that are close to the target. This makes it converge +at the target delay. Although, due to noise there is almost always some amount of +oscillation. This oscillation is typically smaller than the saw tooth TCP forms.

    +

    The figure to the right shows how (TCP) cross traffic causes uTP to essentially +entirely stop sending anything. Its delay measurements are mostly well above the target +during this time. The cross traffic is only a single TCP stream in this test.

    +

    As soon as the cross traffic ceases, uTP will pick up its original send rate within +a second.

    +

    Since uTP constantly measures the delay, with every single packet, the reaction time +to cross traffic causing delays is a single RTT (typically a fraction of a second).

    +
    +
    +

    one way delays

    +

    uTP measures the delay imposed on packets being sent to the other end +of the connection. This measurement only includes buffering delay along +the link, not propagation delay (the speed of light times distance) nor +the routing delay (the time routers spend figuring out where to forward +the packet). It does this by always comparing all measurements to a +baseline measurement, to cancel out any fixed delay. By focusing on the +variable delay along a link, it will specifically detect points where +there might be congestion, since those points will have buffers.

    +delays_thumb.png +

    Delay on the return link is explicitly not included in the delay measurement. +This is because in a peer-to-peer application, the other end is likely to also +be connected via a modem, with the same send buffer restrictions as we assume +for the sending side. The other end having its send queue full is not an indication +of congestion on the path going the other way.

    +

    In order to measure one way delays for packets, we cannot rely on clocks being +synchronized, especially not at the microsecond level. Instead, the actual time +it takes for a packet to arrive at the destination is not measured, only the changes +in the transit time is measured.

    +

    Each packet that is sent includes a time stamp of the current time, in microseconds, +of the sending machine. The receiving machine calculates the difference between its +own timestamp and the one in the packet and sends this back in the ACK. This difference, +since it is in microseconds, will essentially be a random 32 bit number. However, +the difference will stay somewhat similar over time. Any changes in this difference +indicates that packets are either going through faster or slower.

    +

    In order to measure the one-way buffering delay, a base delay is established. The +base delay is the lowest ever seen value of the time stamp difference. Each delay +sample we receive back, is compared against the base delay and the delay is the +difference.

    +

    This is the delay that's fed into the congestion controller.

    +

    A histogram of typical delay measurements is shown to the right. This is from +a transfer between a cable modem connection and a DSL connection.

    +

    The details of the delay measurements are slightly more complicated since the +values needs to be able to wrap (cross the 2^32 boundary and start over at 0).

    +
    +
    +

    Path MTU discovery

    +

    MTU is short for Maximum Transfer Unit and describes the largest packet size that +can be sent over a link. Any datagrams which size exceeds this limit will either +be fragmented or dropped. A fragmented datagram means that the payload is split up +in multiple packets, each with its own individual packet header.

    +

    There are several reasons to avoid sending datagrams that get fragmented:

    +
      +
    1. A fragmented datagram is more likely to be lost. If any fragment is lost, +the whole datagram is dropped.
    2. +
    3. Bandwidth is likely to be wasted. If the datagram size is not divisible +by the MTU the last packet will not contain as much payload as it could, and the +payload over protocol header ratio decreases.
    4. +
    5. It's expensive to fragment datagrams. Few routers are optimized to handle large +numbers of fragmented packets. Datagrams that have to fragment are likely to +be delayed significantly, and contribute to more CPU being used on routers. +Typically fragmentation (and other advanced IP features) are implemented in +software (slow) and not hardware (fast).
    6. +
    +

    The path MTU is the lowest MTU of any link along a path from two endpoints on the +internet. The MTU bottleneck isn't necessarily at one of the endpoints, but can +be anywhere in between.

    +

    The most common MTU is 1500 bytes, which is the largest packet size for ethernet +networks. Many home DSL connections, however, tunnel IP through PPPoE (Point to +Point Protocol over Ethernet. Yes, that is the old dial-up modem protocol). This +protocol uses up 8 bytes per packet for its own header.

    +

    If the user happens to be on an internet connection over a VPN, it will add another +layer, with its own packet headers.

    +

    In short; if you would pick the largest possible packet size on an ethernet network, +1472, and stick with it, you would be quite likely to generate fragments for a lot +of connections. The fragments that will be created will be very small and especially +inflate the overhead waste.

    +

    The other approach of picking a very conservative packet size, that would be very +unlikely to get fragmented has the following drawbacks:

    +
      +
    1. People on good, normal, networks will be penalized with a small packet size. +Both in terms of router load but also bandwidth waste.
    2. +
    3. Software routers are typically not limited by the number of bytes they can route, +but the number of packets. Small packets means more of them, and more load on +software routers.
    4. +
    +

    The solution to the problem of finding the optimal packet size, is to dynamically +adjust the packet size and search for the largest size that can make it through +without being fragmented along the path.

    +

    To help do this, you can set the DF bit (Don't Fragment) in your Datagrams. This +asks routers that otherwise would fragment packets to instead drop them, and send +back an ICMP message reporting the MTU of the link the packet couldn't fit. With +this message, it's very simple to discover the path MTU. You simply mark your packets +not to be fragmented, and change your packet size whenever you receive the ICMP +packet-too-big message.

    +

    Unfortunately it's not quite that simple. There are a significant number of firewalls +in the wild blocking all ICMP messages. This means we can't rely on them, we also have +to guess that a packet was dropped because of its size. This is done by only marking +certain packets with DF, and if all other packets go through, except for the MTU probes, +we know that we need to lower our packet sizes.

    +

    If we set up bounds for the path MTU (say the minimum internet MTU, 576 and ethernet's 1500), +we can do a binary search for the MTU. This would let us find it in just a few round-trips.

    +

    On top of this, libtorrent has an optimization where it figures out which interface a +uTP connection will be sent over, and initialize the MTU ceiling to that interface's MTU. +This means that a VPN tunnel would advertise its MTU as lower, and the uTP connection would +immediately know to send smaller packets, no search required. It also has the side-effect +of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links +with jumbo frames.

    +
    +
    +

    clock drift

    +our_delay_base_thumb.png +

    Clock drift is clocks progressing at different rates. It's different from clock +skew which means clocks set to different values (but which may progress at the same +rate).

    +

    Any clock drift between the two machines involved in a uTP transfer will result +in systematically inflated or deflated delay measurements.

    +

    This can be solved by letting the base delay be the lowest seen sample in the last +n minutes. This is a trade-off between seeing a single packet go straight through +the queue, with no delay, and the amount of clock drift one can assume on normal computers.

    +

    It turns out that it's fairly safe to assume that one of your packets will in fact go +straight through without any significant delay, once every 20 minutes or so. However, +the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms +is quite significant, especially if your target delay is 25 ms (as in the LEDBAT spec).

    +

    Clocks progresses at different rates depending on temperature. This means computers +running hot are likely to have a clock drift compared to computers running cool.

    +

    So, by updating the delay base periodically based on the lowest seen sample, you'll either +end up changing it upwards (artificially making the delay samples appear small) without +the congestion or delay actually having changed, or you'll end up with a significant clock +drift and have artificially low samples because of that.

    +

    The solution to this problem is based on the fact that the clock drift is only a problem +for one of the sides of the connection. Only when your delay measurements keep increasing +is it a problem. If your delay measurements keep decreasing, the samples will simply push +down the delay base along with it. With this in mind, we can simply keep track of the +other end's delay measurements as well, applying the same logic to it. Whenever the +other end's base delay is adjusted downwards, we adjust our base delay upwards by the same +amount.

    +

    This will accurately keep the base delay updated with the clock drift and improve +the delay measurements. The figure on the right shows the absolute timestamp differences +along with the base delay. The slope of the measurements is caused by clock drift.

    +

    For more information on the clock drift compensation, see the slides from BitTorrent's +presentation at IPTPS10.

    +
    +
    +

    features

    +

    libtorrent's uTP implementation includes the following features:

    +
      +
    • Path MTU discovery, including jumbo frames and detecting restricted +MTU tunnels. Binary search packet sizes to find the largest non-fragmented.
    • +
    • Selective ACK. The ability to acknowledge individual packets in the +event of packet loss
    • +
    • Fast resend. The first time a packet is lost, it's resent immediately. +Triggered by duplicate ACKs.
    • +
    • Nagle's algorithm. Minimize protocol overhead by attempting to lump +full packets of payload together before sending a packet.
    • +
    • Delayed ACKs to minimize protocol overhead.
    • +
    • Microsecond resolution timestamps.
    • +
    • Advertised receive window, to support download rate limiting.
    • +
    • Correct handling of wrapping sequence numbers.
    • +
    • Easy configuration of target-delay, gain-factor, timeouts, delayed-ACK +and socket buffers.
    • +
    +
    +
    + +
    +
    +
    + +
    + +
    + + diff --git a/docs/utp.rst b/docs/utp.rst new file mode 100644 index 0000000..879fb43 --- /dev/null +++ b/docs/utp.rst @@ -0,0 +1,342 @@ +.. include:: header.rst + +.. contents:: Table of contents + :depth: 2 + :backlinks: none + +uTP +=== + +uTP (uTorrent transport protocol) is a transport protocol which uses one-way +delay measurements for its congestion controller. This article is about uTP +in general and specifically about libtorrent's implementation of it. + +rationale +--------- + +One of the most common problems users are experiencing using bittorrent is +that their internet "stops working". This can be caused by a number of things, +for example: + +1. a home router that crashes or slows down when its NAT pin-hole + table overflows, triggered by DHT or simply many TCP connections. + +2. a home router that crashes or slows down by UDP traffic (caused by + the DHT) + +3. a home DSL or cable modem having its send buffer filled up by outgoing + data, and the buffer fits seconds worth of bytes. This adds seconds + of delay on interactive traffic. For a web site that needs 10 round + trips to load this may mean 10s of seconds of delay to load compared + to without bittorrent. Skype or other delay sensitive applications + would be affected even more. + +This document will cover (3). + +Typically this is solved by asking the user to enter a number of bytes +that the client is allowed to send per second (i.e. setting an upload +rate limit). The common recommendation is to set this limit to 80% of the +uplink's capacity. This is to leave some headroom for things like TCP +ACKs as well as the user's interactive use of the connection such as +browsing the web or checking email. + +There are two major drawbacks with this technique: + +1. The user needs to actively make this setting (very few protocols + require the user to provide this sort of information). This also + means the user needs to figure out what its up-link capacity is. + This is unfortunately a number that many ISPs are not advertising + (because it's often much lower than the download capacity) which + might make it hard to find. + +2. The 20% headroom is wasted most of the time. Whenever the user + is not using the internet connection for anything, those extra 20% + could have been used by bittorrent to upload, but they're already + allocated for interactive traffic. On top of that, 20% of the up-link + is often not enough to give a good and responsive browsing experience. + +The ideal bandwidth allocation would be to use 100% for bittorrent when +there is no interactive cross traffic, and 100% for interactive traffic +whenever there is any. This would not waste any bandwidth while the user +is idling, and it would make for a much better experience when the user +is using the internet connection for other things. + +This is what uTP does. + +TCP +--- + +The reason TCP will fill the send buffer, and cause the delay on all traffic, +is because its congestion control is *only* based on packet loss (and timeout). + +Since the modem is buffering, packets won't get dropped until the entire queue +is full, and no more packets will fit. The packets will be dropped, TCP will +detect this within an RTT or so. When TCP notices a packet loss, it will slow +down its send rate and the queue will start to drain again. However, TCP will +immediately start to ramp up its send rate again until the buffer is full and +it detects packet loss again. + +TCP is designed to fully utilize the link capacity, without causing congestion. +Whenever it sense congestion (through packet loss) it backs off. TCP is not +designed to keep delays low. When you get the first packet loss (assuming the +kind of queue described above, tail-queue) it is already too late. Your queue +is full and you have the maximum amount of delay your modem can provide. + +TCP controls its send rate by limiting the number of bytes in-flight at any +given time. This limit is called congestion window (*cwnd* for short). During +steady state, the congestion window is constantly increasing linearly. Each +packet that is successfully transferred will increase cwnd. + +:: + + cwnd + send_rate = ---- + RTT + + +Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause +the send rate to be lower and a larger cwnd will cause the send rate to be +higher. + +Using a congestion window instead of controlling the rate directly is simple +because it also introduces an upper bound for memory usage for packets that +haven't been ACKed yet and needs to be kept around. + +The behavior of TCP, where it bumps up against the ceiling, backs off and then +starts increasing again until it hits the ceiling again, forms a saw tooth shape. +If the modem wouldn't have any send buffer at all, a single TCP stream would +not be able to fully utilize the link because of this behavior, since it would +only fully utilize the link right before the packet loss and the back-off. + +LEDBAT congestion controller +---------------------------- + +The congestion controller in uTP is called LEDBAT_, which also is an IETF working +group attempting to standardize it. The congestion controller, on top of reacting +to packet loss the same way TCP does, also reacts to changes in delays. + +For any uTP (or LEDBAT_) implementation, there is a target delay. This is the +amount of delay that is acceptable, and is in fact targeted for the connection. +The target delay is defined to 25 ms in LEDBAT_, uTorrent uses 100 ms and +libtorrent uses 75 ms. Whenever a delay measurement is lower than the target, +cwnd is increased proportional to (target_delay - delay). Whenever the measurement +is higher than the target, cwnd is decreased proportional to (delay - target_delay). + +It can simply be expressed as:: + + cwnd += gain * (target_delay - delay) + +.. image:: cwnd_thumb.png + :target: cwnd.png + :align: right + +Similarly to TCP, this is scaled so that the increase is evened out over one RTT. + +The linear controller will adjust the cwnd more for delays that are far off the +target, and less for delays that are close to the target. This makes it converge +at the target delay. Although, due to noise there is almost always some amount of +oscillation. This oscillation is typically smaller than the saw tooth TCP forms. + +The figure to the right shows how (TCP) cross traffic causes uTP to essentially +entirely stop sending anything. Its delay measurements are mostly well above the target +during this time. The cross traffic is only a single TCP stream in this test. + +As soon as the cross traffic ceases, uTP will pick up its original send rate within +a second. + +Since uTP constantly measures the delay, with every single packet, the reaction time +to cross traffic causing delays is a single RTT (typically a fraction of a second). + +one way delays +-------------- + +uTP measures the delay imposed on packets being sent to the other end +of the connection. This measurement only includes buffering delay along +the link, not propagation delay (the speed of light times distance) nor +the routing delay (the time routers spend figuring out where to forward +the packet). It does this by always comparing all measurements to a +baseline measurement, to cancel out any fixed delay. By focusing on the +variable delay along a link, it will specifically detect points where +there might be congestion, since those points will have buffers. + +.. image:: delays_thumb.png + :target: delays.png + :align: right + +Delay on the return link is explicitly not included in the delay measurement. +This is because in a peer-to-peer application, the other end is likely to also +be connected via a modem, with the same send buffer restrictions as we assume +for the sending side. The other end having its send queue full is not an indication +of congestion on the path going the other way. + +In order to measure one way delays for packets, we cannot rely on clocks being +synchronized, especially not at the microsecond level. Instead, the actual time +it takes for a packet to arrive at the destination is not measured, only the changes +in the transit time is measured. + +Each packet that is sent includes a time stamp of the current time, in microseconds, +of the sending machine. The receiving machine calculates the difference between its +own timestamp and the one in the packet and sends this back in the ACK. This difference, +since it is in microseconds, will essentially be a random 32 bit number. However, +the difference will stay somewhat similar over time. Any changes in this difference +indicates that packets are either going through faster or slower. + +In order to measure the one-way buffering delay, a base delay is established. The +base delay is the lowest ever seen value of the time stamp difference. Each delay +sample we receive back, is compared against the base delay and the delay is the +difference. + +This is the delay that's fed into the congestion controller. + +A histogram of typical delay measurements is shown to the right. This is from +a transfer between a cable modem connection and a DSL connection. + +The details of the delay measurements are slightly more complicated since the +values needs to be able to wrap (cross the 2^32 boundary and start over at 0). + +Path MTU discovery +------------------ + +MTU is short for *Maximum Transfer Unit* and describes the largest packet size that +can be sent over a link. Any datagrams which size exceeds this limit will either +be *fragmented* or dropped. A fragmented datagram means that the payload is split up +in multiple packets, each with its own individual packet header. + +There are several reasons to avoid sending datagrams that get fragmented: + +1. A fragmented datagram is more likely to be lost. If any fragment is lost, + the whole datagram is dropped. + +2. Bandwidth is likely to be wasted. If the datagram size is not divisible + by the MTU the last packet will not contain as much payload as it could, and the + payload over protocol header ratio decreases. + +3. It's expensive to fragment datagrams. Few routers are optimized to handle large + numbers of fragmented packets. Datagrams that have to fragment are likely to + be delayed significantly, and contribute to more CPU being used on routers. + Typically fragmentation (and other advanced IP features) are implemented in + software (slow) and not hardware (fast). + +The path MTU is the lowest MTU of any link along a path from two endpoints on the +internet. The MTU bottleneck isn't necessarily at one of the endpoints, but can +be anywhere in between. + +The most common MTU is 1500 bytes, which is the largest packet size for ethernet +networks. Many home DSL connections, however, tunnel IP through PPPoE (Point to +Point Protocol over Ethernet. Yes, that is the old dial-up modem protocol). This +protocol uses up 8 bytes per packet for its own header. + +If the user happens to be on an internet connection over a VPN, it will add another +layer, with its own packet headers. + +In short; if you would pick the largest possible packet size on an ethernet network, +1472, and stick with it, you would be quite likely to generate fragments for a lot +of connections. The fragments that will be created will be very small and especially +inflate the overhead waste. + +The other approach of picking a very conservative packet size, that would be very +unlikely to get fragmented has the following drawbacks: + +1. People on good, normal, networks will be penalized with a small packet size. + Both in terms of router load but also bandwidth waste. + +2. Software routers are typically not limited by the number of bytes they can route, + but the number of packets. Small packets means more of them, and more load on + software routers. + +The solution to the problem of finding the optimal packet size, is to dynamically +adjust the packet size and search for the largest size that can make it through +without being fragmented along the path. + +To help do this, you can set the DF bit (Don't Fragment) in your Datagrams. This +asks routers that otherwise would fragment packets to instead drop them, and send +back an ICMP message reporting the MTU of the link the packet couldn't fit. With +this message, it's very simple to discover the path MTU. You simply mark your packets +not to be fragmented, and change your packet size whenever you receive the ICMP +packet-too-big message. + +Unfortunately it's not quite that simple. There are a significant number of firewalls +in the wild blocking all ICMP messages. This means we can't rely on them, we also have +to guess that a packet was dropped because of its size. This is done by only marking +certain packets with DF, and if all other packets go through, except for the MTU probes, +we know that we need to lower our packet sizes. + +If we set up bounds for the path MTU (say the minimum internet MTU, 576 and ethernet's 1500), +we can do a binary search for the MTU. This would let us find it in just a few round-trips. + +On top of this, libtorrent has an optimization where it figures out which interface a +uTP connection will be sent over, and initialize the MTU ceiling to that interface's MTU. +This means that a VPN tunnel would advertise its MTU as lower, and the uTP connection would +immediately know to send smaller packets, no search required. It also has the side-effect +of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links +with jumbo frames. + +clock drift +----------- + +.. image:: our_delay_base_thumb.png + :target: our_delay_base.png + :align: right + +Clock drift is clocks progressing at different rates. It's different from clock +skew which means clocks set to different values (but which may progress at the same +rate). + +Any clock drift between the two machines involved in a uTP transfer will result +in systematically inflated or deflated delay measurements. + +This can be solved by letting the base delay be the lowest seen sample in the last +*n* minutes. This is a trade-off between seeing a single packet go straight through +the queue, with no delay, and the amount of clock drift one can assume on normal computers. + +It turns out that it's fairly safe to assume that one of your packets will in fact go +straight through without any significant delay, once every 20 minutes or so. However, +the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms +is quite significant, especially if your target delay is 25 ms (as in the LEDBAT_ spec). + +Clocks progresses at different rates depending on temperature. This means computers +running hot are likely to have a clock drift compared to computers running cool. + +So, by updating the delay base periodically based on the lowest seen sample, you'll either +end up changing it upwards (artificially making the delay samples appear small) without +the congestion or delay actually having changed, or you'll end up with a significant clock +drift and have artificially low samples because of that. + +The solution to this problem is based on the fact that the clock drift is only a problem +for one of the sides of the connection. Only when your delay measurements keep increasing +is it a problem. If your delay measurements keep decreasing, the samples will simply push +down the delay base along with it. With this in mind, we can simply keep track of the +other end's delay measurements as well, applying the same logic to it. Whenever the +other end's base delay is adjusted downwards, we adjust our base delay upwards by the same +amount. + +This will accurately keep the base delay updated with the clock drift and improve +the delay measurements. The figure on the right shows the absolute timestamp differences +along with the base delay. The slope of the measurements is caused by clock drift. + +For more information on the clock drift compensation, see the slides from BitTorrent's +presentation at IPTPS10_. + +.. _IPTPS10: http://www.usenix.org/event/iptps10/tech/slides/cohen.pdf +.. _LEDBAT: https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/ + +features +-------- + +libtorrent's uTP implementation includes the following features: + +* Path MTU discovery, including jumbo frames and detecting restricted + MTU tunnels. Binary search packet sizes to find the largest non-fragmented. +* Selective ACK. The ability to acknowledge individual packets in the + event of packet loss +* Fast resend. The first time a packet is lost, it's resent immediately. + Triggered by duplicate ACKs. +* Nagle's algorithm. Minimize protocol overhead by attempting to lump + full packets of payload together before sending a packet. +* Delayed ACKs to minimize protocol overhead. +* Microsecond resolution timestamps. +* Advertised receive window, to support download rate limiting. +* Correct handling of wrapping sequence numbers. +* Easy configuration of target-delay, gain-factor, timeouts, delayed-ACK + and socket buffers. + diff --git a/docs/utp_stack.diagram b/docs/utp_stack.diagram new file mode 100644 index 0000000..b3b8085 --- /dev/null +++ b/docs/utp_stack.diagram @@ -0,0 +1,9 @@ ++------------------------+ +| "BitTorrent protocol" | ++------------------------+ +| "SSL" | ++------------+-----------+ +| "TCP" | "uTP" | +| +-----------+ +| | "UDP" | ++------------+-----------+ diff --git a/docs/utp_stack.png b/docs/utp_stack.png new file mode 100644 index 0000000000000000000000000000000000000000..bf1ebcb56ecb7a5e27a24f44ef94d08c25320ece GIT binary patch literal 2017 zcmZ8ido37L^;r^Y1-n?Vgy zOzviAniQIGO2uH%W)o&y8VqSTzuh`(oz8y$_}2TbcdhSxzh^zq^X9s{o|A$81O)&< z#?b+Z0sxVHVD1kQ1<#`Dj0pgcc;tw*xsdRH$MJC>MyW}xPEmp)C|U2$$Qi4+_(l$j zlTTs&kk1uSXO8;bYj;&W3wbNjd9ri07QYP5uHNU!IRzVlL3JNAG`Ji?z6<4Zyj|rH zxX-&Pa<6yC&0S9LSB03CtG9>v+_oy?-KCAPN+TYl&n)9D(WT=f9|4 zS@V|YMcvyQ^bdI*(VdZVQL}E3{cF6Kb+#4|?H~%s-vxRR=8{J&Q~U{reFaW`73yB9 z9Y1vFklOvwm8)*^Q^Kw!P$@9*7^CRlo%5=<-FrCNIORF zmi_SX@PVdMotvKZ#??)Ye@EnQ3%dI1*$W#eBUZ?@C( z@4VQFjg3{6&bi!^#(alsyW)n{@UW;*X*sbGc-zC8 zniA$5jb^Fg4EZcwm5rL}PKR$yP${V+R%vfkyW)92$sd%?0gLdlXCbvl8=S6dV1aoua_CmyzyLU; zRRuZDjB`ix+UGfSS$Kbb5TL)QukNV^~#I)ME=(^ zz>&%1`}gn9eviRo{d3{RsvbO0Ya3S|DWq3r)37-(5G7w<-_+F9w4R=Z2JX_x6B3DZ z4AIfivF4`@SKiDvg=88S82p>f?uc9R?BcM(lFoYnNwvp|Xa&f!*=+DH8yg$@vnpF# zKduOHZH(kwuze2ni8n#~ED5>Jt}agGHCtO-hBA5~KihPInk;xzVx+053DSb-phYAS zKOxrqG#S8M1KhyC07yJgCIo`ChFRj5zyzt4lJas^4@;gs#9j&5-$?}Hit*KP8} z01Ys;RRHkrRLS1TB%|F`LzaTghPL(lA9~8fZ2Sl@b^WtPP*4yG^{&RbSm(1-i=(5X zl9G~6s2B884@7PA26|O#tbIC6q7$QM15=a5jVvJ$h={PTQW}akJ?$S7vH+GKtvi9dJV!^0yjjdm1rx|A9C^Z6-bW8=RBJOBFGOZuTt=|rSp z{km}}Qz#TtV*3IrdeGVd(a{FSj~}nTb2;yP{fFhzCK9ORoZ;v2=uwi=U{oSFTS!=# zRLCmnS@Cj}ZQAGDp+YEmBo101Ww6(Ky9w+k1!GosBn_SA(A*_i>^lu##3$e0t za_QS&YU=BCCkMSOuq9$Pu*IdN`ntLcCkv4prb+9;(h?GpgGGoSiYWrY104{&KyrHM z6jm(m%^L0n4GD7SpZLDmI8Y?6$m~Q?+8{X&CLlr3| "plain text" +--=------------->| "disk cache" | ++------------------+ | "buffer" | +------+-------+ + ^ +--------------+ | + | "read() on socket" "write() to file" | + | "(copy)" "(copy)" | +---=----|---------------------------------=---------------------------------|--=---- + | "kernel space" | + | v ++-------+---------+ +---------------------+ +| "socket kernel" | | "kernel page cache" | +| "buffer" | | | ++-----------------+ +---------------------+ diff --git a/docs/write_disk_buffers.png b/docs/write_disk_buffers.png new file mode 100644 index 0000000000000000000000000000000000000000..36f9b92949cfb25e75519475fef582b40733b26e GIT binary patch literal 8665 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 literal 0 HcmV?d00001 diff --git a/ed25519/readme.md b/ed25519/readme.md new file mode 100644 index 0000000..b202892 --- /dev/null +++ b/ed25519/readme.md @@ -0,0 +1,165 @@ +Ed25519 +======= + +This is a portable implementation of [Ed25519](http://ed25519.cr.yp.to/) based +on the SUPERCOP "ref10" implementation. Additionally there is key exchanging +and scalar addition included to further aid building a PKI using Ed25519. All +code is in the public domain. + +All code is pure ANSI C without any dependencies, except for the random seed +generation which uses standard OS cryptography APIs (`CryptGenRandom` on +Windows, `/dev/urandom` on nix). If you wish to be entirely portable define +`ED25519_NO_SEED`. This disables the `ed25519_create_seed` function, so if your +application requires key generation you must supply your own seeding function +(which is simply a 256 bit (32 byte) cryptographic random number generator). + + +Performance +----------- + +On a Windows machine with an Intel Pentium B970 @ 2.3GHz I got the following +speeds (running on only one a single core): + + Seed generation: 64us (15625 per second) + Key generation: 88us (11364 per second) + Message signing (short message): 87us (11494 per second) + Message verifying (short message): 228us (4386 per second) + Scalar addition: 100us (10000 per second) + Key exchange: 220us (4545 per second) + +The speeds on other machines may vary. Sign/verify times will be higher with +longer messages. The implementation significantly benefits from 64 bit +architectures, if possible compile as 64 bit. + + +Usage +----- + +Simply add all .c and .h files in the `src/` folder to your project and include +`ed25519.h` in any file you want to use the API. If you prefer to use a shared +library, only copy `ed25519.h` and define `ED25519_DLL` before importing. A +windows DLL is pre-built. + +There are no defined types for seeds, private keys, public keys, shared secrets +or signatures. Instead simple `unsigned char` buffers are used with the +following sizes: + +```c +unsigned char seed[32]; +unsigned char signature[64]; +unsigned char public_key[32]; +unsigned char private_key[64]; +unsigned char scalar[32]; +unsigned char shared_secret[32]; +``` + +API +--- + +```c +int ed25519_create_seed(unsigned char *seed); +``` + +Creates a 32 byte random seed in `seed` for key generation. `seed` must be a +writable 32 byte buffer. Returns 0 on success, and nonzero on failure. + +```c +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +``` + +Creates a new key pair from the given seed. `public_key` must be a writable 32 +byte buffer, `private_key` must be a writable 64 byte buffer and `seed` must be +a 32 byte buffer. + +```c +void ed25519_sign(unsigned char *signature, + const unsigned char *message, size_t message_len, + const unsigned char *public_key, const unsigned char *private_key); +``` + +Creates a signature of the given message with the given key pair. `signature` +must be a writable 64 byte buffer. `message` must have at least `message_len` +bytes to be read. + +```c +int ed25519_verify(const unsigned char *signature, + const unsigned char *message, size_t message_len, + const unsigned char *public_key); +``` + +Verifies the signature on the given message using `public_key`. `signature` +must be a readable 64 byte buffer. `message` must have at least `message_len` +bytes to be read. Returns 1 if the signature matches, 0 otherwise. + +```c +void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, + const unsigned char *scalar); +``` + +Adds `scalar` to the given key pair where scalar is a 32 byte buffer (possibly +generated with `ed25519_create_seed`), generating a new key pair. You can +calculate the public key sum without knowing the private key and vice versa by +passing in `NULL` for the key you don't know. This is useful for enforcing +randomness on a key pair by a third party while only knowing the public key, +among other things. Warning: the last bit of the scalar is ignored - if +comparing scalars make sure to clear it with `scalar[31] &= 127`. + + +```c +void ed25519_key_exchange(unsigned char *shared_secret, + const unsigned char *public_key, const unsigned char *private_key); +``` + +Performs a key exchange on the given public key and private key, producing a +shared secret. It is recommended to hash the shared secret before using it. +`shared_secret` must be a 32 byte writable buffer where the shared secret will +be stored. + +Example +------- + +```c +unsigned char seed[32], public_key[32], private_key[64], signature[64]; +unsigned char other_public_key[32], other_private_key[64], shared_secret[32]; +const unsigned char message[] = "TEST MESSAGE"; + +/* create a random seed, and a key pair out of that seed */ +if (ed25519_create_seed(seed)) { + printf("error while generating seed\n"); + exit(1); +} + +ed25519_create_keypair(public_key, private_key, seed); + +/* create signature on the message with the key pair */ +ed25519_sign(signature, message, strlen(message), public_key, private_key); + +/* verify the signature */ +if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); +} else { + printf("invalid signature\n"); +} + +/* create a dummy keypair to use for a key exchange, normally you'd only have +the public key and receive it through some communication channel */ +if (ed25519_create_seed(seed)) { + printf("error while generating seed\n"); + exit(1); +} + +ed25519_create_keypair(other_public_key, other_private_key, seed); + +/* do a key exchange with other_public_key */ +ed25519_key_exchange(shared_secret, other_public_key, private_key); + +/* + the magic here is that ed25519_key_exchange(shared_secret, public_key, + other_private_key); would result in the same shared_secret +*/ + +``` + +License +------- +All code is in the public domain. diff --git a/ed25519/src/add_scalar.cpp b/ed25519/src/add_scalar.cpp new file mode 100644 index 0000000..ec09b68 --- /dev/null +++ b/ed25519/src/add_scalar.cpp @@ -0,0 +1,75 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "libtorrent/hasher512.hpp" +#include "ge.h" +#include "sc.h" + +namespace libtorrent +{ + +/* see http://crypto.stackexchange.com/a/6215/4697 */ +void ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar) { + const unsigned char SC_1[32] = {1}; /* scalar with value 1 */ + + unsigned char n[32]; + ge_p3 nB; + ge_p1p1 A_p1p1; + ge_p3 A; + ge_p3 public_key_unpacked; + ge_cached T; + + int i; + + /* copy the scalar and clear highest bit */ + for (i = 0; i < 31; ++i) { + n[i] = scalar[i]; + } + n[31] = scalar[31] & 127; + + /* private key: a = n + t */ + if (private_key) { + sc_muladd(private_key, SC_1, n, private_key); + + // Fixed ed25519_add_scalar vulnerability. + // https://github.com/orlp/ed25519/commit/09ec167693edff9478b53d772487e94569cb58d0 + // https://github.com/orlp/ed25519/issues/3 + hasher512 hash; + hash.update({reinterpret_cast(private_key) + 32, 32}); + hash.update({reinterpret_cast(scalar), 32}); + sha512_hash hashbuf = hash.final(); + for (i = 0; i < 32; ++i) { + private_key[32 + i] = hashbuf[i]; + } + } + + /* public key: A = nB + T */ + if (public_key) { + /* if we know the private key we don't need a point addition, which is faster */ + /* using a "timing attack" you could find out whether or not we know the private + key, but this information seems rather useless - if this is important pass + public_key and private_key separately in 2 function calls */ + if (private_key) { + ge_scalarmult_base(&A, private_key); + } else { + /* unpack public key into T */ + ge_frombytes_negate_vartime(&public_key_unpacked, public_key); + fe_neg(public_key_unpacked.X, public_key_unpacked.X); // undo negate + fe_neg(public_key_unpacked.T, public_key_unpacked.T); // undo negate + ge_p3_to_cached(&T, &public_key_unpacked); + + /* calculate n*B */ + ge_scalarmult_base(&nB, n); + + /* A = n*B + T */ + ge_add(&A_p1p1, &nB, &T); + ge_p1p1_to_p3(&A, &A_p1p1); + } + + /* pack public key */ + ge_p3_tobytes(public_key, &A); + } +} + +} diff --git a/ed25519/src/fe.cpp b/ed25519/src/fe.cpp new file mode 100644 index 0000000..a979ef4 --- /dev/null +++ b/ed25519/src/fe.cpp @@ -0,0 +1,1490 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "fixedint.h" +#include "fe.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4146 ) /* warning C4146: unary minus operator applied to unsigned type, result still unsigned */ +#pragma warning(disable : 4244 ) /* warning C4244: '=' : conversion from 'int64_t' to 'int32_t', possible loss of data */ +#endif // _MSC_VER + +/* + helper functions +*/ +static u64 load_3(const unsigned char *in) { + u64 result; + + result = (u64) in[0]; + result |= ((u64) in[1]) << 8; + result |= ((u64) in[2]) << 16; + + return result; +} + +static u64 load_4(const unsigned char *in) { + u64 result; + + result = (u64) in[0]; + result |= ((u64) in[1]) << 8; + result |= ((u64) in[2]) << 16; + result |= ((u64) in[3]) << 24; + + return result; +} + +static inline i64 shift_left(i64 v, int s) { + return i64(u64(v) << s); +} + + +/* + h = 0 +*/ + +void fe_0(fe h) { + h[0] = 0; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = 1 +*/ + +void fe_1(fe h) { + h[0] = 1; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = f + g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_add(fe h, const fe f, const fe g) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 g0 = g[0]; + i32 g1 = g[1]; + i32 g2 = g[2]; + i32 g3 = g[3]; + i32 g4 = g[4]; + i32 g5 = g[5]; + i32 g6 = g[6]; + i32 g7 = g[7]; + i32 g8 = g[8]; + i32 g9 = g[9]; + i32 h0 = f0 + g0; + i32 h1 = f1 + g1; + i32 h2 = f2 + g2; + i32 h3 = f3 + g3; + i32 h4 = f4 + g4; + i32 h5 = f5 + g5; + i32 h6 = f6 + g6; + i32 h7 = f7 + g7; + i32 h8 = f8 + g8; + i32 h9 = f9 + g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cmov(fe f, const fe g, unsigned int b) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 g0 = g[0]; + i32 g1 = g[1]; + i32 g2 = g[2]; + i32 g3 = g[3]; + i32 g4 = g[4]; + i32 g5 = g[5]; + i32 g6 = g[6]; + i32 g7 = g[7]; + i32 g8 = g[8]; + i32 g9 = g[9]; + i32 x0 = f0 ^ g0; + i32 x1 = f1 ^ g1; + i32 x2 = f2 ^ g2; + i32 x3 = f3 ^ g3; + i32 x4 = f4 ^ g4; + i32 x5 = f5 ^ g5; + i32 x6 = f6 ^ g6; + i32 x7 = f7 ^ g7; + i32 x8 = f8 ^ g8; + i32 x9 = f9 ^ g9; + + b = (unsigned int) (- (int) b); /* silence warning */ + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; +} + +/* + Replace (f,g) with (g,f) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cswap(fe f,fe g,unsigned int b) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 g0 = g[0]; + i32 g1 = g[1]; + i32 g2 = g[2]; + i32 g3 = g[3]; + i32 g4 = g[4]; + i32 g5 = g[5]; + i32 g6 = g[6]; + i32 g7 = g[7]; + i32 g8 = g[8]; + i32 g9 = g[9]; + i32 x0 = f0 ^ g0; + i32 x1 = f1 ^ g1; + i32 x2 = f2 ^ g2; + i32 x3 = f3 ^ g3; + i32 x4 = f4 ^ g4; + i32 x5 = f5 ^ g5; + i32 x6 = f6 ^ g6; + i32 x7 = f7 ^ g7; + i32 x8 = f8 ^ g8; + i32 x9 = f9 ^ g9; + b = -b; // warning C4146: unary minus operator applied to unsigned type, result still unsigned + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; + g[0] = g0 ^ x0; + g[1] = g1 ^ x1; + g[2] = g2 ^ x2; + g[3] = g3 ^ x3; + g[4] = g4 ^ x4; + g[5] = g5 ^ x5; + g[6] = g6 ^ x6; + g[7] = g7 ^ x7; + g[8] = g8 ^ x8; + g[9] = g9 ^ x9; +} + + + +/* + h = f +*/ + +void fe_copy(fe h, const fe f) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + + h[0] = f0; + h[1] = f1; + h[2] = f2; + h[3] = f3; + h[4] = f4; + h[5] = f5; + h[6] = f6; + h[7] = f7; + h[8] = f8; + h[9] = f9; +} + + + +/* + Ignores top bit of h. +*/ + +void fe_frombytes(fe h, const unsigned char *s) { + i64 h0 = load_4(s); + i64 h1 = load_3(s + 4) << 6; + i64 h2 = load_3(s + 7) << 5; + i64 h3 = load_3(s + 10) << 3; + i64 h4 = load_3(s + 13) << 2; + i64 h5 = load_4(s + 16); + i64 h6 = load_3(s + 20) << 7; + i64 h7 = load_3(s + 23) << 5; + i64 h8 = load_3(s + 26) << 4; + i64 h9 = (load_3(s + 29) & 8388607) << 2; + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shift_left(carry9, 25); + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= shift_left(carry1, 25); + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= shift_left(carry3, 25); + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= shift_left(carry5, 25); + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= shift_left(carry7, 25); + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= shift_left(carry2, 26); + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= shift_left(carry6, 26); + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= shift_left(carry8, 26); + + h[0] = (i32) h0; + h[1] = (i32) h1; + h[2] = (i32) h2; + h[3] = (i32) h3; + h[4] = (i32) h4; + h[5] = (i32) h5; + h[6] = (i32) h6; + h[7] = (i32) h7; + h[8] = (i32) h8; + h[9] = (i32) h9; +} + + + +void fe_invert(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + fe t3; + int i; + + fe_sq(t0, z); + + fe_sq(t1, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t2, t0); + + fe_mul(t1, t1, t2); + fe_sq(t2, t1); + + for (i = 1; i < 5; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for (i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for (i = 1; i < 20; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for (i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for (i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for (i = 1; i < 100; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for (i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(out, t1, t0); +} + + + +/* + return 1 if f is in {1,3,5,...,q-2} + return 0 if f is in {0,2,4,...,q-1} + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnegative(const fe f) { + unsigned char s[32]; + + fe_tobytes(s, f); + + return s[0] & 1; +} + + + +/* + return 1 if f == 0 + return 0 if f != 0 + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnonzero(const fe f) { + unsigned char s[32]; + unsigned char r; + + fe_tobytes(s, f); + + r = s[0]; + #define F(i) r |= s[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); + #undef F + + return r != 0; +} + + +/* + h = f * g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + + Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. + */ + + /* + Notes on implementation strategy: + + Using schoolbook multiplication. + Karatsuba would save a little in some cost models. + + Most multiplications by 2 and 19 are 32-bit precomputations; + cheaper than 64-bit postcomputations. + + There is one remaining multiplication by 19 in the carry chain; + one *19 precomputation can be merged into this, + but the resulting data flow is considerably less clean. + + There are 12 carries below. + 10 of them are 2-way parallelizable and vectorizable. + Can get away with 11 carries, but then data flow is much deeper. + + With tighter constraints on inputs can squeeze carries into int32. +*/ + +void fe_mul(fe h, const fe f, const fe g) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 g0 = g[0]; + i32 g1 = g[1]; + i32 g2 = g[2]; + i32 g3 = g[3]; + i32 g4 = g[4]; + i32 g5 = g[5]; + i32 g6 = g[6]; + i32 g7 = g[7]; + i32 g8 = g[8]; + i32 g9 = g[9]; + i32 g1_19 = 19 * g1; /* 1.959375*2^29 */ + i32 g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ + i32 g3_19 = 19 * g3; + i32 g4_19 = 19 * g4; + i32 g5_19 = 19 * g5; + i32 g6_19 = 19 * g6; + i32 g7_19 = 19 * g7; + i32 g8_19 = 19 * g8; + i32 g9_19 = 19 * g9; + i32 f1_2 = 2 * f1; + i32 f3_2 = 2 * f3; + i32 f5_2 = 2 * f5; + i32 f7_2 = 2 * f7; + i32 f9_2 = 2 * f9; + i64 f0g0 = f0 * (i64) g0; + i64 f0g1 = f0 * (i64) g1; + i64 f0g2 = f0 * (i64) g2; + i64 f0g3 = f0 * (i64) g3; + i64 f0g4 = f0 * (i64) g4; + i64 f0g5 = f0 * (i64) g5; + i64 f0g6 = f0 * (i64) g6; + i64 f0g7 = f0 * (i64) g7; + i64 f0g8 = f0 * (i64) g8; + i64 f0g9 = f0 * (i64) g9; + i64 f1g0 = f1 * (i64) g0; + i64 f1g1_2 = f1_2 * (i64) g1; + i64 f1g2 = f1 * (i64) g2; + i64 f1g3_2 = f1_2 * (i64) g3; + i64 f1g4 = f1 * (i64) g4; + i64 f1g5_2 = f1_2 * (i64) g5; + i64 f1g6 = f1 * (i64) g6; + i64 f1g7_2 = f1_2 * (i64) g7; + i64 f1g8 = f1 * (i64) g8; + i64 f1g9_38 = f1_2 * (i64) g9_19; + i64 f2g0 = f2 * (i64) g0; + i64 f2g1 = f2 * (i64) g1; + i64 f2g2 = f2 * (i64) g2; + i64 f2g3 = f2 * (i64) g3; + i64 f2g4 = f2 * (i64) g4; + i64 f2g5 = f2 * (i64) g5; + i64 f2g6 = f2 * (i64) g6; + i64 f2g7 = f2 * (i64) g7; + i64 f2g8_19 = f2 * (i64) g8_19; + i64 f2g9_19 = f2 * (i64) g9_19; + i64 f3g0 = f3 * (i64) g0; + i64 f3g1_2 = f3_2 * (i64) g1; + i64 f3g2 = f3 * (i64) g2; + i64 f3g3_2 = f3_2 * (i64) g3; + i64 f3g4 = f3 * (i64) g4; + i64 f3g5_2 = f3_2 * (i64) g5; + i64 f3g6 = f3 * (i64) g6; + i64 f3g7_38 = f3_2 * (i64) g7_19; + i64 f3g8_19 = f3 * (i64) g8_19; + i64 f3g9_38 = f3_2 * (i64) g9_19; + i64 f4g0 = f4 * (i64) g0; + i64 f4g1 = f4 * (i64) g1; + i64 f4g2 = f4 * (i64) g2; + i64 f4g3 = f4 * (i64) g3; + i64 f4g4 = f4 * (i64) g4; + i64 f4g5 = f4 * (i64) g5; + i64 f4g6_19 = f4 * (i64) g6_19; + i64 f4g7_19 = f4 * (i64) g7_19; + i64 f4g8_19 = f4 * (i64) g8_19; + i64 f4g9_19 = f4 * (i64) g9_19; + i64 f5g0 = f5 * (i64) g0; + i64 f5g1_2 = f5_2 * (i64) g1; + i64 f5g2 = f5 * (i64) g2; + i64 f5g3_2 = f5_2 * (i64) g3; + i64 f5g4 = f5 * (i64) g4; + i64 f5g5_38 = f5_2 * (i64) g5_19; + i64 f5g6_19 = f5 * (i64) g6_19; + i64 f5g7_38 = f5_2 * (i64) g7_19; + i64 f5g8_19 = f5 * (i64) g8_19; + i64 f5g9_38 = f5_2 * (i64) g9_19; + i64 f6g0 = f6 * (i64) g0; + i64 f6g1 = f6 * (i64) g1; + i64 f6g2 = f6 * (i64) g2; + i64 f6g3 = f6 * (i64) g3; + i64 f6g4_19 = f6 * (i64) g4_19; + i64 f6g5_19 = f6 * (i64) g5_19; + i64 f6g6_19 = f6 * (i64) g6_19; + i64 f6g7_19 = f6 * (i64) g7_19; + i64 f6g8_19 = f6 * (i64) g8_19; + i64 f6g9_19 = f6 * (i64) g9_19; + i64 f7g0 = f7 * (i64) g0; + i64 f7g1_2 = f7_2 * (i64) g1; + i64 f7g2 = f7 * (i64) g2; + i64 f7g3_38 = f7_2 * (i64) g3_19; + i64 f7g4_19 = f7 * (i64) g4_19; + i64 f7g5_38 = f7_2 * (i64) g5_19; + i64 f7g6_19 = f7 * (i64) g6_19; + i64 f7g7_38 = f7_2 * (i64) g7_19; + i64 f7g8_19 = f7 * (i64) g8_19; + i64 f7g9_38 = f7_2 * (i64) g9_19; + i64 f8g0 = f8 * (i64) g0; + i64 f8g1 = f8 * (i64) g1; + i64 f8g2_19 = f8 * (i64) g2_19; + i64 f8g3_19 = f8 * (i64) g3_19; + i64 f8g4_19 = f8 * (i64) g4_19; + i64 f8g5_19 = f8 * (i64) g5_19; + i64 f8g6_19 = f8 * (i64) g6_19; + i64 f8g7_19 = f8 * (i64) g7_19; + i64 f8g8_19 = f8 * (i64) g8_19; + i64 f8g9_19 = f8 * (i64) g9_19; + i64 f9g0 = f9 * (i64) g0; + i64 f9g1_38 = f9_2 * (i64) g1_19; + i64 f9g2_19 = f9 * (i64) g2_19; + i64 f9g3_38 = f9_2 * (i64) g3_19; + i64 f9g4_19 = f9 * (i64) g4_19; + i64 f9g5_38 = f9_2 * (i64) g5_19; + i64 f9g6_19 = f9 * (i64) g6_19; + i64 f9g7_38 = f9_2 * (i64) g7_19; + i64 f9g8_19 = f9 * (i64) g8_19; + i64 f9g9_38 = f9_2 * (i64) g9_19; + i64 h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38; + i64 h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19; + i64 h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38; + i64 h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19; + i64 h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38; + i64 h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19; + i64 h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38; + i64 h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19; + i64 h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38; + i64 h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0 ; + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= shift_left(carry1, 25); + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= shift_left(carry5, 25); + + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= shift_left(carry2, 26); + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= shift_left(carry6, 26); + + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= shift_left(carry3, 25); + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= shift_left(carry7, 25); + + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= shift_left(carry8, 26); + + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shift_left(carry9, 25); + + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + + h[0] = (i32) h0; + h[1] = (i32) h1; + h[2] = (i32) h2; + h[3] = (i32) h3; + h[4] = (i32) h4; + h[5] = (i32) h5; + h[6] = (i32) h6; + h[7] = (i32) h7; + h[8] = (i32) h8; + h[9] = (i32) h9; +} + + +/* +h = f * 121666 +Can overlap h with f. + +Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_mul121666(fe h, fe f) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i64 h0 = f0 * (i64) 121666; + i64 h1 = f1 * (i64) 121666; + i64 h2 = f2 * (i64) 121666; + i64 h3 = f3 * (i64) 121666; + i64 h4 = f4 * (i64) 121666; + i64 h5 = f5 * (i64) 121666; + i64 h6 = f6 * (i64) 121666; + i64 h7 = f7 * (i64) 121666; + i64 h8 = f8 * (i64) 121666; + i64 h9 = f9 * (i64) 121666; + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + + carry9 = (h9 + (i64) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= shift_left(carry9, 25); + carry1 = (h1 + (i64) (1<<24)) >> 25; h2 += carry1; h1 -= shift_left(carry1, 25); + carry3 = (h3 + (i64) (1<<24)) >> 25; h4 += carry3; h3 -= shift_left(carry3, 25); + carry5 = (h5 + (i64) (1<<24)) >> 25; h6 += carry5; h5 -= shift_left(carry5, 25); + carry7 = (h7 + (i64) (1<<24)) >> 25; h8 += carry7; h7 -= shift_left(carry7, 25); + + carry0 = (h0 + (i64) (1<<25)) >> 26; h1 += carry0; h0 -= shift_left(carry0, 26); + carry2 = (h2 + (i64) (1<<25)) >> 26; h3 += carry2; h2 -= shift_left(carry2, 26); + carry4 = (h4 + (i64) (1<<25)) >> 26; h5 += carry4; h4 -= shift_left(carry4, 26); + carry6 = (h6 + (i64) (1<<25)) >> 26; h7 += carry6; h6 -= shift_left(carry6, 26); + carry8 = (h8 + (i64) (1<<25)) >> 26; h9 += carry8; h8 -= shift_left(carry8, 26); + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +/* +h = -f + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_neg(fe h, const fe f) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 h0 = -f0; + i32 h1 = -f1; + i32 h2 = -f2; + i32 h3 = -f3; + i32 h4 = -f4; + i32 h5 = -f5; + i32 h6 = -f6; + i32 h7 = -f7; + i32 h8 = -f8; + i32 h9 = -f9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +void fe_pow22523(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + int i; + fe_sq(t0, z); + + fe_sq(t1, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for (i = 1; i < 20; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for (i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for (i = 1; i < 100; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for (i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t0, t0); + + for (i = 1; i < 2; ++i) { + fe_sq(t0, t0); + } + + fe_mul(out, t0, z); + return; +} + + +/* +h = f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq(fe h, const fe f) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 f0_2 = 2 * f0; + i32 f1_2 = 2 * f1; + i32 f2_2 = 2 * f2; + i32 f3_2 = 2 * f3; + i32 f4_2 = 2 * f4; + i32 f5_2 = 2 * f5; + i32 f6_2 = 2 * f6; + i32 f7_2 = 2 * f7; + i32 f5_38 = 38 * f5; /* 1.959375*2^30 */ + i32 f6_19 = 19 * f6; /* 1.959375*2^30 */ + i32 f7_38 = 38 * f7; /* 1.959375*2^30 */ + i32 f8_19 = 19 * f8; /* 1.959375*2^30 */ + i32 f9_38 = 38 * f9; /* 1.959375*2^30 */ + i64 f0f0 = f0 * (i64) f0; + i64 f0f1_2 = f0_2 * (i64) f1; + i64 f0f2_2 = f0_2 * (i64) f2; + i64 f0f3_2 = f0_2 * (i64) f3; + i64 f0f4_2 = f0_2 * (i64) f4; + i64 f0f5_2 = f0_2 * (i64) f5; + i64 f0f6_2 = f0_2 * (i64) f6; + i64 f0f7_2 = f0_2 * (i64) f7; + i64 f0f8_2 = f0_2 * (i64) f8; + i64 f0f9_2 = f0_2 * (i64) f9; + i64 f1f1_2 = f1_2 * (i64) f1; + i64 f1f2_2 = f1_2 * (i64) f2; + i64 f1f3_4 = f1_2 * (i64) f3_2; + i64 f1f4_2 = f1_2 * (i64) f4; + i64 f1f5_4 = f1_2 * (i64) f5_2; + i64 f1f6_2 = f1_2 * (i64) f6; + i64 f1f7_4 = f1_2 * (i64) f7_2; + i64 f1f8_2 = f1_2 * (i64) f8; + i64 f1f9_76 = f1_2 * (i64) f9_38; + i64 f2f2 = f2 * (i64) f2; + i64 f2f3_2 = f2_2 * (i64) f3; + i64 f2f4_2 = f2_2 * (i64) f4; + i64 f2f5_2 = f2_2 * (i64) f5; + i64 f2f6_2 = f2_2 * (i64) f6; + i64 f2f7_2 = f2_2 * (i64) f7; + i64 f2f8_38 = f2_2 * (i64) f8_19; + i64 f2f9_38 = f2 * (i64) f9_38; + i64 f3f3_2 = f3_2 * (i64) f3; + i64 f3f4_2 = f3_2 * (i64) f4; + i64 f3f5_4 = f3_2 * (i64) f5_2; + i64 f3f6_2 = f3_2 * (i64) f6; + i64 f3f7_76 = f3_2 * (i64) f7_38; + i64 f3f8_38 = f3_2 * (i64) f8_19; + i64 f3f9_76 = f3_2 * (i64) f9_38; + i64 f4f4 = f4 * (i64) f4; + i64 f4f5_2 = f4_2 * (i64) f5; + i64 f4f6_38 = f4_2 * (i64) f6_19; + i64 f4f7_38 = f4 * (i64) f7_38; + i64 f4f8_38 = f4_2 * (i64) f8_19; + i64 f4f9_38 = f4 * (i64) f9_38; + i64 f5f5_38 = f5 * (i64) f5_38; + i64 f5f6_38 = f5_2 * (i64) f6_19; + i64 f5f7_76 = f5_2 * (i64) f7_38; + i64 f5f8_38 = f5_2 * (i64) f8_19; + i64 f5f9_76 = f5_2 * (i64) f9_38; + i64 f6f6_19 = f6 * (i64) f6_19; + i64 f6f7_38 = f6 * (i64) f7_38; + i64 f6f8_38 = f6_2 * (i64) f8_19; + i64 f6f9_38 = f6 * (i64) f9_38; + i64 f7f7_38 = f7 * (i64) f7_38; + i64 f7f8_38 = f7_2 * (i64) f8_19; + i64 f7f9_76 = f7_2 * (i64) f9_38; + i64 f8f8_19 = f8 * (i64) f8_19; + i64 f8f9_38 = f8 * (i64) f9_38; + i64 f9f9_38 = f9 * (i64) f9_38; + i64 h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + i64 h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + i64 h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + i64 h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + i64 h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + i64 h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + i64 h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + i64 h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + i64 h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + i64 h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= shift_left(carry1, 25); + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= shift_left(carry5, 25); + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= shift_left(carry2, 26); + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= shift_left(carry6, 26); + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= shift_left(carry3, 25); + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= shift_left(carry7, 25); + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= shift_left(carry8, 26); + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shift_left(carry9, 25); + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + h[0] = (i32) h0; + h[1] = (i32) h1; + h[2] = (i32) h2; + h[3] = (i32) h3; + h[4] = (i32) h4; + h[5] = (i32) h5; + h[6] = (i32) h6; + h[7] = (i32) h7; + h[8] = (i32) h8; + h[9] = (i32) h9; +} + + +/* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq2(fe h, const fe f) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 f0_2 = 2 * f0; + i32 f1_2 = 2 * f1; + i32 f2_2 = 2 * f2; + i32 f3_2 = 2 * f3; + i32 f4_2 = 2 * f4; + i32 f5_2 = 2 * f5; + i32 f6_2 = 2 * f6; + i32 f7_2 = 2 * f7; + i32 f5_38 = 38 * f5; /* 1.959375*2^30 */ + i32 f6_19 = 19 * f6; /* 1.959375*2^30 */ + i32 f7_38 = 38 * f7; /* 1.959375*2^30 */ + i32 f8_19 = 19 * f8; /* 1.959375*2^30 */ + i32 f9_38 = 38 * f9; /* 1.959375*2^30 */ + i64 f0f0 = f0 * (i64) f0; + i64 f0f1_2 = f0_2 * (i64) f1; + i64 f0f2_2 = f0_2 * (i64) f2; + i64 f0f3_2 = f0_2 * (i64) f3; + i64 f0f4_2 = f0_2 * (i64) f4; + i64 f0f5_2 = f0_2 * (i64) f5; + i64 f0f6_2 = f0_2 * (i64) f6; + i64 f0f7_2 = f0_2 * (i64) f7; + i64 f0f8_2 = f0_2 * (i64) f8; + i64 f0f9_2 = f0_2 * (i64) f9; + i64 f1f1_2 = f1_2 * (i64) f1; + i64 f1f2_2 = f1_2 * (i64) f2; + i64 f1f3_4 = f1_2 * (i64) f3_2; + i64 f1f4_2 = f1_2 * (i64) f4; + i64 f1f5_4 = f1_2 * (i64) f5_2; + i64 f1f6_2 = f1_2 * (i64) f6; + i64 f1f7_4 = f1_2 * (i64) f7_2; + i64 f1f8_2 = f1_2 * (i64) f8; + i64 f1f9_76 = f1_2 * (i64) f9_38; + i64 f2f2 = f2 * (i64) f2; + i64 f2f3_2 = f2_2 * (i64) f3; + i64 f2f4_2 = f2_2 * (i64) f4; + i64 f2f5_2 = f2_2 * (i64) f5; + i64 f2f6_2 = f2_2 * (i64) f6; + i64 f2f7_2 = f2_2 * (i64) f7; + i64 f2f8_38 = f2_2 * (i64) f8_19; + i64 f2f9_38 = f2 * (i64) f9_38; + i64 f3f3_2 = f3_2 * (i64) f3; + i64 f3f4_2 = f3_2 * (i64) f4; + i64 f3f5_4 = f3_2 * (i64) f5_2; + i64 f3f6_2 = f3_2 * (i64) f6; + i64 f3f7_76 = f3_2 * (i64) f7_38; + i64 f3f8_38 = f3_2 * (i64) f8_19; + i64 f3f9_76 = f3_2 * (i64) f9_38; + i64 f4f4 = f4 * (i64) f4; + i64 f4f5_2 = f4_2 * (i64) f5; + i64 f4f6_38 = f4_2 * (i64) f6_19; + i64 f4f7_38 = f4 * (i64) f7_38; + i64 f4f8_38 = f4_2 * (i64) f8_19; + i64 f4f9_38 = f4 * (i64) f9_38; + i64 f5f5_38 = f5 * (i64) f5_38; + i64 f5f6_38 = f5_2 * (i64) f6_19; + i64 f5f7_76 = f5_2 * (i64) f7_38; + i64 f5f8_38 = f5_2 * (i64) f8_19; + i64 f5f9_76 = f5_2 * (i64) f9_38; + i64 f6f6_19 = f6 * (i64) f6_19; + i64 f6f7_38 = f6 * (i64) f7_38; + i64 f6f8_38 = f6_2 * (i64) f8_19; + i64 f6f9_38 = f6 * (i64) f9_38; + i64 f7f7_38 = f7 * (i64) f7_38; + i64 f7f8_38 = f7_2 * (i64) f8_19; + i64 f7f9_76 = f7_2 * (i64) f9_38; + i64 f8f8_19 = f8 * (i64) f8_19; + i64 f8f9_38 = f8 * (i64) f9_38; + i64 f9f9_38 = f9 * (i64) f9_38; + i64 h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + i64 h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + i64 h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + i64 h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + i64 h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + i64 h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + i64 h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + i64 h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + i64 h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + i64 h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry1 = (h1 + (i64) (1 << 24)) >> 25; + h2 += carry1; + h1 -= shift_left(carry1, 25); + carry5 = (h5 + (i64) (1 << 24)) >> 25; + h6 += carry5; + h5 -= shift_left(carry5, 25); + carry2 = (h2 + (i64) (1 << 25)) >> 26; + h3 += carry2; + h2 -= shift_left(carry2, 26); + carry6 = (h6 + (i64) (1 << 25)) >> 26; + h7 += carry6; + h6 -= shift_left(carry6, 26); + carry3 = (h3 + (i64) (1 << 24)) >> 25; + h4 += carry3; + h3 -= shift_left(carry3, 25); + carry7 = (h7 + (i64) (1 << 24)) >> 25; + h8 += carry7; + h7 -= shift_left(carry7, 25); + carry4 = (h4 + (i64) (1 << 25)) >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry8 = (h8 + (i64) (1 << 25)) >> 26; + h9 += carry8; + h8 -= shift_left(carry8, 26); + carry9 = (h9 + (i64) (1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shift_left(carry9, 25); + carry0 = (h0 + (i64) (1 << 25)) >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + h[0] = (i32) h0; + h[1] = (i32) h1; + h[2] = (i32) h2; + h[3] = (i32) h3; + h[4] = (i32) h4; + h[5] = (i32) h5; + h[6] = (i32) h6; + h[7] = (i32) h7; + h[8] = (i32) h8; + h[9] = (i32) h9; +} + + +/* +h = f - g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_sub(fe h, const fe f, const fe g) { + i32 f0 = f[0]; + i32 f1 = f[1]; + i32 f2 = f[2]; + i32 f3 = f[3]; + i32 f4 = f[4]; + i32 f5 = f[5]; + i32 f6 = f[6]; + i32 f7 = f[7]; + i32 f8 = f[8]; + i32 f9 = f[9]; + i32 g0 = g[0]; + i32 g1 = g[1]; + i32 g2 = g[2]; + i32 g3 = g[3]; + i32 g4 = g[4]; + i32 g5 = g[5]; + i32 g6 = g[6]; + i32 g7 = g[7]; + i32 g8 = g[8]; + i32 g9 = g[9]; + i32 h0 = f0 - g0; + i32 h1 = f1 - g1; + i32 h2 = f2 - g2; + i32 h3 = f3 - g3; + i32 h4 = f4 - g4; + i32 h5 = f5 - g5; + i32 h6 = f6 - g6; + i32 h7 = f7 - g7; + i32 h8 = f8 - g8; + i32 h9 = f9 - g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* +Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Write p=2^255-19; q=floor(h/p). +Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + +Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + carry0 = h0 >> 26; + h1 += carry0; + h0 -= shift_left(carry0, 26); + carry1 = h1 >> 25; + h2 += carry1; + h1 -= shift_left(carry1, 25); + carry2 = h2 >> 26; + h3 += carry2; + h2 -= shift_left(carry2, 26); + carry3 = h3 >> 25; + h4 += carry3; + h3 -= shift_left(carry3, 25); + carry4 = h4 >> 26; + h5 += carry4; + h4 -= shift_left(carry4, 26); + carry5 = h5 >> 25; + h6 += carry5; + h5 -= shift_left(carry5, 25); + carry6 = h6 >> 26; + h7 += carry6; + h6 -= shift_left(carry6, 26); + carry7 = h7 >> 25; + h8 += carry7; + h7 -= shift_left(carry7, 25); + carry8 = h8 >> 26; + h9 += carry8; + h8 -= shift_left(carry8, 26); + carry9 = h9 >> 25; + h9 -= shift_left(carry9, 25); + + /* h10 = carry9 */ + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + s[0] = (unsigned char) ((h0 >> 0) & 0xff); + s[1] = (unsigned char) ((h0 >> 8) & 0xff); + s[2] = (unsigned char) ((h0 >> 16) & 0xff); + s[3] = (unsigned char) (((h0 >> 24) | (h1 << 2)) & 0xff); + s[4] = (unsigned char) ((h1 >> 6) & 0xff); + s[5] = (unsigned char) ((h1 >> 14) & 0xff); + s[6] = (unsigned char) (((h1 >> 22) | (h2 << 3)) & 0xff); + s[7] = (unsigned char) ((h2 >> 5) & 0xff); + s[8] = (unsigned char) ((h2 >> 13) & 0xff); + s[9] = (unsigned char) (((h2 >> 21) | (h3 << 5)) & 0xff); + s[10] = (unsigned char) ((h3 >> 3) & 0xff); + s[11] = (unsigned char) ((h3 >> 11) & 0xff); + s[12] = (unsigned char) (((h3 >> 19) | (h4 << 6)) & 0xff); + s[13] = (unsigned char) ((h4 >> 2) & 0xff); + s[14] = (unsigned char) ((h4 >> 10) & 0xff); + s[15] = (unsigned char) ((h4 >> 18) & 0xff); + s[16] = (unsigned char) ((h5 >> 0) & 0xff); + s[17] = (unsigned char) ((h5 >> 8) & 0xff); + s[18] = (unsigned char) ((h5 >> 16) & 0xff); + s[19] = (unsigned char) (((h5 >> 24) | (h6 << 1)) & 0xff); + s[20] = (unsigned char) ((h6 >> 7) & 0xff); + s[21] = (unsigned char) ((h6 >> 15) & 0xff); + s[22] = (unsigned char) (((h6 >> 23) | (h7 << 3)) & 0xff); + s[23] = (unsigned char) ((h7 >> 5) & 0xff); + s[24] = (unsigned char) ((h7 >> 13) & 0xff); + s[25] = (unsigned char) (((h7 >> 21) | (h8 << 4)) & 0xff); + s[26] = (unsigned char) ((h8 >> 4) & 0xff); + s[27] = (unsigned char) ((h8 >> 12) & 0xff); + s[28] = (unsigned char) (((h8 >> 20) | (h9 << 6)) & 0xff); + s[29] = (unsigned char) ((h9 >> 2) & 0xff); + s[30] = (unsigned char) ((h9 >> 10) & 0xff); + s[31] = (unsigned char) ((h9 >> 18) & 0xff); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + diff --git a/ed25519/src/fe.h b/ed25519/src/fe.h new file mode 100644 index 0000000..fbe47dc --- /dev/null +++ b/ed25519/src/fe.h @@ -0,0 +1,41 @@ +#ifndef FE_H +#define FE_H + +#include "fixedint.h" + + +/* + fe means field element. + Here the field is \Z/(2^255-19). + An element t, entries t[0]...t[9], represents the integer + t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. + Bounds on each t[i] vary depending on context. +*/ + + +typedef i32 fe[10]; + + +void fe_0(fe h); +void fe_1(fe h); + +void fe_frombytes(fe h, const unsigned char *s); +void fe_tobytes(unsigned char *s, const fe h); + +void fe_copy(fe h, const fe f); +int fe_isnegative(const fe f); +int fe_isnonzero(const fe f); +void fe_cmov(fe f, const fe g, unsigned int b); +void fe_cswap(fe f, fe g, unsigned int b); + +void fe_neg(fe h, const fe f); +void fe_add(fe h, const fe f, const fe g); +void fe_invert(fe out, const fe z); +void fe_sq(fe h, const fe f); +void fe_sq2(fe h, const fe f); +void fe_mul(fe h, const fe f, const fe g); +void fe_mul121666(fe h, fe f); +void fe_pow22523(fe out, const fe z); +void fe_sub(fe h, const fe f, const fe g); + +#endif diff --git a/ed25519/src/fixedint.h b/ed25519/src/fixedint.h new file mode 100644 index 0000000..abb5a37 --- /dev/null +++ b/ed25519/src/fixedint.h @@ -0,0 +1,10 @@ +#ifndef FIXEDINT_H_INCLUDED +#define FIXEDINT_H_INCLUDED + +#include + +typedef std::uint64_t u64; +typedef std::int64_t i64; +typedef std::int32_t i32; + +#endif diff --git a/ed25519/src/ge.cpp b/ed25519/src/ge.cpp new file mode 100644 index 0000000..704877b --- /dev/null +++ b/ed25519/src/ge.cpp @@ -0,0 +1,472 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "ge.h" +#include "precomp_data.h" + + +/* +r = p + q +*/ + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YplusX); + fe_mul(r->Y, r->Y, q->YminusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +static void slide(signed char *r, const unsigned char *a) { + int i; + int b; + int k; + + for (i = 0; i < 256; ++i) { + r[i] = 1 & (a[i >> 3] >> (i & 7)); + } + + for (i = 0; i < 256; ++i) + if (r[i]) { + for (b = 1; b <= 6 && i + b < 256; ++b) { + if (r[i + b]) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + + for (k = i + b; k < 256; ++k) { + if (!r[k]) { + r[k] = 1; + break; + } + + r[k] = 0; + } + } else { + break; + } + } + } + } +} + +/* +r = a * A + b * B +where a = a[0]+256*a[1]+...+256^31 a[31]. +and b = b[0]+256*b[1]+...+256^31 b[31]. +B is the Ed25519 base point (x,4/5) with x positive. +*/ + +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + ge_p1p1 t; + ge_p3 u; + ge_p3 A2; + int i; + slide(aslide, a); + slide(bslide, b); + ge_p3_to_cached(&Ai[0], A); + ge_p3_dbl(&t, A); + ge_p1p1_to_p3(&A2, &t); + ge_add(&t, &A2, &Ai[0]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[1], &u); + ge_add(&t, &A2, &Ai[1]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[2], &u); + ge_add(&t, &A2, &Ai[2]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[3], &u); + ge_add(&t, &A2, &Ai[3]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[4], &u); + ge_add(&t, &A2, &Ai[4]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[5], &u); + ge_add(&t, &A2, &Ai[5]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[6], &u); + ge_add(&t, &A2, &Ai[6]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[7], &u); + ge_p2_0(r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) { + break; + } + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i] / 2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &Bi[bslide[i] / 2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + + +static const fe d = { + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 +}; + +static const fe sqrtm1 = { + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 +}; + +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) { + fe u; + fe v; + fe v3; + fe vxx; + fe check; + fe_frombytes(h->Y, s); + fe_1(h->Z); + fe_sq(u, h->Y); + fe_mul(v, u, d); + fe_sub(u, u, h->Z); /* u = y^2-1 */ + fe_add(v, v, h->Z); /* v = dy^2+1 */ + fe_sq(v3, v); + fe_mul(v3, v3, v); /* v3 = v^3 */ + fe_sq(h->X, v3); + fe_mul(h->X, h->X, v); + fe_mul(h->X, h->X, u); /* x = uv^7 */ + fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ + fe_mul(h->X, h->X, v3); + fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ + fe_sq(vxx, h->X); + fe_mul(vxx, vxx, v); + fe_sub(check, vxx, u); /* vx^2-u */ + + if (fe_isnonzero(check)) { + fe_add(check, vxx, u); /* vx^2+u */ + + if (fe_isnonzero(check)) { + return -1; + } + + fe_mul(h->X, h->X, sqrtm1); + } + + if (fe_isnegative(h->X) == (s[31] >> 7)) { + fe_neg(h->X, h->X); + } + + fe_mul(h->T, h->X, h->Y); + return 0; +} + + +/* +r = p + q +*/ + +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yplusx); + fe_mul(r->Y, r->Y, q->yminusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +/* +r = p - q +*/ + +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yminusx); + fe_mul(r->Y, r->Y, q->yplusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +/* +r = p +*/ + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); +} + + + +/* +r = p +*/ + +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); + fe_mul(r->T, p->X, p->Y); +} + + +void ge_p2_0(ge_p2 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); +} + + + +/* +r = 2 * p +*/ + +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { + fe t0; + + fe_sq(r->X, p->X); + fe_sq(r->Z, p->Y); + fe_sq2(r->T, p->Z); + fe_add(r->Y, p->X, p->Y); + fe_sq(t0, r->Y); + fe_add(r->Y, r->Z, r->X); + fe_sub(r->Z, r->Z, r->X); + fe_sub(r->X, t0, r->Y); + fe_sub(r->T, r->T, r->Z); +} + + +void ge_p3_0(ge_p3 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); + fe_0(h->T); +} + + +/* +r = 2 * p +*/ + +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { + ge_p2 q; + ge_p3_to_p2(&q, p); + ge_p2_dbl(r, &q); +} + + + +/* +r = p +*/ + +static const fe d2 = { + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 +}; + +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) { + fe_add(r->YplusX, p->Y, p->X); + fe_sub(r->YminusX, p->Y, p->X); + fe_copy(r->Z, p->Z); + fe_mul(r->T2d, p->T, d2); +} + + +/* +r = p +*/ + +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { + fe_copy(r->X, p->X); + fe_copy(r->Y, p->Y); + fe_copy(r->Z, p->Z); +} + + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} + + +static unsigned char equal(signed char b, signed char c) { + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + u64 y = x; /* 0: yes; 1..255: no */ + y -= 1; /* large: yes; 0..254: no */ + y >>= 63; /* 1: yes; 0: no */ + return (unsigned char) y; +} + +static unsigned char negative(signed char b) { + u64 x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return (unsigned char) x; +} + +static void cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) { + fe_cmov(t->yplusx, u->yplusx, b); + fe_cmov(t->yminusx, u->yminusx, b); + fe_cmov(t->xy2d, u->xy2d, b); +} + + +static void select(ge_precomp *t, int pos, signed char b) { + typedef signed char schar; + typedef unsigned char uchar; + ge_precomp minust; + unsigned char const bnegative = negative(b); + unsigned char const babs = b - schar(uchar((-bnegative) & b) << 1); + fe_1(t->yplusx); + fe_1(t->yminusx); + fe_0(t->xy2d); + cmov(t, &base[pos][0], equal(babs, 1)); + cmov(t, &base[pos][1], equal(babs, 2)); + cmov(t, &base[pos][2], equal(babs, 3)); + cmov(t, &base[pos][3], equal(babs, 4)); + cmov(t, &base[pos][4], equal(babs, 5)); + cmov(t, &base[pos][5], equal(babs, 6)); + cmov(t, &base[pos][6], equal(babs, 7)); + cmov(t, &base[pos][7], equal(babs, 8)); + fe_copy(minust.yplusx, t->yminusx); + fe_copy(minust.yminusx, t->yplusx); + fe_neg(minust.xy2d, t->xy2d); + cmov(t, &minust, bnegative); +} + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { + signed char e[64]; + signed char carry; + ge_p1p1 r; + ge_p2 s; + ge_precomp t; + int i; + + for (i = 0; i < 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 15; + e[2 * i + 1] = (a[i] >> 4) & 15; + } + + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + carry = 0; + + for (i = 0; i < 63; ++i) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + + e[63] += carry; + /* each e[i] is between -8 and 8 */ + ge_p3_0(h); + + for (i = 1; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } + + ge_p3_dbl(&r, h); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p3(h, &r); + + for (i = 0; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } +} + + +/* +r = p - q +*/ + +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YminusX); + fe_mul(r->Y, r->Y, q->YplusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +void ge_tobytes(unsigned char *s, const ge_p2 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} diff --git a/ed25519/src/ge.h b/ed25519/src/ge.h new file mode 100644 index 0000000..17fde2d --- /dev/null +++ b/ed25519/src/ge.h @@ -0,0 +1,74 @@ +#ifndef GE_H +#define GE_H + +#include "fe.h" + + +/* +ge means group element. + +Here the group is the set of pairs (x,y) of field elements (see fe.h) +satisfying -x^2 + y^2 = 1 + d x^2y^2 +where d = -121665/121666. + +Representations: + ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z + ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT + ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + ge_precomp (Duif): (y+x,y-x,2dxy) +*/ + +typedef struct { + fe X; + fe Y; + fe Z; +} ge_p2; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p3; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p1p1; + +typedef struct { + fe yplusx; + fe yminusx; + fe xy2d; +} ge_precomp; + +typedef struct { + fe YplusX; + fe YminusX; + fe Z; + fe T2d; +} ge_cached; + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h); +void ge_tobytes(unsigned char *s, const ge_p2 *h); +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s); + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b); +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a); + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p); +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p); +void ge_p2_0(ge_p2 *h); +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p); +void ge_p3_0(ge_p3 *h); +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p); +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p); + +#endif diff --git a/ed25519/src/key_exchange.cpp b/ed25519/src/key_exchange.cpp new file mode 100644 index 0000000..26840f8 --- /dev/null +++ b/ed25519/src/key_exchange.cpp @@ -0,0 +1,88 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "fe.h" + +namespace libtorrent +{ + +void ed25519_key_exchange(unsigned char *shared_secret + , const unsigned char *public_key, const unsigned char *private_key) { + unsigned char e[32]; + unsigned int i; + + fe x1; + fe x2; + fe z2; + fe x3; + fe z3; + fe tmp0; + fe tmp1; + + int pos; + unsigned int swap; + unsigned int b; + + /* copy the private key and make sure it's valid */ + for (i = 0; i < 32; ++i) { + e[i] = private_key[i]; + } + + e[0] &= 248; + e[31] &= 63; + e[31] |= 64; + + /* unpack the public key and convert edwards to montgomery */ + /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */ + fe_frombytes(x1, public_key); + fe_1(tmp1); + fe_add(tmp0, x1, tmp1); + fe_sub(tmp1, tmp1, x1); + fe_invert(tmp1, tmp1); + fe_mul(x1, tmp0, tmp1); + + fe_1(x2); + fe_0(z2); + fe_copy(x3, x1); + fe_1(z3); + + swap = 0; + for (pos = 254; pos >= 0; --pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + swap ^= b; + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; + + /* from montgomery.h */ + fe_sub(tmp0, x3, z3); + fe_sub(tmp1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, tmp0, x2); + fe_mul(z2, z2, tmp1); + fe_sq(tmp0, tmp1); + fe_sq(tmp1, x2); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, tmp1, tmp0); + fe_sub(tmp1, tmp1, tmp0); + fe_sq(z2, z2); + fe_mul121666(z3, tmp1); + fe_sq(x3, x3); + fe_add(tmp0, tmp0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, tmp1, tmp0); + } + + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(shared_secret, x2); +} + +} diff --git a/ed25519/src/keypair.cpp b/ed25519/src/keypair.cpp new file mode 100644 index 0000000..2cd6619 --- /dev/null +++ b/ed25519/src/keypair.cpp @@ -0,0 +1,24 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "libtorrent/hasher512.hpp" +#include "ge.h" + +namespace libtorrent +{ + +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { + ge_p3 A; + + hasher512 hash({reinterpret_cast(seed), 32}); + std::memcpy(private_key, hash.final().data(), 64); + private_key[0] &= 248; + private_key[31] &= 63; + private_key[31] |= 64; + + ge_scalarmult_base(&A, private_key); + ge_p3_tobytes(public_key, &A); +} + +} diff --git a/ed25519/src/precomp_data.h b/ed25519/src/precomp_data.h new file mode 100644 index 0000000..791454f --- /dev/null +++ b/ed25519/src/precomp_data.h @@ -0,0 +1,1392 @@ +static const ge_precomp Bi[8] = { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { -22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877 }, + { -6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951 }, + { 4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784 }, + }, + { + { -25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436 }, + { 25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918 }, + { 23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877 }, + }, + { + { -33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800 }, + { -25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305 }, + { -13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300 }, + }, + { + { -3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876 }, + { -24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619 }, + { -3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683 }, + }, +}; + + +/* base[i][j] = (j+1)*256^i*B */ +static const ge_precomp base[32][8] = { + { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 }, + { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 }, + { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 }, + { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 }, + { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 }, + { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 }, + { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 }, + { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 }, + { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 }, + }, + }, + { + { + { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 }, + { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 }, + { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 }, + }, + { + { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 }, + { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 }, + { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 }, + }, + { + { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 }, + { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 }, + { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 }, + }, + { + { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 }, + { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 }, + { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 }, + }, + { + { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 }, + { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 }, + { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 }, + }, + { + { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 }, + { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 }, + { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 }, + }, + { + { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 }, + { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 }, + { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 }, + }, + { + { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 }, + { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 }, + { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 }, + }, + }, + { + { + { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 }, + { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 }, + { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 }, + }, + { + { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 }, + { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 }, + { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 }, + }, + { + { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 }, + { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 }, + { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 }, + }, + { + { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 }, + { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 }, + { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 }, + }, + { + { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 }, + { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 }, + { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 }, + }, + { + { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 }, + { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 }, + { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 }, + }, + { + { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 }, + { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 }, + { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 }, + }, + { + { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 }, + { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 }, + { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 }, + }, + }, + { + { + { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 }, + { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 }, + { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 }, + }, + { + { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 }, + { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 }, + { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 }, + }, + { + { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 }, + { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 }, + { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 }, + }, + { + { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 }, + { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 }, + { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 }, + }, + { + { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 }, + { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 }, + { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 }, + }, + { + { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 }, + { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 }, + { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 }, + }, + { + { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 }, + { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 }, + { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 }, + }, + { + { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 }, + { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 }, + { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 }, + }, + }, + { + { + { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 }, + { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 }, + { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 }, + }, + { + { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 }, + { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 }, + { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 }, + }, + { + { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 }, + { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 }, + { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 }, + }, + { + { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 }, + { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 }, + { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 }, + }, + { + { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 }, + { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 }, + { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 }, + }, + { + { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 }, + { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 }, + { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 }, + }, + { + { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 }, + { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 }, + { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 }, + }, + { + { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 }, + { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 }, + { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 }, + }, + }, + { + { + { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 }, + { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 }, + { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 }, + }, + { + { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 }, + { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 }, + { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 }, + }, + { + { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 }, + { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 }, + { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 }, + }, + { + { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 }, + { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 }, + { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 }, + }, + { + { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 }, + { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 }, + { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 }, + }, + { + { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 }, + { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 }, + { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 }, + }, + { + { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 }, + { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 }, + { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 }, + }, + { + { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 }, + { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 }, + { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 }, + }, + }, + { + { + { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 }, + { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 }, + { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 }, + }, + { + { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 }, + { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 }, + { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 }, + }, + { + { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 }, + { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 }, + { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 }, + }, + { + { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 }, + { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 }, + { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 }, + }, + { + { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 }, + { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 }, + { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 }, + }, + { + { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 }, + { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 }, + { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 }, + }, + { + { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 }, + { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 }, + { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 }, + }, + { + { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 }, + { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 }, + { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 }, + }, + }, + { + { + { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 }, + { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 }, + { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 }, + }, + { + { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 }, + { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 }, + { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 }, + }, + { + { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 }, + { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 }, + { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 }, + }, + { + { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 }, + { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 }, + { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 }, + }, + { + { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 }, + { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 }, + { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 }, + }, + { + { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 }, + { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 }, + { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 }, + }, + { + { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 }, + { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 }, + { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 }, + }, + { + { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 }, + { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 }, + { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 }, + }, + }, + { + { + { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 }, + { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 }, + { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 }, + }, + { + { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 }, + { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 }, + { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 }, + }, + { + { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 }, + { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 }, + { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 }, + }, + { + { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 }, + { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 }, + { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 }, + }, + { + { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 }, + { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 }, + { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 }, + }, + { + { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 }, + { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 }, + { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 }, + }, + { + { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 }, + { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 }, + { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 }, + }, + { + { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 }, + { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 }, + { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 }, + }, + }, + { + { + { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 }, + { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 }, + { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 }, + }, + { + { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 }, + { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 }, + { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 }, + }, + { + { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 }, + { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 }, + { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 }, + }, + { + { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 }, + { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 }, + { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 }, + }, + { + { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 }, + { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 }, + { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 }, + }, + { + { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 }, + { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 }, + { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 }, + }, + { + { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 }, + { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 }, + { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 }, + }, + { + { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 }, + { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 }, + { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 }, + }, + }, + { + { + { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 }, + { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 }, + { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 }, + }, + { + { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 }, + { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 }, + { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 }, + }, + { + { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 }, + { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 }, + { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 }, + }, + { + { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 }, + { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 }, + { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 }, + }, + { + { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 }, + { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 }, + { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 }, + }, + { + { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 }, + { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 }, + { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 }, + }, + { + { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 }, + { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 }, + { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 }, + }, + { + { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 }, + { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 }, + { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 }, + }, + }, + { + { + { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 }, + { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 }, + { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 }, + }, + { + { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 }, + { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 }, + { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 }, + }, + { + { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 }, + { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 }, + { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 }, + }, + { + { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 }, + { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 }, + { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 }, + }, + { + { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 }, + { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 }, + { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 }, + }, + { + { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 }, + { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 }, + { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 }, + }, + { + { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 }, + { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 }, + { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 }, + }, + { + { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 }, + { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 }, + { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 }, + }, + }, + { + { + { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 }, + { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 }, + { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 }, + }, + { + { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 }, + { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 }, + { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 }, + }, + { + { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 }, + { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 }, + { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 }, + }, + { + { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 }, + { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 }, + { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 }, + }, + { + { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 }, + { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 }, + { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 }, + }, + { + { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 }, + { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 }, + { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 }, + }, + { + { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 }, + { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 }, + { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 }, + }, + { + { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 }, + { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 }, + { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 }, + }, + }, + { + { + { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 }, + { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 }, + { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 }, + }, + { + { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 }, + { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 }, + { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 }, + }, + { + { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 }, + { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 }, + { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 }, + }, + { + { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 }, + { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 }, + { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 }, + }, + { + { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 }, + { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 }, + { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 }, + }, + { + { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 }, + { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 }, + { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 }, + }, + { + { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 }, + { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 }, + { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 }, + }, + { + { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 }, + { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 }, + { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 }, + }, + }, + { + { + { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 }, + { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 }, + { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 }, + }, + { + { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 }, + { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 }, + { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 }, + }, + { + { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 }, + { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 }, + { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 }, + }, + { + { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 }, + { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 }, + { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 }, + }, + { + { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 }, + { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 }, + { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 }, + }, + { + { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 }, + { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 }, + { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 }, + }, + { + { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 }, + { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 }, + { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 }, + }, + { + { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 }, + { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 }, + { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 }, + }, + }, + { + { + { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 }, + { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 }, + { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 }, + }, + { + { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 }, + { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 }, + { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 }, + }, + { + { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 }, + { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 }, + { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 }, + }, + { + { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 }, + { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 }, + { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 }, + }, + { + { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 }, + { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 }, + { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 }, + }, + { + { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 }, + { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 }, + { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 }, + }, + { + { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 }, + { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 }, + { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 }, + }, + { + { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 }, + { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 }, + { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 }, + }, + }, + { + { + { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 }, + { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 }, + { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 }, + }, + { + { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 }, + { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 }, + { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 }, + }, + { + { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 }, + { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 }, + { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 }, + }, + { + { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 }, + { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 }, + { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 }, + }, + { + { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 }, + { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 }, + { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 }, + }, + { + { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 }, + { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 }, + { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 }, + }, + { + { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 }, + { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 }, + { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 }, + }, + { + { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 }, + { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 }, + { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 }, + }, + }, + { + { + { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 }, + { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 }, + { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 }, + }, + { + { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 }, + { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 }, + { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 }, + }, + { + { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 }, + { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 }, + { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 }, + }, + { + { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 }, + { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 }, + { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 }, + }, + { + { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 }, + { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 }, + { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 }, + }, + { + { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 }, + { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 }, + { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 }, + }, + { + { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 }, + { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 }, + { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 }, + }, + { + { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 }, + { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 }, + { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 }, + }, + }, + { + { + { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 }, + { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 }, + { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 }, + }, + { + { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 }, + { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 }, + { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 }, + }, + { + { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 }, + { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 }, + { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 }, + }, + { + { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 }, + { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 }, + { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 }, + }, + { + { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 }, + { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 }, + { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 }, + }, + { + { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 }, + { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 }, + { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 }, + }, + { + { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 }, + { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 }, + { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 }, + }, + { + { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 }, + { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 }, + { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 }, + }, + }, + { + { + { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 }, + { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 }, + { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 }, + }, + { + { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 }, + { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 }, + { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 }, + }, + { + { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 }, + { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 }, + { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 }, + }, + { + { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 }, + { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 }, + { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 }, + }, + { + { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 }, + { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 }, + { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 }, + }, + { + { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 }, + { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 }, + { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 }, + }, + { + { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 }, + { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 }, + { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 }, + }, + { + { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 }, + { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 }, + { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 }, + }, + }, + { + { + { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 }, + { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 }, + { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 }, + }, + { + { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 }, + { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 }, + { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 }, + }, + { + { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 }, + { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 }, + { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 }, + }, + { + { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 }, + { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 }, + { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 }, + }, + { + { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 }, + { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 }, + { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 }, + }, + { + { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 }, + { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 }, + { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 }, + }, + { + { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 }, + { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 }, + { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 }, + }, + { + { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 }, + { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 }, + { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 }, + }, + }, + { + { + { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 }, + { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 }, + { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 }, + }, + { + { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 }, + { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 }, + { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 }, + }, + { + { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 }, + { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 }, + { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 }, + }, + { + { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 }, + { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 }, + { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 }, + }, + { + { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 }, + { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 }, + { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 }, + }, + { + { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 }, + { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 }, + { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 }, + }, + { + { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 }, + { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 }, + { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 }, + }, + { + { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 }, + { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 }, + { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 }, + }, + }, + { + { + { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 }, + { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 }, + { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 }, + }, + { + { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 }, + { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 }, + { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 }, + }, + { + { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 }, + { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 }, + { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 }, + }, + { + { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 }, + { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 }, + { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 }, + }, + { + { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 }, + { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 }, + { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 }, + }, + { + { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 }, + { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 }, + { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 }, + }, + { + { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 }, + { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 }, + { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 }, + }, + { + { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 }, + { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 }, + { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 }, + }, + }, + { + { + { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 }, + { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 }, + { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 }, + }, + { + { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 }, + { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 }, + { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 }, + }, + { + { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 }, + { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 }, + { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 }, + }, + { + { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 }, + { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 }, + { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 }, + }, + { + { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 }, + { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 }, + { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 }, + }, + { + { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 }, + { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 }, + { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 }, + }, + { + { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 }, + { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 }, + { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 }, + }, + { + { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 }, + { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 }, + { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 }, + }, + }, + { + { + { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 }, + { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 }, + { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 }, + }, + { + { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 }, + { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 }, + { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 }, + }, + { + { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 }, + { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 }, + { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 }, + }, + { + { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 }, + { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 }, + { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 }, + }, + { + { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 }, + { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 }, + { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 }, + }, + { + { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 }, + { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 }, + { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 }, + }, + { + { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 }, + { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 }, + { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 }, + }, + { + { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 }, + { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 }, + { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 }, + }, + }, + { + { + { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 }, + { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 }, + { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 }, + }, + { + { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 }, + { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 }, + { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 }, + }, + { + { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 }, + { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 }, + { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 }, + }, + { + { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 }, + { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 }, + { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 }, + }, + { + { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 }, + { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 }, + { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 }, + }, + { + { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 }, + { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 }, + { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 }, + }, + { + { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 }, + { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 }, + { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 }, + }, + { + { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 }, + { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 }, + { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 }, + }, + }, + { + { + { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 }, + { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 }, + { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 }, + }, + { + { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 }, + { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 }, + { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 }, + }, + { + { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 }, + { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 }, + { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 }, + }, + { + { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 }, + { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 }, + { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 }, + }, + { + { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 }, + { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 }, + { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 }, + }, + { + { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 }, + { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 }, + { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 }, + }, + { + { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 }, + { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 }, + { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 }, + }, + { + { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 }, + { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 }, + { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 }, + }, + }, + { + { + { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 }, + { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 }, + { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 }, + }, + { + { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 }, + { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 }, + { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 }, + }, + { + { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 }, + { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 }, + { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 }, + }, + { + { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 }, + { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 }, + { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 }, + }, + { + { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 }, + { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 }, + { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 }, + }, + { + { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 }, + { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 }, + { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 }, + }, + { + { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 }, + { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 }, + { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 }, + }, + { + { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 }, + { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 }, + { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 }, + }, + }, + { + { + { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 }, + { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 }, + { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 }, + }, + { + { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 }, + { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 }, + { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 }, + }, + { + { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 }, + { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 }, + { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 }, + }, + { + { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 }, + { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 }, + { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 }, + }, + { + { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 }, + { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 }, + { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 }, + }, + { + { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 }, + { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 }, + { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 }, + }, + { + { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 }, + { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 }, + { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 }, + }, + { + { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 }, + { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 }, + { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 }, + }, + }, + { + { + { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 }, + { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 }, + { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 }, + }, + { + { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 }, + { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 }, + { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 }, + }, + { + { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 }, + { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 }, + { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 }, + }, + { + { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 }, + { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 }, + { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 }, + }, + { + { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 }, + { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 }, + { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 }, + }, + { + { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 }, + { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 }, + { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 }, + }, + { + { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 }, + { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 }, + { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 }, + }, + { + { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 }, + { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 }, + { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 }, + }, + }, + { + { + { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 }, + { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 }, + { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 }, + }, + { + { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 }, + { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 }, + { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 }, + }, + { + { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 }, + { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 }, + { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 }, + }, + { + { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 }, + { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 }, + { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 }, + }, + { + { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 }, + { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 }, + { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 }, + }, + { + { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 }, + { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 }, + { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 }, + }, + { + { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 }, + { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 }, + { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 }, + }, + { + { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 }, + { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 }, + { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 }, + }, + }, + { + { + { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 }, + { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 }, + { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 }, + }, + { + { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 }, + { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 }, + { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 }, + }, + { + { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 }, + { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 }, + { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 }, + }, + { + { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 }, + { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 }, + { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 }, + }, + { + { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 }, + { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 }, + { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 }, + }, + { + { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 }, + { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 }, + { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 }, + }, + { + { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 }, + { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 }, + { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 }, + }, + { + { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 }, + { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 }, + { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 }, + }, + }, +}; + diff --git a/ed25519/src/sc.cpp b/ed25519/src/sc.cpp new file mode 100644 index 0000000..8a18055 --- /dev/null +++ b/ed25519/src/sc.cpp @@ -0,0 +1,816 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "fixedint.h" +#include "sc.h" + +static u64 load_3(const unsigned char *in) { + u64 result; + + result = (u64) in[0]; + result |= ((u64) in[1]) << 8; + result |= ((u64) in[2]) << 16; + + return result; +} + +static u64 load_4(const unsigned char *in) { + u64 result; + + result = (u64) in[0]; + result |= ((u64) in[1]) << 8; + result |= ((u64) in[2]) << 16; + result |= ((u64) in[3]) << 24; + + return result; +} + +static inline i64 shift_left(i64 v, int s) { + return i64(u64(v) << s); +} + +/* +Input: + s[0]+256*s[1]+...+256^63*s[63] = s + +Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. +*/ + +void sc_reduce(unsigned char *s) { + i64 s0 = 2097151 & load_3(s); + i64 s1 = 2097151 & (load_4(s + 2) >> 5); + i64 s2 = 2097151 & (load_3(s + 5) >> 2); + i64 s3 = 2097151 & (load_4(s + 7) >> 7); + i64 s4 = 2097151 & (load_4(s + 10) >> 4); + i64 s5 = 2097151 & (load_3(s + 13) >> 1); + i64 s6 = 2097151 & (load_4(s + 15) >> 6); + i64 s7 = 2097151 & (load_3(s + 18) >> 3); + i64 s8 = 2097151 & load_3(s + 21); + i64 s9 = 2097151 & (load_4(s + 23) >> 5); + i64 s10 = 2097151 & (load_3(s + 26) >> 2); + i64 s11 = 2097151 & (load_4(s + 28) >> 7); + i64 s12 = 2097151 & (load_4(s + 31) >> 4); + i64 s13 = 2097151 & (load_3(s + 34) >> 1); + i64 s14 = 2097151 & (load_4(s + 36) >> 6); + i64 s15 = 2097151 & (load_3(s + 39) >> 3); + i64 s16 = 2097151 & load_3(s + 42); + i64 s17 = 2097151 & (load_4(s + 44) >> 5); + i64 s18 = 2097151 & (load_3(s + 47) >> 2); + i64 s19 = 2097151 & (load_4(s + 49) >> 7); + i64 s20 = 2097151 & (load_4(s + 52) >> 4); + i64 s21 = 2097151 & (load_3(s + 55) >> 1); + i64 s22 = 2097151 & (load_4(s + 57) >> 6); + i64 s23 = (load_4(s + 60) >> 3); + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + i64 carry10; + i64 carry11; + i64 carry12; + i64 carry13; + i64 carry14; + i64 carry15; + i64 carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; // NOLINT + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; // NOLINT + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; // NOLINT + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; // NOLINT + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; // NOLINT + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; // NOLINT + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= shift_left(carry12, 21); + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= shift_left(carry14, 21); + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= shift_left(carry16, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= shift_left(carry13, 21); + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= shift_left(carry15, 21); + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; // NOLINT + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; // NOLINT + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; // NOLINT + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; // NOLINT + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; // NOLINT + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry11 = s11 >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; // NOLINT + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + + s[0] = (unsigned char) ((s0 >> 0) & 0xff); + s[1] = (unsigned char) ((s0 >> 8) & 0xff); + s[2] = (unsigned char) (((s0 >> 16) | (s1 << 5)) & 0xff); + s[3] = (unsigned char) ((s1 >> 3) & 0xff); + s[4] = (unsigned char) ((s1 >> 11) & 0xff); + s[5] = (unsigned char) (((s1 >> 19) | (s2 << 2)) & 0xff); + s[6] = (unsigned char) ((s2 >> 6) & 0xff); + s[7] = (unsigned char) (((s2 >> 14) | (s3 << 7)) & 0xff); + s[8] = (unsigned char) ((s3 >> 1) & 0xff); + s[9] = (unsigned char) ((s3 >> 9) & 0xff); + s[10] = (unsigned char) (((s3 >> 17) | (s4 << 4)) & 0xff); + s[11] = (unsigned char) ((s4 >> 4) & 0xff); + s[12] = (unsigned char) ((s4 >> 12) & 0xff); + s[13] = (unsigned char) (((s4 >> 20) | (s5 << 1)) & 0xff); + s[14] = (unsigned char) ((s5 >> 7) & 0xff); + s[15] = (unsigned char) (((s5 >> 15) | (s6 << 6)) & 0xff); + s[16] = (unsigned char) ((s6 >> 2) & 0xff); + s[17] = (unsigned char) ((s6 >> 10) & 0xff); + s[18] = (unsigned char) (((s6 >> 18) | (s7 << 3)) & 0xff); + s[19] = (unsigned char) ((s7 >> 5) & 0xff); + s[20] = (unsigned char) ((s7 >> 13) & 0xff); + s[21] = (unsigned char) ((s8 >> 0) & 0xff); + s[22] = (unsigned char) ((s8 >> 8) & 0xff); + s[23] = (unsigned char) (((s8 >> 16) | (s9 << 5)) & 0xff); + s[24] = (unsigned char) ((s9 >> 3) & 0xff); + s[25] = (unsigned char) ((s9 >> 11) & 0xff); + s[26] = (unsigned char) (((s9 >> 19) | (s10 << 2)) & 0xff); + s[27] = (unsigned char) ((s10 >> 6) & 0xff); + s[28] = (unsigned char) (((s10 >> 14) | (s11 << 7)) & 0xff); + s[29] = (unsigned char) ((s11 >> 1) & 0xff); + s[30] = (unsigned char) ((s11 >> 9) & 0xff); + s[31] = (unsigned char) ((s11 >> 17) & 0xff); +} + + + +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + i64 a0 = 2097151 & load_3(a); + i64 a1 = 2097151 & (load_4(a + 2) >> 5); + i64 a2 = 2097151 & (load_3(a + 5) >> 2); + i64 a3 = 2097151 & (load_4(a + 7) >> 7); + i64 a4 = 2097151 & (load_4(a + 10) >> 4); + i64 a5 = 2097151 & (load_3(a + 13) >> 1); + i64 a6 = 2097151 & (load_4(a + 15) >> 6); + i64 a7 = 2097151 & (load_3(a + 18) >> 3); + i64 a8 = 2097151 & load_3(a + 21); + i64 a9 = 2097151 & (load_4(a + 23) >> 5); + i64 a10 = 2097151 & (load_3(a + 26) >> 2); + i64 a11 = (load_4(a + 28) >> 7); + i64 b0 = 2097151 & load_3(b); + i64 b1 = 2097151 & (load_4(b + 2) >> 5); + i64 b2 = 2097151 & (load_3(b + 5) >> 2); + i64 b3 = 2097151 & (load_4(b + 7) >> 7); + i64 b4 = 2097151 & (load_4(b + 10) >> 4); + i64 b5 = 2097151 & (load_3(b + 13) >> 1); + i64 b6 = 2097151 & (load_4(b + 15) >> 6); + i64 b7 = 2097151 & (load_3(b + 18) >> 3); + i64 b8 = 2097151 & load_3(b + 21); + i64 b9 = 2097151 & (load_4(b + 23) >> 5); + i64 b10 = 2097151 & (load_3(b + 26) >> 2); + i64 b11 = (load_4(b + 28) >> 7); + i64 c0 = 2097151 & load_3(c); + i64 c1 = 2097151 & (load_4(c + 2) >> 5); + i64 c2 = 2097151 & (load_3(c + 5) >> 2); + i64 c3 = 2097151 & (load_4(c + 7) >> 7); + i64 c4 = 2097151 & (load_4(c + 10) >> 4); + i64 c5 = 2097151 & (load_3(c + 13) >> 1); + i64 c6 = 2097151 & (load_4(c + 15) >> 6); + i64 c7 = 2097151 & (load_3(c + 18) >> 3); + i64 c8 = 2097151 & load_3(c + 21); + i64 c9 = 2097151 & (load_4(c + 23) >> 5); + i64 c10 = 2097151 & (load_3(c + 26) >> 2); + i64 c11 = (load_4(c + 28) >> 7); + i64 s0; + i64 s1; + i64 s2; + i64 s3; + i64 s4; + i64 s5; + i64 s6; + i64 s7; + i64 s8; + i64 s9; + i64 s10; + i64 s11; + i64 s12; + i64 s13; + i64 s14; + i64 s15; + i64 s16; + i64 s17; + i64 s18; + i64 s19; + i64 s20; + i64 s21; + i64 s22; + i64 s23; + i64 carry0; + i64 carry1; + i64 carry2; + i64 carry3; + i64 carry4; + i64 carry5; + i64 carry6; + i64 carry7; + i64 carry8; + i64 carry9; + i64 carry10; + i64 carry11; + i64 carry12; + i64 carry13; + i64 carry14; + i64 carry15; + i64 carry16; + i64 carry17; + i64 carry18; + i64 carry19; + i64 carry20; + i64 carry21; + i64 carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= shift_left(carry12, 21); + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= shift_left(carry14, 21); + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= shift_left(carry16, 21); + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= shift_left(carry18, 21); + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= shift_left(carry20, 21); + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= shift_left(carry22, 21); + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= shift_left(carry13, 21); + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= shift_left(carry15, 21); + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= shift_left(carry17, 21); + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= shift_left(carry19, 21); + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= shift_left(carry21, 21); + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s23 = 0; // NOLINT + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s22 = 0; // NOLINT + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s21 = 0; // NOLINT + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s20 = 0; // NOLINT + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s19 = 0; // NOLINT + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + s18 = 0; // NOLINT + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= shift_left(carry12, 21); + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= shift_left(carry14, 21); + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= shift_left(carry16, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= shift_left(carry13, 21); + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= shift_left(carry15, 21); + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s17 = 0; // NOLINT + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s16 = 0; // NOLINT + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s15 = 0; // NOLINT + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s14 = 0; // NOLINT + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s13 = 0; // NOLINT + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; // NOLINT + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + carry11 = s11 >> 21; + s12 += carry11; + s11 -= shift_left(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; // NOLINT + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shift_left(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shift_left(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shift_left(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shift_left(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shift_left(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shift_left(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shift_left(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shift_left(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shift_left(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shift_left(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shift_left(carry10, 21); + + s[0] = (unsigned char) ((s0 >> 0) & 0xff); + s[1] = (unsigned char) ((s0 >> 8) & 0xff); + s[2] = (unsigned char) (((s0 >> 16) | (s1 << 5)) & 0xff); + s[3] = (unsigned char) ((s1 >> 3) & 0xff); + s[4] = (unsigned char) ((s1 >> 11) & 0xff); + s[5] = (unsigned char) (((s1 >> 19) | (s2 << 2)) & 0xff); + s[6] = (unsigned char) ((s2 >> 6) & 0xff); + s[7] = (unsigned char) (((s2 >> 14) | (s3 << 7)) & 0xff); + s[8] = (unsigned char) ((s3 >> 1) & 0xff); + s[9] = (unsigned char) ((s3 >> 9) & 0xff); + s[10] = (unsigned char) (((s3 >> 17) | (s4 << 4)) & 0xff); + s[11] = (unsigned char) ((s4 >> 4) & 0xff); + s[12] = (unsigned char) ((s4 >> 12) & 0xff); + s[13] = (unsigned char) (((s4 >> 20) | (s5 << 1)) & 0xff); + s[14] = (unsigned char) ((s5 >> 7) & 0xff); + s[15] = (unsigned char) (((s5 >> 15) | (s6 << 6)) & 0xff); + s[16] = (unsigned char) ((s6 >> 2) & 0xff); + s[17] = (unsigned char) ((s6 >> 10) & 0xff); + s[18] = (unsigned char) (((s6 >> 18) | (s7 << 3)) & 0xff); + s[19] = (unsigned char) ((s7 >> 5) & 0xff); + s[20] = (unsigned char) ((s7 >> 13) & 0xff); + s[21] = (unsigned char) ((s8 >> 0) & 0xff); + s[22] = (unsigned char) ((s8 >> 8) & 0xff); + s[23] = (unsigned char) (((s8 >> 16) | (s9 << 5)) & 0xff); + s[24] = (unsigned char) ((s9 >> 3) & 0xff); + s[25] = (unsigned char) ((s9 >> 11) & 0xff); + s[26] = (unsigned char) (((s9 >> 19) | (s10 << 2)) & 0xff); + s[27] = (unsigned char) ((s10 >> 6) & 0xff); + s[28] = (unsigned char) (((s10 >> 14) | (s11 << 7)) & 0xff); + s[29] = (unsigned char) ((s11 >> 1) & 0xff); + s[30] = (unsigned char) ((s11 >> 9) & 0xff); + s[31] = (unsigned char) ((s11 >> 17) & 0xff); +} diff --git a/ed25519/src/sc.h b/ed25519/src/sc.h new file mode 100644 index 0000000..0c058e5 --- /dev/null +++ b/ed25519/src/sc.h @@ -0,0 +1,13 @@ +#ifndef SC_H +#define SC_H + +/* +The set of scalars is \Z/l +where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_reduce(unsigned char *s); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); + +#endif + diff --git a/ed25519/src/sign.cpp b/ed25519/src/sign.cpp new file mode 100644 index 0000000..aef85ab --- /dev/null +++ b/ed25519/src/sign.cpp @@ -0,0 +1,37 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "libtorrent/hasher512.hpp" +#include "ge.h" +#include "sc.h" + +namespace libtorrent +{ + +void ed25519_sign(unsigned char *signature, const unsigned char *message, std::ptrdiff_t message_len, const unsigned char *public_key, const unsigned char *private_key) { + ge_p3 R; + + hasher512 hash; + hash.update({reinterpret_cast(private_key) + 32, 32}); + hash.update({reinterpret_cast(message), message_len}); + sha512_hash r = hash.final(); + + sc_reduce(reinterpret_cast(r.data())); + ge_scalarmult_base(&R, reinterpret_cast(r.data())); + ge_p3_tobytes(signature, &R); + + hash.reset(); + hash.update({reinterpret_cast(signature), 32}); + hash.update({reinterpret_cast(public_key), 32}); + hash.update({reinterpret_cast(message), message_len}); + sha512_hash hram = hash.final(); + + sc_reduce(reinterpret_cast(hram.data())); + sc_muladd(signature + 32 + , reinterpret_cast(hram.data()) + , private_key + , reinterpret_cast(r.data())); +} + +} diff --git a/ed25519/src/verify.cpp b/ed25519/src/verify.cpp new file mode 100644 index 0000000..94f9534 --- /dev/null +++ b/ed25519/src/verify.cpp @@ -0,0 +1,84 @@ +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include "libtorrent/ed25519.hpp" +#include "libtorrent/hasher512.hpp" +#include "ge.h" +#include "sc.h" + +namespace libtorrent +{ + +static int consttime_equal(const unsigned char *x, const unsigned char *y) { + unsigned char r = 0; + + r = x[0] ^ y[0]; + #define F(i) r |= x[i] ^ y[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); + #undef F + + return !r; +} + +int ed25519_verify(const unsigned char *signature, const unsigned char *message, std::ptrdiff_t message_len, const unsigned char *public_key) { + unsigned char checker[32]; + ge_p3 A; + ge_p2 R; + + if (signature[63] & 224) { + return 0; + } + + if (ge_frombytes_negate_vartime(&A, public_key) != 0) { + return 0; + } + + hasher512 hash; + hash.update({reinterpret_cast(signature), 32}); + hash.update({reinterpret_cast(public_key), 32}); + hash.update({reinterpret_cast(message), message_len}); + sha512_hash h = hash.final(); + + sc_reduce(reinterpret_cast(h.data())); + ge_double_scalarmult_vartime(&R, reinterpret_cast(h.data()) + , &A, signature + 32); + ge_tobytes(checker, &R); + + if (!consttime_equal(checker, signature)) { + return 0; + } + + return 1; +} + +} diff --git a/ed25519/test.c b/ed25519/test.c new file mode 100644 index 0000000..a56d202 --- /dev/null +++ b/ed25519/test.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include + +//#define ED25519_DLL +#include "src/ed25519.h" + +#include "src/ge.h" +#include "src/sc.h" + +const char message[] = "Hello, world!"; + + +int main(int argc, char *argv[]) { + unsigned char public_key[32], private_key[64], seed[32], scalar[32]; + unsigned char other_public_key[32], other_private_key[64]; + unsigned char shared_secret[32], other_shared_secret[32]; + unsigned char signature[64]; + + clock_t start; + clock_t end; + int i; + + /* create a random seed, and a keypair out of that seed */ + ed25519_create_seed(seed); + ed25519_create_keypair(public_key, private_key, seed); + + /* create signature on the message with the keypair */ + ed25519_sign(signature, message, strlen(message), public_key, private_key); + + /* verify the signature */ + if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); + } else { + printf("invalid signature\n"); + } + + /* create scalar and add it to the keypair */ + ed25519_create_seed(scalar); + ed25519_add_scalar(public_key, private_key, scalar); + + /* create signature with the new keypair */ + ed25519_sign(signature, message, strlen(message), public_key, private_key); + + /* verify the signature with the new keypair */ + if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("valid signature\n"); + } else { + printf("invalid signature\n"); + } + + /* make a slight adjustment and verify again */ + signature[44] ^= 0x10; + if (ed25519_verify(signature, message, strlen(message), public_key)) { + printf("did not detect signature change\n"); + } else { + printf("correctly detected signature change\n"); + } + + /* generate two keypairs for testing key exchange */ + ed25519_create_seed(seed); + ed25519_create_keypair(public_key, private_key, seed); + ed25519_create_seed(seed); + ed25519_create_keypair(other_public_key, other_private_key, seed); + + /* create two shared secrets - from both perspectives - and check if they're equal */ + ed25519_key_exchange(shared_secret, other_public_key, private_key); + ed25519_key_exchange(other_shared_secret, public_key, other_private_key); + + for (i = 0; i < 32; ++i) { + if (shared_secret[i] != other_shared_secret[i]) { + printf("key exchange was incorrect\n"); + break; + } + } + + if (i == 32) { + printf("key exchange was correct\n"); + } + + /* test performance */ + printf("testing seed generation performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_create_seed(seed); + } + end = clock(); + + printf("%fus per seed\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + + printf("testing key generation performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_create_keypair(public_key, private_key, seed); + } + end = clock(); + + printf("%fus per keypair\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing sign performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_sign(signature, message, strlen(message), public_key, private_key); + } + end = clock(); + + printf("%fus per signature\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing verify performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_verify(signature, message, strlen(message), public_key); + } + end = clock(); + + printf("%fus per signature\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + + printf("testing keypair scalar addition performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_add_scalar(public_key, private_key, scalar); + } + end = clock(); + + printf("%fus per keypair\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing public key scalar addition performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_add_scalar(public_key, NULL, scalar); + } + end = clock(); + + printf("%fus per key\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + printf("testing key exchange performance: "); + start = clock(); + for (i = 0; i < 10000; ++i) { + ed25519_key_exchange(shared_secret, other_public_key, private_key); + } + end = clock(); + + printf("%fus per shared secret\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000); + + return 0; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..d35d541 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,22 @@ +project(libtorrent-examples) + +set(single_file_examples + simple_client + custom_storage + stats_counters + dump_torrent + make_torrent + connection_tester + upnp_test) + +foreach(example ${single_file_examples}) + add_executable(${example} "${example}.cpp") + target_link_libraries(${example} PRIVATE torrent-rasterbar) +endforeach(example) + +add_executable(client_test + client_test.cpp + print.cpp + torrent_view.cpp + session_view.cpp) +target_link_libraries(client_test PRIVATE torrent-rasterbar) diff --git a/examples/Jamfile b/examples/Jamfile new file mode 100644 index 0000000..3ef8f95 --- /dev/null +++ b/examples/Jamfile @@ -0,0 +1,48 @@ +import modules ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +use-project /torrent : .. ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; +} + +variant debug-mode : debug : on on full ; + +project client_test + : requirements + multi /torrent//torrent + darwin:-Wno-unused-command-line-argument +# disable warning C4275: non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + msvc:/wd4275 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + msvc:/wd4373 + : default-build + static + debug-mode + ; + +exe client_test : client_test.cpp print.cpp torrent_view.cpp session_view.cpp ; + +exe simple_client : simple_client.cpp ; +exe custom_storage : custom_storage.cpp ; +exe bt-get : bt-get.cpp ; +exe bt-get2 : bt-get2.cpp ; +exe stats_counters : stats_counters.cpp ; +exe dump_torrent : dump_torrent.cpp ; +exe make_torrent : make_torrent.cpp ; +exe connection_tester : connection_tester.cpp ; +exe upnp_test : upnp_test.cpp ; + +explicit stage_client_test ; +explicit stage_connection_tester ; +explicit stage ; + +install stage : client_test connection_tester make_torrent dump_torrent upnp_test stats_counters bt-get bt-get2 simple_client : . ; +install stage_client_test : client_test : . ; +install stage_connection_tester : connection_tester : . ; + diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..e7bbf41 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,35 @@ +example_programs = \ + client_test \ + stats_counters \ + dump_torrent \ + make_torrent \ + simple_client \ + custom_storage \ + upnp_test \ + bt_get \ + bt_get2 \ + connection_tester + +if ENABLE_EXAMPLES +bin_PROGRAMS = $(example_programs) +endif + +EXTRA_PROGRAMS = $(example_programs) +EXTRA_DIST = Jamfile CMakeLists.txt session_view.hpp torrent_view.hpp print.hpp cmake/FindLibtorrentRasterbar.cmake + +client_test_SOURCES = client_test.cpp print.cpp session_view.cpp torrent_view.cpp +stats_counters_SOURCES = stats_counters.cpp +bt_get_SOURCES = bt-get.cpp +bt_get2_SOURCES = bt-get2.cpp +dump_torrent_SOURCES = dump_torrent.cpp +make_torrent_SOURCES = make_torrent.cpp +simple_client_SOURCES = simple_client.cpp +custom_storage_SOURCES = custom_storage.cpp +connection_tester_SOURCES = connection_tester.cpp +upnp_test_SOURCES = upnp_test.cpp + +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la + +AM_CPPFLAGS = -ftemplate-depth-50 @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ diff --git a/examples/Makefile.in b/examples/Makefile.in new file mode 100644 index 0000000..e65e151 --- /dev/null +++ b/examples/Makefile.in @@ -0,0 +1,854 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@ENABLE_EXAMPLES_TRUE@bin_PROGRAMS = $(am__EXEEXT_1) +EXTRA_PROGRAMS = $(am__EXEEXT_1) +subdir = examples +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = client_test$(EXEEXT) stats_counters$(EXEEXT) \ + dump_torrent$(EXEEXT) make_torrent$(EXEEXT) \ + simple_client$(EXEEXT) custom_storage$(EXEEXT) \ + upnp_test$(EXEEXT) bt_get$(EXEEXT) bt_get2$(EXEEXT) \ + connection_tester$(EXEEXT) +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_bt_get_OBJECTS = bt-get.$(OBJEXT) +bt_get_OBJECTS = $(am_bt_get_OBJECTS) +bt_get_LDADD = $(LDADD) +bt_get_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am_bt_get2_OBJECTS = bt-get2.$(OBJEXT) +bt_get2_OBJECTS = $(am_bt_get2_OBJECTS) +bt_get2_LDADD = $(LDADD) +bt_get2_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +am_client_test_OBJECTS = client_test.$(OBJEXT) print.$(OBJEXT) \ + session_view.$(OBJEXT) torrent_view.$(OBJEXT) +client_test_OBJECTS = $(am_client_test_OBJECTS) +client_test_LDADD = $(LDADD) +client_test_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_connection_tester_OBJECTS = connection_tester.$(OBJEXT) +connection_tester_OBJECTS = $(am_connection_tester_OBJECTS) +connection_tester_LDADD = $(LDADD) +connection_tester_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_custom_storage_OBJECTS = custom_storage.$(OBJEXT) +custom_storage_OBJECTS = $(am_custom_storage_OBJECTS) +custom_storage_LDADD = $(LDADD) +custom_storage_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_dump_torrent_OBJECTS = dump_torrent.$(OBJEXT) +dump_torrent_OBJECTS = $(am_dump_torrent_OBJECTS) +dump_torrent_LDADD = $(LDADD) +dump_torrent_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_make_torrent_OBJECTS = make_torrent.$(OBJEXT) +make_torrent_OBJECTS = $(am_make_torrent_OBJECTS) +make_torrent_LDADD = $(LDADD) +make_torrent_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_simple_client_OBJECTS = simple_client.$(OBJEXT) +simple_client_OBJECTS = $(am_simple_client_OBJECTS) +simple_client_LDADD = $(LDADD) +simple_client_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_stats_counters_OBJECTS = stats_counters.$(OBJEXT) +stats_counters_OBJECTS = $(am_stats_counters_OBJECTS) +stats_counters_LDADD = $(LDADD) +stats_counters_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_upnp_test_OBJECTS = upnp_test.$(OBJEXT) +upnp_test_OBJECTS = $(am_upnp_test_OBJECTS) +upnp_test_LDADD = $(LDADD) +upnp_test_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/bt-get.Po ./$(DEPDIR)/bt-get2.Po \ + ./$(DEPDIR)/client_test.Po ./$(DEPDIR)/connection_tester.Po \ + ./$(DEPDIR)/custom_storage.Po ./$(DEPDIR)/dump_torrent.Po \ + ./$(DEPDIR)/make_torrent.Po ./$(DEPDIR)/print.Po \ + ./$(DEPDIR)/session_view.Po ./$(DEPDIR)/simple_client.Po \ + ./$(DEPDIR)/stats_counters.Po ./$(DEPDIR)/torrent_view.Po \ + ./$(DEPDIR)/upnp_test.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(bt_get_SOURCES) $(bt_get2_SOURCES) $(client_test_SOURCES) \ + $(connection_tester_SOURCES) $(custom_storage_SOURCES) \ + $(dump_torrent_SOURCES) $(make_torrent_SOURCES) \ + $(simple_client_SOURCES) $(stats_counters_SOURCES) \ + $(upnp_test_SOURCES) +DIST_SOURCES = $(bt_get_SOURCES) $(bt_get2_SOURCES) \ + $(client_test_SOURCES) $(connection_tester_SOURCES) \ + $(custom_storage_SOURCES) $(dump_torrent_SOURCES) \ + $(make_torrent_SOURCES) $(simple_client_SOURCES) \ + $(stats_counters_SOURCES) $(upnp_test_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +example_programs = \ + client_test \ + stats_counters \ + dump_torrent \ + make_torrent \ + simple_client \ + custom_storage \ + upnp_test \ + bt_get \ + bt_get2 \ + connection_tester + +EXTRA_DIST = Jamfile CMakeLists.txt session_view.hpp torrent_view.hpp print.hpp cmake/FindLibtorrentRasterbar.cmake +client_test_SOURCES = client_test.cpp print.cpp session_view.cpp torrent_view.cpp +stats_counters_SOURCES = stats_counters.cpp +bt_get_SOURCES = bt-get.cpp +bt_get2_SOURCES = bt-get2.cpp +dump_torrent_SOURCES = dump_torrent.cpp +make_torrent_SOURCES = make_torrent.cpp +simple_client_SOURCES = simple_client.cpp +custom_storage_SOURCES = custom_storage.cpp +connection_tester_SOURCES = connection_tester.cpp +upnp_test_SOURCES = upnp_test.cpp +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la +AM_CPPFLAGS = -ftemplate-depth-50 @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign examples/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign examples/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +bt_get$(EXEEXT): $(bt_get_OBJECTS) $(bt_get_DEPENDENCIES) $(EXTRA_bt_get_DEPENDENCIES) + @rm -f bt_get$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(bt_get_OBJECTS) $(bt_get_LDADD) $(LIBS) + +bt_get2$(EXEEXT): $(bt_get2_OBJECTS) $(bt_get2_DEPENDENCIES) $(EXTRA_bt_get2_DEPENDENCIES) + @rm -f bt_get2$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(bt_get2_OBJECTS) $(bt_get2_LDADD) $(LIBS) + +client_test$(EXEEXT): $(client_test_OBJECTS) $(client_test_DEPENDENCIES) $(EXTRA_client_test_DEPENDENCIES) + @rm -f client_test$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(client_test_OBJECTS) $(client_test_LDADD) $(LIBS) + +connection_tester$(EXEEXT): $(connection_tester_OBJECTS) $(connection_tester_DEPENDENCIES) $(EXTRA_connection_tester_DEPENDENCIES) + @rm -f connection_tester$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(connection_tester_OBJECTS) $(connection_tester_LDADD) $(LIBS) + +custom_storage$(EXEEXT): $(custom_storage_OBJECTS) $(custom_storage_DEPENDENCIES) $(EXTRA_custom_storage_DEPENDENCIES) + @rm -f custom_storage$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(custom_storage_OBJECTS) $(custom_storage_LDADD) $(LIBS) + +dump_torrent$(EXEEXT): $(dump_torrent_OBJECTS) $(dump_torrent_DEPENDENCIES) $(EXTRA_dump_torrent_DEPENDENCIES) + @rm -f dump_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(dump_torrent_OBJECTS) $(dump_torrent_LDADD) $(LIBS) + +make_torrent$(EXEEXT): $(make_torrent_OBJECTS) $(make_torrent_DEPENDENCIES) $(EXTRA_make_torrent_DEPENDENCIES) + @rm -f make_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(make_torrent_OBJECTS) $(make_torrent_LDADD) $(LIBS) + +simple_client$(EXEEXT): $(simple_client_OBJECTS) $(simple_client_DEPENDENCIES) $(EXTRA_simple_client_DEPENDENCIES) + @rm -f simple_client$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(simple_client_OBJECTS) $(simple_client_LDADD) $(LIBS) + +stats_counters$(EXEEXT): $(stats_counters_OBJECTS) $(stats_counters_DEPENDENCIES) $(EXTRA_stats_counters_DEPENDENCIES) + @rm -f stats_counters$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(stats_counters_OBJECTS) $(stats_counters_LDADD) $(LIBS) + +upnp_test$(EXEEXT): $(upnp_test_OBJECTS) $(upnp_test_DEPENDENCIES) $(EXTRA_upnp_test_DEPENDENCIES) + @rm -f upnp_test$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(upnp_test_OBJECTS) $(upnp_test_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bt-get.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bt-get2.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client_test.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection_tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/custom_storage.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump_torrent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/make_torrent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_view.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stats_counters.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent_view.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upnp_test.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/bt-get.Po + -rm -f ./$(DEPDIR)/bt-get2.Po + -rm -f ./$(DEPDIR)/client_test.Po + -rm -f ./$(DEPDIR)/connection_tester.Po + -rm -f ./$(DEPDIR)/custom_storage.Po + -rm -f ./$(DEPDIR)/dump_torrent.Po + -rm -f ./$(DEPDIR)/make_torrent.Po + -rm -f ./$(DEPDIR)/print.Po + -rm -f ./$(DEPDIR)/session_view.Po + -rm -f ./$(DEPDIR)/simple_client.Po + -rm -f ./$(DEPDIR)/stats_counters.Po + -rm -f ./$(DEPDIR)/torrent_view.Po + -rm -f ./$(DEPDIR)/upnp_test.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/bt-get.Po + -rm -f ./$(DEPDIR)/bt-get2.Po + -rm -f ./$(DEPDIR)/client_test.Po + -rm -f ./$(DEPDIR)/connection_tester.Po + -rm -f ./$(DEPDIR)/custom_storage.Po + -rm -f ./$(DEPDIR)/dump_torrent.Po + -rm -f ./$(DEPDIR)/make_torrent.Po + -rm -f ./$(DEPDIR)/print.Po + -rm -f ./$(DEPDIR)/session_view.Po + -rm -f ./$(DEPDIR)/simple_client.Po + -rm -f ./$(DEPDIR)/stats_counters.Po + -rm -f ./$(DEPDIR)/torrent_view.Po + -rm -f ./$(DEPDIR)/upnp_test.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/examples/bt-get.cpp b/examples/bt-get.cpp new file mode 100644 index 0000000..e6aa8eb --- /dev/null +++ b/examples/bt-get.cpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +int main(int argc, char const* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + lt::settings_pack p; + p.set_int(lt::settings_pack::alert_mask, lt::alert_category::status + | lt::alert_category::error); + lt::session ses(p); + + lt::add_torrent_params atp = lt::parse_magnet_uri(argv[1]); + atp.save_path = "."; // save in current dir + lt::torrent_handle h = ses.add_torrent(std::move(atp)); + + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + std::cout << a->message() << std::endl; + // if we receive the finished alert or an error, we're done + if (lt::alert_cast(a)) { + goto done; + } + if (lt::alert_cast(a)) { + goto done; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + done: + std::cout << "done, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} + diff --git a/examples/bt-get2.cpp b/examples/bt-get2.cpp new file mode 100644 index 0000000..cbba225 --- /dev/null +++ b/examples/bt-get2.cpp @@ -0,0 +1,160 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using clk = std::chrono::steady_clock; + +// return the name of a torrent status enum +char const* state(lt::torrent_status::state_t s) +{ + switch(s) { + case lt::torrent_status::checking_files: return "checking"; + case lt::torrent_status::downloading_metadata: return "dl metadata"; + case lt::torrent_status::downloading: return "downloading"; + case lt::torrent_status::finished: return "finished"; + case lt::torrent_status::seeding: return "seeding"; + case lt::torrent_status::allocating: return "allocating"; + case lt::torrent_status::checking_resume_data: return "checking resume"; + default: return "<>"; + } +} + +int main(int argc, char const* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return 1; + } + + lt::settings_pack pack; + pack.set_int(lt::settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::storage + | lt::alert_category::status); + + lt::session ses(pack); + clk::time_point last_save_resume = clk::now(); + + // load resume data from disk and pass it in as we add the magnet link + std::ifstream ifs(".resume_file", std::ios_base::binary); + ifs.unsetf(std::ios_base::skipws); + std::vector buf{std::istream_iterator(ifs) + , std::istream_iterator()}; + + lt::add_torrent_params atp = lt::read_resume_data(buf); + lt::add_torrent_params magnet = lt::parse_magnet_uri(argv[1]); + if (atp.info_hash != magnet.info_hash) { + atp = std::move(magnet); + } + atp.save_path = "."; // save in current dir + ses.async_add_torrent(std::move(atp)); + + // this is the handle we'll set once we get the notification of it being + // added + lt::torrent_handle h; + for (;;) { + std::vector alerts; + ses.pop_alerts(&alerts); + + for (lt::alert const* a : alerts) { + if (auto at = lt::alert_cast(a)) { + h = at->handle; + } + // if we receive the finished alert or an error, we're done + if (lt::alert_cast(a)) { + h.save_resume_data(); + goto done; + } + if (lt::alert_cast(a)) { + std::cout << a->message() << std::endl; + goto done; + } + + // when resume data is ready, save it + if (auto rd = lt::alert_cast(a)) { + std::ofstream of(".resume_file", std::ios_base::binary); + of.unsetf(std::ios_base::skipws); + auto const b = write_resume_data_buf(rd->params); + of.write(b.data(), b.size()); + } + + if (auto st = lt::alert_cast(a)) { + if (st->status.empty()) continue; + + // we only have a single torrent, so we know which one + // the status is for + lt::torrent_status const& s = st->status[0]; + std::cout << "\r" << state(s.state) << " " + << (s.download_payload_rate / 1000) << " kB/s " + << (s.total_done / 1000) << " kB (" + << (s.progress_ppm / 10000) << "%) downloaded\x1b[K"; + std::cout.flush(); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // ask the session to post a state_update_alert, to update our + // state output for the torrent + ses.post_torrent_updates(); + + // save resume data once every 30 seconds + if (clk::now() - last_save_resume > std::chrono::seconds(30)) { + h.save_resume_data(); + last_save_resume = clk::now(); + } + } + + // TODO: ideally we should save resume data here + +done: + std::cout << "\ndone, shutting down" << std::endl; +} +catch (std::exception& e) +{ + std::cerr << "Error: " << e.what() << std::endl; +} + diff --git a/examples/client_test.cpp b/examples/client_test.cpp new file mode 100644 index 0000000..9683aed --- /dev/null +++ b/examples/client_test.cpp @@ -0,0 +1,2160 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // for snprintf +#include // for atoi +#include +#include +#include +#include +#include +#include // for min()/max() + +#include "libtorrent/config.hpp" + +#ifdef TORRENT_WINDOWS +#include // for _mkdir and _getcwd +#include // for _stat +#include +#endif + +#ifdef TORRENT_UTP_LOG_ENABLE +#include "libtorrent/utp_stream.hpp" +#endif + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/disk_interface.hpp" // for open_file_state + +#include "torrent_view.hpp" +#include "session_view.hpp" +#include "print.hpp" + +using lt::total_milliseconds; +using lt::alert; +using lt::piece_index_t; +using lt::file_index_t; +using lt::torrent_handle; +using lt::add_torrent_params; +using lt::cache_status; +using lt::total_seconds; +using lt::torrent_flags_t; +using lt::seconds; +using lt::operator "" _sv; +using lt::address_v4; +using lt::address_v6; + +using std::chrono::duration_cast; +using std::stoi; + +#ifdef _WIN32 + +#include +#include + +bool sleep_and_input(int* c, lt::time_duration const sleep) +{ + for (int i = 0; i < 2; ++i) + { + if (_kbhit()) + { + *c = _getch(); + return true; + } + std::this_thread::sleep_for(sleep / 2); + } + return false; +} + +#else + +#include +#include +#include +#include +#include + +struct set_keypress +{ + enum terminal_mode { + echo = 1, + canonical = 2 + }; + + explicit set_keypress(std::uint8_t const mode = 0) + { + termios new_settings; + tcgetattr(0, &stored_settings); + new_settings = stored_settings; + // Disable canonical mode, and set buffer size to 1 byte + // and disable echo + if (mode & echo) new_settings.c_lflag |= ECHO; + else new_settings.c_lflag &= ~ECHO; + + if (mode & canonical) new_settings.c_lflag |= ICANON; + else new_settings.c_lflag &= ~ICANON; + + new_settings.c_cc[VTIME] = 0; + new_settings.c_cc[VMIN] = 1; + tcsetattr(0,TCSANOW,&new_settings); + } + ~set_keypress() { tcsetattr(0, TCSANOW, &stored_settings); } +private: + termios stored_settings; +}; + +bool sleep_and_input(int* c, lt::time_duration const sleep) +{ + lt::time_point const done = lt::clock_type::now() + sleep; + int ret = 0; +retry: + fd_set set; + FD_ZERO(&set); + FD_SET(0, &set); + int const delay = total_milliseconds(done - lt::clock_type::now()); + timeval tv = {delay / 1000, (delay % 1000) * 1000 }; + ret = select(1, &set, nullptr, nullptr, &tv); + if (ret > 0) + { + *c = getc(stdin); + return true; + } + if (errno == EINTR) + { + if (lt::clock_type::now() < done) + goto retry; + return false; + } + + if (ret < 0 && errno != 0 && errno != ETIMEDOUT) + { + std::fprintf(stderr, "select failed: %s\n", strerror(errno)); + std::this_thread::sleep_for(lt::milliseconds(500)); + } + + return false; +} + +#endif + +bool print_trackers = false; +bool print_peers = false; +bool print_connecting_peers = false; +bool print_log = false; +bool print_downloads = false; +bool print_matrix = false; +bool print_file_progress = false; +bool show_pad_files = false; +bool show_dht_status = false; +bool sequential_download = false; + +bool print_ip = true; +bool print_local_ip = false; +bool print_timers = false; +bool print_block = false; +bool print_fails = false; +bool print_send_bufs = true; +bool print_disk_stats = false; + +// the number of times we've asked to save resume data +// without having received a response (successful or failure) +int num_outstanding_resume_data = 0; + +#ifndef TORRENT_DISABLE_DHT +std::vector dht_active_requests; +std::vector dht_routing_table; +#endif + +std::string to_hex(lt::sha1_hash const& s) +{ + std::stringstream ret; + ret << s; + return ret.str(); +} + +bool load_file(std::string const& filename, std::vector& v + , int limit = 8000000) +{ + std::fstream f(filename, std::ios_base::in | std::ios_base::binary); + f.seekg(0, std::ios_base::end); + auto const s = f.tellg(); + if (s > limit || s < 0) return false; + f.seekg(0, std::ios_base::beg); + v.resize(static_cast(s)); + if (s == std::fstream::pos_type(0)) return !f.fail(); + f.read(v.data(), v.size()); + return !f.fail(); +} + +bool is_absolute_path(std::string const& f) +{ + if (f.empty()) return false; +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + int i = 0; + // match the xx:\ or xx:/ form + while (f[i] && strchr("abcdefghijklmnopqrstuvxyz", f[i])) ++i; + if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) + return true; + + // match the \\ form + if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\') + return true; + return false; +#else + if (f[0] == '/') return true; + return false; +#endif +} + +std::string path_append(std::string const& lhs, std::string const& rhs) +{ + if (lhs.empty() || lhs == ".") return rhs; + if (rhs.empty() || rhs == ".") return lhs; + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR "\\" + bool need_sep = lhs[lhs.size()-1] != '\\' && lhs[lhs.size()-1] != '/'; +#else +#define TORRENT_SEPARATOR "/" + bool need_sep = lhs[lhs.size()-1] != '/'; +#endif + return lhs + (need_sep?TORRENT_SEPARATOR:"") + rhs; +} + +std::string make_absolute_path(std::string const& p) +{ + if (is_absolute_path(p)) return p; + std::string ret; +#if defined TORRENT_WINDOWS + char* cwd = ::_getcwd(nullptr, 0); + ret = path_append(cwd, p); + std::free(cwd); +#else + char* cwd = ::getcwd(nullptr, 0); + ret = path_append(cwd, p); + std::free(cwd); +#endif + return ret; +} + +std::string print_endpoint(lt::tcp::endpoint const& ep) +{ + using namespace lt; + lt::error_code ec; + char buf[200]; + address const& addr = ep.address(); + if (addr.is_v6()) + std::snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string(ec).c_str(), ep.port()); + else + std::snprintf(buf, sizeof(buf), "%s:%d", addr.to_string(ec).c_str(), ep.port()); + return buf; +} + +using lt::torrent_status; + +FILE* g_log_file = nullptr; + +int peer_index(lt::tcp::endpoint addr, std::vector const& peers) +{ + using namespace lt; + auto i = std::find_if(peers.begin(), peers.end() + , [&addr](peer_info const& pi) { return pi.ip == addr; }); + if (i == peers.end()) return -1; + + return int(i - peers.begin()); +} + +// returns the number of lines printed +int print_peer_info(std::string& out + , std::vector const& peers, int max_lines) +{ + using namespace lt; + int pos = 0; + if (print_ip) out += "IP "; + if (print_local_ip) out += "local IP "; + out += "progress down (total | peak ) up (total | peak ) sent-req tmo bsy rcv flags dn up source "; + if (print_fails) out += "fail hshf "; + if (print_send_bufs) out += "rq sndb (recvb |alloc | wmrk ) q-bytes "; + if (print_timers) out += "inactive wait timeout q-time "; + out += " v disk ^ rtt "; + if (print_block) out += "block-progress "; + out += "client \x1b[K\n"; + ++pos; + + char str[500]; + for (std::vector::const_iterator i = peers.begin(); + i != peers.end(); ++i) + { + if ((i->flags & (peer_info::handshake | peer_info::connecting) + && !print_connecting_peers)) + { + continue; + } + + if (print_ip) + { + std::snprintf(str, sizeof(str), "%-30s ", (::print_endpoint(i->ip) + + (i->flags & peer_info::utp_socket ? " [uTP]" : "") + + (i->flags & peer_info::i2p_socket ? " [i2p]" : "") + ).c_str()); + out += str; + } + if (print_local_ip) + { + std::snprintf(str, sizeof(str), "%-30s ", ::print_endpoint(i->local_endpoint).c_str()); + out += str; + } + + char temp[10]; + std::snprintf(temp, sizeof(temp), "%d/%d" + , i->download_queue_length + , i->target_dl_queue_length); + temp[7] = 0; + + char peer_progress[10]; + std::snprintf(peer_progress, sizeof(peer_progress), "%.1f%%", i->progress_ppm / 10000.f); + std::snprintf(str, sizeof(str) + , "%s %s%s (%s|%s) %s%s (%s|%s) %s%7s %4d%4d%4d %s%s%s%s%s%s%s%s%s%s%s%s%s %s%s%s %s%s%s %s%s%s%s%s%s " + , progress_bar(i->progress_ppm / 1000, 15, col_green, '#', '-', peer_progress).c_str() + , esc("32"), add_suffix(i->down_speed, "/s").c_str() + , add_suffix(i->total_download).c_str(), add_suffix(i->download_rate_peak, "/s").c_str() + , esc("31"), add_suffix(i->up_speed, "/s").c_str(), add_suffix(i->total_upload).c_str() + , add_suffix(i->upload_rate_peak, "/s").c_str(), esc("0") + + , temp // sent requests and target number of outstanding reqs. + , i->timed_out_requests + , i->busy_requests + , i->upload_queue_length + + , color("I", (i->flags & peer_info::interesting)?col_white:col_blue).c_str() + , color("C", (i->flags & peer_info::choked)?col_white:col_blue).c_str() + , color("i", (i->flags & peer_info::remote_interested)?col_white:col_blue).c_str() + , color("c", (i->flags & peer_info::remote_choked)?col_white:col_blue).c_str() + , color("x", (i->flags & peer_info::supports_extensions)?col_white:col_blue).c_str() + , color("o", (i->flags & peer_info::local_connection)?col_white:col_blue).c_str() + , color("p", (i->flags & peer_info::on_parole)?col_white:col_blue).c_str() + , color("O", (i->flags & peer_info::optimistic_unchoke)?col_white:col_blue).c_str() + , color("S", (i->flags & peer_info::snubbed)?col_white:col_blue).c_str() + , color("U", (i->flags & peer_info::upload_only)?col_white:col_blue).c_str() + , color("e", (i->flags & peer_info::endgame_mode)?col_white:col_blue).c_str() + , color("E", (i->flags & peer_info::rc4_encrypted)?col_white:(i->flags & peer_info::plaintext_encrypted)?col_cyan:col_blue).c_str() + , color("h", (i->flags & peer_info::holepunched)?col_white:col_blue).c_str() + + , color("d", (i->read_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->read_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->read_state & peer_info::bw_network)?col_white:col_blue).c_str() + , color("d", (i->write_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->write_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->write_state & peer_info::bw_network)?col_white:col_blue).c_str() + + , color("t", (i->source & peer_info::tracker)?col_white:col_blue).c_str() + , color("p", (i->source & peer_info::pex)?col_white:col_blue).c_str() + , color("d", (i->source & peer_info::dht)?col_white:col_blue).c_str() + , color("l", (i->source & peer_info::lsd)?col_white:col_blue).c_str() + , color("r", (i->source & peer_info::resume_data)?col_white:col_blue).c_str() + , color("i", (i->source & peer_info::incoming)?col_white:col_blue).c_str()); + out += str; + + if (print_fails) + { + std::snprintf(str, sizeof(str), "%4d %4d " + , i->failcount, i->num_hashfails); + out += str; + } + if (print_send_bufs) + { + std::snprintf(str, sizeof(str), "%2d %6d %6d|%6d|%6d%5dkB " + , i->requests_in_buffer, i->used_send_buffer + , i->used_receive_buffer + , i->receive_buffer_size + , i->receive_buffer_watermark + , i->queue_bytes / 1000); + out += str; + } + if (print_timers) + { + char req_timeout[20] = "-"; + // timeout is only meaningful if there is at least one outstanding + // request to the peer + if (i->download_queue_length > 0) + std::snprintf(req_timeout, sizeof(req_timeout), "%d", i->request_timeout); + + std::snprintf(str, sizeof(str), "%8d %4d %7s %6d " + , int(total_seconds(i->last_active)) + , int(total_seconds(i->last_request)) + , req_timeout + , int(total_seconds(i->download_queue_time))); + out += str; + } + std::snprintf(str, sizeof(str), "%s|%s %5d " + , add_suffix(i->pending_disk_bytes).c_str() + , add_suffix(i->pending_disk_read_bytes).c_str() + , i->rtt); + out += str; + + if (print_block) + { + if (i->downloading_piece_index >= piece_index_t(0)) + { + char buf[50]; + std::snprintf(buf, sizeof(buf), "%d:%d" + , static_cast(i->downloading_piece_index), i->downloading_block_index); + out += progress_bar( + i->downloading_progress * 1000 / i->downloading_total, 14, col_green, '-', '#', buf); + } + else + { + out += progress_bar(0, 14); + } + } + + out += " "; + + if (i->flags & lt::peer_info::handshake) + { + out += esc("31"); + out += " waiting for handshake"; + out += esc("0"); + } + else if (i->flags & lt::peer_info::connecting) + { + out += esc("31"); + out += " connecting to peer"; + out += esc("0"); + } + else + { + out += " "; + out += i->client; + } + out += "\x1b[K\n"; + ++pos; + if (pos >= max_lines) break; + } + return pos; +} + +lt::storage_mode_t allocation_mode = lt::storage_mode_sparse; +std::string save_path("."); +int torrent_upload_limit = 0; +int torrent_download_limit = 0; +std::string monitor_dir; +int poll_interval = 5; +int max_connections_per_torrent = 50; +bool seed_mode = false; +bool stats_enabled = false; +int cache_size = -1; + +bool share_mode = false; +bool disable_storage = false; + +bool quit = false; + +void signal_handler(int) +{ + // make the main loop terminate + quit = true; +} + +// if non-empty, a peer that will be added to all torrents +std::string peer; + +void print_settings(int const start, int const num + , char const* const fmt) +{ + for (int i = start; i < start + num; ++i) + { + char const* name = lt::name_for_setting(i); + if (!name || name[0] == '\0') continue; + std::printf(fmt, name); + } +} + +void assign_setting(lt::settings_pack& settings, std::string const& key, char const* value) +{ + int const sett_name = lt::setting_by_name(key); + if (sett_name < 0) + { + std::fprintf(stderr, "unknown setting: \"%s\"\n", key.c_str()); + std::exit(1); + } + + using lt::settings_pack; + + switch (sett_name & settings_pack::type_mask) + { + case settings_pack::string_type_base: + settings.set_str(sett_name, value); + break; + case settings_pack::bool_type_base: + if (value == "1"_sv || value == "on"_sv || value == "true"_sv) + { + settings.set_bool(sett_name, true); + } + else if (value == "0"_sv || value == "off"_sv || value == "false"_sv) + { + settings.set_bool(sett_name, false); + } + else + { + std::fprintf(stderr, "invalid value for \"%s\". expected 0 or 1\n" + , key.c_str()); + std::exit(1); + } + break; + case settings_pack::int_type_base: + using namespace libtorrent::literals; + static std::map const enums = { + {"no_piece_suggestions"_sv, settings_pack::no_piece_suggestions}, + {"suggest_read_cache"_sv, settings_pack::suggest_read_cache}, + {"fixed_slots_choker"_sv, settings_pack::fixed_slots_choker}, + {"rate_based_choker"_sv, settings_pack::rate_based_choker}, + {"round_robin"_sv, settings_pack::round_robin}, + {"fastest_upload"_sv, settings_pack::fastest_upload}, + {"anti_leech"_sv, settings_pack::anti_leech}, + {"enable_os_cache"_sv, settings_pack::enable_os_cache}, + {"disable_os_cache"_sv, settings_pack::disable_os_cache}, + {"prefer_tcp"_sv, settings_pack::prefer_tcp}, + {"peer_proportional"_sv, settings_pack::peer_proportional}, + {"pe_forced"_sv, settings_pack::pe_forced}, + {"pe_enabled"_sv, settings_pack::pe_enabled}, + {"pe_disabled"_sv, settings_pack::pe_disabled}, + {"pe_plaintext"_sv, settings_pack::pe_plaintext}, + {"pe_rc4"_sv, settings_pack::pe_rc4}, + {"pe_both"_sv, settings_pack::pe_both}, + {"none"_sv, settings_pack::none}, + {"socks4"_sv, settings_pack::socks4}, + {"socks5"_sv, settings_pack::socks5}, + {"socks5_pw"_sv, settings_pack::socks5_pw}, + {"http"_sv, settings_pack::http}, + {"http_pw"_sv, settings_pack::http_pw}, + {"i2p_proxy"_sv, settings_pack::i2p_proxy}, + }; + + auto const it = enums.find(lt::string_view(value)); + if (it != enums.end()) + { + settings.set_int(sett_name, it->second); + break; + } + + static std::map const alert_categories = { + {"error"_sv, lt::alert_category::error}, + {"peer"_sv, lt::alert_category::peer}, + {"port_mapping"_sv, lt::alert_category::port_mapping}, + {"storage"_sv, lt::alert_category::storage}, + {"tracker"_sv, lt::alert_category::tracker}, + {"connect"_sv, lt::alert_category::connect}, + {"status"_sv, lt::alert_category::status}, + {"ip_block"_sv, lt::alert_category::ip_block}, + {"performance_warning"_sv, lt::alert_category::performance_warning}, + {"dht"_sv, lt::alert_category::dht}, + {"session_log"_sv, lt::alert_category::session_log}, + {"torrent_log"_sv, lt::alert_category::torrent_log}, + {"peer_log"_sv, lt::alert_category::peer_log}, + {"incoming_request"_sv, lt::alert_category::incoming_request}, + {"dht_log"_sv, lt::alert_category::dht_log}, + {"dht_operation"_sv, lt::alert_category::dht_operation}, + {"port_mapping_log"_sv, lt::alert_category::port_mapping_log}, + {"picker_log"_sv, lt::alert_category::picker_log}, + {"file_progress"_sv, lt::alert_category::file_progress}, + {"piece_progress"_sv, lt::alert_category::piece_progress}, + {"upload"_sv, lt::alert_category::upload}, + {"block_progress"_sv, lt::alert_category::block_progress}, + {"all"_sv, lt::alert_category::all}, + }; + + std::stringstream flags(value); + std::string f; + lt::alert_category_t val; + while (std::getline(flags, f, ',')) try + { + auto const it = alert_categories.find(f); + if (it == alert_categories.end()) + val |= lt::alert_category_t{unsigned(std::stoi(f))}; + else + val |= it->second; + } + catch (std::invalid_argument const&) + { + std::fprintf(stderr, "invalid value for \"%s\". expected integer or enum value\n" + , key.c_str()); + std::exit(1); + } + + settings.set_int(sett_name, val); + break; + } +} + +std::string resume_file(lt::sha1_hash const& info_hash) +{ + return path_append(save_path, path_append(".resume" + , to_hex(info_hash) + ".resume")); +} + +void set_torrent_params(lt::add_torrent_params& p) +{ + p.max_connections = max_connections_per_torrent; + p.max_uploads = -1; + p.upload_limit = torrent_upload_limit; + p.download_limit = torrent_download_limit; + + if (seed_mode) p.flags |= lt::torrent_flags::seed_mode; + if (disable_storage) p.storage = lt::disabled_storage_constructor; + if (share_mode) p.flags |= lt::torrent_flags::share_mode; + p.save_path = save_path; + p.storage_mode = allocation_mode; +} + +void add_magnet(lt::session& ses, lt::string_view uri) +{ + lt::error_code ec; + lt::add_torrent_params p = lt::parse_magnet_uri(uri.to_string(), ec); + + if (ec) + { + std::printf("invalid magnet link \"%s\": %s\n" + , uri.to_string().c_str(), ec.message().c_str()); + return; + } + + std::vector resume_data; + if (load_file(resume_file(p.info_hash), resume_data)) + { + p = lt::read_resume_data(resume_data, ec); + if (ec) std::printf(" failed to load resume data: %s\n", ec.message().c_str()); + } + + set_torrent_params(p); + + std::printf("adding magnet: %s\n", uri.to_string().c_str()); + ses.async_add_torrent(std::move(p)); +} + +// return false on failure +bool add_torrent(lt::session& ses, std::string torrent) +{ + using lt::add_torrent_params; + using lt::storage_mode_t; + + static int counter = 0; + + std::printf("[%d] %s\n", counter++, torrent.c_str()); + + lt::error_code ec; + auto ti = std::make_shared(torrent, ec); + if (ec) + { + std::printf("failed to load torrent \"%s\": %s\n" + , torrent.c_str(), ec.message().c_str()); + return false; + } + + add_torrent_params p; + + std::vector resume_data; + if (load_file(resume_file(ti->info_hash()), resume_data)) + { + p = lt::read_resume_data(resume_data, ec); + if (ec) std::printf(" failed to load resume data: %s\n", ec.message().c_str()); + } + + set_torrent_params(p); + + p.ti = ti; + p.flags &= ~lt::torrent_flags::duplicate_is_error; + ses.async_add_torrent(std::move(p)); + return true; +} + +std::vector list_dir(std::string path + , bool (*filter_fun)(lt::string_view) + , lt::error_code& ec) +{ + std::vector ret; +#ifdef TORRENT_WINDOWS + if (!path.empty() && path[path.size()-1] != '\\') path += "\\*"; + else path += "*"; + + WIN32_FIND_DATAA fd; + HANDLE handle = FindFirstFileA(path.c_str(), &fd); + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), boost::system::system_category()); + return ret; + } + + do + { + lt::string_view p = fd.cFileName; + if (filter_fun(p)) + ret.push_back(p.to_string()); + + } while (FindNextFileA(handle, &fd)); + FindClose(handle); +#else + + if (!path.empty() && path[path.size()-1] == '/') + path.resize(path.size()-1); + + DIR* handle = opendir(path.c_str()); + if (handle == nullptr) + { + ec.assign(errno, boost::system::system_category()); + return ret; + } + + struct dirent* de; + while ((de = readdir(handle))) + { + lt::string_view p(de->d_name); + if (filter_fun(p)) + ret.push_back(p.to_string()); + } + closedir(handle); +#endif + return ret; +} + +void scan_dir(std::string const& dir_path, lt::session& ses) +{ + using namespace lt; + + error_code ec; + std::vector ents = list_dir(dir_path + , [](lt::string_view p) { return p.size() > 8 && p.substr(p.size() - 8) == ".torrent"; }, ec); + if (ec) + { + std::fprintf(stderr, "failed to list directory: (%s : %d) %s\n" + , ec.category().name(), ec.value(), ec.message().c_str()); + return; + } + + for (auto const& e : ents) + { + std::string const file = path_append(dir_path, e); + + // there's a new file in the monitor directory, load it up + if (add_torrent(ses, file)) + { + if (::remove(file.c_str()) < 0) + { + std::fprintf(stderr, "failed to remove torrent file: \"%s\"\n" + , file.c_str()); + } + } + } +} + +char const* timestamp() +{ + time_t t = std::time(nullptr); + tm* timeinfo = std::localtime(&t); + static char str[200]; + std::strftime(str, 200, "%b %d %X", timeinfo); + return str; +} + +void print_alert(lt::alert const* a, std::string& str) +{ + using namespace lt; + + if (a->category() & alert_category::error) + { + str += esc("31"); + } + else if (a->category() & (alert_category::peer | alert_category::storage)) + { + str += esc("33"); + } + str += "["; + str += timestamp(); + str += "] "; + str += a->message(); + str += esc("0"); + + if (g_log_file) + std::fprintf(g_log_file, "[%s] %s\n", timestamp(), a->message().c_str()); +} + +int save_file(std::string const& filename, std::vector const& v) +{ + std::fstream f(filename, std::ios_base::trunc | std::ios_base::out | std::ios_base::binary); + f.write(v.data(), v.size()); + return !f.fail(); +} + +// returns true if the alert was handled (and should not be printed to the log) +// returns false if the alert was not handled +bool handle_alert(torrent_view& view, session_view& ses_view + , lt::session&, lt::alert* a) +{ + using namespace lt; + + if (session_stats_alert* s = alert_cast(a)) + { + ses_view.update_counters(s->counters() + , duration_cast(s->timestamp().time_since_epoch()).count()); + return !stats_enabled; + } + +#ifndef TORRENT_DISABLE_DHT + if (dht_stats_alert* p = alert_cast(a)) + { + dht_active_requests = p->active_requests; + dht_routing_table = p->routing_table; + return true; + } +#endif + +#ifdef TORRENT_USE_OPENSSL + if (torrent_need_cert_alert* p = alert_cast(a)) + { + torrent_handle h = p->handle; + std::string base_name = path_append("certificates", to_hex(h.info_hash())); + std::string cert = base_name + ".pem"; + std::string priv = base_name + "_key.pem"; + +#ifdef TORRENT_WINDOWS + struct ::_stat st; + int ret = ::_stat(cert.c_str(), &st); + if (ret < 0 || (st.st_mode & _S_IFREG) == 0) +#else + struct ::stat st; + int ret = ::stat(cert.c_str(), &st); + if (ret < 0 || (st.st_mode & S_IFREG) == 0) +#endif + { + char msg[256]; + std::snprintf(msg, sizeof(msg), "ERROR. could not load certificate %s: %s\n" + , cert.c_str(), std::strerror(errno)); + if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + return true; + } + +#ifdef TORRENT_WINDOWS + ret = ::_stat(priv.c_str(), &st); + if (ret < 0 || (st.st_mode & _S_IFREG) == 0) +#else + ret = ::stat(priv.c_str(), &st); + if (ret < 0 || (st.st_mode & S_IFREG) == 0) +#endif + { + char msg[256]; + std::snprintf(msg, sizeof(msg), "ERROR. could not load private key %s: %s\n" + , priv.c_str(), std::strerror(errno)); + if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + return true; + } + + char msg[256]; + std::snprintf(msg, sizeof(msg), "loaded certificate %s and key %s\n", cert.c_str(), priv.c_str()); + if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg); + + h.set_ssl_certificate(cert, priv, "certificates/dhparams.pem", "1234"); + h.resume(); + } +#endif + + // don't log every peer we try to connect to + if (alert_cast(a)) return true; + + if (peer_disconnected_alert* pd = alert_cast(a)) + { + // ignore failures to connect and peers not responding with a + // handshake. The peers that we successfully connect to and then + // disconnect is more interesting. + if (pd->op == operation_t::connect + || pd->error == errors::timed_out_no_handshake) + return true; + } + +#ifdef _MSC_VER +// it seems msvc makes the definitions of 'p' escape the if-statement here +#pragma warning(push) +#pragma warning(disable: 4456) +#endif + + if (metadata_received_alert* p = alert_cast(a)) + { + torrent_handle h = p->handle; + h.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + else if (add_torrent_alert* p = alert_cast(a)) + { + if (p->error) + { + std::fprintf(stderr, "failed to add torrent: %s %s\n" + , p->params.ti ? p->params.ti->name().c_str() : p->params.name.c_str() + , p->error.message().c_str()); + } + else + { + torrent_handle h = p->handle; + + h.save_resume_data(torrent_handle::save_info_dict | torrent_handle::only_if_modified); + ++num_outstanding_resume_data; + + // if we have a peer specified, connect to it + if (!peer.empty()) + { + char* port = (char*) strrchr((char*)peer.c_str(), ':'); + if (port != nullptr) + { + *port++ = 0; + char const* ip = peer.c_str(); + int peer_port = atoi(port); + error_code ec; + if (peer_port > 0) + h.connect_peer(tcp::endpoint(address::from_string(ip, ec), std::uint16_t(peer_port))); + } + } + } + } + else if (torrent_finished_alert* p = alert_cast(a)) + { + p->handle.set_max_connections(max_connections_per_torrent / 2); + + // write resume data for the finished torrent + // the alert handler for save_resume_data_alert + // will save it to disk + torrent_handle h = p->handle; + h.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + else if (save_resume_data_alert* p = alert_cast(a)) + { + --num_outstanding_resume_data; + auto const buf = write_resume_data_buf(p->params); + save_file(resume_file(p->params.info_hash), buf); + } + else if (save_resume_data_failed_alert* p = alert_cast(a)) + { + --num_outstanding_resume_data; + // don't print the error if it was just that we didn't need to save resume + // data. Returning true means "handled" and not printed to the log + return p->error == lt::errors::resume_data_not_modified; + } + else if (torrent_paused_alert* p = alert_cast(a)) + { + // write resume data for the finished torrent + // the alert handler for save_resume_data_alert + // will save it to disk + torrent_handle h = p->handle; + h.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + else if (state_update_alert* p = alert_cast(a)) + { + view.update_torrents(std::move(p->status)); + return true; + } + else if (torrent_removed_alert* p = alert_cast(a)) + { + view.remove_torrent(std::move(p->handle)); + } + return false; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} + +void pop_alerts(torrent_view& view, session_view& ses_view + , lt::session& ses, std::deque& events) +{ + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + if (::handle_alert(view, ses_view, ses, a)) continue; + + // if we didn't handle the alert, print it to the log + std::string event_string; + print_alert(a, event_string); + events.push_back(event_string); + if (events.size() >= 20) events.pop_front(); + } +} + +void print_piece(lt::partial_piece_info const* pp + , lt::cached_piece_info const* cs + , std::vector const& peers + , std::string& out) +{ + using namespace lt; + + char str[1024]; + assert(pp == nullptr || cs == nullptr || cs->piece == pp->piece_index); + int piece = static_cast(pp ? pp->piece_index : cs->piece); + int num_blocks = pp ? pp->blocks_in_piece : int(cs->blocks.size()); + + std::snprintf(str, sizeof(str), "%5d:[", piece); + out += str; + string_view last_color; + for (int j = 0; j < num_blocks; ++j) + { + int const index = pp ? peer_index(pp->blocks[j].peer(), peers) % 36 : -1; + char const* chr = " "; + bool const snubbed = index >= 0 ? bool(peers[index].flags & lt::peer_info::snubbed) : false; + + char const* color = ""; + + if (pp == nullptr) + { + color = cs->blocks[j] ? esc("34;7") : esc("0"); + chr = " "; + } + else + { + if (cs && cs->blocks[j] && pp->blocks[j].state != block_info::finished) + color = esc("36;7"); + else if (pp->blocks[j].bytes_progress > 0 + && pp->blocks[j].state == block_info::requested) + { + if (pp->blocks[j].num_peers > 1) color = esc("0;1"); + else color = snubbed ? esc("0;35") : esc("0;33"); + +#ifndef TORRENT_WINDOWS + static char const* const progress[] = { + "\u2581", "\u2582", "\u2583", "\u2584", + "\u2585", "\u2586", "\u2587", "\u2588" + }; + chr = progress[pp->blocks[j].bytes_progress * 8 / pp->blocks[j].block_size]; +#else + static char const* const progress[] = { "\xb0", "\xb1", "\xb2" }; + chr = progress[pp->blocks[j].bytes_progress * 3 / pp->blocks[j].block_size]; +#endif + } + else if (pp->blocks[j].state == block_info::finished) color = esc("32;7"); + else if (pp->blocks[j].state == block_info::writing) color = esc("36;7"); + else if (pp->blocks[j].state == block_info::requested) + { + color = snubbed ? esc("0;35") : esc("0"); + chr = "="; + } + else { color = esc("0"); chr = " "; } + } + if (last_color != color) + { + out += color; + last_color = color; + } + out += chr; + } + out += esc("0"); + out += "]"; +} + +bool is_resume_file(std::string const& s) +{ + static std::string const hex_digit = "0123456789abcdef"; + if (s.size() != 40 + 7) return false; + if (s.substr(40) != ".resume") return false; + for (char const c : s.substr(0, 40)) + { + if (hex_digit.find(c) == std::string::npos) return false; + } + return true; +} + +int main(int argc, char* argv[]) +{ +#ifndef _WIN32 + // sets the terminal to single-character mode + // and resets when destructed + set_keypress s; +#endif + + if (argc == 1) + { + std::fprintf(stderr, R"(usage: client_test [OPTIONS] [TORRENT|MAGNETURL] +OPTIONS: + +CLIENT OPTIONS + -f logs all events to the given file + -s sets the save path for downloads. This also determines + the resume data save directory. Torrents from the resume + directory are automatically added to the session on + startup. + -m sets the .torrent monitor directory. torrent files + dropped in the directory are added the session and the + resume data directory, and removed from the monitor dir. + -t sets the scan interval of the monitor dir + -F sets the UI refresh rate. This is the number of + milliseconds between screen refreshes. + -k enable high performance settings. This overwrites any other + previous command line options, so be sure to specify this first + -G Add torrents in seed-mode (i.e. assume all pieces + are present and check hashes on-demand) + -e exit client after the specified number of iterations + through the main loop + -O print session stats counters to the log)" +#ifdef TORRENT_UTP_LOG_ENABLE +R"( + -q Enable uTP transport-level verbose logging +)" +#endif +R"( +LIBTORRENT SETTINGS + --= + set the libtorrent setting to + --list-settings print all libtorrent settings and exit + +BITTORRENT OPTIONS + -T sets the max number of connections per torrent + -U sets per-torrent upload rate + -D sets per-torrent download rate + -Q enables share mode. Share mode attempts to maximize + share ratio rather than downloading + -r connect to specified peer + +NETWORK OPTIONS + -x loads an emule IP-filter file + -Y Rate limit local peers)" +#if TORRENT_USE_I2P +R"( -i the hostname to an I2P SAM bridge to use +)" +#endif +R"( +DISK OPTIONS + -a sets the allocation mode. [sparse|allocate] + -0 disable disk I/O, read garbage and don't flush to disk + +TORRENT is a path to a .torrent file +MAGNETURL is a magnet link + +alert mask flags: + error peer port_mapping storage tracker connect status ip_block + performance_warning dht session_log torrent_log peer_log incoming_request + dht_log dht_operation port_mapping_log picker_log file_progress piece_progress + upload block_progress all + +examples: + --alert_mask=error,port_mapping,tracker,connect,session_log + --alert_mask=error,session_log,torrent_log,peer_log + --alert_mask=error,dht,dht_log,dht_operation + --alert_mask=all +)") ; + return 0; + } + + using lt::settings_pack; + using lt::session_handle; + + torrent_view view; + session_view ses_view; + + lt::session_params params; + +#ifndef TORRENT_DISABLE_DHT + params.dht_settings.privacy_lookups = true; + + std::vector in; + if (load_file(".ses_state", in)) + { + lt::error_code ec; + lt::bdecode_node e = lt::bdecode(in, ec); + if (!ec) params = read_session_params(e, session_handle::save_dht_state); + } +#endif + + auto& settings = params.settings; + settings.set_int(settings_pack::cache_size, cache_size); + settings.set_int(settings_pack::choking_algorithm, settings_pack::rate_based_choker); + + settings.set_str(settings_pack::user_agent, "client_test/" LIBTORRENT_VERSION); + settings.set_int(settings_pack::alert_mask + , lt::alert_category::error + | lt::alert_category::peer + | lt::alert_category::port_mapping + | lt::alert_category::storage + | lt::alert_category::tracker + | lt::alert_category::connect + | lt::alert_category::status + | lt::alert_category::ip_block + | lt::alert_category::performance_warning + | lt::alert_category::dht + | lt::alert_category::incoming_request + | lt::alert_category::dht_operation + | lt::alert_category::port_mapping_log + | lt::alert_category::file_progress); + + lt::time_duration refresh_delay = lt::milliseconds(500); + bool rate_limit_locals = false; + + std::deque events; + int loop_limit = -1; + + lt::time_point next_dir_scan = lt::clock_type::now(); + + // load the torrents given on the commandline + std::vector torrents; + lt::ip_filter loaded_ip_filter; + + for (int i = 1; i < argc; ++i) + { + if (argv[i][0] != '-') + { + torrents.push_back(argv[i]); + continue; + } + + if (argv[i] == "--list-settings"_sv) + { + // print all libtorrent settings and exit + print_settings(settings_pack::string_type_base + , settings_pack::num_string_settings + , "%s=\n"); + print_settings(settings_pack::bool_type_base + , settings_pack::num_bool_settings + , "%s=\n"); + print_settings(settings_pack::int_type_base + , settings_pack::num_int_settings + , "%s=\n"); + return 0; + } + + // maybe this is an assignment of a libtorrent setting + if (argv[i][1] == '-' && strchr(argv[i], '=') != nullptr) + { + char const* equal = strchr(argv[i], '='); + char const* start = argv[i]+2; + // +2 is to skip the -- + std::string const key(start, equal - start); + char const* value = equal + 1; + + assign_setting(settings, key, value); + continue; + } + + // if there's a flag but no argument following, ignore it + if (argc == i) continue; + char const* arg = argv[i+1]; + if (arg == nullptr) arg = ""; + + switch (argv[i][1]) + { + case 'f': g_log_file = std::fopen(arg, "w+"); break; + case 'k': settings = lt::high_performance_seed(); --i; break; + case 'G': seed_mode = true; --i; break; + case 's': save_path = make_absolute_path(arg); break; + case 'O': stats_enabled = true; --i; break; +#ifdef TORRENT_UTP_LOG_ENABLE + case 'q': + lt::set_utp_stream_logging(true); + break; +#endif + case 'U': torrent_upload_limit = atoi(arg) * 1000; break; + case 'D': torrent_download_limit = atoi(arg) * 1000; break; + case 'm': monitor_dir = make_absolute_path(arg); break; + case 'Q': share_mode = true; --i; break; + case 't': poll_interval = atoi(arg); break; + case 'F': refresh_delay = lt::milliseconds(atoi(arg)); break; + case 'a': allocation_mode = (arg == std::string("sparse")) + ? lt::storage_mode_sparse + : lt::storage_mode_allocate; + break; + case 'x': + { + std::fstream filter(arg, std::ios_base::in); + if (!filter.fail()) + { + std::regex regex(R"(^\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\s*-\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\s+([0-9]+)$)"); + + std::string line; + while (std::getline(filter, line)) + { + std::smatch m; + if (std::regex_match(line, m, regex)) + { + address_v4 start((stoi(m[1]) << 24) | (stoi(m[2]) << 16) | (stoi(m[3]) << 8) | stoi(m[4])); + address_v4 last((stoi(m[5]) << 24) | (stoi(m[6]) << 16) | (stoi(m[7]) << 8) | stoi(m[8])); + loaded_ip_filter.add_rule(start, last + , stoi(m[9]) <= 127 ? lt::ip_filter::blocked : 0); + } + } + } + } + break; + case 'T': max_connections_per_torrent = atoi(arg); break; + case 'r': peer = arg; break; + case 'Y': + { + --i; + rate_limit_locals = true; + break; + } + case '0': disable_storage = true; --i; break; + case 'e': + { + loop_limit = atoi(arg); + break; + } + } + ++i; // skip the argument + } + + // create directory for resume files +#ifdef TORRENT_WINDOWS + int mkdir_ret = _mkdir(path_append(save_path, ".resume").c_str()); +#else + int mkdir_ret = mkdir(path_append(save_path, ".resume").c_str(), 0777); +#endif + if (mkdir_ret < 0 && errno != EEXIST) + { + std::fprintf(stderr, "failed to create resume file directory: (%d) %s\n" + , errno, strerror(errno)); + } + + lt::session ses(std::move(params)); + + if (rate_limit_locals) + { + lt::ip_filter pcf; + pcf.add_rule(address_v4::from_string("0.0.0.0") + , address_v4::from_string("255.255.255.255") + , 1 << static_cast(lt::session::global_peer_class_id)); + pcf.add_rule(address_v6::from_string("::") + , address_v6::from_string("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 1); + ses.set_peer_class_filter(pcf); + } + + ses.set_ip_filter(loaded_ip_filter); + + for (auto const& i : torrents) + { + if (i.substr(0, 7) == "magnet:") add_magnet(ses, i); + else add_torrent(ses, i.to_string()); + } + + std::thread resume_data_loader([&ses] + { + // load resume files + lt::error_code ec; + std::string const resume_dir = path_append(save_path, ".resume"); + std::vector ents = list_dir(resume_dir + , [](lt::string_view p) { return p.size() > 7 && p.substr(p.size() - 7) == ".resume"; }, ec); + if (ec) + { + std::fprintf(stderr, "failed to list resume directory \"%s\": (%s : %d) %s\n" + , resume_dir.c_str(), ec.category().name(), ec.value(), ec.message().c_str()); + } + else + { + for (auto const& e : ents) + { + // only load resume files of the form .resume + if (!is_resume_file(e)) continue; + std::string const file = path_append(resume_dir, e); + + std::vector resume_data; + if (!load_file(file, resume_data)) + { + std::printf(" failed to load resume file \"%s\": %s\n" + , file.c_str(), ec.message().c_str()); + continue; + } + add_torrent_params p = lt::read_resume_data(resume_data, ec); + if (ec) + { + std::printf(" failed to parse resume data \"%s\": %s\n" + , file.c_str(), ec.message().c_str()); + continue; + } + + ses.async_add_torrent(std::move(p)); + } + } + }); + + // main loop + std::vector peers; + std::vector queue; + +#ifndef _WIN32 + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); +#endif + + while (!quit && loop_limit != 0) + { + if (loop_limit > 0) --loop_limit; + + ses.post_torrent_updates(); + ses.post_session_stats(); + ses.post_dht_stats(); + + int terminal_width = 80; + int terminal_height = 50; + std::tie(terminal_width, terminal_height) = terminal_size(); + + // the ratio of torrent-list and details below depend on the number of + // torrents we have in the session + int const height = std::min(terminal_height / 2 + , std::max(5, view.num_visible_torrents() + 2)); + view.set_size(terminal_width, height); + ses_view.set_pos(height); + ses_view.set_width(terminal_width); + + int c = 0; + if (sleep_and_input(&c, refresh_delay)) + { + +#ifdef _WIN32 + constexpr int escape_seq = 224; + constexpr int left_arrow = 75; + constexpr int right_arrow = 77; + constexpr int up_arrow = 72; + constexpr int down_arrow = 80; +#else + constexpr int escape_seq = 27; + constexpr int left_arrow = 68; + constexpr int right_arrow = 67; + constexpr int up_arrow = 65; + constexpr int down_arrow = 66; +#endif + + torrent_handle h = view.get_active_handle(); + + if (c == EOF) + { + quit = true; + break; + } + do + { + if (c == escape_seq) + { + // escape code, read another character +#ifdef _WIN32 + int c2 = _getch(); +#else + int c2 = getc(stdin); + if (c2 == EOF) + { + quit = true; + break; + } + if (c2 != '[') continue; + c2 = getc(stdin); +#endif + if (c2 == EOF) + { + quit = true; + break; + } + if (c2 == left_arrow) + { + int const filter = view.filter(); + if (filter > 0) + { + view.set_filter(filter - 1); + h = view.get_active_handle(); + } + } + else if (c2 == right_arrow) + { + int const filter = view.filter(); + if (filter < torrent_view::torrents_max - 1) + { + view.set_filter(filter + 1); + h = view.get_active_handle(); + } + } + else if (c2 == up_arrow) + { + view.arrow_up(); + h = view.get_active_handle(); + } + else if (c2 == down_arrow) + { + view.arrow_down(); + h = view.get_active_handle(); + } + } + + if (c == ' ') + { + if (ses.is_paused()) ses.resume(); + else ses.pause(); + } + + if (c == '[' && h.is_valid()) + { + h.queue_position_up(); + } + + if (c == ']' && h.is_valid()) + { + h.queue_position_down(); + } + + // add magnet link + if (c == 'm') + { + char url[4096]; + url[0] = '\0'; + puts("Enter magnet link:\n"); + +#ifndef _WIN32 + // enable terminal echo temporarily + set_keypress s(set_keypress::echo | set_keypress::canonical); +#endif + if (std::scanf("%4095s", url) == 1) add_magnet(ses, url); + else std::printf("failed to read magnet link\n"); + } + + if (c == 'q') + { + quit = true; + break; + } + + if (c == 'W' && h.is_valid()) + { + std::set seeds = h.url_seeds(); + for (std::set::iterator i = seeds.begin() + , end(seeds.end()); i != end; ++i) + { + h.remove_url_seed(*i); + } + + seeds = h.http_seeds(); + for (std::set::iterator i = seeds.begin() + , end(seeds.end()); i != end; ++i) + { + h.remove_http_seed(*i); + } + } + + if (c == 'D' && h.is_valid()) + { + torrent_status const& st = view.get_active_torrent(); + std::printf("\n\nARE YOU SURE YOU WANT TO DELETE THE FILES FOR '%s'. THIS OPERATION CANNOT BE UNDONE. (y/N)" + , st.name.c_str()); +#ifndef _WIN32 + // enable terminal echo temporarily + set_keypress s(set_keypress::echo | set_keypress::canonical); +#endif + char response = 'n'; + int scan_ret = std::scanf("%c", &response); + if (scan_ret == 1 && response == 'y') + { + // also delete the resume file + std::string const rpath = resume_file(st.info_hash); + if (::remove(rpath.c_str()) < 0) + std::printf("failed to delete resume file (\"%s\")\n" + , rpath.c_str()); + + if (st.handle.is_valid()) + { + ses.remove_torrent(st.handle, lt::session::delete_files); + } + else + { + std::printf("failed to delete torrent, invalid handle: %s\n" + , st.name.c_str()); + } + } + } + + if (c == 'j' && h.is_valid()) + { + h.force_recheck(); + } + + if (c == 'r' && h.is_valid()) + { + h.force_reannounce(); + } + + if (c == 's' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + h.set_flags(~ts.flags, lt::torrent_flags::sequential_download); + } + + if (c == 'R') + { + // save resume data for all torrents + std::vector const torr = ses.get_torrent_status( + [](torrent_status const& st) + { return st.need_save_resume; }, {}); + for (torrent_status const& st : torr) + { + st.handle.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + } + } + + if (c == 'o' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + int num_pieces = ts.num_pieces; + if (num_pieces > 300) num_pieces = 300; + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + h.set_piece_deadline(i, (static_cast(i)+5) * 1000 + , torrent_handle::alert_when_available); + } + } + + if (c == 'v' && h.is_valid()) + { + h.scrape_tracker(); + } + + if (c == 'p' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + if ((ts.flags & (lt::torrent_flags::auto_managed + | lt::torrent_flags::paused)) == lt::torrent_flags::paused) + { + h.set_flags(lt::torrent_flags::auto_managed); + } + else + { + h.unset_flags(lt::torrent_flags::auto_managed); + h.pause(torrent_handle::graceful_pause); + } + } + + // toggle force-start + if (c == 'k' && h.is_valid()) + { + torrent_status const& ts = view.get_active_torrent(); + h.set_flags( + ~(ts.flags & lt::torrent_flags::auto_managed), + lt::torrent_flags::auto_managed); + if ((ts.flags & lt::torrent_flags::auto_managed) + && (ts.flags & lt::torrent_flags::paused)) + { + h.resume(); + } + } + + if (c == 'c' && h.is_valid()) + { + h.clear_error(); + } + + // toggle displays + if (c == 't') print_trackers = !print_trackers; + if (c == 'i') print_peers = !print_peers; + if (c == 'l') print_log = !print_log; + if (c == 'd') print_downloads = !print_downloads; + if (c == 'y') print_matrix = !print_matrix; + if (c == 'f') print_file_progress = !print_file_progress; + if (c == 'P') show_pad_files = !show_pad_files; + if (c == 'g') show_dht_status = !show_dht_status; + if (c == 'x') print_disk_stats = !print_disk_stats; + // toggle columns + if (c == '1') print_ip = !print_ip; + if (c == '2') print_connecting_peers = !print_connecting_peers; + if (c == '3') print_timers = !print_timers; + if (c == '4') print_block = !print_block; + if (c == '6') print_fails = !print_fails; + if (c == '7') print_send_bufs = !print_send_bufs; + if (c == '8') print_local_ip = !print_local_ip; + if (c == 'C') + { + cache_size = (cache_size == 0) ? -1 : 0; + settings_pack p; + p.set_int(settings_pack::cache_size, cache_size); + ses.apply_settings(std::move(p)); + } + if (c == 'h') + { + clear_screen(); + set_cursor_pos(0,0); + print( +R"(HELP SCREEN (press any key to dismiss) + +CLIENT OPTIONS + +[q] quit client [m] add magnet link + +TORRENT ACTIONS +[p] pause/resume selected torrent [C] toggle disk cache +[s] toggle sequential download [j] force recheck +[space] toggle session pause [c] clear error +[v] scrape [D] delete torrent and data +[r] force reannounce [R] save resume data for all torrents +[o] set piece deadlines (sequential dl) [P] toggle auto-managed +[k] toggle force-started [W] remove all web seeds + [ move queue position closer to beginning + ] move queue position closer to end + +DISPLAY OPTIONS +left/right arrow keys: select torrent filter +up/down arrow keys: select torrent +[i] toggle show peers [d] toggle show downloading pieces +[P] show pad files (in file list) [f] toggle show files +[g] show DHT [x] toggle disk cache stats +[t] show trackers [l] toggle show log +[y] toggle show piece matrix + +COLUMN OPTIONS +[1] toggle IP column [2] toggle show peer connection attempts +[3] toggle timers column [4] toggle block progress column + [6] toggle failures column +[7] toggle send buffers column [8] toggle local IP column +)"); + int tmp; + while (sleep_and_input(&tmp, lt::milliseconds(500)) == false); + } + + } while (sleep_and_input(&c, lt::milliseconds(0))); + if (c == 'q') + { + quit = true; + break; + } + } + + pop_alerts(view, ses_view, ses, events); + + std::string out; + + char str[500]; + + int pos = view.height() + ses_view.height(); + set_cursor_pos(0, pos); + + int cache_flags = print_downloads ? 0 : lt::session::disk_cache_no_pieces; + torrent_handle h = view.get_active_handle(); + + cache_status cs; + ses.get_cache_info(&cs, h, cache_flags); + +#ifndef TORRENT_DISABLE_DHT + if (show_dht_status) + { + // TODO: 3 expose these counters as performance counters +/* + std::snprintf(str, sizeof(str), "DHT nodes: %d DHT cached nodes: %d " + "total DHT size: %" PRId64 " total observers: %d\n" + , sess_stat.dht_nodes, sess_stat.dht_node_cache, sess_stat.dht_global_nodes + , sess_stat.dht_total_allocations); + out += str; +*/ + + int bucket = 0; + for (lt::dht_routing_bucket const& n : dht_routing_table) + { + char const* progress_bar = + "################################" + "################################" + "################################" + "################################"; + char const* short_progress_bar = "--------"; + std::snprintf(str, sizeof(str) + , "%3d [%3d, %d] %s%s\x1b[K\n" + , bucket, n.num_nodes, n.num_replacements + , progress_bar + (128 - n.num_nodes) + , short_progress_bar + (8 - std::min(8, n.num_replacements))); + out += str; + pos += 1; + ++bucket; + } + + for (lt::dht_lookup const& l : dht_active_requests) + { + std::snprintf(str, sizeof(str) + , " %10s target: %s " + "[limit: %2d] " + "in-flight: %-2d " + "left: %-3d " + "1st-timeout: %-2d " + "timeouts: %-2d " + "responses: %-2d " + "last_sent: %-2d " + "\x1b[K\n" + , l.type + , to_hex(l.target).c_str() + , l.branch_factor + , l.outstanding_requests + , l.nodes_left + , l.first_timeout + , l.timeouts + , l.responses + , l.last_sent); + out += str; + pos += 1; + } + } +#endif + lt::time_point const now = lt::clock_type::now(); + if (h.is_valid()) + { + torrent_status const& s = view.get_active_torrent(); + + print((piece_bar(s.pieces, terminal_width - 2) + "\x1b[K\n").c_str()); + pos += 1; + + if ((print_downloads && s.state != torrent_status::seeding) + || print_peers) + h.get_peer_info(peers); + + if (print_peers && !peers.empty()) + { + using lt::peer_info; + // sort connecting towards the bottom of the list, and by peer_id + // otherwise, to keep the list as stable as possible + std::sort(peers.begin(), peers.end() + , [](peer_info const& lhs, peer_info const& rhs) + { + { + bool const l = bool(lhs.flags & peer_info::connecting); + bool const r = bool(rhs.flags & peer_info::connecting); + if (l != r) return l < r; + } + + { + bool const l = bool(lhs.flags & peer_info::handshake); + bool const r = bool(rhs.flags & peer_info::handshake); + if (l != r) return l < r; + } + + return lhs.pid < rhs.pid; + }); + pos += print_peer_info(out, peers, terminal_height - pos - 2); + } + + if (print_trackers) + { + snprintf(str, sizeof(str), "next_announce: %4" PRId64 " | current tracker: %s\x1b[K\n" + , std::int64_t(duration_cast(s.next_announce).count()) + , s.current_tracker.c_str()); + out += str; + pos += 1; + std::vector tr = h.trackers(); + for (lt::announce_entry const& ae : h.trackers()) + { + std::snprintf(str, sizeof(str), "%2d %-55s %s\x1b[K\n" + , ae.tier, ae.url.c_str(), ae.verified?"OK ":"- "); + out += str; + pos += 1; + int idx = 0; + for (auto const& ep : ae.endpoints) + { + ++idx; + if (!ep.enabled) continue; + if (pos + 1 >= terminal_height) break; + + std::snprintf(str, sizeof(str), " [%2d] fails: %-3d (%-3d) %s %5d \"%s\" %s\x1b[K\n" + , idx + , ep.fails, ae.fail_limit + , to_string(int(total_seconds(ep.next_announce - now)), 8).c_str() + , ep.min_announce > now ? int(total_seconds(ep.min_announce - now)) : 0 + , ep.last_error ? ep.last_error.message().c_str() : "" + , ep.message.c_str()); + out += str; + pos += 1; + // we only need to show this error once, not for every + // endpoint + if (ep.last_error == boost::asio::error::host_not_found) break; + } + + if (pos + 1 >= terminal_height) break; + } + } + + if (print_matrix) + { + int height_out = 0; + print(piece_matrix(s.pieces, terminal_width, &height_out).c_str()); + pos += height_out; + } + + if (print_downloads) + { + h.get_download_queue(queue); + + std::sort(queue.begin(), queue.end() + , [] (lt::partial_piece_info const& lhs, lt::partial_piece_info const& rhs) + { return lhs.piece_index < rhs.piece_index; }); + + std::sort(cs.pieces.begin(), cs.pieces.end() + , [](lt::cached_piece_info const& lhs, lt::cached_piece_info const& rhs) + { return lhs.piece < rhs.piece; }); + + int p = 0; // this is horizontal position + for (lt::cached_piece_info const& i : cs.pieces) + { + if (pos + 3 >= terminal_height) break; + + lt::partial_piece_info* pp = nullptr; + lt::partial_piece_info tmp; + tmp.piece_index = i.piece; + std::vector::iterator ppi + = std::lower_bound(queue.begin(), queue.end(), tmp + , [](lt::partial_piece_info const& lhs, lt::partial_piece_info const& rhs) + { return lhs.piece_index < rhs.piece_index; }); + + if (ppi != queue.end() && ppi->piece_index == i.piece) pp = &*ppi; + + print_piece(pp, &i, peers, out); + + int num_blocks = pp ? pp->blocks_in_piece : int(i.blocks.size()); + p += num_blocks + 8; + bool continuous_mode = 8 + num_blocks > terminal_width; + if (continuous_mode) + { + while (p > terminal_width) + { + p -= terminal_width; + ++pos; + } + } + else if (p + num_blocks + 8 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + + if (pp) queue.erase(ppi); + } + + for (lt::partial_piece_info const& i : queue) + { + if (pos + 3 >= terminal_height) break; + + print_piece(&i, nullptr, peers, out); + + int num_blocks = i.blocks_in_piece; + p += num_blocks + 8; + bool continuous_mode = 8 + num_blocks > terminal_width; + if (continuous_mode) + { + while (p > terminal_width) + { + p -= terminal_width; + ++pos; + } + } + else if (p + num_blocks + 8 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + } + if (p != 0) + { + out += "\x1b[K\n"; + pos += 1; + } + + std::snprintf(str, sizeof(str), "%s %s read cache | %s %s downloading | %s %s cached | %s %s flushed | %s %s snubbed | = requested\x1b[K\n" + , esc("34;7"), esc("0") // read cache + , esc("33;7"), esc("0") // downloading + , esc("36;7"), esc("0") // cached + , esc("32;7"), esc("0") // flushed + , esc("35;7"), esc("0") // snubbed + ); + out += str; + pos += 1; + } + + if (print_file_progress && s.has_metadata) + { + std::vector file_progress; + h.file_progress(file_progress); + std::vector file_status = h.file_status(); + std::vector file_prio = h.get_file_priorities(); + auto f = file_status.begin(); + std::shared_ptr ti = h.torrent_file(); + + int p = 0; // this is horizontal position + for (file_index_t i(0); i < file_index_t(ti->num_files()); ++i) + { + int const idx = static_cast(i); + if (pos + 1 >= terminal_height) break; + + bool const pad_file = ti->files().pad_file_at(i); + if (pad_file && !show_pad_files) continue; + + int const progress = ti->files().file_size(i) > 0 + ? int(file_progress[idx] * 1000 / ti->files().file_size(i)) : 1000; + assert(file_progress[idx] <= ti->files().file_size(i)); + + bool const complete = file_progress[idx] == ti->files().file_size(i); + + std::string title = ti->files().file_name(i).to_string(); + if (!complete) + { + std::snprintf(str, sizeof(str), " (%.1f%%)", progress / 10.f); + title += str; + } + + if (f != file_status.end() && f->file_index == i) + { + title += " [ "; + if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::read_write) title += "read/write "; + else if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::read_only) title += "read "; + else if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::write_only) title += "write "; + if (f->open_mode & lt::file_open_mode::random_access) title += "random_access "; + if (f->open_mode & lt::file_open_mode::sparse) title += "sparse "; + title += "]"; + ++f; + } + + const int file_progress_width = pad_file ? 10 : 65; + + // do we need to line-break? + if (p + file_progress_width + 13 > terminal_width) + { + out += "\x1b[K\n"; + pos += 1; + p = 0; + } + + std::snprintf(str, sizeof(str), "%s %7s p: %d ", + progress_bar(progress, file_progress_width + , pad_file ? col_blue + : complete ? col_green : col_yellow + , '-', '#', title.c_str()).c_str() + , add_suffix(file_progress[idx]).c_str() + , static_cast(file_prio[idx])); + + p += file_progress_width + 13; + out += str; + } + + if (p != 0) + { + out += "\x1b[K\n"; + pos += 1; + } + } + } + + if (print_log) + { + for (auto const& e : events) + { + if (pos + 1 >= terminal_height) break; + out += e; + out += "\x1b[K\n"; + pos += 1; + } + } + + // clear rest of screen + out += "\x1b[J"; + print(out.c_str()); + + std::fflush(stdout); + + if (!monitor_dir.empty() && next_dir_scan < now) + { + scan_dir(monitor_dir, ses); + next_dir_scan = now + seconds(poll_interval); + } + } + + resume_data_loader.join(); + + ses.pause(); + std::printf("saving resume data\n"); + + // get all the torrent handles that we need to save resume data for + std::vector const temp = ses.get_torrent_status( + [](torrent_status const& st) + { + if (!st.handle.is_valid() || !st.has_metadata || !st.need_save_resume) + return false; + return true; + }, {}); + + int idx = 0; + for (auto const& st : temp) + { + // save_resume_data will generate an alert when it's done + st.handle.save_resume_data(torrent_handle::save_info_dict); + ++num_outstanding_resume_data; + ++idx; + if ((idx % 32) == 0) + { + std::printf("\r%d ", num_outstanding_resume_data); + pop_alerts(view, ses_view, ses, events); + } + } + std::printf("\nwaiting for resume data [%d]\n", num_outstanding_resume_data); + + while (num_outstanding_resume_data > 0) + { + alert const* a = ses.wait_for_alert(seconds(10)); + if (a == nullptr) continue; + pop_alerts(view, ses_view, ses, events); + } + + if (g_log_file) std::fclose(g_log_file); + + // we're just saving the DHT state +#ifndef TORRENT_DISABLE_DHT + std::printf("\nsaving session state\n"); + { + lt::entry session_state; + ses.save_state(session_state, lt::session::save_dht_state); + + std::vector out; + bencode(std::back_inserter(out), session_state); + save_file(".ses_state", out); + } +#endif + + std::printf("closing session\n"); + + return 0; +} + diff --git a/examples/cmake/FindLibtorrentRasterbar.cmake b/examples/cmake/FindLibtorrentRasterbar.cmake new file mode 100644 index 0000000..be13d54 --- /dev/null +++ b/examples/cmake/FindLibtorrentRasterbar.cmake @@ -0,0 +1,186 @@ +# - Try to find libtorrent-rasterbar +# +# This module tries to locate libtorrent-rasterbar Config.cmake files and fallbacks to pkg-config. +# If that does not work, you can pre-set LibtorrentRasterbar_CUSTOM_DEFINITIONS +# for definitions unrelated to Boost's separate compilation (which are already +# decided by the LibtorrentRasterbar_USE_STATIC_LIBS variable). +# +# Once done this will define +# LibtorrentRasterbar_FOUND - System has libtorrent-rasterbar +# LibtorrentRasterbar_OPENSSL_ENABLED - libtorrent-rasterbar uses and links against OpenSSL +# LibtorrentRasterbar::torrent-rasterbar imported target will be created + +function(_try_config_mode) + set(_exactKeyword "") + if (${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT}) + set(_exactKeyword "EXACT") + endif() + + find_package(LibtorrentRasterbar ${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION} ${_exactKeyword} CONFIG) + + if (LibtorrentRasterbar_FOUND) + if (NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} package found in ${LibtorrentRasterbar_DIR}") + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} version: ${LibtorrentRasterbar_VERSION}") + endif() + # Extract target properties into this module variables + get_target_property(_iface_link_libs LibtorrentRasterbar::torrent-rasterbar INTERFACE_LINK_LIBRARIES) + list(FIND _iface_link_libs "OpenSSL::SSL" _openssl_lib_index) + if (_openssl_lib_index GREATER -1) + set(LibtorrentRasterbar_OPENSSL_ENABLED TRUE PARENT_SCOPE) + else() + set(LibtorrentRasterbar_OPENSSL_ENABLED FALSE PARENT_SCOPE) + endif() + endif() +endfunction() + +function(_try_pkgconfig_mode) + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(_quietKeyword "QUIET") + endif() + find_package(Threads ${_quietKeyword} REQUIRED) + find_package(PkgConfig ${_quietKeyword}) + if(PKG_CONFIG_FOUND) + set(_moduleSpec "libtorrent-rasterbar") + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION) + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION_EXACT) + set(_moduleSpec "${_moduleSpec}=${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION}") + else() + set(_moduleSpec "${_moduleSpec}>=${${CMAKE_FIND_PACKAGE_NAME}_FIND_VERSION}") + endif() + endif() + + pkg_check_modules(PC_LIBTORRENT_RASTERBAR ${_quietKeyword} IMPORTED_TARGET GLOBAL ${_moduleSpec}) + if (PC_LIBTORRENT_RASTERBAR_FOUND) + add_library(LibtorrentRasterbar::torrent-rasterbar ALIAS PkgConfig::PC_LIBTORRENT_RASTERBAR) + list(FIND PC_LIBTORRENT_RASTERBAR_LIBRARIES "ssl" _openssl_lib_index) + if (_openssl_lib_index GREATER -1) + set(LibtorrentRasterbar_OPENSSL_ENABLED TRUE PARENT_SCOPE) + else() + set(LibtorrentRasterbar_OPENSSL_ENABLED FALSE PARENT_SCOPE) + endif() + set(LibtorrentRasterbar_FOUND TRUE PARENT_SCOPE) + else() + set(LibtorrentRasterbar_FOUND FALSE PARENT_SCOPE) + endif() + endif() +endfunction() + +function(_try_generic_mode) + if(LibtorrentRasterbar_USE_STATIC_LIBS) + set(LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + if(LibtorrentRasterbar_CUSTOM_DEFINITIONS) + set(LibtorrentRasterbar_DEFINITIONS ${LibtorrentRasterbar_CUSTOM_DEFINITIONS}) + else() + # Without pkg-config, we can't possibly figure out the correct build flags. + # libtorrent is very picky about those. Let's take a set of defaults and + # hope that they apply. If not, you the user are on your own. + set(LibtorrentRasterbar_DEFINITIONS + -DTORRENT_USE_OPENSSL + -DTORRENT_DISABLE_GEO_IP + -DBOOST_ASIO_ENABLE_CANCELIO + -D_FILE_OFFSET_BITS=64) + endif() + + if(NOT LibtorrentRasterbar_USE_STATIC_LIBS) + list(APPEND LibtorrentRasterbar_DEFINITIONS + -DTORRENT_LINKING_SHARED + -DBOOST_SYSTEM_DYN_LINK) + endif() + + find_path(LibtorrentRasterbar_INCLUDE_DIR libtorrent + HINTS ${PC_LIBTORRENT_RASTERBAR_INCLUDEDIR} ${PC_LIBTORRENT_RASTERBAR_INCLUDE_DIRS} + PATH_SUFFIXES libtorrent-rasterbar) + + find_library(LibtorrentRasterbar_LIBRARY NAMES torrent-rasterbar + HINTS ${PC_LIBTORRENT_RASTERBAR_LIBDIR} ${PC_LIBTORRENT_RASTERBAR_LIBRARY_DIRS}) + + if(LibtorrentRasterbar_USE_STATIC_LIBS) + set(CMAKE_FIND_LIBRARY_SUFFIXES ${LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) + endif() + + if (NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} definitions: ${LibtorrentRasterbar_DEFINITIONS}") + if (LibtorrentRasterbar_INCLUDE_DIR) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} include dir: ${LibtorrentRasterbar_INCLUDE_DIR}") + endif() + if (LibtorrentRasterbar_LIBRARY) + message(STATUS "${CMAKE_FIND_PACKAGE_NAME} library: ${LibtorrentRasterbar_LIBRARY}") + endif() + endif() + + mark_as_advanced(LibtorrentRasterbar_LIBRARY LibtorrentRasterbar_INCLUDE_DIR) + + if(NOT LibtorrentRasterbar_LIBRARY OR NOT LibtorrentRasterbar_INCLUDE_DIR) + set(LibtorrentRasterbar_FOUND FALSE PARENT_SCOPE) + return() + endif() + + if(NOT Boost_SYSTEM_FOUND) + find_package(Boost QUIET REQUIRED COMPONENTS system) + endif() + + set(LibtorrentRasterbar_LIBRARIES Boost::system ${CMAKE_THREAD_LIBS_INIT}) + + list(FIND LibtorrentRasterbar_DEFINITIONS -DTORRENT_USE_OPENSSL _ENCRYPTION_INDEX) + if(_ENCRYPTION_INDEX GREATER -1) + find_package(OpenSSL QUIET REQUIRED) + list(APPEND LibtorrentRasterbar_LIBRARIES OpenSSL::SSL) + if (LibtorrentRasterbar_USE_STATIC_LIBS) + list(APPEND LibtorrentRasterbar_LIBRARIES OpenSSL::Crypto) + endif() + set(LibtorrentRasterbar_OPENSSL_ENABLED ON PARENT_SCOPE) + else() + set(LibtorrentRasterbar_OPENSSL_ENABLED OFF PARENT_SCOPE) + endif() + + set(LibtorrentRasterbar_FOUND TRUE PARENT_SCOPE) + + if (NOT TARGET LibtorrentRasterbar::torrent-rasterbar) + if (LibtorrentRasterbar_USE_STATIC_LIBS) + set(_libType "STATIC") + else() + set(_libType "SHARED") + endif() + + add_library(LibtorrentRasterbar::torrent-rasterbar ${_libType} IMPORTED) + + # LibtorrentRasterbar_DEFINITIONS var contains a mix of -D, -f, and possible -std options + # let's split them into definitions and options (that are not definitions) + set(LibtorrentRasterbar_defines "${LibtorrentRasterbar_DEFINITIONS}") + set(LibtorrentRasterbar_options "${LibtorrentRasterbar_DEFINITIONS}") + list(FILTER LibtorrentRasterbar_defines INCLUDE REGEX "(^|;)-D.+") + list(FILTER LibtorrentRasterbar_options EXCLUDE REGEX "(^|;)-D.+") + # remove '-D' from LibtorrentRasterbar_defines + string(REGEX REPLACE "(^|;)(-D)" "\\1" LibtorrentRasterbar_defines "${LibtorrentRasterbar_defines}") + + set_target_properties(LibtorrentRasterbar::torrent-rasterbar PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${LibtorrentRasterbar_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LibtorrentRasterbar_INCLUDE_DIRS}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${LibtorrentRasterbar_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${LibtorrentRasterbar_LIBRARIES}" + INTERFACE_COMPILE_DEFINITIONS "${LibtorrentRasterbar_defines}" + INTERFACE_COMPILE_OPTIONS "${LibtorrentRasterbar_options}" + ) + endif() +endfunction() + +if (NOT LibtorrentRasterbar_FOUND) + _try_config_mode() +endif() + +if (NOT LibtorrentRasterbar_FOUND) + _try_pkgconfig_mode() +endif() + +if (NOT LibtorrentRasterbar_FOUND) + _try_generic_mode() +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LibtorrentRasterbar_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(LibtorrentRasterbar DEFAULT_MSG LibtorrentRasterbar_FOUND) diff --git a/examples/connection_tester.cpp b/examples/connection_tester.cpp new file mode 100644 index 0000000..90a307f --- /dev/null +++ b/examples/connection_tester.cpp @@ -0,0 +1,1130 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/string_view.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#if BOOST_ASIO_DYN_LINK +#include +#endif + +using namespace lt; +using namespace lt::detail; // for write_* and read_* + +using namespace std::placeholders; + +void generate_block(span buffer, piece_index_t const piece + , int const offset) +{ + std::uint32_t const fill = (static_cast(piece) << 8) | ((offset / 0x4000) & 0xff); + for (auto& w : buffer) w = fill; +} + +// in order to circumvent the restricton of only +// one connection per IP that most clients implement +// all sockets created by this tester are bound to +// uniqe local IPs in the range (127.0.0.1 - 127.255.255.255) +// it's only enabled if the target is also on the loopback +int local_if_counter = 0; +bool local_bind = false; + +// when set to true, blocks downloaded are verified to match +// the test torrents +bool verify_downloads = false; + +// if this is true, one block in 1000 will be sent corrupt. +// this only applies to dual and upload tests +bool test_corruption = false; + +// number of seeds we've spawned. The test is terminated +// when this reaches zero, for dual tests +std::atomic num_seeds(0); + +// the kind of test to run. Upload sends data to a +// bittorrent client, download requests data from +// a client and dual uploads and downloads from a client +// at the same time (this is presumably the most realistic +// test) +enum test_mode_t{ none, upload_test, download_test, dual_test }; +test_mode_t test_mode = none; + +// the number of suggest messages received (total across all peers) +std::atomic num_suggest(0); + +// the number of requests made from suggested pieces +std::atomic num_suggested_requests(0); + +std::string leaf_path(std::string f) +{ + if (f.empty()) return ""; + char const* first = f.c_str(); + char const* sep = strrchr(first, '/'); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + char const* altsep = strrchr(first, '\\'); + if (sep == 0 || altsep > sep) sep = altsep; +#endif + if (sep == nullptr) return f; + + if (sep - first == int(f.size()) - 1) + { + // if the last character is a / (or \) + // ignore it + int len = 0; + while (sep > first) + { + --sep; + if (*sep == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || *sep == '\\' +#endif + ) + return std::string(sep + 1, len); + ++len; + } + return std::string(first, len); + } + return std::string(sep + 1); +} + +namespace { +std::random_device dev; +std::mt19937 rng(dev()); +} + +struct peer_conn +{ + peer_conn(io_service& ios, int num_pieces, int blocks_pp, tcp::endpoint const& ep + , char const* ih, bool seed_, int churn_, bool corrupt_) + : s(ios) + , read_pos(0) + , state(handshaking) + , choked(true) + , current_piece(-1) + , current_piece_is_allowed(false) + , block(0) + , blocks_per_piece(blocks_pp) + , info_hash(ih) + , outstanding_requests(0) + , seed(seed_) + , fast_extension(false) + , blocks_received(0) + , blocks_sent(0) + , num_pieces(num_pieces) + , start_time(clock_type::now()) + , churn(churn_) + , corrupt(corrupt_) + , endpoint(ep) + , restarting(false) + { + corruption_counter = rand() % 1000; + if (seed) ++num_seeds; + pieces.reserve(num_pieces); + start_conn(); + } + + void start_conn() + { + if (local_bind) + { + error_code ec; + s.open(endpoint.protocol(), ec); + if (ec) + { + close("ERROR OPEN: %s", ec); + return; + } + tcp::endpoint bind_if(address_v4( + (127 << 24) + + ((local_if_counter / 255) << 16) + + ((local_if_counter % 255) + 1)), 0); + ++local_if_counter; + s.bind(bind_if, ec); + if (ec) + { + close("ERROR BIND: %s", ec); + return; + } + } + restarting = false; + s.async_connect(endpoint, std::bind(&peer_conn::on_connect, this, _1)); + } + + tcp::socket s; + char write_buf_proto[100]; + std::uint32_t write_buffer[17*1024/4]; + std::uint32_t buffer[17*1024/4]; + int read_pos; + int corruption_counter; + + enum state_t + { + handshaking, + sending_request, + receiving_message + }; + int state; + std::vector pieces; + std::vector suggested_pieces; + std::vector allowed_fast; + bool choked; + piece_index_t current_piece; // the piece we're currently requesting blocks from + bool current_piece_is_allowed; + int block; + int blocks_per_piece; + char const* info_hash; + int outstanding_requests; + // if this is true, this connection is a seed + bool seed; + bool fast_extension; + int blocks_received; + int blocks_sent; + int num_pieces; + time_point start_time; + time_point end_time; + int churn; + bool corrupt; + tcp::endpoint endpoint; + bool restarting; + + void on_connect(error_code const& ec) + { + if (ec) + { + close("ERROR CONNECT: %s", ec); + return; + } + + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + char* h = (char*)malloc(sizeof(handshake)); + memcpy(h, handshake, sizeof(handshake)); + std::memcpy(h + 28, info_hash, 20); + std::generate(h + 48, h + 68, &rand); + // for seeds, don't send the interested message + boost::asio::async_write(s, boost::asio::buffer(h, (sizeof(handshake) - 1) - (seed ? 5 : 0)) + , std::bind(&peer_conn::on_handshake, this, h, _1, _2)); + } + + void on_handshake(char* h, error_code const& ec, size_t) + { + free(h); + if (ec) + { + close("ERROR SEND HANDSHAKE: %s", ec); + return; + } + + // read handshake + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 68) + , std::bind(&peer_conn::on_handshake2, this, _1, _2)); + } + + void on_handshake2(error_code const& ec, size_t) + { + if (ec) + { + close("ERROR READ HANDSHAKE: %s", ec); + return; + } + + // buffer is the full 68 byte handshake + // look at the extension bits + + fast_extension = (((char*)buffer)[27] & 4) != 0; + + if (seed) + { + write_have_all(); + } + else + { + work_download(); + } + } + + void write_have_all() + { + if (fast_extension) + { + char* ptr = write_buf_proto; + // have_all + write_uint32(1, ptr); + write_uint8(0xe, ptr); + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, ptr - write_buf_proto) + , std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } + else + { + // bitfield + int len = (num_pieces + 7) / 8; + char* ptr = (char*)buffer; + write_uint32(len + 1, ptr); + write_uint8(5, ptr); + memset(ptr, 255, len); + ptr += len; + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer((char*)buffer, len + 10) + , std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } + } + + void on_have_all_sent(error_code const& ec, size_t) + { + if (ec) + { + close("ERROR SEND HAVE ALL: %s", ec); + return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + + bool write_request() + { + // if we're choked (and there are no allowed-fast pieces left) + if (choked && allowed_fast.empty() && !current_piece_is_allowed) return false; + + // if there are no pieces left to request + if (pieces.empty() && suggested_pieces.empty() + && current_piece == piece_index_t(-1)) + { + return false; + } + + if (current_piece == piece_index_t(-1)) + { + // pick a new piece + if (choked && allowed_fast.size() > 0) + { + current_piece = allowed_fast.front(); + allowed_fast.erase(allowed_fast.begin()); + current_piece_is_allowed = true; + } + else if (suggested_pieces.size() > 0) + { + current_piece = suggested_pieces.front(); + suggested_pieces.erase(suggested_pieces.begin()); + ++num_suggested_requests; + current_piece_is_allowed = false; + } + else if (pieces.size() > 0) + { + current_piece = pieces.front(); + pieces.erase(pieces.begin()); + current_piece_is_allowed = false; + } + else + { + TORRENT_ASSERT_FAIL(); + } + } + char msg[] = "\0\0\0\xd\x06" + " " // piece + " " // offset + " "; // length + char* m = (char*)malloc(sizeof(msg)); + memcpy(m, msg, sizeof(msg)); + char* ptr = m + 5; + write_uint32(static_cast(current_piece), ptr); + write_uint32(block * 16 * 1024, ptr); + write_uint32(16 * 1024, ptr); + boost::asio::async_write(s, boost::asio::buffer(m, sizeof(msg) - 1) + , std::bind(&peer_conn::on_req_sent, this, m, _1, _2)); + + ++outstanding_requests; + ++block; + if (block == blocks_per_piece) + { + block = 0; + current_piece = piece_index_t(-1); + current_piece_is_allowed = false; + } + return true; + } + + void on_req_sent(char* m, error_code const& ec, size_t) + { + free(m); + if (ec) + { + close("ERROR SEND REQUEST: %s", ec); + return; + } + + work_download(); + } + + void close(char const* fmt, error_code const& ec) + { + end_time = clock_type::now(); + char tmp[1024]; + std::snprintf(tmp, sizeof(tmp), fmt, ec.message().c_str()); + int time = int(total_milliseconds(end_time - start_time)); + if (time == 0) time = 1; + float up = (std::int64_t(blocks_sent) * 0x4000) / time / 1000.f; + float down = (std::int64_t(blocks_received) * 0x4000) / time / 1000.f; + error_code e; + + char ep_str[200]; + address const& addr = s.local_endpoint(e).address(); + if (addr.is_v6()) + std::snprintf(ep_str, sizeof(ep_str), "[%s]:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + else + std::snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + std::printf("%s ep: %s sent: %d received: %d duration: %d ms up: %.1fMB/s down: %.1fMB/s\n" + , tmp, ep_str, blocks_sent, blocks_received, time, up, down); + if (seed) --num_seeds; + } + + void work_download() + { + if (pieces.empty() + && suggested_pieces.empty() + && current_piece == piece_index_t(-1) + && outstanding_requests == 0 + && blocks_received >= num_pieces * blocks_per_piece) + { + close("COMPLETED DOWNLOAD", error_code()); + return; + } + + // send requests + if (outstanding_requests < 40) + { + if (write_request()) return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + + void on_msg_length(error_code const& ec, size_t) + { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE PREFIX: %s", ec); + return; + } + char* ptr = (char*)buffer; + unsigned int length = read_uint32(ptr); + if (length > sizeof(buffer)) + { + std::fprintf(stderr, "len: %d\n", length); + close("ERROR RECEIVE MESSAGE PREFIX: packet too big", error_code()); + return; + } + boost::asio::async_read(s, boost::asio::buffer((char*)buffer, length) + , std::bind(&peer_conn::on_message, this, _1, _2)); + } + + void on_message(error_code const& ec, size_t bytes_transferred) + { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE: %s", ec); + return; + } + char* ptr = (char*)buffer; + int msg = read_uint8(ptr); + + if (test_mode == dual_test && num_seeds == 0) + { + TORRENT_ASSERT(!seed); + close("NO MORE SEEDS, test done", error_code()); + return; + } + + //std::printf("msg: %d len: %d\n", msg, int(bytes_transferred)); + + if (seed) + { + if (msg == 6) + { + if (bytes_transferred != 13) + { + close("REQUEST packet has invalid size", error_code()); + return; + } + piece_index_t const piece = piece_index_t(detail::read_int32(ptr)); + int const start = detail::read_int32(ptr); + int const length = detail::read_int32(ptr); + write_piece(piece, start, length); + } + else if (msg == 3) // not-interested + { + close("DONE", error_code()); + return; + } + else + { + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer, 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + } + else + { + if (msg == 0xe) // have_all + { + // build a list of all pieces and request them all! + pieces.resize(num_pieces); + for (piece_index_t i(0); i < piece_index_t(int(pieces.size())); ++i) + pieces[static_cast(i)] = i; + std::shuffle(pieces.begin(), pieces.end(), rng); + } + else if (msg == 4) // have + { + piece_index_t const piece(detail::read_int32(ptr)); + if (pieces.empty()) pieces.push_back(piece); + else pieces.insert(pieces.begin() + (rand() % pieces.size()), piece); + } + else if (msg == 5) // bitfield + { + pieces.reserve(num_pieces); + piece_index_t piece(0); + for (int i = 0; i < int(bytes_transferred); ++i) + { + int mask = 0x80; + for (int k = 0; k < 8; ++k) + { + if (piece > piece_index_t(num_pieces)) break; + if (*ptr & mask) pieces.push_back(piece); + mask >>= 1; + ++piece; + } + ++ptr; + } + std::shuffle(pieces.begin(), pieces.end(), rng); + } + else if (msg == 7) // piece + { + if (verify_downloads) + { + piece_index_t const piece(read_uint32(ptr)); + int start = read_uint32(ptr); + int size = int(bytes_transferred) - 9; + verify_piece(piece, start, ptr, size); + } + ++blocks_received; + --outstanding_requests; + piece_index_t const piece = piece_index_t(detail::read_int32(ptr)); + int start = detail::read_int32(ptr); + + if (churn && (blocks_received % churn) == 0) { + outstanding_requests = 0; + restarting = true; + s.close(); + return; + } + if (int((start + bytes_transferred) / 0x4000) == blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + piece_index_t const piece(detail::read_int32(ptr)); + auto i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + suggested_pieces.push_back(piece); + ++num_suggest; + } + } + else if (msg == 16) // reject request + { + piece_index_t const piece(detail::read_int32(ptr)); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + + // put it back! + if (current_piece != piece) + { + if (pieces.empty() || pieces.back() != piece) + pieces.push_back(piece); + } + else + { + block = std::min(start / 0x4000, block); + if (block == 0) + { + pieces.push_back(current_piece); + current_piece = piece_index_t(-1); + current_piece_is_allowed = false; + } + } + --outstanding_requests; + std::fprintf(stderr, "REJECT: [ piece: %d start: %d length: %d ]\n" + , static_cast(piece), start, length); + } + else if (msg == 0) // choke + { + choked = true; + } + else if (msg == 1) // unchoke + { + choked = false; + } + else if (msg == 17) // allowed_fast + { + piece_index_t const piece = piece_index_t(detail::read_int32(ptr)); + auto i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + allowed_fast.push_back(piece); + } + } + work_download(); + } + } + + bool verify_piece(piece_index_t const piece, int start, char const* ptr, int size) + { + std::uint32_t* buf = (std::uint32_t*)ptr; + std::uint32_t const fill = (static_cast(piece) << 8) | ((start / 0x4000) & 0xff); + for (int i = 0; i < size / 4; ++i) + { + if (buf[i] != fill) + { + std::fprintf(stderr, "received invalid block. piece %d block %d\n" + , static_cast(piece), start / 0x4000); + exit(1); + } + } + return true; + } + + void write_piece(piece_index_t const piece, int start, int length) + { + generate_block({write_buffer, length / 4} + , piece, start); + + if (corrupt) + { + --corruption_counter; + if (corruption_counter == 0) + { + corruption_counter = 1000; + std::memset(write_buffer, 0, 10); + } + } + char* ptr = write_buf_proto; + write_uint32(9 + length, ptr); + assert(length == 0x4000); + write_uint8(7, ptr); + write_uint32(static_cast(piece), ptr); + write_uint32(start, ptr); + std::array vec; + vec[0] = boost::asio::buffer(write_buf_proto, ptr - write_buf_proto); + vec[1] = boost::asio::buffer(write_buffer, length); + boost::asio::async_write(s, vec, std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + ++blocks_sent; + if (churn && (blocks_sent % churn) == 0 && seed) { + outstanding_requests = 0; + restarting = true; + s.close(); + } + } + + void write_have(piece_index_t const piece) + { + char* ptr = write_buf_proto; + write_uint32(5, ptr); + write_uint8(4, ptr); + write_uint32(static_cast(piece), ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto, 9), std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } +}; + +void print_usage() +{ + std::fprintf(stderr, "usage: connection_tester command [options]\n\n" + "command is one of:\n" + " gen-torrent generate a test torrent\n" + " options for this command:\n" + " -s the size of the torrent in megabytes\n" + " -n the number of files in the test torrent\n" + " -a introduce a lot of pad-files\n" + " (pad files are not supported for gen-data or upload)\n" + " -t the file to save the .torrent file to\n" + " -T the name of the torrent (and directory\n" + " its files are saved in)\n\n" + " gen-data generate the data file(s) for the test torrent\n" + " options for this command:\n" + " -t the torrent file that was previously generated\n" + " -P the path to where the data should be stored\n\n" + " gen-test-torrents generate many test torrents (cannot be used for up/down tests)\n" + " options for this command:\n" + " -N number of torrents to generate\n" + " -n number of files in each torrent\n" + " -t base name of torrent files (index is appended)\n\n" + " -T add the specified tracker URL to each torrent\n" + " this option may appear multiple times\n\n" + " upload start an uploader test\n" + " download start a downloader test\n" + " dual start a download and upload test\n" + " options for these commands:\n" + " -c the number of connections to make to the target\n" + " -d the IP address of the target\n" + " -p the port the target listens on\n" + " -t the torrent file previously generated by gen-torrent\n" + " -C send corrupt pieces sometimes (applies to upload and dual)\n" + " -r churn - number of reconnects per second\n\n" + "examples:\n\n" + "connection_tester gen-torrent -s 1024 -n 4 -t test.torrent\n" + "connection_tester upload -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n" + "connection_tester download -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n" + "connection_tester dual -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n"); + exit(1); +} + +void hasher_thread(lt::create_torrent* t, piece_index_t const start_piece + , piece_index_t const end_piece, int piece_size, bool print) +{ + if (print) std::fprintf(stderr, "\n"); + std::uint32_t piece[0x4000 / 4]; + for (piece_index_t i = start_piece; i < end_piece; ++i) + { + hasher ph; + for (int j = 0; j < piece_size; j += 0x4000) + { + generate_block(piece, i, j); + ph.update(reinterpret_cast(piece), 0x4000); + } + t->set_hash(i, ph.final()); + int const range = static_cast(end_piece) - static_cast(start_piece); + if (print && (static_cast(i) & 1)) + { + int const delta_piece = static_cast(i) - static_cast(start_piece); + std::fprintf(stderr, "\r%.1f %% ", float(delta_piece * 100) / float(range)); + } + } + if (print) std::fprintf(stderr, "\n"); +} + +// size is in megabytes +void generate_torrent(std::vector& buf, int num_pieces, int num_files + , char const* torrent_name, bool with_padding) +{ + file_storage fs; + // 1 MiB piece size + const int piece_size = 1024 * 1024; + const std::int64_t total_size = std::int64_t(piece_size) * num_pieces; + + std::int64_t s = total_size; + int file_index = 0; + std::int64_t file_size = total_size / num_files; + while (s > 0) + { + char b[100]; + std::snprintf(b, sizeof(b), "%s/stress_test%d", torrent_name, file_index); + ++file_index; + fs.add_file(b, std::min(s, file_size)); + s -= file_size; + file_size += 200; + } + + lt::create_torrent t(fs, piece_size, with_padding ? 100 : -1); + + num_pieces = t.num_pieces(); + + int const num_threads = std::thread::hardware_concurrency() + ? std::thread::hardware_concurrency() : 4; + std::printf("hashing in %d threads\n", num_threads); + + std::vector threads; + threads.reserve(num_threads); + for (int i = 0; i < num_threads; ++i) + { + threads.emplace_back(&hasher_thread, &t + , piece_index_t(i * num_pieces / num_threads) + , piece_index_t((i + 1) * num_pieces / num_threads) + , piece_size + , i == 0); + } + + for (auto& i : threads) + i.join(); + + std::back_insert_iterator> out(buf); + bencode(out, t.generate()); +} + +void generate_data(char const* path, torrent_info const& ti) +{ + file_storage const& fs = ti.files(); + + file_pool fp; + + aux::vector priorities; + sha1_hash info_hash; + storage_params params{ + fs, + nullptr, + path, + storage_mode_sparse, + priorities, + info_hash + }; + + std::unique_ptr st(default_storage_constructor(params, fp)); + + { + storage_error error; + st->initialize(error); + } + + std::uint32_t piece[0x4000 / 4]; + for (piece_index_t i(0); i < piece_index_t(ti.num_pieces()); ++i) + { + for (int j = 0; j < ti.piece_size(i); j += 0x4000) + { + generate_block(piece, i, j); + int const left_in_piece = ti.piece_size(i) - j; + iovec_t const b = { reinterpret_cast(piece) + , std::min(left_in_piece, 0x4000)}; + storage_error error; + st->writev(b, i, j, open_mode::write_only, error); + if (error) + std::fprintf(stderr, "storage error: %s\n", error.ec.message().c_str()); + } + if (static_cast(i) & 1) + { + std::fprintf(stderr, "\r%.1f %% ", float(static_cast(i) * 100) / float(ti.num_pieces())); + } + } +} + +void io_thread(io_service* ios) +{ + error_code ec; + ios->run(ec); + if (ec) std::fprintf(stderr, "ERROR: %s\n", ec.message().c_str()); +} + +int main(int argc, char* argv[]) +{ + if (argc <= 1) print_usage(); + + char const* command = argv[1]; + int size = 1000; + int num_files = 10; + int num_torrents = 1; + char const* torrent_file = "benchmark.torrent"; + char const* data_path = "."; + int num_connections = 50; + char const* destination_ip = "127.0.0.1"; + int destination_port = 6881; + int churn = 0; + bool gen_pad_files = false; + std::vector trackers; + + argv += 2; + argc -= 2; + + while (argc > 0) + { + char const* optname = argv[0]; + ++argv; + --argc; + + if (optname[0] != '-' || strlen(optname) != 2) + { + std::fprintf(stderr, "unknown option: %s\n", optname); + continue; + } + + // options with no arguments + switch (optname[1]) + { + case 'C': test_corruption = true; continue; + case 'a': gen_pad_files = true; continue; + } + + if (argc == 0) + { + std::fprintf(stderr, "missing argument for option: %s\n", optname); + break; + } + + char const* optarg = argv[0]; + ++argv; + --argc; + + switch (optname[1]) + { + case 's': size = atoi(optarg); break; + case 'n': num_files = atoi(optarg); break; + case 'N': num_torrents = atoi(optarg); break; + case 't': torrent_file = optarg; break; + case 'T': trackers.push_back(optarg); break; + case 'P': data_path = optarg; break; + case 'c': num_connections = atoi(optarg); break; + case 'p': destination_port = atoi(optarg); break; + case 'd': destination_ip = optarg; break; + case 'r': churn = atoi(optarg); break; + default: std::fprintf(stderr, "unknown option: %s\n", optname); + } + } + + if (command == "gen-torrent"_sv) + { + std::vector tmp; + std::string name = leaf_path(torrent_file); + name = name.substr(0, name.find_last_of('.')); + std::printf("generating torrent: %s\n", name.c_str()); + generate_torrent(tmp, size ? size : 1024, num_files ? num_files : 1 + , name.c_str(), gen_pad_files); + + FILE* output = stdout; + if ("-"_sv != torrent_file) + { + if( (output = std::fopen(torrent_file, "wb+")) == nullptr) + { + std::fprintf(stderr, "Could not open file '%s' for writing: %s\n" + , torrent_file, std::strerror(errno)); + exit(2); + } + } + std::fprintf(stderr, "writing file to: %s\n", torrent_file); + fwrite(&tmp[0], 1, tmp.size(), output); + if (output != stdout) + std::fclose(output); + + return 0; + } + else if (command == "gen-data"_sv) + { + error_code ec; + torrent_info ti(torrent_file, ec); + if (ec) + { + std::fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); + return 1; + } + generate_data(data_path, ti); + return 0; + } + else if (command == "gen-test-torrents"_sv) + { + std::vector buf; + for (int i = 0; i < num_torrents; ++i) + { + char torrent_name[100]; + std::snprintf(torrent_name, sizeof(torrent_name), "%s-%d.torrent", torrent_file, i); + + file_storage fs; + for (int j = 0; j < num_files; ++j) + { + char file_name[100]; + std::snprintf(file_name, sizeof(file_name), "%s-%d/file-%d", torrent_file, i, j); + fs.add_file(file_name, std::int64_t(j + i + 1) * 251); + } + // 1 MiB piece size + const int piece_size = 1024 * 1024; + lt::create_torrent t(fs, piece_size); + sha1_hash zero(nullptr); + for (auto const k : fs.piece_range()) + t.set_hash(k, zero); + + int tier = 0; + for (auto const& tr : trackers) + t.add_tracker(tr, tier++); + + buf.clear(); + std::back_insert_iterator> out(buf); + bencode(out, t.generate()); + FILE* f = std::fopen(torrent_name, "w+"); + if (f == nullptr) + { + std::fprintf(stderr, "Could not open file '%s' for writing: %s\n" + , torrent_name, std::strerror(errno)); + return 1; + } + size_t ret = fwrite(buf.data(), 1, buf.size(), f); + if (ret != buf.size()) + { + std::fprintf(stderr, "write returned: %d (expected %d)\n", int(ret), int(buf.size())); + std::fclose(f); + return 1; + } + std::printf("wrote %s\n", torrent_name); + std::fclose(f); + } + return 0; + } + else if (command == "upload"_sv) + { + test_mode = upload_test; + } + else if (command == "download"_sv) + { + test_mode = download_test; + } + else if (command == "dual"_sv) + { + test_mode = dual_test; + } + else + { + std::fprintf(stderr, "unknown command: %s\n\n", command); + print_usage(); + } + + error_code ec; + address_v4 addr = address_v4::from_string(destination_ip, ec); + if (ec) + { + std::fprintf(stderr, "ERROR RESOLVING %s: %s\n", destination_ip, ec.message().c_str()); + return 1; + } + tcp::endpoint ep(addr, std::uint16_t(destination_port)); + +#if !defined __APPLE__ + // apparently darwin doesn't seems to let you bind to + // loopback on any other IP than 127.0.0.1 + std::uint32_t const ip = addr.to_ulong(); + if ((ip & 0xff000000) == 0x7f000000) + { + local_bind = true; + } +#endif + + torrent_info ti(torrent_file, ec); + if (ec) + { + std::fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); + return 1; + } + + std::vector conns; + conns.reserve(num_connections); + int const num_threads = 2; + io_service ios[num_threads]; + for (int i = 0; i < num_connections; ++i) + { + bool corrupt = test_corruption && (i & 1) == 0; + bool seed = false; + if (test_mode == upload_test) seed = true; + else if (test_mode == dual_test) seed = (i & 1); + conns.push_back(new peer_conn(ios[i % num_threads], ti.num_pieces(), ti.piece_length() / 16 / 1024 + , ep, (char const*)&ti.info_hash()[0], seed, churn, corrupt)); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ios[i % num_threads].poll_one(ec); + if (ec) + { + std::fprintf(stderr, "ERROR: %s\n", ec.message().c_str()); + break; + } + } + + std::thread t1(&io_thread, &ios[0]); + std::thread t2(&io_thread, &ios[1]); + + t1.join(); + t2.join(); + + float up = 0.f; + float down = 0.f; + std::uint64_t total_sent = 0; + std::uint64_t total_received = 0; + + for (std::vector::iterator i = conns.begin() + , end(conns.end()); i != end; ++i) + { + peer_conn* p = *i; + int time = int(total_milliseconds(p->end_time - p->start_time)); + if (time == 0) time = 1; + total_sent += p->blocks_sent; + up += (std::int64_t(p->blocks_sent) * 0x4000) / time / 1000.f; + down += (std::int64_t(p->blocks_received) * 0x4000) / time / 1000.f; + delete p; + } + + std::printf("=========================\n" + "suggests: %d suggested-requests: %d\n" + "total sent: %.1f %% received: %.1f %%\n" + "rate sent: %.1f MB/s received: %.1f MB/s\n" + , int(num_suggest), int(num_suggested_requests) + , total_sent * 0x4000 * 100.f / float(ti.total_size()) + , total_received * 0x4000 * 100.f / float(ti.total_size()) + , up, down); + + return 0; +} diff --git a/examples/custom_storage.cpp b/examples/custom_storage.cpp new file mode 100644 index 0000000..6043758 --- /dev/null +++ b/examples/custom_storage.cpp @@ -0,0 +1,125 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/fwd.hpp" + +#include + +// -- example begin +struct temp_storage : lt::storage_interface +{ + explicit temp_storage(lt::file_storage const& fs) : lt::storage_interface(fs) {} + void initialize(lt::storage_error&) override {} + bool has_any_file(lt::storage_error&) override { return false; } + void set_file_priority(lt::aux::vector& + , lt::storage_error&) override {} + int readv(lt::span bufs, lt::piece_index_t piece + , int offset, lt::open_mode_t, lt::storage_error&) override + { + auto const i = m_file_data.find(piece); + if (i == m_file_data.end()) return 0; + if (int(i->second.size()) <= offset) return 0; + lt::iovec_t data{ i->second.data() + offset, int(i->second.size() - offset) }; + int ret = 0; + for (lt::iovec_t const& b : bufs) { + int const to_copy = std::min(int(b.size()), int(data.size())); + memcpy(b.data(), data.data(), to_copy); + data = data.subspan(to_copy); + ret += to_copy; + if (data.empty()) break; + } + return ret; + } + int writev(lt::span bufs + , lt::piece_index_t const piece, int offset, lt::open_mode_t, lt::storage_error&) override + { + auto& data = m_file_data[piece]; + int ret = 0; + for (auto& b : bufs) { + if (int(data.size()) < offset + b.size()) data.resize(offset + b.size()); + std::memcpy(data.data() + offset, b.data(), b.size()); + offset += int(b.size()); + ret += int(b.size()); + } + return ret; + } + void rename_file(lt::file_index_t, std::string const&, lt::storage_error&) override + { assert(false); } + lt::status_t move_storage(std::string const& + , lt::move_flags_t, lt::storage_error&) override { return lt::status_t::no_error; } + bool verify_resume_data(lt::add_torrent_params const& + , lt::aux::vector const& + , lt::storage_error&) override + { return false; } + void release_files(lt::storage_error&) override {} + void delete_files(lt::remove_flags_t, lt::storage_error&) override {} + + std::map> m_file_data; +}; + +lt::storage_interface* temp_storage_constructor(lt::storage_params const& params, lt::file_pool&) +{ + return new temp_storage(params.files); +} + +// -- example end + +int main(int argc, char* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: ./custom_storage torrent-file\n" + "to stop the client, press return.\n"; + return 1; + } + + lt::session s; + lt::add_torrent_params p; + p.storage = temp_storage_constructor; + p.save_path = "./"; + p.ti = std::make_shared(argv[1]); + s.add_torrent(p); + + // wait for the user to end + char a; + int ret = std::scanf("%c\n", &a); + (void)ret; // ignore + return 0; +} +catch (std::exception const& e) { + std::cerr << "ERROR: " << e.what() << "\n"; +} + diff --git a/examples/dump_torrent.cpp b/examples/dump_torrent.cpp new file mode 100644 index 0000000..831cd2d --- /dev/null +++ b/examples/dump_torrent.cpp @@ -0,0 +1,155 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // for snprintf +#include // for PRId64 et.al. + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/magnet_uri.hpp" + +#include +#include + +std::vector load_file(std::string const& filename) +{ + std::fstream in; + in.exceptions(std::ifstream::failbit); + in.open(filename.c_str(), std::ios_base::in | std::ios_base::binary); + in.seekg(0, std::ios_base::end); + size_t const size = size_t(in.tellg()); + in.seekg(0, std::ios_base::beg); + std::vector ret(size); + in.read(ret.data(), size); + return ret; +} + +int main(int argc, char* argv[]) try +{ + if (argc < 2 || argc > 5) { + std::cerr << "usage: dump_torrent torrent-file [total-items-limit] [recursion-limit] [piece-count-limit]\n"; + return 1; + } + + lt::load_torrent_limits cfg; + + if (argc > 2) cfg.max_decode_tokens = atoi(argv[2]); + if (argc > 3) cfg.max_decode_depth = atoi(argv[3]); + if (argc > 4) cfg.max_pieces = atoi(argv[4]); + + std::vector buf = load_file(argv[1]); + int pos = -1; + lt::error_code ec; + std::cout << "decoding. recursion limit: " << cfg.max_decode_depth + << " total item count limit: " << cfg.max_decode_tokens << "\n"; + lt::bdecode_node const e = lt::bdecode(buf, ec, &pos, cfg.max_decode_depth + , cfg.max_decode_tokens); + + std::printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str()); + + if (ec) { + std::cerr << "failed to decode: '" << ec.message() << "' at character: " << pos<< "\n"; + return 1; + } + + lt::torrent_info const t(std::move(e), cfg); + buf.clear(); + + // print info about torrent + std::printf("\n\n----- torrent file info -----\n\nnodes:\n"); + for (auto const& i : t.nodes()) + std::printf("%s: %d\n", i.first.c_str(), i.second); + + puts("trackers:\n"); + for (auto const& i : t.trackers()) + std::printf("%2d: %s\n", i.tier, i.url.c_str()); + + std::stringstream ih; + ih << t.info_hash(); + std::printf("number of pieces: %d\n" + "piece length: %d\n" + "info hash: %s\n" + "comment: %s\n" + "created by: %s\n" + "magnet link: %s\n" + "name: %s\n" + "number of files: %d\n" + "files:\n" + , t.num_pieces() + , t.piece_length() + , ih.str().c_str() + , t.comment().c_str() + , t.creator().c_str() + , make_magnet_uri(t).c_str() + , t.name().c_str() + , t.num_files()); + lt::file_storage const& st = t.files(); + for (auto const i : st.file_range()) + { + auto const first = st.map_file(i, 0, 0).piece; + auto const last = st.map_file(i, std::max(std::int64_t(st.file_size(i)) - 1, std::int64_t(0)), 0).piece; + auto const flags = st.file_flags(i); + std::stringstream file_hash; + if (!st.hash(i).is_all_zeros()) + file_hash << st.hash(i); + std::printf(" %8" PRIx64 " %11" PRId64 " %c%c%c%c [ %5d, %5d ] %7u %s %s %s%s\n" + , st.file_offset(i) + , st.file_size(i) + , ((flags & lt::file_storage::flag_pad_file)?'p':'-') + , ((flags & lt::file_storage::flag_executable)?'x':'-') + , ((flags & lt::file_storage::flag_hidden)?'h':'-') + , ((flags & lt::file_storage::flag_symlink)?'l':'-') + , static_cast(first) + , static_cast(last) + , std::uint32_t(st.mtime(i)) + , file_hash.str().c_str() + , st.file_path(i).c_str() + , (flags & lt::file_storage::flag_symlink) ? "-> " : "" + , (flags & lt::file_storage::flag_symlink) ? st.symlink(i).c_str() : ""); + } + std::printf("web seeds:\n"); + for (auto const& ws : t.web_seeds()) + { + std::printf("%s %s\n" + , ws.type == lt::web_seed_entry::url_seed ? "BEP19" : "BEP17" + , ws.url.c_str()); + } + + return 0; +} +catch (std::exception const& e) +{ + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/make_torrent.cpp b/examples/make_torrent.cpp new file mode 100644 index 0000000..3117e7b --- /dev/null +++ b/examples/make_torrent.cpp @@ -0,0 +1,347 @@ +/* + +Copyright (c) 2006-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/create_torrent.hpp" + +#include +#include +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#include // for _getcwd +#endif + +using namespace std::placeholders; + +std::vector load_file(std::string const& filename) +{ + std::fstream in; + in.exceptions(std::ifstream::failbit); + in.open(filename.c_str(), std::ios_base::in | std::ios_base::binary); + in.seekg(0, std::ios_base::end); + size_t const size = size_t(in.tellg()); + in.seekg(0, std::ios_base::beg); + std::vector ret(size); + in.read(ret.data(), ret.size()); + return ret; +} + +std::string branch_path(std::string const& f) +{ + if (f.empty()) return f; + +#ifdef TORRENT_WINDOWS + if (f == "\\\\") return ""; +#endif + if (f == "/") return ""; + + int len = int(f.size()); + // if the last character is / or \ ignore it + if (f[len-1] == '/' || f[len-1] == '\\') --len; + while (len > 0) { + --len; + if (f[len] == '/' || f[len] == '\\') + break; + } + + if (f[len] == '/' || f[len] == '\\') ++len; + return std::string(f.c_str(), len); +} + +// do not include files and folders whose +// name starts with a . +bool file_filter(std::string const& f) +{ + if (f.empty()) return false; + + char const* first = f.c_str(); + char const* sep = strrchr(first, '/'); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + char const* altsep = strrchr(first, '\\'); + if (sep == nullptr || altsep > sep) sep = altsep; +#endif + // if there is no parent path, just set 'sep' + // to point to the filename. + // if there is a parent path, skip the '/' character + if (sep == nullptr) sep = first; + else ++sep; + + // return false if the first character of the filename is a . + if (sep[0] == '.') return false; + + std::cerr << f << "\n"; + return true; +} + +void print_usage() +{ + std::cerr << R"(usage: make_torrent FILE [OPTIONS] + +Generates a torrent file from the specified file +or directory and writes it to standard out + + +OPTIONS: +-m file generate a merkle hash tree torrent. + merkle torrents require client support + the resulting full merkle tree is written to + the specified file +-w url adds a web seed to the torrent with + the specified url +-t url adds the specified tracker to the + torrent. For multiple trackers, specify more + -t options. Specify a dash character "-" as a tracker to indicate + the following trackers should be in a higher tier. +-c comment sets the comment to the specified string +-C creator sets the created-by field to the specified string +-p bytes enables padding files. Files larger + than bytes will be piece-aligned +-s bytes specifies a piece size for the torrent + This has to be a multiple of 16 kiB +-l Don't follow symlinks, instead encode them as + links in the torrent file +-o file specifies the output filename of the torrent file + If this is not specified, the torrent file is + printed to the standard out, except on windows + where the filename defaults to a.torrent +-r file add root certificate to the torrent, to verify + the HTTPS tracker +-S info-hash add a similar torrent by info-hash. The similar + torrent is expected to share some files with this one +-L collection add a collection name to this torrent. Other torrents + in the same collection is expected to share files + with this one. +-M make the torrent compatible with mutable torrents + this means aligning large files and pad them in order + for piece hashes to uniquely indentify a file without + overlap +)"; +} + +int main(int argc_, char const* argv_[]) try +{ + lt::span args(argv_, size_t(argc_)); + std::string creator_str = "libtorrent"; + std::string comment_str; + + if (args.size() < 2) { + print_usage(); + return 1; + } + + std::vector web_seeds; + std::vector trackers; + std::vector collections; + std::vector similar; + int pad_file_limit = -1; + int piece_size = 0; + lt::create_flags_t flags = {}; + std::string root_cert; + + std::string outfile; + std::string merklefile; +#ifdef TORRENT_WINDOWS + // don't ever write binary data to the console on windows + // it will just be interpreted as text and corrupted + outfile = "a.torrent"; +#endif + + std::string full_path = args[1]; + args = args.subspan(2); + + for (; !args.empty(); args = args.subspan(1)) { + if (args[0][0] != '-') { + print_usage(); + return 1; + } + + char const flag = args[0][1]; + + switch (flag) + { + case 'M': + flags |= lt::create_torrent::mutable_torrent_support; + pad_file_limit = 0x4000; + continue; + case 'l': + flags |= lt::create_torrent::symlinks; + continue; + } + + if (args.size() < 2) { + print_usage(); + return 1; + } + + switch (flag) + { + case 'w': web_seeds.push_back(args[1]); break; + case 't': trackers.push_back(args[1]); break; + case 's': piece_size = atoi(args[1]); break; + case 'o': outfile = args[1]; break; + case 'C': creator_str = args[1]; break; + case 'c': comment_str = args[1]; break; + case 'r': root_cert = args[1]; break; + case 'L': collections.push_back(args[1]); break; + case 'p': + pad_file_limit = atoi(args[1]); + flags |= lt::create_torrent::optimize_alignment; + break; + case 'm': + merklefile = args[1]; + flags |= lt::create_torrent::merkle; + break; + case 'S': { + if (strlen(args[1]) != 40) { + std::cerr << "invalid info-hash for -S. " + "Expected 40 hex characters\n"; + print_usage(); + return 1; + } + std::stringstream hash(args[1]); + lt::sha1_hash ih; + hash >> ih; + if (hash.fail()) { + std::cerr << "invalid info-hash for -S\n"; + print_usage(); + return 1; + } + similar.push_back(ih); + break; + } + default: + print_usage(); + return 1; + } + args = args.subspan(1); + } + + lt::file_storage fs; +#ifdef TORRENT_WINDOWS + if (full_path[1] != ':') +#else + if (full_path[0] != '/') +#endif + { + char cwd[2048]; +#ifdef TORRENT_WINDOWS +#define getcwd_ _getcwd +#else +#define getcwd_ getcwd +#endif + + char const* ret = getcwd_(cwd, sizeof(cwd)); + if (ret == nullptr) { + std::cerr << "failed to get current working directory: " + << strerror(errno) << "\n"; + return 1; + } + +#undef getcwd_ +#ifdef TORRENT_WINDOWS + full_path = cwd + ("\\" + full_path); +#else + full_path = cwd + ("/" + full_path); +#endif + } + + lt::add_files(fs, full_path, file_filter, flags); + if (fs.num_files() == 0) { + std::cerr << "no files specified.\n"; + return 1; + } + + lt::create_torrent t(fs, piece_size, pad_file_limit, flags); + int tier = 0; + for (std::string const& tr : trackers) { + if (tr == "-") ++tier; + else t.add_tracker(tr, tier); + } + + for (std::string const& ws : web_seeds) + t.add_url_seed(ws); + + for (std::string const& c : collections) + t.add_collection(c); + + for (lt::sha1_hash const& s : similar) + t.add_similar_torrent(s); + + auto const num = t.num_pieces(); + lt::set_piece_hashes(t, branch_path(full_path) + , [num] (lt::piece_index_t const p) { + std::cerr << "\r" << p << "/" << num; + }); + + std::cerr << "\n"; + t.set_creator(creator_str.c_str()); + if (!comment_str.empty()) { + t.set_comment(comment_str.c_str()); + } + + if (!root_cert.empty()) { + std::vector const pem = load_file(root_cert); + t.set_root_cert(std::string(&pem[0], pem.size())); + } + + // create the torrent and print it to stdout + std::vector torrent; + lt::bencode(back_inserter(torrent), t.generate()); + if (!outfile.empty()) { + std::fstream out; + out.exceptions(std::ifstream::failbit); + out.open(outfile.c_str(), std::ios_base::out | std::ios_base::binary); + out.write(torrent.data(), torrent.size()); + } + else { + std::cout.write(torrent.data(), torrent.size()); + } + + if (!merklefile.empty()) { + std::fstream merkle; + merkle.exceptions(std::ifstream::failbit); + merkle.open(merklefile.c_str(), std::ios_base::out | std::ios_base::binary); + auto const& tree = t.merkle_tree(); + merkle.write(reinterpret_cast(tree.data()), tree.size() * 20); + } + return 0; +} +catch (std::exception& e) { + std::cerr << "ERROR: " << e.what() << "\n"; + return 1; +} diff --git a/examples/print.cpp b/examples/print.cpp new file mode 100644 index 0000000..358e214 --- /dev/null +++ b/examples/print.cpp @@ -0,0 +1,546 @@ +#ifdef _WIN32 + +#include +#include + +#else + +#include // for close() +#include // for open() +#include + +#endif + +#include "libtorrent/config.hpp" + +#include "print.hpp" + +#include // for atoi +#include // for strlen +#include +#include // for std::min +#include // for back_inserter + +char const* esc(char const* code) +{ + // this is a silly optimization + // to avoid copying of strings + enum { num_strings = 200 }; + static char buf[num_strings][20]; + static int round_robin = 0; + char* ret = buf[round_robin]; + ++round_robin; + if (round_robin >= num_strings) round_robin = 0; + ret[0] = '\033'; + ret[1] = '['; + int i = 2; + int j = 0; + while (code[j]) ret[i++] = code[j++]; + ret[i++] = 'm'; + ret[i++] = 0; + return ret; +} + +std::string to_string(int v, int width) +{ + char buf[100]; + std::snprintf(buf, sizeof(buf), "%*d", width, v); + return buf; +} + +std::string add_suffix_float(double val, char const* suffix) +{ + if (val < 0.001) + { + std::string ret; + ret.resize(4 + 2, ' '); + if (suffix) ret.resize(4 + 2 + strlen(suffix), ' '); + return ret; + } + + const char* prefix[] = {"kB", "MB", "GB", "TB", "PB"}; + const int num_prefix = sizeof(prefix) / sizeof(const char*); + int i = 0; + for (; i < num_prefix - 1; ++i) + { + val /= 1000.; + if (std::fabs(val) < 1000.) break; + } + char ret[100]; + std::snprintf(ret, sizeof(ret), "%4.*f%s%s", val < 99 ? 1 : 0, val, prefix[i], suffix ? suffix : ""); + return ret; +} + +std::string color(std::string const& s, color_code c) +{ + if (c == col_none) return s; + if (std::count(s.begin(), s.end(), ' ') == int(s.size())) return s; + + char buf[1024]; + std::snprintf(buf, sizeof(buf), "\x1b[3%dm%s\x1b[39m", c, s.c_str()); + return buf; +} + +std::string const& progress_bar(int progress, int width, color_code c + , char fill, char bg, std::string caption, int flags) +{ + static std::string bar; + bar.clear(); + bar.reserve(size_t(width + 10)); + + auto const progress_chars = static_cast((progress * width + 500) / 1000); + + if (caption.empty()) + { + char code[10]; + std::snprintf(code, sizeof(code), "\x1b[3%dm", c); + bar = code; + std::fill_n(std::back_inserter(bar), progress_chars, fill); + std::fill_n(std::back_inserter(bar), width - progress_chars, bg); + bar += esc("39"); + } + else + { + // foreground color (depends a bit on background color) + color_code tc = col_black; + if (c == col_black || c == col_blue) + tc = col_white; + + caption.resize(size_t(width), ' '); + +#ifdef _WIN32 + char const* background = "40"; +#else + char const* background = "48;5;238"; +#endif + + char str[256]; + if (flags & progress_invert) + std::snprintf(str, sizeof(str), "\x1b[%sm\x1b[37m%s\x1b[4%d;3%dm%s\x1b[49;39m" + , background, caption.substr(0, progress_chars).c_str(), c, tc + , caption.substr(progress_chars).c_str()); + else + std::snprintf(str, sizeof(str), "\x1b[4%d;3%dm%s\x1b[%sm\x1b[37m%s\x1b[49;39m" + , c, tc, caption.substr(0, progress_chars).c_str(), background + , caption.substr(progress_chars).c_str()); + bar = str; + } + return bar; +} + +int get_piece(lt::bitfield const& p, int index) +{ + if (index < 0 || index >= p.size()) return 0; + return p.get_bit(index) ? 1 : 0; +} + +std::string const& piece_bar(lt::bitfield const& p, int width) +{ +#ifdef _WIN32 + int const table_size = 5; +#else + int const table_size = 18; + width *= 2; // we only print one character for every two "slots" +#endif + + double const piece_per_char = p.size() / double(width); + static std::string bar; + bar.clear(); + bar.reserve(width * 6); + bar += "["; + if (p.size() == 0) + { + for (int i = 0; i < width; ++i) bar += ' '; + bar += "]"; + return bar; + } + + // the [piece, piece + pieces_per_char) range is the pieces that are represented by each character + double piece = 0; + + // we print two blocks at a time, so calculate the color in pair +#ifndef _WIN32 + int color[2]; + int last_color[2] = { -1, -1}; +#endif + + for (int i = 0; i < width; ++i, piece += piece_per_char) + { + int num_pieces = 0; + int num_have = 0; + int end = (std::max)(int(piece + piece_per_char), int(piece) + 1); + for (int k = int(piece); k < end; ++k, ++num_pieces) + if (p[k]) ++num_have; + int const c = int(std::ceil(num_have / float((std::max)(num_pieces, 1)) * (table_size - 1))); + +#ifndef _WIN32 + color[i & 1] = c; + + if ((i & 1) == 1) + { + // now, print color[0] and [1] + // bg determines whether we're settings foreground or background color + static int const bg[] = { 38, 48}; + for (int k = 0; k < 2; ++k) + { + if (color[k] != last_color[k]) + { + char buf[40]; + std::snprintf(buf, sizeof(buf), "\x1b[%d;5;%dm", bg[k & 1], 232 + color[k]); + last_color[k] = color[k]; + bar += buf; + } + } + bar += "\u258C"; + } +#else + static char const table[] = {' ', '\xb0', '\xb1', '\xb2', '\xdb'}; + bar += table[c]; +#endif + } + bar += esc("0"); + bar += "]"; + return bar; +} + +#ifndef _WIN32 +// this function uses the block characters that splits up the glyph in 4 +// segments and provide all combinations of a segment lit or not. This allows us +// to print 4 pieces per character. +std::string piece_matrix(lt::bitfield const& p, int width, int* height) +{ + // print two rows of pieces at a time + int piece = 0; + ++*height; + std::string ret; + ret.reserve((p.size() + width * 2 - 1) / width / 2 * 4); + while (piece < p.size()) + { + for (int i = 0; i < width; ++i) + { + // each character has 4 pieces. store them in a byte to use for lookups + int const c = get_piece(p, piece) + | (get_piece(p, piece+1) << 1) + | (get_piece(p, width*2+piece) << 2) + | (get_piece(p, width*2+piece+1) << 3); + + // we have 4 bits, 16 different combinations + static char const* const chars[] = + { + " ", // no bit is set 0000 + "\u2598", // upper left 0001 + "\u259d", // upper right 0010 + "\u2580", // both top bits 0011 + "\u2596", // lower left 0100 + "\u258c", // both left bits 0101 + "\u259e", // upper right, lower left 0110 + "\u259b", // left and upper sides 0111 + "\u2597", // lower right 1000 + "\u259a", // lower right, upper left 1001 + "\u2590", // right side 1010 + "\u259c", // lower right, top side 1011 + "\u2584", // both lower bits 1100 + "\u2599", // both lower, top left 1101 + "\u259f", // both lower, top right 1110 + "\x1b[7m \x1b[27m" // all bits are set (full block) + }; + + ret += chars[c]; + piece += 2; + } + ret += "\x1b[K\n"; + ++*height; + piece += width * 2; // skip another row, as we've already printed it + } + return ret; +} +#else +// on MS-DOS terminals, we only have block characters for upper half and lower +// half. This lets us print two pieces per character. +std::string piece_matrix(lt::bitfield const& p, int width, int* height) +{ + // print two rows of pieces at a time + int piece = 0; + ++*height; + std::string ret; + ret.reserve((p.size() + width * 2 - 1) / width); + while (piece < p.size()) + { + for (int i = 0; i < width; ++i) + { + // each character has 8 pieces. store them in a byte to use for lookups + // the ordering of these bits + int const c = get_piece(p, piece) + | (get_piece(p, width*2+piece) << 1); + + static char const* const chars[] = + { + " ", // no piece 00 + "\xdf", // top piece 01 + "\xdc", // bottom piece 10 + "\xdb" // both pieces 11 + }; + + ret += chars[c]; + ++piece; + } + ret += '\n'; + ++*height; + piece += width * 2; // skip another row, as we've already printed it + } + return ret; +} +#endif + +void set_cursor_pos(int x, int y) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + COORD c = {SHORT(x), SHORT(y)}; + SetConsoleCursorPosition(out, c); +#else + std::printf("\033[%d;%dH", y + 1, x + 1); +#endif +} + +void clear_screen() +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + COORD c = {0, 0}; + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + DWORD n; + FillConsoleOutputCharacter(out, ' ', si.dwSize.X * si.dwSize.Y, c, &n); + FillConsoleOutputAttribute(out, 0x7, si.dwSize.X * si.dwSize.Y, c, &n); +#else + std::printf("\033[2J"); +#endif +} + +void clear_rows(int y1, int y2) +{ + if (y1 > y2) return; + +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + COORD c = {0, SHORT(y1)}; + SetConsoleCursorPosition(out, c); + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + DWORD n; + int num_chars = si.dwSize.X * (std::min)(si.dwSize.Y - y1, y2 - y1); + FillConsoleOutputCharacter(out, ' ', num_chars, c, &n); + FillConsoleOutputAttribute(out, 0x7, num_chars, c, &n); +#else + for (int i = y1; i < y2; ++i) + std::printf("\033[%d;1H\033[2K", i + 1); +#endif +} + +std::pair terminal_size() +{ + int width = 80; + int height = 50; +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO coninfo; + if (GetConsoleScreenBufferInfo(out, &coninfo)) + { + width = coninfo.dwSize.X; + height = coninfo.srWindow.Bottom - coninfo.srWindow.Top; +#else + int tty = open("/dev/tty", O_RDONLY); + if (tty < 0) + { + width = 190; + height = 100; + return {width, height}; + } + winsize size; + int ret = ioctl(tty, TIOCGWINSZ, reinterpret_cast(&size)); + close(tty); + if (ret == 0) + { + width = size.ws_col; + height = size.ws_row; +#endif + + if (width < 64) + width = 64; + if (height < 25) + height = 25; + } + else + { + width = 190; + height = 100; + } + return {width, height}; +} + +#ifdef _WIN32 +void apply_ansi_code(WORD* attributes, bool* reverse, bool* support_chaining, int code) +{ + static const WORD color_table[8] = + { + 0, // black + FOREGROUND_RED, // red + FOREGROUND_GREEN, // green + FOREGROUND_RED | FOREGROUND_GREEN, // yellow + FOREGROUND_BLUE, // blue + FOREGROUND_RED | FOREGROUND_BLUE, // magenta + FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // white + }; + + enum + { + foreground_mask = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + background_mask = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY + }; + + static const int fg_mask[2] = {foreground_mask, background_mask}; + static const int bg_mask[2] = {background_mask, foreground_mask}; + static const int fg_shift[2] = { 0, 4}; + static const int bg_shift[2] = { 4, 0}; + + // default foreground + if (code == 39) code = 37; + + // default background + if (code == 49) code = 40; + + if (code == 0) + { + // reset + *attributes = color_table[7]; + *reverse = false; + *support_chaining = true; + } + else if (code == 1) + { + // intensity + *attributes |= *reverse ? BACKGROUND_INTENSITY : FOREGROUND_INTENSITY; + *support_chaining = true; + } + else if (code == 7) + { + // reverse video + *support_chaining = true; + if (*reverse) return; + *reverse = true; + int fg_col = *attributes & foreground_mask; + int bg_col = (*attributes & background_mask) >> 4; + *attributes &= ~(foreground_mask + background_mask); + *attributes |= fg_col << 4; + *attributes |= bg_col; + } + else if (code >= 30 && code <= 37) + { + // foreground color + *attributes &= ~fg_mask[*reverse]; + *attributes |= color_table[code - 30] << fg_shift[*reverse]; + *support_chaining = true; + } + else if (code >= 40 && code <= 47) + { + // background color + *attributes &= ~bg_mask[*reverse]; + *attributes |= color_table[code - 40] << bg_shift[*reverse]; + *support_chaining = true; + } +} +#endif +void print(char const* buf) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + WORD current_attributes = 7; + bool reverse = false; + SetConsoleTextAttribute(out, current_attributes); + + char const* start = buf; + DWORD written; + while (*buf != 0) + { + if (*buf == '\033' && buf[1] == '[') + { + WriteFile(out, start, DWORD(buf - start), &written, nullptr); + buf += 2; // skip escape and '[' + start = buf; + if (*buf == 0) break; + if (*start == 'K') + { + // this means clear the rest of the line. + CONSOLE_SCREEN_BUFFER_INFO sbi; + if (GetConsoleScreenBufferInfo(out, &sbi)) + { + COORD const pos = sbi.dwCursorPosition; + int const width = sbi.dwSize.X; + int const run = width - pos.X; + DWORD n; + FillConsoleOutputAttribute(out, 0x7, run, pos, &n); + FillConsoleOutputCharacter(out, ' ', run, pos, &n); + } + ++buf; + start = buf; + continue; + } + else if (*start == 'J') + { + // clear rest of screen + CONSOLE_SCREEN_BUFFER_INFO sbi; + if (GetConsoleScreenBufferInfo(out, &sbi)) + { + COORD pos = sbi.dwCursorPosition; + int width = sbi.dwSize.X; + int run = (width - pos.X) + width * (sbi.dwSize.Y - pos.Y - 1); + DWORD n; + FillConsoleOutputAttribute(out, 0x7, run, pos, &n); + FillConsoleOutputCharacter(out, ' ', run, pos, &n); + } + ++buf; + start = buf; + continue; + } +one_more: + while (*buf != 'm' && *buf != ';' && *buf != 0) ++buf; + + // this is where we handle reset, color and reverse codes + if (*buf == 0) break; + int code = atoi(start); + bool support_chaining = false; + apply_ansi_code(¤t_attributes, &reverse, &support_chaining, code); + if (support_chaining) + { + if (*buf == ';') + { + ++buf; + start = buf; + goto one_more; + } + } + else + { + // ignore codes with multiple fields for now + while (*buf != 'm' && *buf != 0) ++buf; + } + SetConsoleTextAttribute(out, current_attributes); + ++buf; // skip 'm' + start = buf; + } + else + { + ++buf; + } + } + WriteFile(out, start, DWORD(buf - start), &written, nullptr); + +#else + fputs(buf, stdout); +#endif +} diff --git a/examples/print.hpp b/examples/print.hpp new file mode 100644 index 0000000..f8a0430 --- /dev/null +++ b/examples/print.hpp @@ -0,0 +1,53 @@ +#ifndef PRINT_HPP_ +#define PRINT_HPP_ + +#include +#include // for snprintf +#include // for PRId64 et.al. +#include "libtorrent/bitfield.hpp" + +enum color_code +{ + col_none = -1, + col_black = 0, + col_red = 1, + col_green = 2, + col_yellow = 3, + col_blue = 4, + col_magenta = 5, + col_cyan = 6, + col_white = 7 +}; + +char const* esc(char const* code); + +std::string to_string(int v, int width); + +std::string add_suffix_float(double val, char const* suffix); + +template std::string add_suffix(T val, char const* suffix = nullptr) { + return add_suffix_float(double(val), suffix); +} + +std::string color(std::string const& s, color_code c); + +enum { progress_invert = 1}; + +std::string const& progress_bar(int progress, int width, color_code c = col_green + , char fill = '#', char bg = '-', std::string caption = "", int flags = 0); + +std::string const& piece_bar(lt::bitfield const& p, int width); + +void set_cursor_pos(int x, int y); + +void clear_screen(); + +void clear_rows(int y1, int y2); + +std::pair terminal_size(); +std::string piece_matrix(lt::bitfield const& p, int width, int* height); + +void print(char const* str); + +#endif // PRINT_HPP_ + diff --git a/examples/session_view.cpp b/examples/session_view.cpp new file mode 100644 index 0000000..f90a563 --- /dev/null +++ b/examples/session_view.cpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "session_view.hpp" +#include "print.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/torrent_handle.hpp" + +#include // for std::max + +using lt::span; + +session_view::session_view() + : m_position(0) +{ + using lt::find_metric_idx; + + m_width = 128; + + std::vector metrics = lt::session_stats_metrics(); + m_cnt[0].resize(metrics.size(), 0); + m_cnt[1].resize(metrics.size(), 0); +} + +void session_view::set_pos(int pos) +{ + m_position = pos; +} + +void session_view::set_width(int width) +{ + m_width = width; +} + +int session_view::pos() const { return m_position; } + +int session_view::height() const +{ + return 3; +} + +void session_view::render() +{ + char str[1024]; + int pos = 0; + + int y = m_position; + + float const seconds = (m_timestamp[0] - m_timestamp[1]) / 1000000.f; + + int const download_rate = int((m_cnt[0][m_recv_idx] - m_cnt[1][m_recv_idx]) + / seconds); + int const upload_rate = int((m_cnt[0][m_sent_idx] - m_cnt[1][m_sent_idx]) + / seconds); + + pos += std::snprintf(str, sizeof(str), "%s%s fail: %s down: %s (%s) " + " bw queue: %s | %s conns: %3d unchoked: %2d / %2d queued-trackers: %02d%*s\x1b[K" + , esc("48;5;238") + , esc("1") + , add_suffix(m_cnt[0][m_failed_bytes_idx]).c_str() + , color(add_suffix(download_rate, "/s"), col_green).c_str() + , color(add_suffix(m_cnt[0][m_recv_idx]), col_green).c_str() + , color(to_string(int(m_cnt[0][m_limiter_up_queue_idx]), 3), col_red).c_str() + , color(to_string(int(m_cnt[0][m_limiter_down_queue_idx]), 3), col_green).c_str() + , int(m_cnt[0][m_num_peers_idx]) + , int(m_cnt[0][m_unchoked_idx]) + , int(m_cnt[0][m_unchoke_slots_idx]) + , int(m_cnt[0][m_queued_tracker_announces]) + , std::max(0, m_width - 86) + , esc("0")); + + set_cursor_pos(0, y++); + print(str); + + std::snprintf(str, sizeof(str), "%s%swaste: %s up: %s (%s) " + "disk queue: %s | %s cache w: %3d%% r: %3d%% " + "size: w: %s r: %s total: %s %*s\x1b[K" +#ifdef _WIN32 + , esc("40") +#else + , esc("48;5;238") +#endif + , esc("1") + , add_suffix(m_cnt[0][m_wasted_bytes_idx]).c_str() + , color(add_suffix(upload_rate, "/s"), col_red).c_str() + , color(add_suffix(m_cnt[0][m_sent_idx]), col_red).c_str() + , color(to_string(int(m_cnt[0][m_queued_reads_idx]), 3), col_red).c_str() + , color(to_string(int(m_cnt[0][m_queued_writes_idx]), 3), col_green).c_str() + , int((m_cnt[0][m_blocks_written_idx] - m_cnt[0][m_write_ops_idx]) * 100 + / std::max(std::int64_t(1), m_cnt[0][m_blocks_written_idx])) + , int(m_cnt[0][m_cache_hit_idx] * 100 + / std::max(std::int64_t(1), m_cnt[0][m_num_blocks_read_idx])) + , add_suffix(m_cnt[0][m_writes_cache_idx] * 16 * 1024).c_str() + , add_suffix(m_cnt[0][m_reads_cache_idx] * 16 * 1024).c_str() + , add_suffix(m_cnt[0][m_blocks_in_use_idx] * 16 * 1024).c_str() + , std::max(0, m_width - 119) + , esc("0")); + set_cursor_pos(0, y++); + print(str); + + std::snprintf(str, sizeof(str), "%s%suTP idle: %d syn: %d est: %d fin: %d wait: %d%*s\x1b[K" + , esc("48;5;238") + , esc("1") + , int(m_cnt[0][m_utp_idle]) + , int(m_cnt[0][m_utp_syn_sent]) + , int(m_cnt[0][m_utp_connected]) + , int(m_cnt[0][m_utp_fin_sent]) + , int(m_cnt[0][m_utp_close_wait]) + , int(m_width - 37) + , esc("0")); + set_cursor_pos(0, y++); + print(str); +} + +void session_view::update_counters(span stats_counters + , std::uint64_t const t) +{ + // only update the previous counters if there's been enough + // time since it was last updated + if (t - m_timestamp[1] > 2000000) + { + m_cnt[1].swap(m_cnt[0]); + m_timestamp[1] = m_timestamp[0]; + } + + m_cnt[0].assign(stats_counters.begin(), stats_counters.end()); + m_timestamp[0] = t; + render(); +} + diff --git a/examples/session_view.hpp b/examples/session_view.hpp new file mode 100644 index 0000000..1be0bca --- /dev/null +++ b/examples/session_view.hpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SESSION_VIEW_HPP_ +#define SESSION_VIEW_HPP_ + +#include +#include + +#include "libtorrent/session_stats.hpp" +#include "libtorrent/span.hpp" + +struct session_view +{ + session_view(); + + void set_pos(int pos); + void set_width(int width); + + int pos() const; + + int height() const; + + void render(); + + void update_counters(lt::span stats_counters, std::uint64_t t); + +private: + + int m_position; + int m_width; + + // there are two sets of counters. the current one and the last one. This + // is used to calculate rates + std::vector m_cnt[2]; + + // the timestamps of the counters in m_cnt[0] and m_cnt[1] + // respectively. The timestamps are microseconds since session start + std::uint64_t m_timestamp[2]; + + int const m_queued_bytes_idx = lt::find_metric_idx("disk.queued_write_bytes"); + int const m_wasted_bytes_idx = lt::find_metric_idx("net.recv_redundant_bytes"); + int const m_failed_bytes_idx = lt::find_metric_idx("net.recv_failed_bytes"); + int const m_num_peers_idx = lt::find_metric_idx("peer.num_peers_connected"); + int const m_recv_idx = lt::find_metric_idx("net.recv_bytes"); + int const m_sent_idx = lt::find_metric_idx("net.sent_bytes"); + int const m_unchoked_idx = lt::find_metric_idx("peer.num_peers_up_unchoked"); + int const m_unchoke_slots_idx = lt::find_metric_idx("ses.num_unchoke_slots"); + int const m_limiter_up_queue_idx = lt::find_metric_idx("net.limiter_up_queue"); + int const m_limiter_down_queue_idx = lt::find_metric_idx("net.limiter_down_queue"); + int const m_queued_writes_idx = lt::find_metric_idx("disk.num_write_jobs"); + int const m_queued_reads_idx = lt::find_metric_idx("disk.num_read_jobs"); + + int const m_writes_cache_idx = lt::find_metric_idx("disk.write_cache_blocks"); + int const m_reads_cache_idx = lt::find_metric_idx("disk.read_cache_blocks"); + int const m_pinned_idx = lt::find_metric_idx("disk.pinned_blocks"); + int const m_num_blocks_read_idx = lt::find_metric_idx("disk.num_blocks_read"); + int const m_cache_hit_idx = lt::find_metric_idx("disk.num_blocks_cache_hits"); + int const m_blocks_in_use_idx = lt::find_metric_idx("disk.disk_blocks_in_use"); + int const m_blocks_written_idx = lt::find_metric_idx("disk.num_blocks_written"); + int const m_write_ops_idx = lt::find_metric_idx("disk.num_write_ops"); + + int const m_mfu_size_idx = lt::find_metric_idx("disk.arc_mfu_size"); + int const m_mfu_ghost_idx = lt::find_metric_idx("disk.arc_mfu_ghost_size"); + int const m_mru_size_idx = lt::find_metric_idx("disk.arc_mru_size"); + int const m_mru_ghost_idx = lt::find_metric_idx("disk.arc_mru_ghost_size"); + + int const m_utp_idle = lt::find_metric_idx("utp.num_utp_idle"); + int const m_utp_syn_sent = lt::find_metric_idx("utp.num_utp_syn_sent"); + int const m_utp_connected = lt::find_metric_idx("utp.num_utp_connected"); + int const m_utp_fin_sent = lt::find_metric_idx("utp.num_utp_fin_sent"); + int const m_utp_close_wait = lt::find_metric_idx("utp.num_utp_close_wait"); + + int const m_queued_tracker_announces = lt::find_metric_idx("tracker.num_queued_tracker_announces"); +}; + +#endif + diff --git a/examples/simple_client.cpp b/examples/simple_client.cpp new file mode 100644 index 0000000..f3bf003 --- /dev/null +++ b/examples/simple_client.cpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_info.hpp" + +#include + +int main(int argc, char* argv[]) try +{ + if (argc != 2) { + std::cerr << "usage: ./simple_client torrent-file\n" + "to stop the client, press return.\n"; + return 1; + } + + lt::session s; + lt::add_torrent_params p; + p.save_path = "./"; + p.ti = std::make_shared(argv[1]); + s.add_torrent(p); + + // wait for the user to end + char a; + int ret = std::scanf("%c\n", &a); + (void)ret; // ignore + return 0; +} +catch (std::exception const& e) { + std::cerr << "ERROR: " << e.what() << "\n"; +} diff --git a/examples/stats_counters.cpp b/examples/stats_counters.cpp new file mode 100644 index 0000000..a4bd287 --- /dev/null +++ b/examples/stats_counters.cpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session_stats.hpp" +#include // for snprintf +#include // for PRId64 et.al. + +using namespace lt; + +int main() +{ + std::vector m = session_stats_metrics(); + for (auto const& c : m) + { + std::printf("%s: %s (%d)\n" + , c.type == metric_type_t::counter ? "CNTR" : "GAUG" + , c.name, c.value_index); + } + return 0; +} + diff --git a/examples/torrent_view.cpp b/examples/torrent_view.cpp new file mode 100644 index 0000000..7a2434a --- /dev/null +++ b/examples/torrent_view.cpp @@ -0,0 +1,477 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "torrent_view.hpp" +#include "print.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/torrent_info.hpp" + +#include + +const int header_size = 2; +using lt::queue_position_t; + +std::string torrent_state(lt::torrent_status const& s) +{ + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + + if (s.errc) return s.errc.message(); + std::string ret; + if ((s.flags & lt::torrent_flags::paused) && + (s.flags & lt::torrent_flags::auto_managed)) + { + ret += "queued "; + } + + if ((s.flags & lt::torrent_flags::upload_mode)) ret += "upload mode"; + else ret += state_str[s.state]; + + if (!(s.flags & lt::torrent_flags::auto_managed)) + { + if (s.flags & lt::torrent_flags::paused) + ret += " [P]"; + else + ret += " [F]"; + } + char buf[10]; + std::snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.f); + ret += buf; + return ret; +} + +bool compare_torrent(lt::torrent_status const* lhs, lt::torrent_status const* rhs) +{ + if (lhs->queue_position != queue_position_t{-1} && rhs->queue_position != queue_position_t{-1}) + { + // both are downloading, sort by queue pos + return lhs->queue_position < rhs->queue_position; + } + else if (lhs->queue_position == queue_position_t{-1} + && rhs->queue_position == queue_position_t{-1}) + { + // both are seeding, sort by seed-rank + if (lhs->seed_rank != rhs->seed_rank) + return lhs->seed_rank > rhs->seed_rank; + + return lhs->info_hash < rhs->info_hash; + } + + return (lhs->queue_position == queue_position_t{-1}) + < (rhs->queue_position == queue_position_t{-1}); +} + +torrent_view::torrent_view() + : m_active_torrent(0) + , m_scroll_position(0) + , m_torrent_filter(0) + , m_width(80) + , m_height(30) +{} + +void torrent_view::set_size(int width, int height) +{ + if (m_width == width && m_height == height) return; + + m_width = width; + m_height = height; + render(); +} + +int torrent_view::filter() const +{ + return m_torrent_filter; +} + +void torrent_view::set_filter(int filter) +{ + if (filter == m_torrent_filter) return; + m_torrent_filter = filter; + + update_filtered_torrents(); + render(); +} + +// returns the lt::torrent_status of the currently selected torrent. +lt::torrent_status const& torrent_view::get_active_torrent() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + return *m_filtered_handles[m_active_torrent]; +} + +lt::torrent_handle torrent_view::get_active_handle() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + if (m_filtered_handles.empty()) return lt::torrent_handle(); + + return m_filtered_handles[m_active_torrent]->handle; +} + +void torrent_view::remove_torrent(lt::torrent_handle h) +{ + auto i = m_all_handles.find(h); + if (i == m_all_handles.end()) return; + bool need_rerender = false; + if (show_torrent(i->second)) + { + auto j = std::find(m_filtered_handles.begin(), m_filtered_handles.end() + , &i->second); + if (j != m_filtered_handles.end()) + { + m_filtered_handles.erase(j); + need_rerender = true; + } + } + m_all_handles.erase(i); + if (need_rerender) render(); +} + +void torrent_view::update_torrents(std::vector st) +{ + std::set updates; + bool need_filter_update = false; + for (lt::torrent_status& t : st) + { + auto j = m_all_handles.find(t.handle); + // add new entries here + if (j == m_all_handles.end()) + { + auto handle = t.handle; + j = m_all_handles.emplace(handle, std::move(t)).first; + if (show_torrent(j->second)) + { + m_filtered_handles.push_back(&j->second); + need_filter_update = true; + } + } + else + { + bool const prev_show = show_torrent(j->second); + j->second = std::move(t); + if (prev_show != show_torrent(j->second)) + need_filter_update = true; + else + updates.insert(j->second.handle); + } + } + if (need_filter_update) + { + update_filtered_torrents(); + render(); + } + else + { + int torrent_index = 0; + for (auto i = m_filtered_handles.begin(); + i != m_filtered_handles.end(); ++i) + { + if (torrent_index < m_scroll_position + || torrent_index >= m_scroll_position + m_height - header_size) + { + ++torrent_index; + continue; + } + + lt::torrent_status const& s = **i; + + if (!s.handle.is_valid()) + continue; + + if (updates.count(s.handle) == 0) + { + ++torrent_index; + continue; + } + + set_cursor_pos(0, header_size + torrent_index - m_scroll_position); + print_torrent(s, torrent_index == m_active_torrent); + ++torrent_index; + } + } +} + +int torrent_view::height() const +{ + return m_height; +} + +void torrent_view::arrow_up() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent <= 0) return; + + if (m_active_torrent - 1 < m_scroll_position) + { + --m_active_torrent; + m_scroll_position = m_active_torrent; + TORRENT_ASSERT(m_scroll_position >= 0); + render(); + return; + } + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], false); + --m_active_torrent; + TORRENT_ASSERT(m_active_torrent >= 0); + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], true); +} + +void torrent_view::arrow_down() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent >= int(m_filtered_handles.size()) - 1) return; + + int bottom_pos = m_height - header_size - 1; + if (m_active_torrent - m_scroll_position + 1 > bottom_pos) + { + ++m_active_torrent; + m_scroll_position = m_active_torrent - bottom_pos; + TORRENT_ASSERT(m_scroll_position >= 0); + render(); + return; + } + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], false); + + TORRENT_ASSERT(m_active_torrent >= 0); + ++m_active_torrent; + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], true); +} + +void torrent_view::render() +{ + print_tabs(); + print_headers(); + + int lines_printed = header_size; + + int torrent_index = 0; + + for (std::vector::iterator i = m_filtered_handles.begin(); + i != m_filtered_handles.end();) + { + if (torrent_index < m_scroll_position) + { + ++i; + ++torrent_index; + continue; + } + if (lines_printed >= m_height) + break; + + lt::torrent_status const& s = **i; + if (!s.handle.is_valid()) + { + i = m_filtered_handles.erase(i); + continue; + } + ++i; + + set_cursor_pos(0, torrent_index + header_size - m_scroll_position); + print_torrent(s, torrent_index == m_active_torrent); + ++lines_printed; + ++torrent_index; + } + + clear_rows(torrent_index + header_size, m_height); +} + +void torrent_view::print_tabs() +{ + set_cursor_pos(0, 0); + + std::array str; + lt::span dest(str); + static std::array const filter_names{{ "all", "downloading", "non-paused" + , "seeding", "queued", "stopped", "checking"}}; + for (int i = 0; i < int(filter_names.size()); ++i) + { + int const ret = std::snprintf(dest.data(), dest.size(), "%s[%s]%s" + , m_torrent_filter == i?esc("7"):"" + , filter_names[i], m_torrent_filter == i?esc("0"):""); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + } + int const ret = std::snprintf(dest.data(), dest.size(), "\x1b[K"); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + + if (m_width + 1 < int(str.size())) + str[m_width + 1] = '\0'; + print(str.data()); +} + +void torrent_view::print_headers() +{ + set_cursor_pos(0, 1); + + std::array str; + + // print title bar for torrent list + std::snprintf(str.data(), str.size() + , " %-3s %-50s %-35s %-14s %-17s %-17s %-11s %-6s %-6s %-4s\x1b[K" + , "#", "Name", "Progress", "Pieces", "Download", "Upload", "Peers (D:S)" + , "Down", "Up", "Flags"); + + if (m_width + 1 < int(str.size())) + str[m_width + 1] = '\0'; + + print(str.data()); +} + +void torrent_view::print_torrent(lt::torrent_status const& s, bool selected) +{ + std::array str; + lt::span dest(str); + + // the active torrent is highligted in the list + // this inverses the forground and background colors + char const* selection = ""; + if (selected) + selection = "\x1b[1m\x1b[44m"; + + char queue_pos[16] = {0}; + if (s.queue_position == queue_position_t{-1}) + std::snprintf(queue_pos, sizeof(queue_pos), "-"); + else + std::snprintf(queue_pos, sizeof(queue_pos), "%d" + , static_cast(s.queue_position)); + + std::string name = s.name; + if (name.size() > 50) name.resize(50); + + color_code progress_bar_color = col_yellow; + if (s.errc) progress_bar_color = col_red; + else if (s.flags & lt::torrent_flags::paused) progress_bar_color = col_blue; + else if (s.state == lt::torrent_status::downloading_metadata) + progress_bar_color = col_magenta; + else if (s.current_tracker.empty()) + progress_bar_color = col_green; + + auto ti = s.torrent_file.lock(); + int const total_pieces = ti && ti->is_valid() ? ti->num_pieces() : 0; + color_code piece_color = total_pieces == s.num_pieces ? col_green : col_yellow; + + int const ret = std::snprintf(dest.data(), dest.size(), "%s%-3s %-50s %s%s %s/%s %s (%s) " + "%s (%s) %5d:%-5d %s %s %c" + , selection + , queue_pos + , name.c_str() + , progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str() + , selection + , color(to_string(s.num_pieces, 6), piece_color).c_str() + , color(to_string(total_pieces, 6), piece_color).c_str() + , color(add_suffix(s.download_rate, "/s"), col_green).c_str() + , color(add_suffix(s.total_download), col_green).c_str() + , color(add_suffix(s.upload_rate, "/s"), col_red).c_str() + , color(add_suffix(s.total_upload), col_red).c_str() + , s.num_peers - s.num_seeds, s.num_seeds + , color(add_suffix(s.all_time_download), col_green).c_str() + , color(add_suffix(s.all_time_upload), col_red).c_str() + , s.need_save_resume?'S':' '); + if (ret >= 0 && ret <= dest.size()) dest = dest.subspan(ret); + + // if this is the selected torrent, restore the background color + if (selected) + { + int const ret2 = std::snprintf(dest.data(), dest.size(), "%s", esc("0")); + if (ret2 >= 0 && ret2 <= dest.size()) dest = dest.subspan(ret2); + } + + int const ret2 = std::snprintf(dest.data(), dest.size(), "\x1b[K"); + if (ret2 >= 0 && ret2 <= dest.size()) dest = dest.subspan(ret2); + + print(str.data()); +} + +bool torrent_view::show_torrent(lt::torrent_status const& st) +{ + switch (m_torrent_filter) + { + case torrents_all: return true; + case torrents_downloading: + return !(st.flags & lt::torrent_flags::paused) + && st.state != lt::torrent_status::seeding + && st.state != lt::torrent_status::finished; + case torrents_not_paused: + return !(st.flags & lt::torrent_flags::paused); + case torrents_seeding: + return !(st.flags & lt::torrent_flags::paused) + && (st.state == lt::torrent_status::seeding + || st.state == lt::torrent_status::finished); + case torrents_queued: + return (st.flags & lt::torrent_flags::paused) + && (st.flags & lt::torrent_flags::auto_managed); + case torrents_stopped: + return (st.flags & lt::torrent_flags::paused) + && !(st.flags & lt::torrent_flags::auto_managed); + case torrents_checking: return st.state == lt::torrent_status::checking_files; + } + return true; +} + +// refresh all pointers in m_filtered_handles. This must be done when +// inserting or removing elements from m_all_handles, since pointers may +// be invalidated or when a torrent changes status to either become +// visible or filtered +void torrent_view::update_filtered_torrents() +{ + m_filtered_handles.clear(); + for (auto const& h : m_all_handles) + { + if (!show_torrent(h.second)) continue; + m_filtered_handles.push_back(&h.second); + } + if (m_active_torrent >= int(m_filtered_handles.size())) m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + std::sort(m_filtered_handles.begin(), m_filtered_handles.end(), &compare_torrent); + if (m_scroll_position + m_height - header_size > int(m_filtered_handles.size())) + { + m_scroll_position = std::max(0, int(m_filtered_handles.size()) - m_height + header_size); + } +} + diff --git a/examples/torrent_view.hpp b/examples/torrent_view.hpp new file mode 100644 index 0000000..f1248ac --- /dev/null +++ b/examples/torrent_view.hpp @@ -0,0 +1,111 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VIEW_HPP_ +#define TORRENT_VIEW_HPP_ + +#include +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/torrent_status.hpp" + + +struct torrent_view +{ + torrent_view(); + + void set_size(int width, int height); + + enum { + torrents_all, + torrents_downloading, + torrents_not_paused, + torrents_seeding, + torrents_queued, + torrents_stopped, + torrents_checking, + + torrents_max + }; + + int filter() const; + + void set_filter(int filter); + + // returns the lt::torrent_status of the currently selected torrent. + lt::torrent_status const& get_active_torrent() const; + lt::torrent_handle get_active_handle() const; + + void remove_torrent(lt::torrent_handle st); + void update_torrents(std::vector st); + int num_visible_torrents() const { return int(m_filtered_handles.size()); } + + int height() const; + + void arrow_up(); + void arrow_down(); + + void render(); + +private: + + void print_tabs(); + + void print_headers(); + + void print_torrent(lt::torrent_status const& s, bool selected); + + bool show_torrent(lt::torrent_status const& st); + + // refresh all pointers in m_filtered_handles. This must be done when + // inserting or removing elements from m_all_handles, since pointers may + // be invalidated or when a torrent changes status to either become + // visible or filtered + void update_filtered_torrents(); + + // all torrents + std::unordered_map m_all_handles; + + // pointers into m_all_handles of the remaining torrents after filtering + std::vector m_filtered_handles; + + mutable int m_active_torrent; // index into m_filtered_handles + int m_scroll_position; + int m_torrent_filter; + int m_width; + int m_height; +}; + +#endif // TORRENT_VIEW_HPP_ + diff --git a/examples/upnp_test.cpp b/examples/upnp_test.cpp new file mode 100644 index 0000000..78700eb --- /dev/null +++ b/examples/upnp_test.cpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2003, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" + +namespace { +char const* timestamp() +{ + std::time_t t = std::time(nullptr); + std::tm* timeinfo = std::localtime(&t); + static char str[200]; + std::strftime(str, 200, "%b %d %X", timeinfo); + return str; +} + +void print_alert(lt::alert const* a) +{ + using namespace lt; + + if (alert_cast(a)) + { + std::printf("%s","\x1b[32m"); + } + else if (alert_cast(a)) + { + std::printf("%s","\x1b[33m"); + } + + std::printf("[%s] %s\n", timestamp(), a->message().c_str()); + std::printf("%s", "\x1b[0m"); +} +} // anonymous namespace + +int main(int argc, char*[]) +{ + using namespace lt; + + if (argc != 1) + { + fputs("usage: ./upnp_test\n", stderr); + return 1; + } + + settings_pack p; + p.set_int(settings_pack::alert_mask, alert_category::port_mapping); + lt::session s(p); + + for (;;) + { + alert const* a = s.wait_for_alert(seconds(5)); + if (a == nullptr) + { + p.set_bool(settings_pack::enable_upnp, false); + p.set_bool(settings_pack::enable_natpmp, false); + s.apply_settings(p); + break; + } + std::vector alerts; + s.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + print_alert(*i); + } + } + + std::printf("\x1b[1m\n\n===================== done mapping. Now deleting mappings ========================\n\n\n\x1b[0m"); + + for (;;) + { + alert const* a = s.wait_for_alert(seconds(5)); + if (a == nullptr) break; + std::vector alerts; + s.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + print_alert(*i); + } + } + + + return 0; +} + diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am new file mode 100644 index 0000000..a2b37af --- /dev/null +++ b/include/libtorrent/Makefile.am @@ -0,0 +1,251 @@ +includedir = @includedir@/libtorrent + +nobase_include_HEADERS = \ + address.hpp \ + add_torrent_params.hpp \ + alert.hpp \ + alert_manager.hpp \ + alert_types.hpp \ + announce_entry.hpp \ + assert.hpp \ + bandwidth_limit.hpp \ + bandwidth_manager.hpp \ + bandwidth_socket.hpp \ + bandwidth_queue_entry.hpp \ + bencode.hpp \ + bdecode.hpp \ + bitfield.hpp \ + block_cache.hpp \ + bloom_filter.hpp \ + broadcast_socket.hpp \ + bt_peer_connection.hpp \ + buffer.hpp \ + chained_buffer.hpp \ + choker.hpp \ + close_reason.hpp \ + config.hpp \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disk_buffer_holder.hpp \ + disk_buffer_pool.hpp \ + disk_interface.hpp \ + disk_io_job.hpp \ + disk_io_thread.hpp \ + disk_io_thread_pool.hpp \ + disk_observer.hpp \ + disk_job_pool.hpp \ + ed25519.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + extensions.hpp \ + file.hpp \ + file_pool.hpp \ + file_storage.hpp \ + fingerprint.hpp \ + flags.hpp \ + fwd.hpp \ + gzip.hpp \ + hasher.hpp \ + hasher512.hpp \ + hex.hpp \ + heterogeneous_queue.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + invariant_check.hpp \ + io.hpp \ + io_service.hpp \ + io_service_fwd.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + lazy_entry.hpp \ + link.hpp \ + linked_list.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + natpmp.hpp \ + netlink.hpp \ + operations.hpp \ + optional.hpp \ + packet_buffer.hpp \ + packet_pool.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + performance_counters.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_request.hpp \ + pex_flags.hpp \ + piece_block.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + peer_list.hpp \ + portmap.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + read_resume_data.hpp \ + write_resume_data.hpp \ + receive_buffer.hpp \ + resolve_links.hpp \ + resolver.hpp \ + resolver_interface.hpp \ + request_blocks.hpp \ + session.hpp \ + session_handle.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + session_types.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha512.hpp \ + sha1_hash.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socks5_stream.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + tailqueue.hpp \ + string_view.hpp \ + string_util.hpp \ + time.hpp \ + timestamp_history.hpp \ + torrent_flags.hpp \ + torrent_handle.hpp \ + torrent.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + tracker_manager.hpp \ + torrent_status.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + union_endpoint.hpp \ + units.hpp \ + upnp.hpp \ + utp_socket_manager.hpp \ + utp_stream.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + xml_parse.hpp \ + span.hpp \ + download_priority.hpp \ + index_range.hpp \ + \ + aux_/allocating_handler.hpp \ + aux_/aligned_storage.hpp \ + aux_/aligned_union.hpp \ + aux_/bind_to_device.hpp \ + aux_/keepalive.hpp \ + aux_/block_cache_reference.hpp \ + aux_/container_wrapper.hpp \ + aux_/cpuid.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/disk_job_fence.hpp \ + aux_/deferred_handler.hpp \ + aux_/deprecated.hpp \ + aux_/dev_random.hpp \ + aux_/deque.hpp \ + aux_/escape_string.hpp \ + aux_/export.hpp \ + aux_/generate_peer_id.hpp \ + aux_/io.hpp \ + aux_/listen_socket_handle.hpp \ + aux_/path.hpp \ + aux_/merkle.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_settings.hpp \ + aux_/session_udp_sockets.hpp \ + aux_/set_socket_buffer.hpp \ + aux_/proxy_settings.hpp \ + aux_/session_interface.hpp \ + aux_/suggest_piece.hpp \ + aux_/socket_type.hpp \ + aux_/storage_piece_set.hpp \ + aux_/string_ptr.hpp \ + aux_/time.hpp \ + aux_/file_progress.hpp \ + aux_/openssl.hpp \ + aux_/byteswap.hpp \ + aux_/route.h \ + aux_/cppint_import_export.hpp \ + aux_/ffs.hpp \ + aux_/portmap.hpp \ + aux_/lsd.hpp \ + aux_/has_block.hpp \ + aux_/scope_end.hpp \ + aux_/vector.hpp \ + aux_/win_crypto_provider.hpp \ + aux_/win_util.hpp \ + aux_/storage_utils.hpp \ + aux_/numeric_cast.hpp \ + aux_/unique_ptr.hpp \ + aux_/alloca.hpp \ + aux_/throw.hpp \ + aux_/array.hpp \ + aux_/ip_notifier.hpp \ + aux_/noexcept_movable.hpp \ + aux_/torrent_impl.hpp \ + aux_/instantiate_connection.hpp \ + aux_/range.hpp \ + aux_/windows.hpp \ + \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/announce_flags.hpp \ + kademlia/dht_settings.hpp \ + kademlia/dht_state.hpp \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/dht_observer.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/find_data.hpp \ + kademlia/io.hpp \ + kademlia/put_data.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/types.hpp \ + kademlia/ed25519.hpp \ + kademlia/item.hpp \ + kademlia/get_item.hpp \ + kademlia/sample_infohashes.hpp \ + kademlia/get_peers.hpp diff --git a/include/libtorrent/Makefile.in b/include/libtorrent/Makefile.in new file mode 100644 index 0000000..d08544e --- /dev/null +++ b/include/libtorrent/Makefile.in @@ -0,0 +1,868 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = include/libtorrent +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(nobase_include_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(includedir)" +HEADERS = $(nobase_include_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@/libtorrent +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +nobase_include_HEADERS = \ + address.hpp \ + add_torrent_params.hpp \ + alert.hpp \ + alert_manager.hpp \ + alert_types.hpp \ + announce_entry.hpp \ + assert.hpp \ + bandwidth_limit.hpp \ + bandwidth_manager.hpp \ + bandwidth_socket.hpp \ + bandwidth_queue_entry.hpp \ + bencode.hpp \ + bdecode.hpp \ + bitfield.hpp \ + block_cache.hpp \ + bloom_filter.hpp \ + broadcast_socket.hpp \ + bt_peer_connection.hpp \ + buffer.hpp \ + chained_buffer.hpp \ + choker.hpp \ + close_reason.hpp \ + config.hpp \ + copy_ptr.hpp \ + crc32c.hpp \ + create_torrent.hpp \ + deadline_timer.hpp \ + debug.hpp \ + disk_buffer_holder.hpp \ + disk_buffer_pool.hpp \ + disk_interface.hpp \ + disk_io_job.hpp \ + disk_io_thread.hpp \ + disk_io_thread_pool.hpp \ + disk_observer.hpp \ + disk_job_pool.hpp \ + ed25519.hpp \ + entry.hpp \ + enum_net.hpp \ + error.hpp \ + error_code.hpp \ + extensions.hpp \ + file.hpp \ + file_pool.hpp \ + file_storage.hpp \ + fingerprint.hpp \ + flags.hpp \ + fwd.hpp \ + gzip.hpp \ + hasher.hpp \ + hasher512.hpp \ + hex.hpp \ + heterogeneous_queue.hpp \ + http_connection.hpp \ + http_parser.hpp \ + http_seed_connection.hpp \ + http_stream.hpp \ + http_tracker_connection.hpp \ + i2p_stream.hpp \ + identify_client.hpp \ + invariant_check.hpp \ + io.hpp \ + io_service.hpp \ + io_service_fwd.hpp \ + ip_filter.hpp \ + ip_voter.hpp \ + lazy_entry.hpp \ + link.hpp \ + linked_list.hpp \ + lsd.hpp \ + magnet_uri.hpp \ + natpmp.hpp \ + netlink.hpp \ + operations.hpp \ + optional.hpp \ + packet_buffer.hpp \ + packet_pool.hpp \ + parse_url.hpp \ + part_file.hpp \ + pe_crypto.hpp \ + performance_counters.hpp \ + peer_connection.hpp \ + peer_connection_handle.hpp \ + peer_connection_interface.hpp \ + peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ + peer_id.hpp \ + peer_info.hpp \ + peer_request.hpp \ + pex_flags.hpp \ + piece_block.hpp \ + piece_block_progress.hpp \ + piece_picker.hpp \ + platform_util.hpp \ + peer_list.hpp \ + portmap.hpp \ + proxy_base.hpp \ + puff.hpp \ + random.hpp \ + read_resume_data.hpp \ + write_resume_data.hpp \ + receive_buffer.hpp \ + resolve_links.hpp \ + resolver.hpp \ + resolver_interface.hpp \ + request_blocks.hpp \ + session.hpp \ + session_handle.hpp \ + session_settings.hpp \ + session_stats.hpp \ + session_status.hpp \ + session_types.hpp \ + settings_pack.hpp \ + sha1.hpp \ + sha512.hpp \ + sha1_hash.hpp \ + sliding_average.hpp \ + socket.hpp \ + socket_io.hpp \ + socks5_stream.hpp \ + ssl_stream.hpp \ + stack_allocator.hpp \ + stat.hpp \ + stat_cache.hpp \ + storage.hpp \ + storage_defs.hpp \ + tailqueue.hpp \ + string_view.hpp \ + string_util.hpp \ + time.hpp \ + timestamp_history.hpp \ + torrent_flags.hpp \ + torrent_handle.hpp \ + torrent.hpp \ + torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ + tracker_manager.hpp \ + torrent_status.hpp \ + udp_socket.hpp \ + udp_tracker_connection.hpp \ + union_endpoint.hpp \ + units.hpp \ + upnp.hpp \ + utp_socket_manager.hpp \ + utp_stream.hpp \ + utf8.hpp \ + vector_utils.hpp \ + version.hpp \ + web_connection_base.hpp \ + web_peer_connection.hpp \ + xml_parse.hpp \ + span.hpp \ + download_priority.hpp \ + index_range.hpp \ + \ + aux_/allocating_handler.hpp \ + aux_/aligned_storage.hpp \ + aux_/aligned_union.hpp \ + aux_/bind_to_device.hpp \ + aux_/keepalive.hpp \ + aux_/block_cache_reference.hpp \ + aux_/container_wrapper.hpp \ + aux_/cpuid.hpp \ + aux_/disable_warnings_push.hpp \ + aux_/disable_warnings_pop.hpp \ + aux_/disk_job_fence.hpp \ + aux_/deferred_handler.hpp \ + aux_/deprecated.hpp \ + aux_/dev_random.hpp \ + aux_/deque.hpp \ + aux_/escape_string.hpp \ + aux_/export.hpp \ + aux_/generate_peer_id.hpp \ + aux_/io.hpp \ + aux_/listen_socket_handle.hpp \ + aux_/path.hpp \ + aux_/merkle.hpp \ + aux_/session_call.hpp \ + aux_/session_impl.hpp \ + aux_/session_settings.hpp \ + aux_/session_udp_sockets.hpp \ + aux_/set_socket_buffer.hpp \ + aux_/proxy_settings.hpp \ + aux_/session_interface.hpp \ + aux_/suggest_piece.hpp \ + aux_/socket_type.hpp \ + aux_/storage_piece_set.hpp \ + aux_/string_ptr.hpp \ + aux_/time.hpp \ + aux_/file_progress.hpp \ + aux_/openssl.hpp \ + aux_/byteswap.hpp \ + aux_/route.h \ + aux_/cppint_import_export.hpp \ + aux_/ffs.hpp \ + aux_/portmap.hpp \ + aux_/lsd.hpp \ + aux_/has_block.hpp \ + aux_/scope_end.hpp \ + aux_/vector.hpp \ + aux_/win_crypto_provider.hpp \ + aux_/win_util.hpp \ + aux_/storage_utils.hpp \ + aux_/numeric_cast.hpp \ + aux_/unique_ptr.hpp \ + aux_/alloca.hpp \ + aux_/throw.hpp \ + aux_/array.hpp \ + aux_/ip_notifier.hpp \ + aux_/noexcept_movable.hpp \ + aux_/torrent_impl.hpp \ + aux_/instantiate_connection.hpp \ + aux_/range.hpp \ + aux_/windows.hpp \ + \ + extensions/smart_ban.hpp \ + extensions/ut_metadata.hpp \ + extensions/ut_pex.hpp \ + \ + kademlia/announce_flags.hpp \ + kademlia/dht_settings.hpp \ + kademlia/dht_state.hpp \ + kademlia/dht_storage.hpp \ + kademlia/dht_tracker.hpp \ + kademlia/dht_observer.hpp \ + kademlia/direct_request.hpp \ + kademlia/dos_blocker.hpp \ + kademlia/find_data.hpp \ + kademlia/io.hpp \ + kademlia/put_data.hpp \ + kademlia/msg.hpp \ + kademlia/node.hpp \ + kademlia/node_entry.hpp \ + kademlia/node_id.hpp \ + kademlia/observer.hpp \ + kademlia/refresh.hpp \ + kademlia/routing_table.hpp \ + kademlia/rpc_manager.hpp \ + kademlia/traversal_algorithm.hpp \ + kademlia/types.hpp \ + kademlia/ed25519.hpp \ + kademlia/item.hpp \ + kademlia/get_item.hpp \ + kademlia/sample_infohashes.hpp \ + kademlia/get_peers.hpp + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/libtorrent/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/libtorrent/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-nobase_includeHEADERS: $(nobase_include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(nobase_include_HEADERS)'; test -n "$(includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \ + fi; \ + $(am__nobase_list) | while read dir files; do \ + xfiles=; for file in $$files; do \ + if test -f "$$file"; then xfiles="$$xfiles $$file"; \ + else xfiles="$$xfiles $(srcdir)/$$file"; fi; done; \ + test -z "$$xfiles" || { \ + test "x$$dir" = x. || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(includedir)/$$dir'"; \ + $(MKDIR_P) "$(DESTDIR)$(includedir)/$$dir"; }; \ + echo " $(INSTALL_HEADER) $$xfiles '$(DESTDIR)$(includedir)/$$dir'"; \ + $(INSTALL_HEADER) $$xfiles "$(DESTDIR)$(includedir)/$$dir" || exit $$?; }; \ + done + +uninstall-nobase_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(nobase_include_HEADERS)'; test -n "$(includedir)" || list=; \ + $(am__nobase_strip_setup); files=`$(am__nobase_strip)`; \ + dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-nobase_includeHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-nobase_includeHEADERS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool cscopelist-am ctags ctags-am distclean \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man \ + install-nobase_includeHEADERS install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic \ + mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-nobase_includeHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp new file mode 100644 index 0000000..d6a36a9 --- /dev/null +++ b/include/libtorrent/add_torrent_params.hpp @@ -0,0 +1,387 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED +#define TORRENT_ADD_TORRENT_PARAMS_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/version.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/bitfield.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/download_priority.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +namespace libtorrent { + + class torrent_info; + struct torrent_plugin; + struct torrent_handle; + +TORRENT_VERSION_NAMESPACE_2 + + // The add_torrent_params is a parameter pack for adding torrents to a + // session. The key fields when adding a torrent are: + // + // * ti - when you have loaded a .torrent file into a torrent_info object + // * info_hash - when you don't have the metadata (.torrent file) but. This + // is set when adding a magnet link. + // + // one of those fields must be set. Another mandatory field is + // ``save_path``. The add_torrent_params object is passed into one of the + // ``session::add_torrent()`` overloads or ``session::async_add_torrent()``. + // + // If you only specify the info-hash, the torrent file will be downloaded + // from peers, which requires them to support the metadata extension. For + // the metadata extension to work, libtorrent must be built with extensions + // enabled (``TORRENT_DISABLE_EXTENSIONS`` must not be defined). It also + // takes an optional ``name`` argument. This may be left empty in case no + // name should be assigned to the torrent. In case it's not, the name is + // used for the torrent as long as it doesn't have metadata. See + // ``torrent_handle::name``. + // + // The ``add_torrent_params`` is also used when requesting resume data for a + // torrent. It can be saved to and restored from a file and added back to a + // new session. For serialization and de-serialization of + // ``add_torrent_params`` objects, see read_resume_data() and + // write_resume_data(). +#include "libtorrent/aux_/disable_warnings_push.hpp" + struct TORRENT_EXPORT add_torrent_params + { + // The constructor can be used to initialize the storage constructor, + // which determines the storage mechanism for the downloaded or seeding + // data for the torrent. For more information, see the ``storage`` field. + explicit add_torrent_params(storage_constructor_type sc = default_storage_constructor); + add_torrent_params(add_torrent_params&&) noexcept; + add_torrent_params& operator=(add_torrent_params&&) = default; + add_torrent_params(add_torrent_params const&); + add_torrent_params& operator=(add_torrent_params const&); + + // These are all deprecated. use torrent_flags_t instead (in + // libtorrent/torrent_flags.hpp) +#if TORRENT_ABI_VERSION == 1 + + using flags_t = torrent_flags_t; + +#define DECL_FLAG(name) \ + static constexpr torrent_flags_t TORRENT_DEPRECATED_MEMBER flag_##name = torrent_flags::name + + DECL_FLAG(seed_mode); + DECL_FLAG(upload_mode); + DECL_FLAG(share_mode); + DECL_FLAG(apply_ip_filter); + DECL_FLAG(paused); + DECL_FLAG(auto_managed); + DECL_FLAG(duplicate_is_error); + DECL_FLAG(update_subscribe); + DECL_FLAG(super_seeding); + DECL_FLAG(sequential_download); + DECL_FLAG(pinned); + DECL_FLAG(stop_when_ready); + DECL_FLAG(override_trackers); + DECL_FLAG(override_web_seeds); + DECL_FLAG(need_save_resume); + DECL_FLAG(override_resume_data); + DECL_FLAG(merge_resume_trackers); + DECL_FLAG(use_resume_save_path); + DECL_FLAG(merge_resume_http_seeds); + DECL_FLAG(default_flags); +#undef DECL_FLAG +#endif // TORRENT_ABI_VERSION + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // filled in by the constructor and should be left untouched. It is used + // for forward binary compatibility. + int version = LIBTORRENT_VERSION_NUM; + + // torrent_info object with the torrent to add. Unless the + // info_hash is set, this is required to be initialized. + std::shared_ptr ti; + + // If the torrent doesn't have a tracker, but relies on the DHT to find + // peers, the ``trackers`` can specify tracker URLs for the torrent. + aux::noexcept_movable> trackers; + + // the tiers the URLs in ``trackers`` belong to. Trackers belonging to + // different tiers may be treated differently, as defined by the multi + // tracker extension. This is optional, if not specified trackers are + // assumed to be part of tier 0, or whichever the last tier was as + // iterating over the trackers. + aux::noexcept_movable> tracker_tiers; + + // a list of hostname and port pairs, representing DHT nodes to be added + // to the session (if DHT is enabled). The hostname may be an IP address. + aux::noexcept_movable>> dht_nodes; + + // in case there's no other name in this torrent, this name will be used. + // The name out of the torrent_info object takes precedence if available. + std::string name; + + // the path where the torrent is or will be stored. + // + // .. note:: + // On windows this path (and other paths) are interpreted as UNC + // paths. This means they must use backslashes as directory separators + // and may not contain the special directories "." or "..". + // + // Setting this to an absolute path performs slightly better than a + // relative path. + std::string save_path; + + // One of the values from storage_mode_t. For more information, see + // storage-allocation_. + storage_mode_t storage_mode = storage_mode_sparse; + + // can be used to customize how the data is stored. The default storage + // will simply write the data to the files it belongs to, but it could be + // overridden to save everything to a single file at a specific location + // or encrypt the content on disk for instance. For more information + // about the storage_interface that needs to be implemented for a custom + // storage, see storage_interface. + aux::noexcept_movable storage; + + // The ``userdata`` parameter is optional and will be passed on to the + // extension constructor functions, if any + // (see torrent_handle::add_extension()). + void* userdata = nullptr; + + // can be set to control the initial file priorities when adding a + // torrent. The semantics are the same as for + // ``torrent_handle::prioritize_files()``. The file priorities specified + // in here take precedence over those specified in the resume data, if + // any. + aux::noexcept_movable> file_priorities; + + // torrent extension construction functions can be added to this vector + // to have them be added immediately when the torrent is constructed. + // This may be desired over the torrent_handle::add_extension() in order + // to avoid race conditions. For instance it may be important to have the + // plugin catch events that happen very early on after the torrent is + // created. + aux::noexcept_movable(torrent_handle const&, void*)>>> + extensions; + + // the default tracker id to be used when announcing to trackers. By + // default this is empty, and no tracker ID is used, since this is an + // optional argument. If a tracker returns a tracker ID, that ID is used + // instead of this. + std::string trackerid; + + // flags controlling aspects of this torrent and how it's added. See + // torrent_flags_t for details. + // + // .. note:: + // The ``flags`` field is initialized with default flags by the + // constructor. In order to preserve default behavior when clearing or + // setting other flags, make sure to bitwise OR or in a flag or bitwise + // AND the inverse of a flag to clear it. + torrent_flags_t flags = torrent_flags::default_flags; + + // set this to the info hash of the torrent to add in case the info-hash + // is the only known property of the torrent. i.e. you don't have a + // .torrent file nor a magnet link. + // To add a magnet link, use parse_magnet_uri() to populate fields in the + // add_torrent_params object. + sha1_hash info_hash; + + // ``max_uploads``, ``max_connections``, ``upload_limit``, + // ``download_limit`` correspond to the ``set_max_uploads()``, + // ``set_max_connections()``, ``set_upload_limit()`` and + // ``set_download_limit()`` functions on torrent_handle. These values let + // you initialize these settings when the torrent is added, instead of + // calling these functions immediately following adding it. + // + // -1 means unlimited on these settings just like their counterpart + // functions on torrent_handle + // + // For fine grained control over rate limits, including making them apply + // to local peers, see peer-classes_. + int max_uploads = -1; + int max_connections = -1; + + // the upload and download rate limits for this torrent, specified in + // bytes per second. -1 means unlimited. + int upload_limit = -1; + int download_limit = -1; + + // the total number of bytes uploaded and downloaded by this torrent so + // far. + std::int64_t total_uploaded = 0; + std::int64_t total_downloaded = 0; + + // the number of seconds this torrent has spent in started, finished and + // seeding state so far, respectively. + int active_time = 0; + int finished_time = 0; + int seeding_time = 0; + + // if set to a non-zero value, this is the posix time of when this torrent + // was first added, including previous runs/sessions. If set to zero, the + // internal added_time will be set to the time of when add_torrent() is + // called. + std::time_t added_time = 0; + std::time_t completed_time = 0; + + // if set to non-zero, initializes the time (expressed in posix time) when + // we last saw a seed or peers that together formed a complete copy of the + // torrent. If left set to zero, the internal counterpart to this field + // will be updated when we see a seed or a distributed copies >= 1.0. + std::time_t last_seen_complete = 0; + + // these field can be used to initialize the torrent's cached scrape data. + // The scrape data is high level metadata about the current state of the + // swarm, as returned by the tracker (either when announcing to it or by + // sending a specific scrape request). ``num_complete`` is the number of + // peers in the swarm that are seeds, or have every piece in the torrent. + // ``num_incomplete`` is the number of peers in the swarm that do not have + // every piece. ``num_downloaded`` is the number of times the torrent has + // been downloaded (not initiated, but the number of times a download has + // completed). + // + // Leaving any of these values set to -1 indicates we don't know, or we + // have not received any scrape data. + int num_complete = -1; + int num_incomplete = -1; + int num_downloaded = -1; + + // URLs can be added to these two lists to specify additional web + // seeds to be used by the torrent. If the ``flag_override_web_seeds`` + // is set, these will be the _only_ ones to be used. i.e. any web seeds + // found in the .torrent file will be overridden. + // + // http_seeds expects URLs to web servers implementing the original HTTP + // seed specification `BEP 17`_. + // + // url_seeds expects URLs to regular web servers, aka "get right" style, + // specified in `BEP 19`_. + aux::noexcept_movable> http_seeds; + aux::noexcept_movable> url_seeds; + + // peers to add to the torrent, to be tried to be connected to as + // bittorrent peers. + aux::noexcept_movable> peers; + + // peers banned from this torrent. The will not be connected to + aux::noexcept_movable> banned_peers; + + // this is a map of partially downloaded piece. The key is the piece index + // and the value is a bitfield where each bit represents a 16 kiB block. + // A set bit means we have that block. + aux::noexcept_movable> unfinished_pieces; + + // this is a bitfield indicating which pieces we already have of this + // torrent. + typed_bitfield have_pieces; + + // when in seed_mode, pieces with a set bit in this bitfield have been + // verified to be valid. Other pieces will be verified the first time a + // peer requests it. + typed_bitfield verified_pieces; + + // this sets the priorities for each individual piece in the torrent. Each + // element in the vector represent the piece with the same index. If you + // set both file- and piece priorities, file priorities will take + // precedence. + aux::noexcept_movable> piece_priorities; + + // if this is a merkle tree torrent, and you're seeding, this field must + // be set. It is all the hashes in the binary tree, with the root as the + // first entry. See torrent_info::set_merkle_tree() for more info. + aux::noexcept_movable> merkle_tree; + + // this is a map of file indices in the torrent and new filenames to be + // applied before the torrent is added. + aux::noexcept_movable> renamed_files; + + // the posix time of the last time payload was received or sent for this + // torrent, respectively. + std::time_t last_download = 0; + std::time_t last_upload = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // ``url`` can be set to a magnet link, in order to download the .torrent + // file (also known as the metadata), specifically the info-dictionary, + // from the bittorrent swarm. This may require access to the DHT, in case + // the magnet link does not come with trackers. + // + // In earlier versions of libtorrent, the URL could be an HTTP or file:// + // url. These uses are deprecated and discouraged. When adding a torrent + // by magnet link, it will be set to the ``downloading_metadata`` state + // until the .torrent file has been downloaded. If there is any error + // while downloading, the torrent will be stopped and the torrent error + // state (``torrent_status::error``) will indicate what went wrong. + std::string TORRENT_DEPRECATED_MEMBER url; + + // if ``uuid`` is specified, it is used to find duplicates. If another + // torrent is already running with the same UUID as the one being added, + // it will be considered a duplicate. This is mainly useful for RSS feed + // items which has UUIDs specified. + std::string TORRENT_DEPRECATED_MEMBER uuid; + + // The optional parameter, ``resume_data`` can be given if up to date + // fast-resume data is available. The fast-resume data can be acquired + // from a running torrent by calling save_resume_data() on + // torrent_handle. See fast-resume_. The ``vector`` that is passed in + // will be swapped into the running torrent instance with + // ``std::vector::swap()``. + aux::noexcept_movable> TORRENT_DEPRECATED_MEMBER resume_data; + + // to support the deprecated use case of reading the resume data into + // resume_data field and getting a reject alert, any parse failure is + // communicated forward into libtorrent via this field. If this is set, a + // fastresume_rejected_alert will be posted. + error_code internal_resume_data_error; +#endif // TORRENT_ABI_VERSION + + }; + +TORRENT_VERSION_NAMESPACE_2_END + +namespace aux { + bool contains_resume_data(add_torrent_params const&); +} + +} + +#endif diff --git a/include/libtorrent/address.hpp b/include/libtorrent/address.hpp new file mode 100644 index 0000000..a292fbf --- /dev/null +++ b/include/libtorrent/address.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ADDRESS_HPP_INCLUDED +#define TORRENT_ADDRESS_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ip::address; + using sim::asio::ip::address_v4; + using sim::asio::ip::address_v6; +#else + using boost::asio::ip::address; + using boost::asio::ip::address_v4; + using boost::asio::ip::address_v6; +#endif // SIMULATOR + +// the from_string member functions are deprecated starting +// in boost 1.66.0 +#if BOOST_VERSION >= 106600 && !defined TORRENT_BUILD_SIMULATOR + using boost::asio::ip::make_address; + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; +#else + // internal + inline address make_address(string_view str, boost::system::error_code& ec) + { return address::from_string(str.data(), ec); } + // internal + inline address_v4 make_address_v4(string_view str, boost::system::error_code& ec) + { return address_v4::from_string(str.data(), ec); } + // internal + inline address_v6 make_address_v6(string_view str, boost::system::error_code& ec) + { return address_v6::from_string(str.data(), ec); } + // internal + inline address make_address(string_view str) + { return address::from_string(str.data()); } + // internal + inline address_v4 make_address_v4(string_view str) + { return address_v4::from_string(str.data()); } + // internal + inline address_v6 make_address_v6(string_view str) + { return address_v6::from_string(str.data()); } +#endif +} + +#endif diff --git a/include/libtorrent/alert.hpp b/include/libtorrent/alert.hpp new file mode 100644 index 0000000..ab874a2 --- /dev/null +++ b/include/libtorrent/alert.hpp @@ -0,0 +1,351 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_HPP_INCLUDED +#define TORRENT_ALERT_HPP_INCLUDED + +#include + +// OVERVIEW +// +// The pop_alerts() function on session is the main interface for retrieving +// alerts (warnings, messages and errors from libtorrent). If no alerts have +// been posted by libtorrent pop_alerts() will return an empty list. +// +// By default, only errors are reported. settings_pack::alert_mask can be +// used to specify which kinds of events should be reported. The alert mask is +// a combination of the alert_category_t flags in the alert class. +// +// Every alert belongs to one or more category. There is a cost associated with +// posting alerts. Only alerts that belong to an enabled category are +// posted. Setting the alert bitmask to 0 will disable all alerts (except those +// that are non-discardable). Alerts that are responses to API calls such as +// save_resume_data() and post_session_stats() are non-discardable and will be +// posted even if their category is disabled. +// +// There are other alert base classes that some alerts derive from, all the +// alerts that are generated for a specific torrent are derived from +// torrent_alert, and tracker events derive from tracker_alert. +// +// Alerts returned by pop_alerts() are only valid until the next call to +// pop_alerts(). You may not copy an alert object to access it after the next +// call to pop_alerts(). Internal members of alerts also become invalid once +// pop_alerts() is called again. + +#include "libtorrent/time.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +// bitmask type used to define alert categories. Categories can be enabled +// and disabled by the settings_pack::alert_mask setting. Constants are defined +// in the lt::alert_category namespace +using alert_category_t = flags::bitfield_flag; + +namespace alert_category { + + // Enables alerts that report an error. This includes: + // + // * tracker errors + // * tracker warnings + // * file errors + // * resume data failures + // * web seed errors + // * .torrent files errors + // * listen socket errors + // * port mapping errors + constexpr alert_category_t error = 0_bit; + + // Enables alerts when peers send invalid requests, get banned or + // snubbed. + constexpr alert_category_t peer = 1_bit; + + // Enables alerts for port mapping events. For NAT-PMP and UPnP. + constexpr alert_category_t port_mapping = 2_bit; + + // Enables alerts for events related to the storage. File errors and + // synchronization events for moving the storage, renaming files etc. + constexpr alert_category_t storage = 3_bit; + + // Enables all tracker events. Includes announcing to trackers, + // receiving responses, warnings and errors. + constexpr alert_category_t tracker = 4_bit; + + // Low level alerts for when peers are connected and disconnected. + constexpr alert_category_t connect = 5_bit; + + // Enables alerts for when a torrent or the session changes state. + constexpr alert_category_t status = 6_bit; + + // Alerts when a peer is blocked by the ip blocker or port blocker. + constexpr alert_category_t ip_block = 8_bit; + + // Alerts when some limit is reached that might limit the download + // or upload rate. + constexpr alert_category_t performance_warning = 9_bit; + + // Alerts on events in the DHT node. For incoming searches or + // bootstrapping being done etc. + constexpr alert_category_t dht = 10_bit; + + // If you enable these alerts, you will receive a stats_alert + // approximately once every second, for every active torrent. + // These alerts contain all statistics counters for the interval since + // the lasts stats alert. + constexpr alert_category_t stats = 11_bit; + + // Enables debug logging alerts. These are available unless libtorrent + // was built with logging disabled (``TORRENT_DISABLE_LOGGING``). The + // alerts being posted are log_alert and are session wide. + constexpr alert_category_t session_log = 13_bit; + + // Enables debug logging alerts for torrents. These are available + // unless libtorrent was built with logging disabled + // (``TORRENT_DISABLE_LOGGING``). The alerts being posted are + // torrent_log_alert and are torrent wide debug events. + constexpr alert_category_t torrent_log = 14_bit; + + // Enables debug logging alerts for peers. These are available unless + // libtorrent was built with logging disabled + // (``TORRENT_DISABLE_LOGGING``). The alerts being posted are + // peer_log_alert and low-level peer events and messages. + constexpr alert_category_t peer_log = 15_bit; + + // enables the incoming_request_alert. + constexpr alert_category_t incoming_request = 16_bit; + + // enables dht_log_alert, debug logging for the DHT + constexpr alert_category_t dht_log = 17_bit; + + // enable events from pure dht operations not related to torrents + constexpr alert_category_t dht_operation = 18_bit; + + // enables port mapping log events. This log is useful + // for debugging the UPnP or NAT-PMP implementation + constexpr alert_category_t port_mapping_log = 19_bit; + + // enables verbose logging from the piece picker. + constexpr alert_category_t picker_log = 20_bit; + + // alerts when files complete downloading + constexpr alert_category_t file_progress = 21_bit; + + // alerts when pieces complete downloading or fail hash check + constexpr alert_category_t piece_progress = 22_bit; + + // alerts when we upload blocks to other peers + constexpr alert_category_t upload = 23_bit; + + // alerts on individual blocks being requested, downloading, finished, + // rejected, time-out and cancelled. This is likely to post alerts at a + // high rate. + constexpr alert_category_t block_progress = 24_bit; + + // The full bitmask, representing all available categories. + // + // since the enum is signed, make sure this isn't + // interpreted as -1. For instance, boost.python + // does that and fails when assigning it to an + // unsigned parameter. + constexpr alert_category_t all = alert_category_t::all(); + +} // namespace alert_category + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // The ``alert`` class is the base class that specific messages are derived from. + // alert types are not copyable, and cannot be constructed by the client. The + // pointers returned by libtorrent are short lived (the details are described + // under session_handle::pop_alerts()) + class TORRENT_EXPORT alert + { +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + public: + + // hidden + alert(alert const& rhs) = delete; + alert& operator=(alert const&) = delete; + alert(alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + // only here for backwards compatibility + enum TORRENT_DEPRECATED_ENUM severity_t { debug, info, warning, critical, fatal, none }; + + using category_t = alert_category_t; +#endif + + static constexpr alert_category_t error_notification = 0_bit; + static constexpr alert_category_t peer_notification = 1_bit; + static constexpr alert_category_t port_mapping_notification = 2_bit; + static constexpr alert_category_t storage_notification = 3_bit; + static constexpr alert_category_t tracker_notification = 4_bit; + static constexpr alert_category_t connect_notification = 5_bit; +#if TORRENT_ABI_VERSION == 1 + static constexpr alert_category_t TORRENT_DEPRECATED_MEMBER debug_notification = connect_notification; +#endif + static constexpr alert_category_t status_notification = 6_bit; +#if TORRENT_ABI_VERSION == 1 + static constexpr alert_category_t TORRENT_DEPRECATED_MEMBER progress_notification = 7_bit; +#endif + static constexpr alert_category_t ip_block_notification = 8_bit; + static constexpr alert_category_t performance_warning = 9_bit; + static constexpr alert_category_t dht_notification = 10_bit; + static constexpr alert_category_t stats_notification = 11_bit; +#if TORRENT_ABI_VERSION == 1 + static constexpr alert_category_t TORRENT_DEPRECATED_MEMBER rss_notification = 12_bit; +#endif + static constexpr alert_category_t session_log_notification = 13_bit; + static constexpr alert_category_t torrent_log_notification = 14_bit; + static constexpr alert_category_t peer_log_notification = 15_bit; + static constexpr alert_category_t incoming_request_notification = 16_bit; + static constexpr alert_category_t dht_log_notification = 17_bit; + static constexpr alert_category_t dht_operation_notification = 18_bit; + static constexpr alert_category_t port_mapping_log_notification = 19_bit; + static constexpr alert_category_t picker_log_notification = 20_bit; + static constexpr alert_category_t file_progress_notification = 21_bit; + static constexpr alert_category_t piece_progress_notification = 22_bit; + static constexpr alert_category_t upload_notification = 23_bit; + static constexpr alert_category_t block_progress_notification = 24_bit; + static constexpr alert_category_t all_categories = alert_category_t::all(); + + // hidden + alert(); + // hidden + virtual ~alert(); + + // a timestamp is automatically created in the constructor + time_point timestamp() const; + + // returns an integer that is unique to this alert type. It can be + // compared against a specific alert by querying a static constant called ``alert_type`` + // in the alert. It can be used to determine the run-time type of an alert* in + // order to cast to that alert type and access specific members. + // + // e.g: + // + // .. code:: c++ + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // for (alert* a : alerts) { + // switch (a->type()) { + // + // case read_piece_alert::alert_type: + // { + // auto* p = static_cast(a); + // if (p->ec) { + // // read_piece failed + // break; + // } + // // use p + // break; + // } + // case file_renamed_alert::alert_type: + // { + // // etc... + // } + // } + // } + virtual int type() const noexcept = 0; + + // returns a string literal describing the type of the alert. It does + // not include any information that might be bundled with the alert. + virtual char const* what() const noexcept = 0; + + // generate a string describing the alert and the information bundled + // with it. This is mainly intended for debug and development use. It is not suitable + // to use this for applications that may be localized. Instead, handle each alert + // type individually and extract and render the information from the alert depending + // on the locale. + virtual std::string message() const = 0; + + // returns a bitmask specifying which categories this alert belong to. + virtual alert_category_t category() const noexcept = 0; + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // determines whether or not an alert is allowed to be discarded + // when the alert queue is full. There are a few alerts which may not be discarded, + // since they would break the user contract, such as save_resume_data_alert. + TORRENT_DEPRECATED + bool discardable() const { return discardable_impl(); } + + TORRENT_DEPRECATED + severity_t severity() const { return warning; } + + protected: + + virtual bool discardable_impl() const { return true; } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // TORRENT_ABI_VERSION + + private: + time_point const m_timestamp; + }; + +// When you get an alert, you can use ``alert_cast<>`` to attempt to cast the +// pointer to a specific alert type, in order to query it for more +// information. +// +// .. note:: +// ``alert_cast<>`` can only cast to an exact alert type, not a base class +template T* alert_cast(alert* a) +{ + static_assert(std::is_base_of::value + , "alert_cast<> can only be used with alert types (deriving from lt::alert)"); + + if (a == nullptr) return nullptr; + if (a->type() == T::alert_type) return static_cast(a); + return nullptr; +} +template T const* alert_cast(alert const* a) +{ + static_assert(std::is_base_of::value + , "alert_cast<> can only be used with alert types (deriving from lt::alert)"); + if (a == nullptr) return nullptr; + if (a->type() == T::alert_type) return static_cast(a); + return nullptr; +} + +} // namespace libtorrent + +#endif // TORRENT_ALERT_HPP_INCLUDED diff --git a/include/libtorrent/alert_manager.hpp b/include/libtorrent/alert_manager.hpp new file mode 100644 index 0000000..9a0808d --- /dev/null +++ b/include/libtorrent/alert_manager.hpp @@ -0,0 +1,175 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_MANAGER_HPP_INCLUDED +#define TORRENT_ALERT_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/heterogeneous_queue.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/alert_types.hpp" // for num_alert_types +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include // for std::forward +#include +#include +#include +#include + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct plugin; +#endif + + class TORRENT_EXTRA_EXPORT alert_manager + { + public: + explicit alert_manager(int queue_limit + , alert_category_t alert_mask = alert::error_notification); + + alert_manager(alert_manager const&) = delete; + alert_manager& operator=(alert_manager const&) = delete; + + ~alert_manager(); + + template + void emplace_alert(Args&&... args) try + { + std::unique_lock lock(m_mutex); + + // don't add more than this number of alerts, unless it's a + // high priority alert, in which case we try harder to deliver it + // for high priority alerts, double the upper limit + if (m_alerts[m_generation].size() / (1 + T::priority) + >= m_queue_size_limit) + { + // record that we dropped an alert of this type + m_dropped.set(T::alert_type); + return; + } + + T& alert = m_alerts[m_generation].emplace_back( + m_allocations[m_generation], std::forward(args)...); + + maybe_notify(&alert); + } + catch (std::bad_alloc const&) + { + // record that we dropped an alert of this type + std::unique_lock lock(m_mutex); + m_dropped.set(T::alert_type); + } + + bool pending() const; + void get_all(std::vector& alerts); + + template + bool should_post() const + { + return bool(m_alert_mask.load(std::memory_order_relaxed) & T::static_category); + } + + alert* wait_for_alert(time_duration max_wait); + + void set_alert_mask(alert_category_t const m) noexcept + { + m_alert_mask = m; + } + + alert_category_t alert_mask() const noexcept + { + return m_alert_mask; + } + + int alert_queue_size_limit() const noexcept { return m_queue_size_limit; } + int set_alert_queue_size_limit(int queue_size_limit_); + + void set_notify_function(std::function const& fun); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr ext); +#endif + + private: + + void maybe_notify(alert* a); + + // this mutex protects everything. Since it's held while executing user + // callbacks (the notify function and extension on_alert()) it must be + // recursive to support recursively post new alerts. + mutable std::recursive_mutex m_mutex; + std::condition_variable_any m_condition; + std::atomic m_alert_mask; + int m_queue_size_limit; + + // a bitfield where each bit represents an alert type. Every time we drop + // an alert (because the queue is full or of some other error) we set the + // corresponding bit in this mask, to communicate to the client that it + // may have missed an update. + std::bitset m_dropped; + + // this function (if set) is called whenever the number of alerts in + // the alert queue goes from 0 to 1. The client is expected to wake up + // its main message loop for it to poll for alerts (using get_alerts()). + // That call will drain every alert in one atomic operation and this + // notification function will be called again the next time an alert is + // posted to the queue + std::function m_notify; + + // this is either 0 or 1, it indicates which m_alerts and m_allocations + // the alert_manager is allowed to use right now. This is swapped when + // the client calls get_all(), at which point all of the alert objects + // passed to the client will be owned by libtorrent again, and reset. + int m_generation = 0; + + // this is where all alerts are queued up. There are two heterogeneous + // queues to double buffer the thread access. The std::mutex in the alert + // manager gives exclusive access to m_alerts[m_generation] and + // m_allocations[m_generation] whereas the other copy is exclusively + // used by the client thread. + aux::array, 2> m_alerts; + + // this is a stack where alerts can allocate variable length content, + // such as strings, to go with the alerts. + aux::array m_allocations; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_ses_extensions; +#endif + }; +} + +#endif diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp new file mode 100644 index 0000000..18da3ef --- /dev/null +++ b/include/libtorrent/alert_types.hpp @@ -0,0 +1,2989 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALERT_TYPES_HPP_INCLUDED +#define TORRENT_ALERT_TYPES_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/close_reason.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native +#include "libtorrent/string_view.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" +#include "libtorrent/portmap.hpp" // for portmap_transport +#include "libtorrent/aux_/deprecated.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include // for va_list + +#if TORRENT_ABI_VERSION == 1 +#define PROGRESS_NOTIFICATION | alert::progress_notification +#else +#define PROGRESS_NOTIFICATION +#endif + + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED_EXPORT char const* operation_name(int op); +#endif + + // internal + TORRENT_EXPORT char const* alert_name(int alert_type); + + // user defined alerts should use IDs greater than this + constexpr int user_alert_id = 10000; + + // this constant represents "max_alert_index" + 1 + constexpr int num_alert_types = 97; + + // internal + enum alert_priority + { + alert_priority_normal = 0, + alert_priority_high, + alert_priority_critical + }; + + // struct to hold information about a single DHT routing table bucket + struct TORRENT_EXPORT dht_routing_bucket + { + // the total number of nodes and replacement nodes + // in the routing table + int num_nodes; + int num_replacements; + + // number of seconds since last activity + int last_active; + }; + +TORRENT_VERSION_NAMESPACE_2 + + // This is a base class for alerts that are associated with a + // specific torrent. It contains a handle to the torrent. + struct TORRENT_EXPORT torrent_alert : alert + { + // internal + torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h); + torrent_alert(torrent_alert&&) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + static const int TORRENT_DEPRECATED_MEMBER alert_type = 0; +#endif + + // returns the message associated with this alert + std::string message() const override; + + // The torrent_handle pointing to the torrent this + // alert is associated with. + torrent_handle handle; + + char const* torrent_name() const; + + protected: + std::reference_wrapper m_alloc; + private: + aux::allocation_slot m_name_idx; +#if TORRENT_ABI_VERSION == 1 + public: + std::string TORRENT_DEPRECATED_MEMBER name; +#endif + }; + + // The peer alert is a base class for alerts that refer to a specific peer. It includes all + // the information to identify the peer. i.e. ``ip`` and ``peer-id``. + struct TORRENT_EXPORT peer_alert : torrent_alert + { + // internal + peer_alert(aux::stack_allocator& alloc, torrent_handle const& h, + tcp::endpoint const& i, peer_id const& pi); + peer_alert(peer_alert&& rhs) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + static const int TORRENT_DEPRECATED_MEMBER alert_type = 1; +#endif + + std::string message() const override; + + // The peer's IP address and port. + aux::noexcept_movable endpoint; + + // the peer ID, if known. + peer_id pid; + +#if TORRENT_ABI_VERSION == 1 + // The peer's IP address and port. + aux::noexcept_movable TORRENT_DEPRECATED_MEMBER ip; +#endif + }; + + // This is a base class used for alerts that are associated with a + // specific tracker. It derives from torrent_alert since a tracker + // is also associated with a specific torrent. + struct TORRENT_EXPORT tracker_alert : torrent_alert + { + // internal + tracker_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, string_view u); + +#if TORRENT_ABI_VERSION == 1 + static const int TORRENT_DEPRECATED_MEMBER alert_type = 2; +#endif + + std::string message() const override; + + // endpoint of the listen interface being announced + aux::noexcept_movable local_endpoint; + + // returns a 0-terminated string of the tracker's URL + char const* tracker_url() const; + + private: + aux::allocation_slot m_url_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // The tracker URL + std::string TORRENT_DEPRECATED_MEMBER url; +#endif + }; + +#define TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) \ + name(name&&) noexcept = default; \ + static const int priority = prio; \ + static const int alert_type = seq; \ + virtual int type() const noexcept override { return alert_type; } \ + virtual alert_category_t category() const noexcept override { return static_category; } \ + virtual char const* what() const noexcept override { return alert_name(alert_type); } + +#define TORRENT_DEFINE_ALERT(name, seq) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, alert_priority_normal) + +#define TORRENT_DEFINE_ALERT_PRIO(name, seq, prio) \ + TORRENT_DEFINE_ALERT_IMPL(name, seq, prio) + +#if TORRENT_ABI_VERSION == 1 + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // The ``torrent_added_alert`` is posted once every time a torrent is successfully + // added. It doesn't contain any members of its own, but inherits the torrent handle + // from its base class. + // It's posted when the ``status_notification`` bit is set in the alert_mask. + // deprecated in 1.1.3 + // use add_torrent_alert instead + struct TORRENT_DEPRECATED_EXPORT torrent_added_alert final : torrent_alert + { + // internal + torrent_added_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT(torrent_added_alert, 3) + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + + // The ``torrent_removed_alert`` is posted whenever a torrent is removed. Since + // the torrent handle in its base class will always be invalid (since the torrent + // is already removed) it has the info hash as a member, to identify it. + // It's posted when the ``status_notification`` bit is set in the alert_mask. + // + // Even though the ``handle`` member doesn't point to an existing torrent anymore, + // it is still useful for comparing to other handles, which may also no + // longer point to existing torrents, but to the same non-existing torrents. + // + // The ``torrent_handle`` acts as a ``weak_ptr``, even though its object no + // longer exists, it can still compare equal to another weak pointer which + // points to the same non-existent object. + struct TORRENT_EXPORT torrent_removed_alert final : torrent_alert + { + // internal + torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_removed_alert, 4, alert_priority_critical) + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + sha1_hash info_hash; + }; + + // This alert is posted when the asynchronous read operation initiated by + // a call to torrent_handle::read_piece() is completed. If the read failed, the torrent + // is paused and an error state is set and the buffer member of the alert + // is 0. If successful, ``buffer`` points to a buffer containing all the data + // of the piece. ``piece`` is the piece index that was read. ``size`` is the + // number of bytes that was read. + // + // If the operation fails, ``error`` will indicate what went wrong. + struct TORRENT_EXPORT read_piece_alert final : torrent_alert + { + // internal + read_piece_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t p, boost::shared_array d, int s); + read_piece_alert(aux::stack_allocator& alloc, torrent_handle h + , piece_index_t p, error_code e); + + TORRENT_DEFINE_ALERT_PRIO(read_piece_alert, 5, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + error_code const error; + boost::shared_array const buffer; + piece_index_t const piece; + int const size; + +#if TORRENT_ABI_VERSION == 1 + error_code TORRENT_DEPRECATED_MEMBER ec; +#endif + }; + + // This is posted whenever an individual file completes its download. i.e. + // All pieces overlapping this file have passed their hash check. + struct TORRENT_EXPORT file_completed_alert final : torrent_alert + { + // internal + file_completed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , file_index_t idx); + + TORRENT_DEFINE_ALERT_PRIO(file_completed_alert, 6, alert_priority_normal) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::file_progress + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + // refers to the index of the file that completed. + file_index_t const index; + }; + + // This is posted as a response to a torrent_handle::rename_file() call, if the rename + // operation succeeds. + struct TORRENT_EXPORT file_renamed_alert final : torrent_alert + { + // internal + file_renamed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view n, file_index_t idx); + + TORRENT_DEFINE_ALERT_PRIO(file_renamed_alert, 7, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + char const* new_name() const; + + // refers to the index of the file that was renamed, + file_index_t const index; + private: + aux::allocation_slot m_name_idx; +#if TORRENT_ABI_VERSION == 1 + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + + public: + std::string TORRENT_DEPRECATED_MEMBER name; + +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif + }; + + // This is posted as a response to a torrent_handle::rename_file() call, if the rename + // operation failed. + struct TORRENT_EXPORT file_rename_failed_alert final : torrent_alert + { + // internal + file_rename_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, file_index_t idx + , error_code ec); + + TORRENT_DEFINE_ALERT_PRIO(file_rename_failed_alert, 8, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + + std::string message() const override; + + // refers to the index of the file that was supposed to be renamed, + // ``error`` is the error code returned from the filesystem. + file_index_t const index; + error_code const error; + }; + + // This alert is generated when a limit is reached that might have a negative impact on + // upload or download rate performance. + struct TORRENT_EXPORT performance_alert final : torrent_alert + { + enum performance_warning_t + { + + // This warning means that the number of bytes queued to be written to disk + // exceeds the max disk byte queue setting (``settings_pack::max_queued_disk_bytes``). + // This might restrict the download rate, by not queuing up enough write jobs + // to the disk I/O thread. When this alert is posted, peer connections are + // temporarily stopped from downloading, until the queued disk bytes have fallen + // below the limit again. Unless your ``max_queued_disk_bytes`` setting is already + // high, you might want to increase it to get better performance. + outstanding_disk_buffer_limit_reached, + + // This is posted when libtorrent would like to send more requests to a peer, + // but it's limited by ``settings_pack::max_out_request_queue``. The queue length + // libtorrent is trying to achieve is determined by the download rate and the + // assumed round-trip-time (``settings_pack::request_queue_time``). The assumed + // round-trip-time is not limited to just the network RTT, but also the remote disk + // access time and message handling time. It defaults to 3 seconds. The target number + // of outstanding requests is set to fill the bandwidth-delay product (assumed RTT + // times download rate divided by number of bytes per request). When this alert + // is posted, there is a risk that the number of outstanding requests is too low + // and limits the download rate. You might want to increase the ``max_out_request_queue`` + // setting. + outstanding_request_limit_reached, + + // This warning is posted when the amount of TCP/IP overhead is greater than the + // upload rate limit. When this happens, the TCP/IP overhead is caused by a much + // faster download rate, triggering TCP ACK packets. These packets eat into the + // rate limit specified to libtorrent. When the overhead traffic is greater than + // the rate limit, libtorrent will not be able to send any actual payload, such + // as piece requests. This means the download rate will suffer, and new requests + // can be sent again. There will be an equilibrium where the download rate, on + // average, is about 20 times the upload rate limit. If you want to maximize the + // download rate, increase the upload rate limit above 5% of your download capacity. + upload_limit_too_low, + + // This is the same warning as ``upload_limit_too_low`` but referring to the download + // limit instead of upload. This suggests that your download rate limit is much lower + // than your upload capacity. Your upload rate will suffer. To maximize upload rate, + // make sure your download rate limit is above 5% of your upload capacity. + download_limit_too_low, + + // We're stalled on the disk. We want to write to the socket, and we can write + // but our send buffer is empty, waiting to be refilled from the disk. + // This either means the disk is slower than the network connection + // or that our send buffer watermark is too small, because we can + // send it all before the disk gets back to us. + // The number of bytes that we keep outstanding, requested from the disk, is calculated + // as follows: + // + // .. code:: C++ + // + // min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark)) + // + // If you receive this alert, you might want to either increase your ``send_buffer_watermark`` + // or ``send_buffer_watermark_factor``. + send_buffer_watermark_too_low, + + // If the half (or more) of all upload slots are set as optimistic unchoke slots, this + // warning is issued. You probably want more regular (rate based) unchoke slots. + too_many_optimistic_unchoke_slots, + + // If the disk write queue ever grows larger than half of the cache size, this warning + // is posted. The disk write queue eats into the total disk cache and leaves very little + // left for the actual cache. This causes the disk cache to oscillate in evicting large + // portions of the cache before allowing peers to download any more, onto the disk write + // queue. Either lower ``max_queued_disk_bytes`` or increase ``cache_size``. + too_high_disk_queue_limit, + + aio_limit_reached, +#if TORRENT_ABI_VERSION == 1 + bittyrant_with_no_uplimit TORRENT_DEPRECATED_ENUM, +#else + deprecated_bittyrant_with_no_uplimit, +#endif + + // This is generated if outgoing peer connections are failing because of *address in use* + // errors, indicating that ``settings_pack::outgoing_ports`` is set and is too small of + // a range. Consider not using the ``outgoing_ports`` setting at all, or widen the range to + // include more ports. + too_few_outgoing_ports, + + too_few_file_descriptors, + + num_warnings + }; + + // internal + performance_alert(aux::stack_allocator& alloc, torrent_handle const& h + , performance_warning_t w); + + TORRENT_DEFINE_ALERT(performance_alert, 9) + + static constexpr alert_category_t static_category = alert_category::performance_warning; + + std::string message() const override; + + performance_warning_t const warning_code; + }; + + // Generated whenever a torrent changes its state. + struct TORRENT_EXPORT state_changed_alert final : torrent_alert + { + // internal + state_changed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , torrent_status::state_t st + , torrent_status::state_t prev_st); + + TORRENT_DEFINE_ALERT_PRIO(state_changed_alert, 10, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status; + + std::string message() const override; + + // the new state of the torrent. + torrent_status::state_t const state; + + // the previous state. + torrent_status::state_t const prev_state; + }; + + // This alert is generated on tracker time outs, premature disconnects, + // invalid response or a HTTP response other than "200 OK". From the alert + // you can get the handle to the torrent the tracker belongs to. + // + // The ``times_in_row`` member says how many times in a row this tracker has + // failed. ``status_code`` is the code returned from the HTTP server. 401 + // means the tracker needs authentication, 404 means not found etc. If the + // tracker timed out, the code will be set to 0. + struct TORRENT_EXPORT tracker_error_alert final : tracker_alert + { + // internal + tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int times, string_view u + , error_code const& e, string_view m); + + TORRENT_DEFINE_ALERT_PRIO(tracker_error_alert, 11, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + int const times_in_row; + error_code const error; + + // the message associated with this error + char const* error_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + int const TORRENT_DEPRECATED_MEMBER status_code; + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is triggered if the tracker reply contains a warning field. + // Usually this means that the tracker announce was successful, but the + // tracker has a message to the client. + struct TORRENT_EXPORT tracker_warning_alert final : tracker_alert + { + // internal + tracker_warning_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT(tracker_warning_alert, 12) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // the message associated with this warning + char const* warning_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // contains the warning message from the tracker. + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is generated when a scrape request succeeds. + struct TORRENT_EXPORT scrape_reply_alert final : tracker_alert + { + // internal + scrape_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int incomp, int comp, string_view u); + + TORRENT_DEFINE_ALERT_PRIO(scrape_reply_alert, 13, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // the data returned in the scrape response. These numbers + // may be -1 if the response was malformed. + int const incomplete; + int const complete; + }; + + // If a scrape request fails, this alert is generated. This might be due + // to the tracker timing out, refusing connection or returning an http response + // code indicating an error. + struct TORRENT_EXPORT scrape_failed_alert final : tracker_alert + { + // internal + scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, error_code const& e); + scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT_PRIO(scrape_failed_alert, 14, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::tracker | alert_category::error; + std::string message() const override; + + // the error itself. This may indicate that the tracker sent an error + // message (``error::tracker_failure``), in which case it can be + // retrieved by calling ``error_message()``. + error_code const error; + + // if the error indicates there is an associated message, this returns + // that message. Otherwise and empty string. + char const* error_message() const; + + private: + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // contains a message describing the error. + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is only for informational purpose. It is generated when a tracker announce + // succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or + // the DHT. + struct TORRENT_EXPORT tracker_reply_alert final : tracker_alert + { + // internal + tracker_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int np, string_view u); + + TORRENT_DEFINE_ALERT(tracker_reply_alert, 15) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // tells how many peers the tracker returned in this response. This is + // not expected to be greater than the ``num_want`` settings. These are not necessarily + // all new peers, some of them may already be connected. + int const num_peers; + }; + + // This alert is generated each time the DHT receives peers from a node. ``num_peers`` + // is the number of peers we received in this packet. Typically these packets are + // received from multiple DHT nodes, and so the alerts are typically generated + // a few at a time. + struct TORRENT_EXPORT dht_reply_alert final : tracker_alert + { + // internal + dht_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np); + + TORRENT_DEFINE_ALERT(dht_reply_alert, 16) + + static constexpr alert_category_t static_category = alert_category::dht | alert_category::tracker; + std::string message() const override; + + int const num_peers; + }; + + // This alert is generated each time a tracker announce is sent (or attempted to be sent). + // There are no extra data members in this alert. The url can be found in the base class + // however. + struct TORRENT_EXPORT tracker_announce_alert final : tracker_alert + { + // internal + tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, int e); + + TORRENT_DEFINE_ALERT(tracker_announce_alert, 17) + + static constexpr alert_category_t static_category = alert_category::tracker; + std::string message() const override; + + // specifies what event was sent to the tracker. It is defined as: + // + // 0. None + // 1. Completed + // 2. Started + // 3. Stopped + int const event; + }; + + // This alert is generated when a finished piece fails its hash check. You can get the handle + // to the torrent which got the failed piece and the index of the piece itself from the alert. + struct TORRENT_EXPORT hash_failed_alert final : torrent_alert + { + // internal + hash_failed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , piece_index_t index); + + TORRENT_DEFINE_ALERT(hash_failed_alert, 18) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + piece_index_t const piece_index; + }; + + // This alert is generated when a peer is banned because it has sent too many corrupt pieces + // to us. ``ip`` is the endpoint to the peer that was banned. + struct TORRENT_EXPORT peer_ban_alert final : peer_alert + { + // internal + peer_ban_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_ban_alert, 19) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer is un-snubbed. Essentially when it was snubbed for stalling + // sending data, and now it started sending data again. + struct TORRENT_EXPORT peer_unsnubbed_alert final : peer_alert + { + // internal + peer_unsnubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_unsnubbed_alert, 20) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer is snubbed, when it stops sending data when we request + // it. + struct TORRENT_EXPORT peer_snubbed_alert final : peer_alert + { + // internal + peer_snubbed_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + TORRENT_DEFINE_ALERT(peer_snubbed_alert, 21) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer + // will be disconnected, but you get its ip address from the alert, to identify it. + struct TORRENT_EXPORT peer_error_alert final : peer_alert + { + // internal + peer_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, operation_t op + , error_code const& e); + + TORRENT_DEFINE_ALERT(peer_error_alert, 22) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // a 0-terminated string of the low-level operation that failed, or nullptr if + // there was no low level disk operation. + operation_t op; + + // tells you what error caused this alert. + error_code const error; + +#if TORRENT_ABI_VERSION == 1 + int const TORRENT_DEPRECATED_MEMBER operation; + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is posted every time an outgoing peer connect attempts succeeds. + struct TORRENT_EXPORT peer_connect_alert final : peer_alert + { + // internal + peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int type); + + TORRENT_DEFINE_ALERT(peer_connect_alert, 23) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + int const socket_type; + }; + + // This alert is generated when a peer is disconnected for any reason (other than the ones + // covered by peer_error_alert ). + struct TORRENT_EXPORT peer_disconnected_alert final : peer_alert + { + // internal + peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op, int type, error_code const& e + , close_reason_t r); + + TORRENT_DEFINE_ALERT(peer_disconnected_alert, 24) + + static constexpr alert_category_t static_category = alert_category::connect; + std::string message() const override; + + // the kind of socket this peer was connected over + int const socket_type; + + // the operation or level where the error occurred. Specified as an + // value from the operation_t enum. Defined in operations.hpp. + operation_t const op; + + // tells you what error caused peer to disconnect. + error_code const error; + + // the reason the peer disconnected (if specified) + close_reason_t const reason; + +#if TORRENT_ABI_VERSION == 1 + int const TORRENT_DEPRECATED_MEMBER operation; + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This is a debug alert that is generated by an incoming invalid piece request. + // ``ip`` is the address of the peer and the ``request`` is the actual incoming + // request from the peer. See peer_request for more info. + struct TORRENT_EXPORT invalid_request_alert final : peer_alert + { + // internal + invalid_request_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, peer_request const& r + , bool we_have, bool peer_interested, bool withheld); + + TORRENT_DEFINE_ALERT(invalid_request_alert, 25) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // the request we received from the peer + peer_request const request; + + // true if we have this piece + bool const we_have; + + // true if the peer indicated that it was interested to download before + // sending the request + bool const peer_interested; + + // if this is true, the peer is not allowed to download this piece because + // of super-seeding rules. + bool const withheld; + }; + + // This alert is generated when a torrent switches from being a downloader to a seed. + // It will only be generated once per torrent. It contains a torrent_handle to the + // torrent in question. + struct TORRENT_EXPORT torrent_finished_alert final : torrent_alert + { + // internal + torrent_finished_alert(aux::stack_allocator& alloc, + torrent_handle h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_finished_alert, 26, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // this alert is posted every time a piece completes downloading + // and passes the hash check. This alert derives from torrent_alert + // which contains the torrent_handle to the torrent the piece belongs to. + // Note that being downloaded and passing the hash check may happen before + // the piece is also fully flushed to disk. So torrent_handle::have_piece() + // may still return false + struct TORRENT_EXPORT piece_finished_alert final : torrent_alert + { + // internal + piece_finished_alert(aux::stack_allocator& alloc, + torrent_handle const& h, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(piece_finished_alert, 27) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::piece_progress + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + // the index of the piece that finished + piece_index_t const piece_index; + }; + + // This alert is generated when a peer rejects or ignores a piece request. + struct TORRENT_EXPORT request_dropped_alert final : peer_alert + { + // internal + request_dropped_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(request_dropped_alert, 28) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request times out. + struct TORRENT_EXPORT block_timeout_alert final : peer_alert + { + // internal + block_timeout_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_timeout_alert, 29) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::block_progress + | alert_category::peer + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request receives a response. + struct TORRENT_EXPORT block_finished_alert final : peer_alert + { + // internal + block_finished_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_finished_alert, 30) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // This alert is generated when a block request is sent to a peer. + struct TORRENT_EXPORT block_downloading_alert final : peer_alert + { + // internal + block_downloading_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_downloading_alert, 31) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::block_progress + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; +#if TORRENT_ABI_VERSION == 1 + char const* TORRENT_DEPRECATED_MEMBER peer_speedmsg; +#endif + }; + + // This alert is generated when a block is received that was not requested or + // whose request timed out. + struct TORRENT_EXPORT unwanted_block_alert final : peer_alert + { + // internal + unwanted_block_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(unwanted_block_alert, 32) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // The ``storage_moved_alert`` is generated when all the disk IO has + // completed and the files have been moved, as an effect of a call to + // ``torrent_handle::move_storage``. This is useful to synchronize with the + // actual disk. The ``storage_path()`` member return the new path of the + // storage. + struct TORRENT_EXPORT storage_moved_alert final : torrent_alert + { + // internal + storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p); + + TORRENT_DEFINE_ALERT_PRIO(storage_moved_alert, 33, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the path the torrent was moved to + char const* storage_path() const; + + private: + aux::allocation_slot m_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + std::string TORRENT_DEPRECATED_MEMBER path; +#endif + }; + + // The ``storage_moved_failed_alert`` is generated when an attempt to move the storage, + // via torrent_handle::move_storage(), fails. + struct TORRENT_EXPORT storage_moved_failed_alert final : torrent_alert + { + // internal + storage_moved_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, string_view file + , operation_t op); + + TORRENT_DEFINE_ALERT_PRIO(storage_moved_failed_alert, 34, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + error_code const error; + + // If the error happened for a specific file, this returns its path. + char const* file_path() const; + + // this indicates what underlying operation caused the error + operation_t op; + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + char const* TORRENT_DEPRECATED_MEMBER operation; + // If the error happened for a specific file, ``file`` is its path. + std::string TORRENT_DEPRECATED_MEMBER file; +#endif + }; + + // This alert is generated when a request to delete the files of a torrent complete. + // + // The ``info_hash`` is the info-hash of the torrent that was just deleted. Most of + // the time the torrent_handle in the ``torrent_alert`` will be invalid by the time + // this alert arrives, since the torrent is being deleted. The ``info_hash`` member + // is hence the main way of identifying which torrent just completed the delete. + // + // This alert is posted in the ``storage_notification`` category, and that bit + // needs to be set in the alert_mask. + struct TORRENT_EXPORT torrent_deleted_alert final : torrent_alert + { + // internal + torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_deleted_alert, 35, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + sha1_hash info_hash; + }; + + // This alert is generated when a request to delete the files of a torrent fails. + // Just removing a torrent from the session cannot fail + struct TORRENT_EXPORT torrent_delete_failed_alert final : torrent_alert + { + // internal + torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT_PRIO(torrent_delete_failed_alert, 36, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage + | alert_category::error; + std::string message() const override; + + // tells you why it failed. + error_code const error; + + // the info hash of the torrent whose files failed to be deleted + sha1_hash info_hash; + +#if TORRENT_ABI_VERSION == 1 + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is generated as a response to a ``torrent_handle::save_resume_data`` request. + // It is generated once the disk IO thread is done writing the state for this torrent. + struct TORRENT_EXPORT save_resume_data_alert final : torrent_alert + { + // internal + save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params&& params + , torrent_handle const& h); + save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params const& params + , torrent_handle const& h) = delete; + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_alert, 37, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage; + std::string message() const override; + + // the ``params`` structure is populated with the fields to be passed to + // add_torrent() or async_add_torrent() to resume the torrent. To + // save the state to disk, you may pass it on to write_resume_data(). + add_torrent_params params; + +#if TORRENT_ABI_VERSION == 1 + // points to the resume data. + std::shared_ptr TORRENT_DEPRECATED_MEMBER resume_data; +#endif + }; + + // This alert is generated instead of ``save_resume_data_alert`` if there was an error + // generating the resume data. ``error`` describes what went wrong. + struct TORRENT_EXPORT save_resume_data_failed_alert final : torrent_alert + { + // internal + save_resume_data_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e); + + TORRENT_DEFINE_ALERT_PRIO(save_resume_data_failed_alert, 38, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::storage + | alert_category::error; + std::string message() const override; + + // the error code from the resume_data failure + error_code const error; + +#if TORRENT_ABI_VERSION == 1 + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is generated as a response to a ``torrent_handle::pause`` request. It is + // generated once all disk IO is complete and the files in the torrent have been closed. + // This is useful for synchronizing with the disk. + struct TORRENT_EXPORT torrent_paused_alert final : torrent_alert + { + // internal + torrent_paused_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_paused_alert, 39, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is generated as a response to a torrent_handle::resume() request. It is + // generated when a torrent goes from a paused state to an active state. + struct TORRENT_EXPORT torrent_resumed_alert final : torrent_alert + { + // internal + torrent_resumed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_resumed_alert, 40, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is posted when a torrent completes checking. i.e. when it transitions + // out of the ``checking files`` state into a state where it is ready to start downloading + struct TORRENT_EXPORT torrent_checked_alert final : torrent_alert + { + // internal + torrent_checked_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_checked_alert, 41, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is generated when a HTTP seed name lookup fails. + struct TORRENT_EXPORT url_seed_alert final : torrent_alert + { + // internal + url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, error_code const& e); + url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, string_view m); + + TORRENT_DEFINE_ALERT(url_seed_alert, 42) + + static constexpr alert_category_t static_category = alert_category::peer | alert_category::error; + std::string message() const override; + + // the error the web seed encountered. If this is not set, the server + // sent an error message, call ``error_message()``. + error_code const error; + + // the URL the error is associated with + char const* server_url() const; + + // in case the web server sent an error message, this function returns + // it. + char const* error_message() const; + + private: + aux::allocation_slot m_url_idx; + aux::allocation_slot m_msg_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // the HTTP seed that failed + std::string TORRENT_DEPRECATED_MEMBER url; + + // the error message, potentially from the server + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + + }; + + // If the storage fails to read or write files that it needs access to, this alert is + // generated and the torrent is paused. + struct TORRENT_EXPORT file_error_alert final : torrent_alert + { + // internal + file_error_alert(aux::stack_allocator& alloc, error_code const& ec + , string_view file, operation_t op, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(file_error_alert, 43, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status + | alert_category::error + | alert_category::storage; + std::string message() const override; + + // the error code describing the error. + error_code const error; + + // indicates which underlying operation caused the error + operation_t op; + + // the file that experienced the error + char const* filename() const; + + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + char const* TORRENT_DEPRECATED_MEMBER operation; + // the path to the file that was accessed when the error occurred. + std::string TORRENT_DEPRECATED_MEMBER file; + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is generated when the metadata has been completely received and the info-hash + // failed to match it. i.e. the metadata that was received was corrupt. libtorrent will + // automatically retry to fetch it in this case. This is only relevant when running a + // torrent-less download, with the metadata extension provided by libtorrent. + struct TORRENT_EXPORT metadata_failed_alert final : torrent_alert + { + // internal + metadata_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec); + + TORRENT_DEFINE_ALERT(metadata_failed_alert, 44) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // indicates what failed when parsing the metadata. This error is + // what's returned from lazy_bdecode(). + error_code const error; + }; + + // This alert is generated when the metadata has been completely received and the torrent + // can start downloading. It is not generated on torrents that are started with metadata, but + // only those that needs to download it from peers (when utilizing the libtorrent extension). + // + // There are no additional data members in this alert. + // + // Typically, when receiving this alert, you would want to save the torrent file in order + // to load it back up again when the session is restarted. Here's an example snippet of + // code to do that: + // + // .. code:: c++ + // + // torrent_handle h = alert->handle(); + // if (h.is_valid()) { + // std::shared_ptr ti = h.torrent_file(); + // create_torrent ct(*ti); + // entry te = ct.generate(); + // std::vector buffer; + // bencode(std::back_inserter(buffer), te); + // FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+"); + // if (f) { + // fwrite(&buffer[0], 1, buffer.size(), f); + // fclose(f); + // } + // } + // + struct TORRENT_EXPORT metadata_received_alert final : torrent_alert + { + // internal + metadata_received_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT(metadata_received_alert, 45) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + }; + + // This alert is posted when there is an error on a UDP socket. The + // UDP sockets are used for all uTP, DHT and UDP tracker traffic. They are + // global to the session. + struct TORRENT_EXPORT udp_error_alert final : alert + { + // internal + udp_error_alert( + aux::stack_allocator& alloc + , udp::endpoint const& ep + , operation_t op + , error_code const& ec); + + TORRENT_DEFINE_ALERT(udp_error_alert, 46) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the source address associated with the error (if any) + aux::noexcept_movable endpoint; + + // the operation that failed + operation_t operation; + + // the error code describing the error + error_code const error; + }; + + // Whenever libtorrent learns about the machines external IP, this alert is + // generated. The external IP address can be acquired from the tracker (if it + // supports that) or from peers that supports the extension protocol. + // The address can be accessed through the ``external_address`` member. + struct TORRENT_EXPORT external_ip_alert final : alert + { + // internal + external_ip_alert(aux::stack_allocator& alloc, address const& ip); + + TORRENT_DEFINE_ALERT(external_ip_alert, 47) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the IP address that is believed to be our external IP + aux::noexcept_movable
    external_address; + }; + + enum class socket_type_t : std::uint8_t + { + tcp, tcp_ssl, udp, i2p, socks5, utp_ssl + }; + + // This alert is generated when none of the ports, given in the port range, to + // session can be opened for listening. The ``listen_interface`` member is the + // interface that failed, ``error`` is the error code describing the failure. + // + // In the case an endpoint was created before generating the alert, it is + // represented by ``address`` and ``port``. The combinations of socket type + // and operation in which such address and port are not valid are: + // accept - i2p + // accept - socks5 + // enum_if - tcp + // + // libtorrent may sometimes try to listen on port 0, if all other ports failed. + // Port 0 asks the operating system to pick a port that's free). If that fails + // you may see a listen_failed_alert with port 0 even if you didn't ask to + // listen on it. + struct TORRENT_EXPORT listen_failed_alert final : alert + { +#if TORRENT_ABI_VERSION == 1 + enum socket_type_t : std::uint8_t + { + tcp TORRENT_DEPRECATED_ENUM, + tcp_ssl TORRENT_DEPRECATED_ENUM, + udp TORRENT_DEPRECATED_ENUM, + i2p TORRENT_DEPRECATED_ENUM, + socks5 TORRENT_DEPRECATED_ENUM, + utp_ssl TORRENT_DEPRECATED_ENUM + }; +#endif + + // internal + listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , lt::address const& listen_addr, int listen_port + , operation_t op, error_code const& ec, lt::socket_type_t t); + + listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , tcp::endpoint const& ep, operation_t op, error_code const& ec + , lt::socket_type_t t); + + listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , udp::endpoint const& ep, operation_t op, error_code const& ec + , lt::socket_type_t t); + + listen_failed_alert(aux::stack_allocator& alloc, string_view iface + , operation_t op, error_code const& ec, lt::socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_failed_alert, 48, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::status | alert_category::error; + std::string message() const override; + + // the network device libtorrent attempted to listen on, or the IP address + char const* listen_interface() const; + + // the error the system returned + error_code const error; + + // the underlying operation that failed + operation_t op; + + // the type of listen socket this alert refers to. + lt::socket_type_t const socket_type; + + // the address libtorrent attempted to listen on + // see alert documentation for validity of this value + aux::noexcept_movable address; + + // the port libtorrent attempted to listen on + // see alert documentation for validity of this value + int const port; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_interface_idx; +#if TORRENT_ABI_VERSION == 1 + public: + enum TORRENT_DEPRECATED_ENUM op_t + { + parse_addr TORRENT_DEPRECATED_ENUM, + open TORRENT_DEPRECATED_ENUM, + bind TORRENT_DEPRECATED_ENUM, + listen TORRENT_DEPRECATED_ENUM, + get_socket_name TORRENT_DEPRECATED_ENUM, + accept TORRENT_DEPRECATED_ENUM, + enum_if TORRENT_DEPRECATED_ENUM, + bind_to_device TORRENT_DEPRECATED_ENUM + }; + + // the specific low level operation that failed. See op_t. + int const TORRENT_DEPRECATED_MEMBER operation; + + // the address and port libtorrent attempted to listen on + aux::noexcept_movable TORRENT_DEPRECATED_MEMBER endpoint; + + // the type of listen socket this alert refers to. + socket_type_t TORRENT_DEPRECATED_MEMBER sock_type; +#endif + }; + + // This alert is posted when the listen port succeeds to be opened on a + // particular interface. ``address`` and ``port`` is the endpoint that + // successfully was opened for listening. + struct TORRENT_EXPORT listen_succeeded_alert final : alert + { +#if TORRENT_ABI_VERSION == 1 + enum socket_type_t : std::uint8_t + { + tcp TORRENT_DEPRECATED_ENUM, + tcp_ssl TORRENT_DEPRECATED_ENUM, + udp TORRENT_DEPRECATED_ENUM, + i2p TORRENT_DEPRECATED_ENUM, + socks5 TORRENT_DEPRECATED_ENUM, + utp_ssl TORRENT_DEPRECATED_ENUM + }; +#endif + + // internal + listen_succeeded_alert(aux::stack_allocator& alloc + , lt::address const& listen_addr + , int listen_port + , lt::socket_type_t t); + + listen_succeeded_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep + , lt::socket_type_t t); + + listen_succeeded_alert(aux::stack_allocator& alloc + , udp::endpoint const& ep + , lt::socket_type_t t); + + TORRENT_DEFINE_ALERT_PRIO(listen_succeeded_alert, 49, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // the address libtorrent ended up listening on. This address + // refers to the local interface. + aux::noexcept_movable address; + + // the port libtorrent ended up listening on. + int const port; + + // the type of listen socket this alert refers to. + lt::socket_type_t const socket_type; + +#if TORRENT_ABI_VERSION == 1 + // the endpoint libtorrent ended up listening on. The address + // refers to the local interface and the port is the listen port. + aux::noexcept_movable TORRENT_DEPRECATED_MEMBER endpoint; + + // the type of listen socket this alert refers to. + socket_type_t TORRENT_DEPRECATED_MEMBER sock_type; +#endif + }; + + // This alert is generated when a NAT router was successfully found but some + // part of the port mapping request failed. It contains a text message that + // may help the user figure out what is wrong. This alert is not generated in + // case it appears the client is not running on a NAT:ed network or if it + // appears there is no NAT router that can be remote controlled to add port + // mappings. + struct TORRENT_EXPORT portmap_error_alert final : alert + { + // internal + portmap_error_alert(aux::stack_allocator& alloc, port_mapping_t i + , portmap_transport t + , error_code const& e); + + TORRENT_DEFINE_ALERT(portmap_error_alert, 50) + + static constexpr alert_category_t static_category = alert_category::port_mapping + | alert_category::error; + std::string message() const override; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + port_mapping_t const mapping; + + // UPnP or NAT-PMP + portmap_transport map_transport; + + // tells you what failed. + error_code const error; +#if TORRENT_ABI_VERSION == 1 + // is 0 for NAT-PMP and 1 for UPnP. + int const TORRENT_DEPRECATED_MEMBER map_type; + + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is generated when a NAT router was successfully found and + // a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP + // capable router, this is typically generated once when mapping the TCP + // port and, if DHT is enabled, when the UDP port is mapped. + struct TORRENT_EXPORT portmap_alert final : alert + { + // internal + portmap_alert(aux::stack_allocator& alloc, port_mapping_t i, int port + , portmap_transport t, portmap_protocol protocol); + + TORRENT_DEFINE_ALERT(portmap_alert, 51) + + static constexpr alert_category_t static_category = alert_category::port_mapping; + std::string message() const override; + + // refers to the mapping index of the port map that failed, i.e. + // the index returned from add_mapping(). + port_mapping_t const mapping; + + // the external port allocated for the mapping. + int const external_port; + + portmap_protocol const map_protocol; + + portmap_transport const map_transport; + +#if TORRENT_ABI_VERSION == 1 + enum TORRENT_DEPRECATED_ENUM protocol_t + { + tcp, + udp + }; + + // the protocol this mapping was for. one of protocol_t enums + int const TORRENT_DEPRECATED_MEMBER protocol; + + // 0 for NAT-PMP and 1 for UPnP. + int const TORRENT_DEPRECATED_MEMBER map_type; +#endif + }; + + // This alert is generated to log informational events related to either + // UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP + // and 1 = UPnP). Displaying these messages to an end user is only useful + // for debugging the UPnP or NAT-PMP implementation. This alert is only + // posted if the alert_category::port_mapping_log flag is enabled in + // the alert mask. + struct TORRENT_EXPORT portmap_log_alert final : alert + { + // internal + portmap_log_alert(aux::stack_allocator& alloc, portmap_transport t, const char* m); + + TORRENT_DEFINE_ALERT(portmap_log_alert, 52) + + static constexpr alert_category_t static_category = alert_category::port_mapping_log; + std::string message() const override; + + portmap_transport const map_transport; + + // the message associated with this log line + char const* log_message() const; + + private: + + std::reference_wrapper m_alloc; + + aux::allocation_slot m_log_idx; +#if TORRENT_ABI_VERSION == 1 + public: + int const TORRENT_DEPRECATED_MEMBER map_type; + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + + }; + + // This alert is generated when a fast resume file has been passed to + // add_torrent() but the files on disk did not match the fast resume file. + // The error_code explains the reason why the resume file was rejected. + struct TORRENT_EXPORT fastresume_rejected_alert final : torrent_alert + { + // internal + fastresume_rejected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& ec, string_view file + , operation_t op); + + TORRENT_DEFINE_ALERT_PRIO(fastresume_rejected_alert, 53, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::status + | alert_category::error; + std::string message() const override; + + error_code error; + + // If the error happened to a specific file, this returns the path to it. + char const* file_path() const; + + // the underlying operation that failed + operation_t op; + + private: + aux::allocation_slot m_path_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // If the error happened in a disk operation. a 0-terminated string of + // the name of that operation. ``operation`` is nullptr otherwise. + char const* TORRENT_DEPRECATED_MEMBER operation; + + // If the error happened to a specific file, ``file`` is the path to it. + std::string TORRENT_DEPRECATED_MEMBER file; + std::string TORRENT_DEPRECATED_MEMBER msg; +#endif + }; + + // This alert is posted when an incoming peer connection, or a peer that's about to be added + // to our peer list, is blocked for some reason. This could be any of: + // + // * the IP filter + // * i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm) + // * the port filter + // * the peer has a low port and ``no_connect_privileged_ports`` is enabled + // * the protocol of the peer is blocked (uTP/TCP blocking) + struct TORRENT_EXPORT peer_blocked_alert final : peer_alert + { + // internal + peer_blocked_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, int r); + + TORRENT_DEFINE_ALERT(peer_blocked_alert, 54) + + static constexpr alert_category_t static_category = alert_category::ip_block; + std::string message() const override; + + enum reason_t + { + ip_filter, + port_filter, + i2p_mixed, + privileged_ports, + utp_disabled, + tcp_disabled, + invalid_local_interface + }; + + // the reason for the peer being blocked. Is one of the values from the + // reason_t enum. + int const reason; + }; + + // This alert is generated when a DHT node announces to an info-hash on our + // DHT node. It belongs to the ``dht_notification`` category. + struct TORRENT_EXPORT dht_announce_alert final : alert + { + // internal + dht_announce_alert(aux::stack_allocator& alloc, address const& i, int p + , sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_announce_alert, 55) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + aux::noexcept_movable
    ip; + int port; + sha1_hash info_hash; + }; + + // This alert is generated when a DHT node sends a ``get_peers`` message to + // our DHT node. It belongs to the ``dht_notification`` category. + struct TORRENT_EXPORT dht_get_peers_alert final : alert + { + // internal + dht_get_peers_alert(aux::stack_allocator& alloc, sha1_hash const& ih); + + TORRENT_DEFINE_ALERT(dht_get_peers_alert, 56) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + sha1_hash info_hash; + }; + + // This alert is posted approximately once every second, and it contains + // byte counters of most statistics that's tracked for torrents. Each active + // torrent posts these alerts regularly. + // This alert has been superseded by calling ``post_torrent_updates()`` + // regularly on the session object. This alert will be removed + struct TORRENT_EXPORT stats_alert final : torrent_alert + { + // internal + stats_alert(aux::stack_allocator& alloc, torrent_handle const& h, int interval + , stat const& s); + + TORRENT_DEFINE_ALERT(stats_alert, 57) + + static constexpr alert_category_t static_category = alert_category::stats; + std::string message() const override; + + enum stats_channel + { + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, +#if TORRENT_ABI_VERSION == 1 + upload_dht_protocol TORRENT_DEPRECATED_ENUM, + upload_tracker_protocol TORRENT_DEPRECATED_ENUM, +#else + deprecated1, + deprecated2, +#endif + download_ip_protocol, +#if TORRENT_ABI_VERSION == 1 + download_dht_protocol TORRENT_DEPRECATED_ENUM, + download_tracker_protocol TORRENT_DEPRECATED_ENUM, +#else + deprecated3, + deprecated4, +#endif + num_channels + }; + + // an array of samples. The enum describes what each sample is a + // measurement of. All of these are raw, and not smoothing is performed. + std::array const transferred; + + // the number of milliseconds during which these stats were collected. + // This is typically just above 1000, but if CPU is limited, it may be + // higher than that. + int const interval; + }; + + // This alert is posted when the disk cache has been flushed for a specific + // torrent as a result of a call to torrent_handle::flush_cache(). This + // alert belongs to the ``storage_notification`` category, which must be + // enabled to let this alert through. The alert is also posted when removing + // a torrent from the session, once the outstanding cache flush is complete + // and the torrent does no longer have any files open. + struct TORRENT_EXPORT cache_flushed_alert final : torrent_alert + { + // internal + cache_flushed_alert(aux::stack_allocator& alloc, torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(cache_flushed_alert, 58, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::storage; + }; + +#if TORRENT_ABI_VERSION == 1 + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // This alert is posted when a bittorrent feature is blocked because of the + // anonymous mode. For instance, if the tracker proxy is not set up, no + // trackers will be used, because trackers can only be used through proxies + // when in anonymous mode. + struct TORRENT_DEPRECATED_EXPORT anonymous_mode_alert final : torrent_alert + { + // internal + anonymous_mode_alert(aux::stack_allocator& alloc, torrent_handle const& h + , int k, string_view s); + + TORRENT_DEFINE_ALERT(anonymous_mode_alert, 59) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + enum kind_t + { + // means that there's no proxy set up for tracker + // communication and the tracker will not be contacted. + // The tracker which this failed for is specified in the ``str`` member. + tracker_not_anonymous = 0 + }; + + // specifies what error this is, see kind_t. + int kind; + std::string str; + }; + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // TORRENT_ABI_VERSION + + // This alert is generated when we receive a local service discovery message + // from a peer for a torrent we're currently participating in. + struct TORRENT_EXPORT lsd_peer_alert final : peer_alert + { + // internal + lsd_peer_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(lsd_peer_alert, 60) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + }; + + // This alert is posted whenever a tracker responds with a ``trackerid``. + // The tracker ID is like a cookie. libtorrent will store the tracker ID + // for this tracker and repeat it in subsequent announces. + struct TORRENT_EXPORT trackerid_alert final : tracker_alert + { + // internal + trackerid_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep , string_view u, const std::string& id); + + TORRENT_DEFINE_ALERT(trackerid_alert, 61) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // The tracker ID returned by the tracker + char const* tracker_id() const; + + private: + aux::allocation_slot m_tracker_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // The tracker ID returned by the tracker + std::string TORRENT_DEPRECATED_MEMBER trackerid; +#endif + }; + + // This alert is posted when the initial DHT bootstrap is done. + struct TORRENT_EXPORT dht_bootstrap_alert final : alert + { + // internal + explicit dht_bootstrap_alert(aux::stack_allocator& alloc); + + TORRENT_DEFINE_ALERT(dht_bootstrap_alert, 62) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + }; + + // This is posted whenever a torrent is transitioned into the error state. + struct TORRENT_EXPORT torrent_error_alert final : torrent_alert + { + // internal + torrent_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , error_code const& e, string_view f); + + TORRENT_DEFINE_ALERT_PRIO(torrent_error_alert, 64, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::error | alert_category::status; + std::string message() const override; + + // specifies which error the torrent encountered. + error_code const error; + + // the filename (or object) the error occurred on. + char const* filename() const; + + private: + aux::allocation_slot m_file_idx; +#if TORRENT_ABI_VERSION == 1 + public: + // the filename (or object) the error occurred on. + std::string TORRENT_DEPRECATED_MEMBER error_file; +#endif + + }; + + // This is always posted for SSL torrents. This is a reminder to the client that + // the torrent won't work unless torrent_handle::set_ssl_certificate() is called with + // a valid certificate. Valid certificates MUST be signed by the SSL certificate + // in the .torrent file. + struct TORRENT_EXPORT torrent_need_cert_alert final : torrent_alert + { + // internal + torrent_need_cert_alert(aux::stack_allocator& alloc + , torrent_handle const& h); + + TORRENT_DEFINE_ALERT_PRIO(torrent_need_cert_alert, 65, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; +#if TORRENT_ABI_VERSION == 1 + error_code const TORRENT_DEPRECATED_MEMBER error; +#endif + }; + + // The incoming connection alert is posted every time we successfully accept + // an incoming connection, through any mean. The most straight-forward ways + // of accepting incoming connections are through the TCP listen socket and + // the UDP listen socket for uTP sockets. However, connections may also be + // accepted through a Socks5 or i2p listen socket, or via an SSL listen + // socket. + struct TORRENT_EXPORT incoming_connection_alert final : alert + { + // internal + incoming_connection_alert(aux::stack_allocator& alloc, int t + , tcp::endpoint const& i); + + TORRENT_DEFINE_ALERT(incoming_connection_alert, 66) + + static constexpr alert_category_t static_category = alert_category::peer; + std::string message() const override; + + // tells you what kind of socket the connection was accepted + // as: + // + // 0. none (no socket instantiated) + // 1. TCP + // 2. Socks5 + // 3. HTTP + // 4. uTP + // 5. i2p + // 6. SSL/TCP + // 7. SSL/Socks5 + // 8. HTTPS (SSL/HTTP) + // 9. SSL/uTP + // + int const socket_type; + + // is the IP address and port the connection came from. + aux::noexcept_movable endpoint; + +#if TORRENT_ABI_VERSION == 1 + // is the IP address and port the connection came from. + aux::noexcept_movable TORRENT_DEPRECATED_MEMBER ip; +#endif + }; + + // This alert is always posted when a torrent was attempted to be added + // and contains the return status of the add operation. The torrent handle of the new + // torrent can be found as the ``handle`` member in the base class. If adding + // the torrent failed, ``error`` contains the error code. + struct TORRENT_EXPORT add_torrent_alert final : torrent_alert + { + // internal + add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params const& p, error_code const& ec); + + TORRENT_DEFINE_ALERT_PRIO(add_torrent_alert, 67, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // a copy of the parameters used when adding the torrent, it can be used + // to identify which invocation to ``async_add_torrent()`` caused this alert. + add_torrent_params params; + + // set to the error, if one occurred while adding the torrent. + error_code error; + }; + + // This alert is only posted when requested by the user, by calling + // session::post_torrent_updates() on the session. It contains the torrent + // status of all torrents that changed since last time this message was + // posted. Its category is ``status_notification``, but it's not subject to + // filtering, since it's only manually posted anyway. + struct TORRENT_EXPORT state_update_alert final : alert + { + state_update_alert(aux::stack_allocator& alloc + , std::vector st); + + TORRENT_DEFINE_ALERT_PRIO(state_update_alert, 68, alert_priority_high) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // contains the torrent status of all torrents that changed since last + // time this message was posted. Note that you can map a torrent status + // to a specific torrent via its ``handle`` member. The receiving end is + // suggested to have all torrents sorted by the torrent_handle or hashed + // by it, for efficient updates. + std::vector status; + }; + +#if TORRENT_ABI_VERSION == 1 +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + struct TORRENT_DEPRECATED_EXPORT mmap_cache_alert final : alert + { + mmap_cache_alert(aux::stack_allocator& alloc + , error_code const& ec); + TORRENT_DEFINE_ALERT(mmap_cache_alert, 69) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + error_code const error; + }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif // TORRENT_ABI_VERSION + + // The session_stats_alert is posted when the user requests session statistics by + // calling post_session_stats() on the session object. Its category is + // ``status_notification``, but it is not subject to filtering, since it's only + // manually posted anyway. + // + // the ``message()`` member function returns a string representation of the values that + // properly match the line returned in ``session_stats_header_alert::message()``. + // + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + struct TORRENT_EXPORT session_stats_alert final : alert + { + // internal + session_stats_alert(aux::stack_allocator& alloc, counters const& cnt); + +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#endif + TORRENT_DEFINE_ALERT_PRIO(session_stats_alert, 70, alert_priority_critical) +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#endif + + static constexpr alert_category_t static_category = alert_category::stats; + std::string message() const override; + + // An array are a mix of *counters* and *gauges*, which meanings can be + // queries via the session_stats_metrics() function on the session. The + // mapping from a specific metric to an index into this array is constant + // for a specific version of libtorrent, but may differ for other + // versions. The intended usage is to request the mapping, i.e. call + // session_stats_metrics(), once on startup, and then use that mapping to + // interpret these values throughout the process' runtime. + // + // For more information, see the session-statistics_ section. + span counters() const; + +#if TORRENT_ABI_VERSION == 1 + std::array const TORRENT_DEPRECATED_MEMBER values; +#else + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_counters_idx; +#endif + }; + +#if TORRENT_ABI_VERSION == 1 + // hidden + // When a torrent changes its info-hash, this alert is posted. This only + // happens in very specific cases. For instance, when a torrent is + // downloaded from a URL, the true info hash is not known immediately. First + // the .torrent file must be downloaded and parsed. + // + // Once this download completes, the ``torrent_update_alert`` is posted to + // notify the client of the info-hash changing. +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + struct TORRENT_DEPRECATED_EXPORT torrent_update_alert final : torrent_alert + { + // internal + torrent_update_alert(aux::stack_allocator& alloc, torrent_handle h + , sha1_hash const& old_hash, sha1_hash const& new_hash); + + TORRENT_DEFINE_ALERT_PRIO(torrent_update_alert, 71, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::status; + std::string message() const override; + + // ``old_ih`` and ``new_ih`` are the previous and new info-hash for the torrent, respectively. + sha1_hash old_ih; + sha1_hash new_ih; + }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif // TORRENT_ABI_VERSION + + // posted when something fails in the DHT. This is not necessarily a fatal + // error, but it could prevent proper operation + struct TORRENT_EXPORT dht_error_alert final : alert + { + // internal + dht_error_alert(aux::stack_allocator& alloc, operation_t op + , error_code const& ec); + + TORRENT_DEFINE_ALERT(dht_error_alert, 73) + + static constexpr alert_category_t static_category = alert_category::error | alert_category::dht; + std::string message() const override; + + // the error code + error_code error; + + // the operation that failed + operation_t op; + +#if TORRENT_ABI_VERSION == 1 + enum op_t + { + unknown TORRENT_DEPRECATED_ENUM, + hostname_lookup TORRENT_DEPRECATED_ENUM + }; + + // the operation that failed + op_t const TORRENT_DEPRECATED_MEMBER operation; +#endif + }; + + // this alert is posted as a response to a call to session::get_item(), + // specifically the overload for looking up immutable items in the DHT. + struct TORRENT_EXPORT dht_immutable_item_alert final : alert + { + // internal + dht_immutable_item_alert(aux::stack_allocator& alloc, sha1_hash const& t + , entry const& i); + + TORRENT_DEFINE_ALERT_PRIO(dht_immutable_item_alert, 74, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::dht; + + std::string message() const override; + + // the target hash of the immutable item. This must + // match the SHA-1 hash of the bencoded form of ``item``. + sha1_hash target; + + // the data for this item + entry item; + }; + + // this alert is posted as a response to a call to session::get_item(), + // specifically the overload for looking up mutable items in the DHT. + struct TORRENT_EXPORT dht_mutable_item_alert final : alert + { + // internal + dht_mutable_item_alert(aux::stack_allocator& alloc + , std::array const& k, std::array const& sig + , std::int64_t sequence, string_view s, entry const& i, bool a); + + TORRENT_DEFINE_ALERT_PRIO(dht_mutable_item_alert, 75, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the public key that was looked up + std::array key; + + // the signature of the data. This is not the signature of the + // plain encoded form of the item, but it includes the sequence number + // and possibly the hash as well. See the dht_store document for more + // information. This is primarily useful for echoing back in a store + // request. + std::array signature; + + // the sequence number of this item + std::int64_t seq; + + // the salt, if any, used to lookup and store this item. If no + // salt was used, this is an empty string + std::string salt; + + // the data for this item + entry item; + + // the last response for mutable data is authoritative. + bool authoritative; + }; + + // this is posted when a DHT put operation completes. This is useful if the + // client is waiting for a put to complete before shutting down for instance. + struct TORRENT_EXPORT dht_put_alert final : alert + { + // internal + dht_put_alert(aux::stack_allocator& alloc, sha1_hash const& t, int n); + dht_put_alert(aux::stack_allocator& alloc, std::array const& key + , std::array const& sig + , std::string s + , std::int64_t sequence_number + , int n); + + TORRENT_DEFINE_ALERT(dht_put_alert, 76) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the target hash the item was stored under if this was an *immutable* + // item. + sha1_hash target; + + // if a mutable item was stored, these are the public key, signature, + // salt and sequence number the item was stored under. + std::array public_key; + std::array signature; + std::string salt; + std::int64_t seq; + + // DHT put operation usually writes item to k nodes, maybe the node + // is stale so no response, or the node doesn't support 'put', or the + // token for write is out of date, etc. num_success is the number of + // successful responses we got from the puts. + int num_success; + }; + + // this alert is used to report errors in the i2p SAM connection + struct TORRENT_EXPORT i2p_alert final : alert + { + // internal + i2p_alert(aux::stack_allocator& alloc, error_code const& ec); + + TORRENT_DEFINE_ALERT(i2p_alert, 77) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the error that occurred in the i2p SAM connection + error_code error; + }; + + // This alert is generated when we send a get_peers request + // It belongs to the ``dht_notification`` category. + struct TORRENT_EXPORT dht_outgoing_get_peers_alert final : alert + { + // internal + dht_outgoing_get_peers_alert(aux::stack_allocator& alloc + , sha1_hash const& ih, sha1_hash const& obfih + , udp::endpoint ep); + + TORRENT_DEFINE_ALERT(dht_outgoing_get_peers_alert, 78) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the info_hash of the torrent we're looking for peers for. + sha1_hash info_hash; + + // if this was an obfuscated lookup, this is the info-hash target + // actually sent to the node. + sha1_hash obfuscated_info_hash; + + // the endpoint we're sending this query to + aux::noexcept_movable endpoint; + +#if TORRENT_ABI_VERSION == 1 + // the endpoint we're sending this query to + aux::noexcept_movable TORRENT_DEPRECATED_MEMBER ip; +#endif + }; + + // This alert is posted by some session wide event. Its main purpose is + // trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::session_log`` bit. + // Furthermore, it's by default disabled as a build configuration. + struct TORRENT_EXPORT log_alert final : alert + { + // internal + log_alert(aux::stack_allocator& alloc, char const* log); + log_alert(aux::stack_allocator& alloc, char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(log_alert, 79) + + static constexpr alert_category_t static_category = alert_category::session_log; + std::string message() const override; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_str_idx; + }; + + // This alert is posted by torrent wide events. It's meant to be used for + // trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::torrent_log`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT torrent_log_alert final : torrent_alert + { + // internal + torrent_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(torrent_log_alert, 80) + + static constexpr alert_category_t static_category = alert_category::torrent_log; + std::string message() const override; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + aux::allocation_slot m_str_idx; + }; + + // This alert is posted by events specific to a peer. It's meant to be used + // for trouble shooting and debugging. It's not enabled by the default alert + // mask and is enabled by the ``alert_category::peer_log`` bit. By + // default it is disabled as a build configuration. + struct TORRENT_EXPORT peer_log_alert final : peer_alert + { + // describes whether this log refers to in-flow or out-flow of the + // peer. The exception is ``info`` which is neither incoming or outgoing. + enum direction_t + { + incoming_message, + outgoing_message, + incoming, + outgoing, + info + }; + + // internal + peer_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i, peer_id const& pi + , peer_log_alert::direction_t dir + , char const* event, char const* fmt, va_list v); + + TORRENT_DEFINE_ALERT(peer_log_alert, 81) + + static constexpr alert_category_t static_category = alert_category::peer_log; + std::string message() const override; + + // string literal indicating the kind of event. For messages, this is the + // message name. + char const* event_type; + + direction_t direction; + + // returns the log message + char const* log_message() const; + +#if TORRENT_ABI_VERSION == 1 + // returns the log message + TORRENT_DEPRECATED + char const* msg() const; +#endif + + private: + aux::allocation_slot m_str_idx; + }; + + // posted if the local service discovery socket fails to start properly. + // it's categorized as ``error_notification``. + struct TORRENT_EXPORT lsd_error_alert final : alert + { + // internal + lsd_error_alert(aux::stack_allocator& alloc, error_code const& ec); + + TORRENT_DEFINE_ALERT(lsd_error_alert, 82) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // The error code + error_code error; + }; + + // holds statistics about a current dht_lookup operation. + // a DHT lookup is the traversal of nodes, looking up a + // set of target nodes in the DHT for retrieving and possibly + // storing information in the DHT + struct TORRENT_EXPORT dht_lookup + { + // string literal indicating which kind of lookup this is + char const* type; + + // the number of outstanding request to individual nodes + // this lookup has right now + int outstanding_requests; + + // the total number of requests that have timed out so far + // for this lookup + int timeouts; + + // the total number of responses we have received for this + // lookup so far for this lookup + int responses; + + // the branch factor for this lookup. This is the number of + // nodes we keep outstanding requests to in parallel by default. + // when nodes time out we may increase this. + int branch_factor; + + // the number of nodes left that could be queries for this + // lookup. Many of these are likely to be part of the trail + // while performing the lookup and would never end up actually + // being queried. + int nodes_left; + + // the number of seconds ago the + // last message was sent that's still + // outstanding + int last_sent; + + // the number of outstanding requests + // that have exceeded the short timeout + // and are considered timed out in the + // sense that they increased the branch + // factor + int first_timeout; + + // the node-id or info-hash target for this lookup + sha1_hash target; + }; + + // contains current DHT state. Posted in response to session::post_dht_stats(). + struct TORRENT_EXPORT dht_stats_alert final : alert + { + // internal + dht_stats_alert(aux::stack_allocator& alloc + , std::vector table + , std::vector requests); + + TORRENT_DEFINE_ALERT(dht_stats_alert, 83) + + static constexpr alert_category_t static_category = alert_category::stats; + std::string message() const override; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector routing_table; + }; + + // posted every time an incoming request from a peer is accepted and queued + // up for being serviced. This alert is only posted if + // the alert_category::incoming_request flag is enabled in the alert + // mask. + struct TORRENT_EXPORT incoming_request_alert final : peer_alert + { + // internal + incoming_request_alert(aux::stack_allocator& alloc + , peer_request r, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id); + + static constexpr alert_category_t static_category = alert_category::incoming_request; + TORRENT_DEFINE_ALERT(incoming_request_alert, 84) + + std::string message() const override; + + // the request this peer sent to us + peer_request req; + }; + + // debug logging of the DHT when dht_log_notification is set in the alert + // mask. + struct TORRENT_EXPORT dht_log_alert final : alert + { + enum dht_module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + // internal + dht_log_alert(aux::stack_allocator& alloc + , dht_module_t m, char const* fmt, va_list v); + + static constexpr alert_category_t static_category = alert_category::dht_log; + TORRENT_DEFINE_ALERT(dht_log_alert, 85) + + std::string message() const override; + + // the log message + char const* log_message() const; + + // the module, or part, of the DHT that produced this log message. + dht_module_t module; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + }; + + // This alert is posted every time a DHT message is sent or received. It is + // only posted if the ``alert_category::dht_log`` alert category is + // enabled. It contains a verbatim copy of the message. + struct TORRENT_EXPORT dht_pkt_alert final : alert + { + enum direction_t + { incoming, outgoing }; + + // internal + dht_pkt_alert(aux::stack_allocator& alloc, span buf + , dht_pkt_alert::direction_t d, udp::endpoint const& ep); + + static constexpr alert_category_t static_category = alert_category::dht_log; + TORRENT_DEFINE_ALERT(dht_pkt_alert, 86) + + std::string message() const override; + + // returns a pointer to the packet buffer and size of the packet, + // respectively. This buffer is only valid for as long as the alert itself + // is valid, which is owned by libtorrent and reclaimed whenever + // pop_alerts() is called on the session. + span pkt_buf() const; + + // whether this is an incoming or outgoing packet. + direction_t direction; + + // the DHT node we received this packet from, or sent this packet to + // (depending on ``direction``). + aux::noexcept_movable node; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + int const m_size; +#if TORRENT_ABI_VERSION == 1 + public: + direction_t TORRENT_DEPRECATED_MEMBER dir; +#endif + + }; + + // Posted when we receive a response to a DHT get_peers request. + struct TORRENT_EXPORT dht_get_peers_reply_alert final : alert + { + // internal + dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& v); + + static constexpr alert_category_t static_category = alert_category::dht_operation; + TORRENT_DEFINE_ALERT(dht_get_peers_reply_alert, 87) + + std::string message() const override; + + sha1_hash info_hash; + + int num_peers() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void peers(std::vector& v) const; +#endif + std::vector peers() const; + + private: + std::reference_wrapper m_alloc; + int m_v4_num_peers = 0; + int m_v6_num_peers = 0; + aux::allocation_slot m_v4_peers_idx; + aux::allocation_slot m_v6_peers_idx; + }; + + // This is posted exactly once for every call to session_handle::dht_direct_request. + // If the request failed, response() will return a default constructed bdecode_node. + struct TORRENT_EXPORT dht_direct_response_alert final : alert + { + // internal + dht_direct_response_alert(aux::stack_allocator& alloc, void* userdata + , udp::endpoint const& addr, bdecode_node const& response); + + // for when there was a timeout so we don't have a response + dht_direct_response_alert(aux::stack_allocator& alloc, void* userdata + , udp::endpoint const& addr); + + TORRENT_DEFINE_ALERT_PRIO(dht_direct_response_alert, 88, alert_priority_critical) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + void const* userdata; + aux::noexcept_movable endpoint; + + bdecode_node response() const; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_response_idx; + int const m_response_size; +#if TORRENT_ABI_VERSION == 1 + public: + aux::noexcept_movable TORRENT_DEPRECATED_MEMBER addr; +#endif + }; + + // hidden + using picker_flags_t = flags::bitfield_flag; + + // this is posted when one or more blocks are picked by the piece picker, + // assuming the verbose piece picker logging is enabled (see + // picker_log_notification). + struct TORRENT_EXPORT picker_log_alert final : peer_alert + { + // internal + picker_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, picker_flags_t flags + , span blocks); + + TORRENT_DEFINE_ALERT(picker_log_alert, 89) + + static constexpr alert_category_t static_category = alert_category::picker_log; + std::string message() const override; + + static constexpr picker_flags_t partial_ratio = 0_bit; + static constexpr picker_flags_t prioritize_partials = 1_bit; + static constexpr picker_flags_t rarest_first_partials = 2_bit; + static constexpr picker_flags_t rarest_first = 3_bit; + static constexpr picker_flags_t reverse_rarest_first = 4_bit; + static constexpr picker_flags_t suggested_pieces = 5_bit; + static constexpr picker_flags_t prio_sequential_pieces = 6_bit; + static constexpr picker_flags_t sequential_pieces = 7_bit; + static constexpr picker_flags_t reverse_pieces = 8_bit; + static constexpr picker_flags_t time_critical = 9_bit; + static constexpr picker_flags_t random_pieces = 10_bit; + static constexpr picker_flags_t prefer_contiguous = 11_bit; + static constexpr picker_flags_t reverse_sequential = 12_bit; + static constexpr picker_flags_t backup1 = 13_bit; + static constexpr picker_flags_t backup2 = 14_bit; + static constexpr picker_flags_t end_game = 15_bit; + static constexpr picker_flags_t extent_affinity = 16_bit; + + // this is a bitmask of which features were enabled for this particular + // pick. The bits are defined in the picker_flags_t enum. + picker_flags_t const picker_flags; + + std::vector blocks() const; + + private: + aux::allocation_slot m_array_idx; + int const m_num_blocks; + }; + + // this alert is posted when the session encounters a serious error, + // potentially fatal + struct TORRENT_EXPORT session_error_alert final : alert + { + // internal + session_error_alert(aux::stack_allocator& alloc, error_code err + , string_view error_str); + + TORRENT_DEFINE_ALERT(session_error_alert, 90) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // The error code, if one is associated with this error + error_code const error; + + private: + std::reference_wrapper m_alloc; + aux::allocation_slot m_msg_idx; + }; + + // posted in response to a call to session::dht_live_nodes(). It contains the + // live nodes from the DHT routing table of one of the DHT nodes running + // locally. + struct TORRENT_EXPORT dht_live_nodes_alert final : alert + { + // internal + dht_live_nodes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , std::vector> const& nodes); + + TORRENT_DEFINE_ALERT(dht_live_nodes_alert, 91) + + static constexpr alert_category_t static_category = alert_category::dht; + std::string message() const override; + + // the local DHT node's node-ID this routing table belongs to + sha1_hash node_id; + + // the number of nodes in the routing table and the actual nodes. + int num_nodes() const; + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + + // The session_stats_header alert is posted the first time + // post_session_stats() is called + // + // the ``message()`` member function returns a string representation of the + // header that properly match the stats values string returned in + // ``session_stats_alert::message()``. + // + // this specific output is parsed by tools/parse_session_stats.py + // if this is changed, that parser should also be changed + struct TORRENT_EXPORT session_stats_header_alert final : alert + { + // internal + explicit session_stats_header_alert(aux::stack_allocator& alloc); + TORRENT_DEFINE_ALERT(session_stats_header_alert, 92) + + static constexpr alert_category_t static_category = alert_category::stats; + std::string message() const override; + }; + + // posted as a response to a call to session::dht_sample_infohashes() with + // the information from the DHT response message. + struct TORRENT_EXPORT dht_sample_infohashes_alert final : alert + { + // internal + dht_sample_infohashes_alert(aux::stack_allocator& alloc + , udp::endpoint const& endp + , time_duration interval + , int num + , std::vector const& samples + , std::vector> const& nodes); + + static constexpr alert_category_t static_category = alert_category::dht_operation; + TORRENT_DEFINE_ALERT(dht_sample_infohashes_alert, 93) + + std::string message() const override; + + // the node the request was sent to (and this response was received from) + aux::noexcept_movable endpoint; + + // the interval to wait before making another request to this node + time_duration const interval; + + // This field indicates how many info-hash keys are currently in the node's storage. + // If the value is larger than the number of returned samples it indicates that the + // indexer may obtain additional samples after waiting out the interval. + int const num_infohashes; + + // returns the number of info-hashes returned by the node, as well as the + // actual info-hashes. ``num_samples()`` is more efficient than + // ``samples().size()``. + int num_samples() const; + std::vector samples() const; + + // The total number of nodes returned by ``nodes()``. + int num_nodes() const; + + // This is the set of more DHT nodes returned by the request. + // + // The information is included so that indexing nodes can perform a key + // space traversal with a single RPC per node by adjusting the target + // value for each RPC. + std::vector> nodes() const; + + private: + std::reference_wrapper m_alloc; + int const m_num_samples; + aux::allocation_slot m_samples_idx; + int m_v4_num_nodes = 0; + int m_v6_num_nodes = 0; + aux::allocation_slot m_v4_nodes_idx; + aux::allocation_slot m_v6_nodes_idx; + }; + + // This alert is posted when a block intended to be sent to a peer is placed in the + // send buffer. Note that if the connection is closed before the send buffer is sent, + // the alert may be posted without the bytes having been sent to the peer. + // It belongs to the ``upload_notification`` category. + struct TORRENT_EXPORT block_uploaded_alert final : peer_alert + { + // internal + block_uploaded_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num); + + TORRENT_DEFINE_ALERT(block_uploaded_alert, 94) + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + static constexpr alert_category_t static_category = + alert_category::upload + PROGRESS_NOTIFICATION + ; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + std::string message() const override; + + int const block_index; + piece_index_t const piece_index; + }; + + // this alert is posted to indicate to the client that some alerts were + // dropped. Dropped meaning that the alert failed to be delivered to the + // client. The most common cause of such failure is that the internal alert + // queue grew too big (controlled by alert_queue_size). + struct TORRENT_EXPORT alerts_dropped_alert final : alert + { + // internal + explicit alerts_dropped_alert(aux::stack_allocator& alloc + , std::bitset const&); + TORRENT_DEFINE_ALERT_PRIO(alerts_dropped_alert, 95, alert_priority_critical + 1) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // a bitmask indicating which alerts were dropped. Each bit represents the + // alert type ID, where bit 0 represents whether any alert of type 0 has + // been dropped, and so on. + std::bitset dropped_alerts; + }; + + // this alert is posted with SOCKS5 related errors, when a SOCKS5 proxy is + // configured. It's enabled with the error_notification alert category. + struct TORRENT_EXPORT socks5_alert final : alert + { + // internal + explicit socks5_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep, operation_t operation, error_code const& ec); + TORRENT_DEFINE_ALERT(socks5_alert, 96) + + static constexpr alert_category_t static_category = alert_category::error; + std::string message() const override; + + // the error + error_code error; + + // the operation that failed + operation_t op; + + // the endpoint configured as the proxy + aux::noexcept_movable ip; + }; + +TORRENT_VERSION_NAMESPACE_2_END + + // internal + TORRENT_EXTRA_EXPORT char const* performance_warning_str(performance_alert::performance_warning_t i); + + +#undef TORRENT_DEFINE_ALERT_IMPL +#undef TORRENT_DEFINE_ALERT +#undef TORRENT_DEFINE_ALERT_PRIO +#undef PROGRESS_NOTIFICATION + +} + +#endif diff --git a/include/libtorrent/announce_entry.hpp b/include/libtorrent/announce_entry.hpp new file mode 100644 index 0000000..aaf8510 --- /dev/null +++ b/include/libtorrent/announce_entry.hpp @@ -0,0 +1,244 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED +#define TORRENT_ANNOUNCE_ENTRY_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" + +#include +#include +#include + +namespace libtorrent { + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker + struct TORRENT_EXPORT announce_endpoint + { + friend class torrent; +#if TORRENT_ABI_VERSION == 1 + friend struct announce_entry; +#else + friend struct v1_2::announce_entry; +#endif + + // internal + announce_endpoint(aux::listen_socket_handle const& s, bool completed); + + // if this tracker has returned an error or warning message + // that message is stored here + std::string message; + + // if this tracker failed the last time it was contacted + // this error code specifies what error occurred + error_code last_error; + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // the time of next tracker announce + time_point32 next_announce = (time_point32::min)(); + + // no announces before this time + time_point32 min_announce = (time_point32::min)(); + + private: + // internal + aux::listen_socket_handle socket; + + public: + // TODO: include the number of peers received from this tracker, at last + // announce + + // these are either -1 or the scrape information this tracker last + // responded with. *incomplete* is the current number of downloaders in + // the swarm, *complete* is the current number of seeds in the swarm and + // *downloaded* is the cumulative number of completed downloads of this + // torrent, since the beginning of time (from this tracker's point of + // view). + + // if this tracker has returned scrape data, these fields are filled in + // with valid numbers. Otherwise they are set to -1. ``incomplete`` counts + // the number of current downloaders. ``complete`` counts the number of + // current peers completed the download, or "seeds". ``downloaded`` is the + // cumulative number of completed downloads. + int scrape_incomplete = -1; + int scrape_complete = -1; + int scrape_downloaded = -1; + + // the number of times in a row we have failed to announce to this + // tracker. + std::uint8_t fails : 7; + + // true while we're waiting for a response from the tracker. + bool updating : 1; + + // set to true when we get a valid response from an announce + // with event=started. If it is set, we won't send start in the subsequent + // announces. + bool start_sent : 1; + + // set to true when we send a event=completed. + bool complete_sent : 1; + + // internal + bool triggered_manually : 1; + + // set to false to not announce from this endpoint + bool enabled : 1; + + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + void failed(int backoff_ratio, seconds32 retry_interval = seconds32(0)); + + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + bool is_working() const { return fails == 0; } + }; + +TORRENT_VERSION_NAMESPACE_2 + + // this class holds information about one bittorrent tracker, as it + // relates to a specific torrent. + struct TORRENT_EXPORT announce_entry + { + // constructs a tracker announce entry with ``u`` as the URL. + explicit announce_entry(string_view u); + announce_entry(); + ~announce_entry(); + announce_entry(announce_entry const&); + announce_entry& operator=(announce_entry const&); + + // tracker URL as it appeared in the torrent file + std::string url; + + // the current ``&trackerid=`` argument passed to the tracker. + // this is optional and is normally empty (in which case no + // trackerid is sent). + std::string trackerid; + + // each local listen socket (endpoint) will announce to the tracker. This + // list contains state per endpoint. + std::vector endpoints; + + // the tier this tracker belongs to + std::uint8_t tier = 0; + + // the max number of failures to announce to this tracker in + // a row, before this tracker is not used anymore. 0 means unlimited + std::uint8_t fail_limit = 0; + + // flags for the source bitmask, each indicating where + // we heard about this tracker + enum tracker_source + { + // the tracker was part of the .torrent file + source_torrent = 1, + // the tracker was added programmatically via the add_tracker() function + source_client = 2, + // the tracker was part of a magnet link + source_magnet_link = 4, + // the tracker was received from the swarm via tracker exchange + source_tex = 8 + }; + + // a bitmask specifying which sources we got this tracker from. + std::uint8_t source:4; + + // set to true the first time we receive a valid response + // from this tracker. + bool verified:1; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // all of these will be set to false or 0 + // use the corresponding members in announce_endpoint + std::uint8_t TORRENT_DEPRECATED_MEMBER fails:7; + bool TORRENT_DEPRECATED_MEMBER send_stats:1; + bool TORRENT_DEPRECATED_MEMBER start_sent:1; + bool TORRENT_DEPRECATED_MEMBER complete_sent:1; + // internal + bool TORRENT_DEPRECATED_MEMBER triggered_manually:1; + bool TORRENT_DEPRECATED_MEMBER updating:1; +#endif + + // reset announce counters and clears the started sent flag. + // The announce_entry will look like we've never talked to + // the tracker. + void reset(); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2, use announce_endpoint::can_announce + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed) const; + + // deprecated in 1.2, use announce_endpoint::is_working + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + TORRENT_DEPRECATED bool is_working() const; +#endif + + // internal + announce_endpoint* find_endpoint(aux::listen_socket_handle const& s); + + // trims whitespace characters from the beginning of the URL. + void trim(); + }; + +TORRENT_VERSION_NAMESPACE_2_END + +} + +#endif diff --git a/include/libtorrent/assert.hpp b/include/libtorrent/assert.hpp new file mode 100644 index 0000000..ebfff56 --- /dev/null +++ b/include/libtorrent/assert.hpp @@ -0,0 +1,130 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ASSERT_HPP_INCLUDED +#define TORRENT_ASSERT_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" + +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + +#include +namespace libtorrent { +std::string demangle(char const* name); +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth = 0, void* ctx = nullptr); +} +#endif + +// this is to disable the warning of conditional expressions +// being constant in msvc +#ifdef _MSC_VER +#define TORRENT_WHILE_0 \ + __pragma( warning(push) ) \ + __pragma( warning(disable:4127) ) \ + while (false) \ + __pragma( warning(pop) ) +#else +#define TORRENT_WHILE_0 while (false) +#endif + + +namespace libtorrent { +// declarations of the two functions + +// internal +TORRENT_EXPORT void assert_print(char const* fmt, ...) TORRENT_FORMAT(1,2); + +// internal +TORRENT_EXPORT void assert_fail(const char* expr, int line + , char const* file, char const* function, char const* val, int kind = 0); + +} + +#if TORRENT_USE_ASSERTS + +#ifdef TORRENT_PRODUCTION_ASSERTS +extern TORRENT_EXPORT char const* libtorrent_assert_log; +#endif + +#if TORRENT_USE_IOSTREAM +#include +#endif + +#ifndef TORRENT_USE_SYSTEM_ASSERTS + +#define TORRENT_ASSERT_PRECOND(x) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, nullptr, 1); } TORRENT_WHILE_0 + +#define TORRENT_ASSERT(x) \ + do { if (x) {} else libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, nullptr, 0); } TORRENT_WHILE_0 + +#if TORRENT_USE_IOSTREAM +#define TORRENT_ASSERT_VAL(x, y) \ + do { if (x) {} else { std::stringstream __s__; __s__ << #y ": " << y; \ + libtorrent::assert_fail(#x, __LINE__, __FILE__, __func__, __s__.str().c_str(), 0); } } TORRENT_WHILE_0 + +#define TORRENT_ASSERT_FAIL_VAL(y) \ + do { std::stringstream __s__; __s__ << #y ": " << y; \ + libtorrent::assert_fail("", __LINE__, __FILE__, __func__, __s__.str().c_str(), 0); } TORRENT_WHILE_0 + +#else +#define TORRENT_ASSERT_VAL(x, y) TORRENT_ASSERT(x) +#define TORRENT_ASSERT_FAIL_VAL(x) TORRENT_ASSERT_FAIL() +#endif + +#define TORRENT_ASSERT_FAIL() \ + libtorrent::assert_fail("", __LINE__, __FILE__, __func__, nullptr, 0) + +#else +#include +#define TORRENT_ASSERT_PRECOND(x) assert(x) +#define TORRENT_ASSERT(x) assert(x) +#define TORRENT_ASSERT_VAL(x, y) assert(x) +#define TORRENT_ASSERT_FAIL_VAL(x) assert(false) +#define TORRENT_ASSERT_FAIL() assert(false) +#endif + +#else // TORRENT_USE_ASSERTS + +#define TORRENT_ASSERT_PRECOND(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_VAL(a, b) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_FAIL_VAL(a) do {} TORRENT_WHILE_0 +#define TORRENT_ASSERT_FAIL() do {} TORRENT_WHILE_0 + +#endif // TORRENT_USE_ASSERTS + +#endif // TORRENT_ASSERT_HPP_INCLUDED diff --git a/include/libtorrent/aux_/aligned_storage.hpp b/include/libtorrent/aux_/aligned_storage.hpp new file mode 100644 index 0000000..20733cb --- /dev/null +++ b/include/libtorrent/aux_/aligned_storage.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALIGNED_STORAGE_HPP_INCLUDE +#define TORRENT_ALIGNED_STORAGE_HPP_INCLUDE + +#include + +namespace libtorrent { namespace aux { + +#if defined __GNUC__ && __GNUC__ < 5 && !defined(_LIBCPP_VERSION) + +// this is for backwards compatibility with not-quite C++11 compilers +template +struct aligned_storage +{ + struct type + { + alignas(Align) char buffer[Len]; + }; +}; + +#else + +using std::aligned_storage; + +#endif + +}} + +#endif diff --git a/include/libtorrent/aux_/aligned_union.hpp b/include/libtorrent/aux_/aligned_union.hpp new file mode 100644 index 0000000..14ee3ba --- /dev/null +++ b/include/libtorrent/aux_/aligned_union.hpp @@ -0,0 +1,71 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALIGNED_UNION_HPP_INCLUDE +#define TORRENT_ALIGNED_UNION_HPP_INCLUDE + +#include + +namespace libtorrent { namespace aux { + +#if defined __GNUC__ && __GNUC__ < 5 && !defined(_LIBCPP_VERSION) + +constexpr std::size_t max(std::size_t a) +{ return a; } + +constexpr std::size_t max(std::size_t a, std::size_t b) +{ return a > b ? a : b; } + +template +constexpr std::size_t max(std::size_t a, std::size_t b, Vals... v) +{ return max(a, max(b, v...)); } + +// this is for backwards compatibility with not-quite C++11 compilers +template +struct aligned_union +{ + struct type + { + alignas(max(alignof(Types)...)) + char buffer[max(Len, max(sizeof(Types)...))]; + }; +}; + +#else + +using std::aligned_union; + +#endif + +}} + +#endif diff --git a/include/libtorrent/aux_/alloca.hpp b/include/libtorrent/aux_/alloca.hpp new file mode 100644 index 0000000..6ec85af --- /dev/null +++ b/include/libtorrent/aux_/alloca.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2008-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCA_HPP_INCLUDED +#define TORRENT_ALLOCA_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include // for iterator_traits +#include // for addressof + +namespace libtorrent { namespace aux { + +template +inline void uninitialized_default_construct(ForwardIt first, ForwardIt last) +{ + using Value = typename std::iterator_traits::value_type; + ForwardIt current = first; + try { + for (; current != last; ++current) { + ::new (static_cast(std::addressof(*current))) Value; + } + } catch (...) { + for (; first != current; ++first) { + first->~Value(); + } + throw; + } +} + +template +struct alloca_destructor +{ + span objects; + ~alloca_destructor() + { + for (auto& o : objects) + { + TORRENT_UNUSED(o); + o.~T(); + } + } +}; + +}} + +#if defined TORRENT_WINDOWS || defined TORRENT_MINGW + +#include +#define TORRENT_ALLOCA(v, t, n) ::libtorrent::span v; { \ + auto TORRENT_ALLOCA_size = ::libtorrent::aux::numeric_cast(n); \ + auto* TORRENT_ALLOCA_tmp = static_cast(_alloca(sizeof(t) * static_cast(TORRENT_ALLOCA_size))); \ + v = ::libtorrent::span(TORRENT_ALLOCA_tmp, TORRENT_ALLOCA_size); \ + ::libtorrent::aux::uninitialized_default_construct(v.begin(), v.end()); \ + } \ + ::libtorrent::aux::alloca_destructor v##_destructor{v} + +#elif defined TORRENT_BSD + +#include +#define TORRENT_ALLOCA(v, t, n) ::libtorrent::span v; { \ + auto TORRENT_ALLOCA_size = ::libtorrent::aux::numeric_cast(n); \ + auto* TORRENT_ALLOCA_tmp = static_cast(alloca(sizeof(t) * static_cast(TORRENT_ALLOCA_size))); \ + v = ::libtorrent::span(TORRENT_ALLOCA_tmp, TORRENT_ALLOCA_size); \ + ::libtorrent::aux::uninitialized_default_construct(v.begin(), v.end()); \ + } \ + ::libtorrent::aux::alloca_destructor v##_destructor{v} + +#else + +#include +#define TORRENT_ALLOCA(v, t, n) ::libtorrent::span v; { \ + auto TORRENT_ALLOCA_size = ::libtorrent::aux::numeric_cast(n); \ + auto* TORRENT_ALLOCA_tmp = static_cast(alloca(sizeof(t) * static_cast(TORRENT_ALLOCA_size))); \ + v = ::libtorrent::span(TORRENT_ALLOCA_tmp, TORRENT_ALLOCA_size); \ + ::libtorrent::aux::uninitialized_default_construct(v.begin(), v.end()); \ + } \ + ::libtorrent::aux::alloca_destructor v##_destructor{v} + +#endif + +#endif // TORRENT_ALLOCA_HPP_INCLUDED diff --git a/include/libtorrent/aux_/allocating_handler.hpp b/include/libtorrent/aux_/allocating_handler.hpp new file mode 100644 index 0000000..f2353f7 --- /dev/null +++ b/include/libtorrent/aux_/allocating_handler.hpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCATING_HANDLER_HPP_INCLUDED +#define TORRENT_ALLOCATING_HANDLER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/aligned_storage.hpp" + +#include "libtorrent/debug.hpp" // for TORRENT_ASSERT + +#include + +namespace libtorrent { namespace aux { + + // this is meant to provide the actual storage for the handler allocator. + // There's only a single slot, so the allocator is only supposed to be used + // for handlers where there's only a single outstanding operation at a time, + // per storage object. For instance, peers only ever have one outstanding + // read operation at a time, so it can reuse its storage for read handlers. + template + struct handler_storage + { +#if TORRENT_USE_ASSERTS + handler_storage() + : used(false) + {} + + bool used; +#else + handler_storage() = default; +#endif + typename aux::aligned_storage::type bytes; + private: + handler_storage(handler_storage const&); + }; + + struct TORRENT_EXTRA_EXPORT error_handler_interface + { + virtual void on_exception(std::exception const&) = 0; + virtual void on_error(error_code const&) = 0; + + protected: + ~error_handler_interface() {} + }; + + // this class is a wrapper for an asio handler object. Its main purpose + // is to pass along additional parameters to the asio handler allocator + // function, as well as providing a distinct type for the handler + // allocator function to overload on + template + struct allocating_handler + { + allocating_handler( + Handler h, handler_storage& s, error_handler_interface& eh) + : handler(std::move(h)) + , storage(s) +#ifndef BOOST_NO_EXCEPTIONS + , error_handler(eh) +#endif + {} + + template + void operator()(A&&... a) const + { +#ifdef BOOST_NO_EXCEPTIONS + handler(std::forward(a)...); +#else + try + { + handler(std::forward(a)...); + } + catch (system_error const& e) + { + error_handler.on_error(e.code()); + } + catch (std::exception const& e) + { + error_handler.on_exception(e); + } + catch (...) + { + // this is pretty bad + TORRENT_ASSERT(false); + std::runtime_error e("unknown exception"); + error_handler.on_exception(e); + } +#endif + } + + friend void* asio_handler_allocate( + std::size_t size, allocating_handler* ctx) + { + TORRENT_UNUSED(size); + TORRENT_ASSERT_VAL(size <= Size, size); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(!ctx->storage.used); + ctx->storage.used = true; +#endif + return &ctx->storage.bytes; + } + + friend void asio_handler_deallocate( + void* ptr, std::size_t size, allocating_handler* ctx) + { + TORRENT_UNUSED(ptr); + TORRENT_UNUSED(size); + TORRENT_UNUSED(ctx); + + TORRENT_ASSERT_VAL(size <= Size, size); + TORRENT_ASSERT(ptr == &ctx->storage.bytes); +#if TORRENT_USE_ASSERTS + ctx->storage.used = false; +#endif + } + + Handler handler; + handler_storage& storage; +#ifndef BOOST_NO_EXCEPTIONS + error_handler_interface& error_handler; +#endif + }; + + template + aux::allocating_handler + make_handler(Handler handler + , handler_storage& storage + , error_handler_interface& err_handler) + { + return aux::allocating_handler( + std::forward(handler), storage, err_handler); + } +} +} + +#endif diff --git a/include/libtorrent/aux_/array.hpp b/include/libtorrent/aux_/array.hpp new file mode 100644 index 0000000..a0cd476 --- /dev/null +++ b/include/libtorrent/aux_/array.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2017-2018, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ARRAY_HPP +#define TORRENT_ARRAY_HPP + +#include + +#include "libtorrent/aux_/container_wrapper.hpp" + +namespace libtorrent { namespace aux { + + template + using array = container_wrapper>; + +}} + +#endif diff --git a/include/libtorrent/aux_/bind_to_device.hpp b/include/libtorrent/aux_/bind_to_device.hpp new file mode 100644 index 0000000..949f2d8 --- /dev/null +++ b/include/libtorrent/aux_/bind_to_device.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BIND_TO_DEVICE_HPP_INCLUDED +#define TORRENT_BIND_TO_DEVICE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" + +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#include +#endif + +namespace libtorrent { namespace aux { + +#if defined SO_BINDTODEVICE + + struct bind_to_device + { + explicit bind_to_device(char const* device): m_value(device) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return SO_BINDTODEVICE; } + template + char const* data(Protocol const&) const { return m_value; } + template + size_t size(Protocol const&) const { return strlen(m_value) + 1; } + private: + char const* m_value; + }; + + template + void bind_device(T& sock, char const* device, error_code& ec) + { + sock.set_option(bind_to_device(device), ec); + } + +#define TORRENT_HAS_BINDTODEVICE 1 + +#elif defined IP_BOUND_IF + + struct bind_to_device + { + explicit bind_to_device(unsigned int idx): m_value(idx) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_BOUND_IF; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + unsigned int m_value; + }; + + template + void bind_device(T& sock, char const* device, error_code& ec) + { + unsigned int const if_idx = if_nametoindex(device); + if (if_idx == 0) + { + ec.assign(errno, system_category()); + return; + } + sock.set_option(bind_to_device(if_idx), ec); + } + +#define TORRENT_HAS_BINDTODEVICE 1 + +#elif defined IP_FORCE_OUT_IFP + + struct bind_to_device + { + explicit bind_to_device(char const* device): m_value(device) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return IP_FORCE_OUT_IFP; } + template + char const* data(Protocol const&) const { return m_value; } + template + size_t size(Protocol const&) const { return strlen(m_value) + 1; } + private: + char const* m_value; + }; + + template + void bind_device(T& sock, char const* device, error_code& ec) + { + sock.set_option(bind_to_device(device), ec); + } + +#define TORRENT_HAS_BINDTODEVICE 1 + +#else + +#define TORRENT_HAS_BINDTODEVICE 0 + +#endif + +} } + +#endif + diff --git a/include/libtorrent/aux_/block_cache_reference.hpp b/include/libtorrent/aux_/block_cache_reference.hpp new file mode 100644 index 0000000..36c80c3 --- /dev/null +++ b/include/libtorrent/aux_/block_cache_reference.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2010-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOCK_CACHE_REFERENCE_HPP +#define TORRENT_BLOCK_CACHE_REFERENCE_HPP + +#include "libtorrent/units.hpp" +#include "libtorrent/storage_defs.hpp" + +namespace libtorrent { namespace aux { + + struct block_cache_reference + { + block_cache_reference() = default; + block_cache_reference(storage_index_t const idx, std::int32_t const c) + : storage(idx), cookie(c) {} + + // if the cookie is set to this value, it doesn't refer to anything in the + // cache (and the buffer is mutable) + static constexpr std::int32_t none = 0x7fffffff; + + storage_index_t storage{0}; + std::int32_t cookie = none; + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/byteswap.hpp b/include/libtorrent/aux_/byteswap.hpp new file mode 100644 index 0000000..73cafe3 --- /dev/null +++ b/include/libtorrent/aux_/byteswap.hpp @@ -0,0 +1,90 @@ +/* + +Copyright (c) 2013-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BYTESWAP_HPP_INCLUDED +#define TORRENT_BYTESWAP_HPP_INCLUDED + +// this header makes sure htonl(), nothl(), htons() and ntohs() +// are available + +#include "libtorrent/config.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#ifdef TORRENT_WINDOWS +#include +#else +// posix header +// for ntohl and htonl +#include +#endif + +namespace libtorrent { namespace aux { +// these need to be within the disabled warnings because on OSX +// the htonl and ntohl macros cause lots of old-style case warnings +inline std::uint32_t host_to_network(std::uint32_t x) +{ return htonl(x); } + +inline std::uint32_t network_to_host(std::uint32_t x) +{ return ntohl(x); } + +inline std::uint16_t host_to_network(std::uint16_t x) +{ return htons(x); } + +inline std::uint16_t network_to_host(std::uint16_t x) +{ return ntohs(x); } + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +inline std::uint32_t little_endian_to_host(std::uint32_t x) +{ +#if BOOST_ENDIAN_BIG_BYTE + return (x & 0xff000000) >> 24 + | (x & 0x00ff0000) >> 8 + | (x & 0x0000ff00) << 8 + | (x & 0x000000ff) << 24; +#elif BOOST_ENDIAN_LITTLE_BYTE + return x; +#else +#error "unknown endian" +#endif +} + +} +} + +#endif // TORRENT_BYTESWAP_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/container_wrapper.hpp b/include/libtorrent/aux_/container_wrapper.hpp new file mode 100644 index 0000000..837e04c --- /dev/null +++ b/include/libtorrent/aux_/container_wrapper.hpp @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONTAINER_WRAPPER_HPP +#define TORRENT_CONTAINER_WRAPPER_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/units.hpp" + +#include + +namespace libtorrent { namespace aux { + + template + struct container_wrapper : Base + { + using underlying_index = typename underlying_index_t::type; + + // pull in constructors from Base class + using Base::Base; + container_wrapper() = default; + explicit container_wrapper(Base&& b) : Base(std::move(b)) {} + + auto operator[](IndexType idx) const -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype(this->Base::operator[](underlying_index())) +#endif + { + TORRENT_ASSERT(idx >= IndexType(0)); + TORRENT_ASSERT(idx < end_index()); + return this->Base::operator[](std::size_t(static_cast(idx))); + } + + auto operator[](IndexType idx) -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype(this->Base::operator[](underlying_index())) +#endif + { + TORRENT_ASSERT(idx >= IndexType(0)); + TORRENT_ASSERT(idx < end_index()); + return this->Base::operator[](std::size_t(static_cast(idx))); + } + + IndexType end_index() const + { + TORRENT_ASSERT(this->size() <= std::size_t((std::numeric_limits::max)())); + return IndexType(static_cast(this->size())); + } + + // returns an object that can be used in a range-for to iterate over all + // indices + index_range range() const noexcept + { + return {IndexType{0}, end_index()}; + } + + template ::value>::type> + void resize(underlying_index s) + { + TORRENT_ASSERT(s >= 0); + this->Base::resize(std::size_t(s)); + } + + template ::value>::type> + void resize(underlying_index s, T const& v) + { + TORRENT_ASSERT(s >= 0); + this->Base::resize(std::size_t(s), v); + } + + void resize(std::size_t s) + { + TORRENT_ASSERT(s <= std::size_t((std::numeric_limits::max)())); + this->Base::resize(s); + } + + void resize(std::size_t s, T const& v) + { + TORRENT_ASSERT(s <= std::size_t((std::numeric_limits::max)())); + this->Base::resize(s, v); + } + + template ::value>::type> + void reserve(underlying_index s) + { + TORRENT_ASSERT(s >= 0); + this->Base::reserve(std::size_t(s)); + } + + void reserve(std::size_t s) + { + TORRENT_ASSERT(s <= std::size_t((std::numeric_limits::max)())); + this->Base::reserve(s); + } + }; +}} + +#endif + diff --git a/include/libtorrent/aux_/cppint_import_export.hpp b/include/libtorrent/aux_/cppint_import_export.hpp new file mode 100644 index 0000000..b75b1b2 --- /dev/null +++ b/include/libtorrent/aux_/cppint_import_export.hpp @@ -0,0 +1,253 @@ +/////////////////////////////////////////////////////////////// +// Copyright 2015 John Maddock. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_ + +#ifndef BOOST_MP_CPP_INT_IMPORT_EXPORT_HPP +#define BOOST_MP_CPP_INT_IMPORT_EXPORT_HPP + + +namespace boost { + + namespace multiprecision { + + namespace detail { + + template + void assign_bits(Backend& val, Unsigned bits, unsigned bit_location, unsigned chunk_bits, const mpl::false_& tag) + { + unsigned limb = bit_location / (sizeof(limb_type) * CHAR_BIT); + unsigned shift = bit_location % (sizeof(limb_type) * CHAR_BIT); + + limb_type mask = chunk_bits >= sizeof(limb_type) * CHAR_BIT ? ~static_cast(0u) : (static_cast(1u) << chunk_bits) - 1; + + limb_type value = (static_cast(bits) & mask) << shift; + if(value) + { + if(val.size() == limb) + { + val.resize(limb + 1, limb + 1); + if(val.size() > limb) + val.limbs()[limb] = value; + } + else if(val.size() > limb) + val.limbs()[limb] |= value; + } + if(chunk_bits > sizeof(limb_type) * CHAR_BIT - shift) + { + shift = sizeof(limb_type) * CHAR_BIT - shift; + chunk_bits -= shift; + bit_location += shift; + bits >>= shift; + if(bits) + assign_bits(val, bits, bit_location, chunk_bits, tag); + } + } + template + void assign_bits(Backend& val, Unsigned bits, unsigned bit_location, unsigned chunk_bits, const mpl::true_&) + { + typedef typename Backend::local_limb_type local_limb_type; + // + // Check for possible overflow, this may trigger an exception, or have no effect + // depending on whether this is a checked integer or not: + // + if((bit_location >= sizeof(local_limb_type) * CHAR_BIT) && bits) + val.resize(2, 2); + else + { + local_limb_type mask = chunk_bits >= sizeof(local_limb_type) * CHAR_BIT ? ~static_cast(0u) : (static_cast(1u) << chunk_bits) - 1; + local_limb_type value = (static_cast(bits) & mask) << bit_location; + *val.limbs() |= value; + // + // Check for overflow bits: + // + bit_location = sizeof(local_limb_type) * CHAR_BIT - bit_location; + bits >>= bit_location; + if(bits) + val.resize(2, 2); // May throw! + } + } + + template + inline void resize_to_bit_size(cpp_int_backend& newval, unsigned bits, const mpl::false_&) + { + unsigned limb_count = static_cast(bits / (sizeof(limb_type) * CHAR_BIT)); + if(bits % (sizeof(limb_type) * CHAR_BIT)) + ++limb_count; + static const unsigned max_limbs = MaxBits ? MaxBits / (CHAR_BIT * sizeof(limb_type)) + ((MaxBits % (CHAR_BIT * sizeof(limb_type))) ? 1 : 0) : (std::numeric_limits::max)(); + if(limb_count > max_limbs) + limb_count = max_limbs; + newval.resize(limb_count, limb_count); + std::memset(newval.limbs(), 0, newval.size() * sizeof(limb_type)); + } + template + inline void resize_to_bit_size(cpp_int_backend& newval, unsigned, const mpl::true_&) + { + *newval.limbs() = 0; + } + + template + number, ExpressionTemplates>& + import_bits_generic( + number, ExpressionTemplates>& val, Iterator i, Iterator j, unsigned chunk_size = 0, bool msv_first = true) + { + typename number, ExpressionTemplates>::backend_type newval; + + typedef typename std::iterator_traits::value_type value_type; + typedef typename boost::make_unsigned::type unsigned_value_type; + typedef typename std::iterator_traits::difference_type difference_type; + typedef typename boost::make_unsigned::type size_type; + typedef typename cpp_int_backend::trivial_tag tag_type; + + if(!chunk_size) + chunk_size = std::numeric_limits::digits; + + size_type limbs = std::distance(i, j); + size_type bits = limbs * chunk_size; + + detail::resize_to_bit_size(newval, static_cast(bits), tag_type()); + + difference_type bit_location = msv_first ? bits - chunk_size : 0; + difference_type bit_location_change = msv_first ? -static_cast(chunk_size) : chunk_size; + + while(i != j) + { + detail::assign_bits(newval, static_cast(*i), static_cast(bit_location), chunk_size, tag_type()); + ++i; + bit_location += bit_location_change; + } + + newval.normalize(); + + val.backend().swap(newval); + return val; + } + + template + inline typename boost::disable_if_c >::value, number, ExpressionTemplates>&>::type + import_bits_fast( + number, ExpressionTemplates>& val, T* i, T* j, unsigned chunk_size = 0) + { + std::size_t byte_len = (j - i) * (chunk_size ? chunk_size / CHAR_BIT : sizeof(*i)); + std::size_t limb_len = byte_len / sizeof(limb_type); + if(byte_len % sizeof(limb_type)) + ++limb_len; + cpp_int_backend& result = val.backend(); + result.resize(limb_len, limb_len); // checked types may throw here if they're not large enough to hold the data! + result.limbs()[result.size() - 1] = 0u; + std::memcpy(result.limbs(), i, (std::min)(byte_len, result.size() * sizeof(limb_type))); + result.normalize(); // In case data has leading zeros. + return val; + } + template + inline typename boost::enable_if_c >::value, number, ExpressionTemplates>&>::type + import_bits_fast( + number, ExpressionTemplates>& val, T* i, T* j, unsigned chunk_size = 0) + { + cpp_int_backend& result = val.backend(); + std::size_t byte_len = (j - i) * (chunk_size ? chunk_size / CHAR_BIT : sizeof(*i)); + std::size_t limb_len = byte_len / sizeof(result.limbs()[0]); + if(byte_len % sizeof(result.limbs()[0])) + ++limb_len; + result.limbs()[0] = 0u; + result.resize(limb_len, limb_len); // checked types may throw here if they're not large enough to hold the data! + std::memcpy(result.limbs(), i, (std::min)(byte_len, result.size() * sizeof(result.limbs()[0]))); + result.normalize(); // In case data has leading zeros. + return val; + } + } + + + template + inline number, ExpressionTemplates>& + import_bits( + number, ExpressionTemplates>& val, Iterator i, Iterator j, unsigned chunk_size = 0, bool msv_first = true) + { + return detail::import_bits_generic(val, i, j, chunk_size, msv_first); + } + + template + inline number, ExpressionTemplates>& + import_bits( + number, ExpressionTemplates>& val, T* i, T* j, unsigned chunk_size = 0, bool msv_first = true) + { +#ifdef BOOST_LITTLE_ENDIAN + if(((chunk_size % CHAR_BIT) == 0) && !msv_first) + return detail::import_bits_fast(val, i, j, chunk_size); +#endif + return detail::import_bits_generic(val, i, j, chunk_size, msv_first); + } + + namespace detail { + + template + std::uintmax_t extract_bits(const Backend& val, unsigned location, unsigned count, const mpl::false_& tag) + { + unsigned limb = location / (sizeof(limb_type) * CHAR_BIT); + unsigned shift = location % (sizeof(limb_type) * CHAR_BIT); + std::uintmax_t result = 0; + std::uintmax_t mask = count == std::numeric_limits::digits ? ~static_cast(0) : (static_cast(1u) << count) - 1; + if(count > (sizeof(limb_type) * CHAR_BIT - shift)) + { + result = extract_bits(val, location + sizeof(limb_type) * CHAR_BIT - shift, count - sizeof(limb_type) * CHAR_BIT + shift, tag); + result <<= sizeof(limb_type) * CHAR_BIT - shift; + } + if(limb < val.size()) + result |= (val.limbs()[limb] >> shift) & mask; + return result; + } + + template + inline std::uintmax_t extract_bits(const Backend& val, unsigned location, unsigned count, const mpl::true_&) + { + std::uintmax_t result = *val.limbs(); + std::uintmax_t mask = count == std::numeric_limits::digits ? ~static_cast(0) : (static_cast(1u) << count) - 1; + return (result >> location) & mask; + } + + } + + template + OutputIterator export_bits( + const number, ExpressionTemplates>& val, OutputIterator out, unsigned chunk_size, bool msv_first = true) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4244) +#endif + typedef typename cpp_int_backend::trivial_tag tag_type; + if(!val) + { + *out = 0; + ++out; + return out; + } + unsigned bitcount = msb(val) + 1; + unsigned chunks = bitcount / chunk_size; + if(bitcount % chunk_size) + ++chunks; + + int bit_location = msv_first ? bitcount - chunk_size : 0; + int bit_step = msv_first ? -static_cast(chunk_size) : chunk_size; + while(bit_location % bit_step) ++bit_location; + + do + { + *out = detail::extract_bits(val.backend(), bit_location, chunk_size, tag_type()); + ++out; + bit_location += bit_step; + } while((bit_location >= 0) && (bit_location < (int)bitcount)); + + return out; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + } +} + + + +#endif // BOOST_MP_CPP_INT_IMPORT_EXPORT_HPP + diff --git a/include/libtorrent/aux_/cpuid.hpp b/include/libtorrent/aux_/cpuid.hpp new file mode 100644 index 0000000..6e8ea54 --- /dev/null +++ b/include/libtorrent/aux_/cpuid.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CPUID_HPP_INCLUDED +#define TORRENT_CPUID_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + + // initialized by static initializers (in cpuid.cpp) + TORRENT_EXTRA_EXPORT extern bool const sse42_support; + TORRENT_EXTRA_EXPORT extern bool const mmx_support; + TORRENT_EXTRA_EXPORT extern bool const arm_neon_support; + TORRENT_EXTRA_EXPORT extern bool const arm_crc32c_support; +} } + +#endif // TORRENT_CPUID_HPP_INCLUDED diff --git a/include/libtorrent/aux_/deferred_handler.hpp b/include/libtorrent/aux_/deferred_handler.hpp new file mode 100644 index 0000000..ebb83be --- /dev/null +++ b/include/libtorrent/aux_/deferred_handler.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEFERRED_HANDLER_HPP +#define TORRENT_DEFERRED_HANDLER_HPP + +#include "libtorrent/assert.hpp" +#include "libtorrent/io_service.hpp" + +namespace libtorrent { namespace aux { + +template +struct handler_wrapper +{ + handler_wrapper(bool& in_flight, Handler&& h) + : m_handler(std::move(h)) + , m_in_flight(in_flight) {} + template + void operator()(Args&&... a) + { + TORRENT_ASSERT(m_in_flight); + m_in_flight = false; + m_handler(std::forward(a)...); + } + + // forward asio handler allocator to the underlying handler's + friend void* asio_handler_allocate( + std::size_t size, handler_wrapper* h) + { + return asio_handler_allocate(size, &h->m_handler); + } + + friend void asio_handler_deallocate( + void* ptr, std::size_t size, handler_wrapper* h) + { + asio_handler_deallocate(ptr, size, &h->m_handler); + } + +private: + Handler m_handler; + bool& m_in_flight; +}; + +struct deferred_handler +{ + template + void post(io_service& ios, Handler&& h) + { + if (m_in_flight) return; + m_in_flight = true; + ios.post(handler_wrapper(m_in_flight, std::forward(h))); + } +private: + bool m_in_flight = false; +}; + +}} +#endif diff --git a/include/libtorrent/aux_/deprecated.hpp b/include/libtorrent/aux_/deprecated.hpp new file mode 100644 index 0000000..22097ef --- /dev/null +++ b/include/libtorrent/aux_/deprecated.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2019, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEPRECATED_HPP_INCLUDED +#define TORRENT_DEPRECATED_HPP_INCLUDED + +#if defined __clang__ + +// ====== CLANG ======== + +# if !defined TORRENT_BUILDING_LIBRARY +// TODO: figure out which version of clang this is supported in +# define TORRENT_DEPRECATED __attribute__ ((deprecated)) +# define TORRENT_DEPRECATED_ENUM __attribute__ ((deprecated)) +# define TORRENT_DEPRECATED_MEMBER __attribute__ ((deprecated)) +# endif + +#elif defined __GNUC__ + +// ======== GCC ======== + +// deprecation markup is only enabled when libtorrent +// headers are included by clients, not while building +// libtorrent itself +# if __GNUC__ >= 3 && !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED __attribute__ ((deprecated)) +# endif + +# if __GNUC__ >= 6 && !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED_ENUM __attribute__ ((deprecated)) +# define TORRENT_DEPRECATED_MEMBER __attribute__ ((deprecated)) +# endif + +#elif defined _MSC_VER + +// ======= MSVC ========= + +// deprecation markup is only enabled when libtorrent +// headers are included by clients, not while building +// libtorrent itself +#if !defined TORRENT_BUILDING_LIBRARY +# define TORRENT_DEPRECATED __declspec(deprecated) +#endif + +#endif + +#ifndef TORRENT_DEPRECATED +#define TORRENT_DEPRECATED +#endif + +#ifndef TORRENT_DEPRECATED_ENUM +#define TORRENT_DEPRECATED_ENUM +#endif + +#ifndef TORRENT_DEPRECATED_MEMBER +#define TORRENT_DEPRECATED_MEMBER +#endif + +#endif diff --git a/include/libtorrent/aux_/deque.hpp b/include/libtorrent/aux_/deque.hpp new file mode 100644 index 0000000..d21b544 --- /dev/null +++ b/include/libtorrent/aux_/deque.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEQUE_HPP +#define TORRENT_DEQUE_HPP + +#include + +#include "libtorrent/aux_/container_wrapper.hpp" + +namespace libtorrent { namespace aux { + + template + using deque = container_wrapper>; + +}} + +#endif diff --git a/include/libtorrent/aux_/dev_random.hpp b/include/libtorrent/aux_/dev_random.hpp new file mode 100644 index 0000000..e861b36 --- /dev/null +++ b/include/libtorrent/aux_/dev_random.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEV_RANDOM_HPP_INCLUDED +#define TORRENT_DEV_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/error_code.hpp" // for system_error +#include "libtorrent/aux_/throw.hpp" + +#include + +namespace libtorrent { namespace aux { + + struct dev_random + { + dev_random() + : m_fd(open("/dev/random", O_RDONLY)) + { + if (m_fd < 0) + { + throw_ex(error_code(errno, system_category())); + } + } + dev_random(dev_random const&) = delete; + dev_random& operator=(dev_random const&) = delete; + + void read(span buffer) + { + std::int64_t const ret = ::read(m_fd, buffer.data() + , static_cast(buffer.size())); + if (ret != int(buffer.size())) + { + throw_ex(errors::no_entropy); + } + } + + ~dev_random() { close(m_fd); } + + private: + int m_fd; + }; +}} + +#endif + diff --git a/include/libtorrent/aux_/disable_warnings_pop.hpp b/include/libtorrent/aux_/disable_warnings_pop.hpp new file mode 100644 index 0000000..5cf7efe --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_pop.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/include/libtorrent/aux_/disable_warnings_push.hpp b/include/libtorrent/aux_/disable_warnings_push.hpp new file mode 100644 index 0000000..3369d9c --- /dev/null +++ b/include/libtorrent/aux_/disable_warnings_push.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wmissing-noreturn" +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#if __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wshift-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-negative" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#if __GNUC__ >= 7 +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wnoexcept-type" +#endif +#if __GNUC__ >= 8 +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wdeprecated-copy" +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Weverything" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wcast-align" +#pragma clang diagnostic ignored "-Wweak-vtable" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wc++11-extensions" +#pragma clang diagnostic ignored "-Wextra-semi" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wgnu-folding-constant" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4005: macro redefinition +#pragma warning(disable : 4005) +// expression before comma has no effect; expected expression with side-effect +#pragma warning(disable : 4548) +// 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4244) +// potentially uninitialized local variable '' used +#pragma warning(disable : 4701) +#endif diff --git a/include/libtorrent/aux_/disk_job_fence.hpp b/include/libtorrent/aux_/disk_job_fence.hpp new file mode 100644 index 0000000..365c263 --- /dev/null +++ b/include/libtorrent/aux_/disk_job_fence.hpp @@ -0,0 +1,120 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_JOB_FENCE_HPP_INCLUDE +#define TORRENT_DISK_JOB_FENCE_HPP_INCLUDE + +#include "libtorrent/config.hpp" +#include "libtorrent/tailqueue.hpp" + +#include +#include + +namespace libtorrent { + +struct disk_io_job; +struct counters; + +namespace aux { + + // implements the disk I/O job fence used by the storage_interface + // to provide to the disk thread. Whenever a disk job needs + // exclusive access to the storage for that torrent, it raises + // the fence, blocking all new jobs, until there are no longer + // any outstanding jobs on the torrent, then the fence is lowered + // and it can be performed, along with the backlog of jobs that + // accrued while the fence was up + struct TORRENT_EXPORT disk_job_fence + { + disk_job_fence() = default; + +#if TORRENT_USE_ASSERTS + ~disk_job_fence() + { + TORRENT_ASSERT(int(m_outstanding_jobs) == 0); + TORRENT_ASSERT(m_blocked_jobs.size() == 0); + } +#endif + + // returns one of the fence_* enums. + // if there are no outstanding jobs on the + // storage, fence_post_fence is returned, the flush job is expected + // to be discarded by the caller. + // fence_post_flush is returned if the fence job was blocked and queued, + // but the flush job should be posted (i.e. put on the job queue) + // fence_post_none if both the fence and the flush jobs were queued. + enum { fence_post_fence = 0, fence_post_flush = 1, fence_post_none = 2 }; + int raise_fence(disk_io_job* fence_job, disk_io_job* flush_job + , counters& cnt); + bool has_fence() const; + + // called whenever a job completes and is posted back to the + // main network thread. the tailqueue of jobs will have the + // backed-up jobs prepended to it in case this resulted in the + // fence being lowered. + int job_complete(disk_io_job* j, tailqueue& job_queue); + int num_outstanding_jobs() const { return m_outstanding_jobs; } + + // if there is a fence up, returns true and adds the job + // to the queue of blocked jobs + bool is_blocked(disk_io_job* j); + + // the number of blocked jobs + int num_blocked() const; + + private: + // when > 0, this storage is blocked for new async + // operations until all outstanding jobs have completed. + // at that point, the m_blocked_jobs are issued + // the count is the number of fence job currently in the queue + int m_has_fence = 0; + + // when there's a fence up, jobs are queued up in here + // until the fence is lowered + tailqueue m_blocked_jobs; + + // the number of disk_io_job objects there are, belonging + // to this torrent, currently pending, hanging off of + // cached_piece_entry objects. This is used to determine + // when the fence can be lowered + std::atomic m_outstanding_jobs{0}; + + // must be held when accessing m_has_fence and + // m_blocked_jobs + mutable std::mutex m_mutex; + }; + + +}} + +#endif + diff --git a/include/libtorrent/aux_/escape_string.hpp b/include/libtorrent/aux_/escape_string.hpp new file mode 100644 index 0000000..8fddf39 --- /dev/null +++ b/include/libtorrent/aux_/escape_string.hpp @@ -0,0 +1,113 @@ +/* + +Copyright (c) 2003-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ESCAPE_STRING_HPP_INCLUDED +#define TORRENT_ESCAPE_STRING_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // hidden + using encode_string_flags_t = flags::bitfield_flag; + + namespace string + { + // use lower case alphabet used with i2p + constexpr encode_string_flags_t lowercase = 0_bit; + // don't insert padding + constexpr encode_string_flags_t no_padding = 1_bit; + // shortcut used for addresses as sha256 hashes + constexpr encode_string_flags_t i2p = lowercase | no_padding; + } + + TORRENT_EXTRA_EXPORT std::string unescape_string(string_view s, error_code& ec); + // replaces all disallowed URL characters by their %-encoding + TORRENT_EXTRA_EXPORT std::string escape_string(string_view str); + // same as escape_string but does not encode '/' + TORRENT_EXTRA_EXPORT std::string escape_path(string_view str); + // if the url does not appear to be encoded, and it contains illegal url characters + // it will be encoded + TORRENT_EXTRA_EXPORT std::string maybe_url_encode(std::string const& url); + + TORRENT_EXTRA_EXPORT string_view trim(string_view); + TORRENT_EXTRA_EXPORT string_view::size_type find(string_view haystack + , string_view needle, string_view::size_type pos); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // convert a file://-URL to a proper path + TORRENT_EXTRA_EXPORT std::string resolve_file_url(std::string const& url); +#endif + + // returns true if the given string (not 0-terminated) contains + // characters that would need to be escaped if used in a URL + TORRENT_EXTRA_EXPORT bool need_encoding(char const* str, int len); + + // encodes a string using the base64 scheme + TORRENT_EXTRA_EXPORT std::string base64encode(std::string const& s); +#if TORRENT_USE_I2P + // encodes a string using the base32 scheme + TORRENT_EXTRA_EXPORT std::string base32encode(string_view s, encode_string_flags_t flags = {}); +#endif + TORRENT_EXTRA_EXPORT std::string base32decode(string_view s); + + // replaces \ with / + TORRENT_EXTRA_EXPORT void convert_path_to_posix(std::string& path); +#ifdef TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT void convert_path_to_windows(std::string& path); +#endif + + TORRENT_EXTRA_EXPORT std::string read_until(char const*& str, char delim + , char const* end); + +#if defined TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT std::wstring convert_to_wstring(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_wstring(std::wstring const& s); +#endif + +#if TORRENT_USE_ICONV || TORRENT_USE_LOCALE || defined TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT std::string convert_to_native(std::string const& s); + TORRENT_EXTRA_EXPORT std::string convert_from_native(std::string const& s); +#else + // internal + inline std::string const& convert_to_native(std::string const& s) { return s; } + // internal + inline std::string const& convert_from_native(std::string const& s) { return s; } +#endif +} + +#endif // TORRENT_ESCAPE_STRING_HPP_INCLUDED diff --git a/include/libtorrent/aux_/export.hpp b/include/libtorrent/aux_/export.hpp new file mode 100644 index 0000000..b86ea34 --- /dev/null +++ b/include/libtorrent/aux_/export.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2005-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_EXPORT_HPP_INCLUDED +#define TORRENT_EXPORT_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/deprecated.hpp" + +#if !defined TORRENT_ABI_VERSION +# ifdef TORRENT_NO_DEPRECATE +# define TORRENT_ABI_VERSION 2 +# else +# define TORRENT_ABI_VERSION 1 +# endif +#endif + +#if TORRENT_ABI_VERSION >= 2 +# define TORRENT_VERSION_NAMESPACE_2 inline namespace v1_2 { +# define TORRENT_VERSION_NAMESPACE_2_END } +#else +# define TORRENT_VERSION_NAMESPACE_2 +# define TORRENT_VERSION_NAMESPACE_2_END +#endif + +// backwards compatibility with older versions of boost +#if !defined BOOST_SYMBOL_EXPORT && !defined BOOST_SYMBOL_IMPORT +# if defined _MSC_VER || defined __MINGW32__ +# define BOOST_SYMBOL_EXPORT __declspec(dllexport) +# define BOOST_SYMBOL_IMPORT __declspec(dllimport) +# elif __GNU__ >= 4 +# define BOOST_SYMBOL_EXPORT __attribute__((visibility("default"))) +# define BOOST_SYMBOL_IMPORT __attribute__((visibility("default"))) +# else +# define BOOST_SYMBOL_EXPORT +# define BOOST_SYMBOL_IMPORT +# endif +#endif + +#if defined TORRENT_BUILDING_SHARED +# define TORRENT_EXPORT BOOST_SYMBOL_EXPORT +#elif defined TORRENT_LINKING_SHARED +# define TORRENT_EXPORT BOOST_SYMBOL_IMPORT +#endif + +// when this is specified, export a bunch of extra +// symbols, mostly for the unit tests to reach +#if defined TORRENT_EXPORT_EXTRA +# if defined TORRENT_BUILDING_SHARED +# define TORRENT_EXTRA_EXPORT BOOST_SYMBOL_EXPORT +# elif defined TORRENT_LINKING_SHARED +# define TORRENT_EXTRA_EXPORT BOOST_SYMBOL_IMPORT +# endif +#endif + +#ifndef TORRENT_EXPORT +# define TORRENT_EXPORT +#endif + +#ifndef TORRENT_EXTRA_EXPORT +# define TORRENT_EXTRA_EXPORT +#endif + +// only export this type if deprecated functions are enabled +#if TORRENT_ABI_VERSION >= 2 +# define TORRENT_DEPRECATED_EXPORT TORRENT_EXTRA_EXPORT TORRENT_DEPRECATED +#else +# define TORRENT_DEPRECATED_EXPORT TORRENT_EXPORT TORRENT_DEPRECATED +#endif + +#endif + diff --git a/include/libtorrent/aux_/ffs.hpp b/include/libtorrent/aux_/ffs.hpp new file mode 100644 index 0000000..c243b9f --- /dev/null +++ b/include/libtorrent/aux_/ffs.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FFS_HPP_INCLUDE +#define TORRENT_FFS_HPP_INCLUDE + +#include +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { +namespace aux { + + // For a general reference of the problems these routines are about + // see http://en.wikipedia.org/wiki/Find_first_set + + // these functions expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_leading_zeros_sw(span buf); + // if this function is called in an unsupported platform, returns -1 + // consider call always count_leading_zeros(buf) + TORRENT_EXTRA_EXPORT int count_leading_zeros_hw(span buf); + + // this function statically determines if hardware or software is used + // and expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_leading_zeros(span buf); + + // these functions expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_trailing_ones_sw(span buf); + // if this function is called in an unsupported platform, returns -1 + // consider call always count_trailing_ones(buf) + TORRENT_EXTRA_EXPORT int count_trailing_ones_hw(span buf); + + // this function statically determines if hardware or software is used + // and expect the range to be in big-endian byte order + TORRENT_EXTRA_EXPORT int count_trailing_ones(span buf); + + // returns the index of the most significant set bit. + TORRENT_EXTRA_EXPORT int log2p1(std::uint32_t v); +}} + +#endif // TORRENT_FFS_HPP_INCLUDE diff --git a/include/libtorrent/aux_/file_progress.hpp b/include/libtorrent/aux_/file_progress.hpp new file mode 100644 index 0000000..dc5d2b1 --- /dev/null +++ b/include/libtorrent/aux_/file_progress.hpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_PROGRESS_HPP_INCLUDE +#define TORRENT_FILE_PROGRESS_HPP_INCLUDE + +#include +#include +#include + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" + +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/bitfield.hpp" +#include "libtorrent/invariant_check.hpp" +#endif + +#if TORRENT_USE_INVARIANT_CHECKS +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/bitfield.hpp" +#endif + +namespace libtorrent { + +class piece_picker; +class file_storage; + +namespace aux { + + struct TORRENT_EXTRA_EXPORT file_progress + { + file_progress() = default; + + void init(piece_picker const& picker + , file_storage const& fs); + + void export_progress(vector &fp); + + bool empty() const { return m_file_progress.empty(); } + void clear(); + + void update(file_storage const& fs, piece_index_t index + , std::function const& completed_cb); + + private: + + // this vector contains the number of bytes completely + // downloaded (as in passed-hash-check) in each file. + // this lets us trigger on individual files completing + // the vector is allocated lazily, when file progress + // is first queried by the client + vector m_file_progress; + +#if TORRENT_USE_INVARIANT_CHECKS + friend class libtorrent::invariant_access; + void check_invariant() const; + + // this is used to assert we never add the same piece twice + typed_bitfield m_have_pieces; + + // to make sure we never say we've downloaded more bytes of a file than + // its file size + vector m_file_sizes; +#endif + }; +} } + +#endif diff --git a/include/libtorrent/aux_/generate_peer_id.hpp b/include/libtorrent/aux_/generate_peer_id.hpp new file mode 100644 index 0000000..8c4c3eb --- /dev/null +++ b/include/libtorrent/aux_/generate_peer_id.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_GENERATE_PEER_ID_HPP_INCLUDED +#define TORRENT_GENERATE_PEER_ID_HPP_INCLUDED + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { namespace aux { + +struct session_settings; + +TORRENT_EXTRA_EXPORT peer_id generate_peer_id(session_settings const& sett); + +}} + +#endif + diff --git a/include/libtorrent/aux_/has_block.hpp b/include/libtorrent/aux_/has_block.hpp new file mode 100644 index 0000000..6b5709c --- /dev/null +++ b/include/libtorrent/aux_/has_block.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HAS_BLOCK_HPP_INCLUDED +#define TORRENT_HAS_BLOCK_HPP_INCLUDED + +#include "libtorrent/piece_block.hpp" +#include "libtorrent/peer_connection.hpp" // for pending_block + +namespace libtorrent { namespace aux { + + struct has_block + { + has_block(has_block const&) = default; + // explicitly disallow assignment, to silence msvc warning + has_block& operator=(has_block const&) = delete; + + explicit has_block(piece_block const& b): block(b) {} + bool operator()(pending_block const& pb) const + { return pb.block == block; } + private: + piece_block const& block; + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/instantiate_connection.hpp b/include/libtorrent/aux_/instantiate_connection.hpp new file mode 100644 index 0000000..83826a9 --- /dev/null +++ b/include/libtorrent/aux_/instantiate_connection.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INSTANTIATE_CONNECTION +#define TORRENT_INSTANTIATE_CONNECTION + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/aux_/socket_type.hpp" + +namespace libtorrent { + + struct utp_socket_manager; + +namespace aux { + + // instantiate a socket_type (s) according to the specified criteria + TORRENT_EXTRA_EXPORT bool instantiate_connection(io_service& ios + , aux::proxy_settings const& ps, aux::socket_type& s + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection); +}} + +#endif diff --git a/include/libtorrent/aux_/io.hpp b/include/libtorrent/aux_/io.hpp new file mode 100644 index 0000000..0d37a8d --- /dev/null +++ b/include/libtorrent/aux_/io.hpp @@ -0,0 +1,179 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_IO_HPP_INCLUDED +#define TORRENT_AUX_IO_HPP_INCLUDED + +#include +#include +#include +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { namespace aux { + + template struct type {}; + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianess + template + inline typename std::enable_if::type + read_impl(span& view, type) + { + T ret = 0; + for (Byte const b : view.first(sizeof(T))) + { + ret <<= 8; + ret |= static_cast(b); + } + view = view.subspan(int(sizeof(T))); + return ret; + } + + template + inline typename std::enable_if::value + && (std::is_integral::value || std::is_enum::value) + && sizeof(Byte)==1>::type + write_impl(In data, span& view) + { + T val = static_cast(data); + TORRENT_ASSERT(data == static_cast(val)); + int shift = int(sizeof(T)) * 8; + for (Byte& b : view.first(sizeof(T))) + { + shift -= 8; + b = static_cast((val >> shift) & 0xff); + } + view = view.subspan(int(sizeof(T))); + } + + // the single-byte case is separate to avoid a warning on the shift-left by + // 8 bits (which invokes undefined behavior) + + template + inline typename std::enable_if::type + read_impl(span& view, type) + { + std::uint8_t ret = static_cast(view[0]); + view = view.subspan(1); + return ret; + } + + template + inline typename std::enable_if::type + read_impl(span& view, type) + { + std::uint8_t ret = static_cast(view[0]); + view = view.subspan(1); + return ret; + } + + // -- adaptors + + template + std::int64_t read_int64(span& view) + { return read_impl(view, type()); } + + template + std::uint64_t read_uint64(span& view) + { return read_impl(view, type()); } + + template + std::uint32_t read_uint32(span& view) + { return read_impl(view, type()); } + + template + std::int32_t read_int32(span& view) + { return read_impl(view, type()); } + + template + std::int16_t read_int16(span& view) + { return read_impl(view, type()); } + + template + std::uint16_t read_uint16(span& view) + { return read_impl(view, type()); } + + template + std::int8_t read_int8(span& view) + { return read_impl(view, type()); } + + template + std::uint8_t read_uint8(span& view) + { return read_impl(view, type()); } + + + template + void write_uint64(T val, span& view) + { write_impl(val, view); } + + template + void write_int64(T val, span& view) + { write_impl(val, view); } + + template + void write_uint32(T val, span& view) + { write_impl(val, view); } + + template + void write_int32(T val, span& view) + { write_impl(val, view); } + + template + void write_uint16(T val, span& view) + { write_impl(val, view); } + + template + void write_int16(T val, span& view) + { write_impl(val, view); } + + template + void write_uint8(T val, span& view) + { write_impl(val, view); } + + template + void write_int8(T val, span& view) + { write_impl(val, view); } + + template + inline int write_string(std::string const& str, span& view) + { + TORRENT_ASSERT(view.size() >= numeric_cast(str.size())); + std::copy(str.begin(), str.end(), view.begin()); + view = view.subspan(int(str.size())); + return int(str.size()); + } + +}} + +#endif // TORRENT_AUX_IO_HPP_INCLUDED diff --git a/include/libtorrent/aux_/ip_notifier.hpp b/include/libtorrent/aux_/ip_notifier.hpp new file mode 100644 index 0000000..105dcd1 --- /dev/null +++ b/include/libtorrent/aux_/ip_notifier.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_NOTIFIER_HPP_INCLUDED +#define TORRENT_IP_NOTIFIER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service.hpp" + +namespace libtorrent { namespace aux { + + struct TORRENT_EXTRA_EXPORT ip_change_notifier + { + // cb will be invoked when a change is detected in the + // system's IP addresses + virtual void async_wait(std::function cb) = 0; + virtual void cancel() = 0; + + virtual ~ip_change_notifier() {} + }; + + TORRENT_EXTRA_EXPORT std::unique_ptr create_ip_notifier(io_service& ios); +}} + +#endif diff --git a/include/libtorrent/aux_/keepalive.hpp b/include/libtorrent/aux_/keepalive.hpp new file mode 100644 index 0000000..2e5fb30 --- /dev/null +++ b/include/libtorrent/aux_/keepalive.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_KEEP_ALIVE_HPP_INCLUDED +#define TORRENT_KEEP_ALIVE_HPP_INCLUDED + +#if !defined _WIN32 + +#include "libtorrent/config.hpp" + +#include // for IPPROTO_TCP + +namespace libtorrent { +namespace aux { + +#if defined TCP_KEEPIDLE +#define TORRENT_HAS_KEEPALIVE_IDLE + struct tcp_keepalive_idle + { + explicit tcp_keepalive_idle(int seconds): m_value(seconds) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_KEEPIDLE; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + int m_value; + }; +#elif defined TCP_KEEPALIVE +#define TORRENT_HAS_KEEPALIVE_IDLE + struct tcp_keepalive_idle + { + explicit tcp_keepalive_idle(int seconds): m_value(seconds) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_KEEPALIVE; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + int m_value; + }; +#endif + +#ifdef TCP_KEEPINTVL +#define TORRENT_HAS_KEEPALIVE_INTERVAL + struct tcp_keepalive_interval + { + explicit tcp_keepalive_interval(int seconds): m_value(seconds) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_KEEPINTVL; } + template + char const* data(Protocol const&) const { return reinterpret_cast(&m_value); } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + private: + int m_value; + }; +#endif +} +} + +#endif // _WIN32 + +#endif + diff --git a/include/libtorrent/aux_/listen_socket_handle.hpp b/include/libtorrent/aux_/listen_socket_handle.hpp new file mode 100644 index 0000000..776ebf7 --- /dev/null +++ b/include/libtorrent/aux_/listen_socket_handle.hpp @@ -0,0 +1,88 @@ +/* + +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LISTEN_SOCKET_HANDLE_HPP_INCLUDED +#define TORRENT_LISTEN_SOCKET_HANDLE_HPP_INCLUDED + +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include + +namespace libtorrent { namespace aux { + + struct listen_socket_t; + + struct TORRENT_EXTRA_EXPORT listen_socket_handle + { + friend struct session_impl; + + listen_socket_handle() = default; + + listen_socket_handle(std::shared_ptr s) // NOLINT + : m_sock(s) + {} + + listen_socket_handle(listen_socket_handle const& o) = default; + listen_socket_handle& operator=(listen_socket_handle const& o) = default; + + explicit operator bool() const { return !m_sock.expired(); } + + address get_external_address() const; + tcp::endpoint get_local_endpoint() const; + bool can_route(address const&) const; + + bool is_ssl() const; + + bool operator==(listen_socket_handle const& o) const + { + return !m_sock.owner_before(o.m_sock) && !o.m_sock.owner_before(m_sock); + } + + bool operator!=(listen_socket_handle const& o) const + { + return !(*this == o); + } + + bool operator<(listen_socket_handle const& o) const + { return m_sock.owner_before(o.m_sock); } + + listen_socket_t* get() const; + + std::weak_ptr get_ptr() const { return m_sock; } + + private: + std::weak_ptr m_sock; + }; + +} } + +#endif diff --git a/include/libtorrent/aux_/lsd.hpp b/include/libtorrent/aux_/lsd.hpp new file mode 100644 index 0000000..ecc391a --- /dev/null +++ b/include/libtorrent/aux_/lsd.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_LSD_HPP +#define LIBTORRENT_LSD_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/sha1_hash.hpp" + +namespace libtorrent { namespace aux { + struct TORRENT_EXTRA_EXPORT lsd_callback + { + virtual void on_lsd_peer(tcp::endpoint const& peer, sha1_hash const& ih) = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log_lsd() const = 0; + virtual void log_lsd(char const* msg) const = 0; +#endif + + protected: + ~lsd_callback() {} + }; +}} + +#endif // LIBTORRENT_LSD_HPP diff --git a/include/libtorrent/aux_/merkle.hpp b/include/libtorrent/aux_/merkle.hpp new file mode 100644 index 0000000..02b1a10 --- /dev/null +++ b/include/libtorrent/aux_/merkle.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2003-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MERKLE_HPP_INCLUDED +#define TORRENT_MERKLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT int merkle_num_leafs(int); + TORRENT_EXTRA_EXPORT int merkle_num_nodes(int); + TORRENT_EXTRA_EXPORT int merkle_get_parent(int); + TORRENT_EXTRA_EXPORT int merkle_get_sibling(int); +} + +#endif diff --git a/include/libtorrent/aux_/noexcept_movable.hpp b/include/libtorrent/aux_/noexcept_movable.hpp new file mode 100644 index 0000000..b59e7de --- /dev/null +++ b/include/libtorrent/aux_/noexcept_movable.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_NOEXCEPT_MOVABLE_HPP_INCLUDED +#define TORRENT_NOEXCEPT_MOVABLE_HPP_INCLUDED + +#include +#include + +namespace libtorrent { +namespace aux { + + // this is a silly wrapper for address and endpoint to make their move + // constructors be noexcept (because boost.asio is incorrectly making them + // potentially throwing). This can be removed once libtorrent uses the + // networking TS. + template + struct noexcept_movable : T + { + noexcept_movable() = default; + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(rhs)) + {} + noexcept_movable(noexcept_movable const& rhs) = default; + noexcept_movable(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT + noexcept_movable(T const& rhs) : T(rhs) {} // NOLINT + noexcept_movable& operator=(noexcept_movable const& rhs) = default; + noexcept_movable& operator=(noexcept_movable&& rhs) = default; + using T::T; + using T::operator=; + }; + +} +} + +#endif diff --git a/include/libtorrent/aux_/numeric_cast.hpp b/include/libtorrent/aux_/numeric_cast.hpp new file mode 100644 index 0000000..60a5a1b --- /dev/null +++ b/include/libtorrent/aux_/numeric_cast.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NUMERIC_CAST_HPP +#define TORRENT_NUMERIC_CAST_HPP + +#include +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { namespace aux { + + template ::value && std::is_integral::value>::type> + T numeric_cast(In v) + { + T r = static_cast(v); + TORRENT_ASSERT(v == static_cast(r)); + TORRENT_ASSERT(std::is_unsigned::value || std::is_signed::value + || std::int64_t(v) >= 0); + TORRENT_ASSERT(std::is_signed::value || std::is_unsigned::value + || std::size_t(v) <= std::size_t((std::numeric_limits::max)())); + return r; + } + + // in C++ 17 you can use std::clamp + template + T clamp(T v, T lo, T hi) + { + TORRENT_ASSERT(lo <= hi); + if (v < lo) return lo; + if (hi < v) return hi; + return v; + } + +}} + +#endif diff --git a/include/libtorrent/aux_/openssl.hpp b/include/libtorrent/aux_/openssl.hpp new file mode 100644 index 0000000..2084e6c --- /dev/null +++ b/include/libtorrent/aux_/openssl.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_OPENSSL_HPP_INCLUDED +#define TORRENT_OPENSSL_HPP_INCLUDED + +#ifdef TORRENT_USE_LIBCRYPTO + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include // for OPENSSL_VERSION_NUMBER +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if defined __APPLE__ \ + && MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 \ + && OPENSSL_VERSION_NUMBER <= 0x009081dfL +#define TORRENT_MACOS_DEPRECATED_LIBCRYPTO 1 +#endif + +#endif // TORRENT_USE_LIBCRYPTO + +#ifdef TORRENT_USE_OPENSSL + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_WINDOWS +// because openssl includes winsock.h, we must include winsock2.h first +#include +#endif + +#include +#include // for sk_GENERAL_NAME_value +#include // for GENERAL_NAME + +#include +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +namespace ssl { + +#if defined TORRENT_BUILD_SIMULATOR + using sim::asio::ssl::context; + using sim::asio::ssl::stream_base; + using sim::asio::ssl::stream; +#else + using boost::asio::ssl::context; + using boost::asio::ssl::stream_base; + using boost::asio::ssl::stream; +#endif +} // ssl + +namespace aux { + +TORRENT_EXTRA_EXPORT void openssl_set_tlsext_hostname(SSL* s, char const* name); + +#if OPENSSL_VERSION_NUMBER >= 0x90812f + +TORRENT_EXTRA_EXPORT void openssl_set_tlsext_servername_callback(SSL_CTX* ctx + , int (*servername_callback)(SSL*, int*, void*)); + +TORRENT_EXTRA_EXPORT void openssl_set_tlsext_servername_arg(SSL_CTX* ctx, void* userdata); + +TORRENT_EXTRA_EXPORT int openssl_num_general_names(GENERAL_NAMES* gens); + +TORRENT_EXTRA_EXPORT GENERAL_NAME* openssl_general_name_value(GENERAL_NAMES* gens, int i); + +#endif // OPENSSL_VERSION_NUMBER + +} // aux +} // libtorrent + +#endif // TORRENT_USE_OPENSSL + +#endif // TORRENT_OPENSSL_HPP_INCLUDED diff --git a/include/libtorrent/aux_/path.hpp b/include/libtorrent/aux_/path.hpp new file mode 100644 index 0000000..942f33f --- /dev/null +++ b/include/libtorrent/aux_/path.hpp @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PATH_HPP_INCLUDED +#define TORRENT_PATH_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part +#define _FILE_OFFSET_BITS 64 + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#undef _FILE_OFFSET_BITS + +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + + struct file_status + { + std::int64_t file_size = 0; + std::uint64_t atime = 0; + std::uint64_t mtime = 0; + std::uint64_t ctime = 0; + enum { +#if defined TORRENT_WINDOWS + fifo = 0x1000, // named pipe (fifo) + character_special = 0x2000, // character special + directory = 0x4000, // directory + regular_file = 0x8000 // regular +#else + fifo = 0010000, // named pipe (fifo) + character_special = 0020000, // character special + directory = 0040000, // directory + block_special = 0060000, // block special + regular_file = 0100000, // regular + link = 0120000, // symbolic link + socket = 0140000 // socket +#endif + } modes_t; + int mode = 0; + }; + + // internal flags for stat_file + enum { dont_follow_links = 1 }; + TORRENT_EXTRA_EXPORT void stat_file(std::string const& f, file_status* s + , error_code& ec, int flags = 0); + TORRENT_EXTRA_EXPORT void rename(std::string const& f + , std::string const& newf, error_code& ec); + TORRENT_EXTRA_EXPORT void create_directories(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void create_directory(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void remove_all(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void remove(std::string const& f, error_code& ec); + TORRENT_EXTRA_EXPORT bool exists(std::string const& f, error_code& ec); + TORRENT_EXTRA_EXPORT bool exists(std::string const& f); + TORRENT_EXTRA_EXPORT std::int64_t file_size(std::string const& f); + TORRENT_EXTRA_EXPORT bool is_directory(std::string const& f + , error_code& ec); + TORRENT_EXTRA_EXPORT void recursive_copy(std::string const& old_path + , std::string const& new_path, error_code& ec); + TORRENT_EXTRA_EXPORT void copy_file(std::string const& f + , std::string const& newf, error_code& ec); + TORRENT_EXTRA_EXPORT void move_file(std::string const& f + , std::string const& newf, error_code& ec); + + // file is expected to exist, link will be created to point to it. If hard + // links are not supported by the filesystem or OS, the file will be copied. + TORRENT_EXTRA_EXPORT void hard_link(std::string const& file + , std::string const& link, error_code& ec); + + // split out a path segment from the left side or right side + TORRENT_EXTRA_EXPORT std::pair rsplit_path(string_view p); + TORRENT_EXTRA_EXPORT std::pair lsplit_path(string_view p); + TORRENT_EXTRA_EXPORT std::pair lsplit_path(string_view p, std::size_t pos); + + TORRENT_EXTRA_EXPORT std::string extension(std::string const& f); + TORRENT_EXTRA_EXPORT std::string remove_extension(std::string const& f); + TORRENT_EXTRA_EXPORT bool is_root_path(std::string const& f); + TORRENT_EXTRA_EXPORT bool compare_path(std::string const& lhs, std::string const& rhs); + + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string parent_path(std::string const& f); + TORRENT_EXTRA_EXPORT bool has_parent_path(std::string const& f); + + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string filename(std::string const& f); + TORRENT_EXTRA_EXPORT std::string combine_path(string_view lhs + , string_view rhs); + TORRENT_EXTRA_EXPORT void append_path(std::string& branch + , string_view leaf); + TORRENT_EXTRA_EXPORT std::string lexically_relative(string_view base + , string_view target); + + // internal used by create_torrent.hpp + TORRENT_EXTRA_EXPORT std::string complete(string_view f); + TORRENT_EXTRA_EXPORT bool is_complete(string_view f); + TORRENT_EXTRA_EXPORT std::string current_working_directory(); +#if TORRENT_USE_UNC_PATHS + TORRENT_EXTRA_EXPORT std::string canonicalize_path(string_view f); +#endif + +// internal type alias export should be used at unit tests only + using native_path_string = +#if defined TORRENT_WINDOWS + std::wstring; +#else + std::string; +#endif + +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT native_path_string convert_to_native_path_string(std::string const& path); + +#if defined TORRENT_WINDOWS +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT std::string convert_from_native_path(wchar_t const* s); +#else +// internal export should be used at unit tests only + TORRENT_EXTRA_EXPORT std::string convert_from_native_path(char const* s); +#endif + + TORRENT_EXTRA_EXPORT int bufs_size(span bufs); +} + +#endif // TORRENT_PATH_HPP_INCLUDED diff --git a/include/libtorrent/aux_/portmap.hpp b/include/libtorrent/aux_/portmap.hpp new file mode 100644 index 0000000..437b3d0 --- /dev/null +++ b/include/libtorrent/aux_/portmap.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_AUX_PORTMAP_HPP_INCLUDED +#define LIBTORRENT_AUX_PORTMAP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/portmap.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { +namespace aux { + + enum class portmap_action : std::uint8_t + { + none, add, del + }; + + struct TORRENT_EXTRA_EXPORT portmap_callback + { + // int: port-mapping index + // address: external address as queried from router + // int: external port + // int: protocol (UDP, TCP) + // error_code: error, an empty error means success + // int: transport is 0 for NAT-PMP and 1 for UPnP + virtual void on_port_mapping(port_mapping_t mapping, address const& ip, int port + , portmap_protocol proto, error_code const& ec, portmap_transport transport) = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log_portmap(portmap_transport transport) const = 0; + virtual void log_portmap(portmap_transport transport, char const* msg) const = 0; +#endif + + protected: + ~portmap_callback() {} + }; + + struct base_mapping + { + // the time the port mapping will expire + time_point expires; + + portmap_action act = portmap_action::none; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port = 0; + + portmap_protocol protocol = portmap_protocol::none; + }; + + inline char const* to_string(portmap_protocol const p) + { + return p == portmap_protocol::udp ? "UDP" : "TCP"; + } + inline char const* to_string(portmap_action const act) + { + switch (act) + { + case portmap_action::none: return "none"; + case portmap_action::add: return "add"; + case portmap_action::del: return "delete"; + } + return ""; + } +}} + +#endif // LIBTORRENT_AUX_PORTMAP_HPP_INCLUDED diff --git a/include/libtorrent/aux_/proxy_settings.hpp b/include/libtorrent/aux_/proxy_settings.hpp new file mode 100644 index 0000000..9cb982f --- /dev/null +++ b/include/libtorrent/aux_/proxy_settings.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2003-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_SETTINGS_HPP_INCLUDED +#define TORRENT_PROXY_SETTINGS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" + +#include + +namespace libtorrent { + +struct settings_pack; +namespace aux { + + struct session_settings; + + struct TORRENT_EXPORT proxy_settings + { + // defaults constructs proxy settings, initializing it to the default + // settings. + proxy_settings(); + + // construct the proxy_settings object from the settings + // this constructor is implemented in session_impl.cpp + explicit proxy_settings(settings_pack const& sett); + explicit proxy_settings(aux::session_settings const& sett); + + // the name or IP of the proxy server. ``port`` is the port number the + // proxy listens to. If required, ``username`` and ``password`` can be + // set to authenticate with the proxy. + std::string hostname; + + // when using a proxy type that requires authentication, the username + // and password fields must be set to the credentials for the proxy. + std::string username; + std::string password; + + // tells libtorrent what kind of proxy server it is. See proxy_type_t + // enum for options + settings_pack::proxy_type_t type = settings_pack::none; + + // the port the proxy server is running on + std::uint16_t port = 0; + + // defaults to true. It means that hostnames should be attempted to be + // resolved through the proxy instead of using the local DNS service. + // This is only supported by SOCKS5 and HTTP. + bool proxy_hostnames = true; + + // determines whether or not to exempt peer and web seed connections + // from using the proxy. This defaults to true, i.e. peer connections are + // proxied by default. + bool proxy_peer_connections = true; + + // if true, tracker connections are subject to the proxy settings + bool proxy_tracker_connections = true; + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/range.hpp b/include/libtorrent/aux_/range.hpp new file mode 100644 index 0000000..4856ce9 --- /dev/null +++ b/include/libtorrent/aux_/range.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANGE_HPP +#define TORRENT_RANGE_HPP + +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { namespace aux { + + template + struct iterator_range + { + Iter _begin; + Iter _end; + Iter begin() { return _begin; } + Iter end() { return _end; } + }; + + template + iterator_range range(Iter begin, Iter end) + { return { begin, end}; } + + template + iterator_range range(vector& vec + , IndexType begin, IndexType end) + { + using type = typename underlying_index_t::type; + return {vec.data() + static_cast(begin), vec.data() + static_cast(end)}; + } + + template + iterator_range range(vector const& vec + , IndexType begin, IndexType end) + { + using type = typename underlying_index_t::type; + return {vec.data() + static_cast(begin), vec.data() + static_cast(end)}; + } +}} + +#endif diff --git a/include/libtorrent/aux_/route.h b/include/libtorrent/aux_/route.h new file mode 100644 index 0000000..b2bcea3 --- /dev/null +++ b/include/libtorrent/aux_/route.h @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1980, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)route.h 8.3 (Berkeley) 4/19/94 + * $FreeBSD: src/sys/net/route.h,v 1.36.2.1 2000/08/16 06:14:23 jayanth Exp $ + */ + +#ifndef _NET_ROUTE_H_ +#define _NET_ROUTE_H_ +#include +#include +#include +#include + +/* + * Kernel resident routing tables. + * + * The routing tables are initialized when interface addresses + * are set by making entries for all directly connected interfaces. + */ + +/* + * A route consists of a destination address and a reference + * to a routing entry. These are often held by protocols + * in their control blocks, e.g. inpcb. + */ +#ifdef PRIVATE +struct rtentry; +struct route { + /* + * N.B: struct route must begin with ro_rt and ro_flags + * because the code does some casts of a 'struct route_in6 *' + * to a 'struct route *'. + */ + struct rtentry *ro_rt; + uint32_t ro_flags; /* route flags (see below) */ + struct sockaddr ro_dst; +}; + +#define ROF_SRCIF_SELECTED 0x1 /* source interface was selected */ + +#else +struct route; +#endif /* PRIVATE */ + +/* + * These numbers are used by reliable protocols for determining + * retransmission behavior and are included in the routing structure. + */ +struct rt_metrics { + u_int32_t rmx_locks; /* Kernel must leave these values alone */ + u_int32_t rmx_mtu; /* MTU for this path */ + u_int32_t rmx_hopcount; /* max hops expected */ + int32_t rmx_expire; /* lifetime for route, e.g. redirect */ + u_int32_t rmx_recvpipe; /* inbound delay-bandwidth product */ + u_int32_t rmx_sendpipe; /* outbound delay-bandwidth product */ + u_int32_t rmx_ssthresh; /* outbound gateway buffer limit */ + u_int32_t rmx_rtt; /* estimated round trip time */ + u_int32_t rmx_rttvar; /* estimated rtt variance */ + u_int32_t rmx_pksent; /* packets sent using this route */ + u_int32_t rmx_filler[4]; /* will be used for T/TCP later */ +}; + +/* + * rmx_rtt and rmx_rttvar are stored as microseconds; + */ +#define RTM_RTTUNIT 1000000 /* units for rtt, rttvar, as units per sec */ + +/* + * We distinguish between routes to hosts and routes to networks, + * preferring the former if available. For each route we infer + * the interface to use from the gateway address supplied when + * the route was entered. Routes that forward packets through + * gateways are marked so that the output routines know to address the + * gateway rather than the ultimate destination. + */ +#ifdef KERNEL_PRIVATE +#include +#ifndef RNF_NORMAL +#include +#endif +/* + * Kernel routing entry structure (private). + */ +struct rtentry { + struct radix_node rt_nodes[2]; /* tree glue, and other values */ +#define rt_key(r) ((struct sockaddr *)((r)->rt_nodes->rn_key)) +#define rt_mask(r) ((struct sockaddr *)((r)->rt_nodes->rn_mask)) + struct sockaddr *rt_gateway; /* value */ + int32_t rt_refcnt; /* # held references */ + uint32_t rt_flags; /* up/down?, host/net */ + struct ifnet *rt_ifp; /* the answer: interface to use */ + struct ifaddr *rt_ifa; /* the answer: interface addr to use */ + struct sockaddr *rt_genmask; /* for generation of cloned routes */ + void *rt_llinfo; /* pointer to link level info cache */ + void (*rt_llinfo_free)(void *); /* link level info free function */ + struct rt_metrics rt_rmx; /* metrics used by rx'ing protocols */ + struct rtentry *rt_gwroute; /* implied entry for gatewayed routes */ + struct rtentry *rt_parent; /* cloning parent of this route */ + uint32_t generation_id; /* route generation id */ + /* + * See bsd/net/route.c for synchronization notes. + */ + decl_lck_mtx_data(, rt_lock); /* lock for routing entry */ +}; +#endif /* KERNEL_PRIVATE */ + +#ifdef KERNEL_PRIVATE +#define rt_use rt_rmx.rmx_pksent +#endif /* KERNEL_PRIVATE */ + +#define RTF_UP 0x1 /* route usable */ +#define RTF_GATEWAY 0x2 /* destination is a gateway */ +#define RTF_HOST 0x4 /* host entry (net otherwise) */ +#define RTF_REJECT 0x8 /* host or net unreachable */ +#define RTF_DYNAMIC 0x10 /* created dynamically (by redirect) */ +#define RTF_MODIFIED 0x20 /* modified dynamically (by redirect) */ +#define RTF_DONE 0x40 /* message confirmed */ +#define RTF_DELCLONE 0x80 /* delete cloned route */ +#define RTF_CLONING 0x100 /* generate new routes on use */ +#define RTF_XRESOLVE 0x200 /* external daemon resolves name */ +#define RTF_LLINFO 0x400 /* generated by link layer (e.g. ARP) */ +#define RTF_STATIC 0x800 /* manually added */ +#define RTF_BLACKHOLE 0x1000 /* just discard pkts (during updates) */ +#define RTF_PROTO2 0x4000 /* protocol specific routing flag */ +#define RTF_PROTO1 0x8000 /* protocol specific routing flag */ + +#define RTF_PRCLONING 0x10000 /* protocol requires cloning */ +#define RTF_WASCLONED 0x20000 /* route generated through cloning */ +#define RTF_PROTO3 0x40000 /* protocol specific routing flag */ + /* 0x80000 unused */ +#define RTF_PINNED 0x100000 /* future use */ +#define RTF_LOCAL 0x200000 /* route represents a local address */ +#define RTF_BROADCAST 0x400000 /* route represents a bcast address */ +#define RTF_MULTICAST 0x800000 /* route represents a mcast address */ +#define RTF_IFSCOPE 0x1000000 /* has valid interface scope */ +#define RTF_CONDEMNED 0x2000000 /* defunct; no longer modifiable */ + /* 0x4000000 and up unassigned */ + +/* + * Routing statistics. + */ +struct rtstat { + short rts_badredirect; /* bogus redirect calls */ + short rts_dynamic; /* routes created by redirects */ + short rts_newgateway; /* routes modified by redirects */ + short rts_unreach; /* lookups which failed */ + short rts_wildcard; /* lookups satisfied by a wildcard */ +}; + +/* + * Structures for routing messages. + */ +struct rt_msghdr { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + pid_t rtm_pid; /* identify sender */ + int rtm_seq; /* for sender to identify action */ + int rtm_errno; /* why failed */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; + +struct rt_msghdr2 { + u_short rtm_msglen; /* to skip over non-understood messages */ + u_char rtm_version; /* future binary compatibility */ + u_char rtm_type; /* message type */ + u_short rtm_index; /* index for associated ifp */ + int rtm_flags; /* flags, incl. kern & message, e.g. DONE */ + int rtm_addrs; /* bitmask identifying sockaddrs in msg */ + int32_t rtm_refcnt; /* reference count */ + int rtm_parentflags; /* flags of the parent route */ + int rtm_reserved; /* reserved field set to 0 */ + int rtm_use; /* from rtentry */ + u_int32_t rtm_inits; /* which metrics we are initializing */ + struct rt_metrics rtm_rmx; /* metrics themselves */ +}; + + +#define RTM_VERSION 5 /* Up the ante and ignore older versions */ + +/* + * Message types. + */ +#define RTM_ADD 0x1 /* Add Route */ +#define RTM_DELETE 0x2 /* Delete Route */ +#define RTM_CHANGE 0x3 /* Change Metrics or flags */ +#define RTM_GET 0x4 /* Report Metrics */ +#define RTM_LOSING 0x5 /* Kernel Suspects Partitioning */ +#define RTM_REDIRECT 0x6 /* Told to use different route */ +#define RTM_MISS 0x7 /* Lookup failed on this address */ +#define RTM_LOCK 0x8 /* fix specified metrics */ +#define RTM_OLDADD 0x9 /* caused by SIOCADDRT */ +#define RTM_OLDDEL 0xa /* caused by SIOCDELRT */ +#define RTM_RESOLVE 0xb /* req to resolve dst to LL addr */ +#define RTM_NEWADDR 0xc /* address being added to iface */ +#define RTM_DELADDR 0xd /* address being removed from iface */ +#define RTM_IFINFO 0xe /* iface going up/down etc. */ +#define RTM_NEWMADDR 0xf /* mcast group membership being added to if */ +#define RTM_DELMADDR 0x10 /* mcast group membership being deleted */ +#ifdef PRIVATE +#define RTM_GET_SILENT 0x11 +#endif /* PRIVATE */ +#define RTM_IFINFO2 0x12 /* */ +#define RTM_NEWMADDR2 0x13 /* */ +#define RTM_GET2 0x14 /* */ + +/* + * Bitmask values for rtm_inits and rmx_locks. + */ +#define RTV_MTU 0x1 /* init or lock _mtu */ +#define RTV_HOPCOUNT 0x2 /* init or lock _hopcount */ +#define RTV_EXPIRE 0x4 /* init or lock _expire */ +#define RTV_RPIPE 0x8 /* init or lock _recvpipe */ +#define RTV_SPIPE 0x10 /* init or lock _sendpipe */ +#define RTV_SSTHRESH 0x20 /* init or lock _ssthresh */ +#define RTV_RTT 0x40 /* init or lock _rtt */ +#define RTV_RTTVAR 0x80 /* init or lock _rttvar */ + +/* + * Bitmask values for rtm_addrs. + */ +#define RTA_DST 0x1 /* destination sockaddr present */ +#define RTA_GATEWAY 0x2 /* gateway sockaddr present */ +#define RTA_NETMASK 0x4 /* netmask sockaddr present */ +#define RTA_GENMASK 0x8 /* cloning mask sockaddr present */ +#define RTA_IFP 0x10 /* interface name sockaddr present */ +#define RTA_IFA 0x20 /* interface addr sockaddr present */ +#define RTA_AUTHOR 0x40 /* sockaddr for author of redirect */ +#define RTA_BRD 0x80 /* for NEWADDR, broadcast or p-p dest addr */ + +/* + * Index offsets for sockaddr array for alternate internal encoding. + */ +#define RTAX_DST 0 /* destination sockaddr present */ +#define RTAX_GATEWAY 1 /* gateway sockaddr present */ +#define RTAX_NETMASK 2 /* netmask sockaddr present */ +#define RTAX_GENMASK 3 /* cloning mask sockaddr present */ +#define RTAX_IFP 4 /* interface name sockaddr present */ +#define RTAX_IFA 5 /* interface addr sockaddr present */ +#define RTAX_AUTHOR 6 /* sockaddr for author of redirect */ +#define RTAX_BRD 7 /* for NEWADDR, broadcast or p-p dest addr */ +#define RTAX_MAX 8 /* size of array to allocate */ + +struct rt_addrinfo { + int rti_addrs; + struct sockaddr *rti_info[RTAX_MAX]; +}; + +struct route_cb { + int ip_count; + int ip6_count; + int ipx_count; + int ns_count; + int iso_count; + int any_count; +}; + +#ifdef PRIVATE +/* + * For scoped routing; a zero interface scope value means nil/no scope. + */ +#define IFSCOPE_NONE 0 +#endif /* PRIVATE */ + +#ifdef KERNEL_PRIVATE +/* + * Generic call trace used by some subsystems (e.g. route, ifaddr) + */ +#define CTRACE_STACK_SIZE 8 /* depth of stack trace */ +#define CTRACE_HIST_SIZE 4 /* refcnt history size */ +typedef struct ctrace { + void *th; /* thread ptr */ + void *pc[CTRACE_STACK_SIZE]; /* PC stack trace */ +} ctrace_t; + +extern void ctrace_record(ctrace_t *); + +#define RT_LOCK_ASSERT_HELD(_rt) \ + lck_mtx_assert(&(_rt)->rt_lock, LCK_MTX_ASSERT_OWNED) + +#define RT_LOCK_ASSERT_NOTHELD(_rt) \ + lck_mtx_assert(&(_rt)->rt_lock, LCK_MTX_ASSERT_NOTOWNED) + +#define RT_LOCK(_rt) do { \ + if (!rte_debug) \ + lck_mtx_lock(&(_rt)->rt_lock); \ + else \ + rt_lock(_rt, FALSE); \ +} while (0) + +#define RT_LOCK_SPIN(_rt) do { \ + if (!rte_debug) \ + lck_mtx_lock_spin(&(_rt)->rt_lock); \ + else \ + rt_lock(_rt, TRUE); \ +} while (0) + +#define RT_CONVERT_LOCK(_rt) do { \ + RT_LOCK_ASSERT_HELD(_rt); \ + lck_mtx_convert_spin(&(_rt)->rt_lock); \ +} while (0) + +#define RT_UNLOCK(_rt) do { \ + if (!rte_debug) \ + lck_mtx_unlock(&(_rt)->rt_lock); \ + else \ + rt_unlock(_rt); \ +} while (0) + +#define RT_ADDREF_LOCKED(_rt) do { \ + if (!rte_debug) { \ + RT_LOCK_ASSERT_HELD(_rt); \ + if (++(_rt)->rt_refcnt == 0) \ + panic("RT_ADDREF(%p) bad refcnt\n", _rt); \ + } else { \ + rtref(_rt); \ + } \ +} while (0) + +/* + * Spin variant mutex is used here; caller is responsible for + * converting any previously-held similar lock to full mutex. + */ +#define RT_ADDREF(_rt) do { \ + RT_LOCK_SPIN(_rt); \ + RT_ADDREF_LOCKED(_rt); \ + RT_UNLOCK(_rt); \ +} while (0) + +#define RT_REMREF_LOCKED(_rt) do { \ + if (!rte_debug) { \ + RT_LOCK_ASSERT_HELD(_rt); \ + if ((_rt)->rt_refcnt == 0) \ + panic("RT_REMREF(%p) bad refcnt\n", _rt); \ + --(_rt)->rt_refcnt; \ + } else { \ + (void) rtunref(_rt); \ + } \ +} while (0) + +/* + * Spin variant mutex is used here; caller is responsible for + * converting any previously-held similar lock to full mutex. + */ +#define RT_REMREF(_rt) do { \ + RT_LOCK_SPIN(_rt); \ + RT_REMREF_LOCKED(_rt); \ + RT_UNLOCK(_rt); \ +} while (0) + +#define RTFREE(_rt) rtfree(_rt) +#define RTFREE_LOCKED(_rt) rtfree_locked(_rt) + +extern struct route_cb route_cb; +extern struct radix_node_head *rt_tables[AF_MAX+1]; +__private_extern__ lck_mtx_t *rnh_lock; +__private_extern__ int use_routegenid; +__private_extern__ uint32_t route_generation; +__private_extern__ int rttrash; +__private_extern__ unsigned int rte_debug; + +struct ifmultiaddr; +struct proc; + +extern void route_init(void) __attribute__((section("__TEXT, initcode"))); +extern void routegenid_update(void); +extern void rt_ifmsg(struct ifnet *); +extern void rt_missmsg(int, struct rt_addrinfo *, int, int); +extern void rt_newaddrmsg(int, struct ifaddr *, int, struct rtentry *); +extern void rt_newmaddrmsg(int, struct ifmultiaddr *); +extern int rt_setgate(struct rtentry *, struct sockaddr *, struct sockaddr *); +extern void set_primary_ifscope(unsigned int); +extern unsigned int get_primary_ifscope(void); +extern boolean_t rt_inet_default(struct rtentry *, struct sockaddr *); +extern struct rtentry *rt_lookup(boolean_t, struct sockaddr *, + struct sockaddr *, struct radix_node_head *, unsigned int); +extern void rtalloc(struct route *); +extern void rtalloc_ign(struct route *, uint32_t); +extern void rtalloc_ign_locked(struct route *, uint32_t); +extern void rtalloc_scoped_ign(struct route *, uint32_t, unsigned int); +extern void rtalloc_scoped_ign_locked(struct route *, uint32_t, unsigned int); +extern struct rtentry *rtalloc1(struct sockaddr *, int, uint32_t); +extern struct rtentry *rtalloc1_locked(struct sockaddr *, int, uint32_t); +extern struct rtentry *rtalloc1_scoped(struct sockaddr *, int, uint32_t, + unsigned int); +extern struct rtentry *rtalloc1_scoped_locked(struct sockaddr *, int, + uint32_t, unsigned int); +extern void rtfree(struct rtentry *); +extern void rtfree_locked(struct rtentry *); +extern void rtref(struct rtentry *); +/* + * rtunref will decrement the refcount, rtfree will decrement and free if + * the refcount has reached zero and the route is not up. + * Unless you have good reason to do otherwise, use rtfree. + */ +extern int rtunref(struct rtentry *); +extern void rtsetifa(struct rtentry *, struct ifaddr *); +extern int rtinit(struct ifaddr *, int, int); +extern int rtinit_locked(struct ifaddr *, int, int); +extern int rtioctl(unsigned long, caddr_t, struct proc *); +extern void rtredirect(struct ifnet *, struct sockaddr *, struct sockaddr *, + struct sockaddr *, int, struct sockaddr *, struct rtentry **); +extern int rtrequest(int, struct sockaddr *, + struct sockaddr *, struct sockaddr *, int, struct rtentry **); +extern int rtrequest_locked(int, struct sockaddr *, + struct sockaddr *, struct sockaddr *, int, struct rtentry **); +extern int rtrequest_scoped_locked(int, struct sockaddr *, struct sockaddr *, + struct sockaddr *, int, struct rtentry **, unsigned int); +extern unsigned int sa_get_ifscope(struct sockaddr *); +extern void rt_lock(struct rtentry *, boolean_t); +extern void rt_unlock(struct rtentry *); +extern struct sockaddr *rtm_scrub_ifscope(int, struct sockaddr *, + struct sockaddr *, struct sockaddr_storage *); +#endif /* KERNEL_PRIVATE */ + +#endif diff --git a/include/libtorrent/aux_/scope_end.hpp b/include/libtorrent/aux_/scope_end.hpp new file mode 100644 index 0000000..4162d94 --- /dev/null +++ b/include/libtorrent/aux_/scope_end.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SCOPE_END_HPP_INCLUDED +#define TORRENT_SCOPE_END_HPP_INCLUDED + +#include + +namespace libtorrent { namespace aux { + + template + struct scope_end_impl + { + explicit scope_end_impl(Fun f) : m_fun(std::move(f)) {} + ~scope_end_impl() { if (m_armed) m_fun(); } + + // movable + scope_end_impl(scope_end_impl&&) noexcept = default; + scope_end_impl& operator=(scope_end_impl&&) noexcept = default; + + // non-copyable + scope_end_impl(scope_end_impl const&) = delete; + scope_end_impl& operator=(scope_end_impl const&) = delete; + void disarm() { m_armed = false; } + private: + Fun m_fun; + bool m_armed = true; + }; + + template + scope_end_impl scope_end(Fun f) { return scope_end_impl(std::move(f)); } +}} + +#endif + diff --git a/include/libtorrent/aux_/session_call.hpp b/include/libtorrent/aux_/session_call.hpp new file mode 100644 index 0000000..774c609 --- /dev/null +++ b/include/libtorrent/aux_/session_call.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2014, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_CALL_HPP_INCLUDED +#define TORRENT_SESSION_CALL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#include + +namespace libtorrent { namespace aux { + +void blocking_call(); +void dump_call_profile(); + +void torrent_wait(bool& done, aux::session_impl& ses); + +} } // namespace aux namespace libtorrent + +#endif // TORRENT_SESSION_CALL_HPP_INCLUDED + diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp new file mode 100644 index 0000000..0d7a8d8 --- /dev/null +++ b/include/libtorrent/aux_/session_impl.hpp @@ -0,0 +1,1404 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_IMPL_HPP_INCLUDED +#define TORRENT_SESSION_IMPL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/session_udp_sockets.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/allocating_handler.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#endif + +#include "libtorrent/session.hpp" // for user_load_function_t +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/ip_notifier.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/alert_manager.hpp" // for alert_manager +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/socket_io.hpp" // for print_address +#include "libtorrent/address.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/disk_io_job.hpp" // block_cache_reference +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" +#include "libtorrent/resolver.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/lsd.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/span.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#endif + +#if TORRENT_COMPLETE_TYPES_REQUIRED +#include "libtorrent/peer_connection.hpp" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include // for va_start, va_end +#include + +namespace libtorrent { + + struct plugin; + struct upnp; + struct natpmp; + struct lsd; + class torrent; + class alert; + struct torrent_handle; + +namespace dht { + + struct dht_tracker; + class item; + +} + +namespace aux { + + struct session_impl; + struct session_settings; + +#ifndef TORRENT_DISABLE_LOGGING + struct tracker_logger; +#endif + + using listen_socket_flags_t = flags::bitfield_flag; + + struct listen_port_mapping + { + port_mapping_t mapping = port_mapping_t{-1}; + int port = 0; + }; + + struct TORRENT_EXTRA_EXPORT listen_socket_t : utp_socket_interface + { + // we accept incoming connections on this interface + static constexpr listen_socket_flags_t accept_incoming = 0_bit; + + // this interface was specified to be just the local network. If this flag + // is not set, this interface is assumed to have a path to the internet + // (i.e. have a gateway configured) + static constexpr listen_socket_flags_t local_network = 1_bit; + + // this interface was expanded from the user requesting to + // listen on an unspecified address (either IPv4 or IPv6) + static constexpr listen_socket_flags_t was_expanded = 2_bit; + + // there's a proxy configured, and this is the only one interface + // representing that one proxy + static constexpr listen_socket_flags_t proxy = 3_bit; + + listen_socket_t() = default; + + // listen_socket_t should not be copied or moved because + // references to it are held by the DHT and tracker announce + // code. That code expects a listen_socket_t to always refer + // to the same socket. It would be easy to accidentally + // invalidate that assumption if copying or moving were allowed. + listen_socket_t(listen_socket_t const&) = delete; + listen_socket_t(listen_socket_t&&) = delete; + listen_socket_t& operator=(listen_socket_t const&) = delete; + listen_socket_t& operator=(listen_socket_t&&) = delete; + + udp::endpoint get_local_endpoint() override + { + error_code ec; + if (udp_sock) return udp_sock->sock.local_endpoint(ec); + return {local_endpoint.address(), local_endpoint.port()}; + } + + // returns true if this listen socket/interface can reach and be reached + // by the given address. This is useful to know whether it should be + // annoucned to a tracker (given the tracker's IP) or whether it should + // have a SOCKS5 UDP tunnel set up (given the IP of the socks proxy) + bool can_route(address const&) const; + + // this may be empty but can be set + // to the WAN IP address of a NAT router + ip_voter external_address; + + // this is a cached local endpoint for the listen TCP socket + tcp::endpoint local_endpoint; + + address netmask; + + // the name of the device the socket is bound to, may be empty + // if the socket is not bound to a device + std::string device; + + // this is the port that was originally specified to listen on it may be + // different from local_endpoint.port() if we had to retry binding with a + // higher port + int original_port = 0; + + // tcp_external_port and udp_external_port return the port which + // should be published to peers/trackers for this socket + // If there are active NAT mappings the return value will be + // the external port returned by the NAT router, otherwise the + // local listen port is returned + int tcp_external_port() + { + for (auto const& m : tcp_port_mapping) + { + if (m.port != 0) return m.port; + } + return local_endpoint.port(); + } + + int udp_external_port() + { + for (auto const& m : udp_port_mapping) + { + if (m.port != 0) return m.port; + } + if (udp_sock) return udp_sock->sock.local_port(); + return 0; + } + + // 0 is natpmp 1 is upnp + // the order of these arrays determines the priorty in + // which their ports will be announced to peers + aux::array tcp_port_mapping; + aux::array udp_port_mapping; + + // indicates whether this is an SSL listen socket or not + transport ssl = transport::plaintext; + + listen_socket_flags_t flags = accept_incoming; + + // the actual sockets (TCP listen socket and UDP socket) + // An entry does not necessarily have a UDP or TCP socket. One of these + // pointers may be nullptr! + // These must be shared_ptr to avoid a dangling reference if an + // incoming packet is in the event queue when the socket is erased + // TODO: make these direct members and generate shared_ptrs to them + // which alias the listen_socket_t shared_ptr + std::shared_ptr sock; + std::shared_ptr udp_sock; + + // since udp packets are expected to be dispatched frequently, this saves + // time on handler allocation every time we read again. + aux::handler_storage udp_handler_storage; + + std::shared_ptr natpmp_mapper; + std::shared_ptr upnp_mapper; + + std::shared_ptr lsd; + + // set to true when we receive an incoming connection from this listen + // socket + bool incoming_connection = false; + }; + + struct TORRENT_EXTRA_EXPORT listen_endpoint_t + { + listen_endpoint_t(address const& adr, int p, std::string dev, transport s + , listen_socket_flags_t f, address const& nmask = address{}) + : addr(adr), netmask(nmask), port(p), device(std::move(dev)), ssl(s), flags(f) {} + + bool operator==(listen_endpoint_t const& o) const + { + return addr == o.addr + && port == o.port + && device == o.device + && ssl == o.ssl + && flags == o.flags; + } + + address addr; + // if this listen endpoint/interface doesn't have a gateway, we cannot + // route outside of our network, this netmask defines the range of our + // local network + address netmask; + int port; + std::string device; + transport ssl; + listen_socket_flags_t flags; + }; + + // partitions sockets based on whether they match one of the given endpoints + // all matched sockets are ordered before unmatched sockets + // matched endpoints are removed from the vector + // returns an iterator to the first unmatched socket + TORRENT_EXTRA_EXPORT std::vector>::iterator + partition_listen_sockets( + std::vector& eps + , std::vector>& sockets); + + TORRENT_EXTRA_EXPORT void interface_to_endpoints( + listen_interface_t const& iface + , listen_socket_flags_t flags + , span const ifs + , std::vector& eps); + + // expand [::] to all IPv6 interfaces for BEP 45 compliance + TORRENT_EXTRA_EXPORT void expand_unspecified_address( + span ifs + , span routes + , std::vector& eps); + + TORRENT_EXTRA_EXPORT void expand_devices(span + , std::vector& eps); + + // this is the link between the main thread and the + // thread started to run the main downloader loop + struct TORRENT_EXTRA_EXPORT session_impl final + : session_interface + , dht::dht_observer + , aux::portmap_callback + , aux::lsd_callback + , boost::noncopyable + , single_threaded + , aux::error_handler_interface + , std::enable_shared_from_this + { + // plugin feature-index key map + enum + { + plugins_all_idx = 0, // to store all plugins + plugins_optimistic_unchoke_idx = 1, // optimistic_unchoke_feature + plugins_tick_idx = 2, // tick_feature + plugins_dht_request_idx = 3 // dht_request_feature + }; + + template + void wrap(Fun f, Args&&... a); + +#if TORRENT_USE_INVARIANT_CHECKS + friend class libtorrent::invariant_access; +#endif + using connection_map = std::set>; + using torrent_map = std::unordered_map>; + + session_impl(io_service& ios, settings_pack const& pack); + ~session_impl() override; + + void start_session(); + + void init_peer_class_filter(bool unlimited_local); + + void call_abort() + { + auto self = shared_from_this(); + m_io_service.dispatch(make_handler([self] { self->abort(); } + , m_abort_handler_storage, *this)); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + using ext_function_t + = std::function(torrent_handle const&, void*)>; + + struct session_plugin_wrapper : plugin + { + explicit session_plugin_wrapper(ext_function_t f) : m_f(std::move(f)) {} + + std::shared_ptr new_torrent(torrent_handle const& t, void* user) override + { return m_f(t, user); } + ext_function_t m_f; + }; + + void add_extension(std::function( + torrent_handle const&, void*)> ext); + void add_ses_extension(std::shared_ptr ext); +#endif +#if TORRENT_USE_ASSERTS + bool has_peer(peer_connection const* p) const override; + bool any_torrent_has_peer(peer_connection const* p) const override; + bool is_single_thread() const override { return single_threaded::is_single_thread(); } + bool is_posting_torrent_updates() const override { return m_posting_torrent_updates; } + // this is set while the session is building the + // torrent status update message + bool m_posting_torrent_updates = false; + bool verify_queue_position(torrent const* t, queue_position_t pos) override; +#endif + + void on_exception(std::exception const& e) override; + void on_error(error_code const& ec) override; + + void on_ip_change(error_code const& ec); + void reopen_listen_sockets(bool map_ports = true); + void reopen_outgoing_sockets(); + void reopen_network_sockets(reopen_network_flags_t options); + + torrent_peer_allocator_interface& get_peer_allocator() override + { return m_peer_allocator; } + + io_service& get_io_service() override { return m_io_service; } + resolver_interface& get_resolver() override { return m_host_resolver; } + + aux::vector& torrent_list(torrent_list_index_t i) override + { + TORRENT_ASSERT(i >= torrent_list_index_t{}); + TORRENT_ASSERT(i < m_torrent_lists.end_index()); + return m_torrent_lists[i]; + } + + // prioritize this torrent to be allocated some connection + // attempts, because this torrent needs more peers. + // this is typically done when a torrent starts out and + // need the initial push to connect peers + void prioritize_connections(std::weak_ptr t) override; + + void async_accept(std::shared_ptr const& listener, transport ssl); + void on_accept_connection(std::shared_ptr const& s + , std::weak_ptr listener, error_code const& e, transport ssl); + + void incoming_connection(std::shared_ptr const& s); + + std::weak_ptr find_torrent(sha1_hash const& info_hash) const override; +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + + TORRENT_DEPRECATED + void set_load_function(user_load_function_t fun) + { m_user_load_torrent = fun; } + + TORRENT_DEPRECATED + std::weak_ptr find_torrent(std::string const& uuid) const; + + TORRENT_DEPRECATED + void insert_uuid_torrent(std::string uuid, std::shared_ptr const& t) override + { m_uuids.insert(std::make_pair(uuid, t)); } +#endif +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + std::vector> find_collection( + std::string const& collection) const override; +#endif + std::weak_ptr find_disconnect_candidate_torrent() const override; + int num_torrents() const override { return int(m_torrents.size()); } + + void insert_torrent(sha1_hash const& ih, std::shared_ptr const& t +#if TORRENT_ABI_VERSION == 1 + , std::string uuid +#endif + ) override; + + std::shared_ptr delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) override; + void set_queue_position(torrent* t, queue_position_t p) override; + + void close_connection(peer_connection* p) noexcept override; + + void apply_settings_pack(std::shared_ptr pack) override; + void apply_settings_pack_impl(settings_pack const& pack); + session_settings const& settings() const override { return m_settings; } + settings_pack get_settings() const; + +#ifndef TORRENT_DISABLE_DHT + dht::dht_tracker* dht() override { return m_dht.get(); } + bool announce_dht() const override { return !m_listen_sockets.empty(); } + + void add_dht_node_name(std::pair const& node); + void add_dht_node(udp::endpoint const& n) override; + void add_dht_router(std::pair const& node); + void set_dht_settings(dht::dht_settings const& s); + dht::dht_settings const& get_dht_settings() const { return m_dht_settings; } + + // you must give up ownership of the dht state + void set_dht_state(dht::dht_state&& state); + void set_dht_state(dht::dht_state const& state) = delete; + + void set_dht_storage(dht::dht_storage_constructor_type sc); + void start_dht(); + void stop_dht(); + bool has_dht() const override; + + // this is called for torrents when they are started + // it will prioritize them for announcing to + // the DHT, to get the initial peers quickly + void prioritize_dht(std::weak_ptr t) override; + + void get_immutable_callback(sha1_hash target + , dht::item const& i); + void get_mutable_callback(dht::item const& i, bool); + + void dht_get_immutable_item(sha1_hash const& target); + + void dht_get_mutable_item(std::array key + , std::string salt = std::string()); + + void dht_put_immutable_item(entry const& data, sha1_hash target); + + void dht_put_mutable_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt = std::string()); + + void dht_get_peers(sha1_hash const& info_hash); + void dht_announce(sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {}); + + void dht_live_nodes(sha1_hash const& nid); + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); + + void dht_direct_request(udp::endpoint const& ep, entry& e + , void* userdata = nullptr); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + entry dht_state() const; + TORRENT_DEPRECATED + void start_dht_deprecated(entry const& startup_state); +#endif + void on_dht_announce(error_code const& e); + void on_dht_name_lookup(error_code const& e + , std::vector
    const& addresses, int port); + void on_dht_router_name_lookup(error_code const& e + , std::vector
    const& addresses, int port); +#endif + +#if !defined TORRENT_DISABLE_ENCRYPTION + torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) override; + + void add_obfuscated_hash(sha1_hash const& obfuscated, std::weak_ptr const& t) override; +#endif + + void on_lsd_announce(error_code const& e); + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(port_mapping_t mapping, address const& ip, int port + , portmap_protocol proto, error_code const& ec + , portmap_transport transport) override; + + bool is_aborted() const override { return m_abort; } + bool is_paused() const { return m_paused; } + + void pause(); + void resume(); + + void set_ip_filter(std::shared_ptr const& f); + ip_filter const& get_ip_filter(); + + void set_port_filter(port_filter const& f); + port_filter const& get_port_filter() const override; + void ban_ip(address addr) override; + + void queue_tracker_request(tracker_request&& req + , std::weak_ptr c) override; + + // ==== peer class operations ==== + + // implements session_interface + void set_peer_classes(peer_class_set* s, address const& a, int st) override; + peer_class_pool const& peer_classes() const override { return m_classes; } + peer_class_pool& peer_classes() override { return m_classes; } + bool ignore_unchoke_slots_set(peer_class_set const& set) const override; + int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int m) override; + int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) override; + bool use_quota_overhead(bandwidth_channel* ch, int amount); + + peer_class_t create_peer_class(char const* name); + void delete_peer_class(peer_class_t cid); + void set_peer_class_filter(ip_filter const& f); + ip_filter const& get_peer_class_filter() const; + + void set_peer_class_type_filter(peer_class_type_filter f); + peer_class_type_filter get_peer_class_type_filter(); + + peer_class_info get_peer_class(peer_class_t cid) const; + void set_peer_class(peer_class_t cid, peer_class_info const& pci); + + bool is_listening() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extensions_to_torrent( + std::shared_ptr const& torrent_ptr, void* userdata); +#endif + + // the add_torrent_params object must be moved in + torrent_handle add_torrent(add_torrent_params&&, error_code& ec); + + // second return value is true if the torrent was added and false if an + // existing one was found. + std::pair, bool> + add_torrent_impl(add_torrent_params& p, error_code& ec); + void async_add_torrent(add_torrent_params* params); + +#if TORRENT_ABI_VERSION == 1 + void on_async_load_torrent(add_torrent_params* params, error_code ec); +#endif + + void remove_torrent(torrent_handle const& h, remove_flags_t options) override; + void remove_torrent_impl(std::shared_ptr tptr, remove_flags_t options) override; + + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags) const; + void refresh_torrent_status(std::vector* ret + , status_flags_t flags) const; + void post_torrent_updates(status_flags_t flags); + void post_session_stats(); + void post_dht_stats(); + + std::vector get_torrents() const; + + void pop_alerts(std::vector* alerts); + alert* wait_for_alert(time_duration max_wait); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED void pop_alerts(); + TORRENT_DEPRECATED alert const* pop_alert(); + TORRENT_DEPRECATED std::size_t set_alert_queue_size_limit(std::size_t queue_size_limit_); + TORRENT_DEPRECATED int upload_rate_limit_depr() const; + TORRENT_DEPRECATED int download_rate_limit_depr() const; + TORRENT_DEPRECATED int local_upload_rate_limit() const; + TORRENT_DEPRECATED int local_download_rate_limit() const; + + TORRENT_DEPRECATED void set_local_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED void set_local_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED void set_download_rate_limit_depr(int bytes_per_second); + TORRENT_DEPRECATED void set_upload_rate_limit_depr(int bytes_per_second); + TORRENT_DEPRECATED void set_max_connections(int limit); + TORRENT_DEPRECATED void set_max_uploads(int limit); + + TORRENT_DEPRECATED int max_connections() const; + TORRENT_DEPRECATED int max_uploads() const; +#endif + + bandwidth_manager* get_bandwidth_manager(int channel) override; + + int upload_rate_limit(peer_class_t c) const; + int download_rate_limit(peer_class_t c) const; + void set_upload_rate_limit(peer_class_t c, int limit); + void set_download_rate_limit(peer_class_t c, int limit); + + void set_rate_limit(peer_class_t c, int channel, int limit); + int rate_limit(peer_class_t c, int channel) const; + + bool preemptive_unchoke() const override; + + // deprecated, use stats counters ``num_peers_up_unchoked`` instead + int num_uploads() const override + { return int(m_stats_counters[counters::num_peers_up_unchoked]); } + + // deprecated, use stats counters ``num_peers_connected`` + + // ``num_peers_half_open`` instead. + int num_connections() const override { return int(m_connections.size()); } + + void trigger_unchoke() noexcept override + { + TORRENT_ASSERT(is_single_thread()); + m_unchoke_time_scaler = 0; + } + void trigger_optimistic_unchoke() noexcept override + { + TORRENT_ASSERT(is_single_thread()); + m_optimistic_unchoke_time_scaler = 0; + } + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" + session_status status() const; + peer_id deprecated_get_peer_id() const; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + + void get_cache_info(torrent_handle h, cache_status* ret, int flags) const; + + std::uint16_t listen_port() const override; + std::uint16_t listen_port(listen_socket_t* sock) const; + std::uint16_t ssl_listen_port() const override; + std::uint16_t ssl_listen_port(listen_socket_t* sock) const; + + // used by the DHT tracker, returns a UDP listen port + int get_listen_port(transport ssl, aux::listen_socket_handle const& s) override; + // used by peer connections, returns a TCP listen port + // or zero if no matching listen socket is found + int listen_port(transport ssl, address const& local_addr) override; + + void for_each_listen_socket(std::function f) override + { + for (auto& s : m_listen_sockets) + { + f(listen_socket_handle(s)); + } + } + + alert_manager& alerts() override { return m_alerts; } + disk_interface& disk_thread() override { return m_disk_thread; } + + void abort() noexcept; + void abort_stage2() noexcept; + + torrent_handle find_torrent_handle(sha1_hash const& info_hash); + + void announce_lsd(sha1_hash const& ih, int port) override; + + void save_state(entry* e, save_state_flags_t flags) const; + void load_state(bdecode_node const* e, save_state_flags_t flags); + + bool has_connection(peer_connection* p) const override; + void insert_peer(std::shared_ptr const& c) override; + + proxy_settings proxy() const override; + +#ifndef TORRENT_DISABLE_DHT + bool is_dht_running() const { return (m_dht.get() != nullptr); } + int external_udp_port(address const& local_address) const override; +#endif + +#if TORRENT_USE_I2P + char const* i2p_session() const override { return m_i2p_conn.session_id(); } + proxy_settings i2p_proxy() const override; + + void on_i2p_open(error_code const& ec); + void open_new_incoming_i2p_connection(); + void on_i2p_accept(std::shared_ptr const& s + , error_code const& e); +#endif + + void start_ip_notifier(); + void start_lsd(); + void start_natpmp(); + void start_upnp(); + + void stop_ip_notifier(); + void stop_lsd(); + void stop_natpmp(); + void stop_upnp(); + + std::vector add_port_mapping(portmap_protocol t, int external_port + , int local_port); + void delete_port_mapping(port_mapping_t handle); + + int next_port() const; + + void deferred_submit_jobs() override; + + // implements dht_observer + void set_external_address(aux::listen_socket_handle const& iface + , address const& ip, address const& source) override; + void get_peers(sha1_hash const& ih) override; + void announce(sha1_hash const& ih, address const& addr, int port) override; + void outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) override; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(module_t m) const override; + void log(module_t m, char const* fmt, ...) + override TORRENT_FORMAT(3,4); + void log_packet(message_direction_t dir, span pkt + , udp::endpoint const& node) override; + + bool should_log_portmap(portmap_transport transport) const override; + void log_portmap(portmap_transport transport, char const* msg) const override; + + bool should_log_lsd() const override; + void log_lsd(char const* msg) const override; +#endif + + bool on_dht_request(string_view query + , dht::msg const& request, entry& response) override; + + void set_external_address(tcp::endpoint const& local_endpoint + , address const& ip + , ip_source_t source_type, address const& source) override; + external_ip external_address() const override; + + // used when posting synchronous function + // calls to session_impl and torrent objects + mutable std::mutex mut; + mutable std::condition_variable cond; + + // implements session_interface + tcp::endpoint bind_outgoing_socket(socket_type& s + , address const& remote_address, error_code& ec) const override; + bool verify_incoming_interface(address const& addr); + bool verify_bound_address(address const& addr, bool utp + , error_code& ec) override; + + bool has_lsd() const override; + + std::vector& block_info_storage() override { return m_block_info_storage; } + + libtorrent::utp_socket_manager* utp_socket_manager() override + { return &m_utp_socket_manager; } +#ifdef TORRENT_USE_OPENSSL + libtorrent::utp_socket_manager* ssl_utp_socket_manager() override + { return &m_ssl_utp_socket_manager; } +#endif + + void inc_boost_connections() override + { + ++m_boost_connections; + m_stats_counters.inc_stats_counter(counters::boost_connection_attempts); + } + + // the settings for the client + aux::session_settings m_settings; + +#if TORRENT_ABI_VERSION == 1 + void update_ssl_listen(); + void update_local_download_rate(); + void update_local_upload_rate(); + void update_rate_limit_utp(); + void update_ignore_rate_limits_on_local_network(); +#endif + + void update_dht_upload_rate_limit(); + void update_proxy(); + void update_i2p_bridge(); + void update_peer_tos(); + void update_user_agent(); + void update_unchoke_limit(); + void update_connection_speed(); + void update_queued_disk_bytes(); + void update_alert_queue_size(); + void update_disk_threads(); + void update_report_web_seed_downloads(); + void update_outgoing_interfaces(); + void update_listen_interfaces(); + void update_privileged_ports(); + void update_auto_sequential(); + void update_max_failcount(); + void update_resolver_cache_timeout(); + + void update_ip_notifier(); + void update_upnp(); + void update_natpmp(); + void update_lsd(); + void update_dht(); + void update_count_slow(); + void update_dht_bootstrap_nodes(); + void update_dht_settings(); + + void update_socket_buffer_size(); + void update_dht_announce_interval(); + void update_download_rate(); + void update_upload_rate(); + void update_connections_limit(); + void update_alert_mask(); + void update_validate_https(); + + void trigger_auto_manage() override; + + private: + + // return the settings value for int setting "n", if the value is + // negative, return INT_MAX + int get_int_setting(int n) const; + + aux::array, num_torrent_lists, torrent_list_index_t> + m_torrent_lists; + + peer_class_pool m_classes; + + void init(); + + void submit_disk_jobs(); + + void on_trigger_auto_manage(); + + void on_lsd_peer(tcp::endpoint const& peer, sha1_hash const& ih) override; + + void start_natpmp(aux::listen_socket_t& s); + void start_upnp(aux::listen_socket_t& s); + + void set_external_address(std::shared_ptr const& sock, address const& ip + , ip_source_t source_type, address const& source); + + counters m_stats_counters; + + // this is a pool allocator for torrent_peer objects + // torrents and the disk cache (implicitly by holding references to the + // torrents) depend on this outliving them. + torrent_peer_allocator m_peer_allocator; + + // this vector is used to store the block_info + // objects pointed to by partial_piece_info returned + // by torrent::get_download_queue. + std::vector m_block_info_storage; + + io_service& m_io_service; + +#ifdef TORRENT_USE_OPENSSL + // this is a generic SSL context used when talking to HTTPS servers + ssl::context m_ssl_ctx; + + // this is the SSL context used for SSL listen sockets. It doesn't + // verify peers, but it has the servername callback set on it. Once it + // knows which torrent a peer is connecting to, it will switch the + // socket over to the torrent specific context, which does verify peers + ssl::context m_peer_ssl_ctx; +#endif + + // handles delayed alerts + mutable alert_manager m_alerts; + +#if TORRENT_ABI_VERSION == 1 + // the alert pointers stored in m_alerts + mutable aux::vector m_alert_pointers; + + // if not all the alerts in m_alert_pointers have been delivered to + // the client. This is the offset into m_alert_pointers where the next + // alert is. If this is greater than or equal to m_alert_pointers.size() + // it means we need to request new alerts from the main thread. + mutable int m_alert_pointer_pos = 0; +#endif + + // handles disk io requests asynchronously + // peers have pointers into the disk buffer + // pool, and must be destructed before this + // object. The disk thread relies on the file + // pool object, and must be destructed before + // m_files. The disk io thread posts completion + // events to the io service, and needs to be + // constructed after it. + disk_io_thread m_disk_thread; + + // the bandwidth manager is responsible for + // handing out bandwidth to connections that + // asks for it, it can also throttle the + // rate. + bandwidth_manager m_download_rate; + bandwidth_manager m_upload_rate; + + // the peer class that all peers belong to by default + peer_class_t m_global_class{0}; + + // the peer class all TCP peers belong to by default + // all tcp peer connections are subject to these + // bandwidth limits. Local peers are exempted + // from this limit. The purpose is to be able to + // throttle TCP that passes over the internet + // bottleneck (i.e. modem) to avoid starving out + // uTP connections. + peer_class_t m_tcp_peer_class{0}; + + // peer class for local peers + peer_class_t m_local_peer_class{0}; + + resolver m_host_resolver; + + tracker_manager m_tracker_manager; + + // the torrents must be destructed after the torrent_peer_allocator, + // since the torrents hold the peer lists that own the torrent_peers + // (which are allocated in the torrent_peer_allocator) + torrent_map m_torrents; + + // all torrents that are downloading or queued, + // ordered by their queue position + aux::vector m_download_queue; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // this maps obfuscated hashes to torrents. It's only + // used when encryption is enabled + torrent_map m_obfuscated_torrents; +#endif + +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + std::map> m_uuids; +#endif + + // peer connections are put here when disconnected to avoid + // race conditions with the disk thread. It's important that + // peer connections are destructed from the network thread, + // once a peer is disconnected, it's put in this list and + // every second their refcount is checked, and if it's 1, + // they are deleted (from the network thread) + std::vector> m_undead_peers; + + // keep the io_service alive until we have posted the job + // to clear the undead peers + std::unique_ptr m_work; + + // this maps sockets to their peer_connection + // object. It is the complete list of all connected + // peers. + connection_map m_connections; + + // this list holds incoming connections while they + // are performing SSL handshake. When we shut down + // the session, all of these are disconnected, otherwise + // they would linger and stall or hang session shutdown + std::set> m_incoming_sockets; + + // maps IP ranges to bitfields representing peer class IDs + // to assign peers matching a specific IP range based on its + // remote endpoint + ip_filter m_peer_class_filter; + + // maps socket types to peer classes + peer_class_type_filter m_peer_class_type_filter; + + // filters incoming connections + std::shared_ptr m_ip_filter; + + // filters outgoing connections + port_filter m_port_filter; + + // posts a notification when the set of local IPs changes + std::unique_ptr m_ip_notifier; + + // the addresses or device names of the interfaces we are supposed to + // listen on. if empty, it means that we should let the os decide + // which interface to listen on + std::vector m_listen_interfaces; + + // the network interfaces outgoing connections are opened through. If + // there is more then one, they are used in a round-robin fashion + // each element is a device name or IP address (in string form) and + // a port number. The port determines which port to bind the listen + // socket to, and the device or IP determines which network adapter + // to be used. If no adapter with the specified name exists, the listen + // socket fails. + std::vector m_outgoing_interfaces; + + // since we might be listening on multiple interfaces + // we might need more than one listen socket + std::vector> m_listen_sockets; + +#if TORRENT_USE_I2P + i2p_connection m_i2p_conn; + std::shared_ptr m_i2p_listen_socket; +#endif + +#ifdef TORRENT_USE_OPENSSL + ssl::context* ssl_ctx() override { return &m_ssl_ctx; } + void on_incoming_utp_ssl(std::shared_ptr const& s); + void ssl_handshake(error_code const& ec, std::shared_ptr s); +#endif + + // round-robin index into m_outgoing_interfaces + mutable std::uint8_t m_interface_index = 0; + + std::shared_ptr setup_listener( + listen_endpoint_t const& lep, error_code& ec); + +#ifndef TORRENT_DISABLE_DHT + dht::dht_state m_dht_state; +#endif + + // this is initialized to the unchoke_interval + // session_setting and decreased every second. + // when it reaches zero, it is reset to the + // unchoke_interval and the unchoke set is + // recomputed. + // TODO: replace this by a proper asio timer + int m_unchoke_time_scaler = 0; + + // this is used to decide when to recalculate which + // torrents to keep queued and which to activate + // TODO: replace this by a proper asio timer + int m_auto_manage_time_scaler = 0; + + // works like unchoke_time_scaler but it + // is only decreased when the unchoke set + // is recomputed, and when it reaches zero, + // the optimistic unchoke is moved to another peer. + // TODO: replace this by a proper asio timer + int m_optimistic_unchoke_time_scaler = 0; + + // works like unchoke_time_scaler. Each time + // it reaches 0, and all the connections are + // used, the worst connection will be disconnected + // from the torrent with the most peers + int m_disconnect_time_scaler = 90; + + // when this scaler reaches zero, it will + // scrape one of the auto managed, paused, + // torrents. + int m_auto_scrape_time_scaler = 180; + + // statistics gathered from all torrents. + stat m_stat; + + // implements session_interface + void sent_bytes(int bytes_payload, int bytes_protocol) override; + void received_bytes(int bytes_payload, int bytes_protocol) override; + void trancieve_ip_packet(int bytes, bool ipv6) override; + void sent_syn(bool ipv6) override; + void received_synack(bool ipv6) override; + +#if TORRENT_ABI_VERSION == 1 + int m_peak_up_rate = 0; +#endif + + void on_tick(error_code const& e); + + void try_connect_more_peers(); + void auto_manage_checking_torrents(std::vector& list + , int& limit); + void auto_manage_torrents(std::vector& list + , int& dht_limit, int& tracker_limit + , int& lsd_limit, int& hard_limit, int type_limit); + void recalculate_auto_managed_torrents(); + void recalculate_unchoke_slots(); + void recalculate_optimistic_unchoke_slots(); + + time_point m_created; + std::uint16_t session_time() const override + { + // +1 is here to make it possible to distinguish uninitialized (to + // 0) timestamps and timestamps of things that happened during the + // first second after the session was constructed + std::int64_t const ret = total_seconds(aux::time_now() + - m_created) + 1; + TORRENT_ASSERT(ret >= 0); + if (ret > (std::numeric_limits::max)()) + return (std::numeric_limits::max)(); + return static_cast(ret); + } + time_point session_start_time() const override + { + return m_created; + } + + time_point m_last_tick; + time_point m_last_second_tick; + + // the last time we went through the peers + // to decide which ones to choke/unchoke + time_point m_last_choke; + + // the last time we recalculated which torrents should be started + // and stopped (only the auto managed ones) + time_point m_last_auto_manage; + + // when outgoing_ports is configured, this is the + // port we'll bind the next outgoing socket to + mutable int m_next_port = 0; + +#ifndef TORRENT_DISABLE_DHT + std::unique_ptr m_dht_storage; + std::shared_ptr m_dht; + dht::settings m_dht_settings; + dht::dht_storage_constructor_type m_dht_storage_constructor + = dht::dht_default_storage_constructor; + + // these are used when starting the DHT + // (and bootstrapping it), and then erased + std::vector m_dht_router_nodes; + + // if a DHT node is added when there's no DHT instance, they're stored + // here until we start the DHT + std::vector m_dht_nodes; + + // this announce timer is used + // by the DHT. + deadline_timer m_dht_announce_timer; + + // the number of torrents there were when the + // update_dht_announce_interval() was last called. + // if the number of torrents changes significantly + // compared to this number, the DHT announce interval + // is updated again. This especially matters for + // small numbers. + int m_dht_interval_update_torrents = 0; + + // the number of DHT router lookups there are currently outstanding. As + // long as this is > 0, we'll postpone starting the DHT + int m_outstanding_router_lookups = 0; +#endif + + void send_udp_packet_hostname(std::weak_ptr sock + , char const* hostname + , int port + , span p + , error_code& ec + , udp_send_flags_t flags); + + void send_udp_packet_hostname_listen(aux::listen_socket_handle const& sock + , char const* hostname + , int port + , span p + , error_code& ec + , udp_send_flags_t const flags) + { + listen_socket_t* s = sock.get(); + if (!s) + { + ec = boost::asio::error::bad_descriptor; + return; + } + send_udp_packet_hostname(sock.get_ptr(), hostname, port, p, ec, flags); + } + + void send_udp_packet(std::weak_ptr sock + , udp::endpoint const& ep + , span p + , error_code& ec + , udp_send_flags_t flags); + + void send_udp_packet_listen(aux::listen_socket_handle const& sock + , udp::endpoint const& ep + , span p + , error_code& ec + , udp_send_flags_t const flags) + { + listen_socket_t* s = sock.get(); + if (!s) + { + ec = boost::asio::error::bad_descriptor; + return; + } + send_udp_packet(sock.get_ptr(), ep, p, ec, flags); + } + + void on_udp_writeable(std::weak_ptr s, error_code const& ec); + + void on_udp_packet(std::weak_ptr s + , std::weak_ptr ls + , transport ssl, error_code const& ec); + + libtorrent::utp_socket_manager m_utp_socket_manager; + +#ifdef TORRENT_USE_OPENSSL + // used for uTP connections over SSL + libtorrent::utp_socket_manager m_ssl_utp_socket_manager; +#endif + + // the number of torrent connection boosts + // connections that have been made this second + // this is deducted from the connect speed + int m_boost_connections = 0; + +#if TORRENT_ABI_VERSION == 1 + struct work_thread_t + { + work_thread_t() + : work(new boost::asio::io_service::work(ios)) + , thread([this] { ios.run(); }) + {} + ~work_thread_t() + { + work.reset(); + thread.join(); + } + work_thread_t(work_thread_t const&) = delete; + work_thread_t& operator=(work_thread_t const&) = delete; + + boost::asio::io_service ios; + std::unique_ptr work; + std::thread thread; + }; + std::unique_ptr m_torrent_load_thread; +#endif + + // mask is a bitmask of which protocols to remap on: + enum remap_port_mask_t + { + remap_natpmp = 1, + remap_upnp = 2, + remap_natpmp_and_upnp = 3 + }; + void remap_ports(remap_port_mask_t mask, listen_socket_t& s); + + // the timer used to fire the tick + deadline_timer m_timer; + aux::handler_storage m_tick_handler_storage; + + // abort may not fail and cannot allocate memory +#if defined BOOST_ASIO_ENABLE_HANDLER_TRACKING + aux::handler_storage<100> m_abort_handler_storage; +#elif defined _M_AMD64 + aux::handler_storage<88> m_abort_handler_storage; +#else + aux::handler_storage<56> m_abort_handler_storage; +#endif + + // torrents are announced on the local network in a + // round-robin fashion. All torrents are cycled through + // within the LSD announce interval (which defaults to + // 5 minutes) + torrent_map::iterator m_next_lsd_torrent; + +#ifndef TORRENT_DISABLE_DHT + // torrents are announced on the DHT in a + // round-robin fashion. All torrents are cycled through + // within the DHT announce interval (which defaults to + // 15 minutes) + torrent_map::iterator m_next_dht_torrent; + + // torrents that don't have any peers + // when added should be announced to the DHT + // as soon as possible. Such torrents are put + // in this queue and get announced the next time + // the timer fires, instead of the next one in + // the round-robin sequence. + std::deque> m_dht_torrents; +#endif + + // torrents prioritized to get connection attempts + std::deque, int>> m_prio_torrents; + + // this announce timer is used + // by Local service discovery + deadline_timer m_lsd_announce_timer; + + // this is the timer used to call ``close_oldest`` on the ``file_pool`` + // object. This closes the file that's been opened the longest every + // time it's called, to force the windows disk cache to be flushed + deadline_timer m_close_file_timer; + + // the index of the torrent that will be offered to + // connect to a peer next time on_tick is called. + // This implements a round robin peer connections among + // torrents that want more peers. The index is into + // m_torrent_lists[torrent_want_peers_downloading] + // (which is a list of torrent pointers with all + // torrents that want peers and are downloading) + int m_next_downloading_connect_torrent = 0; + int m_next_finished_connect_torrent = 0; + + // this is the number of attempts of connecting to + // peers we have given to downloading torrents. + // when this gets high enough, we try to connect + // a peer from a finished torrent + int m_download_connect_attempts = 0; + + // index into m_torrent_lists[torrent_want_scrape] referring + // to the next torrent to auto-scrape + int m_next_scrape_torrent = 0; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + counters& stats_counters() override { return m_stats_counters; } + + void received_buffer(int size) override; + void sent_buffer(int size) override; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const override; + void session_log(char const* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + // this is a list to allow extensions to potentially remove themselves. + std::array>, 4> m_ses_extensions; +#endif + +#if TORRENT_ABI_VERSION == 1 + user_load_function_t m_user_load_torrent; +#endif + + // this is true whenever we have posted a deferred-disk job + // it means we don't need to post another one + bool m_deferred_submit_disk_jobs = false; + + // this is set to true when a torrent auto-manage + // event is triggered, and reset whenever the message + // is delivered and the auto-manage is executed. + // there should never be more than a single pending auto-manage + // message in-flight at any given time. + bool m_pending_auto_manage = false; + + // this is also set to true when triggering an auto-manage + // of the torrents. However, if the normal auto-manage + // timer comes along and executes the auto-management, + // this is set to false, which means the triggered event + // no longer needs to execute the auto-management. + bool m_need_auto_manage = false; + + // set to true when the session object + // is being destructed and the thread + // should exit + bool m_abort = false; + + // is true if the session is paused + bool m_paused = false; + + // set to true the first time post_session_stats() is + // called and we post the headers alert + bool m_posted_stats_header = false; + }; + +#ifndef TORRENT_DISABLE_LOGGING + struct tracker_logger : request_callback + { + explicit tracker_logger(session_interface& ses); + void tracker_warning(tracker_request const& req + , std::string const& str) override; + void tracker_response(tracker_request const& + , libtorrent::address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, const std::string& str + , seconds32 retry_interval) override; + bool should_log() const override; + void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); + session_interface& m_ses; + private: + // explicitly disallow assignment, to silence msvc warning + tracker_logger& operator=(tracker_logger const&); + }; +#endif + + } +} + +#endif diff --git a/include/libtorrent/aux_/session_interface.hpp b/include/libtorrent/aux_/session_interface.hpp new file mode 100644 index 0000000..9d8a291 --- /dev/null +++ b/include/libtorrent/aux_/session_interface.hpp @@ -0,0 +1,328 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_INTERFACE_HPP_INCLUDED +#define TORRENT_SESSION_INTERFACE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/session_udp_sockets.hpp" // for transport +#include "libtorrent/session_types.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/link.hpp" // for torrent_list_index_t + +#include +#include + +#ifdef TORRENT_USE_OPENSSL +// there is no forward declaration header for asio +namespace boost { +namespace asio { +namespace ssl { + class context; +} +} +} +#endif + +namespace libtorrent { + + class peer_connection; + class torrent; + struct peer_class_set; + struct bandwidth_channel; + struct bandwidth_manager; + struct peer_class_pool; + struct disk_observer; + struct torrent_peer; + class alert_manager; + struct disk_interface; + struct tracker_request; + struct request_callback; + struct utp_socket_manager; + struct external_ip; + struct torrent_peer_allocator_interface; + struct counters; + struct resolver_interface; + + // hidden + using queue_position_t = aux::strong_typedef; + + constexpr queue_position_t no_pos{-1}; + constexpr queue_position_t last_pos{(std::numeric_limits::max)()}; + +#ifndef TORRENT_DISABLE_DHT +namespace dht { + + struct dht_tracker; + } +#endif +} + +namespace libtorrent { +namespace aux { + + struct proxy_settings; + struct session_settings; + struct socket_type; + + using ip_source_t = flags::bitfield_flag; + +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + // This is the basic logging and debug interface offered by the session. + // a release build with logging disabled (which is the default) will + // not have this class at all + struct TORRENT_EXTRA_EXPORT session_logger + { +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log() const = 0; + virtual void session_log(char const* fmt, ...) const TORRENT_FORMAT(2,3) = 0; +#endif + +#if TORRENT_USE_ASSERTS + virtual bool is_single_thread() const = 0; + virtual bool has_peer(peer_connection const* p) const = 0; + virtual bool any_torrent_has_peer(peer_connection const* p) const = 0; + virtual bool is_posting_torrent_updates() const = 0; +#endif + protected: + ~session_logger() {} + }; +#endif // TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + + // TODO: 2 make this interface a lot smaller. It could be split up into + // several smaller interfaces. Each subsystem could then limit the size + // of the mock object to test it. + struct TORRENT_EXTRA_EXPORT session_interface +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + : session_logger +#endif + { + + // TODO: 2 the IP voting mechanism should be factored out + // to its own class, not part of the session + // and these constants should move too + + // the logic in ip_voter relies on more reliable sources are represented + // by more significant bits + static constexpr ip_source_t source_dht = 1_bit; + static constexpr ip_source_t source_peer = 2_bit; + static constexpr ip_source_t source_tracker = 3_bit; + static constexpr ip_source_t source_router = 4_bit; + + virtual void set_external_address(tcp::endpoint const& local_endpoint + , address const& ip + , ip_source_t source_type, address const& source) = 0; + virtual external_ip external_address() const = 0; + + virtual disk_interface& disk_thread() = 0; + + virtual alert_manager& alerts() = 0; + + virtual torrent_peer_allocator_interface& get_peer_allocator() = 0; + virtual io_service& get_io_service() = 0; + virtual resolver_interface& get_resolver() = 0; + + virtual bool has_connection(peer_connection* p) const = 0; + virtual void insert_peer(std::shared_ptr const& c) = 0; + + virtual void remove_torrent(torrent_handle const& h, remove_flags_t options = {}) = 0; + virtual void remove_torrent_impl(std::shared_ptr tptr, remove_flags_t options) = 0; + + // port filter + virtual port_filter const& get_port_filter() const = 0; + virtual void ban_ip(address addr) = 0; + + virtual std::uint16_t session_time() const = 0; + virtual time_point session_start_time() const = 0; + + virtual bool is_aborted() const = 0; + virtual int num_uploads() const = 0; + virtual bool preemptive_unchoke() const = 0; + virtual void trigger_optimistic_unchoke() noexcept = 0; + virtual void trigger_unchoke() noexcept = 0; + + virtual std::weak_ptr find_torrent(sha1_hash const& info_hash) const = 0; + virtual std::weak_ptr find_disconnect_candidate_torrent() const = 0; + virtual std::shared_ptr delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) = 0; + virtual void insert_torrent(sha1_hash const& ih, std::shared_ptr const& t +#if TORRENT_ABI_VERSION == 1 + , std::string uuid +#endif + ) = 0; +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + virtual void insert_uuid_torrent(std::string uuid, std::shared_ptr const& t) = 0; +#endif + virtual void set_queue_position(torrent* t, queue_position_t p) = 0; + virtual int num_torrents() const = 0; + + virtual void close_connection(peer_connection* p) noexcept = 0; + virtual int num_connections() const = 0; + + virtual void deferred_submit_jobs() = 0; + + virtual std::uint16_t listen_port() const = 0; + virtual std::uint16_t ssl_listen_port() const = 0; + + virtual int listen_port(aux::transport ssl, address const& local_addr) = 0; + + virtual void for_each_listen_socket(std::function f) = 0; + + // ask for which interface and port to bind outgoing peer connections on + virtual tcp::endpoint bind_outgoing_socket(socket_type& s, address const& + remote_address, error_code& ec) const = 0; + virtual bool verify_bound_address(address const& addr, bool utp + , error_code& ec) = 0; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + virtual std::vector> find_collection( + std::string const& collection) const = 0; +#endif + + // TODO: it would be nice to not have this be part of session_interface + virtual proxy_settings proxy() const = 0; + +#if TORRENT_USE_I2P + virtual proxy_settings i2p_proxy() const = 0; + virtual char const* i2p_session() const = 0; +#endif + + virtual void prioritize_connections(std::weak_ptr t) = 0; + + virtual void trigger_auto_manage() = 0; + + virtual void apply_settings_pack(std::shared_ptr pack) = 0; + virtual session_settings const& settings() const = 0; + + // the tracker request object must be moved in + virtual void queue_tracker_request(tracker_request&& req + , std::weak_ptr c) = 0; + void queue_tracker_request(tracker_request const& req + , std::weak_ptr c) = delete; + + // peer-classes + virtual void set_peer_classes(peer_class_set* s, address const& a, int st) = 0; + virtual peer_class_pool const& peer_classes() const = 0; + virtual peer_class_pool& peer_classes() = 0; + virtual bool ignore_unchoke_slots_set(peer_class_set const& set) const = 0; + virtual int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int m) = 0; + virtual int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) = 0; + + virtual bandwidth_manager* get_bandwidth_manager(int channel) = 0; + + virtual void sent_bytes(int bytes_payload, int bytes_protocol) = 0; + virtual void received_bytes(int bytes_payload, int bytes_protocol) = 0; + virtual void trancieve_ip_packet(int bytes, bool ipv6) = 0; + virtual void sent_syn(bool ipv6) = 0; + virtual void received_synack(bool ipv6) = 0; + + // this is the set of (subscribed) torrents that have changed + // their states since the last time the user requested updates. + static constexpr torrent_list_index_t torrent_state_updates{0}; + + // all torrents that want to be ticked every second + static constexpr torrent_list_index_t torrent_want_tick{1}; + + // all torrents that want more peers and are still downloading + // these typically have higher priority when connecting peers + static constexpr torrent_list_index_t torrent_want_peers_download{2}; + + // all torrents that want more peers and are finished downloading + static constexpr torrent_list_index_t torrent_want_peers_finished{3}; + + // torrents that want auto-scrape (only paused auto-managed ones) + static constexpr torrent_list_index_t torrent_want_scrape{4}; + + // auto-managed torrents by state. Only these torrents are considered + // when recalculating auto-managed torrents. started auto managed + // torrents that are inactive are not part of these lists, because they + // are not considered for auto managing (they are left started + // unconditionally) + static constexpr torrent_list_index_t torrent_downloading_auto_managed{5}; + static constexpr torrent_list_index_t torrent_seeding_auto_managed{6}; + static constexpr torrent_list_index_t torrent_checking_auto_managed{7}; + + static constexpr std::size_t num_torrent_lists = 8; + + virtual aux::vector& torrent_list(torrent_list_index_t i) = 0; + + virtual bool has_lsd() const = 0; + virtual void announce_lsd(sha1_hash const& ih, int port) = 0; + virtual libtorrent::utp_socket_manager* utp_socket_manager() = 0; + virtual void inc_boost_connections() = 0; + virtual std::vector& block_info_storage() = 0; + +#ifdef TORRENT_USE_OPENSSL + virtual libtorrent::utp_socket_manager* ssl_utp_socket_manager() = 0; + virtual boost::asio::ssl::context* ssl_ctx() = 0 ; +#endif + +#if !defined TORRENT_DISABLE_ENCRYPTION + virtual torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) = 0; + virtual void add_obfuscated_hash(sha1_hash const& obfuscated + , std::weak_ptr const& t) = 0; +#endif + +#ifndef TORRENT_DISABLE_DHT + virtual bool announce_dht() const = 0; + virtual void add_dht_node(udp::endpoint const& n) = 0; + virtual bool has_dht() const = 0; + virtual int external_udp_port(address const& local_address) const = 0; + virtual dht::dht_tracker* dht() = 0; + virtual void prioritize_dht(std::weak_ptr t) = 0; +#endif + + virtual counters& stats_counters() = 0; + virtual void received_buffer(int size) = 0; + virtual void sent_buffer(int size) = 0; + +#if TORRENT_USE_ASSERTS + virtual bool verify_queue_position(torrent const*, queue_position_t) = 0; +#endif + + virtual ~session_interface() {} + }; +}} + +#endif diff --git a/include/libtorrent/aux_/session_settings.hpp b/include/libtorrent/aux_/session_settings.hpp new file mode 100644 index 0000000..4345480 --- /dev/null +++ b/include/libtorrent/aux_/session_settings.hpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/assert.hpp" + +#include +#include +#include +#include +#include + +namespace libtorrent { +namespace aux { + + struct TORRENT_EXTRA_EXPORT session_settings_single_thread + { + void set_str(int name, std::string value) + { set(m_strings, name, std::move(value), settings_pack::string_type_base); } + void set_int(int name, int value) + { set(m_ints, name, value, settings_pack::int_type_base); } + void set_bool(int name, bool value) + { set(m_bools, name, value, settings_pack::bool_type_base); } + + std::string const& get_str(int name) const + { return get(m_strings, name, settings_pack::string_type_base); } + int get_int(int name) const + { return get(m_ints, name, settings_pack::int_type_base); } + bool get_bool(int name) const + { return get(m_bools, name, settings_pack::bool_type_base); } + + session_settings_single_thread(); + + private: + + template + void set(Container& c, int const name, T val + , int const type) + { + TORRENT_ASSERT((name & settings_pack::type_mask) == type); + if ((name & settings_pack::type_mask) != type) return; + size_t const index = name & settings_pack::index_mask; + TORRENT_ASSERT(index < c.size()); + c[index] = std::move(val); + } + + template + T get(Container const& c, int const name, int const type) const + { + static typename std::remove_reference::type empty; + TORRENT_ASSERT((name & settings_pack::type_mask) == type); + if ((name & settings_pack::type_mask) != type) return empty; + size_t const index = name & settings_pack::index_mask; + TORRENT_ASSERT(index < c.size()); + return c[index]; + } + + std::array m_strings; + std::array m_ints; + std::bitset m_bools; + }; + + struct TORRENT_EXTRA_EXPORT session_settings + { + void set_str(int name, std::string value) + { + std::unique_lock l(m_mutex); + return m_store.set_str(name, std::move(value)); + } + void set_int(int name, int value) + { + std::unique_lock l(m_mutex); + m_store.set_int(name, value); + } + void set_bool(int name, bool value) + { + std::unique_lock l(m_mutex); + m_store.set_bool(name, value); + } + + std::string const& get_str(int name) const + { + std::unique_lock l(m_mutex); + return m_store.get_str(name); + } + int get_int(int name) const + { + std::unique_lock l(m_mutex); + return m_store.get_int(name); + } + bool get_bool(int name) const + { + std::unique_lock l(m_mutex); + return m_store.get_bool(name); + } + + session_settings(); + explicit session_settings(settings_pack const&); + + void bulk_set(std::function); + void bulk_get(std::function) const; + + private: + + session_settings_single_thread m_store; + mutable std::mutex m_mutex; + }; + +} +} + +namespace libtorrent { + TORRENT_EXTRA_EXPORT void initialize_default_settings(aux::session_settings_single_thread& s); +} + +#endif diff --git a/include/libtorrent/aux_/session_udp_sockets.hpp b/include/libtorrent/aux_/session_udp_sockets.hpp new file mode 100644 index 0000000..5a8c7a9 --- /dev/null +++ b/include/libtorrent/aux_/session_udp_sockets.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_UDP_SOCKETS_HPP_INCLUDED +#define TORRENT_SESSION_UDP_SOCKETS_HPP_INCLUDED + +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include +#include + +namespace libtorrent { + + class alert_manager; + +namespace aux { + + struct listen_endpoint_t; + struct proxy_settings; + struct listen_socket_t; + + enum class transport : std::uint8_t { plaintext, ssl }; + + struct session_udp_socket + { + explicit session_udp_socket(io_service& ios, listen_socket_handle ls) + : sock(ios, std::move(ls)) {} + + udp::endpoint local_endpoint() { return sock.local_endpoint(); } + + udp_socket sock; + + // since udp packets are expected to be dispatched frequently, this saves + // time on handler allocation every time we read again. + aux::handler_storage udp_handler_storage; + + // this is true when the udp socket send() has failed with EAGAIN or + // EWOULDBLOCK. i.e. we're currently waiting for the socket to become + // writeable again. Once it is, we'll set it to false and notify the utp + // socket manager + bool write_blocked = false; + }; + +} } + +#endif diff --git a/include/libtorrent/aux_/set_socket_buffer.hpp b/include/libtorrent/aux_/set_socket_buffer.hpp new file mode 100644 index 0000000..06ac035 --- /dev/null +++ b/include/libtorrent/aux_/set_socket_buffer.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2018, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SET_SOCKET_BUFFER_HPP +#define TORRENT_SET_SOCKET_BUFFER_HPP + +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { +namespace aux { + + template + void set_socket_buffer_size(Socket& s, session_settings const& sett, error_code& ec) + { +#ifdef TCP_NOTSENT_LOWAT + int const not_sent_low_watermark = sett.get_int(settings_pack::send_not_sent_low_watermark); + if (not_sent_low_watermark) + { + error_code ignore; + s.set_option(tcp_notsent_lowat(not_sent_low_watermark), ignore); + } +#endif + int const snd_size = sett.get_int(settings_pack::send_socket_buffer_size); + if (snd_size) + { + typename Socket::send_buffer_size prev_option; + s.get_option(prev_option, ec); + if (!ec && prev_option.value() != snd_size) + { + typename Socket::send_buffer_size option(snd_size); + s.set_option(option, ec); + if (ec) + { + // restore previous value + s.set_option(prev_option, ec); + return; + } + } + } + int const recv_size = sett.get_int(settings_pack::recv_socket_buffer_size); + if (recv_size) + { + typename Socket::receive_buffer_size prev_option; + s.get_option(prev_option, ec); + if (!ec && prev_option.value() != recv_size) + { + typename Socket::receive_buffer_size option(recv_size); + s.set_option(option, ec); + if (ec) + { + // restore previous value + s.set_option(prev_option, ec); + return; + } + } + } + } + +}} + +#endif diff --git a/include/libtorrent/aux_/socket_type.hpp b/include/libtorrent/aux_/socket_type.hpp new file mode 100644 index 0000000..7cbe90f --- /dev/null +++ b/include/libtorrent/aux_/socket_type.hpp @@ -0,0 +1,352 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_TYPE +#define TORRENT_SOCKET_TYPE + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/aligned_union.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/http_stream.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/utp_stream.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/assert.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#endif + +#include "libtorrent/debug.hpp" + +#if defined TORRENT_OS2 && defined ioc +#undef ioc +#endif + +#if TORRENT_USE_I2P + +#define TORRENT_SOCKTYPE_I2P_FORWARD(x) \ + case socket_type_int_impl::value: \ + get()->x; break; + +#define TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) \ + case socket_type_int_impl::value: \ + return get()->x; + +#else // TORRENT_USE_I2P + +#define TORRENT_SOCKTYPE_I2P_FORWARD(x) +#define TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) + +#endif + +#ifdef TORRENT_USE_OPENSSL + +#define TORRENT_SOCKTYPE_SSL_FORWARD(x) \ + case socket_type_int_impl>::value: \ + get>()->x; break; \ + case socket_type_int_impl>::value: \ + get>()->x; break; \ + case socket_type_int_impl>::value: \ + get>()->x; break; \ + case socket_type_int_impl>::value: \ + get>()->x; break; + +#define TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) \ + case socket_type_int_impl>::value: \ + return get>()->x; \ + case socket_type_int_impl>::value: \ + return get>()->x; \ + case socket_type_int_impl>::value: \ + return get>()->x; \ + case socket_type_int_impl>::value: \ + return get>()->x; + +#else + +#define TORRENT_SOCKTYPE_SSL_FORWARD(x) +#define TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) + +#endif + +#define TORRENT_SOCKTYPE_FORWARD(x) \ + switch (m_type) { \ + case socket_type_int_impl::value: \ + get()->x; break; \ + case socket_type_int_impl::value: \ + get()->x; break; \ + case socket_type_int_impl::value: \ + get()->x; break; \ + case socket_type_int_impl::value: \ + get()->x; break; \ + TORRENT_SOCKTYPE_I2P_FORWARD(x) \ + TORRENT_SOCKTYPE_SSL_FORWARD(x) \ + default: TORRENT_ASSERT_FAIL(); \ + } + +#define TORRENT_SOCKTYPE_FORWARD_RET(x, def) \ + switch (m_type) { \ + case socket_type_int_impl::value: \ + return get()->x; \ + case socket_type_int_impl::value: \ + return get()->x; \ + case socket_type_int_impl::value: \ + return get()->x; \ + case socket_type_int_impl::value: \ + return get()->x; \ + TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) \ + TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) \ + default: TORRENT_ASSERT_FAIL(); return def; \ + } + +namespace libtorrent { +namespace aux { + + template + struct socket_type_int_impl + { static constexpr int value = 0; }; + + template <> + struct socket_type_int_impl + { static constexpr int value = 1; }; + + template <> + struct socket_type_int_impl + { static constexpr int value = 2; }; + + template <> + struct socket_type_int_impl + { static constexpr int value = 3; }; + + template <> + struct socket_type_int_impl + { static constexpr int value = 4; }; + +#if TORRENT_USE_I2P + template <> + struct socket_type_int_impl + { static constexpr int value = 5; }; +#endif + +#ifdef TORRENT_USE_OPENSSL + template <> + struct socket_type_int_impl> + { static constexpr int value = 6; }; + + template <> + struct socket_type_int_impl> + { static constexpr int value = 7; }; + + template <> + struct socket_type_int_impl> + { static constexpr int value = 8; }; + + template <> + struct socket_type_int_impl> + { static constexpr int value = 9; }; +#endif + + struct TORRENT_EXTRA_EXPORT socket_type + { + using endpoint_type = tcp::socket::endpoint_type; + using protocol_type = tcp::socket::protocol_type; + + using receive_buffer_size = tcp::socket::receive_buffer_size; + using send_buffer_size = tcp::socket::send_buffer_size; + +#if BOOST_VERSION >= 106600 + using executor_type = tcp::socket::executor_type; +#endif + + explicit socket_type(io_service& ios): m_io_service(ios), m_type(0) {} + ~socket_type(); + + io_service& get_io_service() const; + bool is_open() const; + + char const* type_name() const; + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p); + void close(); + endpoint_type local_endpoint() const; + endpoint_type remote_endpoint() const; + void bind(endpoint_type const& endpoint); + std::size_t available() const; +#endif + + void open(protocol_type const& p, error_code& ec); + void close(error_code& ec); + + // this is only relevant for uTP connections + void set_close_reason(close_reason_t code); + close_reason_t get_close_reason(); + + endpoint_type local_endpoint(error_code& ec) const; + endpoint_type remote_endpoint(error_code& ec) const; + void bind(endpoint_type const& endpoint, error_code& ec); + std::size_t available(error_code& ec) const; + int type() const; + + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(read_some(buffers, ec), 0) } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { TORRENT_SOCKTYPE_FORWARD(async_read_some(buffers, handler)) } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(write_some(buffers, ec), 0) } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { TORRENT_SOCKTYPE_FORWARD(async_write_some(buffers, handler)) } + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { TORRENT_SOCKTYPE_FORWARD(async_connect(endpoint, handler)) } + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command& ioc) + { TORRENT_SOCKTYPE_FORWARD(io_control(ioc)) } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { TORRENT_SOCKTYPE_FORWARD_RET(read_some(buffers), 0) } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(io_control(ioc, ec)) } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { TORRENT_SOCKTYPE_FORWARD(set_option(opt)) } +#endif + + template + error_code set_option(SettableSocketOption const& opt, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(set_option(opt, ec), ec) } + + void non_blocking(bool b, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(non_blocking(b, ec)) } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { TORRENT_SOCKTYPE_FORWARD(non_blocking(b)) } +#endif + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { TORRENT_SOCKTYPE_FORWARD(get_option(opt)) } +#endif + + template + error_code get_option(GettableSocketOption& opt, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD_RET(get_option(opt, ec), ec) } + + template + void instantiate(io_service& ios, void* userdata = nullptr) + { + TORRENT_UNUSED(ios); + TORRENT_ASSERT(&ios == &m_io_service); + construct(socket_type_int_impl::value, userdata); + } + + template S* get() + { + if (m_type != socket_type_int_impl::value) return nullptr; + return reinterpret_cast(&m_data); + } + + template S const* get() const + { + if (m_type != socket_type_int_impl::value) return nullptr; + return reinterpret_cast(&m_data); + } + + // explicitly disallow assignment, to silence msvc warning + socket_type& operator=(socket_type const&) = delete; + + private: + + void destruct(); + void construct(int type, void* userdata); + + io_service& m_io_service; + int m_type; + + aux::aligned_union<1 + , tcp::socket + , socks5_stream + , http_stream + , utp_stream +#if TORRENT_USE_I2P + , i2p_stream +#endif +#ifdef TORRENT_USE_OPENSSL + , ssl_stream + , ssl_stream + , ssl_stream + , ssl_stream +#endif + >::type m_data; + }; + + // returns true if this socket is an SSL socket + bool is_ssl(socket_type const& s); + + // returns true if this is a uTP socket + bool is_utp(socket_type const& s); + +#if TORRENT_USE_I2P + // returns true if this is an i2p socket + bool is_i2p(socket_type const& s); +#endif + + // assuming the socket_type s is an ssl socket, make sure it + // verifies the hostname in its SSL handshake + void setup_ssl_hostname(socket_type& s, std::string const& hostname, error_code& ec); + + // properly shuts down SSL sockets. holder keeps s alive + void async_shutdown(socket_type& s, std::shared_ptr holder); +} +} + +#endif diff --git a/include/libtorrent/aux_/storage_piece_set.hpp b/include/libtorrent/aux_/storage_piece_set.hpp new file mode 100644 index 0000000..ad05848 --- /dev/null +++ b/include/libtorrent/aux_/storage_piece_set.hpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_PIECE_SET_HPP_INCLUDE +#define TORRENT_STORAGE_PIECE_SET_HPP_INCLUDE + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/block_cache.hpp" // for cached_piece_entry + +namespace libtorrent { + +struct cached_piece_entry; + +namespace aux { + + // this class keeps track of which pieces, belonging to + // a specific storage, are in the cache right now. It's + // used for quickly being able to evict all pieces for a + // specific torrent + struct TORRENT_EXPORT storage_piece_set + { + using list_t = boost::intrusive::list>; + void add_piece(cached_piece_entry* p); + void remove_piece(cached_piece_entry* p); + int num_pieces() const { return m_num_pieces; } + list_t const& cached_pieces() const + { return m_cached_pieces; } + private: + // these are cached pieces belonging to this storage + list_t m_cached_pieces; + int m_num_pieces = 0; + }; +}} + +#endif diff --git a/include/libtorrent/aux_/storage_utils.hpp b/include/libtorrent/aux_/storage_utils.hpp new file mode 100644 index 0000000..555aa44 --- /dev/null +++ b/include/libtorrent/aux_/storage_utils.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_UTILS_HPP_INCLUDE +#define TORRENT_STORAGE_UTILS_HPP_INCLUDE + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/storage_defs.hpp" // for status_t +#include "libtorrent/session_types.hpp" + +namespace libtorrent { + + struct part_file; + struct stat_cache; + + // TODO: 3 remove this typedef, and use span for disk write + // operations + using iovec_t = span; + +namespace aux { + + TORRENT_EXTRA_EXPORT int copy_bufs(span bufs + , int bytes, span target); + TORRENT_EXTRA_EXPORT span advance_bufs(span bufs, int bytes); + TORRENT_EXTRA_EXPORT void clear_bufs(span bufs); + + // this is a read or write operation so that readwritev() knows + // what to do when it's actually touching the file + using fileop = std::function, storage_error&)>; + + // this function is responsible for turning read and write operations in the + // torrent space (pieces) into read and write operations in the filesystem + // space (files on disk). + TORRENT_EXTRA_EXPORT int readwritev(file_storage const& files + , span bufs, piece_index_t piece, int offset + , storage_error& ec, fileop op); + + // moves the files in file_storage f from ``save_path`` to + // ``destination_save_path`` according to the rules defined by ``flags``. + // returns the status code and the new save_path. + TORRENT_EXTRA_EXPORT std::pair + move_storage(file_storage const& f + , std::string const& save_path + , std::string const& destination_save_path + , part_file* pf + , move_flags_t flags, storage_error& ec); + + // deletes the files on fs from save_path according to options. Options may + // opt to only delete the partfile + TORRENT_EXTRA_EXPORT void + delete_files(file_storage const& fs, std::string const& save_path + , std::string const& part_file_name, remove_flags_t options, storage_error& ec); + + TORRENT_EXTRA_EXPORT bool + verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , file_storage const& fs + , aux::vector const& file_priority + , stat_cache& stat + , std::string const& save_path + , storage_error& ec); + + // given the save_path, stat all files on file_storage until one exists. If a + // file exists, return true, otherwise return false. + TORRENT_EXTRA_EXPORT bool has_any_file( + file_storage const& fs + , std::string const& save_path + , stat_cache& stat + , storage_error& ec); +}} + +#endif diff --git a/include/libtorrent/aux_/string_ptr.hpp b/include/libtorrent/aux_/string_ptr.hpp new file mode 100644 index 0000000..10da30c --- /dev/null +++ b/include/libtorrent/aux_/string_ptr.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_PTR_HPP_INCLUDED +#define TORRENT_STRING_PTR_HPP_INCLUDED + +#include "libtorrent/string_view.hpp" + +namespace libtorrent { +namespace aux { + + struct string_ptr + { + explicit string_ptr(string_view str) : m_ptr(new char[str.size() + 1]) + { + std::copy(str.begin(), str.end(), m_ptr); + m_ptr[str.size()] = '\0'; + } + ~string_ptr() + { + delete[] m_ptr; + } + string_ptr(string_ptr&& rhs) + : m_ptr(rhs.m_ptr) + { + rhs.m_ptr = nullptr; + } + string_ptr& operator=(string_ptr&& rhs) + { + if (&rhs == this) return *this; + delete[] m_ptr; + m_ptr = rhs.m_ptr; + rhs.m_ptr = nullptr; + return *this; + } + string_ptr(string_ptr const& rhs) = delete; + string_ptr& operator=(string_ptr const& rhs) = delete; + char const* operator*() const { return m_ptr; } + private: + char* m_ptr; + }; + +} +} + +#endif + diff --git a/include/libtorrent/aux_/suggest_piece.hpp b/include/libtorrent/aux_/suggest_piece.hpp new file mode 100644 index 0000000..87949e1 --- /dev/null +++ b/include/libtorrent/aux_/suggest_piece.hpp @@ -0,0 +1,127 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SUGGEST_PIECE_HPP_INCLUDE +#define TORRENT_SUGGEST_PIECE_HPP_INCLUDE + +#include +#include + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { namespace aux { + +struct suggest_piece +{ + // pick at most n piece indices that are _not_ in p (which represents + // pieces the peer has already sent a suggest for) nor in bits (which are + // pieces the peer already has, and should not be suggested) + int get_pieces(std::vector& p + , typed_bitfield const& bits + , int n) + { + if (m_priority_pieces.empty()) return 0; + + int ret = 0; + + // the highest priority pieces are at the end of m_priority_pieces. + // if we add any piece to the result (p), the farther back the better. + // the prioritization in p is the same, which means we have to first push + // back and then reverse the items we put there. + for (int i = int(m_priority_pieces.size()) - 1; i >= 0; --i) + { + piece_index_t const piece = m_priority_pieces[i]; + if (bits.get_bit(piece)) continue; + if (std::any_of(p.begin(), p.end() - ret + , [piece](piece_index_t pi) { return pi == piece; })) + continue; + + p.push_back(piece); + ++ret; + --n; + if (n == 0) break; + } + + // this it to maintain a strict priority order of pieces. The farther + // back, the higher priority + std::reverse(p.end() - ret, p.end()); + + return ret; + } + + void add_piece(piece_index_t const index, int const availability + , int const max_queue_size) + { + // keep a running average of the availability of pieces, and filter + // anything above average. + int const mean = m_availability.mean(); + m_availability.add_sample(availability); + + if (availability > mean) return; + + auto const it = std::find(m_priority_pieces.begin() + , m_priority_pieces.end(), index); + + if (it != m_priority_pieces.end()) + { + // increase the priority of this piece by moving it to the front + // of the queue + m_priority_pieces.erase(it); + } + + if (int(m_priority_pieces.size()) >= max_queue_size) + { + int const to_remove = int(m_priority_pieces.size()) - max_queue_size + 1; + m_priority_pieces.erase(m_priority_pieces.begin() + , m_priority_pieces.begin() + to_remove); + } + + m_priority_pieces.push_back(index); + } + +private: + + // these are pieces that would be good candidates for suggesting + // to a peer. They represent low availability pieces that we recently + // read from disk (and are likely in our read cache). + // pieces closer to the end were inserted into the cache more recently and + // have higher priority + vector m_priority_pieces; + + sliding_average m_availability; +}; + +}} + +#endif diff --git a/include/libtorrent/aux_/throw.hpp b/include/libtorrent/aux_/throw.hpp new file mode 100644 index 0000000..aa91f12 --- /dev/null +++ b/include/libtorrent/aux_/throw.hpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2017-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_THROW_HPP_INCLUDED +#define TORRENT_THROW_HPP_INCLUDED + +#include // for forward() + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + + template +#ifdef BOOST_NO_EXCEPTIONS + [[noreturn]] void throw_ex(Args&&...) { + std::terminate(); + } +#else + [[noreturn]] void throw_ex(Args&&... args) { + throw T(std::forward(args)...); + } +#endif +}} + +#endif diff --git a/include/libtorrent/aux_/time.hpp b/include/libtorrent/aux_/time.hpp new file mode 100644 index 0000000..ecc717f --- /dev/null +++ b/include/libtorrent/aux_/time.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_AUX_TIME_HPP +#define TORRENT_AUX_TIME_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { namespace aux { + + // returns the current time, as represented by time_point. The + // resolution of this timer is about 100 ms. + TORRENT_EXTRA_EXPORT time_point time_now(); + TORRENT_EXTRA_EXPORT time_point32 time_now32(); +} } + +#endif diff --git a/include/libtorrent/aux_/torrent_impl.hpp b/include/libtorrent/aux_/torrent_impl.hpp new file mode 100644 index 0000000..d7f99a3 --- /dev/null +++ b/include/libtorrent/aux_/torrent_impl.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_IMPL_HPP_INCLUDED +#define TORRENT_TORRENT_IMPL_HPP_INCLUDED + +// this is not a normal header, it's just this template, to be able to call this +// function from more than one translation unit. But it's still internal + +namespace libtorrent { + + template + void torrent::wrap(Fun f, Args&&... a) +#ifndef BOOST_NO_EXCEPTIONS + try +#endif + { + (this->*f)(std::forward(a)...); + } +#ifndef BOOST_NO_EXCEPTIONS + catch (system_error const& e) { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("EXCEPTION: (%d %s) %s" + , e.code().value() + , e.code().message().c_str() + , e.what()); +#endif + alerts().emplace_alert(get_handle() + , e.code(), e.what()); + pause(); + } catch (std::exception const& e) { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("EXCEPTION: %s", e.what()); +#endif + alerts().emplace_alert(get_handle() + , error_code(), e.what()); + pause(); + } catch (...) { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("EXCEPTION: unknown"); +#endif + alerts().emplace_alert(get_handle() + , error_code(), "unknown error"); + pause(); + } +#endif + +} + +#endif + diff --git a/include/libtorrent/aux_/unique_ptr.hpp b/include/libtorrent/aux_/unique_ptr.hpp new file mode 100644 index 0000000..6d76d2a --- /dev/null +++ b/include/libtorrent/aux_/unique_ptr.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNIQUE_PTR_HPP +#define TORRENT_UNIQUE_PTR_HPP + +#include +#include + +#include "libtorrent/units.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { namespace aux { + + template + struct unique_ptr; + + template + struct unique_ptr : std::unique_ptr + { + using base = std::unique_ptr; + using underlying_index = typename underlying_index_t::type; + + unique_ptr() = default; + explicit unique_ptr(T* arr) : base(arr) {} + + auto operator[](IndexType idx) const -> +#if TORRENT_AUTO_RETURN_TYPES + decltype(auto) +#else + decltype(this->base::operator[](underlying_index())) +#endif + { + TORRENT_ASSERT(idx >= IndexType(0)); + return this->base::operator[](std::size_t(static_cast(idx))); + } + }; + +}} + +#endif diff --git a/include/libtorrent/aux_/vector.hpp b/include/libtorrent/aux_/vector.hpp new file mode 100644 index 0000000..cf4e01b --- /dev/null +++ b/include/libtorrent/aux_/vector.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VECTOR_HPP +#define TORRENT_VECTOR_HPP + +#include + +#include "libtorrent/aux_/container_wrapper.hpp" + +namespace libtorrent { namespace aux { + + template + using vector = container_wrapper>; + +}} + +#endif diff --git a/include/libtorrent/aux_/win_crypto_provider.hpp b/include/libtorrent/aux_/win_crypto_provider.hpp new file mode 100644 index 0000000..8b98b43 --- /dev/null +++ b/include/libtorrent/aux_/win_crypto_provider.hpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WIN_CRYPTO_PROVIDER_HPP +#define TORRENT_WIN_CRYPTO_PROVIDER_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/windows.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { namespace aux { + + inline HCRYPTPROV crypt_acquire_provider(DWORD provider_type) + { + HCRYPTPROV ret; + if (CryptAcquireContext(&ret, nullptr, nullptr, provider_type + , CRYPT_VERIFYCONTEXT) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + return ret; + } + + inline void crypt_gen_random(span buffer) + { + static HCRYPTPROV provider = crypt_acquire_provider(PROV_RSA_FULL); + if (!CryptGenRandom(provider, int(buffer.size()) + , reinterpret_cast(buffer.data()))) + { + throw_ex(error_code(GetLastError(), system_category())); + } + } + + template + struct crypt_hash + { + crypt_hash() { m_hash = create(); } + crypt_hash(crypt_hash const& h) { m_hash = duplicate(h); } + ~crypt_hash() { CryptDestroyHash(m_hash); } + + crypt_hash& operator=(crypt_hash const& h) & + { + if (this == &h) return *this; + HCRYPTHASH temp = duplicate(h); + CryptDestroyHash(m_hash); + m_hash = temp; + return *this; + } + + void reset() + { + HCRYPTHASH temp = create(); + CryptDestroyHash(m_hash); + m_hash = temp; + } + + void update(span data) + { + if (CryptHashData(m_hash, reinterpret_cast(data.data()), int(data.size()), 0) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + } + + void get_hash(char *digest, std::size_t digest_size) + { + DWORD size = DWORD(digest_size); + if (CryptGetHashParam(m_hash, HP_HASHVAL + , reinterpret_cast(digest), &size, 0) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + TORRENT_ASSERT(size == DWORD(digest_size)); + } + private: + HCRYPTHASH create() + { + HCRYPTHASH ret; + if (CryptCreateHash(get_provider(), AlgId, 0, 0, &ret) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + return ret; + } + + HCRYPTHASH duplicate(crypt_hash const& h) + { + HCRYPTHASH ret; + if (CryptDuplicateHash(h.m_hash, 0, 0, &ret) == false) + { + throw_ex(error_code(GetLastError(), system_category())); + } + return ret; + } + + HCRYPTPROV get_provider() + { + static HCRYPTPROV provider = crypt_acquire_provider(ProviderType); + return provider; + } + + HCRYPTHASH m_hash; + }; + +} // namespace aux +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/win_util.hpp b/include/libtorrent/aux_/win_util.hpp new file mode 100644 index 0000000..9d93a62 --- /dev/null +++ b/include/libtorrent/aux_/win_util.hpp @@ -0,0 +1,84 @@ +/* + +Copyright (c) 2016-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WIN_UTIL_HPP +#define TORRENT_WIN_UTIL_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + + template + HMODULE get_library_handle() + { + static bool handle_checked = false; + static HMODULE handle = 0; + + if (!handle_checked) + { + handle = LoadLibraryA(Library::library_name); + handle_checked = true; + } + return handle; + } + + template + Signature get_library_procedure(LPCSTR name) + { + static Signature proc = nullptr; + static bool failed_proc = false; + + if ((proc == nullptr) && !failed_proc) + { + HMODULE const handle = get_library_handle(); + if (handle) proc = reinterpret_cast(reinterpret_cast(GetProcAddress(handle, name))); + failed_proc = (proc == nullptr); + } + return proc; + } + + struct iphlpapi { + static constexpr char const* library_name = "iphlpapi.dll"; + }; + + struct kernel32 { + static constexpr char const* library_name = "kernel32.dll"; + }; + + struct advapi32 { + static constexpr char const* library_name = "advapi32.dll"; + }; + +} // namespace aux +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/aux_/windows.hpp b/include/libtorrent/aux_/windows.hpp new file mode 100644 index 0000000..ce6fafd --- /dev/null +++ b/include/libtorrent/aux_/windows.hpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WINDOWS_HPP_INCLUDED +#define TORRENT_WINDOWS_HPP_INCLUDED + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN +#endif +#ifndef STRICT +#define STRICT +#endif +#include + +#endif // TORRENT_WINDOWS_HPP_INCLUDED + + + diff --git a/include/libtorrent/bandwidth_limit.hpp b/include/libtorrent/bandwidth_limit.hpp new file mode 100644 index 0000000..473371f --- /dev/null +++ b/include/libtorrent/bandwidth_limit.hpp @@ -0,0 +1,101 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_CHANNEL_HPP_INCLUDED +#define TORRENT_BANDWIDTH_CHANNEL_HPP_INCLUDED + +#include +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// member of peer_connection +struct TORRENT_EXTRA_EXPORT bandwidth_channel +{ + static constexpr int inf = (std::numeric_limits::max)(); + + bandwidth_channel(); + + // 0 means infinite + void throttle(int limit); + int throttle() const + { + TORRENT_ASSERT_VAL(m_limit >= 0, m_limit); + TORRENT_ASSERT_VAL(m_limit < inf, m_limit); + return m_limit; + } + + int quota_left() const; + void update_quota(int dt_milliseconds); + + // this is used when connections disconnect with + // some quota left. It's returned to its bandwidth + // channels. + void return_quota(int amount); + void use_quota(int amount); + + // this is an optimization. If there is more than one second + // of quota built up in this channel, just apply it right away + // instead of introducing a delay to split it up evenly. This + // should especially help in situations where a single peer + // has a capacity under the rate limit, but would otherwise be + // held back by the latency of getting bandwidth from the limiter + bool need_queueing(int amount) + { + if (m_quota_left - amount < m_limit) return true; + m_quota_left -= amount; + return false; + } + + // used as temporary storage while distributing + // bandwidth + int tmp; + + // this is the number of bytes to distribute this round + int distribute_quota; + +private: + + // this is the amount of bandwidth we have + // been assigned without using yet. + std::int64_t m_quota_left; + + // the limit is the number of bytes + // per second we are allowed to use. + std::int32_t m_limit; +}; + +} + +#endif diff --git a/include/libtorrent/bandwidth_manager.hpp b/include/libtorrent/bandwidth_manager.hpp new file mode 100644 index 0000000..1fd2e9c --- /dev/null +++ b/include/libtorrent/bandwidth_manager.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED +#define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT bandwidth_manager +{ + explicit bandwidth_manager(int channel); + + void close(); + +#if TORRENT_USE_ASSERTS + bool is_queued(bandwidth_socket const* peer) const; +#endif + + int queue_size() const; + std::int64_t queued_bytes() const; + + // non prioritized means that, if there's a line for bandwidth, + // others will cut in front of the non-prioritized peers. + // this is used by web seeds + // returns the number of bytes to assign to the peer, or 0 + // if the peer's 'assign_bandwidth' callback will be called later + int request_bandwidth(std::shared_ptr peer + , int blk, int priority, bandwidth_channel** chan, int num_channels); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + void update_quotas(time_duration const& dt); + +private: + + // these are the consumers that want bandwidth + std::vector m_queue; + // the number of bytes all the requests in queue are for + std::int64_t m_queued_bytes; + + // this is the channel within the consumers + // that bandwidth is assigned to (upload or download) + int m_channel; + + bool m_abort; +}; + +} + +#endif diff --git a/include/libtorrent/bandwidth_queue_entry.hpp b/include/libtorrent/bandwidth_queue_entry.hpp new file mode 100644 index 0000000..828b54c --- /dev/null +++ b/include/libtorrent/bandwidth_queue_entry.hpp @@ -0,0 +1,74 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED +#define TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED + +#include + +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT bw_request +{ + bw_request(std::shared_ptr pe + , int blk, int prio); + + std::shared_ptr peer; + // 1 is normal prio + int priority; + // the number of bytes assigned to this request so far + int assigned; + // once assigned reaches this, we dispatch the request function + int request_size; + + // the max number of rounds for this request to survive + // this ensures that requests gets responses at very low + // rate limits, when the requested size would take a long + // time to satisfy + int ttl; + + // loops over the bandwidth channels and assigns bandwidth + // from the most limiting one + int assign_bandwidth(); + + static constexpr int max_bandwidth_channels = 10; + // we don't actually support more than 10 channels per peer + aux::array channel{}; +}; + +} + +#endif diff --git a/include/libtorrent/bandwidth_socket.hpp b/include/libtorrent/bandwidth_socket.hpp new file mode 100644 index 0000000..ddf9637 --- /dev/null +++ b/include/libtorrent/bandwidth_socket.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED +#define TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT bandwidth_socket + { + virtual void assign_bandwidth(int channel, int amount) = 0; + virtual bool is_disconnecting() const = 0; + virtual ~bandwidth_socket() {} + }; +} + +#endif // TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED diff --git a/include/libtorrent/bdecode.hpp b/include/libtorrent/bdecode.hpp new file mode 100644 index 0000000..680f944 --- /dev/null +++ b/include/libtorrent/bdecode.hpp @@ -0,0 +1,450 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BDECODE_HPP +#define TORRENT_BDECODE_HPP + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +/* + +This is an efficient bdecoder. It decodes into a flat memory buffer of tokens. + +Each token has an offset into the bencoded buffer where the token came from +and a next pointer, which is a relative number of tokens to skip forward to +get to the logical next item in a container. + +strings and ints offset pointers point to the first character of the length +prefix or the 'i' character. This is to maintain uniformity with other types +and to allow easily calculating the span of a node by subtracting its offset +by the offset of the next node. + +example layout: + +{ + "a": { "b": 1, "c": "abcd" }, + "d": 3 +} + + /---------------------------------------------------------------------------------------\ + | | + | | + | /--------------------------------------------\ | + | | | | + | | | | + | /-----\ | /----\ /----\ /----\ /----\ | /----\ /----\ | + | next | | | | | | | | | | | | | | | | | + | pointers | v | | v | v | v | v v | v | v v ++-+-----+----+--+----+--+----+--+----+--+----+--+----+--+-------+----+--+----+--+------+ X +| dict | str | dict | str | int | str | str | end | str | int | end | +| | | | | | | | | | | | +| | | | | | | | | | | | ++-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+-----+-+----+ + | offset| | | | | | | | | | + | | | | | | | | | | | + |/------/ | | | | | | | | | + || /-----------/ | | | | | | | | + || |/------------------/ | | | | | | | + || || /-----------------------/ | | | | | | + || || | /----------------------------/ | | | | | + || || | | /---------------------------------/ | | | | + || || | | | /-----------------------------------/ | | | + || || | | | |/------------------------------------------/ | | + || || | | | || /-----------------------------------------------/ | + || || | | | || | /----------------------------------------------------/ + || || | | | || | | + vv vv v v v vv v v +``d1:ad1:bi1e1:c4:abcde1:di3ee`` + +*/ + +namespace libtorrent { + +TORRENT_EXPORT boost::system::error_category& bdecode_category(); + +#if TORRENT_ABI_VERSION == 1 +TORRENT_DEPRECATED +inline boost::system::error_category& get_bdecode_category() +{ return bdecode_category(); } +#endif + +namespace bdecode_errors +{ + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category bdecode_category() + // with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + // expected digit in bencoded string + expected_digit, + // expected colon in bencoded string + expected_colon, + // unexpected end of file in bencoded string + unexpected_eof, + // expected value (list, dict, int or string) in bencoded string + expected_value, + // bencoded recursion depth limit exceeded + depth_exceeded, + // bencoded item count limit exceeded + limit_exceeded, + // integer overflow + overflow, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); +} +} // namespace libtorrent + +namespace boost { namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} } + +namespace libtorrent { + + using error_code = boost::system::error_code; + +TORRENT_EXTRA_EXPORT char const* parse_int(char const* start + , char const* end, char delimiter, std::int64_t& val + , bdecode_errors::error_code_enum& ec); + +namespace detail { + +// internal +void escape_string(std::string& ret, char const* str, int len); + +// internal +struct bdecode_token +{ + // the node with type 'end' is a logical node, pointing to the end + // of the bencoded buffer. + enum type_t : std::uint8_t + { none, dict, list, string, integer, end }; + + enum limits_t + { + max_offset = (1 << 29) - 1, + max_next_item = (1 << 29) - 1, + max_header = (1 << 3) - 1 + }; + + bdecode_token(std::ptrdiff_t off, bdecode_token::type_t t) + : offset(std::uint32_t(off)) + , type(t) + , next_item(0) + , header(0) + { + TORRENT_ASSERT(off >= 0); + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(t <= end); + static_assert(std::is_unsigned::type>::value + , "we need to assert t >= 0 here"); + } + + bdecode_token(std::ptrdiff_t off, std::uint32_t next + , bdecode_token::type_t t, std::uint8_t header_size = 0) + : offset(std::uint32_t(off)) + , type(t) + , next_item(next) + , header(type == string ? std::uint32_t(header_size - 2) : 0) + { + TORRENT_ASSERT(type != string || header_size >= 2); + TORRENT_ASSERT(off >= 0); + TORRENT_ASSERT(off <= max_offset); + TORRENT_ASSERT(next <= max_next_item); + // the string has 2 implied header bytes, to allow for longer prefixes + TORRENT_ASSERT(header_size < 8 || (type == string && header_size < 10)); + TORRENT_ASSERT(t <= end); + static_assert(std::is_unsigned::type>::value + , "we need to assert t >= 0 here"); + } + + int start_offset() const { TORRENT_ASSERT(type == string); return int(header) + 2; } + + // offset into the bdecoded buffer where this node is + std::uint32_t offset:29; + + // one of type_t enums + std::uint32_t type:3; + + // if this node is a member of a list, 'next_item' is the number of nodes + // to jump forward in th node array to get to the next item in the list. + // if it's a key in a dictionary, it's the number of step forwards to get + // to its corresponding value. If it's a value in a dictionary, it's the + // number of steps to the next key, or to the end node. + // this is the _relative_ offset to the next node + std::uint32_t next_item:29; + + // this is the number of bytes to skip forward from the offset to get to the + // first character of the string, if this is a string. This field is not + // used for other types. Essentially this is the length of the length prefix + // and the colon. Since a string always has at least one character of length + // prefix and always a colon, those 2 characters are implied. + std::uint32_t header:3; +}; +} + +// a ``bdecode_node`` is used to traverse and hold the tree structure defined +// by bencoded data after it has been parse by bdecode(). +// +// There are primarily two kinds of bdecode_nodes. The ones that own the tree +// structure, and defines its lifetime, and nodes that are child nodes in the +// tree, pointing back into the root's tree. +// +// The ``bdecode_node`` passed in to ``bdecode()`` becomes the one owning the +// tree structure. Make sure not to destruct that object for as long as you +// use any of its child nodes. Also, keep in mind that the buffer originally +// parsed also must remain valid while using it. (see switch_underlying_buffer()). +// +// Copying an owning node will create a copy of the whole tree, but will still +// point into the same parsed bencoded buffer as the first one. + +// Sometimes it's important to get a non-owning reference to the root node ( +// to be able to copy it as a reference for instance). For that, use the +// non_owning() member function. +// +// There are 5 different types of nodes, see type_t. +struct TORRENT_EXPORT bdecode_node +{ +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED_EXPORT friend int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos, int depth_limit + , int token_limit); +#endif + + // hidden + TORRENT_EXPORT friend bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos, int depth_limit, int token_limit); + + // creates a default constructed node, it will have the type ``none_t``. + bdecode_node() = default; + + // For owning nodes, the copy will create a copy of the tree, but the + // underlying buffer remains the same. + bdecode_node(bdecode_node const&); + bdecode_node& operator=(bdecode_node const&); + bdecode_node(bdecode_node&&) noexcept; + bdecode_node& operator=(bdecode_node&&) = default; + + // the types of bdecoded nodes + enum type_t + { + // uninitialized or default constructed. This is also used + // to indicate that a node was not found in some cases. + none_t, + // a dictionary node. The ``dict_find_`` functions are valid. + dict_t, + // a list node. The ``list_`` functions are valid. + list_t, + // a string node, the ``string_`` functions are valid. + string_t, + // an integer node. The ``int_`` functions are valid. + int_t + }; + + // the type of this node. See type_t. + type_t type() const noexcept; + + // returns true if type() != none_t. + explicit operator bool() const noexcept; + + // return a non-owning reference to this node. This is useful to refer to + // the root node without copying it in assignments. + bdecode_node non_owning() const; + + // returns the buffer and length of the section in the original bencoded + // buffer where this node is defined. For a dictionary for instance, this + // starts with ``d`` and ends with ``e``, and has all the content of the + // dictionary in between. + span data_section() const noexcept; + + // functions with the ``list_`` prefix operate on lists. These functions are + // only valid if ``type()`` == ``list_t``. ``list_at()`` returns the item + // in the list at index ``i``. ``i`` may not be greater than or equal to the + // size of the list. ``size()`` returns the size of the list. + bdecode_node list_at(int i) const; + string_view list_string_value_at(int i + , string_view default_val = string_view()) const; + std::int64_t list_int_value_at(int i + , std::int64_t default_val = 0) const; + int list_size() const; + + // Functions with the ``dict_`` prefix operates on dictionaries. They are + // only valid if ``type()`` == ``dict_t``. In case a key you're looking up + // contains a 0 byte, you cannot use the 0-terminated string overloads, + // but have to use ``std::string`` instead. ``dict_find_list`` will return a + // valid ``bdecode_node`` if the key is found _and_ it is a list. Otherwise + // it will return a default-constructed bdecode_node. + // + // Functions with the ``_value`` suffix return the value of the node + // directly, rather than the nodes. In case the node is not found, or it has + // a different type, a default value is returned (which can be specified). + bdecode_node dict_find(string_view key) const; + std::pair dict_at(int i) const; + bdecode_node dict_find_dict(string_view key) const; + bdecode_node dict_find_list(string_view key) const; + bdecode_node dict_find_string(string_view key) const; + bdecode_node dict_find_int(string_view key) const; + string_view dict_find_string_value(string_view key + , string_view default_value = string_view()) const; + std::int64_t dict_find_int_value(string_view key + , std::int64_t default_val = 0) const; + int dict_size() const; + + // this function is only valid if ``type()`` == ``int_t``. It returns the + // value of the integer. + std::int64_t int_value() const; + + // these functions are only valid if ``type()`` == ``string_t``. They return + // the string values. Note that ``string_ptr()`` is *not* 0-terminated. + // ``string_length()`` returns the number of bytes in the string. + string_view string_value() const; + char const* string_ptr() const; + int string_length() const; + + // resets the ``bdecoded_node`` to a default constructed state. If this is + // an owning node, the tree is freed and all child nodes are invalidated. + void clear(); + + // Swap contents. + void swap(bdecode_node& n); + + // preallocate memory for the specified numbers of tokens. This is + // useful if you know approximately how many tokens are in the file + // you are about to parse. Doing so will save realloc operations + // while parsing. You should only call this on the root node, before + // passing it in to bdecode(). + void reserve(int tokens); + + // this buffer *MUST* be identical to the one originally parsed. This + // operation is only defined on owning root nodes, i.e. the one passed in to + // decode(). + void switch_underlying_buffer(char const* buf) noexcept; + + // returns true if there is a non-fatal error in the bencoding of this node + // or its children + bool has_soft_error(span error) const; + +private: + bdecode_node(detail::bdecode_token const* tokens, char const* buf + , int len, int idx); + + // if this is the root node, that owns all the tokens, they live in this + // vector. If this is a sub-node, this field is not used, instead the + // m_root_tokens pointer points to the root node's token. + aux::noexcept_movable> m_tokens; + + // this points to the root nodes token vector + // for the root node, this points to its own m_tokens member + detail::bdecode_token const* m_root_tokens = nullptr; + + // this points to the original buffer that was parsed + char const* m_buffer = nullptr; + int m_buffer_size = 0; + + // this is the index into m_root_tokens that this node refers to + // for the root node, it's 0. -1 means uninitialized. + int m_token_idx = -1; + + // this is a cache of the last element index looked up. This only applies + // to lists and dictionaries. If the next lookup is at m_last_index or + // greater, we can start iterating the tokens at m_last_token. + mutable int m_last_index = -1; + mutable int m_last_token = -1; + + // the number of elements in this list or dict (computed on the first + // call to dict_size() or list_size()) + mutable int m_size = -1; +}; + +// print the bencoded structure in a human-readable format to a string +// that's returned. +TORRENT_EXPORT std::string print_entry(bdecode_node const& e + , bool single_line = false, int indent = 0); + +// This function decodes/parses bdecoded data (for example a .torrent file). +// The data structure is returned in the ``ret`` argument. the buffer to parse +// is specified by the ``start`` of the buffer as well as the ``end``, i.e. one +// byte past the end. If the buffer fails to parse, the function returns a +// non-zero value and fills in ``ec`` with the error code. The optional +// argument ``error_pos``, if set to non-nullptr, will be set to the byte offset +// into the buffer where the parse failure occurred. +// +// ``depth_limit`` specifies the max number of nested lists or dictionaries are +// allowed in the data structure. (This affects the stack usage of the +// function, be careful not to set it too high). +// +// ``token_limit`` is the max number of tokens allowed to be parsed from the +// buffer. This is simply a sanity check to not have unbounded memory usage. +// +// The resulting ``bdecode_node`` is an *owning* node. That means it will +// be holding the whole parsed tree. When iterating lists and dictionaries, +// those ``bdecode_node`` objects will simply have references to the root or +// owning ``bdecode_node``. If the root node is destructed, all other nodes +// that refer to anything in that tree become invalid. +// +// However, the underlying buffer passed in to this function (``start``, ``end``) +// must also remain valid while the bdecoded tree is used. The parsed tree +// produced by this function does not copy any data out of the buffer, but +// simply produces references back into it. +TORRENT_EXPORT int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos = nullptr, int depth_limit = 100 + , int token_limit = 2000000); +TORRENT_EXPORT bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos = nullptr, int depth_limit = 100 + , int token_limit = 2000000); +TORRENT_EXPORT bdecode_node bdecode(span buffer + , int depth_limit = 100, int token_limit = 2000000); + +} + +#endif // TORRENT_BDECODE_HPP diff --git a/include/libtorrent/bencode.hpp b/include/libtorrent/bencode.hpp new file mode 100644 index 0000000..bd2c2d9 --- /dev/null +++ b/include/libtorrent/bencode.hpp @@ -0,0 +1,406 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BENCODE_HPP_INCLUDED +#define TORRENT_BENCODE_HPP_INCLUDED + +// OVERVIEW +// +// Bencoding is a common representation in bittorrent used for dictionary, +// list, int and string hierarchies. It's used to encode .torrent files and +// some messages in the network protocol. libtorrent also uses it to store +// settings, resume data and other session state. +// +// Strings in bencoded structures do not necessarily represent text. +// Strings are raw byte buffers of a certain length. If a string is meant to be +// interpreted as text, it is required to be UTF-8 encoded. See `BEP 3`_. +// +// The function for decoding bencoded data bdecode(), returning a bdecode_node. +// This function builds a tree that points back into the original buffer. The +// returned bdecode_node will not be valid once the buffer it was parsed out of +// is discarded. +// +// It's possible to construct an entry from a bdecode_node, if a structure needs +// to be altered and re-encoded. + +#include +#include // for distance + +#include "libtorrent/config.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/io.hpp" // for write_string +#include "libtorrent/string_util.hpp" // for is_digit + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + using invalid_encoding = system_error; +#endif + +namespace detail { + + template ::value>::type> + int write_integer(OutIt& out, In data) + { + entry::integer_type const val = entry::integer_type(data); + TORRENT_ASSERT(data == In(val)); + // the stack allocated buffer for keeping the + // decimal representation of the number can + // not hold number bigger than this: + static_assert(sizeof(entry::integer_type) <= 8, "64 bit integers required"); + static_assert(sizeof(data) <= sizeof(entry::integer_type), "input data too big, see entry::integer_type"); + char buf[21]; + auto const str = integer_to_str(buf, val); + for (char const c : str) + { + *out = c; + ++out; + } + return static_cast(str.size()); + } + + template + void write_char(OutIt& out, char c) + { + *out = c; + ++out; + } + + template + std::string read_until(InIt& in, InIt end, char end_token, bool& err) + { + std::string ret; + if (in == end) + { + err = true; + return ret; + } + while (*in != end_token) + { + ret += *in; + ++in; + if (in == end) + { + err = true; + return ret; + } + } + return ret; + } + + template + void read_string(InIt& in, InIt end, int len, std::string& str, bool& err) + { + TORRENT_ASSERT(len >= 0); + for (int i = 0; i < len; ++i) + { + if (in == end) + { + err = true; + return; + } + str += *in; + ++in; + } + } + + template + int bencode_recursive(OutIt& out, const entry& e) + { + int ret = 0; + switch(e.type()) + { + case entry::int_t: + write_char(out, 'i'); + ret += write_integer(out, e.integer()); + write_char(out, 'e'); + ret += 2; + break; + case entry::string_t: + ret += write_integer(out, e.string().length()); + write_char(out, ':'); + ret += write_string(e.string(), out); + ret += 1; + break; + case entry::list_t: + write_char(out, 'l'); + for (auto const& i : e.list()) + ret += bencode_recursive(out, i); + write_char(out, 'e'); + ret += 2; + break; + case entry::dictionary_t: + write_char(out, 'd'); + for (auto const& i : e.dict()) + { + // write key + ret += write_integer(out, i.first.length()); + write_char(out, ':'); + ret += write_string(i.first, out); + // write value + ret += bencode_recursive(out, i.second); + ret += 1; + } + write_char(out, 'e'); + ret += 2; + break; + case entry::preformatted_t: + std::copy(e.preformatted().begin(), e.preformatted().end(), out); + ret += static_cast(e.preformatted().size()); + break; + case entry::undefined_t: + + // empty string + write_char(out, '0'); + write_char(out, ':'); + + ret += 2; + break; + } + return ret; + } +#if TORRENT_ABI_VERSION == 1 + template + void bdecode_recursive(InIt& in, InIt end, entry& ret, bool& err, int depth) + { + if (depth >= 100) + { + err = true; + return; + } + + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + switch (*in) + { + + // ---------------------------------------------- + // integer + case 'i': + { + ++in; // 'i' + std::string const val = read_until(in, end, 'e', err); + if (err) return; + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + ret = entry(entry::int_t); + char* end_pointer; + ret.integer() = std::strtoll(val.c_str(), &end_pointer, 10); +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + if (end_pointer == val.c_str()) + { + err = true; + return; + } + } + break; + + // ---------------------------------------------- + // list + case 'l': + ret = entry(entry::list_t); + ++in; // 'l' + while (*in != 'e') + { + ret.list().emplace_back(); + entry& e = ret.list().back(); + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + break; + + // ---------------------------------------------- + // dictionary + case 'd': + ret = entry(entry::dictionary_t); + ++in; // 'd' + while (*in != 'e') + { + entry key; + bdecode_recursive(in, end, key, err, depth + 1); + if (err || key.type() != entry::string_t) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + entry& e = ret[key.string()]; + bdecode_recursive(in, end, e, err, depth + 1); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + if (in == end) + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + TORRENT_ASSERT(*in == 'e'); + ++in; // 'e' + break; + + // ---------------------------------------------- + // string + default: + static_assert(sizeof(*in) == 1, "Input iterator to 8 bit data required"); + if (is_digit(char(*in))) + { + std::string len_s = read_until(in, end, ':', err); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + TORRENT_ASSERT(*in == ':'); + ++in; // ':' + int len = atoi(len_s.c_str()); + ret = entry(entry::string_t); + read_string(in, end, len, ret.string(), err); + if (err) + { +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } + } + else + { + err = true; +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + return; + } +#if TORRENT_USE_ASSERTS + ret.m_type_queried = false; +#endif + } + } +#endif // TORRENT_ABI_VERSION + } + + // This function will encode data to bencoded form. + // + // The entry_ class is the internal representation of the bencoded data + // and it can be used to retrieve information, an entry_ can also be build by + // the program and given to ``bencode()`` to encode it into the ``OutIt`` + // iterator. + // + // ``OutIt`` is an OutputIterator_. It's a template and usually + // instantiated as ostream_iterator_ or back_insert_iterator_. This + // function assumes the value_type of the iterator is a ``char``. + // In order to encode entry ``e`` into a buffer, do:: + // + // std::vector buffer; + // bencode(std::back_inserter(buf), e); + // + // .. _OutputIterator: https://en.cppreference.com/w/cpp/named_req/OutputIterator + // .. _ostream_iterator: https://en.cppreference.com/w/cpp/iterator/ostream_iterator + // .. _back_insert_iterator: https://en.cppreference.com/w/cpp/iterator/back_insert_iterator + template int bencode(OutIt out, const entry& e) + { + return detail::bencode_recursive(out, e); + } + +#if TORRENT_ABI_VERSION == 1 + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end) + { + entry e; + bool err = false; + detail::bdecode_recursive(start, end, e, err, 0); + TORRENT_ASSERT(e.m_type_queried == false); + if (err) return entry(); + return e; + } + template + TORRENT_DEPRECATED + entry bdecode(InIt start, InIt end + , typename std::iterator_traits::difference_type& len) + { + entry e; + bool err = false; + InIt s = start; + detail::bdecode_recursive(start, end, e, err, 0); + len = std::distance(s, start); + TORRENT_ASSERT(len >= 0); + if (err) return entry(); + return e; + } +#endif +} + +#endif // TORRENT_BENCODE_HPP_INCLUDED diff --git a/include/libtorrent/bitfield.hpp b/include/libtorrent/bitfield.hpp new file mode 100644 index 0000000..8d67e68 --- /dev/null +++ b/include/libtorrent/bitfield.hpp @@ -0,0 +1,324 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BITFIELD_HPP_INCLUDED +#define TORRENT_BITFIELD_HPP_INCLUDED + +#include "libtorrent/assert.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/ffs.hpp" + +#include // for memset and memcpy +#include // uint32_t + +namespace libtorrent { + + // The bitfield type stores any number of bits as a bitfield + // in a heap allocated array. + struct TORRENT_EXPORT bitfield + { + // constructs a new bitfield. The default constructor creates an empty + // bitfield. ``bits`` is the size of the bitfield (specified in bits). + // ``val`` is the value to initialize the bits to. If not specified + // all bits are initialized to 0. + // + // The constructor taking a pointer ``b`` and ``bits`` copies a bitfield + // from the specified buffer, and ``bits`` number of bits (rounded up to + // the nearest byte boundary). + bitfield() noexcept = default; + explicit bitfield(int bits) { resize(bits); } + bitfield(int bits, bool val) { resize(bits, val); } + bitfield(char const* b, int bits) { assign(b, bits); } + bitfield(bitfield const& rhs) { assign(rhs.data(), rhs.size()); } + bitfield(bitfield&& rhs) noexcept = default; + + // copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to + // the nearest byte boundary. + void assign(char const* b, int const bits) + { + resize(bits); + if (bits > 0) + { + std::memcpy(buf(), b, std::size_t((bits + 7) / 8)); + clear_trailing_bits(); + } + } + + // query bit at ``index``. Returns true if bit is 1, otherwise false. + bool operator[](int index) const noexcept + { return get_bit(index); } + bool get_bit(int index) const noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + return (buf()[index / 32] & aux::host_to_network(0x80000000 >> (index & 31))) != 0; + } + + // set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). + void clear_bit(int index) noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + buf()[index / 32] &= aux::host_to_network(~(0x80000000 >> (index & 31))); + } + void set_bit(int index) noexcept + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < size()); + buf()[index / 32] |= aux::host_to_network(0x80000000 >> (index & 31)); + } + + // returns true if all bits in the bitfield are set + bool all_set() const noexcept; + + // returns true if no bit in the bitfield is set + bool none_set() const noexcept + { + if(size() == 0) return true; + + const int words = num_words(); + std::uint32_t const* b = buf(); + for (int i = 0; i < words; ++i) + { + if (b[i] != 0) return false; + } + return true; + } + + // returns the size of the bitfield in bits. + int size() const noexcept + { + int const bits = m_buf == nullptr ? 0 : int(m_buf[0]); + TORRENT_ASSERT(bits >= 0); + return bits; + } + + // returns the number of 32 bit words are needed to represent all bits in + // this bitfield. + int num_words() const noexcept + { + return (size() + 31) / 32; + } + + // returns true if the bitfield has zero size. + bool empty() const noexcept { return size() == 0; } + + // returns a pointer to the internal buffer of the bitfield, or + // ``nullptr`` if it's empty. + char const* data() const noexcept { return m_buf ? reinterpret_cast(&m_buf[1]) : nullptr; } + char* data() noexcept { return m_buf ? reinterpret_cast(&m_buf[1]) : nullptr; } + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + char const* bytes() const { return data(); } +#endif + + // hidden + bitfield& operator=(bitfield const& rhs) + { + if (&rhs == this) return *this; + assign(rhs.data(), rhs.size()); + return *this; + } + bitfield& operator=(bitfield&& rhs) noexcept = default; + + // swaps the bit-fields two variables refer to + void swap(bitfield& rhs) noexcept + { + std::swap(m_buf, rhs.m_buf); + } + + // count the number of bits in the bitfield that are set to 1. + int count() const noexcept; + + // returns the index of the first set bit in the bitfield, i.e. 1 bit. + int find_first_set() const noexcept; + + // returns the index to the last cleared bit in the bitfield, i.e. 0 bit. + int find_last_clear() const noexcept; + + // internal + struct const_iterator + { + friend struct bitfield; + + using value_type = bool; + using difference_type = ptrdiff_t; + using pointer = bool const*; + using reference = bool&; + using iterator_category = std::forward_iterator_tag; + + bool operator*() noexcept { return (*buf & aux::host_to_network(bit)) != 0; } + const_iterator& operator++() noexcept { inc(); return *this; } + const_iterator operator++(int) noexcept + { const_iterator ret(*this); inc(); return ret; } + const_iterator& operator--() noexcept { dec(); return *this; } + const_iterator operator--(int) noexcept + { const_iterator ret(*this); dec(); return ret; } + + const_iterator() noexcept {} + bool operator==(const_iterator const& rhs) const noexcept + { return buf == rhs.buf && bit == rhs.bit; } + + bool operator!=(const_iterator const& rhs) const noexcept + { return buf != rhs.buf || bit != rhs.bit; } + + private: + void inc() + { + TORRENT_ASSERT(buf); + if (bit == 0x01) + { + bit = 0x80000000; + ++buf; + } + else + { + bit >>= 1; + } + } + void dec() + { + TORRENT_ASSERT(buf); + if (bit == 0x80000000) + { + bit = 0x01; + --buf; + } + else + { + bit <<= 1; + } + } + const_iterator(std::uint32_t const* ptr, int offset) + : buf(ptr), bit(0x80000000 >> offset) {} + std::uint32_t const* buf = nullptr; + std::uint32_t bit = 0x80000000; + }; + + // internal + const_iterator begin() const noexcept { return const_iterator(m_buf ? buf() : nullptr, 0); } + const_iterator end() const noexcept + { + if (m_buf) + return const_iterator(buf() + num_words() - (((size() & 31) == 0) ? 0 : 1), size() & 31); + else + return const_iterator(nullptr, size() & 31); + } + + // set the size of the bitfield to ``bits`` length. If the bitfield is extended, + // the new bits are initialized to ``val``. + void resize(int bits, bool val); + void resize(int bits); + + // set all bits in the bitfield to 1 (set_all) or 0 (clear_all). + void set_all() noexcept + { + if (size() == 0) return; + std::memset(buf(), 0xff, std::size_t(num_words()) * 4); + clear_trailing_bits(); + } + void clear_all() noexcept + { + if (size() == 0) return; + std::memset(buf(), 0x00, std::size_t(num_words()) * 4); + } + + // make the bitfield empty, of zero size. + void clear() noexcept { m_buf.reset(); } + + private: + + std::uint32_t const* buf() const noexcept { TORRENT_ASSERT(m_buf); return &m_buf[1]; } + std::uint32_t* buf() noexcept { TORRENT_ASSERT(m_buf); return &m_buf[1]; } + void clear_trailing_bits() noexcept + { + // clear the tail bits in the last byte + if (size() & 31) buf()[num_words() - 1] &= aux::host_to_network(0xffffffff << (32 - (size() & 31))); + } + + // the first element is not part of the bitfield, it's the + // number of bits. + aux::unique_ptr m_buf; + }; + + template + struct typed_bitfield : bitfield + { + typed_bitfield() noexcept {} + typed_bitfield(typed_bitfield&& rhs) noexcept + : bitfield(std::forward(rhs)) + {} + typed_bitfield(typed_bitfield const& rhs) + : bitfield(static_cast(rhs)) + {} + typed_bitfield(bitfield&& rhs) noexcept : bitfield(std::forward(rhs)) {} // NOLINT + typed_bitfield(bitfield const& rhs) : bitfield(rhs) {} // NOLINT + typed_bitfield& operator=(typed_bitfield&& rhs) noexcept + { + this->bitfield::operator=(std::forward(rhs)); + return *this; + } + typed_bitfield& operator=(typed_bitfield const& rhs) + { + this->bitfield::operator=(rhs); + return *this; + } + using bitfield::bitfield; + + // returns an object that can be used in a range-for to iterate over all + // indices in the bitfield + index_range range() const noexcept + { + return {IndexType{0}, end_index()}; + } + + bool operator[](IndexType const index) const + { return this->bitfield::get_bit(static_cast(index)); } + + bool get_bit(IndexType const index) const + { return this->bitfield::get_bit(static_cast(index)); } + + void clear_bit(IndexType const index) + { this->bitfield::clear_bit(static_cast(index)); } + + void set_bit(IndexType const index) + { this->bitfield::set_bit(static_cast(index)); } + + IndexType end_index() const noexcept { return IndexType(this->size()); } + }; +} + +#endif // TORRENT_BITFIELD_HPP_INCLUDED diff --git a/include/libtorrent/block_cache.hpp b/include/libtorrent/block_cache.hpp new file mode 100644 index 0000000..203f696 --- /dev/null +++ b/include/libtorrent/block_cache.hpp @@ -0,0 +1,556 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOCK_CACHE +#define TORRENT_BLOCK_CACHE + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#if TORRENT_USE_ASSERTS +#include "libtorrent/aux_/vector.hpp" +#endif + +namespace libtorrent { + + struct disk_io_job; + struct storage_interface; + struct cache_status; + struct counters; +namespace aux { + + struct session_settings; + struct block_cache_reference; + } +#if TORRENT_USE_ASSERTS + class file_storage; +#endif + +#if TORRENT_USE_ASSERTS || !defined TORRENT_DISABLE_LOGGING + + // internal + struct piece_log_t + { + explicit piece_log_t(job_action_t j, int b = -1): job(j), block(b) {} + job_action_t job; + int block; + + // these are "jobs" thar cause piece_refcount + // to be incremented + // internal + enum artificial_jobs + { + flushing = static_cast(job_action_t::num_job_ids), // 20 + flush_expired, + try_flush_write_blocks, + try_flush_write_blocks2, + flush_range, + clear_outstanding_jobs, + set_outstanding_jobs, + + last_job + }; + explicit piece_log_t(artificial_jobs j, int b = -1): job(static_cast(j)), block(b) {} + + static std::array const job_names; + }; + + char const* job_name(job_action_t j); + +#endif // TORRENT_DISABLE_LOGGING + +#if TORRENT_USE_ASSERTS + void print_piece_log(aux::vector const& piece_log); + void assert_print_piece(cached_piece_entry const* pe); +#endif + + extern std::array const job_action_name; + + struct TORRENT_EXTRA_EXPORT partial_hash + { + partial_hash(): offset(0) {} + // the number of bytes in the piece that has been hashed + int offset; + // the SHA-1 context + hasher h; + }; + + struct cached_block_entry + { + cached_block_entry() + : refcount(0) + , dirty(0) + , pending(0) + , cache_hit(0) + { + } + + char* buf = nullptr; + + static constexpr int max_refcount = (1 << 29) - 1; + + // the number of references to this buffer. These references + // might be in outstanding asynchronous requests or in peer + // connection send buffers. We can't free the buffer until + // all references are gone and refcount reaches 0. The buf + // pointer in this struct doesn't count as a reference and + // is always the last to be cleared + std::uint32_t refcount:29; + + // if this is true, this block needs to be written to + // disk before it's freed. Typically all blocks in a piece + // would either be dirty (write coalesce cache) or not dirty + // (read-ahead cache). Once blocks are written to disk, the + // dirty flag is cleared and effectively turns the block + // into a read cache block + std::uint32_t dirty:1; + + // pending means that this buffer has not yet been filled in + // with valid data. There's an outstanding read job for this. + // If the dirty flag is set, it means there's an outstanding + // write job to write this block. + std::uint32_t pending:1; + + // this is set to 1 if this block has been read at least once. If the same + // block is read twice, the whole piece is considered *frequently* used, + // not just recently used. + std::uint32_t cache_hit:1; + +#if TORRENT_USE_ASSERTS + // this many of the references are held by hashing operations + int hashing_count = 0; + // this block is being used in this many peer's send buffers currently + int reading_count = 0; + // the number of references held by flushing operations + int flushing_count = 0; +#endif + }; + + // list_node is here to be able to link this cache entry + // into one of the LRU lists + struct TORRENT_EXTRA_EXPORT cached_piece_entry + : list_node + , boost::intrusive::list_base_hook> + { + cached_piece_entry(); + ~cached_piece_entry(); + cached_piece_entry(cached_piece_entry&&) = default; + cached_piece_entry& operator=(cached_piece_entry&&) = default; + + bool ok_to_evict(bool ignore_hash = false) const + { + return refcount == 0 + && piece_refcount == 0 + && !hashing + && read_jobs.empty() + && outstanding_read == 0 + && (ignore_hash || !hash || hash->offset == 0); + } + + // storage this piece belongs to + std::shared_ptr storage; + + // write jobs hanging off of this piece + tailqueue jobs; + + // read jobs waiting for the read job currently outstanding + // on this piece to complete. These are executed at that point. + tailqueue read_jobs; + + piece_index_t get_piece() const { return piece; } + void* get_storage() const { return storage.get(); } + + bool operator==(cached_piece_entry const& rhs) const + { return piece == rhs.piece && storage.get() == rhs.storage.get(); } + + // if this is set, we'll be calculating the hash + // for this piece. This member stores the interim + // state while we're calculating the hash. + std::unique_ptr hash; + + // the pointers to the block data. If this is a ghost + // cache entry, there won't be any data here + aux::unique_ptr blocks; + + // the last time a block was written to this piece + // plus the minimum amount of time the block is guaranteed + // to stay in the cache + //TODO: make this 32 bits and to count seconds since the block cache was created + time_point expire = min_time(); + + piece_index_t piece{0}; + + // the number of dirty blocks in this piece + std::uint64_t num_dirty:14; + + // the number of blocks in the cache for this piece + std::uint64_t num_blocks:14; + + // the total number of blocks in this piece (and the number + // of elements in the blocks array) + std::uint64_t blocks_in_piece:14; + + // ---- 64 bit boundary ---- + + // while we have an outstanding async hash operation + // working on this piece, 'hashing' is set to 1 + // When the operation returns, this is set to 0. + std::uint16_t hashing:1; + + // if we've completed at least one hash job on this + // piece, and returned it. This is set to one + std::uint16_t hashing_done:1; + + // if this is true, whenever refcount hits 0, + // this piece should be deleted from the cache + // (not just demoted) + std::uint16_t marked_for_deletion:1; + + // this is set to true once we flush blocks past + // the hash cursor. Once this happens, there's + // no point in keeping cache blocks around for + // it in avoid_readback mode + std::uint16_t need_readback:1; + + // indicates which LRU list this piece is chained into + enum cache_state_t + { + // not added to the cache + none, + + // this is the LRU list for pieces with dirty blocks + write_lru, + + // this is the LRU list for volatile pieces. i.e. + // pieces with very low cache priority. These are + // always the first ones to be evicted. + volatile_read_lru, + + // this is the LRU list for read blocks that have + // been requested once + read_lru1, + + // the is the LRU list for read blocks that have + // been requested once recently, but then was evicted. + // if these are requested again, they will be moved + // to list 2, the frequently requested pieces + read_lru1_ghost, + + // this is the LRU of frequently used pieces. Any + // piece that has been requested by a second peer + // while pulled in to list 1 by a different peer + // is moved into this list + read_lru2, + + // this is the LRU of frequently used pieces but + // that has been recently evicted. If a piece in + // this list is requested, it's moved back into list 2. + read_lru2_ghost, + num_lrus + }; + + std::uint16_t cache_state:3; + + // this is the number of threads that are currently holding + // a reference to this piece. A piece may not be removed from + // the cache while this is > 0 + std::uint16_t piece_refcount:7; + + // if this is set to one, it means there is an outstanding + // flush_hashed job for this piece, and there's no need to + // issue another one. + std::uint16_t outstanding_flush:1; + + // as long as there is a read operation outstanding on this + // piece, this is set to 1. Otherwise 0. + // the purpose is to make sure not two threads are reading + // the same blocks at the same time. If a new read job is + // added when this is 1, that new job should be hung on the + // read job queue (read_jobs). + std::uint16_t outstanding_read:1; + + // this is set when the piece should be evicted as soon as there + // no longer are any references to it. Evicted here means demoted + // to a ghost list + std::uint32_t marked_for_eviction:1; + + // the number of blocks that have >= 1 refcount + std::uint32_t pinned:15; + + // ---- 32 bit boundary --- + + // the sum of all refcounts in all blocks + std::int32_t refcount = 0; + +#if TORRENT_USE_ASSERTS + // the number of times this piece has finished hashing + int hash_passes = 0; + + // this is a debug facility to keep a log + // of which operations have been run on this piece + aux::vector piece_log; + + bool in_storage = false; + bool in_use = true; +#endif + }; + + struct TORRENT_EXTRA_EXPORT block_cache : disk_buffer_pool + { + block_cache(io_service& ios, std::function const& trigger_trim); + ~block_cache(); + + private: + + struct hash_value + { + std::size_t operator()(cached_piece_entry const& p) const + { return reinterpret_cast(p.storage.get()) + std::size_t(static_cast(p.piece)); } + }; + using cache_t = std::unordered_set; + + public: + + using const_iterator = cache_t::const_iterator; + + // returns the number of blocks this job would cause to be read in + int pad_job(disk_io_job const* j, int blocks_in_piece + , int read_ahead) const; + + void reclaim_block(storage_interface* st, aux::block_cache_reference const& ref); + + // returns a range of all pieces. This might be a very + // long list, use carefully + std::pair all_pieces() const; + int num_pieces() const { return int(m_pieces.size()); } + + list_iterator write_lru_pieces() const + { return m_lru[cached_piece_entry::write_lru].iterate(); } + + int num_write_lru_pieces() const { return m_lru[cached_piece_entry::write_lru].size(); } + + enum eviction_mode + { + allow_ghost, + disallow_ghost + }; + + // mark this piece for deletion. If there are no outstanding + // requests to this piece, it's removed immediately, and the + // passed in iterator will be invalidated + void mark_for_eviction(cached_piece_entry* p, eviction_mode mode); + + // similar to mark_for_eviction, except for actually marking the + // piece for deletion. If the piece was actually deleted, + // the function returns true + bool evict_piece(cached_piece_entry* p, tailqueue& jobs + , eviction_mode mode); + + // if this piece is in L1 or L2 proper, move it to + // its respective ghost list + void move_to_ghost(cached_piece_entry* p); + + // returns the number of bytes read on success (cache hit) + // -1 on cache miss + int try_read(disk_io_job* j, buffer_allocator_interface& allocator + , bool expect_no_fail = false); + + // called when we're reading and we found the piece we're + // reading from in the hash table (not necessarily that we + // hit the block we needed) + void cache_hit(cached_piece_entry* p, int block, bool volatile_read); + + // free block from piece entry + void free_block(cached_piece_entry* pe, int block); + + // erase a piece (typically from the ghost list). Reclaim all + // its blocks and unlink it and free it. + void erase_piece(cached_piece_entry* p); + + // bump the piece 'p' to the back of the LRU list it's + // in (back == MRU) + // this is only used for the write cache + void bump_lru(cached_piece_entry* p); + + // move p into the correct lru queue + void update_cache_state(cached_piece_entry* p); + + // if the piece is marked for deletion and has a refcount + // of 0, this function will post any sync jobs and + // delete the piece from the cache + bool maybe_free_piece(cached_piece_entry* p); + + // either returns the piece in the cache, or allocates + // a new empty piece and returns it. + // cache_state is one of cache_state_t enum + cached_piece_entry* allocate_piece(disk_io_job const* j, std::uint16_t cache_state); + + // looks for this piece in the cache. If it's there, returns a pointer + // to it, otherwise 0. + cached_piece_entry* find_piece(disk_io_job const* j); + cached_piece_entry* find_piece(storage_interface* st, piece_index_t piece); + + // clear free all buffers marked as dirty with + // refcount of 0. + void abort_dirty(cached_piece_entry* p); + + // used to convert dirty blocks into non-dirty ones + // i.e. from being part of the write cache to being part + // of the read cache. it's used when flushing blocks to disk + // returns true if the piece entry was freed + bool blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed); + + // adds a block to the cache, marks it as dirty and + // associates the job with it. When the block is + // flushed, the callback is posted + cached_piece_entry* add_dirty_block(disk_io_job* j, bool add_hasher); + + enum { blocks_inc_refcount = 1 }; + void insert_blocks(cached_piece_entry* pe, int block, span iov + , disk_io_job* j, int flags = 0); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + // try to remove num number of read cache blocks from the cache + // pick the least recently used ones first + // return the number of blocks that was requested to be evicted + // that couldn't be + int try_evict_blocks(int num, cached_piece_entry* ignore = nullptr); + + // try to evict a single volatile piece, if there is one. + void try_evict_one_volatile(); + + // if there are any dirty blocks + void clear(tailqueue& jobs); + + void update_stats_counters(counters& c) const; +#if TORRENT_ABI_VERSION == 1 + void get_stats(cache_status* ret) const; +#endif + void set_settings(aux::session_settings const& sett); + + enum reason_t { ref_hashing = 0, ref_reading = 1, ref_flushing = 2 }; + bool inc_block_refcount(cached_piece_entry* pe, int block, int reason); + void dec_block_refcount(cached_piece_entry* pe, int block, int reason); + + int pinned_blocks() const { return m_pinned_blocks; } + int read_cache_size() const { return m_read_cache_size; } + + private: + + // returns number of bytes read on success, -1 on cache miss + // (just because the piece is in the cache, doesn't mean all + // the blocks are there) + int copy_from_piece(cached_piece_entry* p, disk_io_job* j + , buffer_allocator_interface& allocator, bool expect_no_fail = false); + + int drain_piece_bufs(cached_piece_entry& p, std::vector& buf); + + // block container + cache_t m_pieces; + + // linked list of all elements in m_pieces, in usage order + // the most recently used are in the tail. iterating from head + // to tail gives the least recently used entries first + // the read-list is for read blocks and the write-list is for + // dirty blocks that needs flushing before being evicted + // [0] = write-LRU + // [1] = read-LRU1 + // [2] = read-LRU1-ghost + // [3] = read-LRU2 + // [4] = read-LRU2-ghost + linked_list m_lru[cached_piece_entry::num_lrus]; + + // this is used to determine whether to evict blocks from + // L1 or L2. + enum cache_op_t + { + cache_miss, + ghost_hit_lru1, + ghost_hit_lru2 + }; + int m_last_cache_op; + + // the number of pieces to keep in the ARC ghost lists + // this is determined by being a fraction of the cache size + int m_ghost_size; + + // the is the max number of volatile read cache blocks are allowed in the + // cache. Once this is reached, other volatile blocks will start to be + // evicted. + int m_max_volatile_blocks; + + // the number of blocks (buffers) allocated by volatile pieces. + std::int32_t m_volatile_size; + + // the number of blocks in the cache + // that are in the read cache + std::int32_t m_read_cache_size; + + // the number of blocks in the cache + // that are in the write cache + std::int32_t m_write_cache_size; + + // the number of blocks that are currently sitting + // in peer's send buffers. If two peers are sending + // the same block, it counts as 2, even though there're + // no buffer duplication + std::int32_t m_send_buffer_blocks; + + // the number of blocks with a refcount > 0, i.e. + // they may not be evicted + int m_pinned_blocks; + }; + +} + +#endif // TORRENT_BLOCK_CACHE diff --git a/include/libtorrent/bloom_filter.hpp b/include/libtorrent/bloom_filter.hpp new file mode 100644 index 0000000..d1dcec9 --- /dev/null +++ b/include/libtorrent/bloom_filter.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BLOOM_FILTER_HPP_INCLUDED +#define TORRENT_BLOOM_FILTER_HPP_INCLUDED + +#include "libtorrent/sha1_hash.hpp" + +#include // for log() +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT void set_bits(std::uint8_t const* b, std::uint8_t* bits, int len); + TORRENT_EXTRA_EXPORT bool has_bits(std::uint8_t const* b, std::uint8_t const* bits, int len); + TORRENT_EXTRA_EXPORT int count_zero_bits(std::uint8_t const* bits, int len); + + template + struct bloom_filter + { + bool find(sha1_hash const& k) const + { return has_bits(&k[0], bits, N); } + + void set(sha1_hash const& k) + { set_bits(&k[0], bits, N); } + + std::string to_string() const + { return std::string(reinterpret_cast(&bits[0]), N); } + + void from_string(char const* str) + { std::memcpy(bits, str, N); } + + void clear() { std::memset(bits, 0, N); } + + float size() const + { + const int c = (std::min)(count_zero_bits(bits, N), (N * 8) - 1); + const int m = N * 8; + return std::log(c / float(m)) / (2.f * std::log(1.f - 1.f/m)); + } + + bloom_filter() { clear(); } + + private: + std::uint8_t bits[N]; + }; + +} + +#endif // TORRENT_BLOOM_FILTER_HPP_INCLUDED diff --git a/include/libtorrent/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp new file mode 100644 index 0000000..e577d98 --- /dev/null +++ b/include/libtorrent/broadcast_socket.hpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BROADCAST_SOCKET_HPP_INCLUDED +#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" + +#include +#include +#include + +namespace libtorrent { + + // TODO: 2 factor these functions out + TORRENT_EXTRA_EXPORT bool is_global(address const& a); + TORRENT_EXTRA_EXPORT bool is_local(address const& a); + TORRENT_EXTRA_EXPORT bool is_link_local(address const& addr); + TORRENT_EXTRA_EXPORT bool is_loopback(address const& addr); + TORRENT_EXTRA_EXPORT bool is_any(address const& addr); + TORRENT_EXTRA_EXPORT bool is_teredo(address const& addr); + TORRENT_EXTRA_EXPORT bool is_ip_address(std::string const& host); + + // internal + // TODO: refactor these out too + template + bool is_v4(Endpoint const& ep) + { + return ep.protocol() == Endpoint::protocol_type::v4(); + } + template + bool is_v6(Endpoint const& ep) + { + return ep.protocol() == Endpoint::protocol_type::v6(); + } + + // determines if the operating system supports IPv6 + TORRENT_EXTRA_EXPORT bool supports_ipv6(); + address ensure_v6(address const& a); + + using receive_handler_t = std::function buffer)>; + + class TORRENT_EXTRA_EXPORT broadcast_socket + { + public: + explicit broadcast_socket(udp::endpoint const& multicast_endpoint); + ~broadcast_socket() { close(); } + + void open(receive_handler_t handler, io_service& ios + , error_code& ec, bool loopback = true); + + enum flags_t { flag_broadcast = 1 }; + void send(char const* buffer, int size, error_code& ec, int flags = 0); + void send_to(char const* buffer, int size, udp::endpoint const& to, error_code& ec); + + void close(); + int num_send_sockets() const { return int(m_unicast_sockets.size()); } + + private: + + struct socket_entry + { + explicit socket_entry(std::shared_ptr s) + : socket(std::move(s)), broadcast(false) {} + socket_entry(std::shared_ptr s + , address_v4 const& mask): socket(std::move(s)), netmask(mask), broadcast(false) + {} + std::shared_ptr socket; + std::array buffer{}; + udp::endpoint remote; + address_v4 netmask; + bool broadcast; + void close() + { + if (!socket) return; + error_code ec; + socket->close(ec); + } + bool can_broadcast() const + { + error_code ec; + return broadcast + && netmask != address_v4() + && is_v4(socket->local_endpoint(ec)); + } + address_v4 broadcast_address() const + { + error_code ec; + return address_v4::broadcast(socket->local_endpoint(ec).address().to_v4(), netmask); + } + }; + + void on_receive(socket_entry* s, error_code const& ec + , std::size_t bytes_transferred); + void open_unicast_socket(io_service& ios, address const& addr + , address_v4 const& mask); + void open_multicast_socket(io_service& ios, address const& addr + , bool loopback, error_code& ec); + + // if we're aborting, destruct the handler and return true + bool maybe_abort(); + + // these sockets are used to + // join the multicast group (on each interface) + // and receive multicast messages + std::list m_sockets; + // these sockets are not bound to any + // specific port and are used to + // send messages to the multicast group + // and receive unicast responses + std::list m_unicast_sockets; + udp::endpoint m_multicast_endpoint; + receive_handler_t m_on_receive; + + // the number of outstanding async operations + // we have on these sockets. The m_on_receive + // handler may not be destructed until this reaches + // 0, since it may be holding references to + // the broadcast_socket itself. + int m_outstanding_operations; + // when set to true, we're trying to shut down + // don't initiate new operations and once the + // outstanding counter reaches 0, destruct + // the handler object + bool m_abort; + }; +} + +#endif diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp new file mode 100644 index 0000000..94f30b1 --- /dev/null +++ b/include/libtorrent/bt_peer_connection.hpp @@ -0,0 +1,495 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +Copyright (c) 2007-2018, Arvid Norberg, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/debug.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/io.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct TORRENT_EXTRA_EXPORT ut_pex_peer_store + { + // stores all peers this peer is connected to. These lists + // are updated with each pex message and are limited in size + // to protect against malicious clients. These lists are also + // used for looking up which peer a peer that supports holepunch + // came from. + // these are vectors to save memory and keep the items close + // together for performance. Inserting and removing is relatively + // cheap since the lists' size is limited + using peers4_t = std::vector>; + peers4_t m_peers; + using peers6_t = std::vector>; + peers6_t m_peers6; + + bool was_introduced_by(tcp::endpoint const& ep); + + virtual ~ut_pex_peer_store() {} + }; +#endif + + class TORRENT_EXTRA_EXPORT bt_peer_connection + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + explicit bt_peer_connection(peer_connection_args const& pack); + + void start() override; + + enum + { + // pex_msg = 1, + // metadata_msg = 2, + upload_only_msg = 3, + holepunch_msg = 4, + // recommend_msg = 5, + // comment_msg = 6, + dont_have_msg = 7, + share_mode_msg = 8 + }; + + ~bt_peer_connection() override; + + peer_id our_pid() const override { return m_our_peer_id; } + +#if !defined TORRENT_DISABLE_ENCRYPTION + bool supports_encryption() const + { return m_encrypted; } + bool rc4_encrypted() const + { return m_rc4_encrypted; } + + void switch_send_crypto(std::shared_ptr crypto); + void switch_recv_crypto(std::shared_ptr crypto); +#endif + + connection_type type() const override + { return connection_type::bittorrent; } + + enum message_type + { + // standard messages + msg_choke = 0, + msg_unchoke, + msg_interested, + msg_not_interested, + msg_have, + msg_bitfield, + msg_request, + msg_piece, + msg_cancel, + // DHT extension + msg_dht_port, + // FAST extension + msg_suggest_piece = 0xd, + msg_have_all, + msg_have_none, + msg_reject_request, + msg_allowed_fast, + + // extension protocol message + msg_extended = 20, + + num_supported_messages + }; + + enum class hp_message : std::uint8_t + { + // msg_types + rendezvous = 0, + connect = 1, + failed = 2 + }; + + enum class hp_error + { + // error codes + no_error = 0, + no_such_peer = 1, + not_connected = 2, + no_support = 3, + no_self = 4 + }; + + // called from the main loop when this connection has any + // work to do. + + void on_sent(error_code const& error + , std::size_t bytes_transferred) override; + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + void on_receive_impl(std::size_t bytes_transferred); + +#if !defined TORRENT_DISABLE_ENCRYPTION + // next_barrier, buffers-to-prepend + std::tuple>> + hit_send_barrier(span> iovec) override; +#endif + + void get_specific_peer_info(peer_info& p) const override; + bool in_handshake() const override; + bool packet_finished() const { return m_recv_buffer.packet_finished(); } + + bool supports_holepunch() const { return m_holepunch_id != 0; } +#ifndef TORRENT_DISABLE_EXTENSIONS + void set_ut_pex(std::weak_ptr ut_pex) + { m_ut_pex = std::move(ut_pex); } + bool was_introduced_by(tcp::endpoint const& ep) const + { auto p = m_ut_pex.lock(); return p && p->was_introduced_by(ep); } +#endif + + bool support_extensions() const { return m_supports_extensions; } + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void on_choke(int received); + void on_unchoke(int received); + void on_interested(int received); + void on_not_interested(int received); + void on_have(int received); + void on_bitfield(int received); + void on_request(int received); + void on_piece(int received); + void on_cancel(int received); + + // DHT extension + void on_dht_port(int received); + + // FAST extension + void on_suggest_piece(int received); + void on_have_all(int received); + void on_have_none(int received); + void on_reject_request(int received); + void on_allowed_fast(int received); + void on_holepunch(); + + void on_extended(int received); + + void on_extended_handshake(); + + // the following functions appends messages + // to the send buffer + void write_choke() override; + void write_unchoke() override; + void write_interested() override; + void write_not_interested() override; + void write_request(peer_request const& r) override; + void write_cancel(peer_request const& r) override; + void write_bitfield() override; + void write_have(piece_index_t index) override; + void write_dont_have(piece_index_t index) override; + void write_piece(peer_request const& r, disk_buffer_holder buffer) override; + void write_keepalive() override; + void write_handshake(); + void write_upload_only(bool enabled) override; + void write_extensions(); + void write_share_mode(); + void write_holepunch_msg(hp_message type, tcp::endpoint const& ep + , hp_error error = hp_error::no_error); + + // DHT extension + void write_dht_port(int listen_port); + + // FAST extension + void write_have_all(); + void write_have_none(); + void write_reject_request(peer_request const&) override; + void write_allow_fast(piece_index_t piece) override; + void write_suggest(piece_index_t piece) override; + + void on_connected() override; + void on_metadata() override; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; + time_point m_last_choke; +#endif + + private: + + template + void send_message(message_type const type + , counters::stats_counter_t const counter + , Args... args) + { + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + char msg[5 + sizeof...(Args) * 4] + = { 0,0,0,1 + sizeof...(Args) * 4, static_cast(type) }; + char* ptr = msg + 5; + TORRENT_UNUSED(ptr); + + int tmp[] = {0, (detail::write_int32(args, ptr), 0)...}; + TORRENT_UNUSED(tmp); + + send_buffer(msg); + + stats_counters().inc_stats_counter(counter); + } + + void write_dht_port(); + + bool dispatch_message(int received); + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + +#if !defined TORRENT_DISABLE_ENCRYPTION + + // if (is_local()), we are 'a' otherwise 'b' + // + // 1. a -> b dhkey, pad + // 2. b -> a dhkey, pad + // 3. a -> b sync, payload + // 4. b -> a sync, payload + // 5. a -> b payload + + void write_pe1_2_dhkey(); + void write_pe3_sync(); + void write_pe4_sync(int crypto_select); + + void write_pe_vc_cryptofield(span write_buf + , int crypto_field, int pad_size); + + // helper to cut down on boilerplate + void rc4_decrypt(span buf); +#endif + + public: + + // these functions encrypt the send buffer if m_rc4_encrypted + // is true, otherwise it passes the call to the + // peer_connection functions of the same names + template + void append_const_send_buffer(Holder holder, int size) + { +#if !defined TORRENT_DISABLE_ENCRYPTION + if (!m_enc_handler.is_send_plaintext()) + { + // if we're encrypting this buffer, we need to make a copy + // since we'll mutate it + buffer buf(size, {holder.data(), size}); + append_send_buffer(std::move(buf), size); + } + else +#endif + { + append_send_buffer(std::move(holder), size); + } + } + + private: + + enum class state_t : std::uint8_t + { +#if !defined TORRENT_DISABLE_ENCRYPTION + read_pe_dhkey, + read_pe_syncvc, + read_pe_synchash, + read_pe_skey_vc, + read_pe_cryptofield, + read_pe_pad, + read_pe_ia, + init_bt_handshake, +#endif + read_protocol_identifier, + read_info_hash, + read_peer_id, + + // handshake complete + read_packet_size, + read_packet + }; + + // state of on_receive. one of the enums in state_t + state_t m_state = state_t::read_protocol_identifier; + + // this is set to true if the handshake from + // the peer indicated that it supports the + // extension protocol + bool m_supports_extensions:1; + bool m_supports_dht_port:1; + bool m_supports_fast:1; + + // this is set to true when we send the bitfield message. + // for magnet links we can't do that right away, + // since we don't know how many pieces there are in + // the torrent. + bool m_sent_bitfield:1; + + // true if we're done sending the bittorrent handshake, + // and can send bittorrent messages + bool m_sent_handshake:1; + + // set to true once we send the allowed-fast messages. This is + // only done once per connection + bool m_sent_allowed_fast:1; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // this is set to true after the encryption method has been + // successfully negotiated (either plaintext or rc4), to signal + // automatic encryption/decryption. + bool m_encrypted:1; + + // true if rc4, false if plaintext + bool m_rc4_encrypted:1; + +// this is a legitimate use of a shadow field +#ifdef __clang__ +#pragma clang diagnostic push +// macOS clang doesn't have -Wshadow-field +#pragma clang diagnostic ignored "-Wunknown-pragmas" +// Xcode 9 needs this +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wshadow-field" +#endif + crypto_receive_buffer m_recv_buffer; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + + std::string m_client_version; + + // the peer ID we advertise for ourself + peer_id const m_our_peer_id; + + // this is a queue of ranges that describes + // where in the send buffer actual payload + // data is located. This is currently + // only used to be able to gather statistics + // separately on payload and protocol data. + struct range + { + range(int s, int l) + : start(s) + , length(l) + { + TORRENT_ASSERT(s >= 0); + TORRENT_ASSERT(l > 0); + } + int start; + int length; + }; + + std::vector m_payloads; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // initialized during write_pe1_2_dhkey, and destroyed on + // creation of m_enc_handler. Cannot reinitialize once + // initialized. + std::unique_ptr m_dh_key_exchange; + + // used during an encrypted handshake then moved + // into m_enc_handler if rc4 encryption is negotiated + // otherwise it is destroyed when the handshake completes + std::shared_ptr m_rc4; + + // if encryption is negotiated, this is used for + // encryption/decryption during the entire session. + encryption_handler m_enc_handler; + + // (outgoing only) synchronize verification constant with + // remote peer, this will hold rc4_decrypt(vc). Destroyed + // after the sync step. + std::unique_ptr m_sync_vc; + + // (incoming only) synchronize hash with remote peer, holds + // the sync hash (hash("req1",secret)). Destroyed after the + // sync step. + std::unique_ptr m_sync_hash; + + // used to disconnect peer if sync points are not found within + // the maximum number of bytes + int m_sync_bytes_read = 0; +#endif + + // the message ID for upload only message + // 0 if not supported + std::uint8_t m_upload_only_id = 0; + + // the message ID for holepunch messages + std::uint8_t m_holepunch_id = 0; + + // the message ID for don't-have message + std::uint8_t m_dont_have_id = 0; + + // the message ID for share mode message + // 0 if not supported + std::uint8_t m_share_mode_id = 0; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::weak_ptr m_ut_pex; +#endif + + std::array m_reserved_bits; + }; +} + +#endif // TORRENT_BT_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/buffer.hpp b/include/libtorrent/buffer.hpp new file mode 100644 index 0000000..785e7cf --- /dev/null +++ b/include/libtorrent/buffer.hpp @@ -0,0 +1,165 @@ +/* +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_BUFFER_HPP_INCLUDED +#define TORRENT_BUFFER_HPP_INCLUDED + +#include +#include // for numeric_limits +#include // malloc/free/realloc +#include // for std::swap +#include + +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/throw.hpp" + +#if defined __GLIBC__ +#include +#elif defined _MSC_VER +#include +#elif defined __FreeBSD__ +#include +#elif defined TORRENT_BSD +#include +#endif + +namespace libtorrent { + +// the buffer is allocated once and cannot be resized. The size() may be +// larger than requested, in case the underlying allocator over allocated. In +// order to "grow" an allocation, create a new buffer and initialize it by +// the range of bytes from the existing, and move-assign the new over the +// old. +class buffer +{ +public: + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + // allocate an uninitialized buffer of the specified size + explicit buffer(difference_type size = 0) + { + TORRENT_ASSERT(size < (std::numeric_limits::max)()); + + if (size == 0) return; + + // this rounds up the size to be 8 bytes aligned + // it mostly makes sense for platforms without support + // for a variation of "malloc_size()" + size = (size + 7) & (~difference_type(0x7)); + + // we have to use malloc here, to be compatible with the fancy query + // functions below + m_begin = static_cast(std::malloc(static_cast(size))); + if (m_begin == nullptr) aux::throw_ex(); + + // the actual allocation may be larger than we requested. If so, let the + // user take advantage of every single byte +#if (defined __GLIBC__ && !defined __UCLIBC__) || defined __FreeBSD__ + m_size = static_cast(::malloc_usable_size(m_begin)); +#elif defined _MSC_VER + m_size = static_cast(::_msize(m_begin)); +#elif defined TORRENT_BSD + m_size = static_cast(::malloc_size(m_begin)); +#else + m_size = size; +#endif + } + + // allocate an uninitialized buffer of the specified size + // and copy the initialization range into the start of the buffer + buffer(difference_type const size, span initialize) + : buffer(size) + { + TORRENT_ASSERT(initialize.size() <= size); + if (!initialize.empty()) + { + std::copy(initialize.begin(), initialize.begin() + + (std::min)(initialize.size(), size), m_begin); + } + } + + buffer(buffer const& b) = delete; + + buffer(buffer&& b) + : m_begin(b.m_begin) + , m_size(b.m_size) + { + b.m_begin = nullptr; + b.m_size = 0; + } + + buffer& operator=(buffer&& b) + { + if (&b == this) return *this; + std::free(m_begin); + m_begin = b.m_begin; + m_size = b.m_size; + b.m_begin = nullptr; + b.m_size = 0; + return *this; + } + + buffer& operator=(buffer const& b) = delete; + + ~buffer() { std::free(m_begin); } + + char* data() { return m_begin; } + char const* data() const { return m_begin; } + difference_type size() const { return m_size; } + + bool empty() const { return m_size == 0; } + char& operator[](index_type const i) { TORRENT_ASSERT(i < size()); return m_begin[i]; } + char const& operator[](difference_type const i) const { TORRENT_ASSERT(i < size()); return m_begin[i]; } + + char* begin() { return m_begin; } + char const* begin() const { return m_begin; } + char* end() { return m_begin + m_size; } + char const* end() const { return m_begin + m_size; } + + void swap(buffer& b) + { + using std::swap; + swap(m_begin, b.m_begin); + swap(m_size, b.m_size); + } + +private: + char* m_begin = nullptr; + // m_begin points to an allocation of this size. + difference_type m_size = 0; +}; + +} + +#endif // TORRENT_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/chained_buffer.hpp b/include/libtorrent/chained_buffer.hpp new file mode 100644 index 0000000..24ea1c7 --- /dev/null +++ b/include/libtorrent/chained_buffer.hpp @@ -0,0 +1,233 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CHAINED_BUFFER_HPP_INCLUDED +#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/block_cache_reference.hpp" +#include "libtorrent/aux_/aligned_storage.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/buffer.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef _MSC_VER +// visual studio requires the value in a deque to be copyable. C++11 +// has looser requirements depending on which functions are actually used. +#define TORRENT_CPP98_DEQUE 1 +#else +#define TORRENT_CPP98_DEQUE 0 +#endif + +namespace libtorrent { + + // TODO: 2 this type should probably be renamed to send_buffer + struct TORRENT_EXTRA_EXPORT chained_buffer : private single_threaded + { + chained_buffer(): m_bytes(0), m_capacity(0) + { + thread_started(); +#if TORRENT_USE_ASSERTS + m_destructed = false; +#endif + } + + private: + + // destructs/frees the holder object + using destruct_holder_fun = void (*)(void*); + using move_construct_holder_fun = void (*)(void*, void*); + + struct buffer_t + { + buffer_t() {} +#if TORRENT_CPP98_DEQUE + buffer_t(buffer_t&& rhs) noexcept + { + destruct_holder = rhs.destruct_holder; + move_holder = rhs.move_holder; + buf = rhs.buf; + size = rhs.size; + used_size = rhs.used_size; + move_holder(&holder, &rhs.holder); + } + buffer_t& operator=(buffer_t&& rhs) & noexcept + { + destruct_holder(&holder); + destruct_holder = rhs.destruct_holder; + move_holder = rhs.move_holder; + buf = rhs.buf; + size = rhs.size; + used_size = rhs.used_size; + move_holder(&holder, &rhs.holder); + return *this; + } + buffer_t(buffer_t const& rhs) noexcept + : buffer_t(std::move(const_cast(rhs))) {} + buffer_t& operator=(buffer_t const& rhs) & noexcept + { return this->operator=(std::move(const_cast(rhs))); } +#else + buffer_t(buffer_t&&) = delete; + buffer_t& operator=(buffer_t&&) = delete; + buffer_t(buffer_t const&) = delete; + buffer_t& operator=(buffer_t const&) = delete; +#endif + + destruct_holder_fun destruct_holder; +#if TORRENT_CPP98_DEQUE + move_construct_holder_fun move_holder; +#endif + aux::aligned_storage<32>::type holder; + char* buf = nullptr; // the first byte of the buffer + int size = 0; // the total size of the buffer + int used_size = 0; // this is the number of bytes to send/receive + }; + + public: + + bool empty() const { return m_bytes == 0; } + int size() const { return m_bytes; } + int capacity() const { return m_capacity; } + + void pop_front(int bytes_to_pop); + + template + void append_buffer(Holder buffer, int used_size) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(int(buffer.size()) >= used_size); + m_vec.emplace_back(); + buffer_t& b = m_vec.back(); + init_buffer_entry(b, std::move(buffer), used_size); + } + + template + void prepend_buffer(Holder buffer, int used_size) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(int(buffer.size()) >= used_size); + m_vec.emplace_front(); + buffer_t& b = m_vec.front(); + init_buffer_entry(b, std::move(buffer), used_size); + } + + // returns the number of bytes available at the + // end of the last chained buffer. + int space_in_last_buffer(); + + // tries to copy the given buffer to the end of the + // last chained buffer. If there's not enough room + // it returns nullptr + char* append(span buf); + + // tries to allocate memory from the end + // of the last buffer. If there isn't + // enough room, returns 0 + char* allocate_appendix(int s); + + span build_iovec(int to_send); + + void clear(); + + void build_mutable_iovec(int bytes, std::vector>& vec); + + ~chained_buffer(); + + private: + + template + void init_buffer_entry(buffer_t& b, Holder buf, int used_size) + { + static_assert(sizeof(Holder) <= sizeof(b.holder), "buffer holder too large"); + + b.buf = buf.data(); + b.size = static_cast(buf.size()); + b.used_size = used_size; + +#ifdef _MSC_VER +// this appears to be a false positive msvc warning +#pragma warning(push, 1) +#pragma warning(disable : 4100) +#endif + b.destruct_holder = [](void* holder) + { reinterpret_cast(holder)->~Holder(); }; + +#if TORRENT_CPP98_DEQUE + b.move_holder = [](void* dst, void* src) + { new (dst) Holder(std::move(*reinterpret_cast(src))); }; +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + new (&b.holder) Holder(std::move(buf)); + + m_bytes += used_size; + TORRENT_ASSERT(m_capacity < (std::numeric_limits::max)() - b.size); + m_capacity += b.size; + TORRENT_ASSERT(m_bytes <= m_capacity); + } + + template + void build_vec(int bytes, std::vector& vec); + + // this is the list of all the buffers we want to + // send + std::deque m_vec; + + // this is the number of bytes in the send buf. + // this will always be equal to the sum of the + // size of all buffers in vec + int m_bytes; + + // the total size of all buffers in the chain + // including unused space + int m_capacity; + + // this is the vector of buffers used when + // invoking the async write call + std::vector m_tmp_vec; + +#if TORRENT_USE_ASSERTS + bool m_destructed; +#endif + }; +} + +#endif diff --git a/include/libtorrent/choker.hpp b/include/libtorrent/choker.hpp new file mode 100644 index 0000000..76b651a --- /dev/null +++ b/include/libtorrent/choker.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CHOKER_HPP_INCLUDED +#define TORRENT_CHOKER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include + +namespace libtorrent { + + namespace aux { struct session_settings; } + class peer_connection; + + // sorts the vector of peers in-place. When returning, the top unchoke slots + // elements are the peers we should unchoke. This is similar to a partial + // sort. Only the unchoke slots first elements are sorted. + // the return value are the number of peers that should be unchoked. This + // is also the number of elements that are valid at the beginning of the + // peer list. Peers beyond this initial range are not sorted. + TORRENT_EXTRA_EXPORT int unchoke_sort(std::vector& peers + , int max_upload_rate, time_duration unchoke_interval + , aux::session_settings const& sett); + +} + +#endif // TORRENT_CHOKER_INCLUDED diff --git a/include/libtorrent/close_reason.hpp b/include/libtorrent/close_reason.hpp new file mode 100644 index 0000000..a991f8a --- /dev/null +++ b/include/libtorrent/close_reason.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CLOSE_REASON_HPP +#define TORRENT_CLOSE_REASON_HPP + +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + + // internal: these are all the reasons to disconnect a peer + // all reasons caused by the peer sending unexpected data + // are 256 and up. + enum class close_reason_t : std::uint16_t + { + // no reason specified. Generic close. + none = 0, + + // we're already connected to + duplicate_peer_id, + + // this torrent has been removed, paused or stopped from this client. + torrent_removed, + + // client failed to allocate necessary memory for this peer connection + no_memory, + + // the source port of this peer is blocked + port_blocked, + + // the source IP has been blocked + blocked, + + // both ends of the connection are upload-only. staying connected would + // be redundant + upload_to_upload, + + // connection was closed because the other end is upload only and does + // not have any pieces we're interested in + not_interested_upload_only, + + // peer connection timed out (generic timeout) + timeout, + + // the peers have not been interested in each other for a very long time. + // disconnect + timed_out_interest, + + // the peer has not sent any message in a long time. + timed_out_activity, + + // the peer did not complete the handshake in too long + timed_out_handshake, + + // the peer sent an interested message, but did not send a request + // after a very long time after being unchoked. + timed_out_request, + + // the encryption mode is blocked + protocol_blocked, + + // the peer was disconnected in the hopes of finding a better peer + // in the swarm + peer_churn, + + // we have too many peers connected + too_many_connections, + + // we have too many file-descriptors open + too_many_files, + + // the encryption handshake failed + encryption_error = 256, + + // the info hash sent as part of the handshake was not what we expected + invalid_info_hash, + + self_connection, + + // the metadata received matched the info-hash, but failed to parse. + // this is either someone finding a SHA1 collision, or the author of + // the magnet link creating it from an invalid torrent + invalid_metadata, + + // the advertised metadata size + metadata_too_big, + + // invalid bittorrent messages + message_too_big, + invalid_message_id, + invalid_message, + invalid_piece_message, + invalid_have_message, + invalid_bitfield_message, + invalid_choke_message, + invalid_unchoke_message, + invalid_interested_message, + invalid_not_interested_message, + invalid_request_message, + invalid_reject_message, + invalid_allow_fast_message, + invalid_extended_message, + invalid_cancel_message, + invalid_dht_port_message, + invalid_suggest_message, + invalid_have_all_message, + invalid_dont_have_message, + invalid_have_none_message, + invalid_pex_message, + invalid_metadata_request_message, + invalid_metadata_message, + invalid_metadata_offset, + + // the peer sent a request while being choked + request_when_choked, + + // the peer sent corrupt data + corrupt_pieces, + + pex_message_too_big, + pex_too_frequent + }; + + close_reason_t error_to_close_reason(error_code const& ec); +} + +#endif diff --git a/include/libtorrent/config.hpp b/include/libtorrent/config.hpp new file mode 100644 index 0000000..6fbe0de --- /dev/null +++ b/include/libtorrent/config.hpp @@ -0,0 +1,578 @@ +/* + +Copyright (c) 2005-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CONFIG_HPP_INCLUDED +#define TORRENT_CONFIG_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#define _FILE_OFFSET_BITS 64 + +#include +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// TODO: don't include that here. Make each header that use the export macros +// include it instead. and move it to aux_ +#include "libtorrent/aux_/export.hpp" + +#ifdef __linux__ +#include // for LINUX_VERSION_CODE and KERNEL_VERSION +#endif // __linux + +#if defined __MINGW64__ || defined __MINGW32__ +// GCC warns on format codes that are incompatible with glibc, which the windows +// format codes are. So we need to disable those for mingw targets +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +#endif + +#if defined __GNUC__ + +#ifdef _GLIBCXX_CONCEPT_CHECKS +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 +#endif + +// ======= SUNPRO ========= + +#elif defined __SUNPRO_CC + +#define TORRENT_COMPLETE_TYPES_REQUIRED 1 + +// ======= MSVC ========= + +#elif defined BOOST_MSVC + +// class X needs to have dll-interface to be used by clients of class Y +#pragma warning(disable:4251) + +// auto and decltype(auto) return types supports since MSVS2015 +// https://msdn.microsoft.com/en-us/library/hh567368.aspx +// we need to force C++14 feature due VS2017 inability to parse C++11 syntax +#if defined(_MSC_VER) && (_MSC_VER > 1900) +#define TORRENT_AUTO_RETURN_TYPES 1 +#endif +#endif + + +// ======= PLATFORMS ========= + + +// set up defines for target environments +// ==== AMIGA === +#if defined __AMIGA__ || defined __amigaos__ || defined __AROS__ +#define TORRENT_AMIGA +#define TORRENT_USE_IOSTREAM 0 +// set this to 1 to disable all floating point operations +// (disables some float-dependent APIs) +#define TORRENT_NO_FPU 1 +#define TORRENT_USE_I2P 0 +#ifndef TORRENT_USE_ICONV +#define TORRENT_USE_ICONV 0 +#endif + +// ==== Darwin/BSD === +#elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__ || defined __NetBSD__ \ + || defined __OpenBSD__ || defined __bsdi__ || defined __DragonFly__ \ + || defined __FreeBSD_kernel__ +#define TORRENT_BSD +// we don't need iconv on mac, because +// the locale is always utf-8 +#if defined __APPLE__ + +# ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 0 +# endif +#include +#include + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 +// on OSX, use the built-in common crypto for built-in +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT +# define TORRENT_USE_COMMONCRYPTO 1 +# endif // TORRENT_USE_OPENSSL +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + +// execinfo.h is available in the MacOS X 10.5 SDK. +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +#define TORRENT_USE_EXECINFO 1 +#endif + +#define TORRENT_USE_SYSTEMCONFIGURATION 1 + +#if TARGET_OS_IPHONE +#define TORRENT_USE_SC_NETWORK_REACHABILITY 1 +#endif +#endif // __APPLE__ + +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_USE_DEV_RANDOM 1 +#define TORRENT_HAVE_MMAP 1 + +#define TORRENT_HAS_FALLOCATE 0 + +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_SYSCTL 1 +#define TORRENT_USE_IFCONF 1 + + +// ==== LINUX === +#elif defined __linux__ +#define TORRENT_LINUX + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) && !defined __ANDROID__ +# define TORRENT_USE_PREADV 1 +# define TORRENT_USE_PREAD 0 +#else +# define TORRENT_USE_PREADV 0 +# define TORRENT_USE_PREAD 1 +#endif + +#define TORRENT_HAS_SYMLINK 1 +#define TORRENT_HAVE_MMAP 1 +#define TORRENT_USE_NETLINK 1 +#define TORRENT_USE_IFADDRS 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_FDATASYNC 1 + +// ===== ANDROID ===== (almost linux, sort of) +#if defined __ANDROID__ +#define TORRENT_ANDROID +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_ICONV 0 +#else // ANDROID + +// posix_fallocate() is not available in glibc under these condition +#if defined _XOPEN_SOURCE && _XOPEN_SOURCE < 600 +#define TORRENT_HAS_FALLOCATE 0 +#elif defined _POSIX_C_SOURCE && _POSIX_C_SOURCE < 200112L +#define TORRENT_HAS_FALLOCATE 0 +#endif + +#endif // ANDROID + +#if defined __GLIBC__ && ( defined __x86_64__ || defined __i386 \ + || defined _M_X64 || defined _M_IX86 ) +#define TORRENT_USE_EXECINFO 1 +#endif + +// ==== MINGW === +#elif defined __MINGW32__ || defined __MINGW64__ +#define TORRENT_MINGW +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 1 +#endif +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_USE_NETLINK 0 +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_USE_GETIPFORWARDTABLE 1 +#define TORRENT_USE_UNC_PATHS 1 +// these are emulated on windows +#define TORRENT_USE_PREADV 1 +#define TORRENT_USE_PWRITEV 1 + +// mingw doesn't implement random_device. +#define TORRENT_BROKEN_RANDOM_DEVICE 1 + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION > NTDDI_WINXPSP2) +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#endif + +#endif +// ==== WINDOWS === +#elif defined _WIN32 +#define TORRENT_WINDOWS +#ifndef TORRENT_USE_GETIPFORWARDTABLE +# define TORRENT_USE_GETIPFORWARDTABLE 1 +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +# if !defined TORRENT_USE_LIBCRYPTO && !defined TORRENT_USE_LIBGCRYPT +// unless some other crypto library has been specified, default to the native +// windows CryptoAPI +#define TORRENT_USE_CRYPTOAPI 1 + +#ifdef NTDDI_VERSION +# if (NTDDI_VERSION > NTDDI_WINXPSP2) +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#else // NTDDI_VERSION not defined so use simple _WIN32_WINNT check +# if _WIN32_WINNT >= 0x0600 +# define TORRENT_USE_CRYPTOAPI_SHA_512 1 +# endif +#endif + +#endif + +#define TORRENT_USE_GETADAPTERSADDRESSES 1 +#define TORRENT_HAS_SALEN 0 +// windows has its own functions to convert +#ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 1 +#endif +#define TORRENT_USE_RLIMIT 0 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_UNC_PATHS 1 +// these are emulated on windows +#define TORRENT_USE_PREADV 1 +#define TORRENT_USE_PWRITEV 1 + +// ==== WINRT === +#if defined(WINAPI_FAMILY_PARTITION) +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) \ + && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define TORRENT_WINRT +# endif +#endif + +// ==== SOLARIS === +#elif defined sun || defined __sun +#define TORRENT_SOLARIS +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SALEN 0 +#define TORRENT_HAVE_MMAP 1 +#define TORRENT_HAS_SYMLINK 1 + +// ==== BEOS === +#elif defined __BEOS__ || defined __HAIKU__ +#define TORRENT_BEOS +#include // B_PATH_NAME_LENGTH +#define TORRENT_HAS_FALLOCATE 0 +#ifndef TORRENT_USE_ICONV +#define TORRENT_USE_ICONV 0 +#endif + +// ==== GNU/Hurd === +#elif defined __GNU__ +#define TORRENT_HURD +#define TORRENT_USE_IFADDRS 1 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_HAS_SYMLINK 1 + +// ==== eCS(OS/2) === +#elif defined __OS2__ +#define TORRENT_OS2 +#define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_IFCONF 1 +#define TORRENT_USE_SYSCTL 1 +#define TORRENT_USE_WRITEV 0 +#define TORRENT_USE_READV 0 + +#else + +#ifdef _MSC_VER +#pragma message ( "unknown OS, assuming BSD" ) +#else +#warning "unknown OS, assuming BSD" +#endif + +#define TORRENT_BSD +#endif + +#define TORRENT_UNUSED(x) (void)(x) + +#if defined __GNUC__ || defined __clang__ +#define TORRENT_FORMAT(fmt, ellipsis) __attribute__((__format__(__printf__, fmt, ellipsis))) +#else +#define TORRENT_FORMAT(fmt, ellipsis) +#endif + +#ifndef TORRENT_BROKEN_RANDOM_DEVICE +#define TORRENT_BROKEN_RANDOM_DEVICE 0 +#endif + +// libiconv presence detection is not implemented yet +#ifndef TORRENT_USE_ICONV +#define TORRENT_USE_ICONV 1 +#endif + +#ifndef TORRENT_HAS_SALEN +#define TORRENT_HAS_SALEN 1 +#endif + +#ifndef TORRENT_USE_GETADAPTERSADDRESSES +#define TORRENT_USE_GETADAPTERSADDRESSES 0 +#endif + +#ifndef TORRENT_USE_NETLINK +#define TORRENT_USE_NETLINK 0 +#endif + +#ifndef TORRENT_USE_EXECINFO +#define TORRENT_USE_EXECINFO 0 +#endif + +#ifndef TORRENT_USE_SYSCTL +#define TORRENT_USE_SYSCTL 0 +#endif + +#ifndef TORRENT_USE_GETIPFORWARDTABLE +#define TORRENT_USE_GETIPFORWARDTABLE 0 +#endif + +#ifndef TORRENT_USE_LOCALE +#define TORRENT_USE_LOCALE 0 +#endif + +#if defined BOOST_NO_STD_WSTRING +#error your C++ standard library appears to be missing std::wstring. This type is required on windows +#endif + +#ifndef TORRENT_HAS_FALLOCATE +#define TORRENT_HAS_FALLOCATE 1 +#endif + +#ifndef TORRENT_USE_COMMONCRYPTO +#define TORRENT_USE_COMMONCRYPTO 0 +#endif + +#ifndef TORRENT_USE_SYSTEMCONFIGURATION +#define TORRENT_USE_SYSTEMCONFIGURATION 0 +#endif + +#ifndef TORRENT_USE_SC_NETWORK_REACHABILITY +#define TORRENT_USE_SC_NETWORK_REACHABILITY 0 +#endif + +#ifndef TORRENT_USE_CRYPTOAPI +#define TORRENT_USE_CRYPTOAPI 0 +#endif + +#ifndef TORRENT_USE_CRYPTOAPI_SHA_512 +#define TORRENT_USE_CRYPTOAPI_SHA_512 0 +#endif + +#ifndef TORRENT_USE_DEV_RANDOM +#define TORRENT_USE_DEV_RANDOM 0 +#endif + +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + +#ifndef TORRENT_COMPLETE_TYPES_REQUIRED +#define TORRENT_COMPLETE_TYPES_REQUIRED 0 +#endif + +#ifndef TORRENT_USE_FDATASYNC +#define TORRENT_USE_FDATASYNC 0 +#endif + +#ifndef TORRENT_USE_UNC_PATHS +#define TORRENT_USE_UNC_PATHS 0 +#endif + +#ifndef TORRENT_USE_RLIMIT +#define TORRENT_USE_RLIMIT 1 +#endif + +#ifndef TORRENT_USE_IFADDRS +#define TORRENT_USE_IFADDRS 0 +#endif + +// if preadv() exists, we assume pwritev() does as well +#ifndef TORRENT_USE_PREADV +#define TORRENT_USE_PREADV 0 +#endif + +// if pread() exists, we assume pwrite() does as well +#ifndef TORRENT_USE_PREAD +#define TORRENT_USE_PREAD 1 +#endif + +#ifndef TORRENT_NO_FPU +#define TORRENT_NO_FPU 0 +#endif + +#ifndef TORRENT_USE_IOSTREAM +#ifndef BOOST_NO_IOSTREAM +#define TORRENT_USE_IOSTREAM 1 +#else +#define TORRENT_USE_IOSTREAM 0 +#endif +#endif + +#ifndef TORRENT_USE_I2P +#define TORRENT_USE_I2P 1 +#endif + +#ifndef TORRENT_AUTO_RETURN_TYPES +#define TORRENT_AUTO_RETURN_TYPES 0 +#endif + +#if !defined(TORRENT_READ_HANDLER_MAX_SIZE) +# if defined _GLIBCXX_DEBUG || !defined NDEBUG +// internal +constexpr std::size_t TORRENT_READ_HANDLER_MAX_SIZE = 432; +# else +// internal +// if this is not divisible by 8, we're wasting space +constexpr std::size_t TORRENT_READ_HANDLER_MAX_SIZE = 342; +# endif +#endif + +#if !defined(TORRENT_WRITE_HANDLER_MAX_SIZE) +# if defined _GLIBCXX_DEBUG || !defined NDEBUG +// internal +constexpr std::size_t TORRENT_WRITE_HANDLER_MAX_SIZE = 432; +# else +// internal +// if this is not divisible by 8, we're wasting space +constexpr std::size_t TORRENT_WRITE_HANDLER_MAX_SIZE = 342; +# endif +#endif + +#ifndef TORRENT_HAS_SYMLINK +#define TORRENT_HAS_SYMLINK 0 +#endif + +// debug builds have asserts enabled by default, release +// builds have asserts if they are explicitly enabled by +// the release_asserts macro. +#ifndef TORRENT_USE_ASSERTS +#define TORRENT_USE_ASSERTS 0 +#endif // TORRENT_USE_ASSERTS + +#ifndef TORRENT_USE_INVARIANT_CHECKS +#define TORRENT_USE_INVARIANT_CHECKS 0 +#endif + +#if TORRENT_USE_INVARIANT_CHECKS && !TORRENT_USE_ASSERTS +#error "invariant checks cannot be enabled without asserts" +#endif + +// for non-exception builds +#ifdef BOOST_NO_EXCEPTIONS +#define TORRENT_TRY if (true) +#define TORRENT_CATCH(x) else if (false) +#define TORRENT_CATCH_ALL else if (false) +#define TORRENT_DECLARE_DUMMY(x, y) x y +#else +#define TORRENT_TRY try +#define TORRENT_CATCH(x) catch(x) +#define TORRENT_CATCH_ALL catch(...) +#define TORRENT_DECLARE_DUMMY(x, y) +#endif // BOOST_NO_EXCEPTIONS + +// SSE is x86 / amd64 specific. On top of that, we only +// know how to access it on msvc and gcc (and gcc compatibles). +// GCC requires the user to enable SSE support in order for +// the program to have access to the intrinsics, this is +// indicated by the __SSE4_1__ macro +#ifndef TORRENT_HAS_SSE + +#if (defined _M_AMD64 || defined _M_IX86 || defined _M_X64 \ + || defined __amd64__ || defined __i386 || defined __i386__ \ + || defined __x86_64__ || defined __x86_64) \ + && (defined __GNUC__ || (defined _MSC_VER && _MSC_VER >= 1600)) +#define TORRENT_HAS_SSE 1 +#else +#define TORRENT_HAS_SSE 0 +#endif + +#endif // TORRENT_HAS_SSE + +#if (defined __arm__ || defined __aarch64__ || defined _M_ARM || defined _M_ARM64) +#define TORRENT_HAS_ARM 1 +#else +#define TORRENT_HAS_ARM 0 +#endif // TORRENT_HAS_ARM + +#ifndef __has_builtin +#define __has_builtin(x) 0 // for non-clang compilers +#endif + +#if (TORRENT_HAS_SSE && defined __GNUC__) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#elif (TORRENT_HAS_ARM && defined __GNUC__ && !defined __clang__) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#elif (defined __clang__ && __has_builtin(__builtin_clz)) +# define TORRENT_HAS_BUILTIN_CLZ 1 +#else +# define TORRENT_HAS_BUILTIN_CLZ 0 +#endif // TORRENT_HAS_BUILTIN_CLZ + +#if (TORRENT_HAS_SSE && defined __GNUC__) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#elif (TORRENT_HAS_ARM && defined __GNUC__ && !defined __clang__) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#elif (defined __clang__ && __has_builtin(__builtin_ctz)) +# define TORRENT_HAS_BUILTIN_CTZ 1 +#else +# define TORRENT_HAS_BUILTIN_CTZ 0 +#endif // TORRENT_HAS_BUILTIN_CTZ + +#if TORRENT_HAS_ARM && defined __ARM_NEON +# define TORRENT_HAS_ARM_NEON 1 +#else +# define TORRENT_HAS_ARM_NEON 0 +#endif // TORRENT_HAS_ARM_NEON + +#if TORRENT_HAS_ARM && defined __ARM_FEATURE_CRC32 +# define TORRENT_HAS_ARM_CRC32 1 +#else +#if defined TORRENT_FORCE_ARM_CRC32 +# define TORRENT_HAS_ARM_CRC32 1 +#else +# define TORRENT_HAS_ARM_CRC32 0 +#endif +#endif // TORRENT_HAS_ARM_CRC32 + +namespace libtorrent {} + +// create alias +namespace lt = libtorrent; + +#endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/include/libtorrent/copy_ptr.hpp b/include/libtorrent/copy_ptr.hpp new file mode 100644 index 0000000..e18f2f0 --- /dev/null +++ b/include/libtorrent/copy_ptr.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_COPY_PTR +#define TORRENT_COPY_PTR + +#include + +namespace libtorrent { + + template + struct copy_ptr + { + copy_ptr() = default; + explicit copy_ptr(T* t): m_ptr(t) {} + copy_ptr(copy_ptr const& p): m_ptr(p.m_ptr ? new T(*p.m_ptr) : nullptr) {} + copy_ptr(copy_ptr&& p) noexcept = default; + + void reset(T* t = nullptr) { m_ptr.reset(t); } + copy_ptr& operator=(copy_ptr const& p) & + { + if (m_ptr == p.m_ptr) return *this; + m_ptr.reset(p.m_ptr ? new T(*p.m_ptr) : nullptr); + return *this; + } + copy_ptr& operator=(copy_ptr&& p) & noexcept = default; + T* operator->() { return m_ptr.get(); } + T const* operator->() const { return m_ptr.get(); } + T& operator*() { return *m_ptr; } + T const& operator*() const { return *m_ptr; } + void swap(copy_ptr& p) { std::swap(*this, p); } + explicit operator bool() const { return m_ptr.get() != nullptr; } + private: + std::unique_ptr m_ptr; + }; +} + +#endif // TORRENT_COPY_PTR diff --git a/include/libtorrent/crc32c.hpp b/include/libtorrent/crc32c.hpp new file mode 100644 index 0000000..20a365a --- /dev/null +++ b/include/libtorrent/crc32c.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CRC32C_HPP_INCLUDE +#define TORRENT_CRC32C_HPP_INCLUDE + +#include +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // this is the crc32c (Castagnoli) polynomial + TORRENT_EXTRA_EXPORT std::uint32_t crc32c_32(std::uint32_t v); + TORRENT_EXTRA_EXPORT std::uint32_t crc32c(std::uint64_t const* v + , int num_words); +} + +#endif diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp new file mode 100644 index 0000000..e39ded5 --- /dev/null +++ b/include/libtorrent/create_torrent.hpp @@ -0,0 +1,484 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_CREATE_TORRENT_HPP_INCLUDED +#define TORRENT_CREATE_TORRENT_HPP_INCLUDED + +#include "libtorrent/bencode.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path etc. + +#include +#include +#include + +// OVERVIEW +// +// This section describes the functions and classes that are used +// to create torrent files. It is a layered API with low level classes +// and higher level convenience functions. A torrent is created in 4 +// steps: +// +// 1. first the files that will be part of the torrent are determined. +// 2. the torrent properties are set, such as tracker url, web seeds, +// DHT nodes etc. +// 3. Read through all the files in the torrent, SHA-1 all the data +// and set the piece hashes. +// 4. The torrent is bencoded into a file or buffer. +// +// If there are a lot of files and or deep directory hierarchies to +// traverse, step one can be time consuming. +// +// Typically step 3 is by far the most time consuming step, since it +// requires to read all the bytes from all the files in the torrent. +// +// All of these classes and functions are declared by including +// ``libtorrent/create_torrent.hpp``. +// +// example: +// +// .. code:: c++ +// +// file_storage fs; +// +// // recursively adds files in directories +// add_files(fs, "./my_torrent"); +// +// create_torrent t(fs); +// t.add_tracker("http://my.tracker.com/announce"); +// t.set_creator("libtorrent example"); +// +// // reads the files and calculates the hashes +// set_piece_hashes(t, "."); +// +// ofstream out("my_torrent.torrent", std::ios_base::binary); +// bencode(std::ostream_iterator(out), t.generate()); +// +namespace libtorrent { + + class torrent_info; + + // hidden + using create_flags_t = flags::bitfield_flag; + + // This class holds state for creating a torrent. After having added + // all information to it, call create_torrent::generate() to generate + // the torrent. The entry that's returned can then be bencoded into a + // .torrent file using bencode(). + struct TORRENT_EXPORT create_torrent + { +#if TORRENT_ABI_VERSION == 1 + using flags_t = create_flags_t; +#endif + // This will insert pad files to align the files to piece boundaries, for + // optimized disk-I/O. This will minimize the number of bytes of pad- + // files, to keep the impact down for clients that don't support + // them. + static constexpr create_flags_t optimize_alignment = 0_bit; +#if TORRENT_ABI_VERSION == 1 + // same as optimize_alignment, for backwards compatibility + static constexpr create_flags_t TORRENT_DEPRECATED_MEMBER optimize = 0_bit; +#endif + + // This will create a merkle hash tree torrent. A merkle torrent cannot + // be opened in clients that don't specifically support merkle torrents. + // The benefit is that the resulting torrent file will be much smaller and + // not grow with more pieces. When this option is specified, it is + // recommended to have a fairly small piece size, say 64 kiB. + // When creating merkle torrents, the full hash tree is also generated + // and should be saved off separately. It is accessed through the + // create_torrent::merkle_tree() function. + static constexpr create_flags_t merkle = 1_bit; + + // This will include the file modification time as part of the torrent. + // This is not enabled by default, as it might cause problems when you + // create a torrent from separate files with the same content, hoping to + // yield the same info-hash. If the files have different modification times, + // with this option enabled, you would get different info-hashes for the + // files. + static constexpr create_flags_t modification_time = 2_bit; + + // If this flag is set, files that are symlinks get a symlink attribute + // set on them and their data will not be included in the torrent. This + // is useful if you need to reconstruct a file hierarchy which contains + // symlinks. + static constexpr create_flags_t symlinks = 3_bit; + + // to create a torrent that can be updated via a *mutable torrent* + // (see `BEP 38`_). This also needs to be enabled for torrents that update + // another torrent. + // + // .. _`BEP 38`: http://www.bittorrent.org/beps/bep_0038.html + static constexpr create_flags_t mutable_torrent_support = 4_bit; + + // The ``piece_size`` is the size of each piece in bytes. It must + // be a multiple of 16 kiB. If a piece size of 0 is specified, a + // piece_size will be calculated such that the torrent file is roughly 40 kB. + // + // If a ``pad_file_limit`` is specified (other than -1), any file larger than + // the specified number of bytes will be preceded by a pad file to align it + // with the start of a piece. The pad_file_limit is ignored unless the + // ``optimize_alignment`` flag is passed. Typically it doesn't make sense + // to set this any lower than 4 kiB. + // + // The overload that takes a ``torrent_info`` object will make a verbatim + // copy of its info dictionary (to preserve the info-hash). The copy of + // the info dictionary will be used by create_torrent::generate(). This means + // that none of the member functions of create_torrent that affects + // the content of the info dictionary (such as ``set_hash()``), will + // have any affect. + // + // The ``flags`` arguments specifies options for the torrent creation. It can + // be any combination of the flags defined by create_flags_t. + // + // ``alignment`` is used when pad files are enabled. This is the size + // eligible files are aligned to. The default is -1, which means the + // piece size of the torrent. + explicit create_torrent(file_storage& fs, int piece_size = 0 + , int pad_file_limit = -1, create_flags_t flags = optimize_alignment + , int alignment = -1); + explicit create_torrent(torrent_info const& ti); + + // internal + ~create_torrent(); + + // This function will generate the .torrent file as a bencode tree. In order to + // generate the flat file, use the bencode() function. + // + // It may be useful to add custom entries to the torrent file before bencoding it + // and saving it to disk. + // + // If anything goes wrong during torrent generation, this function will return + // an empty ``entry`` structure. You can test for this condition by querying the + // type of the entry: + // + // .. code:: c++ + // + // file_storage fs; + // // add file ... + // create_torrent t(fs); + // // add trackers and piece hashes ... + // e = t.generate(); + // + // if (e.type() == entry::undefined_t) + // { + // // something went wrong + // } + // + // For instance, you cannot generate a torrent with 0 files in it. If you don't add + // any files to the ``file_storage``, torrent generation will fail. + entry generate() const; + + // returns an immutable reference to the file_storage used to create + // the torrent from. + file_storage const& files() const { return m_files; } + + // Sets the comment for the torrent. The string ``str`` should be utf-8 encoded. + // The comment in a torrent file is optional. + void set_comment(char const* str); + + // Sets the creator of the torrent. The string ``str`` should be utf-8 encoded. + // This is optional. + void set_creator(char const* str); + + // This sets the SHA-1 hash for the specified piece (``index``). You are required + // to set the hash for every piece in the torrent before generating it. If you have + // the files on disk, you can use the high level convenience function to do this. + // See set_piece_hashes(). + void set_hash(piece_index_t index, sha1_hash const& h); + + // This sets the sha1 hash for this file. This hash will end up under the key ``sha1`` + // associated with this file (for multi-file torrents) or in the root info dictionary + // for single-file torrents. + void set_file_hash(file_index_t index, sha1_hash const& h); + + // This adds a url seed to the torrent. You can have any number of url seeds. For a + // single file torrent, this should be an HTTP url, pointing to a file with identical + // content as the file of the torrent. For a multi-file torrent, it should point to + // a directory containing a directory with the same name as this torrent, and all the + // files of the torrent in it. + // + // The second function, ``add_http_seed()`` adds an HTTP seed instead. + void add_url_seed(string_view url); + void add_http_seed(string_view url); + + // This adds a DHT node to the torrent. This especially useful if you're creating a + // tracker less torrent. It can be used by clients to bootstrap their DHT node from. + // The node is a hostname and a port number where there is a DHT node running. + // You can have any number of DHT nodes in a torrent. + void add_node(std::pair node); + + // Adds a tracker to the torrent. This is not strictly required, but most torrents + // use a tracker as their main source of peers. The url should be an http:// or udp:// + // url to a machine running a bittorrent tracker that accepts announces for this torrent's + // info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are + // tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those + // fail, trackers with tier 2 are tried, and so on. + void add_tracker(string_view url, int tier = 0); + + // This function sets an X.509 certificate in PEM format to the torrent. This makes the + // torrent an *SSL torrent*. An SSL torrent requires that each peer has a valid certificate + // signed by this root certificate. For SSL torrents, all peers are connecting over SSL + // connections. For more information, see the section on ssl-torrents_. + // + // The string is not the path to the cert, it's the actual content of the + // certificate. + void set_root_cert(string_view pem); + + // Sets and queries the private flag of the torrent. + // Torrents with the private flag set ask the client to not use any other + // sources than the tracker for peers, and to not use DHT to advertise itself publicly, + // only the tracker. + void set_priv(bool p) { m_private = p; } + bool priv() const { return m_private; } + + // returns the number of pieces in the associated file_storage object. + int num_pieces() const { return m_files.num_pieces(); } + + // ``piece_length()`` returns the piece size of all pieces but the + // last one. ``piece_size()`` returns the size of the specified piece. + // these functions are just forwarding to the associated file_storage. + int piece_length() const { return m_files.piece_length(); } + int piece_size(piece_index_t i) const { return m_files.piece_size(i); } + + // This function returns the merkle hash tree, if the torrent was created as a merkle + // torrent. The tree is created by ``generate()`` and won't be valid until that function + // has been called. When creating a merkle tree torrent, the actual tree itself has to + // be saved off separately and fed into libtorrent the first time you start seeding it, + // through the ``torrent_info::set_merkle_tree()`` function. From that point onwards, the + // tree will be saved in the resume data. + std::vector const& merkle_tree() const { return m_merkle_tree; } + + // Add similar torrents (by info-hash) or collections of similar torrents. + // Similar torrents are expected to share some files with this torrent. + // Torrents sharing a collection name with this torrent are also expected + // to share files with this torrent. A torrent may have more than one + // collection and more than one similar torrents. For more information, + // see `BEP 38`_. + // + // .. _`BEP 38`: http://www.bittorrent.org/beps/bep_0038.html + void add_similar_torrent(sha1_hash ih); + void add_collection(string_view c); + + private: + + file_storage& m_files; + // if m_info_dict is initialized, it is + // used instead of m_files to generate + // the info dictionary + entry m_info_dict; + + // the URLs to the trackers + std::vector> m_urls; + + std::vector m_url_seeds; + std::vector m_http_seeds; + + aux::vector m_piece_hash; + + aux::vector m_filehashes; + + std::vector m_similar; + std::vector m_collections; + + // if we're generating a merkle torrent, this is the + // merkle tree we got. This should be saved in fast-resume + // in order to start seeding the torrent + mutable aux::vector m_merkle_tree; + + // dht nodes to add to the routing table/bootstrap from + std::vector> m_nodes; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + time_t m_creation_date; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // this is the root cert for SSL torrents + std::string m_root_cert; + + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multi-file torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + bool m_multifile:1; + + // this is true if the torrent is private. i.e., the client should not + // advertise itself on the DHT for this torrent + bool m_private:1; + + // if set to one, a merkle torrent will be generated + bool m_merkle_torrent:1; + + // if set, include the 'mtime' modification time in the + // torrent file + bool m_include_mtime:1; + + // if set, symbolic links are declared as such in + // the torrent file. The full data of the pointed-to + // file is still included + bool m_include_symlinks:1; + }; + +namespace detail { + inline void nop(piece_index_t) {} +} + + // Adds the file specified by ``path`` to the file_storage object. In case ``path`` + // refers to a directory, files will be added recursively from the directory. + // + // If specified, the predicate ``p`` is called once for every file and directory that + // is encountered. Files for which ``p`` returns true are added, and directories for + // which ``p`` returns true are traversed. ``p`` must have the following signature: + // + // .. code:: c++ + // + // bool Pred(std::string const& p); + // + // The path that is passed in to the predicate is the full path of the file or + // directory. If no predicate is specified, all files are added, and all directories + // are traversed. + // + // The ".." directory is never traversed. + // + // The ``flags`` argument should be the same as the flags passed to the `create_torrent`_ + // constructor. + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , std::function p, create_flags_t flags = {}); + TORRENT_EXPORT void add_files(file_storage& fs, std::string const& file + , create_flags_t flags = {}); + + // This function will assume that the files added to the torrent file exists at path + // ``p``, read those files and hash the content and set the hashes in the ``create_torrent`` + // object. The optional function ``f`` is called in between every hash that is set. ``f`` + // must have the following signature: + // + // .. code:: c++ + // + // void Fun(piece_index_t); + // + // The overloads that don't take an ``error_code&`` may throw an exception in case of a + // file error, the other overloads sets the error code to reflect the error, if any. + TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f, error_code& ec); + inline void set_piece_hashes(create_torrent& t, std::string const& p, error_code& ec) + { + set_piece_hashes(t, p, detail::nop, ec); + } +#ifndef BOOST_NO_EXCEPTIONS + inline void set_piece_hashes(create_torrent& t, std::string const& p) + { + error_code ec; + set_piece_hashes(t, p, detail::nop, ec); + if (ec) throw system_error(ec); + } + inline void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f) + { + error_code ec; + set_piece_hashes(t, p, f, ec); + if (ec) throw system_error(ec); + } +#endif + +#if TORRENT_ABI_VERSION == 1 +#if defined TORRENT_WINDOWS + + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED_EXPORT + void add_files(file_storage& fs, std::wstring const& wfile + , std::function p, create_flags_t flags = {}); + + TORRENT_DEPRECATED_EXPORT + void add_files(file_storage& fs, std::wstring const& wfile + , create_flags_t flags = {}); + + TORRENT_DEPRECATED_EXPORT + void set_piece_hashes(create_torrent& t, std::wstring const& p + , std::function f, error_code& ec); + + TORRENT_EXPORT void set_piece_hashes_deprecated(create_torrent& t + , std::wstring const& p + , std::function f, error_code& ec); + +#ifndef BOOST_NO_EXCEPTIONS + TORRENT_DEPRECATED + inline void set_piece_hashes(create_torrent& t, std::wstring const& p + , std::function f) + { + error_code ec; + set_piece_hashes_deprecated(t, p, f, ec); + if (ec) throw system_error(ec); + } + + TORRENT_DEPRECATED + inline void set_piece_hashes(create_torrent& t, std::wstring const& p) + { + error_code ec; + set_piece_hashes_deprecated(t, p, detail::nop, ec); + if (ec) throw system_error(ec); + } +#endif + + TORRENT_DEPRECATED + inline void set_piece_hashes(create_torrent& t + , std::wstring const& p, error_code& ec) + { + set_piece_hashes_deprecated(t, p, detail::nop, ec); + } +#endif // TORRENT_WINDOWS +#endif // TORRENT_ABI_VERSION + +namespace aux { + TORRENT_EXTRA_EXPORT file_flags_t get_file_attributes(std::string const& p); + TORRENT_EXTRA_EXPORT std::string get_symlink_path(std::string const& p); +} + +} + +#endif diff --git a/include/libtorrent/deadline_timer.hpp b/include/libtorrent/deadline_timer.hpp new file mode 100644 index 0000000..8e33c70 --- /dev/null +++ b/include/libtorrent/deadline_timer.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEADLINE_TIMER_HPP_INCLUDED +#define TORRENT_DEADLINE_TIMER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using deadline_timer = sim::asio::high_resolution_timer; +#else + using deadline_timer = boost::asio::high_resolution_timer; +#endif +} + +#endif // TORRENT_DEADLINE_TIMER_HPP_INCLUDED diff --git a/include/libtorrent/debug.hpp b/include/libtorrent/debug.hpp new file mode 100644 index 0000000..31ae49b --- /dev/null +++ b/include/libtorrent/debug.hpp @@ -0,0 +1,229 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DEBUG_HPP_INCLUDED +#define TORRENT_DEBUG_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +#if TORRENT_USE_ASSERTS +#include +#endif + +#if defined TORRENT_ASIO_DEBUGGING + +#include "libtorrent/time.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include +#include +#include + +#ifdef __MACH__ +#include +#include +#include + +const mach_msg_type_number_t task_events_info_count = TASK_EVENTS_INFO_COUNT; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +std::string demangle(char const* name); + +namespace libtorrent { + + struct async_t + { + async_t() : refs(0) {} + std::string stack; + int refs; + }; + + // defined in session_impl.cpp + extern std::map _async_ops; + extern int _async_ops_nthreads; + extern std::mutex _async_ops_mutex; + + // timestamp -> operation + struct wakeup_t + { + time_point timestamp; + std::uint64_t context_switches; + char const* operation; + }; + extern std::deque _wakeups; + + inline bool has_outstanding_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + std::map::iterator i = _async_ops.find(name); + return i != _async_ops.end(); + } + + inline void add_outstanding_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + async_t& a = _async_ops[name]; + if (a.stack.empty()) + { + char stack_text[10000]; + print_backtrace(stack_text, sizeof(stack_text), 9); + + // skip the stack frame of 'add_outstanding_async' + char* ptr = strchr(stack_text, '\n'); + if (ptr != nullptr) ++ptr; + else ptr = stack_text; + a.stack = ptr; + } + ++a.refs; + } + + inline void complete_async(char const* name) + { + std::lock_guard l(_async_ops_mutex); + async_t& a = _async_ops[name]; + TORRENT_ASSERT(a.refs > 0); + --a.refs; + + // don't let this grow indefinitely + if (_wakeups.size() < 100000) + { + _wakeups.push_back(wakeup_t()); + wakeup_t& w = _wakeups.back(); + w.timestamp = clock_type::now(); +#ifdef __MACH__ + task_events_info teinfo; + mach_msg_type_number_t t_info_count = task_events_info_count; + task_info(mach_task_self(), TASK_EVENTS_INFO, + reinterpret_cast(&teinfo), &t_info_count); + w.context_switches = static_cast(teinfo.csw); +#else + w.context_switches = 0; +#endif + w.operation = name; + } + } + + inline void async_inc_threads() + { + std::lock_guard l(_async_ops_mutex); + ++_async_ops_nthreads; + } + + inline void async_dec_threads() + { + std::lock_guard l(_async_ops_mutex); + --_async_ops_nthreads; + } + + inline int log_async() + { + std::lock_guard l(_async_ops_mutex); + int ret = 0; + for (std::map::iterator i = _async_ops.begin() + , end(_async_ops.end()); i != end; ++i) + { + if (i->second.refs <= _async_ops_nthreads - 1) continue; + ret += i->second.refs; + std::printf("%s: (%d)\n%s\n", i->first.c_str(), i->second.refs, i->second.stack.c_str()); + } + return ret; + } +} + +#define ADD_OUTSTANDING_ASYNC(x) add_outstanding_async(x) +#define COMPLETE_ASYNC(x) complete_async(x) + +#else + +#define ADD_OUTSTANDING_ASYNC(x) do {} TORRENT_WHILE_0 +#define COMPLETE_ASYNC(x) do {} TORRENT_WHILE_0 + +#endif // TORRENT_ASIO_DEBUGGING + +namespace libtorrent { + +#if TORRENT_USE_ASSERTS + struct TORRENT_EXTRA_EXPORT single_threaded + { + single_threaded(): m_id() {} + ~single_threaded() { m_id = std::thread::id(); } + bool is_single_thread() const + { + if (m_id == std::thread::id()) + { + m_id = std::this_thread::get_id(); + return true; + } + return m_id == std::this_thread::get_id(); + } + bool is_not_thread() const + { + if (m_id == std::thread::id()) return true; + return m_id != std::this_thread::get_id(); + } + + void thread_started() + { m_id = std::this_thread::get_id(); } + + private: + mutable std::thread::id m_id; + }; +#else + struct single_threaded { + bool is_single_thread() const { return true; } + void thread_started() {} + bool is_not_thread() const {return true; } + }; +#endif + +#if TORRENT_USE_ASSERTS + struct increment_guard + { + int& m_cnt; + explicit increment_guard(int& c) : m_cnt(c) { TORRENT_ASSERT(m_cnt >= 0); ++m_cnt; } + ~increment_guard() { --m_cnt; TORRENT_ASSERT(m_cnt >= 0); } + private: + increment_guard(increment_guard const&); + increment_guard operator=(increment_guard const&); + }; +#define TORRENT_INCREMENT(x) increment_guard inc_(x) +#else +#define TORRENT_INCREMENT(x) do {} TORRENT_WHILE_0 +#endif +} + +#endif // TORRENT_DEBUG_HPP_INCLUDED diff --git a/include/libtorrent/disk_buffer_holder.hpp b/include/libtorrent/disk_buffer_holder.hpp new file mode 100644 index 0000000..42c630c --- /dev/null +++ b/include/libtorrent/disk_buffer_holder.hpp @@ -0,0 +1,123 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED +#define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/block_cache_reference.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT buffer_allocator_interface + { + virtual void free_disk_buffer(char* b) = 0; + virtual void reclaim_blocks(span refs) = 0; + protected: + ~buffer_allocator_interface() = default; + }; + + // The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer + // when it's destructed, unless it's released. ``release`` returns the disk + // buffer and transfers ownership and responsibility to free it to the caller. + // + // ``data()`` returns the pointer without transferring ownership. If + // this buffer has been released, ``data()`` will return nullptr. + struct TORRENT_EXTRA_EXPORT disk_buffer_holder + { + // internal + disk_buffer_holder(buffer_allocator_interface& alloc + , char* buf, std::size_t sz) noexcept; + + disk_buffer_holder& operator=(disk_buffer_holder&&) & noexcept; + disk_buffer_holder(disk_buffer_holder&&) noexcept; + + disk_buffer_holder& operator=(disk_buffer_holder const&) = delete; + disk_buffer_holder(disk_buffer_holder const&) = delete; + + // construct a buffer holder that will free the held buffer + // using a disk buffer pool directly (there's only one + // disk_buffer_pool per session) + disk_buffer_holder(buffer_allocator_interface& alloc + , aux::block_cache_reference const& ref + , char* buf + , std::size_t sz) noexcept; + + // frees any unreleased disk buffer held by this object + ~disk_buffer_holder(); + + // return the held disk buffer and clear it from the + // holder. The responsibility to free it is passed on + // to the caller + char* release() noexcept; + + // return a pointer to the held buffer + char* data() const noexcept { return m_buf; } + char* get() const noexcept { return m_buf; } + + // set the holder object to hold the specified buffer + // (or nullptr by default). If it's already holding a + // disk buffer, it will first be freed. + void reset(char* buf = nullptr, std::size_t sz = 0); + void reset(aux::block_cache_reference const& ref, char* buf, std::size_t sz); + + // swap pointers of two disk buffer holders. + void swap(disk_buffer_holder& h) noexcept + { + TORRENT_ASSERT(h.m_allocator == m_allocator); + std::swap(h.m_buf, m_buf); + std::swap(h.m_size, m_size); + std::swap(h.m_ref, m_ref); + } + + // if this returns true, the buffer may not be modified in place + bool is_mutable() const noexcept { return m_ref.cookie == aux::block_cache_reference::none; } + + // implicitly convertible to true if the object is currently holding a + // buffer + explicit operator bool() const noexcept { return m_buf != nullptr; } + + std::size_t size() const { return m_size; } + + private: + + buffer_allocator_interface* m_allocator; + char* m_buf; + std::size_t m_size; + aux::block_cache_reference m_ref; + }; + +} + +#endif diff --git a/include/libtorrent/disk_buffer_pool.hpp b/include/libtorrent/disk_buffer_pool.hpp new file mode 100644 index 0000000..73ed1c2 --- /dev/null +++ b/include/libtorrent/disk_buffer_pool.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_BUFFER_POOL_HPP +#define TORRENT_DISK_BUFFER_POOL_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_INVARIANT_CHECKS +#include +#endif +#include +#include +#include +#include + +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/storage_utils.hpp" // for iovec_t + +namespace libtorrent { + +namespace aux { struct session_settings; } + + struct disk_observer; + + struct TORRENT_EXTRA_EXPORT disk_buffer_pool + { + disk_buffer_pool(io_service& ios, std::function const& trigger_trim); + disk_buffer_pool(disk_buffer_pool const&) = delete; + disk_buffer_pool& operator=(disk_buffer_pool const&) = delete; + ~disk_buffer_pool(); + +#if TORRENT_USE_ASSERTS + bool is_disk_buffer(char* buffer + , std::unique_lock& l) const; + bool is_disk_buffer(char* buffer) const; +#endif + + char* allocate_buffer(char const* category); + char* allocate_buffer(bool& exceeded, std::shared_ptr o + , char const* category); + void free_buffer(char* buf); + void free_multiple_buffers(span bufvec); + + int allocate_iovec(span iov); + void free_iovec(span iov); + + int in_use() const + { + std::unique_lock l(m_pool_mutex); + return m_in_use; + } + int num_to_evict(int num_needed = 0); + + void set_settings(aux::session_settings const& sett); + + protected: + + void free_buffer_impl(char* buf, std::unique_lock& l); + char* allocate_buffer_impl(std::unique_lock& l, char const* category); + + // number of disk buffers currently allocated + int m_in_use; + + // cache size limit + int m_max_use; + + // if we have exceeded the limit, we won't start + // allowing allocations again until we drop below + // this low watermark + int m_low_watermark; + + // if we exceed the max number of buffers, we start + // adding up callbacks to this queue. Once the number + // of buffers in use drops below the low watermark, + // we start calling these functions back + std::vector> m_observers; + + // callback used to tell the cache it needs to free up some blocks + std::function m_trigger_cache_trim; + + // set to true to throttle more allocations + bool m_exceeded_max_size; + + // this is the main thread io_service. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_service& m_ios; + + private: + + void check_buffer_level(std::unique_lock& l); + void remove_buffer_in_use(char* buf); + + mutable std::mutex m_pool_mutex; + + // this is specifically exempt from release_asserts + // since it's a quite costly check. Only for debug + // builds. +#if TORRENT_USE_INVARIANT_CHECKS + std::set m_buffers_in_use; +#endif +#if TORRENT_USE_ASSERTS + int m_magic; + bool m_settings_set; +#endif + }; + +} + +#endif // TORRENT_DISK_BUFFER_POOL_HPP diff --git a/include/libtorrent/disk_interface.hpp b/include/libtorrent/disk_interface.hpp new file mode 100644 index 0000000..d662ab8 --- /dev/null +++ b/include/libtorrent/disk_interface.hpp @@ -0,0 +1,248 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_INTERFACE_HPP +#define TORRENT_DISK_INTERFACE_HPP + +#include "libtorrent/bdecode.hpp" + +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/session_types.hpp" + +namespace libtorrent { + + struct disk_observer; + struct counters; + + struct storage_holder; + + using file_open_mode_t = flags::bitfield_flag; + + // internal + // this is a bittorrent constant + constexpr int default_block_size = 0x4000; + + namespace file_open_mode + { + // open the file for reading only + constexpr file_open_mode_t read_only{}; + + // open the file for writing only + constexpr file_open_mode_t write_only = 0_bit; + + // open the file for reading and writing + constexpr file_open_mode_t read_write = 1_bit; + + // the mask for the bits determining read or write mode + constexpr file_open_mode_t rw_mask = read_only | write_only | read_write; + + // open the file in sparse mode (if supported by the + // filesystem). + constexpr file_open_mode_t sparse = 2_bit; + + // don't update the access timestamps on the file (if + // supported by the operating system and filesystem). + // this generally improves disk performance. + constexpr file_open_mode_t no_atime = 3_bit; + + // open the file for random access. This disables read-ahead + // logic + constexpr file_open_mode_t random_access = 5_bit; + +#if TORRENT_ABI_VERSION == 1 + // prevent the file from being opened by another process + // while it's still being held open by this handle + constexpr file_open_mode_t TORRENT_DEPRECATED locked = 6_bit; +#endif + } + + // this contains information about a file that's currently open by the + // libtorrent disk I/O subsystem. It's associated with a single torrent. + struct TORRENT_EXPORT open_file_state + { + // the index of the file this entry refers to into the ``file_storage`` + // file list of this torrent. This starts indexing at 0. + file_index_t file_index; + + // ``open_mode`` is a bitmask of the file flags this file is currently + // opened with. These are the flags used in the ``file::open()`` function. + // For possible flags, see file_open_mode_t. + // + // Note that the read/write mode is not a bitmask. The two least significant bits are used + // to represent the read/write mode. Those bits can be masked out using the ``rw_mask`` constant. + file_open_mode_t open_mode; + + // a (high precision) timestamp of when the file was last used. + time_point last_use; + }; + +#if TORRENT_ABI_VERSION == 1 + using pool_file_status = open_file_state; +#endif + + using disk_job_flags_t = flags::bitfield_flag; + + struct TORRENT_EXTRA_EXPORT disk_interface + { + // force making a copy of the cached block, rather + // than getting a reference to the block already in + // the cache. + static constexpr disk_job_flags_t force_copy = 0_bit; + + // hint that there may be more disk operations with sequential access to + // the file + static constexpr disk_job_flags_t sequential_access = 3_bit; + + // don't keep the read block in cache + static constexpr disk_job_flags_t volatile_read = 4_bit; + + // this flag is set on a job when a read operation did + // not hit the disk, but found the data in the read cache. + static constexpr disk_job_flags_t cache_hit = 5_bit; + + virtual storage_holder new_torrent(storage_constructor_type sc + , storage_params p, std::shared_ptr const&) = 0; + virtual void remove_torrent(storage_index_t) = 0; + virtual storage_interface* get_torrent(storage_index_t) = 0; + + virtual void async_read(storage_index_t storage, peer_request const& r + , std::function handler + , disk_job_flags_t flags = {}) = 0; + virtual bool async_write(storage_index_t storage, peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , disk_job_flags_t flags = {}) = 0; + virtual void async_hash(storage_index_t storage, piece_index_t piece, disk_job_flags_t flags + , std::function handler) = 0; + virtual void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags + , std::function handler) = 0; + virtual void async_release_files(storage_index_t storage + , std::function handler = std::function()) = 0; + virtual void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector& links + , std::function handler) = 0; + virtual void async_flush_piece(storage_index_t storage, piece_index_t piece + , std::function handler = std::function()) = 0; + virtual void async_stop_torrent(storage_index_t storage + , std::function handler = std::function()) = 0; + virtual void async_rename_file(storage_index_t storage + , file_index_t index, std::string name + , std::function handler) = 0; + virtual void async_delete_files(storage_index_t storage, remove_flags_t options + , std::function handler) = 0; + virtual void async_set_file_priority(storage_index_t storage + , aux::vector prio + , std::function)> handler) = 0; + + virtual void async_clear_piece(storage_index_t storage, piece_index_t index + , std::function handler) = 0; + virtual void clear_piece(storage_index_t storage, piece_index_t index) = 0; + + virtual void update_stats_counters(counters& c) const = 0; + virtual void get_cache_info(cache_status* ret, storage_index_t storage + , bool no_pieces = true, bool session = true) const = 0; + + virtual std::vector get_status(storage_index_t) const = 0; + + virtual void submit_jobs() = 0; + +#if TORRENT_USE_ASSERTS + virtual bool is_disk_buffer(char* buffer) const = 0; +#endif + protected: + ~disk_interface() {} + }; + + struct storage_holder + { + storage_holder() = default; + storage_holder(storage_index_t idx, disk_interface& disk_io) + : m_disk_io(&disk_io) + , m_idx(idx) + {} + ~storage_holder() + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + } + + explicit operator bool() const { return m_disk_io != nullptr; } + + operator storage_index_t() const + { + TORRENT_ASSERT(m_disk_io); + return m_idx; + } + + void reset() + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + m_disk_io = nullptr; + } + + storage_holder(storage_holder const&) = delete; + storage_holder& operator=(storage_holder const&) = delete; + + storage_holder(storage_holder&& rhs) noexcept + : m_disk_io(rhs.m_disk_io) + , m_idx(rhs.m_idx) + { + rhs.m_disk_io = nullptr; + } + + storage_holder& operator=(storage_holder&& rhs) noexcept + { + if (m_disk_io) m_disk_io->remove_torrent(m_idx); + m_disk_io = rhs.m_disk_io; + m_idx = rhs.m_idx; + rhs.m_disk_io = nullptr; + return *this; + } + private: + disk_interface* m_disk_io = nullptr; + storage_index_t m_idx{0}; + }; + +} + +#endif diff --git a/include/libtorrent/disk_io_job.hpp b/include/libtorrent/disk_io_job.hpp new file mode 100644 index 0000000..40accd8 --- /dev/null +++ b/include/libtorrent/disk_io_job.hpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_JOB_HPP +#define TORRENT_DISK_IO_JOB_HPP + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include + +namespace libtorrent { + + struct cached_piece_entry; + + // internal + enum class job_action_t : std::uint8_t + { + read + , write + , hash + , move_storage + , release_files + , delete_files + , check_fastresume + , rename_file + , stop_torrent + , flush_piece + , flush_hashed + , flush_storage + , trim_cache + , file_priority + , clear_piece + , num_job_ids + }; + + // disk_io_jobs are allocated in a pool allocator in disk_io_thread + // they are always allocated from the network thread, posted + // (as pointers) to the disk I/O thread, and then passed back + // to the network thread for completion handling and to be freed. + // each disk_io_job can belong to one tailqueue. The job queue + // in the disk thread, is one, the jobs waiting on completion + // on a cache piece (in block_cache) is another, and a job + // waiting for a storage fence to be lowered is another. Jobs + // are never in more than one queue at a time. Only passing around + // pointers and chaining them back and forth into lists saves + // a lot of heap allocation churn of using general purpose + // containers. + struct TORRENT_EXTRA_EXPORT disk_io_job : tailqueue_node + { + disk_io_job(); + disk_io_job(disk_io_job const&) = delete; + disk_io_job& operator=(disk_io_job const&) = delete; + + void call_callback(); + + // this is set by the storage object when a fence is raised + // for this job. It means that this no other jobs on the same + // storage will execute in parallel with this one. It's used + // to lower the fence when the job has completed + static constexpr disk_job_flags_t fence = 1_bit; + + // this job is currently being performed, or it's hanging + // on a cache piece that may be flushed soon + static constexpr disk_job_flags_t in_progress = 2_bit; + + // this is set for jobs that we're no longer interested in. Any aborted + // job that's executed should immediately fail with operation_aborted + // instead of executing + static constexpr disk_job_flags_t aborted = 6_bit; + + // for write jobs, returns true if its block + // is not dirty anymore + bool completed(cached_piece_entry const* pe); + + // for read and write, this is the disk_buffer_holder + // for other jobs, it may point to other job-specific types + // for move_storage and rename_file this is a string + boost::variant + , remove_flags_t + > argument; + + // the disk storage this job applies to (if applicable) + std::shared_ptr storage; + + // this is called when operation completes + + using read_handler = std::function; + using write_handler = std::function; + using hash_handler = std::function; + using move_handler = std::function; + using release_handler = std::function; + using check_handler = std::function; + using rename_handler = std::function; + using clear_piece_handler = std::function; + using set_file_prio_handler = std::function)>; + + boost::variant callback; + + // the error code from the file operation + // on error, this also contains the path of the + // file the disk operation failed on + storage_error error; + + union un + { + un() {} + // result for hash jobs + sha1_hash piece_hash; + + // this is used for check_fastresume to pass in a vector of hard-links + // to create. Each element corresponds to a file in the file_storage. + // The string is the absolute path of the identical file to create + // the hard link to. + aux::vector* links; + + struct io_args + { + // for read and write, the offset into the piece + // the read or write should start + // for hash jobs, this is the first block the hash + // job is still holding a reference to. The end of + // the range of blocks a hash jobs holds references + // to is always the last block in the piece. + std::int32_t offset; + + // number of bytes 'buffer' points to. Used for read & write + std::uint16_t buffer_size; + } io; + } d; + + // arguments used for read and write + // the piece this job applies to + union { + piece_index_t piece; + file_index_t file_index; + }; + + // the type of job this is + job_action_t action = job_action_t::read; + + // return value of operation + status_t ret = status_t::no_error; + + // flags controlling this job + disk_job_flags_t flags = disk_job_flags_t{}; + + move_flags_t move_flags = move_flags_t::always_replace_files; + +#if TORRENT_USE_ASSERTS + bool in_use = false; + + // set to true when the job is added to the completion queue. + // to make sure we don't add it twice + mutable bool job_posted = false; + + // set to true when the callback has been called once + // used to make sure we don't call it twice + mutable bool callback_called = false; + + // this is true when the job is blocked by a storage_fence + mutable bool blocked = false; +#endif + }; + +} + +#endif // TORRENT_DISK_IO_JOB_HPP diff --git a/include/libtorrent/disk_io_thread.hpp b/include/libtorrent/disk_io_thread.hpp new file mode 100644 index 0000000..a3d8bb5 --- /dev/null +++ b/include/libtorrent/disk_io_thread.hpp @@ -0,0 +1,586 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_THREAD +#define TORRENT_DISK_IO_THREAD + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/disk_io_thread_pool.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/disk_job_pool.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +#include +#include +#include +#include +#include + +namespace libtorrent { + + struct counters; + class alert_manager; + +namespace aux { struct block_cache_reference; } + + struct cached_piece_info + { + storage_interface* storage; + + // holds one entry for each block in this piece. ``true`` represents + // the data for that block being in the disk cache and ``false`` means it's not. + std::vector blocks; + + // the time when a block was last written to this piece. The older + // a piece is, the more likely it is to be flushed to disk. + time_point last_use; + + // The index of the next block that needs to be hashed. + // Blocks are hashed as they are downloaded in order to not + // have to re-read them from disk once the piece is complete, to + // compare its hash against the hashes in the .torrent file. + int next_to_hash; + + // the piece index for this cache entry. + piece_index_t piece; + + enum kind_t { read_cache = 0, write_cache = 1, volatile_read_cache = 2 }; + + // specifies if this piece is part of the read cache or the write cache. + kind_t kind; + + bool need_readback; + }; + + using jobqueue_t = tailqueue; + + // this struct holds a number of statistics counters + // relevant for the disk io thread and disk cache. + struct TORRENT_EXPORT cache_status + { + // initializes all counters to 0 + cache_status() + : pieces() +#if TORRENT_ABI_VERSION == 1 + , blocks_written(0) + , writes(0) + , blocks_read(0) + , blocks_read_hit(0) + , reads(0) + , queued_bytes(0) + , cache_size(0) + , write_cache_size(0) + , read_cache_size(0) + , pinned_blocks(0) + , total_used_buffers(0) + , average_read_time(0) + , average_write_time(0) + , average_hash_time(0) + , average_job_time(0) + , cumulative_job_time(0) + , cumulative_read_time(0) + , cumulative_write_time(0) + , cumulative_hash_time(0) + , total_read_back(0) + , read_queue_size(0) + , blocked_jobs(0) + , queued_jobs(0) + , peak_queued(0) + , pending_jobs(0) + , num_jobs(0) + , num_read_jobs(0) + , num_write_jobs(0) + , arc_mru_size(0) + , arc_mru_ghost_size(0) + , arc_mfu_size(0) + , arc_mfu_ghost_size(0) + , arc_write_size(0) + , arc_volatile_size(0) + , num_writing_threads(0) +#endif + { +#if TORRENT_ABI_VERSION == 1 + std::memset(num_fence_jobs, 0, sizeof(num_fence_jobs)); +#endif + } + + std::vector pieces; + +#if TORRENT_ABI_VERSION == 1 + // the total number of 16 KiB blocks written to disk + // since this session was started. + int blocks_written; + + // the total number of write operations performed since this + // session was started. + // + // The ratio (``blocks_written`` - ``writes``) / ``blocks_written`` represents + // the number of saved write operations per total write operations. i.e. a kind + // of cache hit ratio for the write cahe. + int writes; + + // the number of blocks that were requested from the + // bittorrent engine (from peers), that were served from disk or cache. + int blocks_read; + + // the number of blocks that was just copied from the read cache + // + // The ratio ``blocks_read_hit`` / ``blocks_read`` is the cache hit ratio + // for the read cache. + int blocks_read_hit; + + // the number of read operations used + int reads; + + // the number of bytes queued for writing, including bytes + // submitted to the OS for writing, but not yet complete + mutable std::int64_t queued_bytes; + + // the number of 16 KiB blocks currently in the disk cache (both read and write). + // This includes both read and write cache. + int cache_size; + + // the number of blocks in the cache used for write cache + int write_cache_size; + + // the number of 16KiB blocks in the read cache. + int read_cache_size; + + // the number of blocks with a refcount > 0, i.e. + // they may not be evicted + int pinned_blocks; + + // the total number of buffers currently in use. + // This includes the read/write disk cache as well as send and receive buffers + // used in peer connections. + // deprecated, use session_stats_metrics "disk.disk_blocks_in_use" + mutable int total_used_buffers; + + // the number of microseconds an average disk I/O job + // has to wait in the job queue before it get processed. + + // the time read jobs takes on average to complete + // (not including the time in the queue), in microseconds. This only measures + // read cache misses. + int average_read_time; + + // the time write jobs takes to complete, on average, + // in microseconds. This does not include the time the job sits in the disk job + // queue or in the write cache, only blocks that are flushed to disk. + int average_write_time; + + // the time hash jobs takes to complete on average, in + // microseconds. Hash jobs include running SHA-1 on the data (which for the most + // part is done incrementally) and sometimes reading back parts of the piece. It + // also includes checking files without valid resume data. + int average_hash_time; + int average_job_time; + + // the number of milliseconds spent in all disk jobs, and specific ones + // since the start of the session. Times are specified in milliseconds + int cumulative_job_time; + int cumulative_read_time; + int cumulative_write_time; + int cumulative_hash_time; + + // the number of blocks that had to be read back from disk because + // they were flushed before the SHA-1 hash got to hash them. If this + // is large, a larger cache could significantly improve performance + int total_read_back; + + // number of read jobs in the disk job queue + int read_queue_size; + + // number of jobs blocked because of a fence + int blocked_jobs; + + // number of jobs waiting to be issued (m_to_issue) + // average over 30 seconds + // deprecated, use session_stats_metrics "disk.queued_disk_jobs" + int queued_jobs; + + // largest ever seen number of queued jobs + int peak_queued; + + // number of jobs waiting to complete (m_pending) + // average over 30 seconds + int pending_jobs; + + // total number of disk job objects allocated right now + int num_jobs; + + // total number of disk read job objects allocated right now + int num_read_jobs; + + // total number of disk write job objects allocated right now + int num_write_jobs; + + // ARC cache stats. All of these counters are in number of pieces + // not blocks. A piece does not necessarily correspond to a certain + // number of blocks. The pieces in the ghost list never have any + // blocks in them + int arc_mru_size; + int arc_mru_ghost_size; + int arc_mfu_size; + int arc_mfu_ghost_size; + int arc_write_size; + int arc_volatile_size; + + // the number of threads currently writing to disk + int num_writing_threads; + + // counts only fence jobs that are currently blocking jobs + // not fences that are themself blocked + int num_fence_jobs[static_cast(job_action_t::num_job_ids)]; +#endif + }; + + // this is a singleton consisting of the thread and a queue + // of disk io jobs + struct TORRENT_EXTRA_EXPORT disk_io_thread final + : disk_job_pool + , disk_interface + , buffer_allocator_interface + { + disk_io_thread(io_service& ios, aux::session_settings const&, counters&); +#if TORRENT_USE_ASSERTS + ~disk_io_thread(); +#endif + + enum + { + // every 4:th thread is a hash thread + hasher_thread_divisor = 4 + }; + + void settings_updated(); + + void abort(bool wait); + + storage_holder new_torrent(storage_constructor_type sc + , storage_params p, std::shared_ptr const&) override; + void remove_torrent(storage_index_t) override; + + void async_read(storage_index_t storage, peer_request const& r + , std::function handler, disk_job_flags_t flags = {}) override; + bool async_write(storage_index_t storage, peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , disk_job_flags_t flags = {}) override; + void async_hash(storage_index_t storage, piece_index_t piece, disk_job_flags_t flags + , std::function handler) override; + void async_move_storage(storage_index_t storage, std::string p, move_flags_t flags + , std::function handler) override; + void async_release_files(storage_index_t storage + , std::function handler = std::function()) override; + void async_delete_files(storage_index_t storage, remove_flags_t options + , std::function handler) override; + void async_check_files(storage_index_t storage + , add_torrent_params const* resume_data + , aux::vector& links + , std::function handler) override; + void async_rename_file(storage_index_t storage, file_index_t index, std::string name + , std::function handler) override; + void async_stop_torrent(storage_index_t storage + , std::function handler) override; + void async_flush_piece(storage_index_t storage, piece_index_t piece + , std::function handler = std::function()) override; + void async_set_file_priority(storage_index_t storage + , aux::vector prio + , std::function)> handler) override; + + void async_clear_piece(storage_index_t storage, piece_index_t index + , std::function handler) override; + // this is not asynchronous and requires that the piece does not + // have any pending buffers. It's meant to be used for pieces that + // were just read and hashed and failed the hash check. + // there should be no read-operations left, and all buffers should + // be discardable + void clear_piece(storage_index_t storage, piece_index_t index) override; + + // implements buffer_allocator_interface + void reclaim_blocks(span ref) override; + void free_disk_buffer(char* buf) override { m_disk_cache.free_buffer(buf); } + void trigger_cache_trim(); + void update_stats_counters(counters& c) const override; + void get_cache_info(cache_status* ret, storage_index_t storage + , bool no_pieces, bool session) const override; + storage_interface* get_torrent(storage_index_t) override; + + std::vector get_status(storage_index_t) const override; + + // this submits all queued up jobs to the thread + void submit_jobs() override; + + block_cache* cache() { return &m_disk_cache; } + +#if TORRENT_USE_ASSERTS + bool is_disk_buffer(char* buffer) const override + { return m_disk_cache.is_disk_buffer(buffer); } +#endif + + int prep_read_job_impl(disk_io_job* j, bool check_fence = true); + + void maybe_issue_queued_read_jobs(cached_piece_entry* pe, + jobqueue_t& completed_jobs); + status_t do_read(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_uncached_read(disk_io_job* j); + + status_t do_write(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_uncached_write(disk_io_job* j); + + status_t do_hash(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_uncached_hash(disk_io_job* j); + + status_t do_move_storage(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_release_files(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_delete_files(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_check_fastresume(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_rename_file(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_stop_torrent(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_flush_piece(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_flush_hashed(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_flush_storage(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_trim_cache(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_file_priority(disk_io_job* j, jobqueue_t& completed_jobs); + status_t do_clear_piece(disk_io_job* j, jobqueue_t& completed_jobs); + + void call_job_handlers(); + + private: + + struct job_queue : pool_thread_interface + { + explicit job_queue(disk_io_thread& owner) : m_owner(owner) {} + + void notify_all() override + { + m_job_cond.notify_all(); + } + + void thread_fun(disk_io_thread_pool& pool, io_service::work work) override + { + ADD_OUTSTANDING_ASYNC("disk_io_thread::work"); + m_owner.thread_fun(*this, pool); + + // w's dtor releases the io_service to allow the run() call to return + // we do this once we stop posting new callbacks to it. + // after the dtor has been called, the disk_io_thread object may be destructed + TORRENT_UNUSED(work); + COMPLETE_ASYNC("disk_io_thread::work"); + } + + disk_io_thread& m_owner; + + // used to wake up the disk IO thread when there are new + // jobs on the job queue (m_queued_jobs) + std::condition_variable m_job_cond; + + // jobs queued for servicing + jobqueue_t m_queued_jobs; + }; + + void thread_fun(job_queue& queue, disk_io_thread_pool& pool); + + // returns true if the thread should exit + static bool wait_for_job(job_queue& jobq, disk_io_thread_pool& threads + , std::unique_lock& l); + + void add_completed_jobs(jobqueue_t& jobs); + void add_completed_jobs_impl(jobqueue_t& jobs + , jobqueue_t& completed_jobs); + + void fail_jobs(storage_error const& e, jobqueue_t& jobs_); + void fail_jobs_impl(storage_error const& e, jobqueue_t& src, jobqueue_t& dst); + + void check_cache_level(std::unique_lock& l, jobqueue_t& completed_jobs); + + void perform_job(disk_io_job* j, jobqueue_t& completed_jobs); + + // this queues up another job to be submitted + void add_job(disk_io_job* j, bool user_add = true); + void add_fence_job(disk_io_job* j, bool user_add = true); + + // assumes l is locked (cache std::mutex). + // writes out the blocks [start, end) (releases the lock + // during the file operation) + int flush_range(cached_piece_entry* p, int start, int end + , jobqueue_t& completed_jobs, std::unique_lock& l); + + // low level flush operations, used by flush_range + int build_iovec(cached_piece_entry* pe, int start, int end + , span iov, span flushing, int block_base_index = 0); + void flush_iovec(cached_piece_entry* pe, span iov, span flushing + , int num_blocks, storage_error& error); + // returns true if the piece entry was freed + bool iovec_flushed(cached_piece_entry* pe + , int* flushing, int num_blocks, int block_offset + , storage_error const& error + , jobqueue_t& completed_jobs); + + // assumes l is locked (the cache std::mutex). + // assumes pe->hash to be set. + // If there are new blocks in piece 'pe' that have not been + // hashed by the partial_hash object attached to this piece, + // the piece will + void kick_hasher(cached_piece_entry* pe, std::unique_lock& l); + + // flags to pass in to flush_cache() + enum flush_flags_t : std::uint32_t + { + // only flush read cache (this is cheap) + flush_read_cache = 1, + // flush read cache, and write cache + flush_write_cache = 2, + // flush read cache, delete write cache without flushing to disk + flush_delete_cache = 4, + // expect all pieces for the storage to have been + // cleared when flush_cache() returns. This is only + // used for asserts and only applies for fence jobs + flush_expect_clear = 8 + }; + void flush_cache(storage_interface* storage, std::uint32_t flags, jobqueue_t& completed_jobs, std::unique_lock& l); + void flush_expired_write_blocks(jobqueue_t& completed_jobs, std::unique_lock& l); + void flush_piece(cached_piece_entry* pe, std::uint32_t flags, jobqueue_t& completed_jobs, std::unique_lock& l); + + int try_flush_hashed(cached_piece_entry* p, int cont_blocks, jobqueue_t& completed_jobs, std::unique_lock& l); + + void try_flush_write_blocks(int num, jobqueue_t& completed_jobs, std::unique_lock& l); + + void maybe_flush_write_blocks(); + void execute_job(disk_io_job* j); + void immediate_execute(); + void abort_jobs(); + void abort_hash_jobs(storage_index_t storage); + + // returns the maximum number of threads + // the actual number of threads may be less + int num_threads() const; + job_queue& queue_for_job(disk_io_job* j); + disk_io_thread_pool& pool_for_job(disk_io_job* j); + + // set to true once we start shutting down + std::atomic m_abort{false}; + + // this is a counter of how many threads are currently running. + // it's used to identify the last thread still running while + // shutting down. This last thread is responsible for cleanup + // must hold the job mutex to access + int m_num_running_threads = 0; + + // std::mutex to protect the m_generic_io_jobs and m_hash_io_jobs lists + mutable std::mutex m_job_mutex; + + // most jobs are posted to m_generic_io_jobs + // but hash jobs are posted to m_hash_io_jobs if m_hash_threads + // has a non-zero maximum thread count + job_queue m_generic_io_jobs; + disk_io_thread_pool m_generic_threads; + job_queue m_hash_io_jobs; + disk_io_thread_pool m_hash_threads; + + aux::session_settings const& m_settings; + + // the last time we expired write blocks from the cache + time_point m_last_cache_expiry = min_time(); + + // we call close_oldest_file on the file_pool regularly. This is the next + // time we should call it + time_point m_next_close_oldest_file = min_time(); + + // LRU cache of open files + file_pool m_file_pool{40}; + + // disk cache + mutable std::mutex m_cache_mutex; + block_cache m_disk_cache; + enum + { + cache_check_idle, + cache_check_active, + cache_check_reinvoke + }; + int m_cache_check_state = cache_check_idle; + + // total number of blocks in use by both the read + // and the write cache. This is not supposed to + // exceed m_cache_size + + counters& m_stats_counters; + + // this is the main thread io_service. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_service& m_ios; + + // jobs that are completed are put on this queue + // whenever the queue size grows from 0 to 1 + // a message is posted to the network thread, which + // will then drain the queue and execute the jobs' + // handler functions + std::mutex m_completed_jobs_mutex; + jobqueue_t m_completed_jobs; + + // storages that have had write activity recently and will get ticked + // soon, for deferred actions (say, flushing partfile metadata) + std::vector>> m_need_tick; + std::mutex m_need_tick_mutex; + + // this is protected by the completed_jobs_mutex. It's true whenever + // there's a call_job_handlers message in-flight to the network thread. We + // only ever keep one such message in flight at a time, and coalesce + // completion callbacks in m_completed jobs + bool m_job_completions_in_flight = false; + + aux::vector, storage_index_t> m_torrents; + + // indices into m_torrents to empty slots + std::vector m_free_slots; + + std::atomic_flag m_jobs_aborted = ATOMIC_FLAG_INIT; + +#if TORRENT_USE_ASSERTS + int m_magic = 0x1337; +#endif + }; +} + +#endif diff --git a/include/libtorrent/disk_io_thread_pool.hpp b/include/libtorrent/disk_io_thread_pool.hpp new file mode 100644 index 0000000..5b4e631 --- /dev/null +++ b/include/libtorrent/disk_io_thread_pool.hpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2005-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_IO_THREAD_POOL +#define TORRENT_DISK_IO_THREAD_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/error_code.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct disk_io_thread_pool; + + struct pool_thread_interface + { + virtual ~pool_thread_interface() {} + + virtual void notify_all() = 0; + virtual void thread_fun(disk_io_thread_pool&, io_service::work) = 0; + }; + + // this class implements the policy for creating and destroying I/O threads + // threads are created when job_queued is called to signal the arrival of + // new jobs + // once a minute threads are destroyed if at least one thread has been + // idle for the entire minute + // the pool_thread_interface is used to spawn and notify the worker threads + struct TORRENT_EXTRA_EXPORT disk_io_thread_pool + { + disk_io_thread_pool(pool_thread_interface& thread_iface + , io_service& ios); + ~disk_io_thread_pool(); + + // set the maximum number of I/O threads which may be running + // the actual number of threads will be <= this number + void set_max_threads(int i); + void abort(bool wait); + int max_threads() const { return m_max_threads; } + + // thread_idle, thread_active, and job_queued are NOT thread safe + // all calls to them must be serialized + // it is expected that they will be called while holding the + // job queue mutex + + // these functions should be called by the thread_fun to signal its state + // threads are considered active when they are started so thread_idle should + // be called first + // these calls are not thread safe + void thread_idle() { ++m_num_idle_threads; } + void thread_active(); + + // check if there is an outstanding request for I/O threads to stop + // this is a weak check, if it returns true try_thread_exit may still + // return false + bool should_exit() { return m_threads_to_exit > 0; } + // this should be the last function an I/O thread calls before breaking + // out of its service loop + // if it returns true then the thread MUST exit + // if it returns false the thread should not exit + bool try_thread_exit(std::thread::id id); + + // get the thread id of the first thread in the internal vector + // since this is the first thread it will remain the same until the first + // thread exits + // it can be used to trigger maintenance jobs which should only run on one thread + std::thread::id first_thread_id(); + int num_threads() + { + std::lock_guard l(m_mutex); + return int(m_threads.size()); + } + + // this should be called whenever new jobs are queued + // queue_size is the current size of the job queue + // not thread safe + void job_queued(int queue_size); + + private: + void reap_idle_threads(error_code const& ec); + + // the caller must hold m_mutex + void stop_threads(int num_to_stop); + + pool_thread_interface& m_thread_iface; + + std::atomic m_max_threads; + // the number of threads the reaper decided should exit + std::atomic m_threads_to_exit; + + // must hold m_mutex to access + bool m_abort; + + std::atomic m_num_idle_threads; + // the minimum number of idle threads seen since the last reaping + std::atomic m_min_idle_threads; + + // ensures thread creation/destruction is atomic + std::mutex m_mutex; + + // the actual threads running disk jobs + std::vector m_threads; + + // timer to check for and reap idle threads + deadline_timer m_idle_timer; + }; +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/disk_job_pool.hpp b/include/libtorrent/disk_job_pool.hpp new file mode 100644 index 0000000..232f989 --- /dev/null +++ b/include/libtorrent/disk_job_pool.hpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_JOB_POOL +#define TORRENT_DISK_JOB_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_io_job.hpp" // for job_action_t +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + struct disk_io_job; + + struct TORRENT_EXTRA_EXPORT disk_job_pool + { + disk_job_pool(); + ~disk_job_pool(); + + disk_io_job* allocate_job(job_action_t type); + void free_job(disk_io_job* j); + void free_jobs(disk_io_job** j, int num); + + int jobs_in_use() const { return m_jobs_in_use; } + int read_jobs_in_use() const { return m_read_jobs; } + int write_jobs_in_use() const { return m_write_jobs; } + + private: + + // total number of in-use jobs + int m_jobs_in_use; + // total number of in-use read jobs + int m_read_jobs; + // total number of in-use write jobs + int m_write_jobs; + + std::mutex m_job_mutex; + boost::pool<> m_job_pool; + }; +} + +#endif // TORRENT_DISK_JOB_POOL diff --git a/include/libtorrent/disk_observer.hpp b/include/libtorrent/disk_observer.hpp new file mode 100644 index 0000000..e2c351c --- /dev/null +++ b/include/libtorrent/disk_observer.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT disk_observer + { + // called when the disk cache size has dropped + // below the low watermark again and we can + // resume downloading from peers + virtual void on_disk() = 0; + protected: + ~disk_observer() {} + }; +} + +#endif + diff --git a/include/libtorrent/download_priority.hpp b/include/libtorrent/download_priority.hpp new file mode 100644 index 0000000..1b31427 --- /dev/null +++ b/include/libtorrent/download_priority.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DOWNLOAD_PRIORITY_HPP_INCLUDED +#define TORRENT_DOWNLOAD_PRIORITY_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + +using download_priority_t = aux::strong_typedef; + +// Don't download the file or piece. Partial pieces may still be downloaded when +// setting file priorities. +constexpr download_priority_t dont_download{0}; + +// The default priority for files and pieces. +constexpr download_priority_t default_priority{4}; + +// The lowest priority for files and pieces. +constexpr download_priority_t low_priority{1}; + +// The highest priority for files and pieces. +constexpr download_priority_t top_priority{7}; + +} + +#endif diff --git a/include/libtorrent/ed25519.hpp b/include/libtorrent/ed25519.hpp new file mode 100644 index 0000000..f8a66a6 --- /dev/null +++ b/include/libtorrent/ed25519.hpp @@ -0,0 +1,27 @@ +#ifndef ED25519_HPP +#define ED25519_HPP + +#include "libtorrent/aux_/export.hpp" // for TORRENT_EXPORT +#include // for ptrdiff_t, size_t + +namespace libtorrent { + +enum +{ + ed25519_seed_size = 32, + ed25519_private_key_size = 64, + ed25519_public_key_size = 32, + ed25519_signature_size = 64, + ed25519_scalar_size = 32, + ed25519_shared_secret_size = 32 +}; + +void TORRENT_EXTRA_EXPORT ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +void TORRENT_EXTRA_EXPORT ed25519_sign(unsigned char *signature, const unsigned char *message, std::ptrdiff_t message_len, const unsigned char *public_key, const unsigned char *private_key); +int TORRENT_EXTRA_EXPORT ed25519_verify(const unsigned char *signature, const unsigned char *message, std::ptrdiff_t message_len, const unsigned char *public_key); +void TORRENT_EXTRA_EXPORT ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar); +void TORRENT_EXTRA_EXPORT ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); + +} + +#endif // ED25519_HPP diff --git a/include/libtorrent/entry.hpp b/include/libtorrent/entry.hpp new file mode 100644 index 0000000..6d3baf3 --- /dev/null +++ b/include/libtorrent/entry.hpp @@ -0,0 +1,368 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENTRY_HPP_INCLUDED +#define TORRENT_ENTRY_HPP_INCLUDED + +/* + * + * This file declares the entry class. It is a + * variant-type that can be an integer, list, + * dictionary (map) or a string. This type is + * used to hold bdecoded data (which is the + * encoding BitTorrent messages uses). + * + * it has 4 accessors to access the actual + * type of the object. They are: + * integer() + * string() + * list() + * dict() + * The actual type has to match the type you + * are asking for, otherwise you will get an + * assertion failure. + * When you default construct an entry, it is + * uninitialized. You can initialize it through the + * assignment operator, copy-constructor or + * the constructor that takes a data_type enum. + * + * + */ + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#if TORRENT_USE_IOSTREAM +#include +#endif + +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/aligned_union.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + struct lazy_entry; + // backwards compatibility + using type_error = system_error; +#endif + struct bdecode_node; + +namespace aux { + +#if !defined TORRENT_CXX11_ABI && (__cplusplus > 201103) || (defined _MSC_VER && _MSC_VER >= 1900) + // this enables us to compare a string_view against the std::string that's + // held by the std::map + // is_transparent was introduced in C++14 + struct strview_less + { + using is_transparent = std::true_type; + template + bool operator()(T1 const& rhs, T2 const& lhs) const + { return rhs < lhs; } + }; + + template using map_string = std::map; +#else + template + struct map_string : std::map + { + using base = std::map; + using base::base; + map_string() = default; + map_string(base&& rhs) : base(std::move(rhs)) {} + + typename base::iterator find(const string_view& key) + { + return this->base::find(key.to_string()); + } + + typename base::const_iterator find(const string_view& key) const + { + return this->base::find(key.to_string()); + } + }; +#endif + } + + // The ``entry`` class represents one node in a bencoded hierarchy. It works as a + // variant type, it can be either a list, a dictionary (``std::map``), an integer + // or a string. + class TORRENT_EXPORT entry + { + public: + + // the key is always a string. If a generic entry would be allowed + // as a key, sorting would become a problem (e.g. to compare a string + // to a list). The definition doesn't mention such a limit though. + using dictionary_type = aux::map_string; + using string_type = std::string; + using list_type = std::vector; + using integer_type = std::int64_t; + using preformatted_type = std::vector; + + // the types an entry can have + enum data_type + { + int_t, + string_t, + list_t, + dictionary_t, + undefined_t, + preformatted_t + }; + + // returns the concrete type of the entry + data_type type() const; + + // constructors directly from a specific type. + // The content of the argument is copied into the + // newly constructed entry + entry(dictionary_type); // NOLINT + entry(span); // NOLINT + template ::value + || std::is_same::value + || std::is_same::value>::type> + entry(U v) // NOLINT + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) string_type(std::move(v)); + m_type = string_t; + } + entry(list_type); // NOLINT + entry(integer_type); // NOLINT + entry(preformatted_type); // NOLINT + + // construct an empty entry of the specified type. + // see data_type enum. + entry(data_type t); // NOLINT + + // hidden + entry(entry const& e); + entry(entry&& e) noexcept; + + // construct from bdecode_node parsed form (see bdecode()) + entry(bdecode_node const& n); // NOLINT + + // hidden + entry(); + + // hidden + ~entry(); + + // copies the structure of the right hand side into this + // entry. +#if TORRENT_ABI_VERSION == 1 + entry& operator=(lazy_entry const&) &; +#endif + entry& operator=(bdecode_node const&) &; + entry& operator=(entry const&) &; + entry& operator=(entry&&) & noexcept; + entry& operator=(dictionary_type) &; + entry& operator=(span) &; + template ::value + || std::is_same::value>::type> + entry& operator=(U v) & + { + destruct(); + new(&data) string_type(std::move(v)); + m_type = string_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + entry& operator=(list_type) &; + entry& operator=(integer_type) &; + entry& operator=(preformatted_type) &; + + // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions + // are accessors that return the respective type. If the ``entry`` object + // isn't of the type you request, the accessor will throw + // system_error. You can ask an ``entry`` for its type through the + // ``type()`` function. + // + // If you want to create an ``entry`` you give it the type you want it to + // have in its constructor, and then use one of the non-const accessors + // to get a reference which you then can assign the value you want it to + // have. + // + // The typical code to get info from a torrent file will then look like + // this: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // entry::dictionary_type const& dict = torrent_file.dict(); + // entry::dictionary_type::const_iterator i; + // i = dict.find("announce"); + // if (i != dict.end()) + // { + // std::string tracker_url = i->second.string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // The following code is equivalent, but a little bit shorter: + // + // .. code:: c++ + // + // entry torrent_file; + // // ... + // + // // throws if this is not a dictionary + // if (entry* i = torrent_file.find_key("announce")) + // { + // std::string tracker_url = i->string(); + // std::cout << tracker_url << "\n"; + // } + // + // + // To make it easier to extract information from a torrent file, the + // class torrent_info exists. + integer_type& integer(); + const integer_type& integer() const; + string_type& string(); + const string_type& string() const; + list_type& list(); + const list_type& list() const; + dictionary_type& dict(); + const dictionary_type& dict() const; + preformatted_type& preformatted(); + const preformatted_type& preformatted() const; + + // swaps the content of *this* with ``e``. + void swap(entry& e); + + // All of these functions requires the entry to be a dictionary, if it + // isn't they will throw ``system_error``. + // + // The non-const versions of the ``operator[]`` will return a reference + // to either the existing element at the given key or, if there is no + // element with the given key, a reference to a newly inserted element at + // that key. + // + // The const version of ``operator[]`` will only return a reference to an + // existing element at the given key. If the key is not found, it will + // throw ``system_error``. + entry& operator[](string_view key); + const entry& operator[](string_view key) const; + + // These functions requires the entry to be a dictionary, if it isn't + // they will throw ``system_error``. + // + // They will look for an element at the given key in the dictionary, if + // the element cannot be found, they will return nullptr. If an element + // with the given key is found, the return a pointer to it. + entry* find_key(string_view key); + entry const* find_key(string_view key) const; + + // returns a pretty-printed string representation + // of the bencoded structure, with JSON-style syntax + std::string to_string(bool single_line = false) const; + + protected: + + void construct(data_type t); + void copy(const entry& e); + void destruct(); + + private: + + aux::aligned_union<1 +#if TORRENT_COMPLETE_TYPES_REQUIRED + // for implementations that require complete types, use char and hope + // for the best + , std::list + , std::map +#else + , list_type + , dictionary_type +#endif + , preformatted_type + , string_type + , integer_type + >::type data; + + // the bitfield is used so that the m_type_queried field still fits, so + // that the ABI is the same for debug builds and release builds. It + // appears to be very hard to match debug builds with debug versions of + // libtorrent + std::uint8_t m_type:7; + + public: + // in debug mode this is set to false by bdecode to indicate that the + // program has not yet queried the type of this entry, and should not + // assume that it has a certain type. This is asserted in the accessor + // functions. This does not apply if exceptions are used. + mutable std::uint8_t m_type_queried:1; + }; + + // hidden + TORRENT_EXPORT bool operator==(entry const& lhs, entry const& rhs); + inline bool operator!=(entry const& lhs, entry const& rhs) { return !(lhs == rhs); } + +namespace detail { + + // internal + TORRENT_EXPORT string_view integer_to_str(span buf + , entry::integer_type val); +} + +#if TORRENT_USE_IOSTREAM + // prints the bencoded structure to the ostream as a JSON-style structure. + inline std::ostream& operator<<(std::ostream& os, const entry& e) + { + os << e.to_string(); + return os; + } +#endif + +} + +#endif // TORRENT_ENTRY_HPP_INCLUDED diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp new file mode 100644 index 0000000..f0a8212 --- /dev/null +++ b/include/libtorrent/enum_net.hpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#endif + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/bind_to_device.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/flags.hpp" + +#include + +namespace libtorrent { + +using interface_flags = flags::bitfield_flag; + +namespace if_flags { + + constexpr interface_flags up = 0_bit; + constexpr interface_flags broadcast = 1_bit; + constexpr interface_flags loopback = 2_bit; + constexpr interface_flags pointopoint = 3_bit; + constexpr interface_flags running = 4_bit; + constexpr interface_flags noarp = 5_bit; + constexpr interface_flags promisc = 6_bit; + constexpr interface_flags allmulti = 7_bit; + constexpr interface_flags master = 8_bit; + constexpr interface_flags slave = 9_bit; + constexpr interface_flags multicast = 10_bit; + constexpr interface_flags dynamic = 11_bit; + constexpr interface_flags lower_up = 12_bit; + constexpr interface_flags dormant = 13_bit; +} + +enum class if_state : std::uint8_t { + + up, + dormant, + lowerlayerdown, + down, + notpresent, + testing, + unknown +}; + + struct ip_interface + { + address interface_address; + address netmask; + char name[64]{}; + char friendly_name[128]{}; + char description[128]{}; + // an interface is preferred if its address is + // not tentative/duplicate/deprecated + bool preferred = true; + + interface_flags flags = if_flags::up; + if_state state = if_state::unknown; + }; + + struct ip_route + { + address destination; + address netmask; + address gateway; + address source_hint; + char name[64]{}; + int mtu = 0; + }; + + // returns a list of the configured IP interfaces + // on the machine + TORRENT_EXTRA_EXPORT std::vector enum_net_interfaces(io_service& ios + , error_code& ec); + + TORRENT_EXTRA_EXPORT std::vector enum_routes(io_service& ios + , error_code& ec); + + // returns AF_INET or AF_INET6, depending on the address' family + TORRENT_EXTRA_EXPORT int family(address const& a); + + // return (a1 & mask) == (a2 & mask) + TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 + , address const& a2, address const& mask); + + // return a netmask with the specified address family and the specified + // number of prefix bit set, of the most significant bits in the resulting + // netmask + TORRENT_EXTRA_EXPORT address build_netmask(int bits, int family); + + // return the gateway for the given ip_interface, if there is one. Otherwise + // return nullopt. + TORRENT_EXTRA_EXPORT boost::optional
    get_gateway( + ip_interface const& iface, span routes); + + // returns whether there is a route to the specified device for for any global + // internet address of the specified address family. + TORRENT_EXTRA_EXPORT bool has_internet_route(string_view device, int family + , span routes); + + // attempt to bind socket to the device with the specified name. For systems + // that don't support SO_BINDTODEVICE the socket will be bound to one of the + // IP addresses of the specified device. In this case it is necessary to + // verify the local endpoint of the socket once the connection is established. + // the returned address is the ip the socket was bound to (or address_v4::any() + // in case SO_BINDTODEVICE succeeded and we don't need to verify it). + // TODO: 3 use string_view for device_name + template + address bind_socket_to_device(io_service& ios, Socket& sock + , tcp const& protocol + , char const* device_name, int port, error_code& ec) + { + tcp::endpoint bind_ep(address_v4::any(), std::uint16_t(port)); + + address ip = make_address(device_name, ec); + if (!ec) + { + // this is to cover the case where "0.0.0.0" is considered any IPv4 or + // IPv6 address. If we're asking to be bound to an IPv6 address and + // providing 0.0.0.0 as the device, turn it into "::" + if (ip == address_v4::any() && protocol == boost::asio::ip::tcp::v6()) + ip = address_v6::any(); + bind_ep.address(ip); + // it appears to be an IP. Just bind to that address + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + ec.clear(); + +#if TORRENT_HAS_BINDTODEVICE + // try to use SO_BINDTODEVICE here, if that exists. If it fails, + // fall back to the mechanism we have below + aux::bind_device(sock, device_name, ec); + if (ec) +#endif + { + ec.clear(); + // TODO: 2 this could be done more efficiently by just looking up + // the interface with the given name, maybe even with if_nametoindex() + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return bind_ep.address(); + + bool found = false; + + for (auto const& iface : ifs) + { + // we're looking for a specific interface, and its address + // (which must be of the same family as the address we're + // connecting to) + if (std::strcmp(iface.name, device_name) != 0) continue; + if (iface.interface_address.is_v4() != (protocol == boost::asio::ip::tcp::v4())) + continue; + + bind_ep.address(iface.interface_address); + found = true; + break; + } + + if (!found) + { + ec = error_code(boost::system::errc::no_such_device, generic_category()); + return bind_ep.address(); + } + } + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + TORRENT_EXTRA_EXPORT std::string device_for_address(address addr + , io_service& ios, error_code& ec); + +} + +#endif diff --git a/include/libtorrent/error.hpp b/include/libtorrent/error.hpp new file mode 100644 index 0000000..0403c64 --- /dev/null +++ b/include/libtorrent/error.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_HPP_INCLUDED +#define TORRENT_ERROR_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + namespace error = boost::asio::error; +} + +#endif diff --git a/include/libtorrent/error_code.hpp b/include/libtorrent/error_code.hpp new file mode 100644 index 0000000..a45ac8b --- /dev/null +++ b/include/libtorrent/error_code.hpp @@ -0,0 +1,563 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ERROR_CODE_HPP_INCLUDED +#define TORRENT_ERROR_CODE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/operations.hpp" + +namespace libtorrent { + + namespace errors + { + // libtorrent uses boost.system's ``error_code`` class to represent + // errors. libtorrent has its own error category + // libtorrent_category() with the error codes defined by + // error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + // Two torrents has files which end up overwriting each other + file_collision, + // A piece did not match its piece hash + failed_hash_check, + // The .torrent file does not contain a bencoded dictionary at + // its top level + torrent_is_no_dict, + // The .torrent file does not have an ``info`` dictionary + torrent_missing_info, + // The .torrent file's ``info`` entry is not a dictionary + torrent_info_no_dict, + // The .torrent file does not have a ``piece length`` entry + torrent_missing_piece_length, + // The .torrent file does not have a ``name`` entry + torrent_missing_name, + // The .torrent file's name entry is invalid + torrent_invalid_name, + // The length of a file, or of the whole .torrent file is invalid. + // Either negative or not an integer + torrent_invalid_length, + // Failed to parse a file entry in the .torrent + torrent_file_parse_failed, + // The ``pieces`` field is missing or invalid in the .torrent file + torrent_missing_pieces, + // The ``pieces`` string has incorrect length + torrent_invalid_hashes, + // The .torrent file has more pieces than is supported by libtorrent + too_many_pieces_in_torrent, + // The metadata (.torrent file) that was received from the swarm + // matched the info-hash, but failed to be parsed + invalid_swarm_metadata, + // The file or buffer is not correctly bencoded + invalid_bencoding, + // The .torrent file does not contain any files + no_files_in_torrent, + // The string was not properly url-encoded as expected + invalid_escaped_string, + // Operation is not permitted since the session is shutting down + session_is_closing, + // There's already a torrent with that info-hash added to the + // session + duplicate_torrent, + // The supplied torrent_handle is not referring to a valid torrent + invalid_torrent_handle, + // The type requested from the entry did not match its type + invalid_entry_type, + // The specified URI does not contain a valid info-hash + missing_info_hash_in_uri, + // One of the files in the torrent was unexpectedly small. This + // might be caused by files being changed by an external process + file_too_short, + // The URL used an unknown protocol. Currently ``http`` and + // ``https`` (if built with openssl support) are recognized. For + // trackers ``udp`` is recognized as well. + unsupported_url_protocol, + // The URL did not conform to URL syntax and failed to be parsed + url_parse_error, + // The peer sent a piece message of length 0 + peer_sent_empty_piece, + // A bencoded structure was corrupt and failed to be parsed + parse_failed, + // The fast resume file was missing or had an invalid file version + // tag + invalid_file_tag, + // The fast resume file was missing or had an invalid info-hash + missing_info_hash, + // The info-hash did not match the torrent + mismatching_info_hash, + // The URL contained an invalid hostname + invalid_hostname, + // The URL had an invalid port + invalid_port, + // The port is blocked by the port-filter, and prevented the + // connection + port_blocked, + // The IPv6 address was expected to end with "]" + expected_close_bracket_in_address, + // The torrent is being destructed, preventing the operation to + // succeed + destructing_torrent, + // The connection timed out + timed_out, + // The peer is upload only, and we are upload only. There's no point + // in keeping the connection + upload_upload_connection, + // The peer is upload only, and we're not interested in it. There's + // no point in keeping the connection + uninteresting_upload_peer, + // The peer sent an unknown info-hash + invalid_info_hash, + // The torrent is paused, preventing the operation from succeeding + torrent_paused, + // The peer sent an invalid have message, either wrong size or + // referring to a piece that doesn't exist in the torrent + invalid_have, + // The bitfield message had the incorrect size + invalid_bitfield_size, + // The peer kept requesting pieces after it was choked, possible + // abuse attempt. + too_many_requests_when_choked, + // The peer sent a piece message that does not correspond to a + // piece request sent by the client + invalid_piece, + // memory allocation failed + no_memory, + // The torrent is aborted, preventing the operation to succeed + torrent_aborted, + // The peer is a connection to ourself, no point in keeping it + self_connection, + // The peer sent a piece message with invalid size, either negative + // or greater than one block + invalid_piece_size, + // The peer has not been interesting or interested in us for too + // long, no point in keeping it around + timed_out_no_interest, + // The peer has not said anything in a long time, possibly dead + timed_out_inactivity, + // The peer did not send a handshake within a reasonable amount of + // time, it might not be a bittorrent peer + timed_out_no_handshake, + // The peer has been unchoked for too long without requesting any + // data. It might be lying about its interest in us + timed_out_no_request, + // The peer sent an invalid choke message + invalid_choke, + // The peer send an invalid unchoke message + invalid_unchoke, + // The peer sent an invalid interested message + invalid_interested, + // The peer sent an invalid not-interested message + invalid_not_interested, + // The peer sent an invalid piece request message + invalid_request, + // The peer sent an invalid hash-list message (this is part of the + // merkle-torrent extension) + invalid_hash_list, + // The peer sent an invalid hash-piece message (this is part of the + // merkle-torrent extension) + invalid_hash_piece, + // The peer sent an invalid cancel message + invalid_cancel, + // The peer sent an invalid DHT port-message + invalid_dht_port, + // The peer sent an invalid suggest piece-message + invalid_suggest, + // The peer sent an invalid have all-message + invalid_have_all, + // The peer sent an invalid have none-message + invalid_have_none, + // The peer sent an invalid reject message + invalid_reject, + // The peer sent an invalid allow fast-message + invalid_allow_fast, + // The peer sent an invalid extension message ID + invalid_extended, + // The peer sent an invalid message ID + invalid_message, + // The synchronization hash was not found in the encrypted handshake + sync_hash_not_found, + // The encryption constant in the handshake is invalid + invalid_encryption_constant, + // The peer does not support plain text, which is the selected mode + no_plaintext_mode, + // The peer does not support RC4, which is the selected mode + no_rc4_mode, + // The peer does not support any of the encryption modes that the + // client supports + unsupported_encryption_mode, + // The peer selected an encryption mode that the client did not + // advertise and does not support + unsupported_encryption_mode_selected, + // The pad size used in the encryption handshake is of invalid size + invalid_pad_size, + // The encryption handshake is invalid + invalid_encrypt_handshake, + // The client is set to not support incoming encrypted connections + // and this is an encrypted connection + no_incoming_encrypted, + // The client is set to not support incoming regular bittorrent + // connections, and this is a regular connection + no_incoming_regular, + // The client is already connected to this peer-ID + duplicate_peer_id, + // Torrent was removed + torrent_removed, + // The packet size exceeded the upper sanity check-limit + packet_too_large, + + reserved, + + // The web server responded with an error + http_error, + // The web server response is missing a location header + missing_location, + // The web seed redirected to a path that no longer matches the + // .torrent directory structure + invalid_redirection, + // The connection was closed because it redirected to a different + // URL + redirecting, + // The HTTP range header is invalid + invalid_range, + // The HTTP response did not have a content length + no_content_length, + // The IP is blocked by the IP filter + banned_by_ip_filter, + // At the connection limit + too_many_connections, + // The peer is marked as banned + peer_banned, + // The torrent is stopping, causing the operation to fail + stopping_torrent, + // The peer has sent too many corrupt pieces and is banned + too_many_corrupt_pieces, + // The torrent is not ready to receive peers + torrent_not_ready, + // The peer is not completely constructed yet + peer_not_constructed, + // The session is closing, causing the operation to fail + session_closing, + // The peer was disconnected in order to leave room for a + // potentially better peer + optimistic_disconnect, + // The torrent is finished + torrent_finished, + // No UPnP router found + no_router, + // The metadata message says the metadata exceeds the limit + metadata_too_large, + // The peer sent an invalid metadata request message + invalid_metadata_request, + // The peer advertised an invalid metadata size + invalid_metadata_size, + // The peer sent a message with an invalid metadata offset + invalid_metadata_offset, + // The peer sent an invalid metadata message + invalid_metadata_message, + // The peer sent a peer exchange message that was too large + pex_message_too_large, + // The peer sent an invalid peer exchange message + invalid_pex_message, + // The peer sent an invalid tracker exchange message + invalid_lt_tracker_message, + // The peer sent an pex messages too often. This is a possible + // attempt of and attack + too_frequent_pex, + // The operation failed because it requires the torrent to have + // the metadata (.torrent file) and it doesn't have it yet. + // This happens for magnet links before they have downloaded the + // metadata, and also torrents added by URL. + no_metadata, + // The peer sent an invalid ``dont_have`` message. The don't have + // message is an extension to allow peers to advertise that the + // no longer has a piece they previously had. + invalid_dont_have, + // The peer tried to connect to an SSL torrent without connecting + // over SSL. + requires_ssl_connection, + // The peer tried to connect to a torrent with a certificate + // for a different torrent. + invalid_ssl_cert, + // the torrent is not an SSL torrent, and the operation requires + // an SSL torrent + not_an_ssl_torrent, + // peer was banned because its listen port is within a banned port + // range, as specified by the port_filter. + banned_by_port_filter, + // The session_handle is not referring to a valid session_impl + invalid_session_handle, + // the listen socket associated with this request was closed + invalid_listen_socket, + +#if TORRENT_ABI_VERSION == 1 + // these error codes are deprecated, NAT-PMP/PCP error codes have + // been moved to their own category + + // The NAT-PMP router responded with an unsupported protocol version + unsupported_protocol_version TORRENT_DEPRECATED_ENUM = 120, + // You are not authorized to map ports on this NAT-PMP router + natpmp_not_authorized TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because of a network failure + network_failure TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because of lack of resources + no_resources TORRENT_DEPRECATED_ENUM, + // The NAT-PMP router failed because an unsupported opcode was sent + unsupported_opcode TORRENT_DEPRECATED_ENUM, +#else + deprecated_120 = 120, + deprecated_121, + deprecated_122, + deprecated_123, + deprecated_124, +#endif + + + // The resume data file is missing the ``file sizes`` entry + missing_file_sizes = 130, + // The resume data file ``file sizes`` entry is empty + no_files_in_resume_data, + // The resume data file is missing the ``pieces`` and ``slots`` entry + missing_pieces, + // The number of files in the resume data does not match the number + // of files in the torrent + mismatching_number_of_files, + // One of the files on disk has a different size than in the fast + // resume file + mismatching_file_size, + // One of the files on disk has a different timestamp than in the + // fast resume file + mismatching_file_timestamp, + // The resume data file is not a dictionary + not_a_dictionary, + // The ``blocks per piece`` entry is invalid in the resume data file + invalid_blocks_per_piece, + // The resume file is missing the ``slots`` entry, which is required + // for torrents with compact allocation. *DEPRECATED* + missing_slots, + // The resume file contains more slots than the torrent + too_many_slots, + // The ``slot`` entry is invalid in the resume data + invalid_slot_list, + // One index in the ``slot`` list is invalid + invalid_piece_index, + // The pieces on disk needs to be re-ordered for the specified + // allocation mode. This happens if you specify sparse allocation + // and the files on disk are using compact storage. The pieces needs + // to be moved to their right position. *DEPRECATED* + pieces_need_reorder, + // this error is returned when asking to save resume data and + // specifying the flag to only save when there's anything new to save + // (torrent_handle::only_if_modified) and there wasn't anything changed. + resume_data_not_modified, + + + + // The HTTP header was not correctly formatted + http_parse_error = 150, + // The HTTP response was in the 300-399 range but lacked a location + // header + http_missing_location, + // The HTTP response was encoded with gzip or deflate but + // decompressing it failed + http_failed_decompress, + + + + // The URL specified an i2p address, but no i2p router is configured + no_i2p_router = 160, + // i2p acceptor is not available yet, can't announce without endpoint + no_i2p_endpoint = 161, + + + + // The tracker URL doesn't support transforming it into a scrape + // URL. i.e. it doesn't contain "announce. + scrape_not_available = 170, + // invalid tracker response + invalid_tracker_response, + // invalid peer dictionary entry. Not a dictionary + invalid_peer_dict, + // tracker sent a failure message + tracker_failure, + // missing or invalid ``files`` entry + invalid_files_entry, + // missing or invalid ``hash`` entry + invalid_hash_entry, + // missing or invalid ``peers`` and ``peers6`` entry + invalid_peers_entry, + // UDP tracker response packet has invalid size + invalid_tracker_response_length, + // invalid transaction id in UDP tracker response + invalid_tracker_transaction_id, + // invalid action field in UDP tracker response + invalid_tracker_action, + +#if TORRENT_ABI_VERSION == 1 + // expected string in bencoded string + expected_string = 190, + // expected colon in bencoded string + expected_colon, + // unexpected end of file in bencoded string + unexpected_eof, + // expected value (list, dict, int or string) in bencoded string + expected_value, + // bencoded recursion depth limit exceeded + depth_exceeded, + // bencoded item count limit exceeded + limit_exceeded, + // integer overflow + overflow, +#endif + + // random number generation failed + no_entropy = 200, + + // the number of error codes + error_code_max + }; + + // HTTP errors are reported in the libtorrent::http_category, with error code enums in + // the ``libtorrent::errors`` namespace. + enum http_errors + { + cont = 100, + ok = 200, + created = 201, + accepted = 202, + no_content = 204, + multiple_choices = 300, + moved_permanently = 301, + moved_temporarily = 302, + not_modified = 304, + bad_request = 400, + unauthorized = 401, + forbidden = 403, + not_found = 404, + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + + } // namespace errors + + // return the instance of the libtorrent_error_category which + // maps libtorrent error codes to human readable error messages. + TORRENT_EXPORT boost::system::error_category& libtorrent_category(); + + // returns the error_category for HTTP errors + TORRENT_EXPORT boost::system::error_category& http_category(); + + using error_code = boost::system::error_code; + using error_condition = boost::system::error_condition; + + // internal + using boost::system::generic_category; + using boost::system::system_category; + + using system_error = boost::system::system_error; + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_libtorrent_category() + { return libtorrent_category(); } + + TORRENT_DEPRECATED + inline boost::system::error_category& get_http_category() + { return http_category(); } +#endif +#endif + + // used by storage to return errors + // also includes which underlying file the + // error happened on + struct TORRENT_EXPORT storage_error + { + // hidden + storage_error(): file_idx(-1), operation(operation_t::unknown) {} + explicit storage_error(error_code e): ec(e), file_idx(-1), operation(operation_t::unknown) {} + + // explicitly converts to true if this object represents an error, and + // false if it does not. + explicit operator bool() const { return ec.value() != 0; } + + // the error that occurred + error_code ec; + + // set and query the index (in the torrent) of the file this error + // occurred on. This may also have special values defined in + // torrent_status. + file_index_t file() const { return file_index_t(file_idx); } + void file(file_index_t f) { file_idx = static_cast(f); } + + // internal + std::int32_t file_idx:24; + + // A code from operation_t enum, indicating what + // kind of operation failed. + operation_t operation; + +#if TORRENT_ABI_VERSION == 1 + // Returns a string literal representing the file operation + // that failed. If there were no failure, it returns + // an empty string. + char const* operation_str() const TORRENT_DEPRECATED_MEMBER + { return operation_name(operation); } +#endif + }; + + // internal + std::string print_error(error_code const&); +} + +namespace boost { namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + + template<> struct is_error_code_enum + { static const bool value = true; }; +} } + +#endif diff --git a/include/libtorrent/extensions.hpp b/include/libtorrent/extensions.hpp new file mode 100644 index 0000000..1e52b84 --- /dev/null +++ b/include/libtorrent/extensions.hpp @@ -0,0 +1,519 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_EXTENSIONS_HPP_INCLUDED +#define TORRENT_EXTENSIONS_HPP_INCLUDED + +#include "libtorrent/units.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/torrent_status.hpp" // for torrent_status::state_t + +// OVERVIEW +// +// libtorrent has a plugin interface for implementing extensions to the protocol. +// These can be general extensions for transferring metadata or peer exchange +// extensions, or it could be used to provide a way to customize the protocol +// to fit a particular (closed) network. +// +// In short, the plugin interface makes it possible to: +// +// * register extension messages (sent in the extension handshake), see +// extensions_. +// * add data and parse data from the extension handshake. +// * send extension messages and standard bittorrent messages. +// * override or block the handling of standard bittorrent messages. +// * save and restore state via the session state +// * see all alerts that are posted +// +// .. _extensions: extension_protocol.html +// +// a word of caution +// ----------------- +// +// Writing your own plugin is a very easy way to introduce serious bugs such as +// dead locks and race conditions. Since a plugin has access to internal +// structures it is also quite easy to sabotage libtorrent's operation. +// +// All the callbacks are always called from the libtorrent network thread. In +// case portions of your plugin are called from other threads, typically the main +// thread, you cannot use any of the member functions on the internal structures +// in libtorrent, since those require being called from the libtorrent network +// thread . Furthermore, you also need to synchronize your own shared data +// within the plugin, to make sure it is not accessed at the same time from the +// libtorrent thread (through a callback). If you need to send out a message +// from another thread, it is advised to use an internal queue, and do the +// actual sending in ``tick()``. +// +// Since the plugin interface gives you easy access to internal structures, it +// is not supported as a stable API. Plugins should be considered specific to a +// specific version of libtorrent. Although, in practice the internals mostly +// don't change that dramatically. +// +// +// plugin-interface +// ================ +// +// The plugin interface consists of three base classes that the plugin may +// implement. These are called plugin, torrent_plugin and peer_plugin. +// They are found in the ```` header. +// +// These plugins are instantiated for each session, torrent and possibly each peer, +// respectively. +// +// For plugins that only need per torrent state, it is enough to only implement +// ``torrent_plugin`` and pass a constructor function or function object to +// ``session::add_extension()`` or ``torrent_handle::add_extension()`` (if the +// torrent has already been started and you want to hook in the extension at +// run-time). +// +// The signature of the function is: +// +// .. code:: c++ +// +// std::shared_ptr (*)(torrent_handle const&, void*); +// +// The second argument is the userdata passed to ``session::add_torrent()`` or +// ``torrent_handle::add_extension()``. +// +// The function should return a ``std::shared_ptr`` which +// may or may not be 0. If it is a nullptr, the extension is simply ignored +// for this torrent. If it is a valid pointer (to a class inheriting +// ``torrent_plugin``), it will be associated with this torrent and callbacks +// will be made on torrent events. +// +// For more elaborate plugins which require session wide state, you would +// implement ``plugin``, construct an object (in a ``std::shared_ptr``) and pass +// it in to ``session::add_extension()``. +// +// custom alerts +// ============= +// +// Since plugins are running within internal libtorrent threads, one convenient +// way to communicate with the client is to post custom alerts. +// +// The expected interface of any alert, apart from deriving from the alert +// base class, looks like this: +// +// .. parsed-literal:: +// +// static const int alert_type = **; +// virtual int type() const { return alert_type; } +// +// virtual std::string message() const; +// +// static const alert_category_t static_category = **; +// virtual alert_category_t category() const { return static_category; } +// +// virtual char const* what() const { return **; } +// +// The ``alert_type`` is used for the type-checking in ``alert_cast``. It must +// not collide with any other alert. The built-in alerts in libtorrent will +// not use alert type IDs greater than ``user_alert_id``. When defining your +// own alert, make sure it's greater than this constant. +// +// ``type()`` is the run-time equivalence of the ``alert_type``. +// +// The ``message()`` virtual function is expected to construct a useful +// string representation of the alert and the event or data it represents. +// Something convenient to put in a log file for instance. +// +// ``clone()`` is used internally to copy alerts. The suggested implementation +// of simply allocating a new instance as a copy of ``*this`` is all that's +// expected. +// +// The static category is required for checking whether or not the category +// for a specific alert is enabled or not, without instantiating the alert. +// The ``category`` virtual function is the run-time equivalence. +// +// The ``what()`` virtual function may simply be a string literal of the class +// name of your alert. +// +// For more information, see the `alert section`_. +// +// .. _`alert section`: reference-Alerts.html + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + + // these are flags that can be returned by implemented_features() + // indicating which callbacks this plugin is interested in + using feature_flags_t = flags::bitfield_flag; + + // this is the base class for a session plugin. One primary feature + // is that it is notified of all torrents that are added to the session, + // and can add its own torrent_plugins. + struct TORRENT_EXPORT plugin + { + // hidden + virtual ~plugin() {} + +#if TORRENT_ABI_VERSION == 1 + using feature_flags_t = libtorrent::feature_flags_t; +#endif + + // include this bit if your plugin needs to alter the order of the + // optimistic unchoke of peers. i.e. have the on_optimistic_unchoke() + // callback be called. + static constexpr feature_flags_t optimistic_unchoke_feature = 1_bit; + + // include this bit if your plugin needs to have on_tick() called + static constexpr feature_flags_t tick_feature = 2_bit; + + // include this bit if your plugin needs to have on_dht_request() + // called + static constexpr feature_flags_t dht_request_feature = 3_bit; + + // include this bit if your plugin needs to have on_alert() + // called + static constexpr feature_flags_t alert_feature = 4_bit; + + // This function is expected to return a bitmask indicating which features + // this plugin implements. Some callbacks on this object may not be called + // unless the corresponding feature flag is returned here. Note that + // callbacks may still be called even if the corresponding feature is not + // specified in the return value here. See feature_flags_t for possible + // flags to return. + virtual feature_flags_t implemented_features() { return {}; } + + // this is called by the session every time a new torrent is added. + // The ``torrent*`` points to the internal torrent object created + // for the new torrent. The ``void*`` is the userdata pointer as + // passed in via add_torrent_params. + // + // If the plugin returns a torrent_plugin instance, it will be added + // to the new torrent. Otherwise, return an empty shared_ptr to a + // torrent_plugin (the default). + virtual std::shared_ptr new_torrent(torrent_handle const&, void*) + { return std::shared_ptr(); } + + // called when plugin is added to a session + virtual void added(session_handle const&) {} + + // called when a dht request is received. + // If your plugin expects this to be called, make sure to include the flag + // ``dht_request_feature`` in the return value from implemented_features(). + virtual bool on_dht_request(string_view /* query */ + , udp::endpoint const& /* source */, bdecode_node const& /* message */ + , entry& /* response */) + { return false; } + + // called when an alert is posted alerts that are filtered are not posted. + // If your plugin expects this to be called, make sure to include the flag + // ``alert_feature`` in the return value from implemented_features(). + virtual void on_alert(alert const*) {} + + // return true if the add_torrent_params should be added + virtual bool on_unknown_torrent(sha1_hash const& /* info_hash */ + , peer_connection_handle const& /* pc */, add_torrent_params& /* p */) + { return false; } + + // called once per second. + // If your plugin expects this to be called, make sure to include the flag + // ``tick_feature`` in the return value from implemented_features(). + virtual void on_tick() {} + + // called when choosing peers to optimistically unchoke. The return value + // indicates the peer's priority for unchoking. Lower return values + // correspond to higher priority. Priorities above 2^63-1 are reserved. + // If your plugin has no priority to assign a peer it should return 2^64-1. + // If your plugin expects this to be called, make sure to include the flag + // ``optimistic_unchoke_feature`` in the return value from implemented_features(). + // If multiple plugins implement this function the lowest return value + // (i.e. the highest priority) is used. + virtual uint64_t get_unchoke_priority(peer_connection_handle const& /* peer */) + { return (std::numeric_limits::max)(); } + + // called when saving settings state + virtual void save_state(entry&) {} + + // called when loading settings state + virtual void load_state(bdecode_node const&) {} + }; + + using add_peer_flags_t = flags::bitfield_flag; + + // Torrent plugins are associated with a single torrent and have a number + // of functions called at certain events. Many of its functions have the + // ability to change or override the default libtorrent behavior. + struct TORRENT_EXPORT torrent_plugin + { + // hidden + virtual ~torrent_plugin() {} + +#if TORRENT_ABI_VERSION == 1 + using flags_t = libtorrent::add_peer_flags_t; +#endif + + // This function is called each time a new peer is connected to the torrent. You + // may choose to ignore this by just returning a default constructed + // ``shared_ptr`` (in which case you don't need to override this member + // function). + // + // If you need an extension to the peer connection (which most plugins do) you + // are supposed to return an instance of your peer_plugin class. Which in + // turn will have its hook functions called on event specific to that peer. + // + // The ``peer_connection_handle`` will be valid as long as the ``shared_ptr`` + // is being held by the torrent object. So, it is generally a good idea to not + // keep a ``shared_ptr`` to your own peer_plugin. If you want to keep references + // to it, use ``weak_ptr``. + // + // If this function throws an exception, the connection will be closed. + virtual std::shared_ptr new_connection(peer_connection_handle const&) + { return std::shared_ptr(); } + + // These hooks are called when a piece passes the hash check or fails the hash + // check, respectively. The ``index`` is the piece index that was downloaded. + // It is possible to access the list of peers that participated in sending the + // piece through the ``torrent`` and the ``piece_picker``. + virtual void on_piece_pass(piece_index_t) {} + virtual void on_piece_failed(piece_index_t) {} + + // This hook is called approximately once per second. It is a way of making it + // easy for plugins to do timed events, for sending messages or whatever. + virtual void tick() {} + + // These hooks are called when the torrent is paused and resumed respectively. + // The return value indicates if the event was handled. A return value of + // ``true`` indicates that it was handled, and no other plugin after this one + // will have this hook function called, and the standard handler will also not be + // invoked. So, returning true effectively overrides the standard behavior of + // pause or resume. + // + // Note that if you call ``pause()`` or ``resume()`` on the torrent from your + // handler it will recurse back into your handler, so in order to invoke the + // standard handler, you have to keep your own state on whether you want standard + // behavior or overridden behavior. + virtual bool on_pause() { return false; } + virtual bool on_resume() { return false; } + + // This function is called when the initial files of the torrent have been + // checked. If there are no files to check, this function is called immediately. + // + // i.e. This function is always called when the torrent is in a state where it + // can start downloading. + virtual void on_files_checked() {} + + // called when the torrent changes state + // the state is one of torrent_status::state_t + // enum members + virtual void on_state(torrent_status::state_t) {} + + // this is the first time we see this peer + static constexpr add_peer_flags_t first_time = 1_bit; + + // this peer was not added because it was + // filtered by the IP filter + static constexpr add_peer_flags_t filtered = 2_bit; + + // called every time a new peer is added to the peer list. + // This is before the peer is connected to. For ``flags``, see + // torrent_plugin::flags_t. The ``source`` argument refers to + // the source where we learned about this peer from. It's a + // bitmask, because many sources may have told us about the same + // peer. For peer source flags, see peer_info::peer_source_flags. + virtual void on_add_peer(tcp::endpoint const&, + peer_source_flags_t, add_peer_flags_t) {} + }; + + // peer plugins are associated with a specific peer. A peer could be + // both a regular bittorrent peer (``bt_peer_connection``) or one of the + // web seed connections (``web_peer_connection`` or ``http_seed_connection``). + // In order to only attach to certain peers, make your + // torrent_plugin::new_connection only return a plugin for certain peer + // connection types + struct TORRENT_EXPORT peer_plugin + { + // hidden + virtual ~peer_plugin() {} + + // This function is expected to return the name of + // the plugin. + virtual string_view type() const { return {}; } + + // can add entries to the extension handshake + // this is not called for web seeds + virtual void add_handshake(entry&) {} + + // called when the peer is being disconnected. + virtual void on_disconnect(error_code const&) {} + + // called when the peer is successfully connected. Note that + // incoming connections will have been connected by the time + // the peer plugin is attached to it, and won't have this hook + // called. + virtual void on_connected() {} + + // throwing an exception from any of the handlers (except add_handshake) + // closes the connection + + // this is called when the initial bittorrent handshake is received. + // Returning false means that the other end doesn't support this extension + // and will remove it from the list of plugins. this is not called for web + // seeds + virtual bool on_handshake(span) { return true; } + + // called when the extension handshake from the other end is received + // if this returns false, it means that this extension isn't + // supported by this peer. It will result in this peer_plugin + // being removed from the peer_connection and destructed. + // this is not called for web seeds + virtual bool on_extension_handshake(bdecode_node const&) { return true; } + + // returning true from any of the message handlers + // indicates that the plugin has handled the message. + // it will break the plugin chain traversing and not let + // anyone else handle the message, including the default + // handler. + virtual bool on_choke() { return false; } + virtual bool on_unchoke() { return false; } + virtual bool on_interested() { return false; } + virtual bool on_not_interested() { return false; } + virtual bool on_have(piece_index_t) { return false; } + virtual bool on_dont_have(piece_index_t) { return false; } + virtual bool on_bitfield(bitfield const& /*bitfield*/) { return false; } + virtual bool on_have_all() { return false; } + virtual bool on_have_none() { return false; } + virtual bool on_allowed_fast(piece_index_t) { return false; } + virtual bool on_request(peer_request const&) { return false; } + + // This function is called when the peer connection is receiving + // a piece. ``buf`` points (non-owning pointer) to the data in an + // internal immutable disk buffer. The length of the data is specified + // in the ``length`` member of the ``piece`` parameter. + // returns true to indicate that the piece is handled and the + // rest of the logic should be ignored. + virtual bool on_piece(peer_request const& /*piece*/ + , span /*buf*/) { return false; } + + virtual bool on_cancel(peer_request const&) { return false; } + virtual bool on_reject(peer_request const&) { return false; } + virtual bool on_suggest(piece_index_t) { return false; } + + // called after a choke message has been sent to the peer + virtual void sent_unchoke() {} + + // called after piece data has been sent to the peer + // this can be used for stats book keeping + virtual void sent_payload(int /* bytes */) {} + + // called when libtorrent think this peer should be disconnected. + // if the plugin returns false, the peer will not be disconnected. + virtual bool can_disconnect(error_code const& /*ec*/) { return true; } + + // called when an extended message is received. If returning true, + // the message is not processed by any other plugin and if false + // is returned the next plugin in the chain will receive it to + // be able to handle it. This is not called for web seeds. + // thus function may be called more than once per incoming message, but + // only the last of the calls will the ``body`` size equal the ``length``. + // i.e. Every time another fragment of the message is received, this + // function will be called, until finally the whole message has been + // received. The purpose of this is to allow early disconnects for invalid + // messages and for reporting progress of receiving large messages. + virtual bool on_extended(int /*length*/, int /*msg*/, + span /*body*/) + { return false; } + + // this is not called for web seeds + virtual bool on_unknown_message(int /*length*/, int /*msg*/, + span /*body*/) + { return false; } + + // called when a piece that this peer participated in either + // fails or passes the hash_check + virtual void on_piece_pass(piece_index_t) {} + virtual void on_piece_failed(piece_index_t) {} + + // called approximately once every second + virtual void tick() {} + + // called each time a request message is to be sent. If true + // is returned, the original request message won't be sent and + // no other plugin will have this function called. + virtual bool write_request(peer_request const&) { return false; } + }; +#endif // TORRENT_DISABLE_EXTENSIONS + +#if !defined TORRENT_DISABLE_ENCRYPTION + + struct TORRENT_EXPORT crypto_plugin + { + // hidden + virtual ~crypto_plugin() {} + + virtual void set_incoming_key(span key) = 0; + virtual void set_outgoing_key(span key) = 0; + + // encrypted the provided buffers and returns the number of bytes which + // are now ready to be sent to the lower layer. This must be at least + // as large as the number of bytes passed in and may be larger if there + // is additional data to be inserted at the head of the send buffer. + // The additional data is returned as the second tuple value. Any + // returned buffer as well as the iovec itself, to be prepended to the + // send buffer, must be owned by the crypto plugin and guaranteed to stay + // alive until the crypto_plugin is destructed or this function is called + // again. + virtual std::tuple>> + encrypt(span> /*send_vec*/) = 0; + + // decrypt the provided buffers. + // returns is a tuple representing the values + // (consume, produce, packet_size) + // + // consume is set to the number of bytes which should be trimmed from the + // head of the buffers, default is 0 + // + // produce is set to the number of bytes of payload which are now ready to + // be sent to the upper layer. default is the number of bytes passed in receive_vec + // + // packet_size is set to the minimum number of bytes which must be read to + // advance the next step of decryption. default is 0 + virtual std::tuple decrypt(span> /*receive_vec*/) = 0; + }; + +#endif // TORRENT_DISABLE_ENCRYPTION +} + +#endif // TORRENT_EXTENSIONS_HPP_INCLUDED diff --git a/include/libtorrent/extensions/smart_ban.hpp b/include/libtorrent/extensions/smart_ban.hpp new file mode 100644 index 0000000..f50bc18 --- /dev/null +++ b/include/libtorrent/extensions/smart_ban.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SMART_BAN_HPP_INCLUDED +#define TORRENT_SMART_BAN_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + + // constructor function for the smart ban extension. The extension keeps + // track of the data peers have sent us for failing pieces and once the + // piece completes and passes the hash check bans the peers that turned + // out to have sent corrupt data. + // This function can either be passed in the add_torrent_params::extensions + // field, or via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_smart_ban_plugin(torrent_handle const&, void*); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_SMART_BAN_HPP_INCLUDED diff --git a/include/libtorrent/extensions/ut_metadata.hpp b/include/libtorrent/extensions/ut_metadata.hpp new file mode 100644 index 0000000..efc2918 --- /dev/null +++ b/include/libtorrent/extensions/ut_metadata.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_METADATA_HPP_INCLUDED +#define TORRENT_UT_METADATA_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + + // constructor function for the ut_metadata extension. The ut_metadata + // extension allows peers to request the .torrent file (or more + // specifically the info-dictionary of the .torrent file) from each + // other. This is the main building block in making magnet links work. + // This extension is enabled by default unless explicitly disabled in + // the session constructor. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_ut_metadata_plugin(torrent_handle const&, void*); +} + +#endif // TORRENT_DISABLE_EXTENSIONS +#endif // TORRENT_UT_METADATA_HPP_INCLUDED diff --git a/include/libtorrent/extensions/ut_pex.hpp b/include/libtorrent/extensions/ut_pex.hpp new file mode 100644 index 0000000..c928804 --- /dev/null +++ b/include/libtorrent/extensions/ut_pex.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2006, MassaRoddel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED +#define TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + + // constructor function for the ut_pex extension. The ut_pex + // extension allows peers to gossip about their connections, allowing + // the swarm stay well connected and peers aware of more peers in the + // swarm. This extension is enabled by default unless explicitly disabled in + // the session constructor. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_ut_pex_plugin(torrent_handle const&, void*); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_UT_PEX_EXTENSION_HPP_INCLUDED diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp new file mode 100644 index 0000000..71ff86a --- /dev/null +++ b/include/libtorrent/file.hpp @@ -0,0 +1,199 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_HPP_INCLUDED +#define TORRENT_FILE_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/flags.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#ifdef TORRENT_WINDOWS +// windows part +#include "libtorrent/aux_/windows.hpp" +#include +#include +#else +// posix part +#define _FILE_OFFSET_BITS 64 + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include +#include // for DIR + +#undef _FILE_OFFSET_BITS + +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent { + +#ifdef TORRENT_WINDOWS + using handle_type = HANDLE; +#else + using handle_type = int; +#endif + + class TORRENT_EXTRA_EXPORT directory : public boost::noncopyable + { + public: + directory(std::string const& path, error_code& ec); + ~directory(); + void next(error_code& ec); + std::string file() const; + bool done() const { return m_done; } + private: +#ifdef TORRENT_WINDOWS + HANDLE m_handle; + WIN32_FIND_DATAW m_fd; +#else + DIR* m_handle; + std::string m_name; +#endif + bool m_done; + }; + + struct file; + + using file_handle = std::shared_ptr; + + // hidden + using open_mode_t = flags::bitfield_flag; + + // the open mode for files. Used for the file constructor or + // file::open(). + namespace open_mode { + + // open the file for reading only + constexpr open_mode_t read_only{}; + + // open the file for writing only + constexpr open_mode_t write_only = 0_bit; + + // open the file for reading and writing + constexpr open_mode_t read_write = 1_bit; + + // the mask for the bits making up the read-write mode. + constexpr open_mode_t rw_mask = read_only | write_only | read_write; + + // open the file in sparse mode (if supported by the + // filesystem). + constexpr open_mode_t sparse = 2_bit; + + // don't update the access timestamps on the file (if + // supported by the operating system and filesystem). + // this generally improves disk performance. + constexpr open_mode_t no_atime = 3_bit; + + // open the file for random access. This disables read-ahead + // logic + constexpr open_mode_t random_access = 4_bit; + + // don't put any pressure on the OS disk cache + // because of access to this file. We expect our + // files to be fairly large, and there is already + // a cache at the bittorrent block level. This + // may improve overall system performance by + // leaving running applications in the page cache + constexpr open_mode_t no_cache = 5_bit; + + // this is only used for readv/writev flags + constexpr open_mode_t coalesce_buffers = 6_bit; + + // when creating a file, set the hidden attribute (windows only) + constexpr open_mode_t attribute_hidden = 7_bit; + + // when creating a file, set the executable attribute + constexpr open_mode_t attribute_executable = 8_bit; + + // the mask of all attribute bits + constexpr open_mode_t attribute_mask = attribute_hidden | attribute_executable; + } + + struct TORRENT_EXTRA_EXPORT file : boost::noncopyable + { + file(); + file(file&&) noexcept; + file& operator=(file&&); + file(std::string const& p, open_mode_t m, error_code& ec); + ~file(); + + bool open(std::string const& p, open_mode_t m, error_code& ec); + bool is_open() const; + void close(); + bool set_size(std::int64_t size, error_code& ec); + + open_mode_t open_mode() const { return m_open_mode; } + + std::int64_t writev(std::int64_t file_offset, span bufs + , error_code& ec, open_mode_t flags = open_mode_t{}); + std::int64_t readv(std::int64_t file_offset, span bufs + , error_code& ec, open_mode_t flags = open_mode_t{}); + + std::int64_t get_size(error_code& ec) const; + + handle_type native_handle() const { return m_file_handle; } + + private: + + handle_type m_file_handle; + + open_mode_t m_open_mode{}; + }; +} + +#endif // TORRENT_FILE_HPP_INCLUDED diff --git a/include/libtorrent/file_pool.hpp b/include/libtorrent/file_pool.hpp new file mode 100644 index 0000000..32d6fe3 --- /dev/null +++ b/include/libtorrent/file_pool.hpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_POOL_HPP +#define TORRENT_FILE_POOL_HPP + +#include +#include +#include + +#include "libtorrent/file.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/disk_interface.hpp" // for open_file_state + +namespace libtorrent { + + class file_storage; + struct open_file_state; + + // this is an internal cache of open file handles. It's primarily used by + // storage_interface implementations. It provides semi weak guarantees of + // not opening more file handles than specified. Given multiple threads, + // each with the ability to lock a file handle (via smart pointer), there + // may be windows where more file handles are open. + struct TORRENT_EXPORT file_pool : boost::noncopyable + { + // ``size`` specifies the number of allowed files handles + // to hold open at any given time. + explicit file_pool(int size = 40); + ~file_pool(); + + // return an open file handle to file at ``file_index`` in the + // file_storage ``fs`` opened at save path ``p``. ``m`` is the + // file open mode (see file::open_mode_t). + file_handle open_file(storage_index_t st, std::string const& p + , file_index_t file_index, file_storage const& fs, open_mode_t m + , error_code& ec); + // release all files belonging to the specified storage_interface (``st``) + // the overload that takes ``file_index`` releases only the file with + // that index in storage ``st``. + void release(); + void release(storage_index_t st); + void release(storage_index_t st, file_index_t file_index); + + // update the allowed number of open file handles to ``size``. + void resize(int size); + + // returns the current limit of number of allowed open file handles held + // by the file_pool. + int size_limit() const { return m_size; } + + // internal + void set_low_prio_io(bool b) { m_low_prio_io = b; } + std::vector get_status(storage_index_t st) const; + + // close the file that was opened least recently (i.e. not *accessed* + // least recently). The purpose is to make the OS (really just windows) + // clear and flush its disk cache associated with this file. We don't want + // any file to stay open for too long, allowing the disk cache to accrue. + void close_oldest(); + + private: + + file_handle remove_oldest(std::unique_lock&); + + int m_size; + bool m_low_prio_io = false; + + struct lru_file_entry + { + file_handle file_ptr; + time_point const opened{aux::time_now()}; + time_point last_use{opened}; + open_mode_t mode{}; + }; + + // maps storage pointer, file index pairs to the + // LRU entry for the file + std::map, lru_file_entry> m_files; + mutable std::mutex m_mutex; + }; + +} + +#endif diff --git a/include/libtorrent/file_storage.hpp b/include/libtorrent/file_storage.hpp new file mode 100644 index 0000000..774e371 --- /dev/null +++ b/include/libtorrent/file_storage.hpp @@ -0,0 +1,648 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FILE_STORAGE_HPP_INCLUDED +#define TORRENT_FILE_STORAGE_HPP_INCLUDED + + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + // information about a file in a file_storage + struct TORRENT_DEPRECATED_EXPORT file_entry + { +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // hidden + file_entry(); + // hidden + ~file_entry(); + file_entry(file_entry const&) = default; + file_entry& operator=(file_entry const&) & = default; + file_entry(file_entry&&) noexcept = default; + file_entry& operator=(file_entry&&) & noexcept = default; + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif + + // the full path of this file. The paths are unicode strings + // encoded in UTF-8. + std::string path; + + // the path which this is a symlink to, or empty if this is + // not a symlink. This field is only used if the ``symlink_attribute`` is set. + std::string symlink_path; + + // the offset of this file inside the torrent + std::int64_t offset; + + // the size of the file (in bytes) and ``offset`` is the byte offset + // of the file within the torrent. i.e. the sum of all the sizes of the files + // before it in the list. + std::int64_t size; + + // the modification time of this file specified in posix time. + std::time_t mtime; + + // a SHA-1 hash of the content of the file, or zeros, if no + // file hash was present in the torrent file. It can be used to potentially + // find alternative sources for the file. + sha1_hash filehash; + + // set to true for files that are not part of the data of the torrent. + // They are just there to make sure the next file is aligned to a particular byte offset + // or piece boundary. These files should typically be hidden from an end user. They are + // not written to disk. + bool pad_file:1; + + // true if the file was marked as hidden (on windows). + bool hidden_attribute:1; + + // true if the file was marked as executable (posix) + bool executable_attribute:1; + + // true if the file was a symlink. If this is the case + // the ``symlink_index`` refers to a string which specifies the original location + // where the data for this file was found. + bool symlink_attribute:1; + }; +#endif // TORRENT_ABI_VERSION + + // internal + struct internal_file_entry + { + friend class file_storage; +#if TORRENT_USE_INVARIANT_CHECKS + // for torrent_info::invariant_check + friend class torrent_info; +#endif + + internal_file_entry(); + internal_file_entry(internal_file_entry const& fe); + internal_file_entry& operator=(internal_file_entry const& fe) &; + internal_file_entry(internal_file_entry&& fe) noexcept; + internal_file_entry& operator=(internal_file_entry&& fe) & noexcept; + ~internal_file_entry(); + + void set_name(string_view n, bool borrow_string = false); + string_view filename() const; + + enum { + name_is_owned = (1 << 12) - 1, + not_a_symlink = (1 << 15) - 1 + }; + + // the offset of this file inside the torrent + std::uint64_t offset:48; + + // index into file_storage::m_symlinks or not_a_symlink + // if this is not a symlink + std::uint64_t symlink_index:15; + + // if this is true, don't include m_name as part of the + // path to this file + std::uint64_t no_root_dir:1; + + // the size of this file + std::uint64_t size:48; + + // the number of characters in the name. If this is + // name_is_owned, name is 0-terminated and owned by this object + // (i.e. it should be freed in the destructor). If + // the len is not name_is_owned, the name pointer does not belong + // to this object, and it's not 0-terminated + std::uint64_t name_len:12; + std::uint64_t pad_file:1; + std::uint64_t hidden_attribute:1; + std::uint64_t executable_attribute:1; + std::uint64_t symlink_attribute:1; + + // make it available for logging + private: + // This string is not necessarily 0-terminated! + // that's why it's private, to keep people away from it + char const* name; + public: + + // the index into file_storage::m_paths. To get + // the full path to this file, concatenate the path + // from that array with the 'name' field in + // this struct + // values for path_index include: + // -1 means no path (i.e. single file torrent) + // -2, it means the filename + // in this field contains the full, absolute path + // to the file + int path_index; + }; + + + // represents a window of a file in a torrent. + // + // The ``file_index`` refers to the index of the file (in the torrent_info). + // To get the path and filename, use ``file_path()`` and give the ``file_index`` + // as argument. The ``offset`` is the byte offset in the file where the range + // starts, and ``size`` is the number of bytes this range is. The size + offset + // will never be greater than the file size. + struct TORRENT_EXPORT file_slice + { + // the index of the file + file_index_t file_index; + + // the offset from the start of the file, in bytes + std::int64_t offset; + + // the size of the window, in bytes + std::int64_t size; + }; + + // hidden + using file_flags_t = flags::bitfield_flag; + + // The ``file_storage`` class represents a file list and the piece + // size. Everything necessary to interpret a regular bittorrent storage + // file structure. + class TORRENT_EXPORT file_storage + { + friend class torrent_info; + public: + // hidden + file_storage(); + // hidden + ~file_storage(); + file_storage(file_storage const&); + file_storage& operator=(file_storage const&); + file_storage(file_storage&&) noexcept; + file_storage& operator=(file_storage&&) = default; + + // returns true if the piece length has been initialized + // on the file_storage. This is typically taken as a proxy + // of whether the file_storage as a whole is initialized or + // not. + bool is_valid() const { return m_piece_length > 0; } + +#if TORRENT_ABI_VERSION == 1 + using flags_t = file_flags_t; + static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER pad_file = 0_bit; + static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER attribute_hidden = 1_bit; + static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER attribute_executable = 2_bit; + static constexpr file_flags_t TORRENT_DEPRECATED_MEMBER attribute_symlink = 3_bit; +#endif + + // allocates space for ``num_files`` in the internal file list. This can + // be used to avoid reallocating the internal file list when the number + // of files to be added is known up-front. + void reserve(int num_files); + + // Adds a file to the file storage. The ``add_file_borrow`` version + // expects that ``filename`` is the file name (without a path) of + // the file that's being added. + // This memory is *borrowed*, i.e. it is the caller's + // responsibility to make sure it stays valid throughout the lifetime + // of this file_storage object or any copy of it. The same thing applies + // to ``filehash``, which is an optional pointer to a 20 byte binary + // SHA-1 hash of the file. + // + // if ``filename`` is empty, the filename from ``path`` is used and not + // borrowed. + // + // The ``path`` argument is the full path (in the torrent file) to + // the file to add. Note that this is not supposed to be an absolute + // path, but it is expected to include the name of the torrent as the + // first path element. + // + // ``file_size`` is the size of the file in bytes. + // + // The ``file_flags`` argument sets attributes on the file. The file + // attributes is an extension and may not work in all bittorrent clients. + // + // For possible file attributes, see file_storage::flags_t. + // + // The ``mtime`` argument is optional and can be set to 0. If non-zero, + // it is the posix time of the last modification time of this file. + // + // ``symlink_path`` is the path the file is a symlink to. To make this a + // symlink you also need to set the file_storage::flag_symlink file flag. + // + // If more files than one are added, certain restrictions to their paths + // apply. In a multi-file file storage (torrent), all files must share + // the same root directory. + // + // That is, the first path element of all files must be the same. + // This shared path element is also set to the name of the torrent. It + // can be changed by calling ``set_name``. + void add_file_borrow(string_view filename + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view()); + void add_file(std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {} + , std::time_t mtime = 0, string_view symlink_path = string_view()); + + // renames the file at ``index`` to ``new_filename``. Keep in mind + // that filenames are expected to be UTF-8 encoded. + void rename_file(file_index_t index, std::string const& new_filename); + +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable: 4996) +#endif + TORRENT_DEPRECATED + void add_file_borrow(char const* filename, int filename_len + , std::string const& path, std::int64_t file_size + , file_flags_t file_flags = {}, char const* filehash = nullptr + , std::int64_t mtime = 0, string_view symlink_path = string_view()); + TORRENT_DEPRECATED + void add_file(file_entry const& fe, char const* filehash = nullptr); + + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings +#if defined TORRENT_WINDOWS + TORRENT_DEPRECATED + void add_file(std::wstring const& p, std::int64_t size + , file_flags_t flags = {} + , std::time_t mtime = 0, string_view s_p = ""); + TORRENT_DEPRECATED + void rename_file(file_index_t index, std::wstring const& new_filename); + TORRENT_DEPRECATED + void set_name(std::wstring const& n); + + void rename_file_deprecated(file_index_t index, std::wstring const& new_filename); +#endif + + // all functions depending on internal_file_entry + // were deprecated in 1.0. Use the variants that take an + // index instead + using iterator = std::vector::const_iterator; + using reverse_iterator = std::vector::const_reverse_iterator; + + TORRENT_DEPRECATED + iterator file_at_offset(std::int64_t offset) const; + TORRENT_DEPRECATED + iterator begin() const { return m_files.begin(); } + TORRENT_DEPRECATED + iterator end() const { return m_files.end(); } + TORRENT_DEPRECATED + reverse_iterator rbegin() const { return m_files.rbegin(); } + TORRENT_DEPRECATED + reverse_iterator rend() const { return m_files.rend(); } + TORRENT_DEPRECATED + internal_file_entry const& internal_at(int const index) const; + TORRENT_DEPRECATED + file_entry at(iterator i) const; + + // returns a file_entry with information about the file + // at ``index``. Index must be in the range [0, ``num_files()`` ). + TORRENT_DEPRECATED + file_entry at(int index) const; + + iterator begin_deprecated() const { return m_files.begin(); } + iterator end_deprecated() const { return m_files.end(); } + reverse_iterator rbegin_deprecated() const { return m_files.rbegin(); } + reverse_iterator rend_deprecated() const { return m_files.rend(); } + iterator file_at_offset_deprecated(std::int64_t offset) const; + file_entry at_deprecated(int index) const; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif // TORRENT_ABI_VERSION + + // returns a list of file_slice objects representing the portions of + // files the specified piece index, byte offset and size range overlaps. + // this is the inverse mapping of map_file(). + // + // Preconditions of this function is that the input range is within the + // torrents address space. ``piece`` may not be negative and + // + // ``piece`` * piece_size + ``offset`` + ``size`` + // + // may not exceed the total size of the torrent. + std::vector map_block(piece_index_t piece, std::int64_t offset + , int size) const; + + // returns a peer_request representing the piece index, byte offset + // and size the specified file range overlaps. This is the inverse + // mapping over map_block(). Note that the ``peer_request`` return type + // is meant to hold bittorrent block requests, which may not be larger + // than 16 kiB. Mapping a range larger than that may return an overflown + // integer. + peer_request map_file(file_index_t file, std::int64_t offset, int size) const; + + // returns the number of files in the file_storage + int num_files() const noexcept; + + // returns the index of the one-past-end file in the file storage + file_index_t end_file() const noexcept; + + // returns an implementation-defined type that can be used as the + // container in a range-for loop. Where the values are the indices of all + // files in the file_storage. + index_range file_range() const noexcept; + + // returns the total number of bytes all the files in this torrent spans + std::int64_t total_size() const { return m_total_size; } + + // set and get the number of pieces in the torrent + void set_num_pieces(int n) { m_num_pieces = n; } + int num_pieces() const { TORRENT_ASSERT(m_piece_length > 0); return m_num_pieces; } + + // returns the index of the one-past-end piece in the file storage + piece_index_t end_piece() const + { return piece_index_t(m_num_pieces); } + + // returns the index of the last piece in the torrent. The last piece is + // special in that it may be smaller than the other pieces (and the other + // pieces are all the same size). + piece_index_t last_piece() const + { return piece_index_t(m_num_pieces - 1); } + + // returns an implementation-defined type that can be used as the + // container in a range-for loop. Where the values are the indices of all + // pieces in the file_storage. + index_range piece_range() const noexcept; + + // set and get the size of each piece in this torrent. This size is typically an even power + // of 2. It doesn't have to be though. It should be divisible by 16 kiB however. + void set_piece_length(int l) { m_piece_length = l; } + int piece_length() const { TORRENT_ASSERT(m_piece_length > 0); return m_piece_length; } + + // returns the piece size of ``index``. This will be the same as piece_length(), except + // for the last piece, which may be shorter. + int piece_size(piece_index_t index) const; + + // set and get the name of this torrent. For multi-file torrents, this is also + // the name of the root directory all the files are stored in. + void set_name(std::string const& n) { m_name = n; } + std::string const& name() const { return m_name; } + + // swap all content of *this* with *ti*. + void swap(file_storage& ti) noexcept; + + // if pad_file_limit >= 0, files larger than that limit will be padded, + // default is to not add any padding (-1). The alignment specifies the + // alignment files should be padded to. This defaults to the piece size + // (-1) but it may also make sense to set it to 16 kiB, or something + // divisible by 16 kiB. + // If pad_file_limit is 0, every file will be padded (except empty ones). + // ``tail_padding`` indicates whether aligned files also are padded at + // the end to make them end aligned. This is required for mutable + // torrents, since piece hashes are compared + void optimize(int pad_file_limit = -1, int alignment = -1 + , bool tail_padding = false); + + // These functions are used to query attributes of files at + // a given index. + // + // The ``hash()`` is a SHA-1 hash of the file, or 0 if none was + // provided in the torrent file. This can potentially be used to + // join a bittorrent network with other file sharing networks. + // + // The ``mtime()`` is the modification time is the posix + // time when a file was last modified when the torrent + // was created, or 0 if it was not included in the torrent file. + // + // ``file_path()`` returns the full path to a file. + // + // ``file_size()`` returns the size of a file. + // + // ``pad_file_at()`` returns true if the file at the given + // index is a pad-file. + // + // ``file_name()`` returns *just* the name of the file, whereas + // ``file_path()`` returns the path (inside the torrent file) with + // the filename appended. + // + // ``file_offset()`` returns the byte offset within the torrent file + // where this file starts. It can be used to map the file to a piece + // index (given the piece size). + sha1_hash hash(file_index_t index) const; + std::string const& symlink(file_index_t index) const; + std::time_t mtime(file_index_t index) const; + std::string file_path(file_index_t index, std::string const& save_path = "") const; + string_view file_name(file_index_t index) const; + std::int64_t file_size(file_index_t index) const; + bool pad_file_at(file_index_t index) const; + std::int64_t file_offset(file_index_t index) const; + + // returns the crc32 hash of file_path(index) + std::uint32_t file_path_hash(file_index_t index, std::string const& save_path) const; + + // this will add the CRC32 hash of all directory entries to the table. No + // filename will be included, just directories. Every depth of directories + // are added separately to allow test for collisions with files at all + // levels. i.e. if one path in the torrent is ``foo/bar/baz``, the CRC32 + // hashes for ``foo``, ``foo/bar`` and ``foo/bar/baz`` will be added to + // the set. + void all_path_hashes(std::unordered_set& table) const; + + // the file is a pad file. It's required to contain zeros + // at it will not be saved to disk. Its purpose is to make + // the following file start on a piece boundary. + static constexpr file_flags_t flag_pad_file = 0_bit; + + // this file has the hidden attribute set. This is primarily + // a windows attribute + static constexpr file_flags_t flag_hidden = 1_bit; + + // this file has the executable attribute set. + static constexpr file_flags_t flag_executable = 2_bit; + + // this file is a symbolic link. It should have a link + // target string associated with it. + static constexpr file_flags_t flag_symlink = 3_bit; + + // returns all directories used in the torrent. Files in the torrent are + // located in one of these directories. This is not a tree, it's a flat + // list of all *leaf* directories. i.e. the union of the parent paths of + // all files. + std::vector const& paths() const { return m_paths; } + + // returns a bitmask of flags from file_flags_t that apply + // to file at ``index``. + file_flags_t file_flags(file_index_t index) const; + + // returns true if the file at the specified index has been renamed to + // have an absolute path, i.e. is not anchored in the save path of the + // torrent. + bool file_absolute_path(file_index_t index) const; + + // returns the index of the file at the given offset in the torrent + file_index_t file_index_at_offset(std::int64_t offset) const; + + // low-level function. returns a pointer to the internal storage for + // the filename. This string may not be 0-terminated! + // the ``file_name_len()`` function returns the length of the filename. + // prefer to use ``file_name()`` instead, which returns a ``string_view``. + char const* file_name_ptr(file_index_t index) const; + int file_name_len(file_index_t index) const; + +#if TORRENT_ABI_VERSION == 1 + // these were deprecated in 1.0. Use the versions that take an index instead + TORRENT_DEPRECATED + sha1_hash hash(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + std::string const& symlink(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + std::time_t mtime(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + int file_index(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + std::string file_path(internal_file_entry const& fe, std::string const& save_path = "") const; + TORRENT_DEPRECATED + std::string file_name(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_size(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + bool pad_file_at(internal_file_entry const& fe) const; + TORRENT_DEPRECATED + std::int64_t file_offset(internal_file_entry const& fe) const; +#endif + + // if the backing buffer changed for this storage, this is the pointer + // offset to add to any pointers to make them point into the new buffer + void apply_pointer_offset(std::ptrdiff_t off); + + // validate any symlinks, to ensure they all point to + // other files or directories inside this storage. Any invalid symlinks + // are updated to point to themselves. + void sanitize_symlinks(); + + private: + + std::string internal_file_path(file_index_t index) const; + file_index_t last_file() const noexcept; + + int get_or_add_path(string_view path); + + void add_pad_file(int size + , std::vector::iterator& i + , std::int64_t& offset + , int& pad_file_counter); + + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length; + + // the number of pieces in the torrent + int m_num_pieces; + + void update_path_index(internal_file_entry& e, std::string const& path + , bool set_name = true); + void reorder_file(int index, int dst); + + // the list of files that this torrent consists of + aux::vector m_files; + + // if there are sha1 hashes for each individual file there are as many + // entries in this array as the m_files array. Each entry in m_files has + // a corresponding hash pointer in this array. The reason to split it up + // in separate arrays is to save memory in case the torrent doesn't have + // file hashes + // the pointers in this vector are pointing into the .torrent file in + // memory which is _not_ owned by this file_storage object. It's simply + // a non-owning pointer. It is the user's responsibility that the hash + // stays valid throughout the lifetime of this file_storage object. + aux::vector m_file_hashes; + + // for files that are symlinks, the symlink + // path_index in the internal_file_entry indexes + // this vector of strings + std::vector m_symlinks; + + // the modification times of each file. This vector + // is empty if no file have a modification time. + // each element corresponds to the file with the same + // index in m_files + aux::vector m_mtime; + + // all unique paths files have. The internal_file_entry::path_index + // points into this array. The paths don't include the root directory + // name for multi-file torrents. The m_name field need to be + // prepended to these paths, and the filename of a specific file + // entry appended, to form full file paths + aux::vector m_paths; + + // name of torrent. For multi-file torrents + // this is always the root directory + std::string m_name; + + // the sum of all file sizes + std::int64_t m_total_size; + }; + + namespace aux { + + // returns the piece range that entirely falls within the specified file. the + // end piece is one-past the last piece that entirely falls within the file. + // i.e. They can conveniently be used as loop boundaries. No edge partial + // pieces will be included. + TORRENT_EXTRA_EXPORT std::tuple + file_piece_range_exclusive(file_storage const& fs, file_index_t file); + + // returns the piece range of pieces that overlaps with the specified file. + // the end piece is one-past the last piece. i.e. They can conveniently be + // used as loop boundaries. + TORRENT_EXTRA_EXPORT std::tuple + file_piece_range_inclusive(file_storage const& fs, file_index_t file); + +} // namespace aux +} // namespace libtorrent + +#endif // TORRENT_FILE_STORAGE_HPP_INCLUDED diff --git a/include/libtorrent/fingerprint.hpp b/include/libtorrent/fingerprint.hpp new file mode 100644 index 0000000..afc26dc --- /dev/null +++ b/include/libtorrent/fingerprint.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FINGERPRINT_HPP_INCLUDED +#define TORRENT_FINGERPRINT_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" + +namespace libtorrent { + + // This is a utility function to produce a client ID fingerprint formatted to + // the most common convention. The fingerprint can be set via the + // ``peer_fingerprint`` setting, in settings_pack. + // + // The name string should contain exactly two characters. These are the + // characters unique to your client, used to identify it. Make sure not to + // clash with anybody else. Here are some taken id's: + // + // +----------+-----------------------+ + // | id chars | client | + // +==========+=======================+ + // | LT | libtorrent (default) | + // +----------+-----------------------+ + // | UT | uTorrent | + // +----------+-----------------------+ + // | UM | uTorrent Mac | + // +----------+-----------------------+ + // | qB | qBittorrent | + // +----------+-----------------------+ + // | BP | BitTorrent Pro | + // +----------+-----------------------+ + // | BT | BitTorrent | + // +----------+-----------------------+ + // | DE | Deluge | + // +----------+-----------------------+ + // | AZ | Azureus | + // +----------+-----------------------+ + // | TL | Tribler | + // +----------+-----------------------+ + // + // There's an informal directory of client id's here_. + // + // .. _here: http://wiki.theory.org/BitTorrentSpecification#peer_id + // + // The ``major``, ``minor``, ``revision`` and ``tag`` parameters are used to + // identify the version of your client. + TORRENT_EXPORT std::string generate_fingerprint(std::string name + , int major, int minor = 0, int revision = 0, int tag = 0); + + // The fingerprint class represents information about a client and its version. It is used + // to encode this information into the client's peer id. + struct TORRENT_DEPRECATED_EXPORT fingerprint + { + fingerprint(const char* id_string, int major, int minor, int revision, int tag); + +#if TORRENT_ABI_VERSION == 1 + // generates the actual string put in the peer-id, and return it. + std::string to_string() const; +#endif + + char name[2]; + int major_version; + int minor_version; + int revision_version; + int tag_version; + }; + +} + +#endif // TORRENT_FINGERPRINT_HPP_INCLUDED diff --git a/include/libtorrent/flags.hpp b/include/libtorrent/flags.hpp new file mode 100644 index 0000000..7f8177c --- /dev/null +++ b/include/libtorrent/flags.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FLAGS_HPP_INCLUDED +#define TORRENT_FLAGS_HPP_INCLUDED + +#include // for enable_if +#include + +namespace libtorrent { + +struct bit_t +{ + explicit constexpr bit_t(int b) : m_bit_idx(b) {} + explicit constexpr operator int() const { return m_bit_idx; } +private: + int m_bit_idx; +}; + +constexpr bit_t operator "" _bit(unsigned long long int b) { return bit_t{static_cast(b)}; } + +namespace flags { + +template::value>::type> +struct bitfield_flag +{ + static_assert(std::is_unsigned::value + , "flags must use unsigned integers as underlying types"); + + using underlying_type = UnderlyingType; + + constexpr bitfield_flag(bitfield_flag const& rhs) noexcept = default; + constexpr bitfield_flag(bitfield_flag&& rhs) noexcept = default; + constexpr bitfield_flag() noexcept : m_val(0) {} + explicit constexpr bitfield_flag(UnderlyingType const val) noexcept : m_val(val) {} + constexpr bitfield_flag(bit_t const bit) noexcept : m_val(static_cast(UnderlyingType{1} << static_cast(bit))) {} +#if TORRENT_ABI_VERSION >= 2 + explicit constexpr operator UnderlyingType() const noexcept { return m_val; } +#else + constexpr operator UnderlyingType() const noexcept { return m_val; } +#endif + explicit constexpr operator bool() const noexcept { return m_val != 0; } + + static constexpr bitfield_flag all() + { + return bitfield_flag(static_cast(~UnderlyingType{0})); + } + + bool constexpr operator==(bitfield_flag const f) const noexcept + { return m_val == f.m_val; } + + bool constexpr operator!=(bitfield_flag const f) const noexcept + { return m_val != f.m_val; } + + bitfield_flag& operator|=(bitfield_flag const f) & noexcept + { + m_val |= f.m_val; + return *this; + } + + bitfield_flag& operator&=(bitfield_flag const f) & noexcept + { + m_val &= f.m_val; + return *this; + } + + bitfield_flag& operator^=(bitfield_flag const f) & noexcept + { + m_val ^= f.m_val; + return *this; + } + + constexpr friend bitfield_flag operator|(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val | rhs.m_val); + } + + constexpr friend bitfield_flag operator&(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val & rhs.m_val); + } + + constexpr friend bitfield_flag operator^(bitfield_flag const lhs, bitfield_flag const rhs) noexcept + { + return bitfield_flag(lhs.m_val ^ rhs.m_val); + } + + constexpr bitfield_flag operator~() const noexcept + { + // technically, m_val is promoted to int before applying operator~, which + // means the result may not fit into the underlying type again. So, + // explicitly cast it + return bitfield_flag(static_cast(~m_val)); + } + + bitfield_flag& operator=(bitfield_flag const& rhs) & noexcept = default; + bitfield_flag& operator=(bitfield_flag&& rhs) & noexcept = default; +private: + UnderlyingType m_val; +}; + +#if TORRENT_USE_IOSTREAM + template + std::ostream& operator<<(std::ostream& os, bitfield_flag val) + { return os << static_cast(val); } +#endif + +} // flags +} // libtorrent + +#endif diff --git a/include/libtorrent/fwd.hpp b/include/libtorrent/fwd.hpp new file mode 100644 index 0000000..d518063 --- /dev/null +++ b/include/libtorrent/fwd.hpp @@ -0,0 +1,300 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_FWD_HPP +#define TORRENT_FWD_HPP + +#include "libtorrent/config.hpp" + +namespace libtorrent { + +// include/libtorrent/add_torrent_params.hpp +TORRENT_VERSION_NAMESPACE_2 +struct add_torrent_params; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/alert.hpp +class alert; + +// include/libtorrent/alert_types.hpp +struct dht_routing_bucket; +TORRENT_VERSION_NAMESPACE_2 +struct torrent_alert; +struct peer_alert; +struct tracker_alert; +struct torrent_removed_alert; +struct read_piece_alert; +struct file_completed_alert; +struct file_renamed_alert; +struct file_rename_failed_alert; +struct performance_alert; +struct state_changed_alert; +struct tracker_error_alert; +struct tracker_warning_alert; +struct scrape_reply_alert; +struct scrape_failed_alert; +struct tracker_reply_alert; +struct dht_reply_alert; +struct tracker_announce_alert; +struct hash_failed_alert; +struct peer_ban_alert; +struct peer_unsnubbed_alert; +struct peer_snubbed_alert; +struct peer_error_alert; +struct peer_connect_alert; +struct peer_disconnected_alert; +struct invalid_request_alert; +struct torrent_finished_alert; +struct piece_finished_alert; +struct request_dropped_alert; +struct block_timeout_alert; +struct block_finished_alert; +struct block_downloading_alert; +struct unwanted_block_alert; +struct storage_moved_alert; +struct storage_moved_failed_alert; +struct torrent_deleted_alert; +struct torrent_delete_failed_alert; +struct save_resume_data_alert; +struct save_resume_data_failed_alert; +struct torrent_paused_alert; +struct torrent_resumed_alert; +struct torrent_checked_alert; +struct url_seed_alert; +struct file_error_alert; +struct metadata_failed_alert; +struct metadata_received_alert; +struct udp_error_alert; +struct external_ip_alert; +struct listen_failed_alert; +struct listen_succeeded_alert; +struct portmap_error_alert; +struct portmap_alert; +struct portmap_log_alert; +struct fastresume_rejected_alert; +struct peer_blocked_alert; +struct dht_announce_alert; +struct dht_get_peers_alert; +struct stats_alert; +struct cache_flushed_alert; +struct lsd_peer_alert; +struct trackerid_alert; +struct dht_bootstrap_alert; +struct torrent_error_alert; +struct torrent_need_cert_alert; +struct incoming_connection_alert; +struct add_torrent_alert; +struct state_update_alert; +struct session_stats_alert; +struct dht_error_alert; +struct dht_immutable_item_alert; +struct dht_mutable_item_alert; +struct dht_put_alert; +struct i2p_alert; +struct dht_outgoing_get_peers_alert; +struct log_alert; +struct torrent_log_alert; +struct peer_log_alert; +struct lsd_error_alert; +struct dht_lookup; +struct dht_stats_alert; +struct incoming_request_alert; +struct dht_log_alert; +struct dht_pkt_alert; +struct dht_get_peers_reply_alert; +struct dht_direct_response_alert; +struct picker_log_alert; +struct session_error_alert; +struct dht_live_nodes_alert; +struct session_stats_header_alert; +struct dht_sample_infohashes_alert; +struct block_uploaded_alert; +struct alerts_dropped_alert; +struct socks5_alert; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/announce_entry.hpp +struct announce_endpoint; +TORRENT_VERSION_NAMESPACE_2 +struct announce_entry; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/bdecode.hpp +struct bdecode_node; + +// include/libtorrent/bitfield.hpp +struct bitfield; + +// include/libtorrent/create_torrent.hpp +struct create_torrent; + +// include/libtorrent/disk_interface.hpp +struct open_file_state; + +// include/libtorrent/disk_io_thread.hpp +struct cache_status; + +// include/libtorrent/entry.hpp +class entry; + +// include/libtorrent/error_code.hpp +struct storage_error; + +// include/libtorrent/extensions.hpp +struct plugin; +struct torrent_plugin; +struct peer_plugin; +struct crypto_plugin; + +// include/libtorrent/file_pool.hpp +struct file_pool; + +// include/libtorrent/file_storage.hpp +struct file_slice; +class file_storage; + +// include/libtorrent/hasher.hpp +class hasher; + +// include/libtorrent/hasher512.hpp +class hasher512; + +// include/libtorrent/ip_filter.hpp +struct ip_filter; +class port_filter; + +// include/libtorrent/kademlia/dht_settings.hpp +namespace dht { +struct dht_settings; +} + +// include/libtorrent/kademlia/dht_state.hpp +namespace dht { +struct dht_state; +} + +// include/libtorrent/kademlia/dht_storage.hpp +namespace dht { +struct dht_storage_counters; +} +namespace dht { +struct dht_storage_interface; +} + +// include/libtorrent/peer_class.hpp +struct peer_class_info; + +// include/libtorrent/peer_class_type_filter.hpp +struct peer_class_type_filter; + +// include/libtorrent/peer_connection_handle.hpp +struct peer_connection_handle; +struct bt_peer_connection_handle; + +// include/libtorrent/peer_info.hpp +TORRENT_VERSION_NAMESPACE_2 +struct peer_info; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/peer_request.hpp +struct peer_request; + +// include/libtorrent/session.hpp +class session_proxy; +struct session_params; +class session; + +// include/libtorrent/session_handle.hpp +struct session_handle; + +// include/libtorrent/session_stats.hpp +struct stats_metric; + +// include/libtorrent/session_status.hpp +struct utp_status; +struct session_status; + +// include/libtorrent/settings_pack.hpp +struct settings_pack; + +// include/libtorrent/storage.hpp +struct storage_interface; +class default_storage; + +// include/libtorrent/storage_defs.hpp +struct storage_interface; +struct storage_params; + +// include/libtorrent/torrent_handle.hpp +struct block_info; +struct partial_piece_info; +struct torrent_handle; + +// include/libtorrent/torrent_info.hpp +struct web_seed_entry; +class torrent_info; + +// include/libtorrent/torrent_status.hpp +TORRENT_VERSION_NAMESPACE_2 +struct torrent_status; +TORRENT_VERSION_NAMESPACE_2_END + +#if TORRENT_ABI_VERSION == 1 + +// include/libtorrent/alert_types.hpp +TORRENT_VERSION_NAMESPACE_2 +struct torrent_added_alert; +struct anonymous_mode_alert; +struct mmap_cache_alert; +struct torrent_update_alert; +TORRENT_VERSION_NAMESPACE_2_END + +// include/libtorrent/file_storage.hpp +struct file_entry; + +// include/libtorrent/fingerprint.hpp +struct fingerprint; + +// include/libtorrent/lazy_entry.hpp +struct pascal_string; +struct lazy_entry; + +// include/libtorrent/session_settings.hpp +struct pe_settings; + +#endif // TORRENT_ABI_VERSION + +} + +namespace lt = libtorrent; + +#endif // TORRENT_FWD_HPP diff --git a/include/libtorrent/gzip.hpp b/include/libtorrent/gzip.hpp new file mode 100644 index 0000000..cc92ee3 --- /dev/null +++ b/include/libtorrent/gzip.hpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_GZIP_HPP_INCLUDED +#define TORRENT_GZIP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" + +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT void inflate_gzip( + span in + , std::vector& buffer + , int maximum_size + , error_code& error); + + // get the ``error_category`` for zip errors + TORRENT_EXPORT boost::system::error_category& gzip_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_gzip_category() + { return gzip_category(); } +#endif + + namespace gzip_errors + { + // libtorrent uses boost.system's ``error_code`` class to represent errors. libtorrent has + // its own error category get_gzip_category() with the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + + // the supplied gzip buffer has invalid header + invalid_gzip_header, + + // the gzip buffer would inflate to more bytes than the specified + // maximum size, and was rejected. + inflated_data_too_large, + + // available inflate data did not terminate + data_did_not_terminate, + + // output space exhausted before completing inflate + space_exhausted, + + // invalid block type (type == 3) + invalid_block_type, + + // stored block length did not match one's complement + invalid_stored_block_length, + + // dynamic block code description: too many length or distance codes + too_many_length_or_distance_codes, + + // dynamic block code description: code lengths codes incomplete + code_lengths_codes_incomplete, + + // dynamic block code description: repeat lengths with no first length + repeat_lengths_with_no_first_length, + + // dynamic block code description: repeat more than specified lengths + repeat_more_than_specified_lengths, + + // dynamic block code description: invalid literal/length code lengths + invalid_literal_length_code_lengths, + + // dynamic block code description: invalid distance code lengths + invalid_distance_code_lengths, + + // invalid literal/length or distance code in fixed or dynamic block + invalid_literal_code_in_block, + + // distance is too far back in fixed or dynamic block + distance_too_far_back_in_block, + + // an unknown error occurred during gzip inflation + unknown_gzip_error, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + } + +} + +namespace boost { namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +template<> +struct is_error_condition_enum +{ static const bool value = true; }; + +} } + +#endif diff --git a/include/libtorrent/hasher.hpp b/include/libtorrent/hasher.hpp new file mode 100644 index 0000000..20bc709 --- /dev/null +++ b/include/libtorrent/hasher.hpp @@ -0,0 +1,127 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER_HPP_INCLUDED +#define TORRENT_HASHER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/span.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#ifdef TORRENT_USE_LIBGCRYPT +#include + +#elif TORRENT_USE_COMMONCRYPTO +#include + +#elif TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#elif defined TORRENT_USE_LIBCRYPTO + +extern "C" { +#include +} + +#else +#include "libtorrent/sha1.hpp" +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + // this is a SHA-1 hash class. + // + // You use it by first instantiating it, then call ``update()`` to feed it + // with data. i.e. you don't have to keep the entire buffer of which you want to + // create the hash in memory. You can feed the hasher parts of it at a time. When + // You have fed the hasher with all the data, you call ``final()`` and it + // will return the sha1-hash of the data. + // + // The constructor that takes a ``char const*`` and an integer will construct the + // sha1 context and feed it the data passed in. + // + // If you want to reuse the hasher object once you have created a hash, you have to + // call ``reset()`` to reinitialize it. + // + // The built-in software version of sha1-algorithm was implemented + // by Steve Reid and released as public domain. + // For more info, see ``src/sha1.cpp``. + class TORRENT_EXPORT hasher + { + public: + + hasher(); + + // this is the same as default constructing followed by a call to + // ``update(data, len)``. + hasher(char const* data, int len); + explicit hasher(span data); + hasher(hasher const&); + hasher& operator=(hasher const&) &; + + // append the following bytes to what is being hashed + hasher& update(span data); + hasher& update(char const* data, int len); + + // returns the SHA-1 digest of the buffers previously passed to + // update() and the hasher constructor. + sha1_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + // hidden + ~hasher(); + + private: + +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_CTX m_context; +#elif TORRENT_USE_CRYPTOAPI + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA_CTX m_context; +#else + sha1_ctx m_context; +#endif + }; + +} + +#endif // TORRENT_HASHER_HPP_INCLUDED diff --git a/include/libtorrent/hasher512.hpp b/include/libtorrent/hasher512.hpp new file mode 100644 index 0000000..c54f94d --- /dev/null +++ b/include/libtorrent/hasher512.hpp @@ -0,0 +1,127 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HASHER512_HPP_INCLUDED +#define TORRENT_HASHER512_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/span.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#ifdef TORRENT_USE_LIBGCRYPT +#include + +#elif TORRENT_USE_COMMONCRYPTO +#include + +#elif TORRENT_USE_CRYPTOAPI_SHA_512 +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#elif defined TORRENT_USE_LIBCRYPTO + +extern "C" { +#include +} + +#else +#include "libtorrent/sha512.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + using sha512_hash = digest32<512>; + + // internal + class TORRENT_EXPORT hasher512 + { + // this is a SHA-512 hash class. + // + // You use it by first instantiating it, then call ``update()`` to feed it + // with data. i.e. you don't have to keep the entire buffer of which you want to + // create the hash in memory. You can feed the hasher parts of it at a time. When + // You have fed the hasher with all the data, you call ``final()`` and it + // will return the sha1-hash of the data. + // + // The constructor that takes a ``char const*`` and an integer will construct the + // sha1 context and feed it the data passed in. + // + // If you want to reuse the hasher object once you have created a hash, you have to + // call ``reset()`` to reinitialize it. + // + // The built-in software version of the sha512-algorithm is from LibTomCrypt + public: + + hasher512(); + + // this is the same as default constructing followed by a call to + // ``update(data)``. + explicit hasher512(span data); + hasher512(hasher512 const&); + hasher512& operator=(hasher512 const&) &; + + // append the following bytes to what is being hashed + hasher512& update(span data); + + // store the SHA-512 digest of the buffers previously passed to + // update() and the hasher constructor. + sha512_hash final(); + + // restore the hasher state to be as if the hasher has just been + // default constructed. + void reset(); + + // hidden + ~hasher512(); + + private: + +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_hd_t m_context; +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA512_CTX m_context; +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + aux::crypt_hash m_context; +#elif defined TORRENT_USE_LIBCRYPTO + SHA512_CTX m_context; +#else + sha512_ctx m_context; +#endif + }; + +} + +#endif // TORRENT_HASHER512_HPP_INCLUDED diff --git a/include/libtorrent/heterogeneous_queue.hpp b/include/libtorrent/heterogeneous_queue.hpp new file mode 100644 index 0000000..cd479e6 --- /dev/null +++ b/include/libtorrent/heterogeneous_queue.hpp @@ -0,0 +1,259 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HETEROGENEOUS_QUEUE_HPP_INCLUDED +#define TORRENT_HETEROGENEOUS_QUEUE_HPP_INCLUDED + +#include +#include +#include // for malloc +#include +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/throw.hpp" + +namespace libtorrent { +namespace aux { + + struct free_deleter + { void operator()(char* ptr) { return std::free(ptr); } }; + + inline std::size_t calculate_pad_bytes(char const* inptr, std::size_t alignment) + { + std::uintptr_t const ptr = reinterpret_cast(inptr); + std::uintptr_t const offset = ptr & (alignment - 1); + return (alignment - offset) & (alignment - 1); + } +} + + template + struct heterogeneous_queue + { + heterogeneous_queue() : m_storage(nullptr, aux::free_deleter()) {} + heterogeneous_queue(heterogeneous_queue const&) = delete; + heterogeneous_queue& operator=(heterogeneous_queue const&) = delete; + + template + typename std::enable_if::value, U&>::type + emplace_back(Args&&... args) + { + // make the conservative assumption that we'll need the maximum padding + // for this object, just for purposes of growing the storage + if (std::size_t(m_size) + sizeof(header_t) + alignof(U) + sizeof(U) > std::size_t(m_capacity)) + grow_capacity(sizeof(header_t) + alignof(U) + sizeof(U)); + + char* ptr = m_storage.get() + m_size; + + std::size_t const pad_bytes = aux::calculate_pad_bytes(ptr + sizeof(header_t), alignof(U)); + + // pad_bytes is only 8 bits in the header, so types that need more than + // 256 byte alignment may not be supported + static_assert(alignof(U) <= 256 + , "heterogeneous_queue does not support types with alignment requirements > 256"); + + // if this assert triggers, the type being added to the queue has + // alignment requirements stricter than what malloc() returns. This is + // not supported + TORRENT_ASSERT((reinterpret_cast(m_storage.get()) + & (alignof(U) - 1)) == 0); + + // make sure the current position in the storage is aligned for + // creating a heder_t object + TORRENT_ASSERT((reinterpret_cast(ptr) + & (alignof(header_t) - 1)) == 0); + + // length prefix + header_t* hdr = new (ptr) header_t; + hdr->pad_bytes = static_cast(pad_bytes); + hdr->move = &move; + ptr += sizeof(header_t) + pad_bytes; + hdr->len = static_cast(sizeof(U) + + aux::calculate_pad_bytes(ptr + sizeof(U), alignof(header_t))); + + // make sure ptr is correctly aligned for the object we're about to + // create there + TORRENT_ASSERT((reinterpret_cast(ptr) + & (alignof(U) - 1)) == 0); + + // construct in-place + U* const ret = new (ptr) U(std::forward(args)...); + + // if we constructed the object without throwing any exception + // update counters to indicate the new item is in there + ++m_num_items; + m_size += int(sizeof(header_t) + pad_bytes + hdr->len); + return *ret; + } + + void get_pointers(std::vector& out) + { + out.clear(); + + char* ptr = m_storage.get(); + char const* const end = m_storage.get() + m_size; + while (ptr < end) + { + header_t* hdr = reinterpret_cast(ptr); + ptr += sizeof(header_t) + hdr->pad_bytes; + TORRENT_ASSERT(ptr + hdr->len <= end); + out.push_back(reinterpret_cast(ptr)); + ptr += hdr->len; + } + } + + void swap(heterogeneous_queue& rhs) + { + std::swap(m_storage, rhs.m_storage); + std::swap(m_capacity, rhs.m_capacity); + std::swap(m_size, rhs.m_size); + std::swap(m_num_items, rhs.m_num_items); + } + + int size() const { return m_num_items; } + bool empty() const { return m_num_items == 0; } + + void clear() + { + char* ptr = m_storage.get(); + char const* const end = m_storage.get() + m_size; + while (ptr < end) + { + header_t* hdr = reinterpret_cast(ptr); + ptr += sizeof(header_t) + hdr->pad_bytes; + TORRENT_ASSERT(ptr + hdr->len <= end); + T* a = reinterpret_cast(ptr); + a->~T(); + ptr += hdr->len; + hdr->~header_t(); + } + m_size = 0; + m_num_items = 0; + } + + T* front() + { + if (m_size == 0) return nullptr; + + TORRENT_ASSERT(m_size > 1); + char* ptr = m_storage.get(); + header_t* hdr = reinterpret_cast(ptr); + TORRENT_ASSERT(sizeof(header_t) + hdr->pad_bytes + hdr->len + <= std::size_t(m_size)); + ptr += sizeof(header_t) + hdr->pad_bytes; + return reinterpret_cast(ptr); + } + + ~heterogeneous_queue() { clear(); } + + private: + + // this header is put in front of every element. It tells us + // how many bytes it's using for its allocation, and it + // also tells us how to move this type if we need to grow our + // allocation. + struct header_t + { + // the size of the object. From the start of the object, skip this many + // bytes to get to the next header. Meaning this includes sufficient + // padding to have the next entry be appropriately aligned for header_t + std::uint16_t len; + + // the number of pad bytes between the end of this + // header and the start of the object. This supports allocating types with + // stricter alignment requirements + std::uint8_t pad_bytes; + + void (*move)(char* dst, char* src); + }; + + void grow_capacity(int const size) + { + int const amount_to_grow = (std::max)(size + , (std::max)(m_capacity * 3 / 2, 128)); + + // we use malloc() to guarantee alignment + std::unique_ptr new_storage( + static_cast(std::malloc(std::size_t(m_capacity + amount_to_grow))) + , aux::free_deleter()); + + if (!new_storage) + aux::throw_ex(); + + char* src = m_storage.get(); + char* dst = new_storage.get(); + char const* const end = m_storage.get() + m_size; + while (src < end) + { + header_t* src_hdr = reinterpret_cast(src); + new (dst) header_t(*src_hdr); + src += sizeof(header_t) + src_hdr->pad_bytes; + dst += sizeof(header_t) + src_hdr->pad_bytes; + int const len = src_hdr->len; + TORRENT_ASSERT(src + len <= end); + // this is no-throw + src_hdr->move(dst, src); + src_hdr->~header_t(); + src += len ; + dst += len; + } + + m_storage.swap(new_storage); + m_capacity += amount_to_grow; + } + + template + static void move(char* dst, char* src) noexcept + { + static_assert(std::is_nothrow_move_constructible::value + , "heterogeneous queue only supports noexcept move constructible types"); + static_assert(std::is_nothrow_destructible::value + , "heterogeneous queue only supports noexcept destructible types"); + U& rhs = *reinterpret_cast(src); + + TORRENT_ASSERT((reinterpret_cast(dst) & (alignof(U) - 1)) == 0); + + new (dst) U(std::move(rhs)); + rhs.~U(); + } + + std::unique_ptr m_storage; + // number of bytes of storage allocated + int m_capacity = 0; + // the number of bytes used under m_storage + int m_size = 0; + // the number of objects allocated in m_storage + int m_num_items = 0; + }; +} + +#endif diff --git a/include/libtorrent/hex.hpp b/include/libtorrent/hex.hpp new file mode 100644 index 0000000..ef7db00 --- /dev/null +++ b/include/libtorrent/hex.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HEX_HPP_INCLUDED +#define TORRENT_HEX_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/span.hpp" + +#include + +namespace libtorrent { + + namespace aux { + + TORRENT_EXTRA_EXPORT int hex_to_int(char in); + TORRENT_EXTRA_EXPORT bool is_hex(span in); + +#if TORRENT_ABI_VERSION == 1 +#define TORRENT_CONDITIONAL_EXPORT TORRENT_EXPORT +#else +#define TORRENT_CONDITIONAL_EXPORT TORRENT_EXTRA_EXPORT +#endif + + // The overload taking a ``std::string`` converts (binary) the string ``s`` + // to hexadecimal representation and returns it. + // The overload taking a ``char const*`` and a length converts the binary + // buffer [``in``, ``in`` + len) to hexadecimal and prints it to the buffer + // ``out``. The caller is responsible for making sure the buffer pointed to + // by ``out`` is large enough, i.e. has at least len * 2 bytes of space. + TORRENT_CONDITIONAL_EXPORT std::string to_hex(span s); + TORRENT_CONDITIONAL_EXPORT void to_hex(span in, char* out); + TORRENT_CONDITIONAL_EXPORT void to_hex(char const* in, int const len, char* out); + + // converts the buffer [``in``, ``in`` + len) from hexadecimal to + // binary. The binary output is written to the buffer pointed to + // by ``out``. The caller is responsible for making sure the buffer + // at ``out`` has enough space for the result to be written to, i.e. + // (len + 1) / 2 bytes. + TORRENT_CONDITIONAL_EXPORT bool from_hex(span in, char* out); + +#undef TORRENT_CONDITIONAL_EXPORT + + } + +#if TORRENT_ABI_VERSION == 1 + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // deprecated in 1.2 + TORRENT_DEPRECATED + inline void to_hex(char const* in, int len, char* out) + { aux::to_hex({in, len}, out); } + TORRENT_DEPRECATED + inline std::string to_hex(std::string const& s) + { return aux::to_hex(s); } + TORRENT_DEPRECATED + inline bool from_hex(char const *in, int len, char* out) + { return aux::from_hex({in, len}, out); } + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif +} + +#endif // TORRENT_HEX_HPP_INCLUDED diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp new file mode 100644 index 0000000..6a840ff --- /dev/null +++ b/include/libtorrent/http_connection.hpp @@ -0,0 +1,253 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_CONNECTION +#define TORRENT_HTTP_CONNECTION + +#ifdef TORRENT_USE_OPENSSL +// there is no forward declaration header for asio +namespace boost { +namespace asio { +namespace ssl { + class context; +} +} +} +#endif + +#include +#include +#include + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/optional.hpp" + +namespace libtorrent { + +struct http_connection; +struct resolver_interface; + +// internal +constexpr int default_max_bottled_buffer_size = 2 * 1024 * 1024; + +using http_handler = std::function data, http_connection&)>; + +using http_connect_handler = std::function; + +using http_filter_handler = std::function&)>; + +// when bottled, the last two arguments to the handler +// will always be 0 +struct TORRENT_EXTRA_EXPORT http_connection + : std::enable_shared_from_this +{ + http_connection(io_service& ios + , resolver_interface& resolver + , http_handler const& handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler const& ch + , http_filter_handler const& fh +#ifdef TORRENT_USE_OPENSSL + , ssl::context* ssl_ctx +#endif + ); + + // non-copyable + http_connection(http_connection const&) = delete; + http_connection& operator=(http_connection const&) = delete; + + virtual ~http_connection(); + + void rate_limit(int limit); + + int rate_limit() const + { return m_rate_limit; } + + std::string m_sendbuffer; + + void get(std::string const& url, time_duration timeout = seconds(30) + , int prio = 0, aux::proxy_settings const* ps = nullptr, int handle_redirects = 5 + , std::string const& user_agent = std::string() + , boost::optional
    const& bind_addr = boost::optional
    () + , resolver_flags resolve_flags = resolver_flags{}, std::string const& auth_ = std::string() +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = nullptr +#endif + ); + + void start(std::string const& hostname, int port + , time_duration timeout, int prio = 0, aux::proxy_settings const* ps = nullptr + , bool ssl = false, int handle_redirect = 5 + , boost::optional
    const& bind_addr = boost::optional
    () + , resolver_flags resolve_flags = resolver_flags{} +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = nullptr +#endif + ); + + void close(bool force = false); + + aux::socket_type const& socket() const { return m_sock; } + + std::vector const& endpoints() const { return m_endpoints; } + +private: + +#if TORRENT_USE_I2P + void connect_i2p_tracker(char const* destination); + void on_i2p_resolve(error_code const& e + , char const* destination); +#endif + void on_resolve(error_code const& e + , std::vector
    const& addresses); + void connect(); + void on_connect(error_code const& e); + void on_write(error_code const& e); + void on_read(error_code const& e, std::size_t bytes_transferred); + static void on_timeout(std::weak_ptr p + , error_code const& e); + void on_assign_bandwidth(error_code const& e); + + void callback(error_code e, span data = {}); + + aux::vector m_recvbuffer; + + std::string m_hostname; + std::string m_url; + std::string m_user_agent; + + aux::vector m_endpoints; + + // if the current connection attempt fails, we'll connect to the + // endpoint with this index (in m_endpoints) next + int m_next_ep; + + aux::socket_type m_sock; + +#ifdef TORRENT_USE_OPENSSL + ssl::context* m_ssl_ctx; +#endif + +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + deadline_timer m_timer; + + time_duration m_completion_timeout; + + // the timer fires every 250 millisecond as long + // as all the quota was used. + deadline_timer m_limiter_timer; + + time_point m_last_receive; + time_point m_start_time; + + // specifies whether or not the connection is + // configured to use a proxy + aux::proxy_settings m_proxy; + + // the address to bind to. unset means do not bind + boost::optional
    m_bind_addr; + + // if username password was passed in, remember it in case we need to + // re-issue the request for a redirect + std::string m_auth; + + int m_read_pos; + + // the number of redirects to follow (in sequence) + int m_redirects; + + // maximum size of bottled buffer + int m_max_bottled_buffer_size; + + // the current download limit, in bytes per second + // 0 is unlimited. + int m_rate_limit; + + // the number of bytes we are allowed to receive + int m_download_quota; + + // the priority we have in the connection queue. + // 0 is normal, 1 is high + int m_priority; + + // used for DNS lookups + resolver_flags m_resolve_flags; + + std::uint16_t m_port; + + // bottled means that the handler is called once, when + // everything is received (and buffered in memory). + // non bottled means that once the headers have been + // received, data is streamed to the handler + bool m_bottled; + + // set to true the first time the handler is called + bool m_called = false; + + // only hand out new quota 4 times a second if the + // quota is 0. If it isn't 0 wait for it to reach + // 0 and continue to hand out quota at that time. + bool m_limiter_timer_active = false; + + // true if the connection is using ssl + bool m_ssl = false; + + bool m_abort = false; + + // true while waiting for an async_connect + bool m_connecting = false; + + // true while resolving hostname + bool m_resolving_host = false; +}; + +} + +#endif diff --git a/include/libtorrent/http_parser.hpp b/include/libtorrent/http_parser.hpp new file mode 100644 index 0000000..2c81a63 --- /dev/null +++ b/include/libtorrent/http_parser.hpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_PARSER_HPP_INCLUDED +#define TORRENT_HTTP_PARSER_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/time.hpp" // for seconds32 +#include "libtorrent/optional.hpp" + +namespace libtorrent { + + // return true if the status code is 200, 206, or in the 300-400 range + TORRENT_EXTRA_EXPORT bool is_ok_status(int http_status); + + // return true if the status code is a redirect + TORRENT_EXTRA_EXPORT bool is_redirect(int http_status); + + TORRENT_EXTRA_EXPORT std::string resolve_redirect_location(std::string referrer + , std::string location); + + class TORRENT_EXTRA_EXPORT http_parser + { + public: + enum flags_t { dont_parse_chunks = 1 }; + explicit http_parser(int flags = 0); + ~http_parser(); + std::string const& header(string_view key) const; + boost::optional header_duration(string_view key) const; + std::string const& protocol() const { return m_protocol; } + int status_code() const { return m_status_code; } + std::string const& method() const { return m_method; } + std::string const& path() const { return m_path; } + std::string const& message() const { return m_server_message; } + span get_body() const; + bool header_finished() const { return m_state == read_body; } + bool finished() const { return m_finished; } + std::tuple incoming(span recv_buffer + , bool& error); + int body_start() const { return m_body_start_pos; } + std::int64_t content_length() const { return m_content_length; } + std::pair content_range() const + { return std::make_pair(m_range_start, m_range_end); } + + // returns true if this response is using chunked encoding. + // in this case the body is split up into chunks. You need + // to call parse_chunk_header() for each chunk, starting with + // the start of the body. + bool chunked_encoding() const { return m_chunked_encoding; } + + // removes the chunk headers from the supplied buffer. The buffer + // must be the stream received from the http server this parser + // instanced parsed. It will use the internal chunk list to determine + // where the chunks are in the buffer. It returns the new length of + // the buffer + span collapse_chunk_headers(span buffer) const; + + // returns false if the buffer doesn't contain a complete + // chunk header. In this case, call the function again with + // a bigger buffer once more bytes have been received. + // chunk_size is filled in with the number of bytes in the + // chunk that follows. 0 means the response terminated. In + // this case there might be additional headers in the parser + // object. + // header_size is filled in with the number of bytes the header + // itself was. Skip this number of bytes to get to the actual + // chunk data. + // if the function returns false, the chunk size and header + // size may still have been modified, but their values are + // undefined + bool parse_chunk_header(span buf + , std::int64_t* chunk_size, int* header_size); + + // reset the whole state and start over + void reset(); + + bool connection_close() const { return m_connection_close; } + + std::multimap const& headers() const { return m_header; } + std::vector> const& chunks() const { return m_chunked_ranges; } + + private: + std::int64_t m_recv_pos = 0; + std::string m_method; + std::string m_path; + std::string m_protocol; + std::string m_server_message; + + std::int64_t m_content_length = -1; + std::int64_t m_range_start = -1; + std::int64_t m_range_end = -1; + + std::multimap m_header; + span m_recv_buffer; + // contains offsets of the first and one-past-end of + // each chunked range in the response + std::vector> m_chunked_ranges; + + // while reading a chunk, this is the offset where the + // current chunk will end (it refers to the first character + // in the chunk tail header or the next chunk header) + std::int64_t m_cur_chunk_end = -1; + + int m_status_code = -1; + + // the sum of all chunk headers read so far + int m_chunk_header_size = 0; + + int m_partial_chunk_header = 0; + + // controls some behaviors of the parser + int m_flags; + + int m_body_start_pos = 0; + + enum { read_status, read_header, read_body, error_state } m_state = read_status; + + // this is true if the server is HTTP/1.0 or + // if it sent "connection: close" + bool m_connection_close = false; + bool m_chunked_encoding = false; + bool m_finished = false; + }; + +} + +#endif // TORRENT_HTTP_PARSER_HPP_INCLUDED diff --git a/include/libtorrent/http_seed_connection.hpp b/include/libtorrent/http_seed_connection.hpp new file mode 100644 index 0000000..8f594fd --- /dev/null +++ b/include/libtorrent/http_seed_connection.hpp @@ -0,0 +1,113 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_SEED_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/piece_block_progress.hpp" + +namespace libtorrent { + + class torrent; + struct peer_request; + + class TORRENT_EXTRA_EXPORT http_seed_connection + : public web_connection_base + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + http_seed_connection(peer_connection_args const& pack + , web_seed_t& web); + + connection_type type() const override + { return connection_type::http_seed; } + + // called from the main loop when this connection has any + // work to do. + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + + void on_connected() override; + + std::string const& url() const override { return m_url; } + + void get_specific_peer_info(peer_info& p) const override; + void disconnect(error_code const& ec, operation_t op + , disconnect_severity_t error = peer_connection_interface::normal) override; + + void write_request(peer_request const& r) override; + + private: + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + + // this is const since it's used as a key in the web seed list in the torrent + // if it's changed referencing back into that list will fail + const std::string m_url; + + web_seed_t* m_web; + + // the number of bytes left to receive of the response we're + // currently parsing + std::int64_t m_response_left; + + // this is the offset inside the current receive + // buffer where the next chunk header will be. + // this is updated for each chunk header that's + // parsed. It does not necessarily point to a valid + // offset in the receive buffer, if we haven't received + // it yet. This offset never includes the HTTP header + std::int64_t m_chunk_pos; + + // this is the number of bytes we've already received + // from the next chunk header we're waiting for + int m_partial_chunk_header; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/http_stream.hpp b/include/libtorrent/http_stream.hpp new file mode 100644 index 0000000..1c5eddf --- /dev/null +++ b/include/libtorrent/http_stream.hpp @@ -0,0 +1,120 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_STREAM_HPP_INCLUDED +#define TORRENT_HTTP_STREAM_HPP_INCLUDED + +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" + +namespace libtorrent { + +class http_stream : public proxy_base +{ +public: + + explicit http_stream(io_service& io_service) + : proxy_base(io_service) + , m_no_connect(false) + {} + + void set_no_connect(bool c) { m_no_connect = c; } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + m_dst_name = host; + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. send HTTP CONNECT method and possibly username+password + // 4. read CONNECT response + + using std::placeholders::_1; + using std::placeholders::_2; + tcp::resolver::query q(m_hostname, to_string(m_port).data()); + m_resolver.async_resolve(q, std::bind( + &http_stream::name_lookup, this, _1, _2, handler_type(std::move(handler)))); + } + +private: + + void name_lookup(error_code const& e, tcp::resolver::iterator i + , handler_type& h); + void connected(error_code const& e, handler_type& h); + void handshake1(error_code const& e, handler_type& h); + void handshake2(error_code const& e, handler_type& h); + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + // this is true if the connection is HTTP based and + // want to talk directly to the proxy + bool m_no_connect; +}; + +} + +#endif diff --git a/include/libtorrent/http_tracker_connection.hpp b/include/libtorrent/http_tracker_connection.hpp new file mode 100644 index 0000000..7fe09a7 --- /dev/null +++ b/include/libtorrent/http_tracker_connection.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tracker_manager.hpp" // for tracker_connection + +namespace libtorrent { + + class tracker_manager; + struct http_connection; + class http_parser; + struct bdecode_node; + struct peer_entry; + + class TORRENT_EXTRA_EXPORT http_tracker_connection + : public tracker_connection + { + friend class tracker_manager; + public: + + http_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , std::weak_ptr c); + + void start() override; + void close() override; + + private: + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void on_filter(http_connection& c, std::vector& endpoints); + void on_connect(http_connection& c); + void on_response(error_code const& ec, http_parser const& parser + , span data); + + void on_timeout(error_code const&) override {} + + std::shared_ptr m_tracker_connection; + address m_tracker_ip; + }; + + TORRENT_EXTRA_EXPORT tracker_response parse_tracker_response( + span data, error_code& ec + , int flags, sha1_hash const& scrape_ih); + + TORRENT_EXTRA_EXPORT bool extract_peer_info(bdecode_node const& info + , peer_entry& ret, error_code& ec); +} + +#endif // TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/i2p_stream.hpp b/include/libtorrent/i2p_stream.hpp new file mode 100644 index 0000000..be086d7 --- /dev/null +++ b/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,239 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_I2P_STREAM_HPP_INCLUDED +#define TORRENT_I2P_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_I2P + +#include +#include +#include +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" + +namespace libtorrent { + + namespace i2p_error { + + // error values for the i2p_category error_category. + enum i2p_error_code + { + no_error = 0, + parse_failed, + cant_reach_peer, + i2p_error, + invalid_key, + invalid_id, + timeout, + key_not_found, + duplicated_id, + num_errors + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(i2p_error_code e); + } + + // returns the error category for I2P errors + TORRENT_EXPORT boost::system::error_category& i2p_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_i2p_category() + { return i2p_category(); } +#endif + +class i2p_stream : public proxy_base +{ +public: + + explicit i2p_stream(io_service& io_service); +#if TORRENT_USE_ASSERTS + ~i2p_stream(); +#endif + + enum command_t + { + cmd_none, + cmd_create_session, + cmd_connect, + cmd_accept, + cmd_name_lookup, + cmd_incoming + }; + + void set_command(command_t c) { m_command = c; } + + void set_session_id(char const* id) { m_id = id; } + + void set_destination(string_view d) { m_dest = d.to_string(); } + std::string const& destination() { return m_dest; } + + template + void async_connect(endpoint_type const&, Handler const& handler) + { + // since we don't support regular endpoints, just ignore the one + // provided and use m_dest. + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to SAM bridge + // 4 send command message (CONNECT/ACCEPT) + + using std::placeholders::_1; + using std::placeholders::_2; + tcp::resolver::query q(m_hostname, to_string(m_port).data()); + m_resolver.async_resolve(q, std::bind( + &i2p_stream::do_connect, this, _1, _2, handler_type(std::move(handler)))); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + void send_name_lookup(handler_type h); + +private: + // explicitly disallow assignment, to silence msvc warning + i2p_stream& operator=(i2p_stream const&); + + void do_connect(error_code const& e, tcp::resolver::iterator i + , handler_type h); + void connected(error_code const& e, handler_type& h); + void start_read_line(error_code const& e, handler_type& h); + void read_line(error_code const& e, handler_type& h); + void send_connect(handler_type h); + void send_accept(handler_type h); + void send_session_create(handler_type h); + + // send and receive buffer + aux::vector m_buffer; + char const* m_id; + command_t m_command; + std::string m_dest; + std::string m_name_lookup; + + enum state_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response + }; + + state_t m_state; +#if TORRENT_USE_ASSERTS + int m_magic; +#endif +}; + +class i2p_connection +{ +public: + explicit i2p_connection(io_service& ios); + ~i2p_connection(); + + aux::proxy_settings proxy() const; + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + void open(std::string const& hostname, int port, i2p_stream::handler_type h); + void close(error_code&); + + char const* session_id() const { return m_session_id.c_str(); } + std::string const& local_endpoint() const { return m_i2p_local_endpoint; } + + using name_lookup_handler = std::function; + void async_name_lookup(char const* name, name_lookup_handler handler); + +private: + // explicitly disallow assignment, to silence msvc warning + i2p_connection& operator=(i2p_connection const&); + + void on_sam_connect(error_code const& ec, i2p_stream::handler_type& h + , std::shared_ptr); + void do_name_lookup(std::string const& name + , name_lookup_handler h); + void on_name_lookup(error_code const& ec + , name_lookup_handler& handler + , std::shared_ptr); + + void set_local_endpoint(error_code const& ec, char const* dest + , i2p_stream::handler_type& h); + + // to talk to i2p SAM bridge + std::shared_ptr m_sam_socket; + std::string m_hostname; + int m_port; + + // our i2p endpoint key + std::string m_i2p_local_endpoint; + std::string m_session_id; + + std::list> m_name_lookup; + + enum state_t + { + sam_connecting, + sam_name_lookup, + sam_idle + }; + + state_t m_state; + + io_service& m_io_service; +}; + +} + +namespace boost { namespace system { + +template<> +struct is_error_code_enum +{ static const bool value = true; }; + +} } + +#endif // TORRENT_USE_I2P + +#endif diff --git a/include/libtorrent/identify_client.hpp b/include/libtorrent/identify_client.hpp new file mode 100644 index 0000000..f9b023c --- /dev/null +++ b/include/libtorrent/identify_client.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED +#define TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/fingerprint.hpp" + +// TODO: hide this declaration when deprecated functions are disabled, and +// remove its internal use +namespace libtorrent { + +namespace aux { + + TORRENT_EXTRA_EXPORT + std::string identify_client_impl(const peer_id& p); + +} + + // these functions don't really need to be public. This mechanism of + // advertising client software and version is also out-dated. + + // This function can can be used to extract a string describing a client + // version from its peer-id. It will recognize most clients that have this + // kind of identification in the peer-id. + TORRENT_DEPRECATED_EXPORT + std::string identify_client(const peer_id& p); + +#if TORRENT_ABI_VERSION == 1 + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable: 4996) +#endif + // Returns an optional fingerprint if any can be identified from the peer + // id. This can be used to automate the identification of clients. It will + // not be able to identify peers with non- standard encodings. Only Azureus + // style, Shadow's style and Mainline style. + TORRENT_DEPRECATED_EXPORT + boost::optional + client_fingerprint(peer_id const& p); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // TORRENT_ABI_VERSION + +} + +#endif // TORRENT_IDENTIFY_CLIENT_HPP_INCLUDED diff --git a/include/libtorrent/index_range.hpp b/include/libtorrent/index_range.hpp new file mode 100644 index 0000000..9f3ab7c --- /dev/null +++ b/include/libtorrent/index_range.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_INDEX_RANGE_HPP +#define TORRENT_INDEX_RANGE_HPP + +namespace libtorrent { + +template +struct index_iter +{ + explicit index_iter(Index i) : m_idx(i) {} + index_iter operator++() + { + ++m_idx; + return *this; + } + index_iter operator--() + { + --m_idx; + return *this; + } + Index operator*() const { return m_idx; } + friend inline bool operator==(index_iter lhs, index_iter rhs) + { return lhs.m_idx == rhs.m_idx; } + friend inline bool operator!=(index_iter lhs, index_iter rhs) + { return lhs.m_idx != rhs.m_idx; } +private: + Index m_idx; +}; + +template +struct index_range +{ + Index _begin; + Index _end; + index_iter begin() { return index_iter{_begin}; } + index_iter end() { return index_iter{_end}; } +}; + +} + +#endif diff --git a/include/libtorrent/invariant_check.hpp b/include/libtorrent/invariant_check.hpp new file mode 100644 index 0000000..861e336 --- /dev/null +++ b/include/libtorrent/invariant_check.hpp @@ -0,0 +1,88 @@ +// Copyright Daniel Wallin 2004. Use, modification and distribution is +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TORRENT_INVARIANT_ACCESS_HPP_INCLUDED +#define TORRENT_INVARIANT_ACCESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include + +#if TORRENT_USE_INVARIANT_CHECKS + +namespace libtorrent { + + class invariant_access + { + public: + template + static void check_invariant(T const& self) + { + self.check_invariant(); + } + }; + + template + void check_invariant(T const& x) + { +#ifndef BOOST_NO_EXCEPTIONS + try + { + invariant_access::check_invariant(x); + } + catch (std::exception const& err) + { + std::fprintf(stderr, "invariant_check failed with exception: %s\n" + , err.what()); + } + catch (...) + { + std::fprintf(stderr, "invariant_check failed with exception\n"); + } +#else + invariant_access::check_invariant(x); +#endif + } + + struct invariant_checker {}; + + template + struct invariant_checker_impl : invariant_checker + { + explicit invariant_checker_impl(T const& self_) + : self(self_) + { + check_invariant(self); + } + + invariant_checker_impl(invariant_checker_impl const& rhs) + : self(rhs.self) {} + + ~invariant_checker_impl() + { + check_invariant(self); + } + + T const& self; + + private: + invariant_checker_impl& operator=(invariant_checker_impl const&); + }; + + template + invariant_checker_impl make_invariant_checker(T const& x) + { + return invariant_checker_impl(x); + } +} + +#define INVARIANT_CHECK \ + invariant_checker const& _invariant_check = make_invariant_checker(*this); \ + (void)_invariant_check +#else +#define INVARIANT_CHECK do {} TORRENT_WHILE_0 +#endif + +#endif // TORRENT_INVARIANT_ACCESS_HPP_INCLUDED + diff --git a/include/libtorrent/io.hpp b/include/libtorrent/io.hpp new file mode 100644 index 0000000..51ff063 --- /dev/null +++ b/include/libtorrent/io.hpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_HPP_INCLUDED +#define TORRENT_IO_HPP_INCLUDED + +#include +#include +#include // for copy +#include // for memcpy +#include +#include + +#include "assert.hpp" + +namespace libtorrent { +namespace detail { + + template struct type {}; + + // reads an integer from a byte stream + // in big endian byte order and converts + // it to native endianess + template + inline T read_impl(InIt& start, type) + { + T ret = 0; + for (int i = 0; i < int(sizeof(T)); ++i) + { + ret <<= 8; + ret |= static_cast(*start); + ++start; + } + return ret; + } + + template + std::uint8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + std::int8_t read_impl(InIt& start, type) + { + return static_cast(*start++); + } + + template + typename std::enable_if<(std::is_integral::value + && !std::is_same::value) + || std::is_enum::value, void>::type + write_impl(In data, OutIt& start) + { + // Note: the test for [OutItT==void] below is necessary because + // in C++11 std::back_insert_iterator::value_type is void. + // This could change in C++17 or above + using OutItT = typename std::iterator_traits::value_type; + using Byte = typename std::conditional< + std::is_same::value, char, OutItT>::type; + static_assert(sizeof(Byte) == 1, "wrong iterator or pointer type"); + + T val = static_cast(data); + TORRENT_ASSERT(data == static_cast(val)); + for (int i = int(sizeof(T)) - 1; i >= 0; --i) + { + *start = static_cast((val >> (i * 8)) & 0xff); + ++start; + } + } + + template + typename std::enable_if::value, void>::type + write_impl(Val val, OutIt& start) + { write_impl(val ? 1 : 0, start); } + + // -- adaptors + + template + std::int64_t read_int64(InIt& start) + { return read_impl(start, type()); } + + template + std::uint64_t read_uint64(InIt& start) + { return read_impl(start, type()); } + + template + std::uint32_t read_uint32(InIt& start) + { return read_impl(start, type()); } + + template + std::int32_t read_int32(InIt& start) + { return read_impl(start, type()); } + + template + std::int16_t read_int16(InIt& start) + { return read_impl(start, type()); } + + template + std::uint16_t read_uint16(InIt& start) + { return read_impl(start, type()); } + + template + std::int8_t read_int8(InIt& start) + { return read_impl(start, type()); } + + template + std::uint8_t read_uint8(InIt& start) + { return read_impl(start, type()); } + + + template + void write_uint64(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int64(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint32(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int32(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint16(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int16(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_uint8(T val, OutIt& start) + { write_impl(val, start); } + + template + void write_int8(T val, OutIt& start) + { write_impl(val, start); } + + inline int write_string(std::string const& str, char*& start) + { + std::memcpy(reinterpret_cast(start), str.c_str(), str.size()); + start += str.size(); + return int(str.size()); + } + + template + int write_string(std::string const& val, OutIt& out) + { + for (auto const c : val) *out++ = c; + return int(val.length()); + } +} // namespace detail +} + +#endif // TORRENT_IO_HPP_INCLUDED diff --git a/include/libtorrent/io_service.hpp b/include/libtorrent/io_service.hpp new file mode 100644 index 0000000..5f6be1a --- /dev/null +++ b/include/libtorrent/io_service.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_SERVICE_HPP_INCLUDED +#define TORRENT_IO_SERVICE_HPP_INCLUDED + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#else +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // SIMULATOR + +#include "libtorrent/io_service_fwd.hpp" + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using io_service = sim::asio::io_service; +#else + using io_service = boost::asio::io_service; +#endif +} + +#endif diff --git a/include/libtorrent/io_service_fwd.hpp b/include/libtorrent/io_service_fwd.hpp new file mode 100644 index 0000000..1813134 --- /dev/null +++ b/include/libtorrent/io_service_fwd.hpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IO_SERVICE_FWD_HPP_INCLUDED +#define TORRENT_IO_SERVICE_FWD_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include + +#if defined TORRENT_BUILD_SIMULATOR +namespace sim { namespace asio { + + struct io_service; +}} +#else +namespace boost { namespace asio { +#if BOOST_VERSION < 106600 + class io_service; +#else + class io_context; + typedef io_context io_service; +#endif +}} +#endif + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using io_service = sim::asio::io_service; +#else + using io_service = boost::asio::io_service; +#endif + +#if BOOST_VERSION >= 107000 && !defined TORRENT_BUILD_SIMULATOR +template +io_service& get_io_service(T& o) { return static_cast(o.get_executor().context()); } +#else +template +io_service& get_io_service(T& o) { return o.get_io_service(); } +#endif + +} + +#endif diff --git a/include/libtorrent/ip_filter.hpp b/include/libtorrent/ip_filter.hpp new file mode 100644 index 0000000..cce596a --- /dev/null +++ b/include/libtorrent/ip_filter.hpp @@ -0,0 +1,351 @@ +/* + +Copyright (c) 2005-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_FILTER_HPP +#define TORRENT_IP_FILTER_HPP + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include // for next +#include + +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// hidden +inline bool operator<=(address const& lhs + , address const& rhs) +{ + return lhs < rhs || lhs == rhs; +} + +template +struct ip_range +{ + Addr first; + Addr last; + std::uint32_t flags; + friend bool operator==(ip_range const& lhs, ip_range const& rhs) + { + return lhs.first == rhs.first + && lhs.last == rhs.last + && lhs.flags == rhs.flags; + } +}; + +namespace detail { + + template + Addr zero() + { + Addr zero; + std::fill(zero.begin(), zero.end(), static_cast(0)); + return zero; + } + + template<> + inline std::uint16_t zero() { return 0; } + + template + Addr plus_one(Addr const& a) + { + Addr tmp(a); + for (int i = int(tmp.size()) - 1; i >= 0; --i) + { + auto& t = tmp[std::size_t(i)]; + if (t < (std::numeric_limits::max)()) + { + t += 1; + break; + } + t = 0; + } + return tmp; + } + + inline std::uint16_t plus_one(std::uint16_t val) { return val + 1; } + + template + Addr minus_one(Addr const& a) + { + Addr tmp(a); + for (int i = int(tmp.size()) - 1; i >= 0; --i) + { + auto& t = tmp[std::size_t(i)]; + if (t > 0) + { + t -= 1; + break; + } + t = (std::numeric_limits::max)(); + } + return tmp; + } + + inline std::uint16_t minus_one(std::uint16_t val) { return val - 1; } + + template + Addr max_addr() + { + Addr tmp; + std::fill(tmp.begin(), tmp.end() + , (std::numeric_limits::max)()); + return tmp; + } + + template<> + inline std::uint16_t max_addr() + { return (std::numeric_limits::max)(); } + + // this is the generic implementation of + // a filter for a specific address type. + // it works with IPv4 and IPv6 + template + class filter_impl + { + public: + + filter_impl() + { + // make the entire ip-range non-blocked + m_access_list.insert(range(zero(), 0)); + } + + void add_rule(Addr first, Addr last, std::uint32_t const flags) + { + TORRENT_ASSERT(!m_access_list.empty()); + TORRENT_ASSERT(first < last || first == last); + + auto i = m_access_list.upper_bound(first); + auto j = m_access_list.upper_bound(last); + + if (i != m_access_list.begin()) --i; + + TORRENT_ASSERT(j != m_access_list.begin()); + TORRENT_ASSERT(j != i); + + std::uint32_t first_access = i->access; + std::uint32_t last_access = std::prev(j)->access; + + if (i->start != first && first_access != flags) + { + i = m_access_list.insert(i, range(first, flags)); + } + else if (i != m_access_list.begin() && std::prev(i)->access == flags) + { + --i; + first_access = i->access; + } + TORRENT_ASSERT(!m_access_list.empty()); + TORRENT_ASSERT(i != m_access_list.end()); + + if (i != j) m_access_list.erase(std::next(i), j); + if (i->start == first) + { + // This is an optimization over erasing and inserting a new element + // here. + // this const-cast is OK because we know that the new + // start address will keep the set correctly ordered + const_cast(i->start) = first; + const_cast(i->access) = flags; + } + else if (first_access != flags) + { + m_access_list.insert(i, range(first, flags)); + } + + if ((j != m_access_list.end() + && minus_one(j->start) != last) + || (j == m_access_list.end() + && last != max_addr())) + { + TORRENT_ASSERT(j == m_access_list.end() || last < minus_one(j->start)); + if (last_access != flags) + j = m_access_list.insert(j, range(plus_one(last), last_access)); + } + + if (j != m_access_list.end() && j->access == flags) m_access_list.erase(j); + TORRENT_ASSERT(!m_access_list.empty()); + } + + std::uint32_t access(Addr const& addr) const + { + TORRENT_ASSERT(!m_access_list.empty()); + auto i = m_access_list.upper_bound(addr); + if (i != m_access_list.begin()) --i; + TORRENT_ASSERT(i != m_access_list.end()); + TORRENT_ASSERT(i->start <= addr && (std::next(i) == m_access_list.end() + || addr < std::next(i)->start)); + return i->access; + } + + template + std::vector> export_filter() const + { + std::vector> ret; + ret.reserve(m_access_list.size()); + + for (auto i = m_access_list.begin() + , end(m_access_list.end()); i != end;) + { + ip_range r; + r.first = ExternalAddressType(i->start); + r.flags = i->access; + + ++i; + if (i == end) + r.last = ExternalAddressType(max_addr()); + else + r.last = ExternalAddressType(minus_one(i->start)); + + ret.push_back(r); + } + return ret; + } + + private: + + struct range + { + range(Addr addr, std::uint32_t a = 0) : start(addr), access(a) {} // NOLINT + bool operator<(range const& r) const { return start < r.start; } + bool operator<(Addr const& a) const { return start < a; } + Addr start; + // the end of the range is implicit + // and given by the next entry in the set + std::uint32_t access; + }; + + std::set m_access_list; + }; + +} + +// The ``ip_filter`` class is a set of rules that uniquely categorizes all +// ip addresses as allowed or disallowed. The default constructor creates +// a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +// the IPv4 range, and the equivalent range covering all addresses for the +// IPv6 range). +// +// A default constructed ip_filter does not filter any address. +struct TORRENT_EXPORT ip_filter +{ + // the flags defined for an IP range + enum access_flags + { + // indicates that IPs in this range should not be connected + // to nor accepted as incoming connections + blocked = 1 + }; + + // Adds a rule to the filter. ``first`` and ``last`` defines a range of + // ip addresses that will be marked with the given flags. The ``flags`` + // can currently be 0, which means allowed, or ``ip_filter::blocked``, which + // means disallowed. + // + // precondition: + // ``first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()`` + // + // postcondition: + // ``access(x) == flags`` for every ``x`` in the range [``first``, ``last``] + // + // This means that in a case of overlapping ranges, the last one applied takes + // precedence. + void add_rule(address const& first, address const& last, std::uint32_t flags); + + // Returns the access permissions for the given address (``addr``). The permission + // can currently be 0 or ``ip_filter::blocked``. The complexity of this operation + // is O(``log`` n), where n is the minimum number of non-overlapping ranges to describe + // the current filter. + std::uint32_t access(address const& addr) const; + + using filter_tuple_t = std::tuple> + , std::vector>>; + + // This function will return the current state of the filter in the minimum number of + // ranges possible. They are sorted from ranges in low addresses to high addresses. Each + // entry in the returned vector is a range with the access control specified in its + // ``flags`` field. + // + // The return value is a tuple containing two range-lists. One for IPv4 addresses + // and one for IPv6 addresses. + filter_tuple_t export_filter() const; + +// void print() const; + +private: + + detail::filter_impl m_filter4; + detail::filter_impl m_filter6; +}; + +// the port filter maps non-overlapping port ranges to flags. This +// is primarily used to indicate whether a range of ports should +// be connected to or not. The default is to have the full port +// range (0-65535) set to flag 0. +class TORRENT_EXPORT port_filter +{ +public: + + // the defined flags for a port range + enum access_flags + { + // this flag indicates that destination ports in the + // range should not be connected to + blocked = 1 + }; + + // set the flags for the specified port range (``first``, ``last``) to + // ``flags`` overwriting any existing rule for those ports. The range + // is inclusive, i.e. the port ``last`` also has the flag set on it. + void add_rule(std::uint16_t first, std::uint16_t last, std::uint32_t flags); + + // test the specified port (``port``) for whether it is blocked + // or not. The returned value is the flags set for this port. + // see access_flags. + std::uint32_t access(std::uint16_t port) const; + +private: + + detail::filter_impl m_filter; + +}; + +} + +#endif diff --git a/include/libtorrent/ip_voter.hpp b/include/libtorrent/ip_voter.hpp new file mode 100644 index 0000000..1260ea7 --- /dev/null +++ b/include/libtorrent/ip_voter.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_IP_VOTER_HPP_INCLUDED +#define TORRENT_IP_VOTER_HPP_INCLUDED + +#include +#include "libtorrent/address.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/time.hpp" // for time_point +#include "libtorrent/aux_/session_interface.hpp" // for ip_source_t + +namespace libtorrent { + + // this is an object that keeps the state for a single external IP + // based on peoples votes + struct TORRENT_EXTRA_EXPORT ip_voter + { + ip_voter(); + + // returns true if a different IP is the top vote now + // i.e. we changed our idea of what our external IP is + bool cast_vote(address const& ip, aux::ip_source_t source_type, address const& source); + + address external_address() const { return m_external_address; } + + private: + + bool maybe_rotate(); + + struct external_ip_t + { + bool add_vote(sha1_hash const& k, aux::ip_source_t type); + + // we want to sort descending + bool operator<(external_ip_t const& rhs) const + { + if (num_votes > rhs.num_votes) return true; + if (num_votes < rhs.num_votes) return false; + return static_cast(sources) > static_cast(rhs.sources); + } + + // this is a bloom filter of the IPs that have + // reported this address + bloom_filter<16> voters; + // this is the actual external address + address addr; + // a bitmask of sources the reporters have come from + aux::ip_source_t sources{}; + // the total number of votes for this IP + std::uint16_t num_votes = 0; + }; + + // this is a bloom filter of all the IPs that have + // been the first to report an external address. Each + // IP only gets to add a new item once. + bloom_filter<32> m_external_address_voters; + + std::vector m_external_addresses; + address m_external_address; + + // the total number of unique IPs that have voted + int m_total_votes; + + // this is true from the first time we rotate. Before + // we rotate for the first time, we keep updating the + // external address as we go, since we don't have any + // stable setting to fall back on. Once this is true, + // we stop updating it on the fly, and just use the + // address from when we rotated. + bool m_valid_external; + + // the last time we rotated this ip_voter. i.e. threw + // away all the votes and started from scratch, in case + // our IP has changed + time_point m_last_rotate; + }; + + // stores one address for each combination of local/global and ipv4/ipv6 + // use of this class should be avoided, get the IP from the appropriate + // listen interface wherever possible + struct TORRENT_EXTRA_EXPORT external_ip + { + external_ip() + : m_addresses{{address_v4(), address_v6()}, {address_v4(), address_v6()}} + {} + + external_ip(address const& local4, address const& global4 + , address const& local6, address const& global6); + + // the external IP as it would be observed from `ip` + address external_address(address const& ip) const; + + private: + + // support one local and one global address per address family + // [0][n] = global [1][n] = local + // [n][0] = IPv4 [n][1] = IPv6 + // TODO: 1 have one instance per possible subnet, 192.168.x.x, 10.x.x.x, etc. + address m_addresses[2][2]; + }; + +} + +#endif diff --git a/include/libtorrent/kademlia/announce_flags.hpp b/include/libtorrent/kademlia/announce_flags.hpp new file mode 100644 index 0000000..198d6d6 --- /dev/null +++ b/include/libtorrent/kademlia/announce_flags.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ANNOUNCE_FLAGS_HPP +#define ANNOUNCE_FLAGS_HPP + +#include + +#include "libtorrent/flags.hpp" + +namespace libtorrent { namespace dht { + +using announce_flags_t = flags::bitfield_flag; + +namespace announce { + +// announce to DHT as a seed +constexpr announce_flags_t seed = 0_bit; + +// announce to DHT with the implied-port flag set. This tells the network to use +// your source UDP port as your listen port, rather than the one specified in +// the message. This may improve the chances of traversing NATs when using uTP. +constexpr announce_flags_t implied_port = 1_bit; + +// Specify the port number for the SSL listen socket in the DHT announce. +constexpr announce_flags_t ssl_torrent = 2_bit; + +} + +}} + +#endif diff --git a/include/libtorrent/kademlia/dht_observer.hpp b/include/libtorrent/kademlia/dht_observer.hpp new file mode 100644 index 0000000..f56fa86 --- /dev/null +++ b/include/libtorrent/kademlia/dht_observer.hpp @@ -0,0 +1,95 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef DHT_OBSERVER_HPP +#define DHT_OBSERVER_HPP + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/aux_/session_udp_sockets.hpp" // for transport + +namespace libtorrent { + +class entry; + +namespace aux { struct listen_socket_handle; } + +namespace dht { + + struct TORRENT_EXTRA_EXPORT dht_logger + { +#ifndef TORRENT_DISABLE_LOGGING + enum module_t + { + tracker, + node, + routing_table, + rpc_manager, + traversal + }; + + enum message_direction_t + { + incoming_message, + outgoing_message + }; + + virtual bool should_log(module_t m) const = 0; + virtual void log(module_t m, char const* fmt, ...) TORRENT_FORMAT(3,4) = 0; + virtual void log_packet(message_direction_t dir, span pkt + , udp::endpoint const& node) = 0; +#endif + + protected: + ~dht_logger() = default; + }; + + struct TORRENT_EXTRA_EXPORT dht_observer : dht_logger + { + virtual void set_external_address(aux::listen_socket_handle const& iface + , address const& addr, address const& source) = 0; + virtual int get_listen_port(aux::transport ssl, aux::listen_socket_handle const& s) = 0; + virtual void get_peers(sha1_hash const& ih) = 0; + virtual void outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) = 0; + virtual void announce(sha1_hash const& ih, address const& addr, int port) = 0; + virtual bool on_dht_request(string_view query + , dht::msg const& request, entry& response) = 0; + + protected: + ~dht_observer() = default; + }; +}} + +#endif diff --git a/include/libtorrent/kademlia/dht_settings.hpp b/include/libtorrent/kademlia/dht_settings.hpp new file mode 100644 index 0000000..7882d07 --- /dev/null +++ b/include/libtorrent/kademlia/dht_settings.hpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2006, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_SETTINGS_HPP_INCLUDED +#define TORRENT_DHT_SETTINGS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/entry.hpp" + +namespace libtorrent { +namespace dht { + + // structure used to hold configuration options for the DHT + // + // The ``dht_settings`` struct used to contain a ``service_port`` member to + // control which port the DHT would listen on and send messages from. This + // field is deprecated and ignored. libtorrent always tries to open the UDP + // socket on the same port as the TCP socket. + struct TORRENT_EXPORT dht_settings + { + // the maximum number of peers to send in a reply to ``get_peers`` + int max_peers_reply = 100; + + // the number of concurrent search request the node will send when + // announcing and refreshing the routing table. This parameter is called + // alpha in the kademlia paper + int search_branching = 5; + + // the maximum number of failed tries to contact a node before it is + // removed from the routing table. If there are known working nodes that + // are ready to replace a failing node, it will be replaced immediately, + // this limit is only used to clear out nodes that don't have any node + // that can replace them. + int max_fail_count = 20; + + // the total number of torrents to track from the DHT. This is simply an + // upper limit to make sure malicious DHT nodes cannot make us allocate + // an unbounded amount of memory. + int max_torrents = 2000; + + // max number of items the DHT will store + int max_dht_items = 700; + + // the max number of peers to store per torrent (for the DHT) + int max_peers = 500; + + // the max number of torrents to return in a torrent search query to the + // DHT + int max_torrent_search_reply = 20; + + // determines if the routing table entries should restrict entries to one + // per IP. This defaults to true, which helps mitigate some attacks on + // the DHT. It prevents adding multiple nodes with IPs with a very close + // CIDR distance. + // + // when set, nodes whose IP address that's in the same /24 (or /64 for + // IPv6) range in the same routing table bucket. This is an attempt to + // mitigate node ID spoofing attacks also restrict any IP to only have a + // single entry in the whole routing table + bool restrict_routing_ips = true; + + // determines if DHT searches should prevent adding nodes with IPs with + // very close CIDR distance. This also defaults to true and helps + // mitigate certain attacks on the DHT. + bool restrict_search_ips = true; + + // makes the first buckets in the DHT routing table fit 128, 64, 32 and + // 16 nodes respectively, as opposed to the standard size of 8. All other + // buckets have size 8 still. + bool extended_routing_table = true; + + // slightly changes the lookup behavior in terms of how many outstanding + // requests we keep. Instead of having branch factor be a hard limit, we + // always keep *branch factor* outstanding requests to the closest nodes. + // i.e. every time we get results back with closer nodes, we query them + // right away. It lowers the lookup times at the cost of more outstanding + // queries. + bool aggressive_lookups = true; + + // when set, perform lookups in a way that is slightly more expensive, + // but which minimizes the amount of information leaked about you. + bool privacy_lookups = false; + + // when set, node's whose IDs that are not correctly generated based on + // its external IP are ignored. When a query arrives from such node, an + // error message is returned with a message saying "invalid node ID". + bool enforce_node_id = false; + + // ignore DHT messages from parts of the internet we wouldn't expect to + // see any traffic from + bool ignore_dark_internet = true; + + // the number of seconds a DHT node is banned if it exceeds the rate + // limit. The rate limit is averaged over 10 seconds to allow for bursts + // above the limit. + int block_timeout = 5 * 60; + + // the max number of packets per second a DHT node is allowed to send + // without getting banned. + int block_ratelimit = 5; + + // when set, the other nodes won't keep this node in their routing + // tables, it's meant for low-power and/or ephemeral devices that + // cannot support the DHT, it is also useful for mobile devices which + // are sensitive to network traffic and battery life. + // this node no longer responds to 'query' messages, and will place a + // 'ro' key (value = 1) in the top-level message dictionary of outgoing + // query messages. + bool read_only = false; + + // the number of seconds a immutable/mutable item will be expired. + // default is 0, means never expires. + int item_lifetime = 0; + + // the number of bytes per second (on average) the DHT is allowed to send. + // If the incoming requests causes to many bytes to be sent in responses, + // incoming requests will be dropped until the quota has been replenished. + int upload_rate_limit = 8000; + + // the info-hashes sample recomputation interval (in seconds). + // The node will precompute a subset of the tracked info-hashes and return + // that instead of calculating it upon each request. The permissible range + // is between 0 and 21600 seconds (inclusive). + int sample_infohashes_interval = 21600; + + // the maximum number of elements in the sampled subset of info-hashes. + // If this number is too big, expect the DHT storage implementations + // to clamp it in order to allow UDP packets go through + int max_infohashes_sample_count = 20; + +#if TORRENT_ABI_VERSION == 1 + // the listen port for the dht. This is a UDP port. zero means use the + // same as the tcp interface + int service_port = 0; +#endif + }; + + // internal + struct settings : dht_settings + { + // when this is true, nodes whose IDs are derived from their source IP + // according to BEP 42 (https://www.bittorrent.org/beps/bep_0042.html) are + // preferred in the routing table. + bool prefer_verified_node_ids = true; + }; + + +TORRENT_EXTRA_EXPORT dht_settings read_dht_settings(bdecode_node const& e); +TORRENT_EXTRA_EXPORT entry save_dht_settings(dht_settings const& settings); + +} +} + +#endif diff --git a/include/libtorrent/kademlia/dht_state.hpp b/include/libtorrent/kademlia/dht_state.hpp new file mode 100644 index 0000000..cb279f7 --- /dev/null +++ b/include/libtorrent/kademlia/dht_state.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_DHT_STATE_HPP +#define LIBTORRENT_DHT_STATE_HPP + +#include +#include +#include + +#include + +#include +#include + +namespace libtorrent { + + struct bdecode_node; +} + +namespace libtorrent { namespace dht { + + using node_ids_t = std::vector>; + // This structure helps to store and load the state + // of the ``dht_tracker``. + // At this moment the library is only a dual stack + // implementation of the DHT. See `BEP 32`_ + // + // .. _`BEP 32`: https://www.bittorrent.org/beps/bep_0032.html + struct TORRENT_EXPORT dht_state + { + node_ids_t nids; + + // the bootstrap nodes saved from the buckets node + std::vector nodes; + // the bootstrap nodes saved from the IPv6 buckets node + std::vector nodes6; + + void clear(); + }; + + TORRENT_EXTRA_EXPORT node_ids_t extract_node_ids(bdecode_node const& e, string_view key); + TORRENT_EXTRA_EXPORT dht_state read_dht_state(bdecode_node const& e); + TORRENT_EXTRA_EXPORT entry save_dht_state(dht_state const& state); +}} + +#endif // LIBTORRENT_DHT_STATE_HPP diff --git a/include/libtorrent/kademlia/dht_storage.hpp b/include/libtorrent/kademlia/dht_storage.hpp new file mode 100644 index 0000000..7d19a84 --- /dev/null +++ b/include/libtorrent/kademlia/dht_storage.hpp @@ -0,0 +1,238 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_STORAGE_HPP +#define TORRENT_DHT_STORAGE_HPP + +#include + +#include +#include + +#include +#include +#include +#include + +namespace libtorrent { + class entry; +} + +namespace libtorrent { namespace dht { + struct dht_settings; + + // This structure hold the relevant counters for the storage + struct TORRENT_EXPORT dht_storage_counters + { + std::int32_t torrents = 0; + std::int32_t peers = 0; + std::int32_t immutable_data = 0; + std::int32_t mutable_data = 0; + + // This member function set the counters to zero. + void reset(); + }; + + // The DHT storage interface is a pure virtual class that can + // be implemented to customize how the data for the DHT is stored. + // + // The default storage implementation uses three maps in RAM to save + // the peers, mutable and immutable items and it's designed to + // provide a fast and fully compliant behavior of the BEPs. + // + // libtorrent comes with one built-in storage implementation: + // ``dht_default_storage`` (private non-accessible class). Its + // constructor function is called dht_default_storage_constructor(). + // You should know that if this storage becomes full of DHT items, + // the current implementation could degrade in performance. + struct TORRENT_EXPORT dht_storage_interface + { +#if TORRENT_ABI_VERSION == 1 + // This function returns the number of torrents tracked by + // the DHT at the moment. It's used to fill session_status. + // It's deprecated. + virtual size_t num_torrents() const = 0; + + // This function returns the sum of all of peers per torrent + // tracker byt the DHT at the moment. + // It's deprecated. + virtual size_t num_peers() const = 0; +#endif + + // This member function notifies the list of all node's ids + // of each DHT running inside libtorrent. It's advisable + // that the concrete implementation keeps a copy of this list + // for an eventual prioritization when deleting an element + // to make room for a new one. + virtual void update_node_ids(std::vector const& ids) = 0; + + // This function retrieve the peers tracked by the DHT + // corresponding to the given info_hash. You can specify if + // you want only seeds and/or you are scraping the data. + // + // For future implementers: + // If the torrent tracked contains a name, such a name + // must be stored as a string in peers["n"] + // + // If the scrape parameter is true, you should fill these keys: + // + // peers["BFpe"] + // with the standard bit representation of a + // 256 bloom filter containing the downloaders + // peers["BFsd"] + // with the standard bit representation of a + // 256 bloom filter containing the seeders + // + // If the scrape parameter is false, you should fill the + // key peers["values"] with a list containing a subset of + // peers tracked by the given info_hash. Such a list should + // consider the value of dht_settings::max_peers_reply. + // If noseed is true only peers marked as no seed should be included. + // + // returns true if the maximum number of peers are stored + // for this info_hash. + virtual bool get_peers(sha1_hash const& info_hash + , bool noseed, bool scrape, address const& requester + , entry& peers) const = 0; + + // This function is named announce_peer for consistency with the + // upper layers, but has nothing to do with networking. Its only + // responsibility is store the peer in such a way that it's returned + // in the entry with the lookup_peers. + // + // The ``name`` parameter is the name of the torrent if provided in + // the announce_peer DHT message. The length of this value should + // have a maximum length in the final storage. The default + // implementation truncate the value for a maximum of 50 characters. + virtual void announce_peer(sha1_hash const& info_hash + , tcp::endpoint const& endp + , string_view name, bool seed) = 0; + + // This function retrieves the immutable item given its target hash. + // + // For future implementers: + // The value should be returned as an entry in the key item["v"]. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + virtual bool get_immutable_item(sha1_hash const& target + , entry& item) const = 0; + + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // This data can be stored only if the target is not already + // present. The implementation should consider the value of + // dht_settings::max_dht_items. + virtual void put_immutable_item(sha1_hash const& target + , span buf + , address const& addr) = 0; + + // This function retrieves the sequence number of a mutable item. + // + // returns true if the item is found and the data is returned + // inside the out parameter seq. + virtual bool get_mutable_item_seq(sha1_hash const& target + , sequence_number& seq) const = 0; + + // This function retrieves the mutable stored in the DHT. + // + // For implementers: + // The item sequence should be stored in the key item["seq"]. + // if force_fill is true or (0 <= seq and seq < item["seq"]) + // the following keys should be filled + // item["v"] - with the value no encoded. + // item["sig"] - with a string representation of the signature. + // item["k"] - with a string representation of the public key. + // + // returns true if the item is found and the data is returned + // inside the (entry) out parameter item. + virtual bool get_mutable_item(sha1_hash const& target + , sequence_number seq, bool force_fill + , entry& item) const = 0; + + // Store the item's data. This layer is only for storage. + // The authentication of the item is performed by the upper layer. + // + // For implementers: + // The sequence number should be checked if the item is already + // present. The implementation should consider the value of + // dht_settings::max_dht_items. + virtual void put_mutable_item(sha1_hash const& target + , span buf + , signature const& sig + , sequence_number seq + , public_key const& pk + , span salt + , address const& addr) = 0; + + // This function retrieves a sample info-hashes + // + // For implementers: + // The info-hashes should be stored in ["samples"] (N x 20 bytes). + // the following keys should be filled + // item["interval"] - the subset refresh interval in seconds. + // item["num"] - number of info-hashes in storage. + // + // Internally, this function is allowed to lazily evaluate, cache + // and modify the actual sample to put in ``item`` + // + // returns the number of info-hashes in the sample. + virtual int get_infohashes_sample(entry& item) = 0; + + // This function is called periodically (non-constant frequency). + // + // For implementers: + // Use this functions for expire peers or items or any other + // storage cleanup. + virtual void tick() = 0; + + // return stats counters for the store + virtual dht_storage_counters counters() const = 0; + + // hidden + virtual ~dht_storage_interface() {} + }; + + using dht_storage_constructor_type + = std::function(dht_settings const& settings)>; + + // constructor for the default DHT storage. The DHT storage is responsible + // for maintaining peers and mutable and immutable items announced and + // stored/put to the DHT node. + TORRENT_EXPORT std::unique_ptr dht_default_storage_constructor( + dht_settings const& settings); + +} } // namespace libtorrent::dht + +#endif //TORRENT_DHT_STORAGE_HPP diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp new file mode 100644 index 0000000..c5659b5 --- /dev/null +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -0,0 +1,218 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_TRACKER +#define TORRENT_DHT_TRACKER + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { + + struct counters; +#if TORRENT_ABI_VERSION == 1 + struct session_status; +#endif +} + +namespace libtorrent { namespace dht { + struct settings; + + struct TORRENT_EXTRA_EXPORT dht_tracker final + : socket_manager + , std::enable_shared_from_this + { + using send_fun_t = std::function, error_code&, udp_send_flags_t)>; + + dht_tracker(dht_observer* observer + , io_service& ios + , send_fun_t const& send_fun + , dht::settings const& settings + , counters& cnt + , dht_storage_interface& storage + , dht_state&& state); + + // the dht_state must be moved in! + dht_tracker(dht_observer* observer + , io_service& ios + , send_fun_t const& send_fun + , dht::settings const& settings + , counters& cnt + , dht_storage_interface& storage + , dht_state const& state) = delete; + +#if defined(_MSC_VER) && _MSC_VER < 1910 + // workaround for a bug in msvc 14.0 + // it attempts to generate a copy constructor for some strange reason + // and fails because tracker_node is not copyable + dht_tracker(dht_tracker const&) = delete; +#endif + + void start(find_data::nodes_callback const& f); + void stop(); + + // tell the node to recalculate its node id based on the current + // understanding of its external address (which may have changed) + void update_node_id(aux::listen_socket_handle const& s); + + void new_socket(aux::listen_socket_handle const& s); + void delete_socket(aux::listen_socket_handle const& s); + + void add_node(udp::endpoint const& node); + void add_router_node(udp::endpoint const& node); + + dht_state state() const; + + void get_peers(sha1_hash const& ih + , std::function const&)> f); + void announce(sha1_hash const& ih, int listen_port, announce_flags_t flags + , std::function const&)> f); + + void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f); + + void get_item(sha1_hash const& target + , std::function cb); + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + void get_item(public_key const& key + , std::function cb + , std::string salt = std::string()); + + // for immutable_item. + // the callback function will be called when put operation is done. + // the int parameter indicates the success numbers of put operation. + void put_item(entry const& data + , std::function cb); + + // for mutable_item. + // the data_cb will be called when we get authoritative mutable_item, + // the cb is same as put immutable_item. + void put_item(public_key const& key + , std::function cb + , std::function data_cb, std::string salt = std::string()); + + // send an arbitrary DHT request directly to a node + void direct_request(udp::endpoint const& ep, entry& e + , std::function f); + +#if TORRENT_ABI_VERSION == 1 + void dht_status(session_status& s); +#endif + void dht_status(std::vector& table + , std::vector& requests); + void update_stats_counters(counters& c) const; + + void incoming_error(error_code const& ec, udp::endpoint const& ep); + bool incoming_packet(aux::listen_socket_handle const& s + , udp::endpoint const& ep, span buf); + + std::vector> live_nodes(node_id const& nid); + + private: + struct tracker_node + { + tracker_node(io_service& ios + , aux::listen_socket_handle const& s, socket_manager* sock + , dht::settings const& settings + , node_id const& nid + , dht_observer* observer, counters& cnt + , get_foreign_node_t get_foreign_node + , dht_storage_interface& storage); + tracker_node(tracker_node const&) = delete; + tracker_node(tracker_node&&) = delete; + + node dht; + deadline_timer connection_timer; + }; + using tracker_nodes_t = std::map; + + std::shared_ptr self() + { return shared_from_this(); } + + void connection_timeout(aux::listen_socket_handle const& s, error_code const& e); + void refresh_timeout(error_code const& e); + void refresh_key(error_code const& e); + void update_storage_node_ids(); + node* get_node(node_id const& id, std::string const& family_name); + + // implements socket_manager + bool has_quota() override; + bool send_packet(aux::listen_socket_handle const& s, entry& e, udp::endpoint const& addr) override; + + // this is the bdecode_node DHT messages are parsed into. It's a member + // in order to avoid having to deallocate and re-allocate it for every + // message. + bdecode_node m_msg; + + counters& m_counters; + dht_storage_interface& m_storage; + dht_state m_state; // to be used only once + tracker_nodes_t m_nodes; + send_fun_t m_send_fun; + dht_observer* m_log; + + std::vector m_send_buf; + dos_blocker m_blocker; + + deadline_timer m_key_refresh_timer; + deadline_timer m_refresh_timer; + dht::settings const& m_settings; + + bool m_running; + + // used to resolve hostnames for nodes + udp::resolver m_host_resolver; + + // state for the send rate limit + int m_send_quota; + time_point m_last_tick; + }; +}} + +#endif diff --git a/include/libtorrent/kademlia/direct_request.hpp b/include/libtorrent/kademlia/direct_request.hpp new file mode 100644 index 0000000..aaca2cf --- /dev/null +++ b/include/libtorrent/kademlia/direct_request.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2014, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DIRECT_REQUEST_HPP +#define TORRENT_DIRECT_REQUEST_HPP + +#include +#include + +namespace libtorrent { namespace dht { + +struct direct_traversal : traversal_algorithm +{ + using message_callback = std::function; + + direct_traversal(node& node + , node_id const& target + , message_callback cb) + : traversal_algorithm(node, target) + , m_cb(std::move(cb)) + {} + + char const* name() const override { return "direct_traversal"; } + + void invoke_cb(msg const& m) + { + if (m_cb) + { + m_cb(m); + m_cb = nullptr; + done(); + } + } + +protected: + message_callback m_cb; +}; + +struct direct_observer : observer +{ + direct_observer(std::shared_ptr algo + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algo), ep, id) + {} + + void reply(msg const& m) override + { + flags |= flag_done; + static_cast(algorithm())->invoke_cb(m); + } + + void timeout() override + { + if (flags & flag_done) return; + flags |= flag_done; + bdecode_node e; + msg m(e, target_ep()); + static_cast(algorithm())->invoke_cb(m); + } +}; + +}} // namespace libtorrent::dht + +#endif //TORRENT_DIRECT_REQUEST_HPP diff --git a/include/libtorrent/kademlia/dos_blocker.hpp b/include/libtorrent/kademlia/dos_blocker.hpp new file mode 100644 index 0000000..1407ad1 --- /dev/null +++ b/include/libtorrent/kademlia/dos_blocker.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DHT_DOS_BLOCKER +#define TORRENT_DHT_DOS_BLOCKER + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { namespace dht { + + struct dht_logger; + + // this is a class that maintains a list of abusive DHT nodes, + // blocking their access to our DHT node. + struct TORRENT_EXTRA_EXPORT dos_blocker + { + dos_blocker(); + + // called every time we receive an incoming packet. Returns + // true if we should let the packet through, and false if + // it's blocked + bool incoming(address const& addr, time_point now, dht_logger* logger); + + void set_rate_limit(int l) + { + TORRENT_ASSERT(l > 0); + m_message_rate_limit = l; + } + + void set_block_timer(int t) + { + TORRENT_ASSERT(t > 0); + m_block_timeout = t; + } + + private: + + // used to ignore abusive dht nodes + struct node_ban_entry + { + node_ban_entry(): count(0) {} + address src; + time_point limit; + int count; + }; + + static constexpr int num_ban_nodes = 20; + + // the max number of packets we can receive per second from a node before + // we block it. + int m_message_rate_limit; + + // the number of seconds a node gets blocked for when it exceeds the rate + // limit + int m_block_timeout; + + node_ban_entry m_ban_nodes[num_ban_nodes]; + }; +}} + +#endif diff --git a/include/libtorrent/kademlia/ed25519.hpp b/include/libtorrent/kademlia/ed25519.hpp new file mode 100644 index 0000000..37719ca --- /dev/null +++ b/include/libtorrent/kademlia/ed25519.hpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_ED25519_HPP +#define LIBTORRENT_ED25519_HPP + +#include +#include +#include + +#include +#include + +namespace libtorrent { namespace dht { + + // See documentation of internal random_bytes + TORRENT_EXPORT std::array ed25519_create_seed(); + + // Creates a new key pair from the given seed. + // + // It's important to clarify that the seed completely determines + // the key pair. Then it's enough to save the seed and the + // public key as the key-pair in a buffer of 64 bytes. The standard + // is (32 bytes seed, 32 bytes public key). + // + // This function does work with a given seed, giving you a pair of + // (64 bytes private key, 32 bytes public key). It's a trade-off between + // space and CPU, saving in one format or another. + // + // The smaller format is not weaker by any means, in fact, it is only + // the seed (32 bytes) that determines the point in the curve. + TORRENT_EXPORT std::tuple ed25519_create_keypair( + std::array const& seed); + + // Creates a signature of the given message with the given key pair. + TORRENT_EXPORT signature ed25519_sign(span msg + , public_key const& pk, secret_key const& sk); + + // Verifies the signature on the given message using ``pk`` + TORRENT_EXPORT bool ed25519_verify(signature const& sig + , span msg, public_key const& pk); + + // Adds a scalar to the given key pair where scalar is a 32 byte buffer + // (possibly generated with `ed25519_create_seed`), generating a new key pair. + // + // You can calculate the public key sum without knowing the private key and + // vice versa by passing in null for the key you don't know. This is useful + // when a third party (an authoritative server for example) needs to enforce + // randomness on a key pair while only knowing the public key of the other + // side. + // + // Warning: the last bit of the scalar is ignored - if comparing scalars make + // sure to clear it with `scalar[31] &= 127`. + // + // see http://crypto.stackexchange.com/a/6215/4697 + // see test_ed25519 for a practical example + TORRENT_EXPORT public_key ed25519_add_scalar(public_key const& pk + , std::array const& scalar); + TORRENT_EXPORT secret_key ed25519_add_scalar(secret_key const& sk + , std::array const& scalar); + + // Performs a key exchange on the given public key and private key, producing a + // shared secret. It is recommended to hash the shared secret before using it. + // + // This is useful when two parties want to share a secret but both only knows + // their respective public keys. + // see test_ed25519 for a practical example + TORRENT_EXPORT std::array ed25519_key_exchange( + public_key const& pk, secret_key const& sk); + +}} + +#endif // LIBTORRENT_ED25519_HPP diff --git a/include/libtorrent/kademlia/find_data.hpp b/include/libtorrent/kademlia/find_data.hpp new file mode 100644 index 0000000..1085687 --- /dev/null +++ b/include/libtorrent/kademlia/find_data.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef FIND_DATA_050323_HPP +#define FIND_DATA_050323_HPP + +#include +#include +#include +#include + +#include +#include + +namespace libtorrent { namespace dht { + +class node; + +// -------- find data ----------- + +struct find_data : traversal_algorithm +{ + using nodes_callback = std::function> const&)>; + + find_data(node& dht_node, node_id const& target + , nodes_callback const& ncallback); + + void got_write_token(node_id const& n, std::string write_token); + + void start() override; + + char const* name() const override; + +protected: + + void done() override; + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + + nodes_callback m_nodes_callback; + std::map m_write_tokens; + bool m_done; +}; + +struct find_data_observer : traversal_observer +{ + find_data_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(std::move(algorithm), ep, id) + {} + + void reply(msg const&) override; +}; + +} } // namespace libtorrent::dht + +#endif // FIND_DATA_050323_HPP diff --git a/include/libtorrent/kademlia/get_item.hpp b/include/libtorrent/kademlia/get_item.hpp new file mode 100644 index 0000000..2729e45 --- /dev/null +++ b/include/libtorrent/kademlia/get_item.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_GET_ITEM_HPP +#define LIBTORRENT_GET_ITEM_HPP + +#include +#include + +#include + +namespace libtorrent { namespace dht { + +class get_item : public find_data +{ +public: + using data_callback = std::function; + + void got_data(bdecode_node const& v, + public_key const& pk, + sequence_number seq, + signature const& sig); + + // for immutable items + get_item(node& dht_node + , node_id const& target + , data_callback const& dcallback + , nodes_callback const& ncallback); + + // for mutable items + get_item(node& dht_node + , public_key const& pk + , span salt + , data_callback const& dcallback + , nodes_callback const& ncallback); + + char const* name() const override; + +protected: + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + bool invoke(observer_ptr o) override; + void done() override; + + data_callback m_data_callback; + item m_data; + bool m_immutable; +}; + +class get_item_observer : public find_data_observer +{ +public: + get_item_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(std::move(algorithm), ep, id) + {} + + void reply(msg const&) override; +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_GET_ITEM_HPP diff --git a/include/libtorrent/kademlia/get_peers.hpp b/include/libtorrent/kademlia/get_peers.hpp new file mode 100644 index 0000000..2ee4527 --- /dev/null +++ b/include/libtorrent/kademlia/get_peers.hpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_GET_PEERS_HPP +#define LIBTORRENT_GET_PEERS_HPP + +#include + +namespace libtorrent { namespace dht { + +struct get_peers : find_data +{ + using data_callback = std::function const&)>; + + void got_peers(std::vector const& peers); + + get_peers(node& dht_node, node_id const& target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds); + + char const* name() const override; + +protected: + bool invoke(observer_ptr o) override; + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + + data_callback m_data_callback; + bool m_noseeds; +}; + +struct obfuscated_get_peers : get_peers +{ + obfuscated_get_peers(node& dht_node, node_id const& target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds); + + char const* name() const override; + +protected: + + observer_ptr new_observer(udp::endpoint const& ep, + node_id const& id) override; + bool invoke(observer_ptr o) override; + void done() override; +private: + // when set to false, we no longer obfuscate + // the target hash, and send regular get_peers + bool m_obfuscated; +}; + +struct get_peers_observer : find_data_observer +{ + get_peers_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : find_data_observer(std::move(algorithm), ep, id) + {} + + void reply(msg const&) override; +#ifndef TORRENT_DISABLE_LOGGING +private: + void log_peers(msg const& m, bdecode_node const& r, int size) const; +#endif +}; + +struct obfuscated_get_peers_observer : traversal_observer +{ + obfuscated_get_peers_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(std::move(algorithm), ep, id) + {} + void reply(msg const&) override; +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_GET_PEERS_HPP diff --git a/include/libtorrent/kademlia/io.hpp b/include/libtorrent/kademlia/io.hpp new file mode 100644 index 0000000..4e11758 --- /dev/null +++ b/include/libtorrent/kademlia/io.hpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2006-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef KADEMLIA_IO_HPP +#define KADEMLIA_IO_HPP + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/socket_io.hpp" + +namespace libtorrent { namespace dht { + + struct node_endpoint + { + node_id id; + udp::endpoint ep; + }; + + template + node_endpoint read_node_endpoint(udp protocol, InIt&& in) + { + node_endpoint ep; + std::copy(in, in + 20, ep.id.begin()); + in += 20; + if (protocol == udp::v6()) + ep.ep = detail::read_v6_endpoint(in); + else + ep.ep = detail::read_v4_endpoint(in); + return ep; + } + +}} + +#endif diff --git a/include/libtorrent/kademlia/item.hpp b/include/libtorrent/kademlia/item.hpp new file mode 100644 index 0000000..718ad27 --- /dev/null +++ b/include/libtorrent/kademlia/item.hpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2013, Steven Siloti, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_ITEM_HPP +#define LIBTORRENT_ITEM_HPP + +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht { + +// calculate the target hash for an immutable item. +TORRENT_EXTRA_EXPORT sha1_hash item_target_id(span v); + +// calculate the target hash for a mutable item. +TORRENT_EXTRA_EXPORT sha1_hash item_target_id(span salt + , public_key const& pk); + +TORRENT_EXTRA_EXPORT bool verify_mutable_item( + span v + , span salt + , sequence_number seq + , public_key const& pk + , signature const& sig); + +// TODO: since this is a public function, it should probably be moved +// out of this header and into one with other public functions. + +// given a byte range ``v`` and an optional byte range ``salt``, a +// sequence number, public key ``pk`` (must be 32 bytes) and a secret key +// ``sk`` (must be 64 bytes), this function produces a signature which +// is written into a 64 byte buffer pointed to by ``sig``. The caller +// is responsible for allocating the destination buffer that's passed in +// as the ``sig`` argument. Typically it would be allocated on the stack. +TORRENT_EXPORT signature sign_mutable_item( + span v + , span salt + , sequence_number seq + , public_key const& pk + , secret_key const& sk); + +class TORRENT_EXTRA_EXPORT item +{ +public: + item() {} + item(public_key const& pk, span salt); + explicit item(entry v); + item(entry v + , span salt + , sequence_number seq + , public_key const& pk + , secret_key const& sk); + explicit item(bdecode_node const& v); + + void assign(entry v); + void assign(entry v, span salt + , sequence_number seq + , public_key const& pk + , secret_key const& sk); + void assign(bdecode_node const& v); + bool assign(bdecode_node const& v, span salt + , sequence_number seq + , public_key const& pk + , signature const& sig); + void assign(entry v, span salt + , sequence_number seq + , public_key const& pk + , signature const& sig); + + void clear() { m_value = entry(); } + bool empty() const { return m_value.type() == entry::undefined_t; } + + bool is_mutable() const { return m_mutable; } + + entry const& value() const { return m_value; } + public_key const& pk() const + { return m_pk; } + signature const& sig() const + { return m_sig; } + sequence_number seq() const { return m_seq; } + std::string const& salt() const { return m_salt; } + +private: + entry m_value; + std::string m_salt; + public_key m_pk; + signature m_sig; + sequence_number m_seq{0}; + bool m_mutable = false; +}; + +} } // namespace libtorrent::dht + +#endif // LIBTORRENT_ITEM_HPP diff --git a/include/libtorrent/kademlia/msg.hpp b/include/libtorrent/kademlia/msg.hpp new file mode 100644 index 0000000..7c011c0 --- /dev/null +++ b/include/libtorrent/kademlia/msg.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_KADEMLIA_MSG_HPP +#define TORRENT_KADEMLIA_MSG_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { + +struct bdecode_node; + +namespace dht { + +struct msg +{ + msg(bdecode_node const& m, udp::endpoint const& ep): message(m), addr(ep) {} + + // the message + bdecode_node const& message; + + // the address of the process sending or receiving + // the message. + udp::endpoint addr; +private: + // explicitly disallow assignment, to silence msvc warning + msg& operator=(msg const&); +}; + +struct key_desc_t +{ + char const* name; + int type; + int size; + int flags; + + enum { + // this argument is optional, parsing will not + // fail if it's not present + optional = 1, + // for dictionaries, the following entries refer + // to child nodes to this node, up until and including + // the next item that has the last_child flag set. + // these flags are nestable + parse_children = 2, + // this is the last item in a child dictionary + last_child = 4, + // the size argument refers to that the size + // has to be divisible by the number, instead + // of having that exact size + size_divisible = 8 + }; +}; + +// TODO: move this to its own .hpp/.cpp pair? +TORRENT_EXTRA_EXPORT bool verify_message_impl(bdecode_node const& msg, span desc + , span ret, span error); + +// verifies that a message has all the required +// entries and returns them in ret +template +bool verify_message(bdecode_node const& msg, key_desc_t const (&desc)[Size] + , bdecode_node (&ret)[Size], span error) +{ + return verify_message_impl(msg, desc, ret, error); +} + +} } + +#endif diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp new file mode 100644 index 0000000..68031ea --- /dev/null +++ b/include/libtorrent/kademlia/node.hpp @@ -0,0 +1,286 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef NODE_HPP +#define NODE_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // for udp::endpoint +#include +#include + +namespace libtorrent { + struct counters; +} + +namespace libtorrent { namespace dht { + +struct traversal_algorithm; +struct dht_observer; +struct msg; + +TORRENT_EXTRA_EXPORT entry write_nodes_entry(std::vector const& nodes); + +class announce_observer : public observer +{ +public: + announce_observer(std::shared_ptr algo + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algo), ep, id) + {} + + void reply(msg const&) override { flags |= flag_done; } +}; + +struct socket_manager +{ + virtual bool has_quota() = 0; + virtual bool send_packet(aux::listen_socket_handle const& s, entry& e, udp::endpoint const& addr) = 0; +protected: + ~socket_manager() = default; +}; + +// get the closest node to the id with the given family_name +using get_foreign_node_t = std::function; + +class TORRENT_EXTRA_EXPORT node +{ +public: + node(aux::listen_socket_handle const& sock, socket_manager* sock_man + , dht::settings const& settings + , node_id const& nid + , dht_observer* observer, counters& cnt + , get_foreign_node_t get_foreign_node + , dht_storage_interface& storage); + + ~node(); + + node(node const&) = delete; + node& operator=(node const&) = delete; + node(node&&) = delete; + node& operator=(node&&) = delete; + + void update_node_id(); + + void tick(); + void bootstrap(std::vector const& nodes + , find_data::nodes_callback const& f); + void add_router_node(udp::endpoint const& router); + + void unreachable(udp::endpoint const& ep); + void incoming(aux::listen_socket_handle const& s, msg const& m); + +#if TORRENT_ABI_VERSION == 1 + int num_torrents() const { return int(m_storage.num_torrents()); } + int num_peers() const { return int(m_storage.num_peers()); } +#endif + + int bucket_size(int bucket); + + node_id const& nid() const { return m_id; } + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t search_id() { return m_search_id++; } +#endif + + std::tuple size() const { return m_table.size(); } + std::int64_t num_global_nodes() const + { return m_table.num_global_nodes(); } + +#if TORRENT_ABI_VERSION == 1 + int data_size() const { return int(m_storage.num_torrents()); } +#endif + + void get_peers(sha1_hash const& info_hash + , std::function const&)> dcallback + , std::function> const&)> ncallback + , announce_flags_t flags); + void announce(sha1_hash const& info_hash, int listen_port, announce_flags_t flags + , std::function const&)> f); + + void direct_request(udp::endpoint const& ep, entry& e + , std::function f); + + void get_item(sha1_hash const& target, std::function f); + void get_item(public_key const& pk, std::string const& salt, std::function f); + + void put_item(sha1_hash const& target, entry const& data, std::function f); + void put_item(public_key const& pk, std::string const& salt + , std::function f + , std::function data_cb); + + void sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f); + + bool verify_token(string_view token, sha1_hash const& info_hash + , udp::endpoint const& addr) const; + + std::string generate_token(udp::endpoint const& addr, sha1_hash const& info_hash); + + // the returned time is the delay until connection_timeout() + // should be called again the next time + time_duration connection_timeout(); + + // generates a new secret number used to generate write tokens + void new_write_key(); + + // pings the given node, and adds it to + // the routing table if it response and if the + // bucket is not full. + void add_node(udp::endpoint const& node); + + int branch_factor() const { return m_settings.search_branching; } + + void add_traversal_algorithm(traversal_algorithm* a) + { + std::lock_guard l(m_mutex); + m_running_requests.insert(a); + } + + void remove_traversal_algorithm(traversal_algorithm* a) + { + std::lock_guard l(m_mutex); + m_running_requests.erase(a); + } + + void status(std::vector& table + , std::vector& requests); + + std::tuple get_stats_counters() const; + +#if TORRENT_ABI_VERSION == 1 + void status(libtorrent::session_status& s); +#endif + + dht::settings const& settings() const { return m_settings; } + counters& stats_counters() const { return m_counters; } + + dht_observer* observer() const { return m_observer; } + + udp protocol() const { return m_protocol.protocol; } + char const* protocol_family_name() const { return m_protocol.family_name; } + char const* protocol_nodes_key() const { return m_protocol.nodes_key; } + + bool native_address(udp::endpoint const& ep) const + { return ep.protocol().family() == m_protocol.protocol.family(); } + bool native_address(tcp::endpoint const& ep) const + { return ep.protocol().family() == m_protocol.protocol.family(); } + bool native_address(address const& addr) const + { + return (addr.is_v4() && m_protocol.protocol == udp::v4()) + || (addr.is_v6() && m_protocol.protocol == udp::v6()); + } + +private: + + void send_single_refresh(udp::endpoint const& ep, int bucket + , node_id const& id = node_id()); + bool lookup_peers(sha1_hash const& info_hash, entry& reply + , bool noseed, bool scrape, address const& requester) const; + + dht::settings const& m_settings; + + std::mutex m_mutex; + + // this list must be destructed after the rpc manager + // since it might have references to it + std::set m_running_requests; + + void incoming_request(msg const& h, entry& e); + + void write_nodes_entries(sha1_hash const& info_hash + , bdecode_node const& want, entry& r); + + node_id m_id; + +public: + routing_table m_table; + rpc_manager m_rpc; + aux::listen_socket_handle const m_sock; + +private: + + struct protocol_descriptor + { + udp protocol; + char const* family_name; + char const* nodes_key; + }; + + static protocol_descriptor const& map_protocol_to_descriptor(udp protocol); + + socket_manager* m_sock_man; + + get_foreign_node_t m_get_foreign_node; + + dht_observer* m_observer; + + protocol_descriptor const& m_protocol; + + time_point m_last_tracker_tick; + + // the last time we issued a bootstrap or a refresh on our own ID, to expand + // the routing table buckets close to us. + time_point m_last_self_refresh; + + // secret random numbers used to create write tokens + std::uint32_t m_secret[2]; + + counters& m_counters; + + dht_storage_interface& m_storage; + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t m_search_id = 0; +#endif +}; + +} } // namespace libtorrent::dht + +#endif // NODE_HPP diff --git a/include/libtorrent/kademlia/node_entry.hpp b/include/libtorrent/kademlia/node_entry.hpp new file mode 100644 index 0000000..fc8ca39 --- /dev/null +++ b/include/libtorrent/kademlia/node_entry.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef KADEMLIA_NODE_ENTRY_HPP +#define KADEMLIA_NODE_ENTRY_HPP + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/time.hpp" // for time_point +#include "libtorrent/aux_/time.hpp" // for time_now + +namespace libtorrent { namespace dht { + +struct TORRENT_EXTRA_EXPORT node_entry +{ + node_entry(node_id const& id_, udp::endpoint const& ep, int roundtriptime = 0xffff + , bool pinged = false); + explicit node_entry(udp::endpoint const& ep); + node_entry() = default; + void update_rtt(int new_rtt); + + bool pinged() const { return timeout_count != 0xff; } + void set_pinged() { if (timeout_count == 0xff) timeout_count = 0; } + void timed_out() { if (pinged() && timeout_count < 0xfe) ++timeout_count; } + int fail_count() const { return pinged() ? timeout_count : 0; } + void reset_fail_count() { if (pinged()) timeout_count = 0; } + udp::endpoint ep() const { return endpoint; } + bool confirmed() const { return timeout_count == 0; } + address addr() const { return endpoint.address(); } + int port() const { return endpoint.port; } + + // compares which node_entry is "better". Smaller is better + bool operator<(node_entry const& rhs) const + { + return std::make_tuple(!verified, rtt) < std::make_tuple(!rhs.verified, rhs.rtt); + } + +#ifndef TORRENT_DISABLE_LOGGING + time_point first_seen = aux::time_now(); +#endif + + // the time we last received a response for a request to this peer + time_point last_queried = min_time(); + + node_id id{nullptr}; + + union_endpoint endpoint; + + // the average RTT of this node + std::uint16_t rtt = 0xffff; + + // the number of times this node has failed to + // respond in a row + // 0xff is a special value to indicate we have not pinged this node yet + std::uint8_t timeout_count = 0xff; + + bool verified = false; +}; + +} } // namespace libtorrent::dht + +#endif diff --git a/include/libtorrent/kademlia/node_id.hpp b/include/libtorrent/kademlia/node_id.hpp new file mode 100644 index 0000000..f470f41 --- /dev/null +++ b/include/libtorrent/kademlia/node_id.hpp @@ -0,0 +1,74 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef NODE_ID_HPP +#define NODE_ID_HPP + +#include +#include + +#include +#include +#include + +namespace libtorrent { namespace dht { + +using node_id = libtorrent::sha1_hash; + +// returns the distance between the two nodes +// using the kademlia XOR-metric +TORRENT_EXTRA_EXPORT node_id distance(node_id const& n1, node_id const& n2); + +// returns true if: distance(n1, ref) < distance(n2, ref) +TORRENT_EXTRA_EXPORT bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref); + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// useful for finding out which bucket a node belongs to +// the value that's returned is the number of trailing bits +// after the shared bit prefix of ``n1`` and ``n2``. +// if the first bits are different, that's 160. +TORRENT_EXTRA_EXPORT int distance_exp(node_id const& n1, node_id const& n2); +TORRENT_EXTRA_EXPORT int min_distance_exp(node_id const& n1, std::vector const& ids); + +TORRENT_EXTRA_EXPORT node_id generate_id(address const& external_ip); +TORRENT_EXTRA_EXPORT node_id generate_random_id(); +TORRENT_EXTRA_EXPORT void make_id_secret(node_id& in); +TORRENT_EXTRA_EXPORT node_id generate_secret_id(); +TORRENT_EXTRA_EXPORT bool verify_secret_id(node_id const& nid); +TORRENT_EXTRA_EXPORT node_id generate_id_impl(address const& ip_, std::uint32_t r); + +TORRENT_EXTRA_EXPORT bool verify_id(node_id const& nid, address const& source_ip); +TORRENT_EXTRA_EXPORT bool matching_prefix(node_id const& nid, int mask, int prefix, int offset); +TORRENT_EXTRA_EXPORT node_id generate_prefix_mask(int bits); + +} } // namespace libtorrent::dht + +#endif // NODE_ID_HPP diff --git a/include/libtorrent/kademlia/observer.hpp b/include/libtorrent/kademlia/observer.hpp new file mode 100644 index 0000000..2ae5637 --- /dev/null +++ b/include/libtorrent/kademlia/observer.hpp @@ -0,0 +1,153 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef OBSERVER_HPP +#define OBSERVER_HPP + +#include +#include + +#include +#include +#include +#include // for udp +#include + +namespace libtorrent { +namespace dht { + +struct dht_observer; +struct observer; +struct msg; +struct traversal_algorithm; + +using observer_flags_t = libtorrent::flags::bitfield_flag; + +struct TORRENT_EXTRA_EXPORT observer + : std::enable_shared_from_this +{ + observer(std::shared_ptr a + , udp::endpoint const& ep, node_id const& id) + : m_algorithm(std::move(a)) + , m_id(id) + { + TORRENT_ASSERT(m_algorithm); + set_target(ep); + } + + observer(observer const&) = delete; + observer& operator=(observer const&) = delete; + + // defined in rpc_manager.cpp + virtual ~observer(); + + // this is called when a reply is received + virtual void reply(msg const& m) = 0; + + // this is called if no response has been received after + // a few seconds, before the request has timed out + void short_timeout(); + + bool has_short_timeout() const { return bool(flags & flag_short_timeout); } + + // this is called when no reply has been received within + // some timeout, or a reply with incorrect format. + virtual void timeout(); + + // if this is called the destructor should + // not invoke any new messages, and should + // only clean up. It means the rpc-manager + // is being destructed + void abort(); + + dht_observer* get_observer() const; + + traversal_algorithm* algorithm() const { return m_algorithm.get(); } + + time_point sent() const { return m_sent; } + + void set_target(udp::endpoint const& ep); + address target_addr() const; + udp::endpoint target_ep() const; + + void set_id(node_id const& id); + node_id const& id() const { return m_id; } + + static constexpr observer_flags_t flag_queried = 0_bit; + static constexpr observer_flags_t flag_initial = 1_bit; + static constexpr observer_flags_t flag_no_id = 2_bit; + static constexpr observer_flags_t flag_short_timeout = 3_bit; + static constexpr observer_flags_t flag_failed = 4_bit; + static constexpr observer_flags_t flag_ipv6_address = 5_bit; + static constexpr observer_flags_t flag_alive = 6_bit; + static constexpr observer_flags_t flag_done = 7_bit; + +protected: + + void done(); + +private: + + std::shared_ptr self() + { return shared_from_this(); } + + time_point m_sent; + + std::shared_ptr const m_algorithm; + + node_id m_id; + + union addr_t + { + address_v6::bytes_type v6; + address_v4::bytes_type v4; + } m_addr; + + std::uint16_t m_port = 0; + +public: + observer_flags_t flags{}; + +#if TORRENT_USE_ASSERTS + bool m_in_constructor = true; + bool m_was_sent = false; + bool m_was_abandoned = false; + bool m_in_use = true; +#endif +}; + +using observer_ptr = std::shared_ptr; + +} +} + +#endif diff --git a/include/libtorrent/kademlia/put_data.hpp b/include/libtorrent/kademlia/put_data.hpp new file mode 100644 index 0000000..61e1980 --- /dev/null +++ b/include/libtorrent/kademlia/put_data.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg, Thomas Yuan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PUT_DATA_HPP +#define TORRENT_PUT_DATA_HPP + +#include +#include +#include +#include + +#include + +namespace libtorrent { namespace dht { + +struct msg; +class node; + +struct put_data: traversal_algorithm +{ + using put_callback = std::function; + + put_data(node& node, put_callback const& callback); + + char const* name() const override; + void start() override; + + void set_data(item&& data) { m_data = std::move(data); } + void set_data(item const& data) = delete; + + void set_targets(std::vector> const& targets); + +protected: + + void done() override; + bool invoke(observer_ptr o) override; + + put_callback m_put_callback; + item m_data; + bool m_done = false; +}; + +struct put_data_observer : traversal_observer +{ + put_data_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id, std::string token) + : traversal_observer(std::move(algorithm), ep, id) + , m_token(std::move(token)) + { + } + + void reply(msg const&) override { done(); } + + std::string m_token; +}; + +} } // namespace libtorrent::dht + +#endif // TORRENT_PUT_DATA_HPP diff --git a/include/libtorrent/kademlia/refresh.hpp b/include/libtorrent/kademlia/refresh.hpp new file mode 100644 index 0000000..38200ec --- /dev/null +++ b/include/libtorrent/kademlia/refresh.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef REFRESH_050324_HPP +#define REFRESH_050324_HPP + +#include +#include + +namespace libtorrent { namespace dht { + +class bootstrap : public get_peers +{ +public: + using done_callback = get_peers::nodes_callback; + + bootstrap(node& dht_node, node_id const& target + , done_callback const& callback); + char const* name() const override; + + observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id) override; + +protected: + + bool invoke(observer_ptr o) override; + + void done() override; + +}; + +} } // namespace libtorrent::dht + +#endif // REFRESH_050324_HPP diff --git a/include/libtorrent/kademlia/routing_table.hpp b/include/libtorrent/kademlia/routing_table.hpp new file mode 100644 index 0000000..a945470 --- /dev/null +++ b/include/libtorrent/kademlia/routing_table.hpp @@ -0,0 +1,334 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ROUTING_TABLE_HPP +#define ROUTING_TABLE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht { + +struct settings; +struct dht_logger; + +using bucket_t = aux::vector; + +struct routing_table_node +{ + bucket_t replacements; + bucket_t live_nodes; +}; + +struct ipv4_hash +{ + using argument_type = address_v4::bytes_type; + using result_type = std::size_t; + result_type operator()(argument_type const& ip) const + { + return std::hash()(*reinterpret_cast(&ip[0])); + } +}; + +struct ipv6_hash +{ + using argument_type = address_v6::bytes_type; + using result_type = std::size_t; + result_type operator()(argument_type const& ip) const + { + return std::hash()(*reinterpret_cast(&ip[0])); + } +}; + +struct TORRENT_EXTRA_EXPORT ip_set +{ + void insert(address const& addr); + bool exists(address const& addr) const; + void erase(address const& addr); + + void clear() + { + m_ip4s.clear(); + m_ip6s.clear(); + } + + bool operator==(ip_set const& rh) + { + return m_ip4s == rh.m_ip4s && m_ip6s == rh.m_ip6s; + } + + std::size_t size() const { return m_ip4s.size() + m_ip6s.size(); } + + // these must be multisets because there can be multiple routing table + // entries for a single IP when restrict_routing_ips is set to false + std::unordered_multiset m_ip4s; + std::unordered_multiset m_ip6s; +}; + +// Each routing table bucket represents node IDs with a certain number of bits +// of prefix in common with our own node ID. Each bucket fits 8 nodes (and +// sometimes more, closer to the top). In order to minimize the number of hops +// necessary to traverse the DHT, we want the nodes in our buckets to be spread +// out across all possible "sub-branches". This is what the "classify" refers +// to. The 3 (or more) bits following the shared bit prefix. +TORRENT_EXTRA_EXPORT std::uint8_t classify_prefix(int bucket_idx, bool last_bucket + , int bucket_size, node_id nid); + +TORRENT_EXTRA_EXPORT bool all_in_same_bucket(span b + , node_id const& id, int bucket_index); + +// differences in the implementation from the description in +// the paper: +// +// * Nodes are not marked as being stale, they keep a counter +// that tells how many times in a row they have failed. When +// a new node is to be inserted, the node that has failed +// the most times is replaced. If none of the nodes in the +// bucket has failed, then it is put in the replacement +// cache (just like in the paper). +// * The routing table bucket sizes are larger towards the "top" of the routing +// table. This is to get closer to the target in fewer round-trips. +// * Nodes with lower RTT are preferred and may replace nodes with higher RTT +// * Nodes that are "verified" (i.e. use a node-ID derived from their IP) are +// preferred and may replace nodes that are not verified. + +TORRENT_EXTRA_EXPORT bool mostly_verified_nodes(bucket_t const&); +TORRENT_EXTRA_EXPORT bool compare_ip_cidr(address const& lhs, address const& rhs); + +class TORRENT_EXTRA_EXPORT routing_table +{ +public: + // TODO: 3 to improve memory locality and scanning performance, turn the + // routing table into a single vector with boundaries for the nodes instead. + // Perhaps replacement nodes should be in a separate vector. + using table_t = aux::vector; + + routing_table(node_id const& id, udp proto + , int bucket_size + , dht::settings const& settings + , dht_logger* log); + + routing_table(routing_table const&) = delete; + routing_table& operator=(routing_table const&) = delete; + +#if TORRENT_ABI_VERSION == 1 + void status(session_status& s) const; +#endif + + void status(std::vector& s) const; + + void node_failed(node_id const& id, udp::endpoint const& ep); + + // adds an endpoint that will never be added to + // the routing table + void add_router_node(udp::endpoint const& router); + + // iterates over the router nodes added + using router_iterator = std::set::const_iterator; + router_iterator begin() const { return m_router_nodes.begin(); } + router_iterator end() const { return m_router_nodes.end(); } + + enum add_node_status_t { + failed_to_add = 0, + node_added, + need_bucket_split + }; + add_node_status_t add_node_impl(node_entry e); + + bool add_node(node_entry const& e); + + // this function is called every time the node sees + // a sign of a node being alive. This node will either + // be inserted in the k-buckets or be moved to the top + // of its bucket. + bool node_seen(node_id const& id, udp::endpoint const& ep, int rtt); + + // this may add a node to the routing table and mark it as + // not pinged. If the bucket the node falls into is full, + // the node will be ignored. + void heard_about(node_id const& id, udp::endpoint const& ep); + + // change our node ID. This can be expensive since nodes must be moved around + // and potentially dropped + void update_node_id(node_id const& id); + + node_entry const* next_refresh(); + + enum + { + // nodes that have not been pinged are considered failed by this flag + include_failed = 1 + }; + + // fills the vector with the count nodes from our buckets that + // are nearest to the given id. + void find_node(node_id const& id, std::vector& l + , int options, int count = 0); + void remove_node(node_entry* n, bucket_t* b); + + int bucket_size(int bucket) const + { + int num_buckets = int(m_buckets.size()); + if (num_buckets == 0) return 0; + if (bucket >= num_buckets) bucket = num_buckets - 1; + table_t::const_iterator i = m_buckets.begin(); + std::advance(i, bucket); + return int(i->live_nodes.size()); + } + + void for_each_node(std::function live_cb + , std::function replacements_cb) const; + + void for_each_node(std::function f) const + { for_each_node(f, f); } + + int bucket_size() const { return m_bucket_size; } + + // returns the number of nodes in the main buckets, number of nodes in the + // replacement buckets and the number of nodes in the main buckets that have + // been pinged and confirmed up + std::tuple size() const; + + std::int64_t num_global_nodes() const; + + // the number of bits down we have full buckets + // i.e. essentially the number of full buckets + // we have + int depth() const; + + int num_active_buckets() const { return int(m_buckets.size()); } + + int bucket_limit(int bucket) const; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + bool is_full(int bucket) const; + + bool native_address(address const& addr) const + { + return (addr.is_v4() && m_protocol == udp::v4()) + || (addr.is_v6() && m_protocol == udp::v6()); + } + + bool native_endpoint(udp::endpoint const& ep) const + { return ep.protocol() == m_protocol; } + + node_id const& id() const + { return m_id; } + + table_t const& buckets() const + { return m_buckets; } + +private: + +#ifndef TORRENT_DISABLE_LOGGING + dht_logger* m_log; + void log_node_failed(node_id const& nid, node_entry const& ne) const; +#endif + + table_t::iterator find_bucket(node_id const& id); + void remove_node_internal(node_entry* n, bucket_t& b); + + void split_bucket(); + + // return a pointer the node_entry with the given endpoint + // or 0 if we don't have such a node. Both the address and the + // port has to match + std::tuple + find_node(udp::endpoint const& ep); + + // if the bucket is not full, try to fill it with nodes from the + // replacement list + void fill_from_replacements(table_t::iterator bucket); + + void prune_empty_bucket(); + + dht::settings const& m_settings; + + // (k-bucket, replacement cache) pairs + // the first entry is the bucket the furthest + // away from our own ID. Each time the bucket + // closest to us (m_buckets.back()) has more than + // bucket size nodes in it, another bucket is + // added to the end and it's split up between them + table_t m_buckets; + + node_id m_id; // our own node id + udp m_protocol; // protocol this table is for + + // the last seen depth (i.e. levels in the routing table) + // it's mutable because it's updated by depth(), which is const + mutable int m_depth; + + // the last time we refreshed our own bucket + // refreshed every 15 minutes + mutable time_point m_last_self_refresh; + + // this is a set of all the endpoints that have + // been identified as router nodes. They will + // be used in searches, but they will never + // be added to the routing table. + std::set m_router_nodes; + + // these are all the IPs that are in the routing + // table. It's used to only allow a single entry + // per IP in the whole table. + ip_set m_ips; + + // constant called k in paper + int const m_bucket_size; +}; + +TORRENT_EXTRA_EXPORT routing_table::add_node_status_t +replace_node_impl(node_entry const& e, bucket_t& b, ip_set& ips + , int bucket_index, int bucket_size_limit, bool last_bucket +#ifndef TORRENT_DISABLE_LOGGING + , dht_logger* log +#endif + ); + +} } // namespace libtorrent::dht + +#endif // ROUTING_TABLE_HPP diff --git a/include/libtorrent/kademlia/rpc_manager.hpp b/include/libtorrent/kademlia/rpc_manager.hpp new file mode 100644 index 0000000..bb155d0 --- /dev/null +++ b/include/libtorrent/kademlia/rpc_manager.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef RPC_MANAGER_HPP +#define RPC_MANAGER_HPP + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include +#include + +namespace libtorrent { class entry; } + +namespace libtorrent { +namespace dht { + +struct dht_settings; +struct dht_logger; +struct socket_manager; + +struct TORRENT_EXTRA_EXPORT null_observer : observer +{ + null_observer(std::shared_ptr a + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(a), ep, id) {} + void reply(msg const&) override { flags |= flag_done; } +}; + +class routing_table; + +class TORRENT_EXTRA_EXPORT rpc_manager +{ +public: + + rpc_manager(node_id const& our_id + , dht_settings const& settings + , routing_table& table + , aux::listen_socket_handle const& sock + , socket_manager* sock_man + , dht_logger* log); + ~rpc_manager(); + + void unreachable(udp::endpoint const& ep); + + // returns true if the node needs a refresh + // if so, id is assigned the node id to refresh + bool incoming(msg const&, node_id* id); + time_duration tick(); + + bool invoke(entry& e, udp::endpoint const& target + , observer_ptr o); + + void add_our_id(entry& e); + +#if TORRENT_USE_ASSERTS + size_t allocation_size() const; +#endif +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + template + std::shared_ptr allocate_observer(Args&&... args) + { + void* ptr = allocate_observer(); + if (ptr == nullptr) return std::shared_ptr(); + + auto deleter = [this](observer* o) + { + TORRENT_ASSERT(o->m_in_use); + o->~observer(); + free_observer(o); + }; + return std::shared_ptr(new (ptr) T(std::forward(args)...), deleter); + } + + int num_allocated_observers() const { return m_allocated_observers; } + + void update_node_id(node_id const& id) { m_our_id = id; } + +private: + + void* allocate_observer(); + void free_observer(void* ptr); + + mutable boost::pool<> m_pool_allocator; + + std::unordered_multimap m_transactions; + + aux::listen_socket_handle m_sock; + socket_manager* m_sock_man; +#ifndef TORRENT_DISABLE_LOGGING + dht_logger* m_log; +#endif + dht_settings const& m_settings; + routing_table& m_table; + node_id m_our_id; + std::uint32_t m_allocated_observers:31; + std::uint32_t m_destructing:1; +}; + +} } // namespace libtorrent::dht + +#endif diff --git a/include/libtorrent/kademlia/sample_infohashes.hpp b/include/libtorrent/kademlia/sample_infohashes.hpp new file mode 100644 index 0000000..1fbd863 --- /dev/null +++ b/include/libtorrent/kademlia/sample_infohashes.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SAMPLE_INFOHASHES_HPP +#define TORRENT_SAMPLE_INFOHASHES_HPP + +#include + +#include +#include + +namespace libtorrent { namespace dht +{ + +class sample_infohashes final : public traversal_algorithm +{ +public: + + using data_callback = std::function + , std::vector>)>; + + sample_infohashes(node& dht_node + , node_id const& target + , data_callback const& dcallback); + + char const* name() const override; + + void got_samples(time_duration interval + , int num, std::vector samples + , std::vector> nodes); + +protected: + + data_callback m_data_callback; +}; + +class sample_infohashes_observer final : public traversal_observer +{ +public: + + sample_infohashes_observer(std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id); + + void reply(msg const&) override; +}; + +}} // namespace libtorrent::dht + +#endif // TORRENT_SAMPLE_INFOHASHES_HPP diff --git a/include/libtorrent/kademlia/traversal_algorithm.hpp b/include/libtorrent/kademlia/traversal_algorithm.hpp new file mode 100644 index 0000000..aa62246 --- /dev/null +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -0,0 +1,172 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRAVERSAL_ALGORITHM_050324_HPP +#define TRAVERSAL_ALGORITHM_050324_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { + +namespace dht { + +class node; +struct node_endpoint; + +using traversal_flags_t = libtorrent::flags::bitfield_flag; + +// this class may not be instantiated as a stack object +struct TORRENT_EXTRA_EXPORT traversal_algorithm + : std::enable_shared_from_this +{ + void traverse(node_id const& id, udp::endpoint const& addr); + void finished(observer_ptr o); + + static constexpr traversal_flags_t prevent_request = 0_bit; + static constexpr traversal_flags_t short_timeout = 1_bit; + + void failed(observer_ptr o, traversal_flags_t flags = {}); + virtual ~traversal_algorithm(); + void status(dht_lookup& l); + + virtual char const* name() const; + virtual void start(); + + node_id const& target() const { return m_target; } + + void resort_result(observer*); + void add_entry(node_id const& id, udp::endpoint const& addr, observer_flags_t flags); + + traversal_algorithm(node& dht_node, node_id const& target); + traversal_algorithm(traversal_algorithm const&) = delete; + traversal_algorithm& operator=(traversal_algorithm const&) = delete; + int invoke_count() const { TORRENT_ASSERT(m_invoke_count >= 0); return m_invoke_count; } + int branch_factor() const { TORRENT_ASSERT(m_branch_factor >= 0); return m_branch_factor; } + + node& get_node() const { return m_node; } + +#ifndef TORRENT_DISABLE_LOGGING + std::uint32_t id() const { return m_id; } +#endif + +protected: + + std::shared_ptr self() + { return shared_from_this(); } + + // returns true if we're done + bool add_requests(); + + void add_router_entries(); + void init(); + + virtual void done(); + // should construct an algorithm dependent + // observer in ptr. + virtual observer_ptr new_observer(udp::endpoint const& ep + , node_id const& id); + + virtual bool invoke(observer_ptr) { return false; } + + int num_responses() const { return m_responses; } + int num_timeouts() const { return m_timeouts; } + + node& m_node; + + // this vector is sorted by node-id distance from our node id. Closer nodes + // are earlier in the vector. However, not the entire vector is necessarily + // sorted, the tail of the vector may contain nodes out-of-order. This is + // used when bootstrapping. The ``m_sorted_results`` member indicates how + // many of the first elements are sorted. + std::vector m_results; + + int num_sorted_results() const { return m_sorted_results; } + +private: + + node_id const m_target; + std::int8_t m_invoke_count = 0; + std::int8_t m_branch_factor = 3; + // the number of elements at the beginning of m_results that are sorted by + // node_id. + std::int8_t m_sorted_results = 0; + std::int16_t m_responses = 0; + std::int16_t m_timeouts = 0; + + // set to true when done() is called, and will prevent adding new results, as + // they would never be serviced and the whole traversal algorithm would stall + // and leak + bool m_done = false; + +#ifndef TORRENT_DISABLE_LOGGING + // this is a unique ID for this specific traversal_algorithm instance, + // just used for logging + std::uint32_t m_id; +#endif + + // the IP addresses of the nodes in m_results + std::set m_peer4_prefixes; + std::set m_peer6_prefixes; +#ifndef TORRENT_DISABLE_LOGGING + void log_timeout(observer_ptr const& o, char const* prefix) const; +#endif +}; + +void look_for_nodes(char const* nodes_key, udp const& protocol + , bdecode_node const& r, std::function f); + +struct traversal_observer : observer +{ + traversal_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algorithm), ep, id) + {} + + // parses out "nodes" and keeps traversing + void reply(msg const&) override; +}; + +} // namespace dht +} // namespace libtorrent + +#endif // TRAVERSAL_ALGORITHM_050324_HPP diff --git a/include/libtorrent/kademlia/types.hpp b/include/libtorrent/kademlia/types.hpp new file mode 100644 index 0000000..23c0c87 --- /dev/null +++ b/include/libtorrent/kademlia/types.hpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef LIBTORRENT_TYPES_HPP +#define LIBTORRENT_TYPES_HPP + +#include +#include +#include + +namespace libtorrent { namespace dht { + + struct public_key + { + public_key() = default; + explicit public_key(char const* b) + { std::copy(b, b + len, bytes.begin()); } + bool operator==(public_key const& rhs) const + { return bytes == rhs.bytes; } + static constexpr int len = 32; + std::array bytes; + }; + + struct secret_key + { + secret_key() = default; + explicit secret_key(char const* b) + { std::copy(b, b + len, bytes.begin()); } + bool operator==(secret_key const& rhs) const + { return bytes == rhs.bytes; } + static constexpr int len = 64; + std::array bytes; + }; + + struct signature + { + signature() = default; + explicit signature(char const* b) + { std::copy(b, b + len, bytes.begin()); } + bool operator==(signature const& rhs) const + { return bytes == rhs.bytes; } + static constexpr int len = 64; + std::array bytes; + }; + + struct sequence_number + { + sequence_number() : value(0) {} + explicit sequence_number(std::int64_t v) : value(v) {} + sequence_number(sequence_number const& sqn) = default; + bool operator<(sequence_number rhs) const + { return value < rhs.value; } + bool operator>(sequence_number rhs) const + { return value > rhs.value; } + sequence_number& operator=(sequence_number rhs) + { value = rhs.value; return *this; } + bool operator<=(sequence_number rhs) const + { return value <= rhs.value; } + bool operator==(sequence_number const& rhs) const + { return value == rhs.value; } + sequence_number& operator++() + { ++value; return *this; } + std::int64_t value; + }; + +}} + +#endif // LIBTORRENT_TYPES_HPP diff --git a/include/libtorrent/lazy_entry.hpp b/include/libtorrent/lazy_entry.hpp new file mode 100644 index 0000000..bc91d3d --- /dev/null +++ b/include/libtorrent/lazy_entry.hpp @@ -0,0 +1,430 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LAZY_ENTRY_HPP_INCLUDED +#define TORRENT_LAZY_ENTRY_HPP_INCLUDED + +#if TORRENT_ABI_VERSION == 1 + +#include +#include +#include +#include +#include +#include +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/bdecode.hpp" // for error codes + +namespace libtorrent { + + struct lazy_entry; + + // This function decodes bencoded_ data. + // + // .. _bencoded: http://wiki.theory.org/index.php/BitTorrentSpecification + // + // The lazy bdecoder and lazy_entry has been deprecated in favour of + // bdecode_node and its corresponding bdecode() function. + // + // *lazy* refers to the fact that it doesn't copy any actual data out of the + // bencoded buffer. It builds a tree of ``lazy_entry`` which has pointers into + // the bencoded buffer. This makes it very fast and efficient. On top of that, + // it is not recursive, which saves a lot of stack space when parsing deeply + // nested trees. However, in order to protect against potential attacks, the + // ``depth_limit`` and ``item_limit`` control how many levels deep the tree is + // allowed to get. With recursive parser, a few thousand levels would be enough + // to exhaust the threads stack and terminate the process. The ``item_limit`` + // protects against very large structures, not necessarily deep. Each bencoded + // item in the structure causes the parser to allocate some amount of memory, + // this memory is constant regardless of how much data actually is stored in + // the item. One potential attack is to create a bencoded list of hundreds of + // thousands empty strings, which would cause the parser to allocate a significant + // amount of memory, perhaps more than is available on the machine, and effectively + // provide a denial of service. The default item limit is set as a reasonable + // upper limit for desktop computers. Very few torrents have more items in them. + // The limit corresponds to about 25 MB, which might be a bit much for embedded + // systems. + // + // ``start`` and ``end`` defines the bencoded buffer to be decoded. ``ret`` is + // the ``lazy_entry`` which is filled in with the whole decoded tree. ``ec`` + // is a reference to an ``error_code`` which is set to describe the error encountered + // in case the function fails. ``error_pos`` is an optional pointer to an int, + // which will be set to the byte offset into the buffer where an error occurred, + // in case the function fails. + TORRENT_DEPRECATED_EXPORT int lazy_bdecode(char const* start, char const* end + , lazy_entry& ret, error_code& ec, int* error_pos = nullptr + , int depth_limit = 1000, int item_limit = 1000000); + + // for backwards compatibility, does not report error code + // deprecated in 0.16 + TORRENT_DEPRECATED_EXPORT int lazy_bdecode(char const* start, char const* end + , lazy_entry& ret, int depth_limit = 1000, int item_limit = 1000000); + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // this is a string that is not 0-terminated. Instead it + // comes with a length, specified in bytes. This is particularly + // useful when parsing bencoded structures, because strings are + // not 0-terminated internally, and requiring 0-termination + // would require copying the string. + // + // see lazy_entry::string_pstr(). + struct TORRENT_DEPRECATED_EXPORT pascal_string + { + // construct a string pointing to the characters at ``p`` + // of length ``l`` characters. No 0-termination is required. + pascal_string(char const* p, int l): len(l), ptr(p) {} + + // the number of characters in the string. + int len; + + // the pointer to the first character in the string. This is + // not 0-terminated, but instead consult the ``len`` field + // to know how many characters follow. + char const* ptr; + + // lexicographical comparison of strings. Order is consistent + // with memcmp. + bool operator<(pascal_string const& rhs) const + { + return std::memcmp(ptr, rhs.ptr, std::size_t((std::min)(len, rhs.len))) < 0 + || len < rhs.len; + } + }; + + struct lazy_dict_entry; + + // this object represent a node in a bencoded structure. It is a variant + // type whose concrete type is one of: + // + // 1. dictionary (maps strings -> lazy_entry) + // 2. list (sequence of lazy_entry, i.e. heterogeneous) + // 3. integer + // 4. string + // + // There is also a ``none`` type, which is used for uninitialized + // lazy_entries. + struct TORRENT_DEPRECATED_EXPORT lazy_entry + { + // The different types a lazy_entry can have + enum entry_type_t + { + none_t, dict_t, list_t, string_t, int_t + }; + + // internal + lazy_entry() : m_size(0), m_type(none_t) + { m_data.start = nullptr; } + + // tells you which specific type this lazy entry has. + // See entry_type_t. The type determines which subset of + // member functions are valid to use. + entry_type_t type() const { return entry_type_t(m_type); } + + // start points to the first decimal digit + // length is the number of digits + void construct_int(char const* start, int const length) + { + TORRENT_ASSERT(m_type == none_t); + m_type = int_t; + m_data.start = start; + m_size = std::uint32_t(length); + m_begin = start - 1; // include 'i' + m_len = std::uint32_t(length + 2); // include 'e' + } + + // requires the type to be an integer. return the integer value + std::int64_t int_value() const; + + // internal + void construct_string(char const* start, int length); + + // the string is not 0-terminated! + // use string_length() to determine how many bytes + // are part of the string. + char const* string_ptr() const + { + TORRENT_ASSERT(m_type == string_t); + return m_data.start; + } + + // this will return a 0-terminated string + // it will write to the source buffer! + char const* string_cstr() const + { + TORRENT_ASSERT(m_type == string_t); + const_cast(m_data.start)[m_size] = 0; + return m_data.start; + } + + // if this is a string, returns a pascal_string + // representing the string value. + pascal_string string_pstr() const + { + TORRENT_ASSERT(m_type == string_t); + return pascal_string(m_data.start, m_size); + } + + // if this is a string, returns the string as a std::string. + // (which requires a copy) + std::string string_value() const + { + TORRENT_ASSERT(m_type == string_t); + return std::string(m_data.start, m_size); + } + + // if the lazy_entry is a string, returns the + // length of the string, in bytes. + int string_length() const + { return m_size; } + + // internal + void construct_dict(char const* begin) + { + TORRENT_ASSERT(m_type == none_t); + m_type = dict_t; + m_size = 0; + m_begin = begin; + } + + // internal + lazy_entry* dict_append(char const* name); + // internal + void pop(); + + // if this is a dictionary, look for a key ``name``, and return + // a pointer to its value, or nullptr if there is none. + lazy_entry* dict_find(char const* name); + lazy_entry const* dict_find(char const* name) const + { return const_cast(this)->dict_find(name); } + lazy_entry* dict_find(std::string const& name); + lazy_entry const* dict_find(std::string const& name) const + { return const_cast(this)->dict_find(name); } + lazy_entry const* dict_find_string(char const* name) const; + + // if this is a dictionary, look for a key ``name`` whose value + // is a string. If such key exist, return a pointer to + // its value, otherwise nullptr. + std::string dict_find_string_value(char const* name) const; + pascal_string dict_find_pstr(char const* name) const; + + // if this is a dictionary, look for a key ``name`` whose value + // is an int. If such key exist, return a pointer to its value, + // otherwise nullptr. + std::int64_t dict_find_int_value(char const* name + , std::int64_t default_val = 0) const; + lazy_entry const* dict_find_int(char const* name) const; + + // these functions require that ``this`` is a dictionary. + // (this->type() == dict_t). They look for an element with the + // specified name in the dictionary. ``dict_find_dict`` only + // finds dictionaries and ``dict_find_list`` only finds lists. + // if no key with the corresponding value of the right type is + // found, nullptr is returned. + lazy_entry const* dict_find_dict(char const* name) const; + lazy_entry const* dict_find_dict(std::string const& name) const; + lazy_entry const* dict_find_list(char const* name) const; + + // if this is a dictionary, return the key value pair at + // position ``i`` from the dictionary. + std::pair dict_at(int i) const; + + // requires that ``this`` is a dictionary. return the + // number of items in it + int dict_size() const + { + TORRENT_ASSERT(m_type == dict_t); + return m_size; + } + + // internal + void construct_list(char const* begin) + { + TORRENT_ASSERT(m_type == none_t); + m_type = list_t; + m_size = 0; + m_begin = begin; + } + + // internal + lazy_entry* list_append(); + + // requires that ``this`` is a list. return + // the item at index ``i``. + lazy_entry* list_at(int i) + { + TORRENT_ASSERT(m_type == list_t); + TORRENT_ASSERT(i < int(m_size)); + return &m_data.list[i+1]; + } + lazy_entry const* list_at(int i) const + { return const_cast(this)->list_at(i); } + + // these functions require ``this`` to have the type list. + // (this->type() == list_t). ``list_string_value_at`` returns + // the string at index ``i``. ``list_pstr_at`` + // returns a pascal_string of the string value at index ``i``. + // if the element at ``i`` is not a string, an empty string + // is returned. + std::string list_string_value_at(int i) const; + pascal_string list_pstr_at(int i) const; + + // this function require ``this`` to have the type list. + // (this->type() == list_t). returns the integer value at + // index ``i``. If the element at ``i`` is not an integer + // ``default_val`` is returned, which defaults to 0. + std::int64_t list_int_value_at(int i, std::int64_t default_val = 0) const; + + // if this is a list, return the number of items in it. + int list_size() const + { + TORRENT_ASSERT(m_type == list_t); + return int(m_size); + } + + // internal: end points one byte passed last byte in the source + // buffer backing the bencoded structure. + void set_end(char const* end) + { + TORRENT_ASSERT(end > m_begin); + TORRENT_ASSERT(end - m_begin < (std::numeric_limits::max)()); + m_len = std::uint32_t(end - m_begin); + } + + // internal + void clear(); + + // internal: releases ownership of any memory allocated + void release() + { + m_data.start = nullptr; + m_size = 0; + m_type = none_t; + } + + // internal + ~lazy_entry() + { clear(); } + + // returns pointers into the source buffer where + // this entry has its bencoded data + std::pair data_section() const; + + // swap values of ``this`` and ``e``. + void swap(lazy_entry& e) + { + using std::swap; + std::uint32_t tmp = e.m_type; + e.m_type = m_type; + m_type = tmp; + tmp = e.m_size; + e.m_size = m_size; + m_size = tmp; + swap(m_data.start, e.m_data.start); + swap(m_begin, e.m_begin); + swap(m_len, e.m_len); + } + + lazy_entry(lazy_entry&&); + lazy_entry& operator=(lazy_entry&&); + + private: + + int capacity() const; + + union data_t + { + // for the dict and list arrays, the first item is not part + // of the array. Instead its m_len member indicates the capacity + // of the allocation + lazy_dict_entry* dict; + lazy_entry* list; + char const* start; + } m_data; + + // used for dictionaries and lists to record the range + // in the original buffer they are based on + char const* m_begin = nullptr; + + // the number of bytes this entry extends in the + // bencoded buffer + std::uint32_t m_len = 0; + + // if list or dictionary, the number of items + std::uint32_t m_size:29; + // element type (dict, list, int, string) + std::uint32_t m_type:3; + + // non-copyable + lazy_entry(lazy_entry const&) = delete; + lazy_entry const& operator=(lazy_entry const&) = delete; + }; + + struct TORRENT_DEPRECATED lazy_dict_entry + { + char const* name = nullptr; + lazy_entry val; + }; + + // print the bencoded structure in a human-readable format to a string + // that's returned. + TORRENT_DEPRECATED_EXPORT std::string print_entry(lazy_entry const& e + , bool single_line = false, int indent = 0); + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + // defined in bdecode.cpp + TORRENT_DEPRECATED + TORRENT_EXTRA_EXPORT char const* parse_int(char const* start + , char const* end, char delimiter, std::int64_t& val + , bdecode_errors::error_code_enum& ec); + +} + +#endif // TORRENT_ABI_VERSION + +#endif diff --git a/include/libtorrent/link.hpp b/include/libtorrent/link.hpp new file mode 100644 index 0000000..e5e1d24 --- /dev/null +++ b/include/libtorrent/link.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LINK_HPP_INCLUDED +#define TORRENT_LINK_HPP_INCLUDED + +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + using torrent_list_index_t = aux::strong_typedef; + + struct link + { + link() : index(-1) {} + // this is either -1 (not in the list) + // or the index of where in the list this + // element is found + int index; + + bool in_list() const { return index >= 0; } + + void clear() { index = -1; } + + template + void unlink(aux::vector& list + , torrent_list_index_t const link_index) + { + if (index == -1) return; + TORRENT_ASSERT(index >= 0 && index < int(list.size())); + int const last = int(list.size()) - 1; + if (index < last) + { + list[last]->m_links[link_index].index = index; + list[index] = list[last]; + } + list.resize(last); + index = -1; + } + + template + void insert(aux::vector& list, T* self) + { + if (index >= 0) return; + TORRENT_ASSERT(index == -1); + list.push_back(self); + index = int(list.size()) - 1; + } + }; +} + +#endif diff --git a/include/libtorrent/linked_list.hpp b/include/libtorrent/linked_list.hpp new file mode 100644 index 0000000..f688652 --- /dev/null +++ b/include/libtorrent/linked_list.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LINKED_LIST_HPP +#define TORRENT_LINKED_LIST_HPP + +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + template + struct list_node + { + list_node() : prev(nullptr), next(nullptr) {} + T* prev; + T* next; + }; + + template + struct list_iterator + { + template + friend struct linked_list; + + T const* get() const { return m_current; } + T* get() { return m_current; } + void next() { m_current = m_current->next; } + void prev() { m_current = m_current->prev; } + + private: + explicit list_iterator(T* cur) + : m_current(cur) {} + // the current element + T* m_current; + }; + + template , T>::value>::type> + struct linked_list + { + linked_list(): m_first(nullptr), m_last(nullptr), m_size(0) {} + + list_iterator iterate() const + { return list_iterator(m_first); } + + void erase(T* e) + { +#if TORRENT_USE_ASSERTS + T* tmp = m_first; + bool found = false; + while (tmp) + { + if (tmp == e) + { + found = true; + break; + } + tmp = tmp->next; + } + TORRENT_ASSERT(found); +#endif + if (e == m_first) + { + TORRENT_ASSERT(e->prev == nullptr); + m_first = e->next; + } + if (e == m_last) + { + TORRENT_ASSERT(e->next == nullptr); + m_last = e->prev; + } + if (e->prev) e->prev->next = e->next; + if (e->next) e->next->prev = e->prev; + e->next = nullptr; + e->prev = nullptr; + TORRENT_ASSERT(m_size > 0); + --m_size; + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + } + void push_front(T* e) + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(e->prev== nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + e->prev = nullptr; + e->next = m_first; + if (m_first) m_first->prev = e; + else m_last = e; + m_first = e; + ++m_size; + } + void push_back(T* e) + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(e->prev== nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + e->prev = m_last; + e->next = nullptr; + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + ++m_size; + } + T* get_all() + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(m_first == nullptr || m_first->prev == nullptr); + T* e = m_first; + m_first = nullptr; + m_last = nullptr; + m_size = 0; + return e; + } + T* back() { return m_last; } + T* front() { return m_first; } + T const* back() const { return m_last; } + T const* front() const { return m_first; } + int size() const { return m_size; } + bool empty() const { return m_size == 0; } + private: + T* m_first; + T* m_last; + int m_size; + }; +} + +#endif // LINKED_LIST_HPP diff --git a/include/libtorrent/lsd.hpp b/include/libtorrent/lsd.hpp new file mode 100644 index 0000000..39c036c --- /dev/null +++ b/include/libtorrent/lsd.hpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_LSD_HPP +#define TORRENT_LSD_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/aux_/lsd.hpp" + +namespace libtorrent { + +struct lsd : std::enable_shared_from_this +{ + lsd(io_service& ios, aux::lsd_callback& cb + , address const& listen_address, address const& netmask); + ~lsd(); + + void start(error_code& ec); + + void announce(sha1_hash const& ih, int listen_port); + void close(); + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void announce_impl(sha1_hash const& ih, int listen_port, int retry_count); + void resend_announce(error_code const& e, sha1_hash const& ih + , int listen_port, int retry_count); + void on_announce(error_code const& ec); + + aux::lsd_callback& m_callback; + + address m_listen_address; + address m_netmask; + + udp::socket m_socket; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void debug_log(char const* fmt, ...) const TORRENT_FORMAT(2, 3); +#endif + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // this is a random (presumably unique) + // ID for this LSD node. It is used to + // ignore our own broadcast messages. + // There's no point in adding ourselves + // as a peer + int m_cookie; + + bool m_disabled = false; +}; + +} + +#endif diff --git a/include/libtorrent/magnet_uri.hpp b/include/libtorrent/magnet_uri.hpp new file mode 100644 index 0000000..32b6cd9 --- /dev/null +++ b/include/libtorrent/magnet_uri.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_MAGNET_URI_HPP_INCLUDED +#define TORRENT_MAGNET_URI_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + struct torrent_handle; + class session; + + // Generates a magnet URI from the specified torrent. If the torrent + // handle is invalid, an empty string is returned. + // + // For more information about magnet links, see magnet-links_. + // + TORRENT_EXPORT std::string make_magnet_uri(torrent_handle const& handle); + TORRENT_EXPORT std::string make_magnet_uri(torrent_info const& info); + +#if TORRENT_ABI_VERSION == 1 +#ifndef BOOST_NO_EXCEPTIONS + // deprecated in 0.14 + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , std::string const& save_path + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = nullptr); + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p); +#endif + + // deprecated in 0.16. Instead, pass in the magnet link as add_torrent_params::url + TORRENT_DEPRECATED_EXPORT + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec); +#endif // TORRENT_ABI_VERSION + + + // This function parses out information from the magnet link and populates the + // add_torrent_params object. The overload that does not take an + // ``error_code`` reference will throw a system_error on error + // The overload taking an ``add_torrent_params`` reference will fill in the + // fields specified in the magnet URI. + TORRENT_EXPORT add_torrent_params parse_magnet_uri(string_view uri, error_code& ec); + TORRENT_EXPORT add_torrent_params parse_magnet_uri(string_view uri); + TORRENT_EXPORT void parse_magnet_uri(string_view uri, add_torrent_params& p, error_code& ec); +} + +#endif diff --git a/include/libtorrent/natpmp.hpp b/include/libtorrent/natpmp.hpp new file mode 100644 index 0000000..63e804b --- /dev/null +++ b/include/libtorrent/natpmp.hpp @@ -0,0 +1,210 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/enum_net.hpp" // for ip_interface + +namespace libtorrent { + + namespace errors + { + // See RFC 6887 Section 7.4 + enum pcp_errors + { + pcp_success = 0, + pcp_unsupp_version, + pcp_not_authorized, + pcp_malformed_request, + pcp_unsupp_opcode, + pcp_unsupp_option, + pcp_malformed_option, + pcp_network_failure, + pcp_no_resources, + pcp_unsupp_protocol, + pcp_user_ex_quota, + pcp_cannot_provide_external, + pcp_address_mismatch, + pcp_excessive_remote_peers, + }; + + boost::system::error_code make_error_code(pcp_errors e); + } + + TORRENT_EXPORT boost::system::error_category& pcp_category(); +} + +namespace boost { namespace system { + template<> struct is_error_code_enum + { static const bool value = true; }; +} } + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT natpmp + : std::enable_shared_from_this + , single_threaded +{ + natpmp(io_service& ios, aux::portmap_callback& cb); + + void start(ip_interface const& ip); + + // maps the ports, if a port is set to 0 + // it will not be mapped + port_mapping_t add_mapping(portmap_protocol p, int external_port, tcp::endpoint local_ep); + void delete_mapping(port_mapping_t mapping_index); + bool get_mapping(port_mapping_t mapping_index, int& local_port, int& external_port + , portmap_protocol& protocol) const; + + void close(); + +private: + static error_code from_result_code(int version, int result); + + std::shared_ptr self() { return shared_from_this(); } + + void update_mapping(port_mapping_t i); + void send_map_request(port_mapping_t i); + void send_get_ip_address_request(); + void on_resend_request(port_mapping_t i, error_code const& e); + void resend_request(port_mapping_t); + void on_reply(error_code const& e + , std::size_t bytes_transferred); + void try_next_mapping(port_mapping_t i); + void update_expiration_timer(); + void mapping_expired(error_code const& e, port_mapping_t i); + void close_impl(); + + void disable(error_code const& ec); + + enum protocol_version + { + version_natpmp = 0, + version_pcp = 2, + }; + + static char const* version_to_string(protocol_version version); + + // See RFC 6887 Section 19.2 + enum pcp_opcode + { + opcode_announce = 0, + opcode_map, + opcode_peer, + }; + + struct mapping_t : aux::base_mapping + { + // random identifier, used by PCP + std::array nonce; + + // only valid if the router supports PCP + address external_address; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port = 0; + + // set to true when the first map request is sent + bool map_sent = false; + + // set to true while we're waiting for a response + bool outstanding_request = false; + }; + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void log(char const* fmt, ...) const TORRENT_FORMAT(2, 3); + void mapping_log(char const* op, mapping_t const& m) const; +#endif + + aux::portmap_callback& m_callback; + + protocol_version m_version = version_pcp; + + aux::vector m_mappings; + + // the endpoint to the nat router + udp::endpoint m_nat_endpoint; + + // this is the mapping that is currently + // being updated. It is -1 in case no + // mapping is being updated at the moment + port_mapping_t m_currently_mapping{-1}; + + // current retry count + int m_retry_count = 0; + + // used to receive responses in + // 1100 octets is the maximum size of a PCP packet + char m_response_buffer[1100]; + + // router external IP address + // this is only used if the router does not support PCP + // with PCP the external IP is stored with the mapping + address m_external_ip; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the udp socket used to communicate + // with the NAT router + udp::socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_send_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + // the mapping index that will expire next + port_mapping_t m_next_refresh{-1}; + + bool m_disabled = false; + + bool m_abort = false; +}; + +} + +#endif diff --git a/include/libtorrent/netlink.hpp b/include/libtorrent/netlink.hpp new file mode 100644 index 0000000..87e9d02 --- /dev/null +++ b/include/libtorrent/netlink.hpp @@ -0,0 +1,200 @@ +/* + +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_NETLINK_HPP +#define TORRENT_NETLINK_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_NETLINK + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + template + class basic_nl_endpoint + { + public: + using protocol_type = Protocol; + using data_type = boost::asio::detail::socket_addr_type; + + basic_nl_endpoint() noexcept : basic_nl_endpoint(protocol_type(), 0, 0) {} + + basic_nl_endpoint(protocol_type netlink_family, std::uint32_t group, std::uint32_t pid = 0) + : m_proto(netlink_family) + { + std::memset(&m_sockaddr, 0, sizeof(sockaddr_nl)); + m_sockaddr.nl_family = AF_NETLINK; + m_sockaddr.nl_groups = group; + m_sockaddr.nl_pid = pid; + } + + basic_nl_endpoint(basic_nl_endpoint const& other) = default; + basic_nl_endpoint(basic_nl_endpoint&& other) noexcept = default; + + basic_nl_endpoint& operator=(basic_nl_endpoint const& other) + { + m_sockaddr = other.m_sockaddr; + return *this; + } + + basic_nl_endpoint& operator=(basic_nl_endpoint&& other) noexcept + { + m_sockaddr = other.m_sockaddr; + return *this; + } + + protocol_type protocol() const + { + return m_proto; + } + + data_type* data() + { + return reinterpret_cast(&m_sockaddr); + } + + const data_type* data() const + { + return reinterpret_cast(&m_sockaddr); + } + + std::size_t size() const + { + return sizeof(m_sockaddr); + } + + std::size_t capacity() const + { + return sizeof(m_sockaddr); + } + + // commented the comparison operators for now, until the + // same operators are implemented for sockaddr_nl + /* + friend bool operator==(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.m_sockaddr == r.m_sockaddr; + } + + friend bool operator!=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l.m_sockaddr == r.m_sockaddr); + } + + friend bool operator<(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.m_sockaddr < r.m_sockaddr; + } + + friend bool operator>(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return r.m_sockaddr < l.m_sockaddr; + } + + friend bool operator<=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(r < l); + } + + friend bool operator>=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l < r); + } + */ + + private: + protocol_type m_proto; + sockaddr_nl m_sockaddr; + }; + + class netlink + { + public: + using endpoint = basic_nl_endpoint; + using socket = boost::asio::basic_raw_socket; + + netlink() : netlink(NETLINK_ROUTE) {} + + explicit netlink(int nl_family) + : m_nl_family(nl_family) + { + } + + int type() const + { + return SOCK_RAW; + } + + int protocol() const + { + return m_nl_family; + } + + int family() const + { + return AF_NETLINK; + } + + friend bool operator==(const netlink& l, const netlink& r) + { + return l.m_nl_family == r.m_nl_family; + } + + friend bool operator!=(const netlink& l, const netlink& r) + { + return l.m_nl_family != r.m_nl_family; + } + + private: + int m_nl_family; + }; + +} + +#endif // TORRENT_USE_NETLINK + +#endif diff --git a/include/libtorrent/operations.hpp b/include/libtorrent/operations.hpp new file mode 100644 index 0000000..10a1644 --- /dev/null +++ b/include/libtorrent/operations.hpp @@ -0,0 +1,252 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_OPERATIONS_HPP_INCLUDED +#define TORRENT_OPERATIONS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include + +namespace libtorrent { + + // these constants are used to identify the operation that failed, causing a + // peer to disconnect + enum class operation_t : std::uint8_t + { + // the error was unexpected and it is unknown which operation caused it + unknown, + + // this is used when the bittorrent logic + // determines to disconnect + bittorrent, + + // a call to iocontrol failed + iocontrol, + + // a call to ``getpeername()`` failed (querying the remote IP of a + // connection) + getpeername, + + // a call to getname failed (querying the local IP of a + // connection) + getname, + + // an attempt to allocate a receive buffer failed + alloc_recvbuf, + + // an attempt to allocate a send buffer failed + alloc_sndbuf, + + // writing to a file failed + file_write, + + // reading from a file failed + file_read, + + // a non-read and non-write file operation failed + file, + + // a socket write operation failed + sock_write, + + // a socket read operation failed + sock_read, + + // a call to open(), to create a socket socket failed + sock_open, + + // a call to bind() on a socket failed + sock_bind, + + // an attempt to query the number of bytes available to read from a socket + // failed + available, + + // a call related to bittorrent protocol encryption failed + encryption, + + // an attempt to connect a socket failed + connect, + + // establishing an SSL connection failed + ssl_handshake, + + // a connection failed to satisfy the bind interface setting + get_interface, + + // a call to listen() on a socket + sock_listen, + + // a call to the ioctl to bind a socket to a specific network device or + // adapter + sock_bind_to_device, + + // a call to accept() on a socket + sock_accept, + + // convert a string into a valid network address + parse_address, + + // enumeration network devices or adapters + enum_if, + + // invoking stat() on a file + file_stat, + + // copying a file + file_copy, + + // allocating storage for a file + file_fallocate, + + // creating a hard link + file_hard_link, + + // removing a file + file_remove, + + // renaming a file + file_rename, + + // opening a file + file_open, + + // creating a directory + mkdir, + + // check fast resume data against files on disk + check_resume, + + // an unknown exception + exception, + + // allocate space for a piece in the cache + alloc_cache_piece, + + // move a part-file + partfile_move, + + // read from a part file + partfile_read, + + // write to a part-file + partfile_write, + + // a hostname lookup + hostname_lookup, + + // create or read a symlink + symlink, + + // handshake with a peer or server + handshake, + + // set socket option + sock_option, + + // enumeration network routes + enum_route, + }; + + // maps an operation id (from peer_error_alert and peer_disconnected_alert) + // to its name. See operation_t for the constants + TORRENT_EXPORT char const* operation_name(operation_t op); + +#if TORRENT_ABI_VERSION == 1 + enum deprecated_operation_t : std::uint8_t + { + // the error was unexpected and it is unknown which operation caused it + op_unknown TORRENT_DEPRECATED_ENUM, + + // this is used when the bittorrent logic + // determines to disconnect + op_bittorrent TORRENT_DEPRECATED_ENUM , + + // a call to ``iocontrol()`` failed + op_iocontrol TORRENT_DEPRECATED_ENUM, + + // a call to ``getpeername()`` failed (querying the remote IP of a + // connection) + op_getpeername TORRENT_DEPRECATED_ENUM, + + // a call to ``getsockname()`` failed (querying the local IP of a + // connection) + op_getname TORRENT_DEPRECATED_ENUM, + + // an attempt to allocate a receive buffer failed + op_alloc_recvbuf TORRENT_DEPRECATED_ENUM, + + // an attempt to allocate a send buffer failed + op_alloc_sndbuf TORRENT_DEPRECATED_ENUM, + + // writing to a file failed + op_file_write TORRENT_DEPRECATED_ENUM, + + // reading from a file failed + op_file_read TORRENT_DEPRECATED_ENUM, + + // a non-read and non-write file operation failed + op_file TORRENT_DEPRECATED_ENUM, + + // a socket write operation failed + op_sock_write TORRENT_DEPRECATED_ENUM, + + // a socket read operation failed + op_sock_read TORRENT_DEPRECATED_ENUM, + + // a call to open(), to create a socket socket failed + op_sock_open TORRENT_DEPRECATED_ENUM, + + // a call to bind() on a socket failed + op_sock_bind TORRENT_DEPRECATED_ENUM, + + // an attempt to query the number of bytes available to read from a socket + // failed + op_available TORRENT_DEPRECATED_ENUM, + + // a call related to bittorrent protocol encryption failed + op_encryption TORRENT_DEPRECATED_ENUM, + + // an attempt to connect a socket failed + op_connect TORRENT_DEPRECATED_ENUM, + + // establishing an SSL connection failed + op_ssl_handshake TORRENT_DEPRECATED_ENUM, + + // a connection failed to satisfy the bind interface setting + op_get_interface TORRENT_DEPRECATED_ENUM, + }; +#endif + +} + +#endif // TORRENT_OPERATIONS_HPP_INCLUDED diff --git a/include/libtorrent/optional.hpp b/include/libtorrent/optional.hpp new file mode 100644 index 0000000..81f193d --- /dev/null +++ b/include/libtorrent/optional.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2017-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#ifndef TORRENT_OPTIONAL_HPP_INCLUDED +#define TORRENT_OPTIONAL_HPP_INCLUDED + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + template + T value_or(boost::optional opt, U def) + { + return opt ? *opt : T(def); + } +} + +#endif + diff --git a/include/libtorrent/packet_buffer.hpp b/include/libtorrent/packet_buffer.hpp new file mode 100644 index 0000000..8549f0c --- /dev/null +++ b/include/libtorrent/packet_buffer.hpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg, Daniel Wallin. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PACKET_BUFFER_HPP_INCLUDED +#define TORRENT_PACKET_BUFFER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/unique_ptr.hpp" +#include "libtorrent/packet_pool.hpp" // for packet_ptr/packet_deleter +#include +#include +#include // for unique_ptr + +namespace libtorrent { + + struct packet; + + // this is a circular buffer that automatically resizes + // itself as elements are inserted. Elements are indexed + // by integers and are assumed to be sequential. Unless the + // old elements are removed when new elements are inserted, + // the buffer will be resized. + + // m_capacity is the number of elements in m_array + // and must be an even 2^x. + // m_first is the lowest index that has an element + // it also determines which indices the other slots + // refers to. Since it's a circular buffer, it wraps + // around. For example + + // m_first = 9 + // | refers to index 14 + // | | + // V V + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | | | | | | | | | | | | | | | | mask = (m_capacity-1) + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // ^ + // | + // refers to index 15 + + // whenever the element at the cursor is removed, the + // cursor is bumped to the next occupied element + + class TORRENT_EXTRA_EXPORT packet_buffer + { + public: + using index_type = std::uint32_t; + + packet_ptr insert(index_type idx, packet_ptr value); + + int size() const { return m_size; } + + bool empty() const { return m_size == 0; } + + std::uint32_t capacity() const + { return m_capacity; } + + packet* at(index_type idx) const; + + packet_ptr remove(index_type idx); + + void reserve(std::uint32_t size); + + index_type cursor() const { return m_first; } + + index_type span() const { return (m_last - m_first) & 0xffff; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + private: + aux::unique_ptr m_storage; + std::uint32_t m_capacity = 0; + + // this is the total number of elements that are occupied + // in the array + int m_size = 0; + + // This defines the first index that is part of the m_storage. + // last is one passed the last used slot + index_type m_first{0}; + index_type m_last{0}; + }; + +} + +#endif // TORRENT_PACKET_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/packet_pool.hpp b/include/libtorrent/packet_pool.hpp new file mode 100644 index 0000000..92086c1 --- /dev/null +++ b/include/libtorrent/packet_pool.hpp @@ -0,0 +1,221 @@ +/* + +Copyright (c) 2005-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PACKET_POOL_HPP +#define TORRENT_PACKET_POOL_HPP + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" // for single_threaded + +#include + +namespace libtorrent { + + // internal: some MTU and protocol header sizes constants + constexpr int TORRENT_IPV4_HEADER = 20; + constexpr int TORRENT_IPV6_HEADER = 40; + constexpr int TORRENT_UDP_HEADER = 8; + constexpr int TORRENT_UTP_HEADER = 20; + constexpr int TORRENT_SOCKS5_HEADER = 6; // plus the size of the destination address + constexpr int TORRENT_ETHERNET_MTU = 1500; + constexpr int TORRENT_TEREDO_MTU = 1280; + constexpr int TORRENT_INET_MIN_MTU = 576; + + // used for out-of-order incoming packets + // as well as sent packets that are waiting to be ACKed + struct packet + { + // the last time this packet was sent + time_point send_time; + + // the number of bytes actually allocated in 'buf' + std::uint16_t allocated; + + // the size of the buffer 'buf' points to + std::uint16_t size; + + // this is the offset to the payload inside the buffer + // this is also used as a cursor to describe where the + // next payload that hasn't been consumed yet starts + std::uint16_t header_size; + + // the number of times this packet has been sent + std::uint8_t num_transmissions:6; + + // true if we need to send this packet again. All + // outstanding packets are marked as needing to be + // resent on timeouts + bool need_resend:1; + + // this is set to true for packets that were + // sent with the DF bit set (Don't Fragment) + bool mtu_probe:1; + +#if TORRENT_USE_ASSERTS + int num_fast_resend; +#endif + + // the actual packet buffer + std::uint8_t buf[1]; + }; + + struct packet_deleter + { + // deleter for std::unique_ptr + void operator()(packet* p) const + { + TORRENT_ASSERT(p != nullptr); + p->~packet(); + std::free(p); + } + }; + + using packet_ptr = std::unique_ptr; + + // internal + inline packet_ptr create_packet(int const size) + { + packet* p = static_cast(std::malloc(sizeof(packet) + aux::numeric_cast(size))); + if (p == nullptr) aux::throw_ex(); + p = new (p) packet(); + p->allocated = aux::numeric_cast(size); + return packet_ptr(p); + } + + struct TORRENT_EXTRA_EXPORT packet_slab + { + int const allocate_size; + + explicit packet_slab(int const alloc_size, std::size_t const limit = 10) + : allocate_size(alloc_size) + , m_limit(limit) + { + m_storage.reserve(m_limit); + } + + packet_slab(const packet_slab&) = delete; + packet_slab(packet_slab&&) = default; + + void try_push_back(packet_ptr &p) + { + if (m_storage.size() < m_limit) + m_storage.push_back(std::move(p)); + } + + packet_ptr alloc() + { + if (m_storage.empty()) return create_packet(allocate_size); + auto ret = std::move(m_storage.back()); + m_storage.pop_back(); + return ret; + } + + void decay() + { + if (m_storage.empty()) return; + m_storage.erase(m_storage.end() - 1); + } + + private: + const std::size_t m_limit; + std::vector m_storage; + }; + + // single thread packet allocation packet pool + // can handle common cases of packet size by 3 pools + struct TORRENT_EXTRA_EXPORT packet_pool : private single_threaded + { + // there's a bug in GCC where allocating these in + // member initializer expressions won't propagate exceptions. + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80683 + packet_pool() + : m_syn_slab(TORRENT_UTP_HEADER) + , m_mtu_floor_slab(mtu_floor_size) + , m_mtu_ceiling_slab(mtu_ceiling_size) + {} + packet_pool(packet_pool&&) = default; + + packet_ptr acquire(int const allocate) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(allocate >= 0); + TORRENT_ASSERT(allocate <= (std::numeric_limits::max)()); + + return alloc(allocate); + } + + void release(packet_ptr p) + { + TORRENT_ASSERT(is_single_thread()); + + if (!p) return; + + int const allocated = p->allocated; + + if (allocated == m_syn_slab.allocate_size) { m_syn_slab.try_push_back(p); } + else if (allocated == m_mtu_floor_slab.allocate_size) { m_mtu_floor_slab.try_push_back(p); } + else if (allocated == m_mtu_ceiling_slab.allocate_size) { m_mtu_ceiling_slab.try_push_back(p); } + } + + // periodically free up some of the cached packets + void decay() + { + TORRENT_ASSERT(is_single_thread()); + + m_syn_slab.decay(); + m_mtu_floor_slab.decay(); + m_mtu_ceiling_slab.decay(); + } + + private: + packet_ptr alloc(int const allocate) + { + if (allocate <= m_syn_slab.allocate_size) { return m_syn_slab.alloc(); } + else if (allocate <= m_mtu_floor_slab.allocate_size) { return m_mtu_floor_slab.alloc(); } + else if (allocate <= m_mtu_ceiling_slab.allocate_size) { return m_mtu_ceiling_slab.alloc(); } + return create_packet(allocate); + } + static constexpr int mtu_floor_size = TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + static constexpr int mtu_ceiling_size = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + packet_slab m_syn_slab; + packet_slab m_mtu_floor_slab; + packet_slab m_mtu_ceiling_slab; + }; +} + +#endif // TORRENT_PACKET_POOL_HPP diff --git a/include/libtorrent/parse_url.hpp b/include/libtorrent/parse_url.hpp new file mode 100644 index 0000000..6a48fa1 --- /dev/null +++ b/include/libtorrent/parse_url.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PARSE_URL_HPP_INCLUDED +#define TORRENT_PARSE_URL_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + + // returns protocol, auth, hostname, port, path + TORRENT_EXTRA_EXPORT std::tuple + parse_url_components(std::string url, error_code& ec); + + // split a URL in its base and path parts + TORRENT_EXTRA_EXPORT std::tuple + split_url(std::string url, error_code& ec); +} + +#endif diff --git a/include/libtorrent/part_file.hpp b/include/libtorrent/part_file.hpp new file mode 100644 index 0000000..f701122 --- /dev/null +++ b/include/libtorrent/part_file.hpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PART_FILE_HPP_INCLUDE +#define TORRENT_PART_FILE_HPP_INCLUDE + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + using slot_index_t = aux::strong_typedef; + + struct TORRENT_EXTRA_EXPORT part_file + { + // create a part file at ``path``, that can hold ``num_pieces`` pieces. + // each piece being ``piece_size`` number of bytes + part_file(std::string const& path, std::string const& name, int num_pieces, int piece_size); + ~part_file(); + + int writev(span bufs, piece_index_t piece, int offset, error_code& ec); + int readv(span bufs, piece_index_t piece, int offset, error_code& ec); + + // free the slot the given piece is stored in. We no longer need to store this + // piece in the part file + void free_piece(piece_index_t piece); + + void move_partfile(std::string const& path, error_code& ec); + + // the function is called for every block of data belonging to the + // specified range that's in the part_file. The first parameter is the + // offset within the range + void export_file(std::function)> f + , std::int64_t offset, std::int64_t size, error_code& ec); + + // flush the metadata + void flush_metadata(error_code& ec); + + private: + + file open_file(open_mode_t mode, error_code& ec); + void flush_metadata_impl(error_code& ec); + + std::int64_t slot_offset(slot_index_t const slot) const + { + return static_cast(slot) * static_cast(m_piece_size) + + m_header_size; + } + + std::string m_path; + std::string const m_name; + + // allocate a slot and return the slot index + slot_index_t allocate_slot(piece_index_t piece); + + // this mutex must be held while accessing the data + // structure. Not while reading or writing from the file though! + // it's important to support multithreading + std::mutex m_mutex; + + // this is a list of unallocated slots in the part file + // within the m_num_allocated range + std::vector m_free_slots; + + // this is the number of slots allocated + slot_index_t m_num_allocated{0}; + + // the max number of pieces in the torrent this part file is + // backing + int const m_max_pieces; + + // number of bytes each piece contains + int const m_piece_size; + + // this is the size of the part_file header, it is added + // to offsets when calculating the offset to read and write + // payload data from + int const m_header_size; + + // if this is true, the metadata in memory has changed since + // we last saved or read it from disk. It means that we + // need to flush the metadata before closing the file + bool m_dirty_metadata = false; + + // maps a piece index to the part-file slot it is stored in + std::unordered_map m_piece_map; + }; +} + +#endif diff --git a/include/libtorrent/pe_crypto.hpp b/include/libtorrent/pe_crypto.hpp new file mode 100644 index 0000000..3bc1500 --- /dev/null +++ b/include/libtorrent/pe_crypto.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2007-2018, Un Shyam & Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PE_CRYPTO_HPP_INCLUDED +#define TORRENT_PE_CRYPTO_HPP_INCLUDED + +#if !defined TORRENT_DISABLE_ENCRYPTION + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/receive_buffer.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include + +namespace libtorrent { + + namespace mp = boost::multiprecision; + + using key_t = mp::number>; + + TORRENT_EXTRA_EXPORT std::array export_key(key_t const& k); + + // RC4 state from libtomcrypt + struct rc4 { + int x; + int y; + aux::array buf; + }; + + // TODO: 3 dh_key_exchange should probably move into its own file + class TORRENT_EXTRA_EXPORT dh_key_exchange + { + public: + dh_key_exchange(); + bool good() const { return true; } + + // Get local public key + key_t const& get_local_key() const { return m_dh_local_key; } + + // read remote_pubkey, generate and store shared secret in + // m_dh_shared_secret. + void compute_secret(std::uint8_t const* remote_pubkey); + void compute_secret(key_t const& remote_pubkey); + + key_t const& get_secret() const { return m_dh_shared_secret; } + + sha1_hash const& get_hash_xor_mask() const { return m_xor_mask; } + + private: + + key_t m_dh_local_key; + key_t m_dh_local_secret; + key_t m_dh_shared_secret; + sha1_hash m_xor_mask; + }; + + struct TORRENT_EXTRA_EXPORT encryption_handler + { + std::tuple>> + encrypt(span> iovec); + + int decrypt(crypto_receive_buffer& recv_buffer + , std::size_t& bytes_transferred); + + bool switch_send_crypto(std::shared_ptr crypto + , int pending_encryption); + + void switch_recv_crypto(std::shared_ptr crypto + , crypto_receive_buffer& recv_buffer); + + bool is_send_plaintext() const + { + return m_send_barriers.empty() || m_send_barriers.back().next != INT_MAX; + } + + bool is_recv_plaintext() const + { + return m_dec_handler.get() == nullptr; + } + + private: + struct barrier + { + barrier(std::shared_ptr plugin, int n) + : enc_handler(plugin), next(n) {} + std::shared_ptr enc_handler; + // number of bytes to next barrier + int next; + }; + std::list m_send_barriers; + std::shared_ptr m_dec_handler; + }; + + struct TORRENT_EXTRA_EXPORT rc4_handler : crypto_plugin + { + public: + rc4_handler(); + + // Input keys must be 20 bytes + void set_incoming_key(span key) override; + void set_outgoing_key(span key) override; + + std::tuple>> + encrypt(span> buf) override; + + std::tuple decrypt(span> buf) override; + + private: + rc4 m_rc4_incoming; + rc4 m_rc4_outgoing; + + // determines whether or not encryption and decryption is enabled + bool m_encrypt; + bool m_decrypt; + }; + +} // namespace libtorrent + +#endif // TORRENT_DISABLE_ENCRYPTION + +#endif // TORRENT_PE_CRYPTO_HPP_INCLUDED diff --git a/include/libtorrent/peer.hpp b/include/libtorrent/peer.hpp new file mode 100644 index 0000000..cd76130 --- /dev/null +++ b/include/libtorrent/peer.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_HPP_INCLUDED +#define TORRENT_PEER_HPP_INCLUDED + +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT peer_entry + { + std::string hostname; + peer_id pid; + std::uint16_t port; + + bool operator==(const peer_entry& p) const + { return pid == p.pid; } + + bool operator<(const peer_entry& p) const + { return pid < p.pid; } + }; + + struct ipv4_peer_entry + { + address_v4::bytes_type ip; + std::uint16_t port; + }; + + struct ipv6_peer_entry + { + address_v6::bytes_type ip; + std::uint16_t port; + }; + +} + +#endif // TORRENT_PEER_HPP_INCLUDED diff --git a/include/libtorrent/peer_class.hpp b/include/libtorrent/peer_class.hpp new file mode 100644 index 0000000..778a1f6 --- /dev/null +++ b/include/libtorrent/peer_class.hpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/deque.hpp" + +#include +#include +#include +#include + +namespace libtorrent { + + using peer_class_t = aux::strong_typedef; + + // holds settings for a peer class. Used in set_peer_class() and + // get_peer_class() calls. + struct TORRENT_EXPORT peer_class_info + { + // ``ignore_unchoke_slots`` determines whether peers should always + // unchoke a peer, regardless of the choking algorithm, or if it should + // honor the unchoke slot limits. It's used for local peers by default. + // If *any* of the peer classes a peer belongs to has this set to true, + // that peer will be unchoked at all times. + bool ignore_unchoke_slots; + + // adjusts the connection limit (global and per torrent) that applies to + // this peer class. By default, local peers are allowed to exceed the + // normal connection limit for instance. This is specified as a percent + // factor. 100 makes the peer class apply normally to the limit. 200 + // means as long as there are fewer connections than twice the limit, we + // accept this peer. This factor applies both to the global connection + // limit and the per-torrent limit. Note that if not used carefully one + // peer class can potentially completely starve out all other over time. + int connection_limit_factor; + + // not used by libtorrent. It's intended as a potentially user-facing + // identifier of this peer class. + std::string label; + + // transfer rates limits for the whole peer class. They are specified in + // bytes per second and apply to the sum of all peers that are members of + // this class. + int upload_limit; + int download_limit; + + // relative priorities used by the bandwidth allocator in the rate + // limiter. If no rate limits are in use, the priority is not used + // either. Priorities start at 1 (0 is not a valid priority) and may not + // exceed 255. + int upload_priority; + int download_priority; + }; + + struct TORRENT_EXTRA_EXPORT peer_class + { + friend struct peer_class_pool; + + explicit peer_class(std::string l) + : ignore_unchoke_slots(false) + , connection_limit_factor(100) + , label(std::move(l)) + , in_use(true) + , references(1) + { + priority[0] = 1; + priority[1] = 1; + } + + void clear() + { + in_use = false; + label.clear(); + } + + void set_info(peer_class_info const* pci); + void get_info(peer_class_info* pci) const; + + void set_upload_limit(int limit); + void set_download_limit(int limit); + + // the bandwidth channels, upload and download + // keeps track of the current quotas + bandwidth_channel channel[2]; + + bool ignore_unchoke_slots; + int connection_limit_factor; + + // priority for bandwidth allocation + // in rate limiter. One for upload and one + // for download + int priority[2]; + + // the name of this peer class + std::string label; + + private: + // this is set to false when this slot is not in use for a peer_class + bool in_use; + + int references; + }; + + struct TORRENT_EXTRA_EXPORT peer_class_pool + { + peer_class_t new_peer_class(std::string label); + void decref(peer_class_t c); + void incref(peer_class_t c); + peer_class* at(peer_class_t c); + peer_class const* at(peer_class_t c) const; + + private: + + // state for peer classes (a peer can belong to multiple classes) + // this can control + aux::deque m_peer_classes; + + // indices in m_peer_classes that are no longer used + std::vector m_free_list; + }; +} + +#endif // TORRENT_PEER_CLASS_HPP_INCLUDED diff --git a/include/libtorrent/peer_class_set.hpp b/include/libtorrent/peer_class_set.hpp new file mode 100644 index 0000000..1d0fa82 --- /dev/null +++ b/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_SET_HPP_INCLUDED +#define TORRENT_PEER_CLASS_SET_HPP_INCLUDED + +#include "libtorrent/peer_class.hpp" +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { + + // this represents an object that can have many peer classes applied + // to it. Most notably, peer connections and torrents derive from this. + struct TORRENT_EXTRA_EXPORT peer_class_set + { + peer_class_set() : m_size(0) {} + void add_class(peer_class_pool& pool, peer_class_t c); + bool has_class(peer_class_t c) const; + void remove_class(peer_class_pool& pool, peer_class_t c); + int num_classes() const { return m_size; } + peer_class_t class_at(int i) const + { + TORRENT_ASSERT(i >= 0 && i < int(m_size)); + return m_class[i]; + } + + private: + + // the number of elements used in the m_class array + std::int8_t m_size; + + // if this object belongs to any peer-class, this vector contains all + // class IDs. Each ID refers to a an entry in m_ses.m_peer_classes which + // holds the metadata about the class. Classes affect bandwidth limits + // among other things + aux::array m_class; + }; +} + +#endif // TORRENT_PEER_CLASS_SET_HPP_INCLUDED diff --git a/include/libtorrent/peer_class_type_filter.hpp b/include/libtorrent/peer_class_type_filter.hpp new file mode 100644 index 0000000..3ba679a --- /dev/null +++ b/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED +#define TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED + +#include +#include + +#include "aux_/export.hpp" +#include "peer_class.hpp" // for peer_class_t + +namespace libtorrent { + + // ``peer_class_type_filter`` is a simple container for rules for adding and subtracting + // peer-classes from peers. It is applied *after* the peer class filter is applied (which + // is based on the peer's IP address). + struct TORRENT_EXPORT peer_class_type_filter + { + // hidden + peer_class_type_filter() + { + m_peer_class_type_mask.fill(0xffffffff); + m_peer_class_type.fill(0); + } + + enum socket_type_t : std::uint8_t + { + // these match the socket types from socket_type.hpp + // shifted one down + tcp_socket = 0, + utp_socket, + ssl_tcp_socket, + ssl_utp_socket, + i2p_socket, + num_socket_types + }; + + // ``add()`` and ``remove()`` adds and removes a peer class to be added + // to new peers based on socket type. + void add(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type[st] |= 1 << static_cast(peer_class); + } + void remove(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type[st] &= ~(1 << static_cast(peer_class)); + } + + // ``disallow()`` and ``allow()`` adds and removes a peer class to be + // removed from new peers based on socket type. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks representing + // peer classes in the ``peer_class_type_filter`` are 32 bits. + void disallow(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type_mask[st] &= ~(1 << static_cast(peer_class)); + } + void allow(socket_type_t const st, peer_class_t const peer_class) + { + TORRENT_ASSERT(peer_class < peer_class_t{32}); + if (peer_class > peer_class_t{31}) return; + + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return; + m_peer_class_type_mask[st] |= 1 << static_cast(peer_class); + } + + // takes a bitmask of peer classes and returns a new bitmask of + // peer classes after the rules have been applied, based on the socket type argument + // (``st``). + std::uint32_t apply(socket_type_t const st, std::uint32_t peer_class_mask) + { + TORRENT_ASSERT(st < num_socket_types); + if (st >= num_socket_types) return peer_class_mask; + + // filter peer classes based on type + peer_class_mask &= m_peer_class_type_mask[st]; + // add peer classes based on type + peer_class_mask |= m_peer_class_type[st]; + return peer_class_mask; + } + + friend bool operator==(peer_class_type_filter const& lhs + , peer_class_type_filter const& rhs) + { + return lhs.m_peer_class_type_mask == rhs.m_peer_class_type_mask + && lhs.m_peer_class_type == rhs.m_peer_class_type; + } + + private: + // maps socket type to a bitmask that's used to filter out + // (mask) bits from the m_peer_class_filter. + std::array m_peer_class_type_mask; + // peer class bitfield added based on socket type + std::array m_peer_class_type; + }; + +} + +#endif diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp new file mode 100644 index 0000000..3e46ac4 --- /dev/null +++ b/include/libtorrent/peer_connection.hpp @@ -0,0 +1,1226 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/chained_buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/socket.hpp" // for tcp::endpoint +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/receive_buffer.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/piece_picker.hpp" // for picker_options_t +#include "libtorrent/units.hpp" + +#include +#include +#include +#include +#include // for std::forward +#include // for make_tuple +#include +#include + +namespace libtorrent { + + class torrent; + struct torrent_peer; + struct disk_interface; + +#ifndef TORRENT_DISABLE_EXTENSIONS + struct peer_plugin; +#endif + +namespace aux { + + struct socket_type; + struct session_interface; + +} + + struct pending_block + { + pending_block(piece_block const& b) // NOLINT + : block(b), send_buffer_offset(not_in_buffer), not_wanted(false) + , timed_out(false), busy(false) + {} + + piece_block block; + + static constexpr std::uint32_t not_in_buffer = 0x1fffffff; + + // the number of bytes into the send buffer this request is. Every time + // some portion of the send buffer is transmitted, this offset is + // decremented by the number of bytes sent. once this drops below 0, the + // request_time field is set to the current time. + // if the request has not been written to the send buffer, this field + // remains not_in_buffer. + std::uint32_t send_buffer_offset:29; + + // if any of these are set to true, this block + // is not allocated + // in the piece picker anymore, and open for + // other peers to pick. This may be caused by + // it either timing out or being received + // unexpectedly from the peer + std::uint32_t not_wanted:1; + std::uint32_t timed_out:1; + + // the busy flag is set if the block was + // requested from another peer when this + // request was queued. We only allow a single + // busy request at a time in each peer's queue + std::uint32_t busy:1; + + bool operator==(pending_block const& b) const + { + return b.block == block + && b.not_wanted == not_wanted + && b.timed_out == timed_out; + } + }; + + // argument pack passed to peer_connection constructor + struct peer_connection_args + { + aux::session_interface* ses; + aux::session_settings const* sett; + counters* stats_counters; + disk_interface* disk_thread; + io_service* ios; + std::weak_ptr tor; + std::shared_ptr s; + tcp::endpoint endp; + torrent_peer* peerinfo; + peer_id our_peer_id; + }; + + struct TORRENT_EXTRA_EXPORT peer_connection_hot_members + { + // if tor is set, this is an outgoing connection + peer_connection_hot_members( + std::weak_ptr t + , aux::session_interface& ses + , aux::session_settings const& sett) + : m_torrent(std::move(t)) + , m_ses(ses) + , m_settings(sett) + , m_disconnecting(false) + , m_connecting(!m_torrent.expired()) + , m_endgame_mode(false) + , m_snubbed(false) + , m_interesting(false) + , m_choked(true) + , m_ignore_stats(false) + {} + + // explicitly disallow assignment, to silence msvc warning + peer_connection_hot_members& operator=(peer_connection_hot_members const&) = delete; + + protected: + + // the pieces the other end have + typed_bitfield m_have_piece; + + // this is the torrent this connection is + // associated with. If the connection is an + // incoming connection, this is set to zero + // until the info_hash is received. Then it's + // set to the torrent it belongs to. + + // TODO: make this a raw pointer (to save size in + // the first cache line) and make the constructor + // take a raw pointer. torrent objects should always + // outlive their peers + std::weak_ptr m_torrent; + + public: + + // a back reference to the session + // the peer belongs to. + aux::session_interface& m_ses; + + // settings that apply to this peer + aux::session_settings const& m_settings; + + protected: + + // this is true if this connection has been added + // to the list of connections that will be closed. + bool m_disconnecting:1; + + // this is true until this socket has become + // writable for the first time (i.e. the + // connection completed). While connecting + // the timeout will not be triggered. This is + // because windows XP SP2 may delay connection + // attempts, which means that the connection + // may not even have been attempted when the + // time out is reached. + bool m_connecting:1; + + // this is set to true if the last time we tried to + // pick a piece to download, we could only find + // blocks that were already requested from other + // peers. In this case, we should not try to pick + // another piece until the last one we requested is done + bool m_endgame_mode:1; + + // set to true when a piece request times out. The + // result is that the desired pending queue size + // is set to 1 + bool m_snubbed:1; + + // the peer has pieces we are interested in + bool m_interesting:1; + + // we have choked the upload to the peer + bool m_choked:1; + + // when this is set, the transfer stats for this connection + // is not included in the torrent or session stats + bool m_ignore_stats:1; + }; + + enum class connection_type : std::uint8_t + { + bittorrent, + url_seed, + http_seed + }; + + using request_flags_t = flags::bitfield_flag; + + class TORRENT_EXTRA_EXPORT peer_connection + : public peer_connection_hot_members + , public bandwidth_socket + , public peer_class_set + , public disk_observer + , public peer_connection_interface + , public std::enable_shared_from_this + , public aux::error_handler_interface + { + friend class invariant_access; + friend class torrent; + friend struct cork; + public: + + void on_exception(std::exception const& e) override; + void on_error(error_code const& ec) override; + + virtual connection_type type() const = 0; + + enum channels + { + upload_channel, + download_channel, + num_channels + }; + + explicit peer_connection(peer_connection_args const& pack); + + // this function is called after it has been constructed and properly + // reference counted. It is safe to call self() in this function + // and schedule events with references to itself (that is not safe to + // do in the constructor). + virtual void start(); + + ~peer_connection() override; + + void set_peer_info(torrent_peer* pi) override + { + TORRENT_ASSERT(m_peer_info == nullptr || pi == nullptr ); + TORRENT_ASSERT(pi != nullptr || m_disconnect_started); + m_peer_info = pi; + } + + torrent_peer* peer_info_struct() const override + { return m_peer_info; } + + // this is called when the peer object is created, in case + // it was let in by the connections limit slack. This means + // the peer needs to, as soon as the handshake is done, either + // disconnect itself or another peer. + void peer_exceeds_limit() + { m_exceeded_limit = true; } + + // this is called if this peer causes another peer + // to be disconnected, in which case it has fulfilled + // its requirement. + void peer_disconnected_other() + { m_exceeded_limit = false; } + + void send_allowed_set(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr); + peer_plugin const* find_plugin(string_view type); +#endif + + // this function is called once the torrent associated + // with this peer connection has retrieved the meta- + // data. If the torrent was spawned with metadata + // this is called from the constructor. + void init(); + + // this is called when the metadata is retrieved + // and the files has been checked + virtual void on_metadata() {} + + void on_metadata_impl(); + + void picker_options(picker_options_t o) { m_picker_options = o; } + + int prefer_contiguous_blocks() const + { + if (on_parole()) return 1; + return m_prefer_contiguous_blocks; + } + + bool on_parole() const; + + picker_options_t picker_options() const; + + void prefer_contiguous_blocks(int num) + { m_prefer_contiguous_blocks = num; } + + bool request_large_blocks() const + { return m_request_large_blocks; } + + void request_large_blocks(bool b) + { m_request_large_blocks = b; } + + void set_endgame(bool b); + bool endgame() const { return m_endgame_mode; } + + bool no_download() const { return m_no_download; } + void no_download(bool b) { m_no_download = b; } + + bool ignore_stats() const { return m_ignore_stats; } + void ignore_stats(bool b) { m_ignore_stats = b; } + + std::uint32_t peer_rank() const; + + void fast_reconnect(bool r); + bool fast_reconnect() const override { return m_fast_reconnect; } + + // this is called when we receive a new piece + // (and it has passed the hash check) + void received_piece(piece_index_t index); + + // this adds an announcement in the announcement queue + // it will let the peer know that we have the given piece + void announce_piece(piece_index_t index); + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // this will tell the peer to announce the given piece + // and only allow it to request that piece + void superseed_piece(piece_index_t replace_piece, piece_index_t new_piece); + bool super_seeded_piece(piece_index_t index) const + { + return m_superseed_piece[0] == index + || m_superseed_piece[1] == index; + } +#endif + + // tells if this connection has data it want to send + // and has enough upload bandwidth quota left to send it. + bool can_write() const; + bool can_read(); + + bool is_seed() const; + int num_have_pieces() const { return m_num_pieces; } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void set_share_mode(bool m); + bool share_mode() const { return m_share_mode; } +#endif + + void set_upload_only(bool u); + bool upload_only() const { return m_upload_only; } + + void set_holepunch_mode() override; + + // will send a keep-alive message to the peer + void keep_alive(); + + peer_id const& pid() const override { return m_peer_id; } + void set_pid(peer_id const& peer_id) { m_peer_id = peer_id; } + bool has_piece(piece_index_t i) const; + + std::vector const& download_queue() const; + std::vector const& request_queue() const; + std::vector const& upload_queue() const; + + void clear_request_queue(); + void clear_download_queue(); + + // estimate of how long it will take until we have + // received all piece requests that we have sent + // if extra_bytes is specified, it will include those + // bytes as if they've been requested + time_duration download_queue_time(int extra_bytes = 0) const; + + bool is_interesting() const { return m_interesting; } + bool is_choked() const override { return m_choked; } + + bool is_peer_interested() const { return m_peer_interested; } + bool has_peer_choked() const { return m_peer_choked; } + + void choke_this_peer(); + void maybe_unchoke_this_peer(); + + void update_interest(); + + void get_peer_info(peer_info& p) const override; + + // returns the torrent this connection is a part of + // may be zero if the connection is an incoming connection + // and it hasn't received enough information to determine + // which torrent it should be associated with + std::weak_ptr associated_torrent() const + { return m_torrent; } + + stat const& statistics() const override { return m_statistics; } + void add_stat(std::int64_t downloaded, std::int64_t uploaded) override; + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); + + // is called once every second by the main loop + void second_tick(int tick_interval_ms); + + std::shared_ptr get_socket() const { return m_socket; } + tcp::endpoint const& remote() const override { return m_remote; } + tcp::endpoint local_endpoint() const override { return m_local; } + + typed_bitfield const& get_bitfield() const; + std::vector const& allowed_fast(); + std::vector const& suggested_pieces() const { return m_suggested_pieces; } + + time_point connected_time() const { return m_connect; } + time_point last_received() const { return m_last_receive; } + + // this will cause this peer_connection to be disconnected. + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t = peer_connection_interface::normal) override; + + // called when a connect attempt fails (not when an + // established connection fails) + void connect_failed(error_code const& e); + bool is_disconnecting() const override { return m_disconnecting; } + + // this is called when the connection attempt has succeeded + // and the peer_connection is supposed to set m_connecting + // to false, and stop monitor writability + void on_connection_complete(error_code const& e); + + // returns true if this connection is still waiting to + // finish the connection attempt + bool is_connecting() const { return m_connecting; } + + // trust management. + virtual void received_valid_data(piece_index_t index); + // returns false if the peer should not be + // disconnected + virtual bool received_invalid_data(piece_index_t index, bool single_peer); + + // a connection is local if it was initiated by us. + // if it was an incoming connection, it is remote + bool is_outgoing() const final { return m_outgoing; } + + bool received_listen_port() const { return m_received_listen_port; } + void received_listen_port() + { m_received_listen_port = true; } + + bool on_local_network() const; + bool ignore_unchoke_slots() const; + + bool failed() const override { return m_failed; } + + int desired_queue_size() const + { + // this peer is in end-game mode we only want + // one outstanding request + return (m_endgame_mode || m_snubbed) ? 1 : m_desired_queue_size; + } + + // compares this connection against the given connection + // for which one is more eligible for an unchoke. + // returns true if this is more eligible + + int download_payload_rate() const { return m_statistics.download_payload_rate(); } + + // resets the byte counters that are used to measure + // the number of bytes transferred within unchoke cycles + void reset_choke_counters(); + + // if this peer connection is useless (neither party is + // interested in the other), disconnect it + // returns true if the connection was disconnected + bool disconnect_if_redundant(); + +#if TORRENT_ABI_VERSION == 1 + void increase_est_reciprocation_rate(); + void decrease_est_reciprocation_rate(); + int est_reciprocation_rate() const { return m_est_reciprocation_rate; } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(peer_log_alert::direction_t direction) const final; + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const noexcept final TORRENT_FORMAT(4,5); + void peer_log(peer_log_alert::direction_t direction + , char const* event) const noexcept; +#endif + + // the message handlers are called + // each time a recv() returns some new + // data, the last time it will be called + // is when the entire packet has been + // received, then it will no longer + // be called. i.e. most handlers need + // to check how much of the packet they + // have received before any processing + void incoming_keepalive(); + void incoming_choke(); + void incoming_unchoke(); + void incoming_interested(); + void incoming_not_interested(); + void incoming_have(piece_index_t piece_index); + void incoming_dont_have(piece_index_t piece_index); + void incoming_bitfield(typed_bitfield const& bits); + void incoming_request(peer_request const& r); + void incoming_piece(peer_request const& p, char const* data); + void incoming_piece_fragment(int bytes); + void start_receive_piece(peer_request const& r); + void incoming_cancel(peer_request const& r); + + bool can_disconnect(error_code const& ec) const; + void incoming_dht_port(int listen_port); + + void incoming_reject_request(peer_request const& r); + void incoming_have_all(); + void incoming_have_none(); + void incoming_allowed_fast(piece_index_t index); + void incoming_suggest(piece_index_t index); + + void set_has_metadata(bool m) { m_has_metadata = m; } + bool has_metadata() const { return m_has_metadata; } + + // the following functions appends messages + // to the send buffer + bool send_choke(); + bool send_unchoke(); + void send_interested(); + void send_not_interested(); + void send_suggest(piece_index_t piece); + void send_upload_only(bool enabled); + + void snub_peer(); + // reject any request in the request + // queue from this piece + void reject_piece(piece_index_t index); + + bool can_request_time_critical() const; + + // returns true if the specified block was actually made time-critical. + // if the block was already time-critical, it returns false. + bool make_time_critical(piece_block const& block); + + static constexpr request_flags_t time_critical = 0_bit; + static constexpr request_flags_t busy = 1_bit; + + // adds a block to the request queue + // returns true if successful, false otherwise + bool add_request(piece_block const& b, request_flags_t flags = {}); + + // clears the request queue and sends cancels for all messages + // in the download queue + void cancel_all_requests(); + + // removes a block from the request queue or download queue + // sends a cancel message if appropriate + // refills the request queue, and possibly ignoring pieces requested + // by peers in the ignore list (to avoid recursion) + // if force is true, the blocks is also freed from the piece + // picker, allowing another peer to request it immediately + void cancel_request(piece_block const& b, bool force = false); + void send_block_requests(); + + void assign_bandwidth(int channel, int amount) override; + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + // is true until we can be sure that the other end + // speaks our protocol (be it bittorrent or http). + virtual bool in_handshake() const = 0; + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, implementors + // must return an object with the piece_index + // value invalid (the default constructor). + virtual piece_block_progress downloading_piece_progress() const; + + void send_buffer(span buf); + void setup_send(); + + template + void append_send_buffer(Holder buffer, int size) + { + TORRENT_ASSERT(is_single_thread()); + m_send_buffer.append_buffer(std::move(buffer), size); + } + + int outstanding_bytes() const { return m_outstanding_bytes; } + + int send_buffer_size() const + { return m_send_buffer.size(); } + + int send_buffer_capacity() const + { return m_send_buffer.capacity(); } + + void max_out_request_queue(int s); + int max_out_request_queue() const; + +#if TORRENT_USE_ASSERTS + bool piece_failed; +#endif + + std::time_t last_seen_complete() const { return m_last_seen_complete; } + void set_last_seen_complete(int ago) { m_last_seen_complete = ::time(nullptr) - ago; } + + std::int64_t uploaded_in_last_round() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_round; } + + std::int64_t downloaded_in_last_round() const + { return m_statistics.total_payload_download() - m_downloaded_at_last_round; } + + std::int64_t uploaded_since_unchoked() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + + // the time we last unchoked this peer + time_point time_of_last_unchoke() const + { return m_last_unchoke; } + + // called when the disk write buffer is drained again, and we can + // start downloading payload again + void on_disk() override; + + int num_reading_bytes() const { return m_reading_bytes; } + + void setup_receive(); + + std::shared_ptr self() + { + TORRENT_ASSERT(!m_destructed); + TORRENT_ASSERT(m_in_use == 1337); + TORRENT_ASSERT(!m_in_constructor); + return shared_from_this(); + } + + counters& stats_counters() const { return m_counters; } + + int get_priority(int channel) const; + + protected: + + virtual void get_specific_peer_info(peer_info& p) const = 0; + + virtual void write_choke() = 0; + virtual void write_unchoke() = 0; + virtual void write_interested() = 0; + virtual void write_not_interested() = 0; + virtual void write_request(peer_request const& r) = 0; + virtual void write_cancel(peer_request const& r) = 0; + virtual void write_have(piece_index_t index) = 0; + virtual void write_dont_have(piece_index_t index) = 0; + virtual void write_keepalive() = 0; + virtual void write_piece(peer_request const& r, disk_buffer_holder buffer) = 0; + virtual void write_suggest(piece_index_t piece) = 0; + virtual void write_bitfield() = 0; + + virtual void write_reject_request(peer_request const& r) = 0; + virtual void write_allow_fast(piece_index_t piece) = 0; + virtual void write_upload_only(bool enabled) = 0; + + virtual void on_connected() = 0; + virtual void on_tick() {} + + // implemented by concrete connection classes + virtual void on_receive(error_code const& error + , std::size_t bytes_transferred) = 0; + virtual void on_sent(error_code const& error + , std::size_t bytes_transferred) = 0; + + void send_piece_suggestions(int num); + + virtual + std::tuple>> + hit_send_barrier(span> /* iovec */) + { + return std::make_tuple(INT_MAX + , span>()); + } + + void attach_to_torrent(sha1_hash const& ih); + + bool verify_piece(peer_request const& p) const; + + void update_desired_queue_size(); + + void set_send_barrier(int bytes) + { + TORRENT_ASSERT(bytes == INT_MAX || bytes <= send_buffer_size()); + m_send_barrier = bytes; + } + + int get_send_barrier() const { return m_send_barrier; } + + virtual int timeout() const; + + io_service& get_io_service() { return m_ios; } + + private: + + // callbacks for data being sent or received + void on_send_data(error_code const& error + , std::size_t bytes_transferred); + void on_receive_data(error_code const& error + , std::size_t bytes_transferred); + + void account_received_bytes(int bytes_transferred); + + // explicitly disallow assignment, to silence msvc warning + peer_connection& operator=(peer_connection const&); + + void do_update_interest(); + void fill_send_buffer(); + void on_disk_read_complete(disk_buffer_holder disk_block, disk_job_flags_t flags + , storage_error const& error, peer_request const& r, time_point issue_time); + void on_disk_write_complete(storage_error const& error + , peer_request const &r, std::shared_ptr t); + void on_seed_mode_hashed(piece_index_t piece + , sha1_hash const& piece_hash, storage_error const& error); + int request_timeout() const; + void check_graceful_pause(); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + std::shared_ptr m_socket; + + // the queue of blocks we have requested + // from this peer + aux::vector m_download_queue; + + // the queue of requests we have got + // from this peer that haven't been issued + // to the disk thread yet + aux::vector m_requests; + + // this peer's peer info struct. This may + // be 0, in case the connection is incoming + // and hasn't been added to a torrent yet. + torrent_peer* m_peer_info; + + // stats counters + counters& m_counters; + + // the number of pieces this peer + // has. Must be the same as + // std::count(m_have_piece.begin(), + // m_have_piece.end(), true) + int m_num_pieces; + + + public: + // upload and download channel state + // enum from peer_info::bw_state + bandwidth_state_flags_t m_channel_state[2]; + + protected: + receive_buffer m_recv_buffer; + + // number of bytes this peer can send and receive + int m_quota[2]; + + // the blocks we have reserved in the piece + // picker and will request from this peer. + std::vector m_request_queue; + + // this is the limit on the number of outstanding requests + // we have to this peer. This is initialized to the settings + // in the settings_pack. But it may be lowered + // if the peer is known to require a smaller limit (like BitComet). + // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. + int m_max_out_request_queue; + + // this is the peer we're actually talking to + // it may not necessarily be the peer we're + // connected to, in case we use a proxy + tcp::endpoint m_remote; + + public: + chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + // io service + io_service& m_ios; + + protected: +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_extensions; +#endif + private: + + // the average time between incoming pieces. Or, if there is no + // outstanding request, the time since the piece was requested. It + // is essentially an estimate of the time it will take to completely + // receive a payload message after it has been requested. + sliding_average m_request_time; + + // keep the io_service running as long as we + // have peer connections + io_service::work m_work; + + // the time when we last got a part of a + // piece packet from this peer + time_point m_last_piece = aux::time_now(); + + // the time we sent a request to + // this peer the last time + time_point m_last_request = aux::time_now(); + // the time we received the last + // piece request from the peer + time_point m_last_incoming_request = min_time(); + + // the time when we unchoked this peer + time_point m_last_unchoke = aux::time_now(); + + // if we're unchoked by this peer, this + // was the time + time_point m_last_unchoked = aux::time_now(); + + // the time we last choked this peer. min_time() in + // case we never unchoked it + time_point m_last_choke = min_time(); + + // timeouts + time_point m_last_receive = aux::time_now(); + time_point m_last_sent = aux::time_now(); + + // the last time we filled our send buffer with payload + // this is used for timeouts + time_point m_last_sent_payload = aux::time_now(); + + // the time when the first entry in the request queue was requested. Used + // for request timeout. it doesn't necessarily represent the time when a + // specific request was made. Since requests can be handled out-of-order, + // it represents whichever request the other end decided to respond to. + // Once we get that response, we set it to the current time. + // for more information, see the blog post at: + // http://blog.libtorrent.org/2011/11/block-request-time-outs/ + time_point m_requested = aux::time_now(); + + // the time when async_connect was called + // or when the incoming connection was established + time_point m_connect = aux::time_now(); + + // the time when this peer sent us a not_interested message + // the last time. + time_point m_became_uninterested = aux::time_now(); + + // the time when we sent a not_interested message to + // this peer the last time. + time_point m_became_uninteresting = aux::time_now(); + + // the total payload download bytes + // at the last unchoke round. This is used to + // measure the number of bytes transferred during + // an unchoke cycle, to unchoke peers the more bytes + // they sent us + std::int64_t m_downloaded_at_last_round = 0; + std::int64_t m_uploaded_at_last_round = 0; + + // this is the number of bytes we had uploaded the + // last time this peer was unchoked. This does not + // reset each unchoke interval/round. This is used to + // track upload across rounds, for the full duration of + // the peer being unchoked. Specifically, it's used + // for the round-robin unchoke algorithm. + std::int64_t m_uploaded_at_last_unchoke = 0; + + // the number of payload bytes downloaded last second tick + std::int32_t m_downloaded_last_second = 0; + + // the number of payload bytes uploaded last second tick + std::int32_t m_uploaded_last_second = 0; + + // the number of bytes that the other + // end has to send us in order to respond + // to all outstanding piece requests we + // have sent to it + int m_outstanding_bytes = 0; + + aux::handler_storage m_read_handler_storage; + aux::handler_storage m_write_handler_storage; + + // these are pieces we have recently sent suggests for to this peer. + // it just serves as a queue to remember what we've sent, to avoid + // re-sending suggests for the same piece + // i.e. outgoing suggest pieces + aux::vector m_suggest_pieces; + + // the pieces we will send to the peer + // if requested (regardless of choke state) + std::vector m_accept_fast; + + // a sent-piece counter for the allowed fast set + // to avoid exploitation. Each slot is a counter + // for one of the pieces from the allowed-fast set + aux::vector m_accept_fast_piece_cnt; + + // the pieces the peer will send us if + // requested (regardless of choke state) + std::vector m_allowed_fast; + + // pieces that has been suggested to be downloaded from this peer + // i.e. incoming suggestions + // TODO: 2 this should really be a circular buffer + aux::vector m_suggested_pieces; + + // the time when this peer last saw a complete copy + // of this torrent + time_t m_last_seen_complete = 0; + + // the block we're currently receiving. Or + // (-1, -1) if we're not receiving one + piece_block m_receiving_block = piece_block::invalid; + + // the local endpoint for this peer, i.e. our address + // and our port. If this is set for outgoing connections + // before the connection completes, it means we want to + // force the connection to be bound to the specified interface. + // if it ends up being bound to a different local IP, the connection + // is closed. + tcp::endpoint m_local; + + // remote peer's id + peer_id m_peer_id; + + protected: + + template + void wrap(Fun f, Args&&... a); + + // statistics about upload and download speeds + // and total amount of uploads and downloads for + // this peer + // TODO: factor this out into its own class with a virtual interface + // torrent and session should implement this interface + stat m_statistics; + + // the number of outstanding bytes expected + // to be received by extensions + int m_extension_outstanding_bytes = 0; + + // the number of time critical requests + // queued up in the m_request_queue that + // soon will be committed to the download + // queue. This is included in download_queue_time() + // so that it can be used while adding more + // requests and take the previous requests + // into account without submitting it all + // immediately + int m_queued_time_critical = 0; + + // the number of bytes we are currently reading + // from disk, that will be added to the send + // buffer as soon as they complete + int m_reading_bytes = 0; + + // options used for the piece picker. These flags will + // be augmented with flags controlled by other settings + // like sequential download etc. These are here to + // let plugins control flags that should always be set + picker_options_t m_picker_options{}; + + // the number of invalid piece-requests + // we have got from this peer. If the request + // queue gets empty, and there have been + // invalid requests, we can assume the + // peer is waiting for those pieces. + // we can then clear its download queue + // by sending choke, unchoke. + int m_num_invalid_requests = 0; + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // if [0] is -1, super-seeding is not active. If it is >= 0 + // this is the piece that is available to this peer. Only + // these two pieces can be downloaded from us by this peer. + // This will remain the current piece for this peer until + // another peer sends us a have message for this piece + std::array m_superseed_piece = {{piece_index_t(-1), piece_index_t(-1)}}; +#endif + + // the number of bytes send to the disk-io + // thread that hasn't yet been completely written. + int m_outstanding_writing_bytes = 0; + + // max transfer rates seen on this peer + int m_download_rate_peak = 0; + int m_upload_rate_peak = 0; + +#if TORRENT_ABI_VERSION == 1 + // when using the BitTyrant choker, this is our + // estimated reciprocation rate. i.e. the rate + // we need to send to this peer for it to unchoke + // us + int m_est_reciprocation_rate; +#endif + + // stop sending data after this many bytes, INT_MAX = inf + int m_send_barrier = INT_MAX; + + // the number of request we should queue up + // at the remote end. + // TODO: 2 rename this target queue size + std::uint16_t m_desired_queue_size = 4; + + // if set to non-zero, this peer will always prefer + // to request entire n pieces, rather than blocks. + // where n is the value of this variable. + // if it is 0, the download rate limit setting + // will be used to determine if whole pieces + // are preferred. + int m_prefer_contiguous_blocks = 0; + + // this is the number of times this peer has had + // a request rejected because of a disk I/O failure. + // once this reaches a certain threshold, the + // peer is disconnected in order to avoid infinite + // loops of consistent failures + std::uint8_t m_disk_read_failures = 0; + + // this is used in seed mode whenever we trigger a hash check + // for a piece, before we read it. It's used to throttle + // the hash checks to just a few per peer at a time. + std::uint8_t m_outstanding_piece_verification:3; + + // is true if it was we that connected to the peer + // and false if we got an incoming connection + // could be considered: true = local, false = remote + bool m_outgoing:1; + + // is true if we learn the incoming connections listening + // during the extended handshake + bool m_received_listen_port:1; + + // if this is true, the disconnection + // timestamp is not updated when the connection + // is closed. This means the time until we can + // reconnect to this peer is shorter, and likely + // immediate. + bool m_fast_reconnect:1; + + // this is set to true if the connection timed + // out or closed the connection. In that + // case we will not try to reconnect to + // this peer + bool m_failed:1; + + // this is set to true if the connection attempt + // succeeded. i.e. the TCP 3-way handshake + bool m_connected:1; + + // if this is true, the blocks picked by the piece + // picker will be merged before passed to the + // request function. i.e. subsequent blocks are + // merged into larger blocks. This is used by + // the http-downloader, to request whole pieces + // at a time. + bool m_request_large_blocks:1; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // set to true if this peer is in share mode + bool m_share_mode:1; +#endif + + // set to true when this peer is only uploading + bool m_upload_only:1; + + // this is set to true once the bitfield is received + bool m_bitfield_received:1; + + // if this is set to true, the client will not + // pick any pieces from this peer + bool m_no_download:1; + + // 1 bit + + // set to true while we're trying to holepunch + bool m_holepunch_mode:1; + + // the other side has told us that it won't send anymore + // data to us for a while + bool m_peer_choked:1; + + // this is set to true when a have_all + // message is received. This information + // is used to fill the bitmask in init() + bool m_have_all:1; + + // other side says that it's interested in downloading + // from us. + bool m_peer_interested:1; + + // set to true when we should recalculate interest + // for this peer. Since this is a fairly expensive + // operation, it's delayed until the second_tick is + // fired, so that multiple events that wants to recalc + // interest are coalesced into only triggering it once + // the actual computation is done in do_update_interest(). + bool m_need_interest_update:1; + + // set to true if this peer has metadata, and false + // otherwise. + bool m_has_metadata:1; + + // this is set to true if this peer was accepted exceeding + // the connection limit. It means it has to disconnect + // itself, or some other peer, as soon as it's completed + // the handshake. We need to wait for the handshake in + // order to know which torrent it belongs to, to know which + // other peers to compare it to. + bool m_exceeded_limit:1; + + // this is slow-start at the bittorrent layer. It affects how we increase + // desired queue size (i.e. the number of outstanding requests we keep). + // While the underlying transport protocol is in slow-start, the number of + // outstanding requests need to increase at the same pace to keep up. + bool m_slow_start:1; + +#if TORRENT_USE_ASSERTS + public: + bool m_in_constructor = true; + bool m_disconnect_started = false; + bool m_initialized = false; + int m_in_use = 1337; + int m_received_in_piece = 0; + bool m_destructed = false; + // this is true while there is an outstanding + // async write job on the socket + bool m_socket_is_writing = false; + bool is_single_thread() const; +#endif + }; + + struct cork + { + explicit cork(peer_connection& p): m_pc(p) + { + if (m_pc.m_channel_state[peer_connection::upload_channel] & peer_info::bw_network) + return; + + // pretend that there's an outstanding send operation already, to + // prevent future calls to setup_send() from actually causing an + // async_send() to be issued. + m_pc.m_channel_state[peer_connection::upload_channel] |= peer_info::bw_network; + m_need_uncork = true; + } + cork(cork const&) = delete; + cork& operator=(cork const&) = delete; + + ~cork() + { + if (!m_need_uncork) return; + try { + m_pc.m_channel_state[peer_connection::upload_channel] &= ~peer_info::bw_network; + m_pc.setup_send(); + } + catch (std::bad_alloc const&) { + m_pc.disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } + catch (boost::system::system_error const& err) { + m_pc.disconnect(err.code(), operation_t::sock_write); + } + catch (...) { + m_pc.disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } + } + private: + peer_connection& m_pc; + bool m_need_uncork = false; + }; + +} + +#endif // TORRENT_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/peer_connection_handle.hpp b/include/libtorrent/peer_connection_handle.hpp new file mode 100644 index 0000000..904618e --- /dev/null +++ b/include/libtorrent/peer_connection_handle.hpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED +#define TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/operations.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/peer_connection.hpp" // for connection_type +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +class bt_peer_connection; + +// the peer_connection_handle class provides a handle to the internal peer +// connection object, to be used by plugins. This is a low level interface that +// may not be stable across libtorrent versions +struct TORRENT_EXPORT peer_connection_handle +{ + explicit peer_connection_handle(std::weak_ptr impl) + : m_connection(std::move(impl)) + {} + + connection_type type() const; + + void add_extension(std::shared_ptr); + peer_plugin const* find_plugin(string_view type) const; + + bool is_seed() const; + + bool upload_only() const; + + peer_id const& pid() const; + bool has_piece(piece_index_t i) const; + + bool is_interesting() const; + bool is_choked() const; + + bool is_peer_interested() const; + bool has_peer_choked() const; + + void choke_this_peer(); + void maybe_unchoke_this_peer(); + + void get_peer_info(peer_info& p) const; + + torrent_handle associated_torrent() const; + + tcp::endpoint const& remote() const; + tcp::endpoint local_endpoint() const; + + void disconnect(error_code const& ec, operation_t op + , disconnect_severity_t = peer_connection_interface::normal); + bool is_disconnecting() const; + bool is_connecting() const; + bool is_outgoing() const; + + bool on_local_network() const; + bool ignore_unchoke_slots() const; + + bool failed() const; + + bool should_log(peer_log_alert::direction_t direction) const; + void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5); + + bool can_disconnect(error_code const& ec) const; + + bool has_metadata() const; + + bool in_handshake() const; + + void send_buffer(char const* begin, int size); + + std::time_t last_seen_complete() const; + time_point time_of_last_unchoke() const; + + bool operator==(peer_connection_handle const& o) const + { return !lt(m_connection, o.m_connection) && !lt(o.m_connection, m_connection); } + bool operator!=(peer_connection_handle const& o) const + { return lt(m_connection, o.m_connection) || lt(o.m_connection, m_connection); } + bool operator<(peer_connection_handle const& o) const + { return lt(m_connection, o.m_connection); } + + std::shared_ptr native_handle() const + { + return m_connection.lock(); + } + +private: + std::weak_ptr m_connection; + + // copied from boost::weak_ptr + bool lt(std::weak_ptr const& a + , std::weak_ptr const& b) const + { + return a.owner_before(b); + } +}; + +// The bt_peer_connection_handle provides a handle to the internal bittorrent +// peer connection object to plugins. It's low level and may not be a stable API +// across libtorrent versions. +struct TORRENT_EXPORT bt_peer_connection_handle : peer_connection_handle +{ + explicit bt_peer_connection_handle(peer_connection_handle pc) + : peer_connection_handle(std::move(pc)) + {} + + bool packet_finished() const; + bool support_extensions() const; + + bool supports_encryption() const; + + void switch_send_crypto(std::shared_ptr crypto); + void switch_recv_crypto(std::shared_ptr crypto); + + std::shared_ptr native_handle() const; +}; + +} // namespace libtorrent + +#endif // TORRENT_PEER_CONNECTION_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/peer_connection_interface.hpp b/include/libtorrent/peer_connection_interface.hpp new file mode 100644 index 0000000..cada62f --- /dev/null +++ b/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,83 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_CONNECTION_INTERFACE_HPP +#define TORRENT_PEER_CONNECTION_INTERFACE_HPP + +#include "libtorrent/fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct torrent_peer; + class stat; + + using disconnect_severity_t = aux::strong_typedef; + + // TODO: make this interface smaller! + struct TORRENT_EXTRA_EXPORT peer_connection_interface + { + static constexpr disconnect_severity_t normal{0}; + static constexpr disconnect_severity_t failure{1}; + static constexpr disconnect_severity_t peer_error{2}; + + virtual tcp::endpoint const& remote() const = 0; + virtual tcp::endpoint local_endpoint() const = 0; + virtual void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t = peer_connection_interface::normal) = 0; + virtual peer_id const& pid() const = 0; + virtual peer_id our_pid() const = 0; + virtual void set_holepunch_mode() = 0; + virtual torrent_peer* peer_info_struct() const = 0; + virtual void set_peer_info(torrent_peer* pi) = 0; + virtual bool is_outgoing() const = 0; + virtual void add_stat(std::int64_t downloaded, std::int64_t uploaded) = 0; + virtual bool fast_reconnect() const = 0; + virtual bool is_choked() const = 0; + virtual bool failed() const = 0; + virtual stat const& statistics() const = 0; + virtual void get_peer_info(peer_info& p) const = 0; +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log(peer_log_alert::direction_t direction) const = 0; + virtual void peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt = "", ...) const noexcept TORRENT_FORMAT(4,5) = 0; +#endif + protected: + ~peer_connection_interface() {} + }; +} + +#endif diff --git a/include/libtorrent/peer_id.hpp b/include/libtorrent/peer_id.hpp new file mode 100644 index 0000000..1227b27 --- /dev/null +++ b/include/libtorrent/peer_id.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ID_HPP_INCLUDED +#define TORRENT_PEER_ID_HPP_INCLUDED + +#include "libtorrent/sha1_hash.hpp" + +namespace libtorrent { + + using peer_id = sha1_hash; +} + +#endif // TORRENT_PEER_ID_HPP_INCLUDED diff --git a/include/libtorrent/peer_info.hpp b/include/libtorrent/peer_info.hpp new file mode 100644 index 0000000..f8082a9 --- /dev/null +++ b/include/libtorrent/peer_info.hpp @@ -0,0 +1,463 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_INFO_HPP_INCLUDED +#define TORRENT_PEER_INFO_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // flags for the peer_info::flags field. Indicates various states + // the peer may be in. These flags are not mutually exclusive, but + // not every combination of them makes sense either. + using peer_flags_t = flags::bitfield_flag; + + // the flags indicating which sources a peer can + // have come from. A peer may have been seen from + // multiple sources + using peer_source_flags_t = flags::bitfield_flag; + + // flags indicating what is blocking network transfers in up- and down + // direction + using bandwidth_state_flags_t = flags::bitfield_flag; + +TORRENT_VERSION_NAMESPACE_2 + + // holds information and statistics about one peer + // that libtorrent is connected to + struct TORRENT_EXPORT peer_info + { + // hidden + peer_info(); + ~peer_info(); + peer_info(peer_info const&); + peer_info(peer_info&&); + peer_info& operator=(peer_info const&); + + // a string describing the software at the other end of the connection. + // In some cases this information is not available, then it will contain + // a string that may give away something about which software is running + // in the other end. In the case of a web seed, the server type and + // version will be a part of this string. + std::string client; + + // a bitfield, with one bit per piece in the torrent. Each bit tells you + // if the peer has that piece (if it's set to 1) or if the peer miss that + // piece (set to 0). + typed_bitfield pieces; + + // the total number of bytes downloaded from and uploaded to this peer. + // These numbers do not include the protocol chatter, but only the + // payload data. + std::int64_t total_download; + std::int64_t total_upload; + + // the time since we last sent a request to this peer and since any + // transfer occurred with this peer + time_duration last_request; + time_duration last_active; + + // the time until all blocks in the request queue will be downloaded + time_duration download_queue_time; + +#if TORRENT_ABI_VERSION == 1 + using peer_flags_t = libtorrent::peer_flags_t; + using peer_source_flags = libtorrent::peer_source_flags_t; +#endif + + // **we** are interested in pieces from this peer. + static constexpr peer_flags_t interesting = 0_bit; + + // **we** have choked this peer. + static constexpr peer_flags_t choked = 1_bit; + + // the peer is interested in **us** + static constexpr peer_flags_t remote_interested = 2_bit; + + // the peer has choked **us**. + static constexpr peer_flags_t remote_choked = 3_bit; + + // means that this peer supports the + // `extension protocol`__. + // + // __ extension_protocol.html + static constexpr peer_flags_t supports_extensions = 4_bit; + + // The connection was initiated by us, the peer has a + // listen port open, and that port is the same as in the + // address of this peer. If this flag is not set, this + // peer connection was opened by this peer connecting to + // us. + static constexpr peer_flags_t local_connection = 5_bit; + + // The connection is opened, and waiting for the + // handshake. Until the handshake is done, the peer + // cannot be identified. + static constexpr peer_flags_t handshake = 6_bit; + + // The connection is in a half-open state (i.e. it is + // being connected). + static constexpr peer_flags_t connecting = 7_bit; + +#if TORRENT_ABI_VERSION == 1 + // The connection is currently queued for a connection + // attempt. This may happen if there is a limit set on + // the number of half-open TCP connections. + TORRENT_DEPRECATED_MEMBER static constexpr peer_flags_t queued = 8_bit; +#endif + + // The peer has participated in a piece that failed the + // hash check, and is now "on parole", which means we're + // only requesting whole pieces from this peer until + // it either fails that piece or proves that it doesn't + // send bad data. + static constexpr peer_flags_t on_parole = 9_bit; + + // This peer is a seed (it has all the pieces). + static constexpr peer_flags_t seed = 10_bit; + + // This peer is subject to an optimistic unchoke. It has + // been unchoked for a while to see if it might unchoke + // us in return an earn an upload/unchoke slot. If it + // doesn't within some period of time, it will be choked + // and another peer will be optimistically unchoked. + static constexpr peer_flags_t optimistic_unchoke = 11_bit; + + // This peer has recently failed to send a block within + // the request timeout from when the request was sent. + // We're currently picking one block at a time from this + // peer. + static constexpr peer_flags_t snubbed = 12_bit; + + // This peer has either explicitly (with an extension) + // or implicitly (by becoming a seed) told us that it + // will not downloading anything more, regardless of + // which pieces we have. + static constexpr peer_flags_t upload_only = 13_bit; + + // This means the last time this peer picket a piece, + // it could not pick as many as it wanted because there + // were not enough free ones. i.e. all pieces this peer + // has were already requested from other peers. + static constexpr peer_flags_t endgame_mode = 14_bit; + + // This flag is set if the peer was in holepunch mode + // when the connection succeeded. This typically only + // happens if both peers are behind a NAT and the peers + // connect via the NAT holepunch mechanism. + static constexpr peer_flags_t holepunched = 15_bit; + + // indicates that this socket is running on top of the + // I2P transport. + static constexpr peer_flags_t i2p_socket = 16_bit; + + // indicates that this socket is a uTP socket + static constexpr peer_flags_t utp_socket = 17_bit; + + // indicates that this socket is running on top of an SSL + // (TLS) channel + static constexpr peer_flags_t ssl_socket = 18_bit; + + // this connection is obfuscated with RC4 + static constexpr peer_flags_t rc4_encrypted = 19_bit; + + // the handshake of this connection was obfuscated + // with a Diffie-Hellman exchange + static constexpr peer_flags_t plaintext_encrypted = 20_bit; + + // tells you in which state the peer is in. It is set to + // any combination of the peer_flags_t flags above. + peer_flags_t flags; + + // The peer was received from the tracker. + static constexpr peer_source_flags_t tracker = 0_bit; + + // The peer was received from the kademlia DHT. + static constexpr peer_source_flags_t dht = 1_bit; + + // The peer was received from the peer exchange + // extension. + static constexpr peer_source_flags_t pex = 2_bit; + + // The peer was received from the local service + // discovery (The peer is on the local network). + static constexpr peer_source_flags_t lsd = 3_bit; + + // The peer was added from the fast resume data. + static constexpr peer_source_flags_t resume_data = 4_bit; + + // we received an incoming connection from this peer + static constexpr peer_source_flags_t incoming = 5_bit; + + // a combination of flags describing from which sources this peer + // was received. A combination of the peer_source_flags_t above. + peer_source_flags_t source; + + // the current upload and download speed we have to and from this peer + // (including any protocol messages). updated about once per second + int up_speed; + int down_speed; + + // The transfer rates of payload data only updated about once per second + int payload_up_speed; + int payload_down_speed; + + // the peer's id as used in the bit torrent protocol. This id can be used + // to extract 'fingerprints' from the peer. Sometimes it can tell you + // which client the peer is using. See identify_client()_ + peer_id pid; + + // the number of bytes we have requested from this peer, but not yet + // received. + int queue_bytes; + + // the number of seconds until the current front piece request will time + // out. This timeout can be adjusted through + // ``settings_pack::request_timeout``. + // -1 means that there is not outstanding request. + int request_timeout; + + // the number of bytes allocated + // and used for the peer's send buffer, respectively. + int send_buffer_size; + int used_send_buffer; + + // the number of bytes + // allocated and used as receive buffer, respectively. + int receive_buffer_size; + int used_receive_buffer; + int receive_buffer_watermark; + + // the number of pieces this peer has participated in sending us that + // turned out to fail the hash check. + int num_hashfails; + + // this is the number of requests we have sent to this peer that we + // haven't got a response for yet + int download_queue_length; + + // the number of block requests that have timed out, and are still in the + // download queue + int timed_out_requests; + + // the number of busy requests in the download queue. A busy request is a + // request for a block we've also requested from a different peer + int busy_requests; + + // the number of requests messages that are currently in the send buffer + // waiting to be sent. + int requests_in_buffer; + + // the number of requests that is tried to be maintained (this is + // typically a function of download speed) + int target_dl_queue_length; + + // the number of piece-requests we have received from this peer + // that we haven't answered with a piece yet. + int upload_queue_length; + + // the number of times this peer has "failed". i.e. failed to connect or + // disconnected us. The failcount is decremented when we see this peer in + // a tracker response or peer exchange message. + int failcount; + + // You can know which piece, and which part of that piece, that is + // currently being downloaded from a specific peer by looking at these + // four members. ``downloading_piece_index`` is the index of the piece + // that is currently being downloaded. This may be set to -1 if there's + // currently no piece downloading from this peer. If it is >= 0, the + // other three members are valid. ``downloading_block_index`` is the + // index of the block (or sub-piece) that is being downloaded. + // ``downloading_progress`` is the number of bytes of this block we have + // received from the peer, and ``downloading_total`` is the total number + // of bytes in this block. + piece_index_t downloading_piece_index; + int downloading_block_index; + int downloading_progress; + int downloading_total; + + // the kind of connection this is. Used for the connection_type field. + enum connection_type_t + { + // Regular bittorrent connection + standard_bittorrent = 0, + + // HTTP connection using the `BEP 19`_ protocol + web_seed = 1, + + // HTTP connection using the `BEP 17`_ protocol + http_seed = 2 + }; + + // the kind of connection this peer uses. See connection_type_t. + int connection_type; + +#if TORRENT_ABI_VERSION == 1 + // an estimate of the rate this peer is downloading at, in + // bytes per second. + TORRENT_DEPRECATED_MEMBER int remote_dl_rate; +#endif + + // the number of bytes this peer has pending in the disk-io thread. + // Downloaded and waiting to be written to disk. This is what is capped + // by ``settings_pack::max_queued_disk_bytes``. + int pending_disk_bytes; + + // number of outstanding bytes to read + // from disk + int pending_disk_read_bytes; + + // the number of bytes this peer has been assigned to be allowed to send + // and receive until it has to request more quota from the bandwidth + // manager. + int send_quota; + int receive_quota; + + // an estimated round trip time to this peer, in milliseconds. It is + // estimated by timing the TCP ``connect()``. It may be 0 for + // incoming connections. + int rtt; + + // the number of pieces this peer has. + int num_pieces; + + // the highest download and upload rates seen on this connection. They + // are given in bytes per second. This number is reset to 0 on reconnect. + int download_rate_peak; + int upload_rate_peak; + + // the progress of the peer in the range [0, 1]. This is always 0 when + // floating point operations are disabled, instead use ``progress_ppm``. + float progress; // [0, 1] + + // indicates the download progress of the peer in the range [0, 1000000] + // (parts per million). + int progress_ppm; + +#if TORRENT_ABI_VERSION == 1 + // this is an estimation of the upload rate, to this peer, where it will + // unchoke us. This is a coarse estimation based on the rate at which + // we sent right before we were choked. This is primarily used for the + // bittyrant choking algorithm. + TORRENT_DEPRECATED_MEMBER int estimated_reciprocation_rate; +#else + int deprecated_estimated_reciprocation_rate; +#endif + + // the IP-address to this peer. The type is an asio endpoint. For + // more info, see the asio_ documentation. + // + // .. _asio: http://asio.sourceforge.net/asio-0.3.8/doc/asio/reference.html + tcp::endpoint ip; + + // the IP and port pair the socket is bound to locally. i.e. the IP + // address of the interface it's going out over. This may be useful for + // multi-homed clients with multiple interfaces to the internet. + tcp::endpoint local_endpoint; + + // The peer is not waiting for any external events to + // send or receive data. + static constexpr bandwidth_state_flags_t bw_idle = 0_bit; + + // The peer is waiting for the rate limiter. + static constexpr bandwidth_state_flags_t bw_limit = 1_bit; + + // The peer has quota and is currently waiting for a + // network read or write operation to complete. This is + // the state all peers are in if there are no bandwidth + // limits. + static constexpr bandwidth_state_flags_t bw_network = 2_bit; + + // The peer is waiting for the disk I/O thread to catch + // up writing buffers to disk before downloading more. + static constexpr bandwidth_state_flags_t bw_disk = 4_bit; + + // bitmasks indicating what state this peer + // is in with regards to sending and receiving data. The states are + // defined as independent flags of type bandwidth_state_flags_t, in this + // class. + bandwidth_state_flags_t read_state; + bandwidth_state_flags_t write_state; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED_MEMBER static constexpr bandwidth_state_flags_t bw_torrent = bw_limit; + TORRENT_DEPRECATED_MEMBER static constexpr bandwidth_state_flags_t bw_global = bw_limit; + + // the number of bytes per second we are allowed to send to or receive + // from this peer. It may be -1 if there's no local limit on the peer. + // The global limit and the torrent limit may also be enforced. + TORRENT_DEPRECATED_MEMBER int upload_limit; + TORRENT_DEPRECATED_MEMBER int download_limit; + + // a measurement of the balancing of free download (that we get) and free + // upload that we give. Every peer gets a certain amount of free upload, + // but this member says how much *extra* free upload this peer has got. + // If it is a negative number it means that this was a peer from which we + // have got this amount of free download. + TORRENT_DEPRECATED_MEMBER std::int64_t load_balancing; +#endif + }; + +TORRENT_VERSION_NAMESPACE_2_END + +#if TORRENT_ABI_VERSION == 1 + // internal + struct TORRENT_EXTRA_EXPORT peer_list_entry + { + // internal + enum flags_t + { + banned = 1 + }; + + // internal + tcp::endpoint ip; + // internal + int flags; + // internal + std::uint8_t failcount; + // internal + std::uint8_t source; + }; +#endif + +} + +#endif // TORRENT_PEER_INFO_HPP_INCLUDED diff --git a/include/libtorrent/peer_list.hpp b/include/libtorrent/peer_list.hpp new file mode 100644 index 0000000..94b1c75 --- /dev/null +++ b/include/libtorrent/peer_list.hpp @@ -0,0 +1,271 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_POLICY_HPP_INCLUDED +#define TORRENT_POLICY_HPP_INCLUDED + +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/request_blocks.hpp" // for source_rank + +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/aux_/deque.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/string_view.hpp" +#include "libtorrent/pex_flags.hpp" + +namespace libtorrent { + + struct torrent_peer_allocator_interface; + + // this object is used to communicate torrent state and + // some configuration to the peer_list object. This make + // the peer_list type not depend on the torrent type directly. + struct torrent_state + { + bool is_paused = false; + bool is_finished = false; + bool allow_multiple_connections_per_ip = false; + + // this is set by peer_list::add_peer to either true or false + // true means the peer we just added was new, false means + // we already knew about the peer + bool first_time_seen = false; + + int max_peerlist_size = 1000; + int min_reconnect_time = 60; + + // the number of iterations over the peer list for this operation + int loop_counter = 0; + + // these are used only by find_connect_candidates in order + // to implement peer ranking. See: + // http://blog.libtorrent.org/2012/12/swarm-connectivity/ + external_ip ip; + int port = 0; + + // the number of times a peer must fail before it's no longer considered + // a connect candidate + int max_failcount = 3; + + // if any peer were removed during this call, they are returned in + // this vector. The caller would want to make sure there are no + // references to these torrent_peers anywhere + std::vector erased; + }; + + struct erase_peer_flags_tag; + using erase_peer_flags_t = flags::bitfield_flag; + + struct TORRENT_EXTRA_EXPORT peer_list : single_threaded + { + explicit peer_list(torrent_peer_allocator_interface& alloc); + ~peer_list(); + + void clear(); + + // not copyable + peer_list(peer_list const&) = delete; + peer_list& operator=(peer_list const&) = delete; + +#if TORRENT_USE_I2P + torrent_peer* add_i2p_peer(string_view destination + , peer_source_flags_t src, pex_flags_t flags + , torrent_state* state); +#endif + + // this is called once for every torrent_peer we get from + // the tracker, pex, lsd or dht. + torrent_peer* add_peer(tcp::endpoint const& remote + , peer_source_flags_t source, pex_flags_t flags + , torrent_state* state); + + // false means duplicate connection + bool update_peer_port(int port, torrent_peer* p + , peer_source_flags_t src + , torrent_state* state); + + // called when an incoming connection is accepted + // false means the connection was refused or failed + bool new_connection(peer_connection_interface& c, int session_time, torrent_state* state); + + // the given connection was just closed + void connection_closed(const peer_connection_interface& c + , int session_time, torrent_state* state); + + bool ban_peer(torrent_peer* p); + void set_connection(torrent_peer* p, peer_connection_interface* c); + void set_failcount(torrent_peer* p, int f); + void inc_failcount(torrent_peer* p); + + void apply_ip_filter(ip_filter const& filter, torrent_state* state + , std::vector
    & banned); + void apply_port_filter(port_filter const& filter, torrent_state* state + , std::vector
    & banned); + + void set_seed(torrent_peer* p, bool s); + + // this clears all cached peer priorities. It's called when + // our external IP changes + void clear_peer_prio(); + +#if TORRENT_USE_ASSERTS + bool has_connection(const peer_connection_interface* p); +#endif +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + int num_peers() const { return int(m_peers.size()); } + + using peers_t = aux::deque; + using iterator = peers_t::iterator; + using const_iterator = peers_t::const_iterator; + iterator begin() { return m_peers.begin(); } + iterator end() { return m_peers.end(); } + const_iterator begin() const { return m_peers.begin(); } + const_iterator end() const { return m_peers.end(); } + + std::pair find_peers(address const& a) + { +#if TORRENT_USE_I2P + if (a == address()) + return std::pair(m_peers.end(), m_peers.end()); +#endif + return std::equal_range( + m_peers.begin(), m_peers.end(), a, peer_address_compare()); + } + + std::pair find_peers(address const& a) const + { + return std::equal_range( + m_peers.begin(), m_peers.end(), a, peer_address_compare()); + } + + torrent_peer* connect_one_peer(int session_time, torrent_state* state); + + bool has_peer(torrent_peer const* p) const; + + int num_seeds() const { return int(m_num_seeds); } + int num_connect_candidates() const { return m_num_connect_candidates; } + + void erase_peer(torrent_peer* p, torrent_state* state); + void erase_peer(iterator i, torrent_state* state); + + void set_max_failcount(torrent_state* st); + + private: + + void recalculate_connect_candidates(torrent_state* state); + + void update_connect_candidates(int delta); + + void update_peer(torrent_peer* p, peer_source_flags_t src + , pex_flags_t flags, tcp::endpoint const& remote); + bool insert_peer(torrent_peer* p, iterator iter + , pex_flags_t flags, torrent_state* state); + + bool compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) const; + bool compare_peer(torrent_peer const* lhs, torrent_peer const* rhs + , external_ip const& external, int source_port) const; + + void find_connect_candidates(std::vector& peers + , int session_time, torrent_state* state); + + bool is_connect_candidate(torrent_peer const& p) const; + bool is_erase_candidate(torrent_peer const& p) const; + bool is_force_erase_candidate(torrent_peer const& pe) const; + bool should_erase_immediately(torrent_peer const& p) const; + + static constexpr erase_peer_flags_t force_erase = 1_bit; + void erase_peers(torrent_state* state, erase_peer_flags_t flags = {}); + + peers_t m_peers; + + // this should be nullptr for the most part. It's set + // to point to a valid torrent_peer object if that + // object needs to be kept alive. If we ever feel + // like removing a torrent_peer from m_peers, we + // first check if the peer matches this one, and + // if so, don't delete it. + torrent_peer* m_locked_peer; + + // the peer allocator, as stored from the constructor + // this must be available in the destructor to free all peers + torrent_peer_allocator_interface& m_peer_allocator; + + // the number of seeds in the torrent_peer list + std::uint32_t m_num_seeds:31; + + // this was the state of the torrent the + // last time we recalculated the number of + // connect candidates. Since seeds (or upload + // only) peers are not connect candidates + // when we're finished, the set depends on + // this state. Every time m_torrent->is_finished() + // is different from this state, we need to + // recalculate the connect candidates. + std::uint32_t m_finished:1; + + // since the torrent_peer list can grow too large + // to scan all of it, start at this index + int m_round_robin = 0; + + // a list of good connect candidates + std::vector m_candidate_cache; + + // The number of peers in our torrent_peer list + // that are connect candidates. i.e. they're + // not already connected and they have not + // yet reached their max try count and they + // have the connectable state (we have a listen + // port for them). + int m_num_connect_candidates = 0; + + // if a peer has failed this many times or more, we don't consider + // it a connect candidate anymore. + int m_max_failcount = 3; + }; + +} + +#endif // TORRENT_POLICY_HPP_INCLUDED diff --git a/include/libtorrent/peer_request.hpp b/include/libtorrent/peer_request.hpp new file mode 100644 index 0000000..f31e7d8 --- /dev/null +++ b/include/libtorrent/peer_request.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_REQUEST_HPP_INCLUDED +#define TORRENT_PEER_REQUEST_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + // represents a byte range within a piece. Internally this is + // is used for incoming piece requests. + struct TORRENT_EXPORT peer_request + { + // the index of the piece in which the range starts. + piece_index_t piece; + // the offset within that piece where the range starts. + int start; + // the size of the range, in bytes. + int length; + + // returns true if the right hand side peer_request refers to the same + // range as this does. + bool operator==(peer_request const& r) const + { return piece == r.piece && start == r.start && length == r.length; } + }; +} + +#endif // TORRENT_PEER_REQUEST_HPP_INCLUDED diff --git a/include/libtorrent/performance_counters.hpp b/include/libtorrent/performance_counters.hpp new file mode 100644 index 0000000..f4e3f39 --- /dev/null +++ b/include/libtorrent/performance_counters.hpp @@ -0,0 +1,510 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED +#define TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/array.hpp" + +#include +#include +#include + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT counters + { + // TODO: move this out of counters + enum stats_counter_t + { + // the number of peers that were disconnected this + // tick due to protocol error + error_peers, + disconnected_peers, + eof_peers, + connreset_peers, + connrefused_peers, + connaborted_peers, + notconnected_peers, + perm_peers, + buffer_peers, + unreachable_peers, + broken_pipe_peers, + addrinuse_peers, + no_access_peers, + invalid_arg_peers, + aborted_peers, + + piece_requests, + max_piece_requests, + invalid_piece_requests, + choked_piece_requests, + cancelled_piece_requests, + piece_rejects, + error_incoming_peers, + error_outgoing_peers, + error_rc4_peers, + error_encrypted_peers, + error_tcp_peers, + error_utp_peers, + + // the number of times the piece picker was + // successfully invoked, split by the reason + // it was invoked + reject_piece_picks, + unchoke_piece_picks, + incoming_redundant_piece_picks, + incoming_piece_picks, + end_game_piece_picks, + snubbed_piece_picks, + interesting_piece_picks, + hash_fail_piece_picks, + + // these counters indicate which parts + // of the piece picker CPU is spent in + piece_picker_partial_loops, + piece_picker_suggest_loops, + piece_picker_sequential_loops, + piece_picker_reverse_rare_loops, + piece_picker_rare_loops, + piece_picker_rand_start_loops, + piece_picker_rand_loops, + piece_picker_busy_loops, + + // reasons to disconnect peers + connect_timeouts, + uninteresting_peers, + timeout_peers, + no_memory_peers, + too_many_peers, + transport_timeout_peers, + num_banned_peers, + banned_for_hash_failure, + + // connection attempts (not necessarily successful) + connection_attempts, + // the number of iterations over the peer list when finding + // a connect candidate + connection_attempt_loops, + + // the number of peer connection attempts made as high + // priority connections for new torrents + boost_connection_attempts, + + // calls to torrent::connect_to_peer() that failed + missed_connection_attempts, + + // calls to peer_list::connect_one_peer() resulting in + // no peer candidate being found + no_peer_connection_attempts, + + // successful incoming connections (not rejected for any reason) + incoming_connections, + + // counts events where the network + // thread wakes up + on_read_counter, + on_write_counter, + on_tick_counter, + on_lsd_counter, + on_lsd_peer_counter, + on_udp_counter, + on_accept_counter, + on_disk_queue_counter, + on_disk_counter, + +#if TORRENT_ABI_VERSION == 1 + torrent_evicted_counter, +#endif + + // bittorrent message counters + // TODO: should keepalives be in here too? + // how about dont-have, share-mode, upload-only + num_incoming_choke, + num_incoming_unchoke, + num_incoming_interested, + num_incoming_not_interested, + num_incoming_have, + num_incoming_bitfield, + num_incoming_request, + num_incoming_piece, + num_incoming_cancel, + num_incoming_dht_port, + num_incoming_suggest, + num_incoming_have_all, + num_incoming_have_none, + num_incoming_reject, + num_incoming_allowed_fast, + num_incoming_ext_handshake, + num_incoming_pex, + num_incoming_metadata, + num_incoming_extended, + + num_outgoing_choke, + num_outgoing_unchoke, + num_outgoing_interested, + num_outgoing_not_interested, + num_outgoing_have, + num_outgoing_bitfield, + num_outgoing_request, + num_outgoing_piece, + num_outgoing_cancel, + num_outgoing_dht_port, + num_outgoing_suggest, + num_outgoing_have_all, + num_outgoing_have_none, + num_outgoing_reject, + num_outgoing_allowed_fast, + num_outgoing_ext_handshake, + num_outgoing_pex, + num_outgoing_metadata, + num_outgoing_extended, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_hashed, + num_blocks_cache_hits, + num_write_ops, + num_read_ops, + num_read_back, + + disk_read_time, + disk_write_time, + disk_hash_time, + disk_job_time, + + waste_piece_timed_out, + waste_piece_cancelled, + waste_piece_unknown, + waste_piece_seed, + waste_piece_end_game, + waste_piece_closing, + + sent_payload_bytes, + sent_bytes, + sent_ip_overhead_bytes, + sent_tracker_bytes, + recv_payload_bytes, + recv_bytes, + recv_ip_overhead_bytes, + recv_tracker_bytes, + + recv_failed_bytes, + recv_redundant_bytes, + + dht_messages_in, + dht_messages_in_dropped, + dht_messages_out, + dht_messages_out_dropped, + dht_bytes_in, + dht_bytes_out, + + dht_ping_in, + dht_ping_out, + dht_find_node_in, + dht_find_node_out, + dht_get_peers_in, + dht_get_peers_out, + dht_announce_peer_in, + dht_announce_peer_out, + dht_get_in, + dht_get_out, + dht_put_in, + dht_put_out, + dht_sample_infohashes_in, + dht_sample_infohashes_out, + + dht_invalid_announce, + dht_invalid_get_peers, + dht_invalid_find_node, + dht_invalid_put, + dht_invalid_get, + dht_invalid_sample_infohashes, + + // uTP counters. + utp_packet_loss, + utp_timeout, + utp_packets_in, + utp_packets_out, + utp_fast_retransmit, + utp_packet_resend, + utp_samples_above_target, + utp_samples_below_target, + utp_payload_pkts_in, + utp_payload_pkts_out, + utp_invalid_pkts_in, + utp_redundant_pkts_in, + + // the buffer sizes accepted by + // socket send calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_send_size3, + socket_send_size4, + socket_send_size5, + socket_send_size6, + socket_send_size7, + socket_send_size8, + socket_send_size9, + socket_send_size10, + socket_send_size11, + socket_send_size12, + socket_send_size13, + socket_send_size14, + socket_send_size15, + socket_send_size16, + socket_send_size17, + socket_send_size18, + socket_send_size19, + socket_send_size20, + + // the buffer sizes returned by + // socket recv calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_recv_size3, + socket_recv_size4, + socket_recv_size5, + socket_recv_size6, + socket_recv_size7, + socket_recv_size8, + socket_recv_size9, + socket_recv_size10, + socket_recv_size11, + socket_recv_size12, + socket_recv_size13, + socket_recv_size14, + socket_recv_size15, + socket_recv_size16, + socket_recv_size17, + socket_recv_size18, + socket_recv_size19, + socket_recv_size20, + + num_stats_counters + }; + + // == ALL FOLLOWING ARE GAUGES == + + // it is important that all gauges have a higher index than counters. + // This assumption is relied upon in other parts of the code + enum stats_gauge_t + { + num_checking_torrents = num_stats_counters, + num_stopped_torrents, + num_upload_only_torrents, // upload_only means finished + num_downloading_torrents, + num_seeding_torrents, + num_queued_seeding_torrents, + num_queued_download_torrents, + num_error_torrents, + + // the number of torrents that don't have the + // IP filter applied to them. + non_filter_torrents, + + // these counter indices deliberately + // match the order of socket type IDs + // defined in socket_type.hpp. + num_tcp_peers, + num_socks5_peers, + num_http_proxy_peers, + num_utp_peers, + num_i2p_peers, + num_ssl_peers, + num_ssl_socks5_peers, + num_ssl_http_proxy_peers, + num_ssl_utp_peers, + + // the number of peer connections that are half-open (i.e. in the + // process of completing a connection attempt) and fully connected. + // These states are mutually exclusive (a connection cannot be in both + // states simultaneously). + num_peers_half_open, + num_peers_connected, + + // the number of peers interested in us (``up_interested``) and peers + // we are interested in (``down_interested``). + num_peers_up_interested, + num_peers_down_interested, + + // the total number of unchoked peers (``up_unchoked_all``), the number + // of peers unchoked via the optimistic unchoke + // (``up_unchoked_optimistic``) and peers unchoked via the + // reciprocation (regular) unchoke mechanism (``up_unchoked``). + // and the number of peers that have unchoked us (``down_unchoked). + num_peers_up_unchoked_all, + num_peers_up_unchoked_optimistic, + num_peers_up_unchoked, + num_peers_down_unchoked, + + // the number of peers with at least one piece request pending, + // downloading (``down_requests``) or uploading (``up_requests``) + num_peers_up_requests, + num_peers_down_requests, + + // the number of peers that have at least one outstanding disk request, + // either reading (``up_disk``) or writing (``down_disk``). + num_peers_up_disk, + num_peers_down_disk, + + // the number of peers in end-game mode. End game mode is where there + // are no blocks that we have not sent any requests to download. In ths + // mode, blocks are allowed to be requested from more than one peer at + // at time. + num_peers_end_game, + + write_cache_blocks, + read_cache_blocks, + request_latency, + pinned_blocks, + disk_blocks_in_use, + queued_disk_jobs, + num_running_disk_jobs, + num_read_jobs, + num_write_jobs, + num_jobs, + num_writing_threads, + num_running_threads, + blocked_disk_jobs, + queued_write_bytes, + num_unchoke_slots, + + num_fenced_read, + num_fenced_write, + num_fenced_hash, + num_fenced_move_storage, + num_fenced_release_files, + num_fenced_delete_files, + num_fenced_check_fastresume, + num_fenced_save_resume_data, + num_fenced_rename_file, + num_fenced_stop_torrent, + num_fenced_flush_piece, + num_fenced_flush_hashed, + num_fenced_flush_storage, + num_fenced_trim_cache, + num_fenced_file_priority, + num_fenced_load_torrent, + num_fenced_clear_piece, + num_fenced_tick_storage, + + arc_mru_size, + arc_mru_ghost_size, + arc_mfu_size, + arc_mfu_ghost_size, + arc_write_size, + arc_volatile_size, + + dht_nodes, + dht_node_cache, + dht_torrents, + dht_peers, + dht_immutable_data, + dht_mutable_data, + dht_allocated_observers, + + has_incoming_connections, + + limiter_up_queue, + limiter_down_queue, + limiter_up_bytes, + limiter_down_bytes, + + // the number of uTP connections in each respective state + // these must be defined in the same order as the state_t enum + // in utp_stream + num_utp_idle, + num_utp_syn_sent, + num_utp_connected, + num_utp_fin_sent, + num_utp_close_wait, + num_utp_deleted, + + num_outstanding_accept, + + num_queued_tracker_announces, + + num_counters, + num_gauges_counters = num_counters - num_stats_counters + }; +#ifdef ATOMIC_LLONG_LOCK_FREE +#define TORRENT_COUNTER_NOEXCEPT noexcept +#else +#define TORRENT_COUNTER_NOEXCEPT +#endif + + counters() TORRENT_COUNTER_NOEXCEPT; + + counters(counters const&) TORRENT_COUNTER_NOEXCEPT; + counters& operator=(counters const&) TORRENT_COUNTER_NOEXCEPT; + + // returns the new value + std::int64_t inc_stats_counter(int c, std::int64_t value = 1) TORRENT_COUNTER_NOEXCEPT; + std::int64_t operator[](int i) const TORRENT_COUNTER_NOEXCEPT; + + void set_value(int c, std::int64_t value) TORRENT_COUNTER_NOEXCEPT; + void blend_stats_counter(int c, std::int64_t value, int ratio) TORRENT_COUNTER_NOEXCEPT; + + private: + + // TODO: some space could be saved here by making gauges 32 bits + // TODO: restore these to regular integers. Instead have one copy + // of the counters per thread and collect them at convenient + // synchronization points +#ifdef ATOMIC_LLONG_LOCK_FREE + aux::array, num_counters> m_stats_counter; +#else + // if the atomic type is't lock-free, use a single lock instead, for + // the whole array + mutable std::mutex m_mutex; + aux::array m_stats_counter; +#endif + }; +} + +#endif diff --git a/include/libtorrent/pex_flags.hpp b/include/libtorrent/pex_flags.hpp new file mode 100644 index 0000000..d4fa99a --- /dev/null +++ b/include/libtorrent/pex_flags.hpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEX_FLAGS_HPP_INCLUDE +#define TORRENT_PEX_FLAGS_HPP_INCLUDE + +#include + +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + using pex_flags_t = flags::bitfield_flag; + + // the peer supports protocol encryption + constexpr pex_flags_t pex_encryption = 1_bit; + + // the peer is a seed + constexpr pex_flags_t pex_seed = 2_bit; + + // the peer supports the uTP, transport protocol over UDP. + constexpr pex_flags_t pex_utp = 3_bit; + + // the peer supports the holepunch extension If this flag is received from a + // peer, it can be used as a rendezvous point in case direct connections to + // the peer fail + constexpr pex_flags_t pex_holepunch = 4_bit; +} + +#endif + diff --git a/include/libtorrent/piece_block.hpp b/include/libtorrent/piece_block.hpp new file mode 100644 index 0000000..0fe8f27 --- /dev/null +++ b/include/libtorrent/piece_block.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_HPP_INCLUDED + +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT piece_block + { + static const piece_block invalid; + + piece_block() = default; + piece_block(piece_index_t p_index, int b_index) + : piece_index(p_index) + , block_index(b_index) + { + } + piece_index_t piece_index {0}; + int block_index = 0; + + bool operator<(piece_block const& b) const + { + if (piece_index < b.piece_index) return true; + if (piece_index == b.piece_index) return block_index < b.block_index; + return false; + } + + bool operator==(piece_block const& b) const + { return piece_index == b.piece_index && block_index == b.block_index; } + + bool operator!=(piece_block const& b) const + { return piece_index != b.piece_index || block_index != b.block_index; } + }; +} +#endif diff --git a/include/libtorrent/piece_block_progress.hpp b/include/libtorrent/piece_block_progress.hpp new file mode 100644 index 0000000..b0ec037 --- /dev/null +++ b/include/libtorrent/piece_block_progress.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED +#define TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + struct piece_block_progress + { + static constexpr piece_index_t invalid_index{-1}; + + // the piece and block index + // determines exactly which + // part of the torrent that + // is currently being downloaded + piece_index_t piece_index{invalid_index}; + int block_index; + // the number of bytes we have received + // of this block + int bytes_downloaded; + // the number of bytes in the block + int full_block_bytes; + }; +} + +#endif // TORRENT_PIECE_BLOCK_PROGRESS_HPP_INCLUDED diff --git a/include/libtorrent/piece_picker.hpp b/include/libtorrent/piece_picker.hpp new file mode 100644 index 0000000..dec5c9f --- /dev/null +++ b/include/libtorrent/piece_picker.hpp @@ -0,0 +1,876 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef TORRENT_PIECE_PICKER_HPP_INCLUDED +#define TORRENT_PIECE_PICKER_HPP_INCLUDED + +// heavy weight reference counting invariant checks +//#define TORRENT_DEBUG_REFCOUNTS + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/peer_id.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/alert_types.hpp" // for picker_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + class torrent; + class peer_connection; + template + struct typed_bitfield; + struct counters; + struct torrent_peer; + + using prio_index_t = aux::strong_typedef; + using picker_options_t = flags::bitfield_flag; + using download_queue_t = aux::strong_typedef; + using piece_extent_t = aux::strong_typedef; + + struct piece_count + { + // the number of pieces included in the "set" + int num_pieces; + // the number of blocks, out of those pieces, that are pad + // blocks (i.e. entirely part of pad files) + int pad_blocks; + // true if the last piece is part of the set + bool last_piece; + }; + + class TORRENT_EXTRA_EXPORT piece_picker + { + // only defined when TORRENT_PICKER_LOG is defined, used for debugging + // unit tests + friend void print_pieces(piece_picker const& p); + + public: + + enum + { + // the number of priority levels + priority_levels = 8, + // priority factor + prio_factor = 3, + // max blocks per piece + // there are counters in downloading_piece that only have 15 bits to + // count blocks per piece, that's restricting this + max_blocks_per_piece = (1 << 15) - 1 + }; + + struct block_info + { + block_info(): num_peers(0), state(state_none) {} + // the peer this block was requested or + // downloaded from. + torrent_peer* peer = nullptr; + // the number of peers that has this block in their + // download or request queues + unsigned num_peers:14; + // the state of this block + enum { state_none, state_requested, state_writing, state_finished }; + unsigned state:2; +#if TORRENT_USE_ASSERTS + // to allow verifying the invariant of blocks belonging to the right piece + piece_index_t piece_index{-1}; + std::set peers; +#endif + }; + + // pick rarest first + static constexpr picker_options_t rarest_first = 0_bit; + + // pick the most common first, or the last pieces if sequential + static constexpr picker_options_t reverse = 1_bit; + + // only pick pieces exclusively requested from this peer + static constexpr picker_options_t on_parole = 2_bit; + + // always pick partial pieces before any other piece + static constexpr picker_options_t prioritize_partials = 3_bit; + + // pick pieces in sequential order + static constexpr picker_options_t sequential = 4_bit; + + // treat pieces with priority 6 and below as filtered + // to trigger end-game mode until all prio 7 pieces are + // completed + static constexpr picker_options_t time_critical_mode = 5_bit; + + // only expands pieces (when prefer contiguous blocks is set) + // within properly aligned ranges, not the largest possible + // range of pieces. + static constexpr picker_options_t align_expanded_pieces = 6_bit; + + // this will create an affinity to pick pieces in extents of 4 MiB, in an + // attempt to improve disk I/O by picking ranges of pieces (if pieces are + // small) + static constexpr picker_options_t piece_extent_affinity = 7_bit; + + struct downloading_piece + { + downloading_piece() + : finished(0) + , passed_hash_check(0) + , writing(0) + , locked(0) + , requested(0) + , outstanding_hash_check(0) {} + + bool operator<(downloading_piece const& rhs) const { return index < rhs.index; } + + // the index of the piece + piece_index_t index{(std::numeric_limits::max)()}; + + // info about each block in this piece. this is an index into the + // m_block_info array, when multiplied by m_blocks_per_piece. + // The m_blocks_per_piece following entries contain information about + // all blocks in this piece. + std::uint16_t info_idx{(std::numeric_limits::max)()}; + + // the number of blocks in the finished state + std::uint16_t finished:15; + + // set to true when the hash check job + // returns with a valid hash for this piece. + // we might not 'have' the piece yet though, + // since it might not have been written to + // disk. This is not set of locked is + // set. + std::uint16_t passed_hash_check:1; + + // the number of blocks in the writing state + std::uint16_t writing:15; + + // when this is set, blocks from this piece may + // not be picked. This is used when the hash check + // fails or writing to the disk fails, while waiting + // to synchronize the disk thread and clear out any + // remaining state. Once this synchronization is + // done, restore_piece() is called to clear the + // locked flag. + std::uint16_t locked:1; + + // the number of blocks in the requested state + std::uint16_t requested:15; + + // set to true while there is an outstanding + // hash check for this piece + std::uint16_t outstanding_hash_check:1; + }; + + piece_picker(int blocks_per_piece, int blocks_in_last_piece, int total_num_pieces); + + void get_availability(aux::vector& avail) const; + int get_availability(piece_index_t piece) const; + + // increases the peer count for the given piece + // (is used when a HAVE message is received) + void inc_refcount(piece_index_t index, const torrent_peer* peer); + void dec_refcount(piece_index_t index, const torrent_peer* peer); + + // increases the peer count for the given piece + // (is used when a BITFIELD message is received) + void inc_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer); + // decreases the peer count for the given piece + // (used when a peer disconnects) + void dec_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer); + + // these will increase and decrease the peer count + // of all pieces. They are used when seeds join + // or leave the swarm. + void inc_refcount_all(const torrent_peer* peer); + void dec_refcount_all(const torrent_peer* peer); + + // we have every piece. This is used when creating a piece picker for a + // seed + void we_have_all(); + + // This indicates that we just received this piece + // it means that the refcounter will indicate that + // we are not interested in this piece anymore + // (i.e. we don't have to maintain a refcount) + void we_have(piece_index_t index); + void we_dont_have(piece_index_t index); + + // the lowest piece index we do not have + piece_index_t cursor() const { return m_cursor; } + + // one past the last piece we do not have. + piece_index_t reverse_cursor() const { return m_reverse_cursor; } + + // sets all pieces to dont-have + void resize(int blocks_per_piece, int blocks_in_last_piece, int total_num_pieces); + int num_pieces() const { return int(m_piece_map.size()); } + + bool have_piece(piece_index_t index) const; + + bool is_downloading(piece_index_t index) const + { + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_piece_map.end_index()); + + piece_pos const& p = m_piece_map[index]; + return p.downloading(); + } + + // sets the priority of a piece. + // returns true if the priority was changed from 0 to non-0 + // or vice versa + bool set_piece_priority(piece_index_t index, download_priority_t prio); + + // returns the priority for the piece at 'index' + download_priority_t piece_priority(piece_index_t index) const; + + // returns the current piece priorities for all pieces + void piece_priorities(std::vector& pieces) const; + + // pieces should be the vector that represents the pieces a + // client has. It returns a list of all pieces that this client + // has and that are interesting to download. It returns them in + // priority order. It doesn't care about the download flag. + // The user of this function must lookup if any piece is + // marked as being downloaded. If the user of this function + // decides to download a piece, it must mark it as being downloaded + // itself, by using the mark_as_downloading() member function. + // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! + // The last argument is the torrent_peer pointer for the peer that + // we'll download from. + // prefer_contiguous_blocks indicates how many blocks we would like + // to request contiguously. The blocks are not merged by the piece + // picker, but may be coalesced later by the peer_connection. + // this feature is used by web_peer_connection to request larger blocks + // at a time to mitigate limited pipelining and lack of keep-alive + // (i.e. higher overhead per request). + picker_flags_t pick_pieces(typed_bitfield const& pieces + , std::vector& interesting_blocks, int num_blocks + , int prefer_contiguous_blocks, torrent_peer* peer + , picker_options_t options, std::vector const& suggested_pieces + , int num_peers + , counters& pc + ) const; + + // picks blocks from each of the pieces in the piece_list + // vector that is also in the piece bitmask. The blocks + // are added to interesting_blocks, and busy blocks are + // added to backup_blocks. num blocks is the number of + // blocks to be picked. Blocks are not picked from pieces + // that are being downloaded + int add_blocks(piece_index_t piece, typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, std::vector const& ignore + , picker_options_t options) const; + + // picks blocks only from downloading pieces + int add_blocks_downloading(downloading_piece const& dp + , typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer + , picker_options_t options) const; + + // clears the peer pointer in all downloading pieces with this + // peer pointer + void clear_peer(torrent_peer* peer); + +#if TORRENT_USE_INVARIANT_CHECKS + // this is an invariant check + void check_peers(); +#endif + + // returns true if any client is currently downloading this + // piece-block, or if it's queued for downloading by some client + // or if it already has been successfully downloaded + bool is_requested(piece_block block) const; + // returns true if the block has been downloaded + bool is_downloaded(piece_block block) const; + // returns true if the block has been downloaded and written to disk + bool is_finished(piece_block block) const; + + // marks this piece-block as queued for downloading + // options are flags from options_t. + bool mark_as_downloading(piece_block block, torrent_peer* peer + , picker_options_t options = {}); + + // returns true if the block was marked as writing, + // and false if the block is already finished or writing + bool mark_as_writing(piece_block block, torrent_peer* peer); + + void mark_as_canceled(piece_block block, torrent_peer* peer); + void mark_as_finished(piece_block block, torrent_peer* peer); + + void mark_as_pad(piece_block block); + + // prevent blocks from being picked from this piece. + // to unlock the piece, call restore_piece() on it + void lock_piece(piece_index_t piece); + + void write_failed(piece_block block); + int num_peers(piece_block block) const; + + void piece_passed(piece_index_t index); + + // returns information about the given piece + void piece_info(piece_index_t index, piece_picker::downloading_piece& st) const; + + struct piece_stats_t + { + int peer_count; + int priority; + bool have; + bool downloading; + }; + + piece_stats_t piece_stats(piece_index_t index) const; + + // if a piece had a hash-failure, it must be restored and + // made available for redownloading + void restore_piece(piece_index_t index); + + // clears the given piece's download flag + // this means that this piece-block can be picked again + void abort_download(piece_block block, torrent_peer* peer = nullptr); + + // returns true if all blocks in this piece are finished + // or if we have the piece + bool is_piece_finished(piece_index_t index) const; + + // returns true if we have the piece or if the piece + // has passed the hash check + bool has_piece_passed(piece_index_t index) const; + + // returns the number of blocks there is in the given piece + int blocks_in_piece(piece_index_t index) const; + + // return the peer pointers to all peers that participated in + // this piece + void get_downloaders(std::vector& d, piece_index_t index) const; + + std::vector get_download_queue() const; + int get_download_queue_size() const; + + void get_download_queue_sizes(int* partial + , int* full, int* finished, int* zero_prio) const; + + torrent_peer* get_downloader(piece_block block) const; + + + // piece states + // + // have: ----------- + // pieces: # # # # # # # # # # # + // filtered: ------- + // pads blk: ^ ^ ^ + // + // want-have: * * * * + // want: * * * * * * * + // total-have: * * * * * * + // + // we only care about: + // 1. pieces we have (less pad blocks we have) + // 2. pieces we have AND want (less pad blocks we have and want) + // 3. pieces we want (less pad blocks we want) + + // number of pieces not filtered, as well as the number of + // blocks out of those pieces that are pad blocks. + // ``last_piece`` is set if the last piece is one of the + // pieces. + piece_count want() const; + + // number of pieces we have out of the ones we have not filtered + piece_count have_want() const; + + // number of pieces we have (regardless of whether they are filtered) + piece_count have() const; + + piece_count all_pieces() const; + + int pad_blocks_in_piece(piece_index_t const index) const; + + // number of pieces whose hash has passed (but haven't necessarily + // been flushed to disk yet) + int num_passed() const { return m_num_passed; } + + // return true if all the pieces we want have passed the hash check (but + // may not have been written to disk yet) + bool is_finished() const + { + // this expression warrants some explanation: + // if the number of pieces we *want* to download + // is less than or (more likely) equal to the number of pieces that + // have passed the hash check (discounting the pieces that have passed + // the check but then had their priority set to 0). Then we're + // finished. Note that any piece we *have* implies it's both passed the + // hash check *and* been written to disk. + // num_pieces() - m_num_filtered - m_num_have_filtered + // <= (num_passed() - m_num_have_filtered) + // this can be simplified. Note how m_num_have_filtered appears on both + // side of the equation. + // + return num_pieces() - m_num_filtered <= num_passed(); + } + + bool is_seeding() const { return m_num_have == num_pieces(); } + + // the number of pieces we want and don't have + int num_want_left() const { return num_pieces() - m_num_have - m_num_filtered + m_num_have_filtered; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_piece_state() const; + // used in debug mode + void verify_priority(prio_index_t start, prio_index_t end, int prio) const; + void verify_pick(std::vector const& picked + , typed_bitfield const& bits) const; + + void check_peer_invariant(typed_bitfield const& have + , torrent_peer const* p) const; + void check_invariant(const torrent* t = nullptr) const; +#endif + + // functor that compares indices on downloading_pieces + struct has_index + { + explicit has_index(piece_index_t const i) : index(i) + { TORRENT_ASSERT(i >= piece_index_t(0)); } + bool operator()(downloading_piece const& p) const + { return p.index == index; } + piece_index_t const index; + }; + + int blocks_in_last_piece() const + { return m_blocks_in_last_piece; } + + std::pair distributed_copies() const; + + // return the array of block_info objects for a given downloading_piece. + // this array has m_blocks_per_piece elements in it + span blocks_for_piece(downloading_piece const& dp) const; + + private: + + piece_extent_t extent_for(piece_index_t) const; + index_range extent_for(piece_extent_t) const; + + void record_downloading_piece(piece_index_t const p); + + int num_pad_blocks() const { return m_num_pad_blocks; } + + span mutable_blocks_for_piece(downloading_piece const& dp); + + std::tuple requested_from( + piece_picker::downloading_piece const& p + , int num_blocks_in_piece, torrent_peer* peer) const; + + bool can_pick(piece_index_t piece, typed_bitfield const& bitmask) const; + bool is_piece_free(piece_index_t piece, typed_bitfield const& bitmask) const; + std::pair + expand_piece(piece_index_t piece, int whole_pieces + , typed_bitfield const& have + , picker_options_t options) const; + + struct piece_pos + { + piece_pos() {} + piece_pos(int const peer_count_, int const index_) + : peer_count(static_cast(peer_count_)) + , download_state(static_cast(piece_pos::piece_open)) + , piece_priority(static_cast(default_priority)) + , index(index_) + { + TORRENT_ASSERT(peer_count_ >= 0); + TORRENT_ASSERT(peer_count_ < (std::numeric_limits::max)()); + TORRENT_ASSERT(index_ >= 0); + } + + // the piece is partially downloaded or requested + static constexpr download_queue_t piece_downloading{0}; + + // partial pieces where all blocks in the piece have been requested + static constexpr download_queue_t piece_full{1}; + // partial pieces where all blocks in the piece have been received + // and are either finished or writing + static constexpr download_queue_t piece_finished{2}; + // partial pieces whose priority is 0 + static constexpr download_queue_t piece_zero_prio{3}; + + // the states up to this point indicate the piece is being + // downloaded (or at least has a partially downloaded piece + // in one of the m_downloads buckets). + static constexpr download_queue_t num_download_categories{4}; + + // the piece is open to be picked + static constexpr download_queue_t piece_open{4}; + + // this is not a new download category/download list bucket. + // it still goes into the piece_downloading bucket. However, + // it indicates that this piece only has outstanding requests + // from reverse peers. This is to de-prioritize it somewhat + static constexpr download_queue_t piece_downloading_reverse{5}; + static constexpr download_queue_t piece_full_reverse{6}; + + // returns one of the valid download categories of state_t or + // piece_open if this piece is not being downloaded + download_queue_t download_queue() const + { + if (state() == piece_downloading_reverse) + return piece_downloading; + if (state() == piece_full_reverse) + return piece_full; + return state(); + } + + bool reverse() const + { + return state() == piece_downloading_reverse + || state() == piece_full_reverse; + } + + void unreverse() + { + if (state() == piece_downloading_reverse) + state(piece_downloading); + else if (state() == piece_full_reverse) + state(piece_full); + } + + void make_reverse() + { + if (state() == piece_downloading) + state(piece_downloading_reverse); + else if (state() == piece_full) + state(piece_full_reverse); + } + + // the number of peers that has this piece + // (availability) + std::uint32_t peer_count : 26; + + // one of the download_queue_t values. This indicates whether this piece + // is currently being downloaded or not, and what state it's in if + // it is. Specifically, as an optimization, pieces that have all blocks + // requested from them are separated out into separate lists to make + // lookups quicker. The main oddity is that whether a downloading piece + // has only been requested from peers that are reverse, that's + // recorded as piece_downloading_reverse, which really means the same + // as piece_downloading, it just saves space to also indicate that it + // has a bit lower priority. The reverse bit is only relevant if the + // state is piece_downloading. + std::uint32_t download_state : 3; + + // TODO: 2 having 8 priority levels is probably excessive. It should + // probably be changed to 3 levels + dont-download + + // is 0 if the piece is filtered (not to be downloaded) + // 1 is low priority + // 2 is low priority + // 3 is mid priority + // 4 is default priority + // 5 is mid priority + // 6 is high priority + // 7 is high priority + std::uint32_t piece_priority : 3; + + // index in to the piece_info vector + prio_index_t index; + +#ifdef TORRENT_DEBUG_REFCOUNTS + // all the peers that have this piece + std::set have_peers; +#endif + + // index is set to this to indicate that we have the + // piece. There is no entry for the piece in the + // buckets if this is the case. + static constexpr prio_index_t we_have_index{-1}; + + // the priority value that means the piece is filtered + static constexpr std::uint32_t filter_priority = 0; + + // the max number the peer count can hold + static constexpr std::uint32_t max_peer_count = 0xffff; + + bool have() const { return index == we_have_index; } + void set_have() { index = we_have_index; TORRENT_ASSERT(have()); } + void set_not_have() { index = prio_index_t(0); TORRENT_ASSERT(!have()); } + bool downloading() const { return state() != piece_open; } + + bool filtered() const { return piece_priority == filter_priority; } + + // this function returns the effective priority of the piece. It's + // actually the sort order of this piece compared to other pieces. A + // lower index means it will be picked before a piece with a higher + // index. + // The availability of the piece (the number of peers that have this + // piece) is fundamentally controlling the priority. It's multiplied + // by 3 to form 3 levels of priority for each availability. + // + // downloading pieces (not reverse) + // | open pieces (not downloading) + // | | downloading pieces (reverse peers) + // | | | + // +---+---+---+ + // | 0 | 1 | 2 | + // +---+---+---+ + // this '3' is called prio_factor + // + // the manually set priority takes precedence over the availability + // by multiplying availability by priority. + + int priority(piece_picker const* picker) const + { + // filtered pieces (prio = 0), pieces we have or pieces with + // availability = 0 should not be present in the piece list + // returning -1 indicates that they shouldn't. + if (filtered() || have() || peer_count + picker->m_seeds == 0 + || state() == piece_full + || state() == piece_finished) + return -1; + + TORRENT_ASSERT(piece_priority > 0); + + // this is to keep downloading pieces at higher priority than + // pieces that are not being downloaded, and to make reverse + // downloading pieces to be lower priority + int adjustment = -2; + if (reverse()) adjustment = -1; + else if (state() != piece_open) adjustment = -3; + + // the + 1 here is because peer_count count be 0, it m_seeds + // is > 0. We don't actually care about seeds (except for the + // first one) since the order of the pieces is unaffected. + int availability = int(peer_count) + 1; + TORRENT_ASSERT(availability > 0); + TORRENT_ASSERT(int(priority_levels - piece_priority) > 0); + + return availability * int(priority_levels - piece_priority) + * prio_factor + adjustment; + } + + bool operator!=(piece_pos const& p) const + { return index != p.index || peer_count != p.peer_count; } + + bool operator==(piece_pos const& p) const + { return index == p.index && peer_count == p.peer_count; } + + download_queue_t state() const { return download_queue_t(download_state); } + void state(download_queue_t q) { download_state = static_cast(q); } + }; + +#ifndef TORRENT_DEBUG_REFCOUNTS + static_assert(sizeof(piece_pos) == sizeof(char) * 8, "unexpected struct size"); +#endif + + bool partial_compare_rarest_first(downloading_piece const* lhs + , downloading_piece const* rhs) const; + + void break_one_seed(); + + void update_pieces() const; + + prio_index_t priority_begin(int prio) const; + prio_index_t priority_end(int prio) const; + + // fills in the range [start, end) of pieces in + // m_pieces that have priority 'prio' + std::pair priority_range(int prio) const; + + // adds the piece 'index' to m_pieces + void add(piece_index_t index); + // removes the piece with the given priority and the + // elem_index in the m_pieces vector + void remove(int priority, prio_index_t elem_index); + // updates the position of the piece with the given + // priority and the elem_index in the m_pieces vector + void update(int priority, prio_index_t elem_index); + // shuffles the given piece inside it's priority range + void shuffle(int priority, prio_index_t elem_index); + + std::vector::iterator add_download_piece(piece_index_t index); + void erase_download_piece(std::vector::iterator i); + + std::vector::const_iterator find_dl_piece(download_queue_t, piece_index_t) const; + std::vector::iterator find_dl_piece(download_queue_t, piece_index_t); + + // returns an iterator to the downloading piece, whichever + // download list it may live in now + std::vector::iterator update_piece_state( + std::vector::iterator dp); + + private: + +#if TORRENT_USE_ASSERTS || TORRENT_USE_INVARIANT_CHECKS + index_range categories() const + { return {{}, piece_picker::piece_pos::num_download_categories}; } +#endif + + // the following vectors are mutable because they sometimes may + // be updated lazily, triggered by const functions + + // this maps indices to number of peers that has this piece and + // index into the m_piece_info vectors. + // piece_pos::we_have_index means that we have the piece, so it + // doesn't exist in the piece_info buckets + // pieces with the filtered flag set doesn't have entries in + // the m_piece_info buckets either + // TODO: should this be allocated lazily? + mutable aux::vector m_piece_map; + + // this indicates whether a block has been marked as a pad + // block or not. It's indexed by block index, i.e. piece_index + // * blocks_per_piece + block. These blocks should not be + // picked and are considered to be had + // TODO: this could be a much more efficient data structure + bitfield m_pad_blocks; + + // tracks the number of blocks in a specific piece that are pad blocks + std::unordered_map m_pads_in_piece; + + // when the adjecent_piece affinity is enabled, this contains the most + // recent "extents" of adjecent pieces that have been requested from + // this is mutable because it's updated by functions to pick pieces, which + // are const. That's an efficient place to update it, since it's being + // traversed already. + mutable std::vector m_recent_extents; + + // the number of bits set in the m_pad_blocks bitfield, i.e. + // the number of blocks marked as pads + int m_num_pad_blocks = 0; + + // the number of pad blocks that we already have + int m_have_pad_blocks = 0; + + // the number of pad blocks part of filtered pieces we don't have + int m_filtered_pad_blocks = 0; + + // the number of pad blocks we have that are also filtered + int m_have_filtered_pad_blocks = 0; + + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds = 0; + + // the number of pieces that have passed the hash check + int m_num_passed = 0; + + // this vector contains all piece indices that are pickable + // sorted by priority. Pieces are in random random order + // among pieces with the same priority + mutable aux::vector m_pieces; + + // these are indices to the priority boundaries inside + // the m_pieces vector. priority 0 always start at + // 0, priority 1 starts at m_priority_boundaries[0] etc. + mutable aux::vector m_priority_boundaries; + + // each piece that's currently being downloaded has an entry in this list + // with block allocations. i.e. it says which parts of the piece that is + // being downloaded. This list is ordered by piece index to make lookups + // efficient there are as many buckets as there are piece states. See + // piece_pos::state_t. The only download state that does not have a + // corresponding downloading_piece vector is piece_open and + // piece_downloading_reverse (the latter uses the same as + // piece_downloading). + aux::array + , static_cast(piece_pos::num_download_categories) + , download_queue_t> m_downloads; + + // this holds the information of the blocks in partially downloaded + // pieces. the downloading_piece::info index point into this vector for + // its storage + aux::vector m_block_info; + + // these are block ranges in m_block_info that are free. The numbers + // in here, when multiplied by m_blocks_per_piece is the index to the + // first block in the range that's free to use by a new downloading_piece. + // this is a free-list. + std::vector m_free_block_infos; + + std::uint16_t m_blocks_per_piece = 0; + std::uint16_t m_blocks_in_last_piece = 0; + + // the number of filtered pieces that we don't already + // have. total_number_of_pieces - number_of_pieces_we_have + // - num_filtered is supposed to the number of pieces + // we still want to download + // TODO: it would be more intuitive to account "wanted" pieces + // instead of filtered + int m_num_filtered = 0; + + // the number of pieces we have that also are filtered + int m_num_have_filtered = 0; + + // we have all pieces in the range [0, m_cursor) + // m_cursor is the first piece we don't have + piece_index_t m_cursor{0}; + + // we have all pieces in the range [m_reverse_cursor, end) + // m_reverse_cursor is the first piece where we also have + // all the subsequent pieces + piece_index_t m_reverse_cursor{0}; + + // the number of pieces we have (i.e. passed + flushed). + // This includes pieces that we have filtered but still have + int m_num_have = 0; + + // if this is set to true, it means update_pieces() + // has to be called before accessing m_pieces. + mutable bool m_dirty = false; + public: + + enum { max_pieces = (std::numeric_limits::max)() - 1 }; + + }; +} + +#endif // TORRENT_PIECE_PICKER_HPP_INCLUDED diff --git a/include/libtorrent/platform_util.hpp b/include/libtorrent/platform_util.hpp new file mode 100644 index 0000000..a988fd8 --- /dev/null +++ b/include/libtorrent/platform_util.hpp @@ -0,0 +1,13 @@ +#ifndef TORRENT_PLATFORM_UTIL_HPP +#define TORRENT_PLATFORM_UTIL_HPP + +#include + +namespace libtorrent { + + int max_open_files(); + + std::int64_t total_physical_ram(); +} + +#endif // TORRENT_PLATFORM_UTIL_HPP diff --git a/include/libtorrent/portmap.hpp b/include/libtorrent/portmap.hpp new file mode 100644 index 0000000..9a1ddfd --- /dev/null +++ b/include/libtorrent/portmap.hpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2003-2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PORTMAP_HPP_INCLUDED +#define TORRENT_PORTMAP_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/units.hpp" + +namespace libtorrent { + + enum class portmap_transport : std::uint8_t + { + // natpmp can be NAT-PMP or PCP + natpmp, upnp + }; + + enum class portmap_protocol : std::uint8_t + { + none, tcp, udp + }; + + // this type represents an index referring to a port mapping + using port_mapping_t = aux::strong_typedef; + +} + +#endif //TORRENT_PORTMAP_HPP_INCLUDED diff --git a/include/libtorrent/proxy_base.hpp b/include/libtorrent/proxy_base.hpp new file mode 100644 index 0000000..d766654 --- /dev/null +++ b/include/libtorrent/proxy_base.hpp @@ -0,0 +1,280 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PROXY_BASE_HPP_INCLUDED +#define TORRENT_PROXY_BASE_HPP_INCLUDED + +#include "libtorrent/io.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent { + +class proxy_base +{ +public: + + using handler_type = std::function; + + using next_layer_type = tcp::socket; + using lowest_layer_type = tcp::socket::lowest_layer_type; + using endpoint_type = tcp::socket::endpoint_type; + using protocol_type = tcp::socket::protocol_type; + + explicit proxy_base(io_service& io_service); + ~proxy_base(); + proxy_base(proxy_base const&) = delete; + proxy_base& operator=(proxy_base const&) = delete; + + void set_proxy(std::string hostname, int port) + { + m_hostname = hostname; + m_port = port; + } + +#if BOOST_VERSION >= 106600 + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_sock.get_executor(); } +#endif + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + m_sock.async_read_some(buffers, handler); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + return m_sock.read_some(buffers, ec); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + return m_sock.write_some(buffers, ec); + } + + std::size_t available(error_code& ec) const + { return m_sock.available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return m_sock.available(); } + + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock.read_some(buffers); + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + return m_sock.write_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock.io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + m_sock.io_control(ioc, ec); + } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + m_sock.async_write_some(buffers, handler); + } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) + { + m_sock.non_blocking(b); + } +#endif + + error_code non_blocking(bool b, error_code& ec) + { + return m_sock.non_blocking(b, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.set_option(opt); + } +#endif + + template + error_code set_option(SettableSocketOption const& opt, error_code& ec) + { + return m_sock.set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock.get_option(opt); + } +#endif + + template + error_code get_option(GettableSocketOption& opt, error_code& ec) + { + return m_sock.get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /* endpoint */) + { +// m_sock.bind(endpoint); + } +#endif + + error_code cancel(error_code& ec) + { + return m_sock.cancel(ec); + } + + void bind(endpoint_type const& /* endpoint */, error_code& /* ec */) + { + // the reason why we ignore binds here is because we don't + // (necessarily) yet know what address family the proxy + // will resolve to, and binding to the wrong one would + // break our connection attempt later. The caller here + // doesn't necessarily know that we're proxying, so this + // bind address is based on the final endpoint, not the + // proxy. + // TODO: it would be nice to remember the bind port and bind once we know where the proxy is +// m_sock.bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const&) + { +// m_sock.open(p); + } +#endif + + void open(protocol_type const&, error_code&) + { + // we need to ignore this for the same reason as stated + // for ignoring bind() +// m_sock.open(p, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_remote_endpoint = endpoint_type(); + m_sock.close(); + m_resolver.cancel(); + } +#endif + + void close(error_code& ec) + { + m_remote_endpoint = endpoint_type(); + m_sock.close(ec); + m_resolver.cancel(); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return m_remote_endpoint; + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + if (!m_sock.is_open()) ec = boost::asio::error::not_connected; + return m_remote_endpoint; + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_sock.local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return m_sock.local_endpoint(ec); + } + + io_service& get_io_service() + { + return lt::get_io_service(m_sock); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock; + } + + bool is_open() const { return m_sock.is_open(); } + +protected: + + bool handle_error(error_code const& e, handler_type const& h); + + tcp::socket m_sock; + std::string m_hostname; // proxy host + int m_port; // proxy port + + endpoint_type m_remote_endpoint; + + // TODO: 2 use the resolver interface that has a built-in cache + tcp::resolver m_resolver; +}; + +} + +#endif diff --git a/include/libtorrent/puff.hpp b/include/libtorrent/puff.hpp new file mode 100644 index 0000000..9aa5911 --- /dev/null +++ b/include/libtorrent/puff.hpp @@ -0,0 +1,35 @@ +/* puff.h + Copyright (C) 2002, 2003 Mark Adler, all rights reserved + version 1.7, 3 Mar 2002 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +#ifndef PUFF_HPP_INCLUDED +#define PUFF_HPP_INCLUDED + +/* + * See puff.c for purpose and usage. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ + +#endif // PUFF_HPP_INCLUDED diff --git a/include/libtorrent/random.hpp b/include/libtorrent/random.hpp new file mode 100644 index 0000000..2fc6bb2 --- /dev/null +++ b/include/libtorrent/random.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RANDOM_HPP_INCLUDED +#define TORRENT_RANDOM_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +#include +#include +#include + +namespace libtorrent { +namespace aux { + + TORRENT_EXTRA_EXPORT std::mt19937& random_engine(); + + template + void random_shuffle(Range& range) + { + std::shuffle(range.data(), range.data() + range.size(), random_engine()); + } + + // Fills the buffer with random bytes. + // + // This functions perform differently under different setups + // For Windows and all platforms when compiled with libcrypto, it + // generates cryptographically random bytes. + // If the above conditions are not true, then a standard + // fill of bytes is used. + TORRENT_EXTRA_EXPORT void random_bytes(span buffer); +} + + TORRENT_EXTRA_EXPORT std::uint32_t random(std::uint32_t m); +} + +#endif // TORRENT_RANDOM_HPP_INCLUDED diff --git a/include/libtorrent/read_resume_data.hpp b/include/libtorrent/read_resume_data.hpp new file mode 100644 index 0000000..3db37a5 --- /dev/null +++ b/include/libtorrent/read_resume_data.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_READ_RESUME_DATA_HPP_INCLUDE +#define TORRENT_READ_RESUME_DATA_HPP_INCLUDE + +#include "libtorrent/fwd.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { + + // these functions are used to parse resume data and populate the appropriate + // fields in an add_torrent_params object. This object can then be used to add + // the actual torrent_info object to and pass to session::add_torrent() or + // session::async_add_torrent(). + // + // If the client wants to override any field that was loaded from the resume + // data, e.g. save_path, those fields must be changed after loading resume + // data but before adding the torrent. + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd + , error_code& ec); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer + , error_code& ec); + TORRENT_EXPORT add_torrent_params read_resume_data(bdecode_node const& rd); + TORRENT_EXPORT add_torrent_params read_resume_data(span buffer); +} + +#endif diff --git a/include/libtorrent/receive_buffer.hpp b/include/libtorrent/receive_buffer.hpp new file mode 100644 index 0000000..023b155 --- /dev/null +++ b/include/libtorrent/receive_buffer.hpp @@ -0,0 +1,224 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED +#define TORRENT_RECEIVE_BUFFER_HPP_INCLUDED + +#include "libtorrent/buffer.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT receive_buffer +{ + friend struct crypto_receive_buffer; + + int packet_size() const { return m_packet_size; } + int packet_bytes_remaining() const + { + TORRENT_ASSERT(m_recv_start == 0); + TORRENT_ASSERT(m_packet_size > 0); + return m_packet_size - m_recv_pos; + } + + int max_receive() const; + + bool packet_finished() const { return m_packet_size <= m_recv_pos; } + int pos() const { return m_recv_pos; } + int capacity() const { return aux::numeric_cast(m_recv_buffer.size()); } + int watermark() const { return aux::numeric_cast(m_watermark.mean()); } + + span reserve(int size); + void grow(int limit); + + // tell the buffer we just received more bytes at the end of it. This will + // advance the end cursor + void received(int bytes_transferred) + { + TORRENT_ASSERT(m_packet_size > 0); + m_recv_end += bytes_transferred; + TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size())); + } + + // tell the buffer we consumed some bytes of it. This will advance the read + // cursor + int advance_pos(int bytes); + + // has the read cursor reached the end cursor? + bool pos_at_end() { return m_recv_pos == m_recv_end; } + + // size = the packet size to remove from the receive buffer + // packet_size = the next packet size to receive in the buffer + // offset = the offset into the receive buffer where to remove `size` bytes + void cut(int size, int packet_size, int offset = 0); + + // return the interval between the start of the buffer to the read cursor. + // This is the "current" packet. + span get() const; + +#if !defined TORRENT_DISABLE_ENCRYPTION + // returns the buffer from the current packet start position to the last + // received byte (possibly part of another packet) + span mutable_buffer(); + + // returns the last 'bytes' from the receive buffer + span mutable_buffer(int bytes); +#endif + + // the purpose of this function is to free up and cut off all messages + // in the receive buffer that have been parsed and processed. + void normalize(int force_shrink = 0); + bool normalized() const { return m_recv_start == 0; } + + void reset(int packet_size); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const + { + TORRENT_ASSERT(m_recv_end >= m_recv_start); + TORRENT_ASSERT(m_recv_end <= int(m_recv_buffer.size())); + TORRENT_ASSERT(m_recv_start <= int(m_recv_buffer.size())); + TORRENT_ASSERT(m_recv_start + m_recv_pos <= int(m_recv_buffer.size())); + } +#endif + +private: + // explicitly disallow assignment, to silence msvc warning + receive_buffer& operator=(receive_buffer const&); + + // m_recv_buffer.data() (start of actual receive buffer) + // | + // | m_recv_start (start of current packet) + // | | + // | | m_recv_pos (number of bytes consumed + // | | | by upper layer, from logical receive buffer) + // | | | + // | x---------x + // | | | m_recv_buffer.size() (end of actual receive buffer) + // | | | | + // v v v v + // *------==========--------- + // ^ + // | + // | + // ------------------->x m_recv_end (end of received data, + // beyond this point is garbage) + // m_recv_buffer + + // the start of the logical receive buffer + int m_recv_start = 0; + + // the number of valid, received bytes in m_recv_buffer + int m_recv_end = 0; + + // the byte offset in m_recv_buffer that we have + // are passing on to the upper layer. This is + // always <= m_recv_end + int m_recv_pos = 0; + + // the size (in bytes) of the bittorrent message + // we're currently receiving + int m_packet_size = 0; + + // keep track of how much of the receive buffer we use, if we're not using + // enough of it we shrink it + sliding_average m_watermark; + + buffer m_recv_buffer; +}; + +#if !defined TORRENT_DISABLE_ENCRYPTION +// Wraps a receive_buffer to provide the ability to inject +// possibly authenticated crypto beneath the bittorrent protocol. +// When authenticated crypto is in use the wrapped receive_buffer +// holds the receive state of the crypto layer while this class +// tracks the state of the bittorrent protocol. +struct crypto_receive_buffer +{ + explicit crypto_receive_buffer(receive_buffer& next) + : m_connection_buffer(next) + {} + + span mutable_buffer() { return m_connection_buffer.mutable_buffer(); } + + bool packet_finished() const; + + bool crypto_packet_finished() const + { + return m_recv_pos == (std::numeric_limits::max)() + || m_connection_buffer.packet_finished(); + } + + int packet_size() const; + + int crypto_packet_size() const + { + TORRENT_ASSERT(m_recv_pos != (std::numeric_limits::max)()); + return m_connection_buffer.packet_size() - m_recv_pos; + } + + int pos() const; + + void cut(int size, int packet_size, int offset = 0); + + void crypto_cut(int size, int packet_size) + { + TORRENT_ASSERT(m_recv_pos != (std::numeric_limits::max)()); + m_connection_buffer.cut(size, m_recv_pos + packet_size, m_recv_pos); + } + + void reset(int packet_size); + void crypto_reset(int packet_size); + + int advance_pos(int bytes); + + span get() const; + + span mutable_buffer(int bytes); + +private: + // explicitly disallow assignment, to silence msvc warning + crypto_receive_buffer& operator=(crypto_receive_buffer const&); + + int m_recv_pos = (std::numeric_limits::max)(); + int m_packet_size = 0; + receive_buffer& m_connection_buffer; +}; +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace libtorrent + +#endif // #ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED diff --git a/include/libtorrent/request_blocks.hpp b/include/libtorrent/request_blocks.hpp new file mode 100644 index 0000000..0470b76 --- /dev/null +++ b/include/libtorrent/request_blocks.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + class torrent; + class peer_connection; + + // returns false if the piece picker was not invoked, because + // of an early exit condition. In this case, the stats counter + // shouldn't be incremented, since it won't use any significant + // amount of CPU + bool request_a_block(torrent& t, peer_connection& c); + + // returns the rank of a peer's source. We have an affinity + // to connecting to peers with higher rank. This is to avoid + // problems when our peer list is diluted by stale peers from + // the resume data for instance + int source_rank(peer_source_flags_t source_bitmask); +} + +#endif diff --git a/include/libtorrent/resolve_links.hpp b/include/libtorrent/resolve_links.hpp new file mode 100644 index 0000000..e5775aa --- /dev/null +++ b/include/libtorrent/resolve_links.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVE_LINKS_HPP +#define TORRENT_RESOLVE_LINKS_HPP + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + class torrent_info; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + // this class is used for mutable torrents, to discover identical files + // in other torrents. + struct TORRENT_EXTRA_EXPORT resolve_links + { + struct TORRENT_EXTRA_EXPORT link_t + { + std::shared_ptr ti; + std::string save_path; + file_index_t file_idx; + }; + + explicit resolve_links(std::shared_ptr ti); + + // check to see if any files are shared with this torrent + void match(std::shared_ptr const& ti + , std::string const& save_path); + + aux::vector const& get_links() const + { return m_links; } + + private: + // this is the torrent we're trying to find files for. + std::shared_ptr m_torrent_file; + + // each file in m_torrent_file has an entry in this vector. Any file + // that also exists somewhere else, is filled in with the corresponding + // torrent_info object and file index + aux::vector m_links; + + // maps file size to file index, in m_torrent_file + std::unordered_multimap m_file_sizes; + }; +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} + +#endif diff --git a/include/libtorrent/resolver.hpp b/include/libtorrent/resolver.hpp new file mode 100644 index 0000000..520b394 --- /dev/null +++ b/include/libtorrent/resolver.hpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVER_HPP_INCLUDE +#define TORRENT_RESOLVER_HPP_INCLUDE + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + +struct TORRENT_EXTRA_EXPORT resolver final : resolver_interface +{ + explicit resolver(io_service& ios); + + void async_resolve(std::string const& host, resolver_flags flags + , callback_t h) override; + + void abort() override; + + void set_cache_timeout(seconds timeout) override; + +private: + + void on_lookup(error_code const& ec, tcp::resolver::iterator i + , std::string const& hostname); + + void callback(resolver_interface::callback_t h + , error_code const& ec, std::vector
    const& ips); + + struct dns_cache_entry + { + time_point last_seen; + std::vector
    addresses; + }; + + std::unordered_map m_cache; + io_service& m_ios; + + // all lookups in this resolver are aborted on shutdown. + tcp::resolver m_resolver; + + // lookups in this resolver are not aborted on shutdown + tcp::resolver m_critical_resolver; + + // max number of cached entries + int m_max_size; + + // timeout of cache entries + time_duration m_timeout; + + // the callbacks to call when a host resolution completes. This allows to + // attach more callbacks if the same host is looked up mutliple times + std::multimap m_callbacks; +}; + +} + +#endif diff --git a/include/libtorrent/resolver_interface.hpp b/include/libtorrent/resolver_interface.hpp new file mode 100644 index 0000000..7145bc1 --- /dev/null +++ b/include/libtorrent/resolver_interface.hpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RESOLVER_INTERFACE_HPP_INCLUDE +#define TORRENT_RESOLVER_INTERFACE_HPP_INCLUDE + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +// hidden +using resolver_flags = flags::bitfield_flag; + +struct TORRENT_EXTRA_EXPORT resolver_interface +{ + using callback_t = std::function const&)>; + + // this flag will make async_resolve() only use the cache and fail if we + // don't have a cache entry, regardless of how old it is. This is usefull + // when completing the lookup quickly is more important than accuracy, + // like on shutdown + static constexpr resolver_flags cache_only = 0_bit; + + // set this flag for lookups that are not critical during shutdown. i.e. + // for looking up tracker names _except_ when stopping a tracker. + static constexpr resolver_flags abort_on_shutdown = 1_bit; + + virtual void async_resolve(std::string const& host, resolver_flags flags + , callback_t h) = 0; + + virtual void abort() = 0; + + virtual void set_cache_timeout(seconds timeout) = 0; + +protected: + ~resolver_interface() {} +}; + +} + +#endif diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp new file mode 100644 index 0000000..d19cb17 --- /dev/null +++ b/include/libtorrent/session.hpp @@ -0,0 +1,380 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HPP_INCLUDED +#define TORRENT_SESSION_HPP_INCLUDED + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/session_handle.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/kademlia/dht_state.hpp" +#include "libtorrent/kademlia/dht_storage.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/fingerprint.hpp" +#include // for snprintf +#endif + +namespace libtorrent { + + struct plugin; + + // The default values of the session settings are set for a regular + // bittorrent client running on a desktop system. There are functions that + // can set the session settings to pre set settings for other environments. + // These can be used for the basis, and should be tweaked to fit your needs + // better. + // + // ``min_memory_usage`` returns settings that will use the minimal amount of + // RAM, at the potential expense of upload and download performance. It + // adjusts the socket buffer sizes, disables the disk cache, lowers the send + // buffer watermarks so that each connection only has at most one block in + // use at any one time. It lowers the outstanding blocks send to the disk + // I/O thread so that connections only have one block waiting to be flushed + // to disk at any given time. It lowers the max number of peers in the peer + // list for torrents. It performs multiple smaller reads when it hashes + // pieces, instead of reading it all into memory before hashing. + // + // This configuration is intended to be the starting point for embedded + // devices. It will significantly reduce memory usage. + // + // ``high_performance_seed`` returns settings optimized for a seed box, + // serving many peers and that doesn't do any downloading. It has a 128 MB + // disk cache and has a limit of 400 files in its file pool. It support fast + // upload rates by allowing large send buffers. + TORRENT_EXPORT settings_pack min_memory_usage(); + TORRENT_EXPORT settings_pack high_performance_seed(); +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline void min_memory_usage(settings_pack& set) + { set = min_memory_usage(); } + TORRENT_DEPRECATED + inline void high_performance_seed(settings_pack& set) + { set = high_performance_seed(); } +#endif + +namespace aux { + + struct session_impl; +} + + struct disk_interface; + struct counters; + + // this is a holder for the internal session implementation object. Once the + // session destruction is explicitly initiated, this holder is used to + // synchronize the completion of the shutdown. The lifetime of this object + // may outlive session, causing the session destructor to not block. The + // session_proxy destructor will block however, until the underlying session + // is done shutting down. + class TORRENT_EXPORT session_proxy + { + friend class session; + public: + // default constructor, does not refer to any session + // implementation object. + session_proxy(); + ~session_proxy(); + session_proxy(session_proxy const&); + session_proxy& operator=(session_proxy const&); + session_proxy(session_proxy&&) noexcept; + session_proxy& operator=(session_proxy&&) noexcept; + private: + session_proxy( + std::shared_ptr ios + , std::shared_ptr t + , std::shared_ptr impl); + + std::shared_ptr m_io_service; + std::shared_ptr m_thread; + std::shared_ptr m_impl; + }; + + // The session_params is a parameters pack for configuring the session + // before it's started. + struct TORRENT_EXPORT session_params + { + // This constructor can be used to start with the default plugins + // (ut_metadata, ut_pex and smart_ban). The default values in the + // settings is to start the default features like upnp, NAT-PMP, + // and dht for example. + explicit session_params(settings_pack&& sp); + explicit session_params(settings_pack const& sp); + session_params(); + + // This constructor helps to configure the set of initial plugins + // to be added to the session before it's started. + session_params(settings_pack&& sp + , std::vector> exts); + session_params(settings_pack const& sp + , std::vector> exts); + + // hidden + session_params(session_params const&) = default; + session_params(session_params&&) = default; + session_params& operator=(session_params const&) = default; + session_params& operator=(session_params&&) = default; + + settings_pack settings; + + std::vector> extensions; + + dht::dht_settings dht_settings; + + dht::dht_state dht_state; + + dht::dht_storage_constructor_type dht_storage_constructor; + }; + + // This function helps to construct a ``session_params`` from a + // bencoded data generated by ``session_handle::save_state`` + TORRENT_EXPORT session_params read_session_params(bdecode_node const& e + , save_state_flags_t flags = save_state_flags_t::all()); + + // The session holds all state that spans multiple torrents. Among other + // things it runs the network loop and manages all torrents. Once it's + // created, the session object will spawn the main thread that will do all + // the work. The main thread will be idle as long it doesn't have any + // torrents to participate in. + // + // You have some control over session configuration through the + // ``session_handle::apply_settings()`` member function. To change one or more + // configuration options, create a settings_pack. object and fill it with + // the settings to be set and pass it in to ``session::apply_settings()``. + // + // see apply_settings(). + class TORRENT_EXPORT session : public session_handle + { + public: + + // Constructs the session objects which acts as the container of torrents. + // In order to avoid a race condition between starting the session and + // configuring it, you can pass in a session_params object. Its settings + // will take effect before the session starts up. + explicit session(session_params const& params) + { start(session_params(params), nullptr); } + explicit session(session_params&& params) + { start(std::move(params), nullptr); } + session() + { + session_params params; + start(std::move(params), nullptr); + } + + // Overload of the constructor that takes an external io_service to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_service, or low resource + // systems where additional threads are expensive and sharing an + // io_service with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_service``. The ``io_service::run()`` call _must_ have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_service, then + // destruct the session_proxy object. + session(session_params&& params, io_service& ios) + { start(std::move(params), &ios); } + session(session_params const& params, io_service& ios) + { start(session_params(params), &ios); } + + // Constructs the session objects which acts as the container of torrents. + // It provides configuration options across torrents (such as rate limits, + // disk cache, ip filter etc.). In order to avoid a race condition between + // starting the session and configuring it, you can pass in a + // settings_pack object. Its settings will take effect before the session + // starts up. + // + // The ``flags`` parameter can be used to start default features (UPnP & + // NAT-PMP) and default plugins (ut_metadata, ut_pex and smart_ban). The + // default is to start those features. If you do not want them to start, + // pass 0 as the flags parameter. + session(settings_pack&& pack + , session_flags_t const flags = add_default_plugins) + { start(flags, std::move(pack), nullptr); } + session(settings_pack const& pack + , session_flags_t const flags = add_default_plugins) + { start(flags, settings_pack(pack), nullptr); } + + // movable + session(session&&) = default; + session& operator=(session&&) = default; + + // non-copyable + session(session const&) = delete; + session& operator=(session const&) = delete; + + // overload of the constructor that takes an external io_service to run + // the session object on. This is primarily useful for tests that may want + // to run multiple sessions on a single io_service, or low resource + // systems where additional threads are expensive and sharing an + // io_service with other events is fine. + // + // .. warning:: + // The session object does not cleanly terminate with an external + // ``io_service``. The ``io_service::run()`` call _must_ have returned + // before it's safe to destruct the session. Which means you *MUST* + // call session::abort() and save the session_proxy first, then + // destruct the session object, then sync with the io_service, then + // destruct the session_proxy object. + session(settings_pack&& pack + , io_service& ios + , session_flags_t const flags = add_default_plugins) + { start(flags, std::move(pack), &ios); } + session(settings_pack const& pack + , io_service& ios + , session_flags_t const flags = add_default_plugins) + { start(flags, settings_pack(pack), &ios); } + +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable: 4996) +#endif + TORRENT_DEPRECATED + session(fingerprint const& print + , session_flags_t const flags = start_default_features | add_default_plugins + , alert_category_t const alert_mask = alert_category::error) + { + settings_pack pack; + pack.set_int(settings_pack::alert_mask, int(alert_mask)); + pack.set_str(settings_pack::peer_fingerprint, print.to_string()); + if (!(flags & start_default_features)) + { + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); + } + + start(flags, std::move(pack), nullptr); + } + + TORRENT_DEPRECATED + session(fingerprint const& print + , std::pair listen_port_range + , char const* listen_interface = "0.0.0.0" + , session_flags_t const flags = start_default_features | add_default_plugins + , alert_category_t const alert_mask = alert_category::error) + { + TORRENT_ASSERT(listen_port_range.first > 0); + TORRENT_ASSERT(listen_port_range.first <= listen_port_range.second); + + settings_pack pack; + pack.set_int(settings_pack::alert_mask, int(alert_mask)); + pack.set_int(settings_pack::max_retry_port_bind, listen_port_range.second - listen_port_range.first); + pack.set_str(settings_pack::peer_fingerprint, print.to_string()); + char if_string[100]; + + if (listen_interface == nullptr) listen_interface = "0.0.0.0"; + std::snprintf(if_string, sizeof(if_string), "%s:%d", listen_interface, listen_port_range.first); + pack.set_str(settings_pack::listen_interfaces, if_string); + + if (!(flags & start_default_features)) + { + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); + } + start(flags, std::move(pack), nullptr); + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif // TORRENT_ABI_VERSION + + // The destructor of session will notify all trackers that our torrents + // have been shut down. If some trackers are down, they will time out. + // All this before the destructor of session returns. So, it's advised + // that any kind of interface (such as windows) are closed before + // destructing the session object. Because it can take a few second for + // it to finish. The timeout can be set with apply_settings(). + ~session(); + + // In case you want to destruct the session asynchronously, you can + // request a session destruction proxy. If you don't do this, the + // destructor of the session object will block while the trackers are + // contacted. If you keep one ``session_proxy`` to the session when + // destructing it, the destructor will not block, but start to close down + // the session, the destructor of the proxy will then synchronize the + // threads. So, the destruction of the session is performed from the + // ``session`` destructor call until the ``session_proxy`` destructor + // call. The ``session_proxy`` does not have any operations on it (since + // the session is being closed down, no operations are allowed on it). + // The only valid operation is calling the destructor:: + // + // class session_proxy + // { + // public: + // session_proxy(); + // ~session_proxy() + // }; + session_proxy abort(); + + private: + + void start(session_params&& params, io_service* ios); + void start(session_flags_t flags, settings_pack&& sp, io_service* ios); + + void start(session_params const& params, io_service* ios) = delete; + void start(session_flags_t flags, settings_pack const& sp, io_service* ios) = delete; + + // data shared between the main thread + // and the working thread + std::shared_ptr m_io_service; + std::shared_ptr m_thread; + std::shared_ptr m_impl; + }; + +} + +#endif // TORRENT_SESSION_HPP_INCLUDED diff --git a/include/libtorrent/session_handle.hpp b/include/libtorrent/session_handle.hpp new file mode 100644 index 0000000..89d6ef8 --- /dev/null +++ b/include/libtorrent/session_handle.hpp @@ -0,0 +1,1133 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_HANDLE_HPP_INCLUDED +#define TORRENT_SESSION_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/disk_io_thread.hpp" // for cached_piece_info +#include "libtorrent/alert.hpp" // alert_category::error +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/session_types.hpp" +#include "libtorrent/portmap.hpp" // for portmap_protocol + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/kademlia/announce_flags.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/session_settings.hpp" +#include +#endif + +namespace libtorrent { + + class torrent; + +#if TORRENT_ABI_VERSION == 1 + struct session_status; + using user_load_function_t = std::function&, error_code&)>; +#endif + + // this class provides a non-owning handle to a session and a subset of the + // interface of the session class. If the underlying session is destructed + // any handle to it will no longer be valid. is_valid() will return false and + // any operation on it will throw a system_error exception, with error code + // invalid_session_handle. + struct TORRENT_EXPORT session_handle + { + friend class session; + friend struct aux::session_impl; + + // hidden + session_handle() = default; + session_handle(session_handle const& t) = default; + session_handle(session_handle&& t) noexcept = default; + session_handle& operator=(session_handle const&) = default; + session_handle& operator=(session_handle&&) noexcept = default; + +#if TORRENT_ABI_VERSION == 1 + using save_state_flags_t = libtorrent::save_state_flags_t; + using session_flags_t = libtorrent::session_flags_t; +#endif + + // returns true if this handle refers to a valid session object. If the + // session has been destroyed, all session_handle objects will expire and + // not be valid. + bool is_valid() const { return !m_impl.expired(); } + + // saves settings (i.e. the settings_pack) + static constexpr save_state_flags_t save_settings = 0_bit; + + // saves dht_settings + static constexpr save_state_flags_t save_dht_settings = 1_bit; + + // saves dht state such as nodes and node-id, possibly accelerating + // joining the DHT if provided at next session startup. + static constexpr save_state_flags_t save_dht_state = 2_bit; + +#if TORRENT_ABI_VERSION == 1 + // save pe_settings + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_encryption_settings = 3_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_as_map = 4_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_proxy = 5_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_i2p_proxy = 6_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_dht_proxy = 7_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_peer_proxy = 8_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_web_proxy = 9_bit; + static constexpr save_state_flags_t TORRENT_DEPRECATED_MEMBER save_tracker_proxy = 10_bit; +#endif + + // loads and saves all session settings, including dht_settings, + // encryption settings and proxy settings. ``save_state`` writes all keys + // to the ``entry`` that's passed in, which needs to either not be + // initialized, or initialized as a dictionary. + // + // ``load_state`` expects a bdecode_node which can be built from a bencoded + // buffer with bdecode(). + // + // The ``flags`` argument is used to filter which parts of the session + // state to save or load. By default, all state is saved/restored (except + // for the individual torrents). + // + // When saving settings, there are two fields that are *not* loaded. + // ``peer_fingerprint`` and ``user_agent``. Those are left as configured + // by the ``session_settings`` passed to the session constructor or + // subsequently set via apply_settings(). + void save_state(entry& e, save_state_flags_t flags = save_state_flags_t::all()) const; + void load_state(bdecode_node const& e, save_state_flags_t flags = save_state_flags_t::all()); + + // .. note:: + // these calls are potentially expensive and won't scale well with + // lots of torrents. If you're concerned about performance, consider + // using ``post_torrent_updates()`` instead. + // + // ``get_torrent_status`` returns a vector of the torrent_status for + // every torrent which satisfies ``pred``, which is a predicate function + // which determines if a torrent should be included in the returned set + // or not. Returning true means it should be included and false means + // excluded. The ``flags`` argument is the same as to + // torrent_handle::status(). Since ``pred`` is guaranteed to be + // called for every torrent, it may be used to count the number of + // torrents of different categories as well. + // + // ``refresh_torrent_status`` takes a vector of torrent_status structs + // (for instance the same vector that was returned by + // get_torrent_status() ) and refreshes the status based on the + // ``handle`` member. It is possible to use this function by first + // setting up a vector of default constructed ``torrent_status`` objects, + // only initializing the ``handle`` member, in order to request the + // torrent status for multiple torrents in a single call. This can save a + // significant amount of time if you have a lot of torrents. + // + // Any torrent_status object whose ``handle`` member is not referring to + // a valid torrent are ignored. + // + // The intended use of these functions is to start off by calling + // ``get_torrent_status()`` to get a list of all torrents that match your + // criteria. Then call ``refresh_torrent_status()`` on that list. This + // will only refresh the status for the torrents in your list, and thus + // ignore all other torrents you might be running. This may save a + // significant amount of time, especially if the number of torrents you're + // interested in is small. In order to keep your list of interested + // torrents up to date, you can either call ``get_torrent_status()`` from + // time to time, to include torrents you might have become interested in + // since the last time. In order to stop refreshing a certain torrent, + // simply remove it from the list. + std::vector get_torrent_status( + std::function const& pred + , status_flags_t flags = {}) const; + void refresh_torrent_status(std::vector* ret + , status_flags_t flags = {}) const; + + // This functions instructs the session to post the state_update_alert, + // containing the status of all torrents whose state changed since the + // last time this function was called. + // + // Only torrents who has the state subscription flag set will be + // included. This flag is on by default. See add_torrent_params. + // the ``flags`` argument is the same as for torrent_handle::status(). + // see status_flags_t in torrent_handle. + void post_torrent_updates(status_flags_t flags = status_flags_t::all()); + + // This function will post a session_stats_alert object, containing a + // snapshot of the performance counters from the internals of libtorrent. + // To interpret these counters, query the session via + // session_stats_metrics(). + // + // For more information, see the session-statistics_ section. + void post_session_stats(); + + // This will cause a dht_stats_alert to be posted. + void post_dht_stats(); + + // internal + io_service& get_io_service(); + + // ``find_torrent()`` looks for a torrent with the given info-hash. In + // case there is such a torrent in the session, a torrent_handle to that + // torrent is returned. In case the torrent cannot be found, an invalid + // torrent_handle is returned. + // + // See ``torrent_handle::is_valid()`` to know if the torrent was found or + // not. + // + // ``get_torrents()`` returns a vector of torrent_handles to all the + // torrents currently in the session. + torrent_handle find_torrent(sha1_hash const& info_hash) const; + std::vector get_torrents() const; + + // You add torrents through the add_torrent() function where you give an + // object with all the parameters. The add_torrent() overloads will block + // until the torrent has been added (or failed to be added) and returns + // an error code and a torrent_handle. In order to add torrents more + // efficiently, consider using async_add_torrent() which returns + // immediately, without waiting for the torrent to add. Notification of + // the torrent being added is sent as add_torrent_alert. + // + // The overload that does not take an error_code throws an exception on + // error and is not available when building without exception support. + // The torrent_handle returned by add_torrent() can be used to retrieve + // information about the torrent's progress, its peers etc. It is also + // used to abort a torrent. + // + // If the torrent you are trying to add already exists in the session (is + // either queued for checking, being checked or downloading) + // ``add_torrent()`` will throw system_error which derives from + // ``std::exception`` unless duplicate_is_error is set to false. In that + // case, add_torrent() will return the handle to the existing torrent. + // + // The add_torrent_params class has a flags field. It can be used to + // control what state the new torrent will be added in. Common flags to + // want to control are torrent_flags::paused and + // torrent_flags::auto_managed. In order to add a magnet link that will + // just download the metadata, but no payload, set the + // torrent_flags::upload_mode flag. +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle add_torrent(add_torrent_params&& params); + torrent_handle add_torrent(add_torrent_params const& params); +#endif + torrent_handle add_torrent(add_torrent_params&& params, error_code& ec); + torrent_handle add_torrent(add_torrent_params const& params, error_code& ec); + void async_add_torrent(add_torrent_params&& params); + void async_add_torrent(add_torrent_params const& params); + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + torrent_info const& ti + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor); + + // deprecated in 0.14 + TORRENT_DEPRECATED + torrent_handle add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , std::string const& save_path + , entry const& resume_data = entry() + , storage_mode_t storage_mode = storage_mode_sparse + , bool paused = false + , storage_constructor_type sc = default_storage_constructor + , void* userdata = nullptr); +#endif // TORRENT_ABI_VERSION +#endif + + // Pausing the session has the same effect as pausing every torrent in + // it, except that torrents will not be resumed by the auto-manage + // mechanism. Resuming will restore the torrents to their previous paused + // state. i.e. the session pause state is separate from the torrent pause + // state. A torrent is inactive if it is paused or if the session is + // paused. + void pause(); + void resume(); + bool is_paused() const; + +#if TORRENT_ABI_VERSION == 1 + // *the feature of dynamically loading/unloading torrents is deprecated + // and discouraged* + // + // This function enables dynamic-loading-of-torrent-files_. When a + // torrent is unloaded but needs to be available in memory, this function + // is called **from within the libtorrent network thread**. From within + // this thread, you can **not** use any of the public APIs of libtorrent + // itself. The info-hash of the torrent is passed in to the function + // and it is expected to fill in the passed in ``vector`` with the + // .torrent file corresponding to it. + // + // If there is an error loading the torrent file, the ``error_code`` + // (``ec``) should be set to reflect the error. In such case, the torrent + // itself is stopped and set to an error state with the corresponding + // error code. + // + // Given that the function is called from the internal network thread of + // libtorrent, it's important to not stall. libtorrent will not be able + // to send nor receive any data until the function call returns. + // + // The signature of the function to pass in is:: + // + // void fun(sha1_hash const& info_hash, std::vector& buf, error_code& ec); + TORRENT_DEPRECATED + void set_load_function(user_load_function_t fun); + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // deprecated in libtorrent 1.1, use performance_counters instead + // returns session wide-statistics and status. For more information, see + // the ``session_status`` struct. + TORRENT_DEPRECATED + session_status status() const; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // deprecated in libtorrent 1.1 + // fills out the supplied vector with information for each piece that is + // currently in the disk cache for the torrent with the specified + // info-hash (``ih``). + TORRENT_DEPRECATED + void get_cache_info(sha1_hash const& ih + , std::vector& ret) const; + + // Returns status of the disk cache for this session. + // For more information, see the cache_status type. + TORRENT_DEPRECATED + cache_status get_cache_status() const; + + // deprecated in 1.2 + TORRENT_DEPRECATED + void get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t flags = {}) const; +#endif // TORRENT_ABI_VERSION + + enum { disk_cache_no_pieces = 1 }; + + // Fills in the cache_status struct with information about the given torrent. + // If ``flags`` is ``session::disk_cache_no_pieces`` the ``cache_status::pieces`` field + // will not be set. This may significantly reduce the cost of this call. + void get_cache_info(cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const; + +#if TORRENT_ABI_VERSION == 1 + // ``start_dht`` starts the dht node and makes the trackerless service + // available to torrents. + // + // ``stop_dht`` stops the dht node. + // deprecated. use settings_pack::enable_dht instead + TORRENT_DEPRECATED + void start_dht(); + TORRENT_DEPRECATED + void stop_dht(); +#endif + + // ``set_dht_settings`` sets some parameters available to the dht node. + // See dht_settings for more information. + // + // ``is_dht_running()`` returns true if the DHT support has been started + // and false + // otherwise. + // + // ``get_dht_settings()`` returns the current settings + void set_dht_settings(dht::dht_settings const& settings); + bool is_dht_running() const; + dht::dht_settings get_dht_settings() const; + + // ``set_dht_storage`` set a dht custom storage constructor function + // to be used internally when the dht is created. + // + // Since the dht storage is a critical component for the dht behavior, + // this function will only be effective the next time the dht is started. + // If you never touch this feature, a default map-memory based storage + // is used. + // + // If you want to make sure the dht is initially created with your + // custom storage, create a session with the setting + // ``settings_pack::enable_dht`` to false, set your constructor function + // and call ``apply_settings`` with ``settings_pack::enable_dht`` to true. + void set_dht_storage(dht::dht_storage_constructor_type sc); + + // ``add_dht_node`` takes a host name and port pair. That endpoint will be + // pinged, and if a valid DHT reply is received, the node will be added to + // the routing table. + void add_dht_node(std::pair const& node); + +#if TORRENT_ABI_VERSION == 1 + // deprecated, use settings_pack::dht_bootstrap_nodes instead + // + // ``add_dht_router`` adds the given endpoint to a list of DHT router + // nodes. If a search is ever made while the routing table is empty, + // those nodes will be used as backups. Nodes in the router node list + // will also never be added to the regular routing table, which + // effectively means they are only used for bootstrapping, to keep the + // load off them. + // + // An example routing node that you could typically add is + // ``router.bittorrent.com``. + TORRENT_DEPRECATED + void add_dht_router(std::pair const& node); +#endif + + // query the DHT for an immutable item at the ``target`` hash. + // the result is posted as a dht_immutable_item_alert. + void dht_get_item(sha1_hash const& target); + + // query the DHT for a mutable item under the public key ``key``. + // this is an ed25519 key. ``salt`` is optional and may be left + // as an empty string if no salt is to be used. + // if the item is found in the DHT, a dht_mutable_item_alert is + // posted. + void dht_get_item(std::array key + , std::string salt = std::string()); + + // store the given bencoded data as an immutable item in the DHT. + // the returned hash is the key that is to be used to look the item + // up again. It's just the SHA-1 hash of the bencoded form of the + // structure. + sha1_hash dht_put_item(entry data); + + // store a mutable item. The ``key`` is the public key the blob is + // to be stored under. The optional ``salt`` argument is a string that + // is to be mixed in with the key when determining where in the DHT + // the value is to be stored. The callback function is called from within + // the libtorrent network thread once we've found where to store the blob, + // possibly with the current value stored under the key. + // The values passed to the callback functions are: + // + // entry& value + // the current value stored under the key (may be empty). Also expected + // to be set to the value to be stored by the function. + // + // std::array& signature + // the signature authenticating the current value. This may be zeros + // if there is currently no value stored. The function is expected to + // fill in this buffer with the signature of the new value to store. + // To generate the signature, you may want to use the + // ``sign_mutable_item`` function. + // + // std::int64_t& seq + // current sequence number. May be zero if there is no current value. + // The function is expected to set this to the new sequence number of + // the value that is to be stored. Sequence numbers must be monotonically + // increasing. Attempting to overwrite a value with a lower or equal + // sequence number will fail, even if the signature is correct. + // + // std::string const& salt + // this is the salt that was used for this put call. + // + // Since the callback function ``cb`` is called from within libtorrent, + // it is critical to not perform any blocking operations. Ideally not + // even locking a mutex. Pass any data required for this function along + // with the function object's context and make the function entirely + // self-contained. The only reason data blob's value is computed + // via a function instead of just passing in the new value is to avoid + // race conditions. If you want to *update* the value in the DHT, you + // must first retrieve it, then modify it, then write it back. The way + // the DHT works, it is natural to always do a lookup before storing and + // calling the callback in between is convenient. + void dht_put_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt = std::string()); + + // ``dht_get_peers()`` will issue a DHT get_peer request to the DHT for the + // specified info-hash. The response (the peers) will be posted back in a + // dht_get_peers_reply_alert. + // + // ``dht_announce()`` will issue a DHT announce request to the DHT to the + // specified info-hash, advertising the specified port. If the port is + // left at its default, 0, the port will be implied by the DHT message's + // source port (which may improve connectivity through a NAT). + // + // Both these functions are exposed for advanced custom use of the DHT. + // All torrents eligible to be announce to the DHT will be automatically, + // by libtorrent. + // + // For possible flags, see announce_flags_t. + void dht_get_peers(sha1_hash const& info_hash); + void dht_announce(sha1_hash const& info_hash, int port = 0, dht::announce_flags_t flags = {}); + + // Retrieve all the live DHT (identified by ``nid``) nodes. All the + // nodes id and endpoint will be returned in the list of nodes in the + // alert ``dht_live_nodes_alert``. + // Since this alert is a response to an explicit call, it will always be + // posted, regardless of the alert mask. + void dht_live_nodes(sha1_hash const& nid); + + // Query the DHT node specified by ``ep`` to retrieve a sample of the + // info-hashes that the node currently have in their storage. + // The ``target`` is included for iterative lookups so that indexing nodes + // can perform a key space traversal with a single RPC per node by adjusting + // the target value for each RPC. It has no effect on the returned sample value. + // The result is posted as a ``dht_sample_infohashes_alert``. + void dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target); + + // Send an arbitrary DHT request directly to the specified endpoint. This + // function is intended for use by plugins. When a response is received + // or the request times out, a dht_direct_response_alert will be posted + // with the response (if any) and the userdata pointer passed in here. + // Since this alert is a response to an explicit call, it will always be + // posted, regardless of the alert mask. + void dht_direct_request(udp::endpoint const& ep, entry const& e, void* userdata = nullptr); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.15 + // use save_state and load_state instead + TORRENT_DEPRECATED + entry dht_state() const; + TORRENT_DEPRECATED + void start_dht(entry const& startup_state); +#endif + + // This function adds an extension to this session. The argument is a + // function object that is called with a ``torrent_handle`` and which should + // return a ``std::shared_ptr``. To write custom + // plugins, see `libtorrent plugins`_. For the typical bittorrent client + // all of these extensions should be added. The main plugins implemented + // in libtorrent are: + // + // uTorrent metadata + // Allows peers to download the metadata (.torrent files) from the swarm + // directly. Makes it possible to join a swarm with just a tracker and + // info-hash. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_ut_metadata_plugin); + // + // uTorrent peer exchange + // Exchanges peers between clients. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_ut_pex_plugin); + // + // smart ban plugin + // A plugin that, with a small overhead, can ban peers + // that sends bad data with very high accuracy. Should + // eliminate most problems on poisoned torrents. + // + // .. code:: c++ + // + // #include + // ses.add_extension(<::create_smart_ban_plugin); + // + // + // .. _`libtorrent plugins`: libtorrent_plugins.html + void add_extension(std::function( + torrent_handle const&, void*)> ext); + void add_extension(std::shared_ptr ext); + +#if TORRENT_ABI_VERSION == 1 + // GeoIP support has been removed from libtorrent internals. If you + // still need to resolve peers, please do so on the client side, using + // libgeoip directly. This was removed in libtorrent 1.1 + + // These functions expects a path to the `MaxMind ASN database`_ and + // `MaxMind GeoIP database`_ respectively. This will be used to look up + // which AS and country peers belong to. + // + // ``as_for_ip`` returns the AS number for the IP address specified. If + // the IP is not in the database or the ASN database is not loaded, 0 is + // returned. + // + // .. _`MaxMind ASN database`: http://www.maxmind.com/app/asnum + // .. _`MaxMind GeoIP database`: http://www.maxmind.com/app/geolitecountry + TORRENT_DEPRECATED + void load_asnum_db(char const* file); + TORRENT_DEPRECATED + void load_country_db(char const* file); + TORRENT_DEPRECATED + int as_for_ip(address const& addr); + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED + void load_country_db(wchar_t const* file); + TORRENT_DEPRECATED + void load_asnum_db(wchar_t const* file); + + // deprecated in 0.15 + // use load_state and save_state instead + TORRENT_DEPRECATED + void load_state(entry const& ses_state + , save_state_flags_t flags = save_state_flags_t::all()); + TORRENT_DEPRECATED + entry state() const; + // deprecated in 1.1 + TORRENT_DEPRECATED + void load_state(lazy_entry const& ses_state + , save_state_flags_t flags = save_state_flags_t::all()); +#endif // TORRENT_ABI_VERSION + + // Sets a filter that will be used to reject and accept incoming as well + // as outgoing connections based on their originating ip address. The + // default filter will allow connections to any ip address. To build a + // set of rules for which addresses are accepted and not, see ip_filter. + // + // Each time a peer is blocked because of the IP filter, a + // peer_blocked_alert is generated. ``get_ip_filter()`` Returns the + // ip_filter currently in the session. See ip_filter. + void set_ip_filter(ip_filter const& f); + ip_filter get_ip_filter() const; + + // apply port_filter ``f`` to incoming and outgoing peers. a port filter + // will reject making outgoing peer connections to certain remote ports. + // The main intention is to be able to avoid triggering certain + // anti-virus software by connecting to SMTP, FTP ports. + void set_port_filter(port_filter const& f); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.1, use settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + void set_peer_id(peer_id const& pid); + + // deprecated in 1.1.7. read settings_pack::peer_fingerprint instead + TORRENT_DEPRECATED + peer_id id() const; +#endif + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // sets the key sent to trackers. If it's not set, it is initialized + // by libtorrent. The key may be used by the tracker to identify the + // peer potentially across you changing your IP. + void set_key(std::uint32_t key); +#endif + + // built-in peer classes + static constexpr peer_class_t global_peer_class_id{0}; + static constexpr peer_class_t tcp_peer_class_id{1}; + static constexpr peer_class_t local_peer_class_id{2}; + + // ``is_listening()`` will tell you whether or not the session has + // successfully opened a listening port. If it hasn't, this function will + // return false, and then you can set a new + // settings_pack::listen_interfaces to try another interface and port to + // bind to. + // + // ``listen_port()`` returns the port we ended up listening on. + unsigned short listen_port() const; + unsigned short ssl_listen_port() const; + bool is_listening() const; + + // Sets the peer class filter for this session. All new peer connections + // will take this into account and be added to the peer classes specified + // by this filter, based on the peer's IP address. + // + // The ip-filter essentially maps an IP -> uint32. Each bit in that 32 + // bit integer represents a peer class. The least significant bit + // represents class 0, the next bit class 1 and so on. + // + // For more info, see ip_filter. + // + // For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 + // belong to their own peer class, apply the following filter: + // + // .. code:: c++ + // + // ip_filter f = ses.get_peer_class_filter(); + // peer_class_t my_class = ses.create_peer_class("200.1.x.x IP range"); + // f.add_rule(make_address("200.1.1.0"), make_address("200.1.255.255") + // , 1 << static_cast(my_class)); + // ses.set_peer_class_filter(f); + // + // This setting only applies to new connections, it won't affect existing + // peer connections. + // + // This function is limited to only peer class 0-31, since there are only + // 32 bits in the IP range mapping. Only the set bits matter; no peer + // class will be removed from a peer as a result of this call, peer + // classes are only added. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks + // representing peer classes in the ``peer_class_filter`` are 32 bits. + // + // The ``get_peer_class_filter()`` function returns the current filter. + // + // For more information, see peer-classes_. + void set_peer_class_filter(ip_filter const& f); + ip_filter get_peer_class_filter() const; + + // Sets and gets the *peer class type filter*. This is controls automatic + // peer class assignments to peers based on what kind of socket it is. + // + // It does not only support assigning peer classes, it also supports + // removing peer classes based on socket type. + // + // The order of these rules being applied are: + // + // 1. peer-class IP filter + // 2. peer-class type filter, removing classes + // 3. peer-class type filter, adding classes + // + // For more information, see peer-classes_. + void set_peer_class_type_filter(peer_class_type_filter const& f); + peer_class_type_filter get_peer_class_type_filter() const; + + // Creates a new peer class (see peer-classes_) with the given name. The + // returned integer is the new peer class identifier. Peer classes may + // have the same name, so each invocation of this function creates a new + // class and returns a unique identifier. + // + // Identifiers are assigned from low numbers to higher. So if you plan on + // using certain peer classes in a call to set_peer_class_filter(), + // make sure to create those early on, to get low identifiers. + // + // For more information on peer classes, see peer-classes_. + peer_class_t create_peer_class(char const* name); + + // This call dereferences the reference count of the specified peer + // class. When creating a peer class it's automatically referenced by 1. + // If you want to recycle a peer class, you may call this function. You + // may only call this function **once** per peer class you create. + // Calling it more than once for the same class will lead to memory + // corruption. + // + // Since peer classes are reference counted, this function will not + // remove the peer class if it's still assigned to torrents or peers. It + // will however remove it once the last peer and torrent drops their + // references to it. + // + // There is no need to call this function for custom peer classes. All + // peer classes will be properly destructed when the session object + // destructs. + // + // For more information on peer classes, see peer-classes_. + void delete_peer_class(peer_class_t cid); + + // These functions queries information from a peer class and updates the + // configuration of a peer class, respectively. + // + // ``cid`` must refer to an existing peer class. If it does not, the + // return value of ``get_peer_class()`` is undefined. + // + // ``set_peer_class()`` sets all the information in the + // peer_class_info object in the specified peer class. There is no + // option to only update a single property. + // + // A peer or torrent belonging to more than one class, the highest + // priority among any of its classes is the one that is taken into + // account. + // + // For more information, see peer-classes_. + peer_class_info get_peer_class(peer_class_t cid) const; + void set_peer_class(peer_class_t cid, peer_class_info const& pci); + +#if TORRENT_ABI_VERSION == 1 + // if the listen port failed in some way you can retry to listen on + // another port- range with this function. If the listener succeeded and + // is currently listening, a call to this function will shut down the + // listen port and reopen it using these new properties (the given + // interface and port range). As usual, if the interface is left as 0 + // this function will return false on failure. If it fails, it will also + // generate alerts describing the error. It will return true on success. + enum listen_on_flags_t + { + // this is always on starting with 0.16.2 + listen_reuse_address TORRENT_DEPRECATED_ENUM = 0x01, + listen_no_system_port TORRENT_DEPRECATED_ENUM = 0x02 + }; + + // deprecated in 0.16 + + // specify which interfaces to bind outgoing connections to + // This has been moved to a session setting + TORRENT_DEPRECATED + void use_interfaces(char const* interfaces); + + // instead of using this, specify listen interface and port in + // the settings_pack::listen_interfaces setting + TORRENT_DEPRECATED + void listen_on( + std::pair const& port_range + , error_code& ec + , const char* net_interface = nullptr + , int flags = 0); +#endif + + // delete the files belonging to the torrent from disk. + // including the part-file, if there is one + static constexpr remove_flags_t delete_files = 0_bit; + + // delete just the part-file associated with this torrent + static constexpr remove_flags_t delete_partfile = 1_bit; + + // this will add common extensions like ut_pex, ut_metadata, lt_tex + // smart_ban and possibly others. + static constexpr session_flags_t add_default_plugins = 0_bit; + +#if TORRENT_ABI_VERSION == 1 + // this will start features like DHT, local service discovery, UPnP + // and NAT-PMP. + static constexpr session_flags_t TORRENT_DEPRECATED_MEMBER start_default_features = 1_bit; +#endif + + // ``remove_torrent()`` will close all peer connections associated with + // the torrent and tell the tracker that we've stopped participating in + // the swarm. This operation cannot fail. When it completes, you will + // receive a torrent_removed_alert. + // + // The optional second argument ``options`` can be used to delete all the + // files downloaded by this torrent. To do so, pass in the value + // ``session_handle::delete_files``. The removal of the torrent is asynchronous, + // there is no guarantee that adding the same torrent immediately after + // it was removed will not throw a system_error exception. Once + // the torrent is deleted, a torrent_deleted_alert is posted. + // + // Note that when a queued or downloading torrent is removed, its position + // in the download queue is vacated and every subsequent torrent in the + // queue has their queue positions updated. This can potentially cause a + // large state_update to be posted. When removing all torrents, it is + // advised to remove them from the back of the queue, to minimize the + // shifting. + void remove_torrent(const torrent_handle& h, remove_flags_t options = {}); + + // Applies the settings specified by the settings_pack ``s``. This is an + // asynchronous operation that will return immediately and actually apply + // the settings to the main thread of libtorrent some time later. + void apply_settings(settings_pack const& s); + void apply_settings(settings_pack&& s); + settings_pack get_settings() const; + +#if TORRENT_ABI_VERSION == 1 + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED + void set_pe_settings(pe_settings const& settings); + TORRENT_DEPRECATED + pe_settings get_pe_settings() const; + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + // ``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistent + // connection to it. The only used fields in the proxy settings structs + // are ``hostname`` and ``port``. + // + // ``i2p_proxy`` returns the current i2p proxy in use. + // + // .. _i2p: http://www.i2p2.de + + TORRENT_DEPRECATED + void set_i2p_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings i2p_proxy() const; + + // These functions sets and queries the proxy settings to be used for the + // session. + // + // For more information on what settings are available for proxies, see + // proxy_settings. If the session is not in anonymous mode, proxies that + // aren't working or fail, will automatically be disabled and packets + // will flow without using any proxy. If you want to enforce using a + // proxy, even when the proxy doesn't work, enable anonymous_mode in + // settings_pack. + TORRENT_DEPRECATED + void set_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings proxy() const; + + // deprecated in 0.16 + // Get the number of uploads. + TORRENT_DEPRECATED + int num_uploads() const; + + // Get the number of connections. This number also contains the + // number of half open connections. + TORRENT_DEPRECATED + int num_connections() const; + + // deprecated in 0.15. + TORRENT_DEPRECATED + void set_peer_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_web_seed_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + void set_tracker_proxy(proxy_settings const& s); + + TORRENT_DEPRECATED + proxy_settings peer_proxy() const; + TORRENT_DEPRECATED + proxy_settings web_seed_proxy() const; + TORRENT_DEPRECATED + proxy_settings tracker_proxy() const; + + TORRENT_DEPRECATED + void set_dht_proxy(proxy_settings const& s); + TORRENT_DEPRECATED + proxy_settings dht_proxy() const; + + // deprecated in 0.16 + TORRENT_DEPRECATED + int upload_rate_limit() const; + TORRENT_DEPRECATED + int download_rate_limit() const; + TORRENT_DEPRECATED + int local_upload_rate_limit() const; + TORRENT_DEPRECATED + int local_download_rate_limit() const; + TORRENT_DEPRECATED + int max_half_open_connections() const; + + TORRENT_DEPRECATED + void set_local_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_local_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_upload_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_download_rate_limit(int bytes_per_second); + TORRENT_DEPRECATED + void set_max_uploads(int limit); + TORRENT_DEPRECATED + void set_max_connections(int limit); + TORRENT_DEPRECATED + void set_max_half_open_connections(int limit); + + TORRENT_DEPRECATED + int max_connections() const; + TORRENT_DEPRECATED + int max_uploads() const; + +#endif + + // Alerts is the main mechanism for libtorrent to report errors and + // events. ``pop_alerts`` fills in the vector passed to it with pointers + // to new alerts. The session still owns these alerts and they will stay + // valid until the next time ``pop_alerts`` is called. You may not delete + // the alert objects. + // + // It is safe to call ``pop_alerts`` from multiple different threads, as + // long as the alerts themselves are not accessed once another thread + // calls ``pop_alerts``. Doing this requires manual synchronization + // between the popping threads. + // + // ``wait_for_alert`` will block the current thread for ``max_wait`` time + // duration, or until another alert is posted. If an alert is available + // at the time of the call, it returns immediately. The returned alert + // pointer is the head of the alert queue. ``wait_for_alert`` does not + // pop alerts from the queue, it merely peeks at it. The returned alert + // will stay valid until ``pop_alerts`` is called twice. The first time + // will pop it and the second will free it. + // + // If there is no alert in the queue and no alert arrives within the + // specified timeout, ``wait_for_alert`` returns nullptr. + // + // In the python binding, ``wait_for_alert`` takes the number of + // milliseconds to wait as an integer. + // + // The alert queue in the session will not grow indefinitely. Make sure + // to pop periodically to not miss notifications. To control the max + // number of alerts that's queued by the session, see + // ``settings_pack::alert_queue_size``. + // + // Some alerts are considered so important that they are posted even when + // the alert queue is full. Some alerts are considered mandatory and cannot + // be disabled by the ``alert_mask``. For instance, + // save_resume_data_alert and save_resume_data_failed_alert are always + // posted, regardless of the alert mask. + // + // To control which alerts are posted, set the alert_mask + // (settings_pack::alert_mask). + // + // If the alert queue fills up to the point where alerts are dropped, this + // will be indicated by a alerts_dropped_alert, which contains a bitmask + // of which types of alerts were dropped. Generally it is a good idea to + // make sure the alert queue is large enough, the alert_mask doesn't have + // unnecessary categories enabled and to call pop_alert() frequently, to + // avoid alerts being dropped. + // + // the ``set_alert_notify`` function lets the client set a function object + // to be invoked every time the alert queue goes from having 0 alerts to + // 1 alert. This function is called from within libtorrent, it may be the + // main thread, or it may be from within a user call. The intention of + // of the function is that the client wakes up its main thread, to poll + // for more alerts using ``pop_alerts()``. If the notify function fails + // to do so, it won't be called again, until ``pop_alerts`` is called for + // some other reason. For instance, it could signal an eventfd, post a + // message to an HWND or some other main message pump. The actual + // retrieval of alerts should not be done in the callback. In fact, the + // callback should not block. It should not perform any expensive work. + // It really should just notify the main application thread. + // + // The type of an alert is returned by the polymorphic function + // ``alert::type()`` but can also be queries from a concrete type via + // ``T::alert_type``, as a static constant. + void pop_alerts(std::vector* alerts); + alert* wait_for_alert(time_duration max_wait); + void set_alert_notify(std::function const& fun); + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" + + TORRENT_DEPRECATED + void set_severity_level(alert::severity_t s); + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // use the setting instead + TORRENT_DEPRECATED + size_t set_alert_queue_size_limit(size_t queue_size_limit_); + + // Changes the mask of which alerts to receive. By default only errors + // are reported. ``m`` is a bitmask where each bit represents a category + // of alerts. + // + // ``get_alert_mask()`` returns the current mask; + // + // See category_t enum for options. + TORRENT_DEPRECATED + void set_alert_mask(std::uint32_t m); + TORRENT_DEPRECATED + std::uint32_t get_alert_mask() const; + + // Starts and stops Local Service Discovery. This service will broadcast + // the info-hashes of all the non-private torrents on the local network to + // look for peers on the same swarm within multicast reach. + // + // deprecated. use settings_pack::enable_lsd instead + TORRENT_DEPRECATED + void start_lsd(); + TORRENT_DEPRECATED + void stop_lsd(); + + // Starts and stops the UPnP service. When started, the listen port and + // the DHT port are attempted to be forwarded on local UPnP router + // devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and + // remove arbitrary port mappings. Mapping status is returned through the + // portmap_alert and the portmap_error_alert. The object will be valid + // until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_upnp instead + TORRENT_DEPRECATED + void start_upnp(); + TORRENT_DEPRECATED + void stop_upnp(); + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router through + // NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_natpmp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_natpmp instead + TORRENT_DEPRECATED + void start_natpmp(); + TORRENT_DEPRECATED + void stop_natpmp(); +#endif + + // protocols used by add_port_mapping() + static constexpr portmap_protocol udp = portmap_protocol::udp; + static constexpr portmap_protocol tcp = portmap_protocol::tcp; + + // add_port_mapping adds one or more port forwards on UPnP and/or NAT-PMP, + // whichever is enabled. A mapping is created for each listen socket + // in the session. The return values are all handles referring to the + // port mappings that were just created. Pass them to delete_port_mapping() + // to remove them. + std::vector add_port_mapping(portmap_protocol t, int external_port, int local_port); + void delete_port_mapping(port_mapping_t handle); + + // This option indicates if the ports are mapped using natpmp + // and upnp. If mapping was already made, they are deleted and added + // again. This only works if natpmp and/or upnp are configured to be + // enable. + static constexpr reopen_network_flags_t reopen_map_ports = 0_bit; + + // Instructs the session to reopen all listen and outgoing sockets. + // + // It's useful in the case your platform doesn't support the built in + // IP notifier mechanism, or if you have a better more reliable way to + // detect changes in the IP routing table. + void reopen_network_sockets(reopen_network_flags_t options = reopen_map_ports); + + // This function is intended only for use by plugins. This type does + // not have a stable API and should be relied on as little as possible. + std::shared_ptr native_handle() const + { return m_impl.lock(); } + + private: + + template + void async_call(Fun f, Args&&... a) const; + + template + void sync_call(Fun f, Args&&... a) const; + + template + Ret sync_call_ret(Fun f, Args&&... a) const; + + explicit session_handle(std::weak_ptr impl) + : m_impl(std::move(impl)) + {} + + std::weak_ptr m_impl; + }; + +} // namespace libtorrent + +#endif // TORRENT_SESSION_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp new file mode 100644 index 0000000..271af3f --- /dev/null +++ b/include/libtorrent/session_settings.hpp @@ -0,0 +1,113 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_SESSION_SETTINGS_HPP_INCLUDED + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" + +#include + +namespace libtorrent { + + using dht_settings = dht::dht_settings; + + using aux::proxy_settings; + + // The ``pe_settings`` structure is used to control the settings related + // to peer protocol encryption. + struct TORRENT_DEPRECATED_EXPORT pe_settings + { + // initializes the encryption settings with the default values + pe_settings() + : out_enc_policy(enabled) + , in_enc_policy(enabled) + , allowed_enc_level(both) + , prefer_rc4(false) + {} + + // the encoding policy options for use with pe_settings::out_enc_policy + // and pe_settings::in_enc_policy. + enum enc_policy + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + enabled, + + // only non-encrypted connections are allowed. + disabled + }; + + // the encryption levels, to be used with pe_settings::allowed_enc_level. + enum enc_level + { + // use only plaintext encryption + plaintext = 1, + // use only rc4 encryption + rc4 = 2, + // allow both + both = 3 + }; + + // control the settings for incoming + // and outgoing connections respectively. + // see enc_policy enum for the available options. + std::uint8_t out_enc_policy; + std::uint8_t in_enc_policy; + + // determines the encryption level of the + // connections. This setting will adjust which encryption scheme is + // offered to the other peer, as well as which encryption scheme is + // selected by the client. See enc_level enum for options. + std::uint8_t allowed_enc_level; + + // if the allowed encryption level is both, setting this to + // true will prefer rc4 if both methods are offered, plaintext + // otherwise + bool prefer_rc4; + }; +} + +#endif // TORRENT_ABI_VERSION +#endif diff --git a/include/libtorrent/session_stats.hpp b/include/libtorrent/session_stats.hpp new file mode 100644 index 0000000..8ffd5ef --- /dev/null +++ b/include/libtorrent/session_stats.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATS_HPP_INCLUDED +#define TORRENT_SESSION_STATS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" + +#include + +namespace libtorrent { + + enum class metric_type_t + { + counter, gauge + }; + + // describes one statistics metric from the session. For more information, + // see the session-statistics_ section. + struct TORRENT_EXPORT stats_metric + { + // the name of the counter or gauge + char const* name; + + // the index into the session stats array, where the underlying value of + // this counter or gauge is found. The session stats array is part of the + // session_stats_alert object. + int value_index; +#if TORRENT_ABI_VERSION == 1 + static constexpr metric_type_t TORRENT_DEPRECATED_MEMBER type_counter = metric_type_t::counter; + static constexpr metric_type_t TORRENT_DEPRECATED_MEMBER type_gauge = metric_type_t::gauge; +#endif + metric_type_t type; + }; + + // This free function returns the list of available metrics exposed by + // libtorrent's statistics API. Each metric has a name and a *value index*. + // The value index is the index into the array in session_stats_alert where + // this metric's value can be found when the session stats is sampled (by + // calling post_session_stats()). + TORRENT_EXPORT std::vector session_stats_metrics(); + + // given a name of a metric, this function returns the counter index of it, + // or -1 if it could not be found. The counter index is the index into the + // values array returned by session_stats_alert. + TORRENT_EXPORT int find_metric_idx(string_view name); +} + +#endif diff --git a/include/libtorrent/session_status.hpp b/include/libtorrent/session_status.hpp new file mode 100644 index 0000000..6dff3d7 --- /dev/null +++ b/include/libtorrent/session_status.hpp @@ -0,0 +1,236 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_STATUS_HPP_INCLUDED +#define TORRENT_SESSION_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/export.hpp" +#include + +#if TORRENT_ABI_VERSION == 1 +// for dht_lookup and dht_routing_bucket +#include "libtorrent/alert_types.hpp" +#endif + +#if TORRENT_ABI_VERSION == 1 +namespace libtorrent { + + // holds counters and gauges for the uTP sockets + // deprecated in 1.1 in favor of session_stats counters, which is a more + // flexible, extensible and performant mechanism for stats. + struct TORRENT_DEPRECATED_EXPORT utp_status + { + // gauges. These are snapshots of the number of + // uTP sockets in each respective state + int num_idle; + int num_syn_sent; + int num_connected; + int num_fin_sent; + int num_close_wait; + + // These are monotonically increasing + // and cumulative counters for their respective event. + std::uint64_t packet_loss; + std::uint64_t timeout; + std::uint64_t packets_in; + std::uint64_t packets_out; + std::uint64_t fast_retransmit; + std::uint64_t packet_resend; + std::uint64_t samples_above_target; + std::uint64_t samples_below_target; + std::uint64_t payload_pkts_in; + std::uint64_t payload_pkts_out; + std::uint64_t invalid_pkts_in; + std::uint64_t redundant_pkts_in; + }; + + // contains session wide state and counters + // deprecated in 1.1 in favor of session_stats counters, which is a more + // flexible, extensible and performant mechanism for stats. + struct TORRENT_DEPRECATED_EXPORT session_status + { + // false as long as no incoming connections have been + // established on the listening socket. Every time you change the listen port, this will + // be reset to false. + bool has_incoming_connections; + + // the total download and upload rates accumulated + // from all torrents. This includes bittorrent protocol, DHT and an estimated TCP/IP + // protocol overhead. + // deprecated, use session_stats_metrics "net.recv_bytes" + "net.recv_ip_overhead_bytes" + // they does include payload + protocol + ip overhead bytes + int upload_rate; + int download_rate; + + // the total number of bytes downloaded and + // uploaded to and from all torrents. This also includes all the protocol overhead. + // deprecated, use session_stats_metrics "net.recv_bytes" + "net.recv_ip_overhead_bytes" + // they does include payload + protocol + ip overhead bytes + std::int64_t total_download; + std::int64_t total_upload; + + // the rate of the payload + // down- and upload only. + // deprecated, use session_stats_metrics "net.recv_payload_bytes" + int payload_upload_rate; + // deprecated, use session_stats_metrics "net.sent_payload_bytes" + int payload_download_rate; + + // the total transfers of payload + // only. The payload does not include the bittorrent protocol overhead, but only parts of the + // actual files to be downloaded. + // ``total_payload_download`` is deprecated, use session_stats_metrics + // "net.recv_payload_bytes" ``total_payload_upload`` is deprecated, use + // session_stats_metrics "net.sent_payload_bytes" + std::int64_t total_payload_download; + std::int64_t total_payload_upload; + + // the estimated TCP/IP overhead in each direction. + int ip_overhead_upload_rate; + int ip_overhead_download_rate; + std::int64_t total_ip_overhead_download; + std::int64_t total_ip_overhead_upload; + + // the upload and download rate used by DHT traffic. Also the total number + // of bytes sent and received to and from the DHT. + int dht_upload_rate; + int dht_download_rate; + std::int64_t total_dht_download; + std::int64_t total_dht_upload; + + // the upload and download rate used by tracker traffic. Also the total number + // of bytes sent and received to and from trackers. + int tracker_upload_rate; + int tracker_download_rate; + std::int64_t total_tracker_download; + std::int64_t total_tracker_upload; + + // the number of bytes that has been received more than once. + // This can happen if a request from a peer times out and is requested from a different + // peer, and then received again from the first one. To make this lower, increase the + // ``request_timeout`` and the ``piece_timeout`` in the session settings. + std::int64_t total_redundant_bytes; + + // the number of bytes that was downloaded which later failed + // the hash-check. + std::int64_t total_failed_bytes; + + // the total number of peer connections this session has. This includes + // incoming connections that still hasn't sent their handshake or outgoing connections + // that still hasn't completed the TCP connection. This number may be slightly higher + // than the sum of all peers of all torrents because the incoming connections may not + // be assigned a torrent yet. + int num_peers; + + int num_dead_peers; + + // the current number of unchoked peers. + int num_unchoked; + + // the current allowed number of unchoked peers. + int allowed_upload_slots; + + // the number of peers that are + // waiting for more bandwidth quota from the torrent rate limiter. + int up_bandwidth_queue; + int down_bandwidth_queue; + + // count the number of + // bytes the connections are waiting for to be able to send and receive. + int up_bandwidth_bytes_queue; + int down_bandwidth_bytes_queue; + + // tells the number of + // seconds until the next optimistic unchoke change and the start of the next + // unchoke interval. These numbers may be reset prematurely if a peer that is + // unchoked disconnects or becomes not interested. + int optimistic_unchoke_counter; + int unchoke_counter; + + // the number of peers currently + // waiting on a disk write or disk read to complete before it receives or sends + // any more data on the socket. It'a a metric of how disk bound you are. + int disk_write_queue; + int disk_read_queue; + + // only available when + // built with DHT support. They are all set to 0 if the DHT isn't running. When + // the DHT is running, ``dht_nodes`` is set to the number of nodes in the routing + // table. This number only includes *active* nodes, not cache nodes. The + // ``dht_node_cache`` is set to the number of nodes in the node cache. These nodes + // are used to replace the regular nodes in the routing table in case any of them + // becomes unresponsive. + // deprecated, use session_stats_metrics "dht.dht_nodes" and "dht.dht_nodes_cache" + int dht_nodes; + int dht_node_cache; + + // the number of torrents tracked by the DHT at the moment. + int dht_torrents; + + // an estimation of the total number of nodes in the DHT + // network. + std::int64_t dht_global_nodes; + + // a vector of the currently running DHT lookups. + std::vector active_requests; + + // contains information about every bucket in the DHT routing + // table. + std::vector dht_routing_table; + + // the number of nodes allocated dynamically for a + // particular DHT lookup. This represents roughly the amount of memory used + // by the DHT. + int dht_total_allocations; + +#include "libtorrent/aux_/disable_warnings_push.hpp" + + // statistics on the uTP sockets. + utp_status utp_stats; + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + + // the number of known peers across all torrents. These are not necessarily + // connected peers, just peers we know of. + int peerlist_size; + + // the number of torrents in the + // session and the number of them that are currently paused, respectively. + int num_torrents; + int num_paused_torrents; + }; +} +#endif // TORRENT_ABI_VERSION + +#endif // TORRENT_SESSION_STATUS_HPP_INCLUDED + diff --git a/include/libtorrent/session_types.hpp b/include/libtorrent/session_types.hpp new file mode 100644 index 0000000..1a5a3b0 --- /dev/null +++ b/include/libtorrent/session_types.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SESSION_TYPES_HPP_INCLUDED +#define TORRENT_SESSION_TYPES_HPP_INCLUDED + +#include +#include "libtorrent/flags.hpp" + +namespace libtorrent { + + // hidden + using save_state_flags_t = flags::bitfield_flag; + + // hidden + using session_flags_t = flags::bitfield_flag; + + // hidden + using remove_flags_t = flags::bitfield_flag; + + // hidden + using reopen_network_flags_t = flags::bitfield_flag; +} + +#endif + diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp new file mode 100644 index 0000000..fe0264b --- /dev/null +++ b/include/libtorrent/settings_pack.hpp @@ -0,0 +1,1928 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SETTINGS_PACK_HPP_INCLUDED +#define TORRENT_SETTINGS_PACK_HPP_INCLUDED + +#include "libtorrent/entry.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/flags.hpp" + +#include +#include + +// OVERVIEW +// +// You have some control over session configuration through the session::apply_settings() +// member function. To change one or more configuration options, create a settings_pack +// object and fill it with the settings to be set and pass it in to session::apply_settings(). +// +// The settings_pack object is a collection of settings updates that are applied +// to the session when passed to session::apply_settings(). It's empty when +// constructed. +// +// You have control over proxy and authorization settings and also the user-agent +// that will be sent to the tracker. The user-agent will also be used to identify the +// client with other peers. +// +// Each configuration option is named with an enum value inside the +// settings_pack class. These are the available settings: +namespace libtorrent { + +namespace aux { + struct session_impl; + struct session_settings; + struct session_settings_single_thread; +} + + struct settings_pack; + struct bdecode_node; + + TORRENT_EXTRA_EXPORT settings_pack load_pack_from_dict(bdecode_node const& settings); + TORRENT_EXTRA_EXPORT void save_settings_to_dict(aux::session_settings const& s, entry::dictionary_type& sett); + TORRENT_EXTRA_EXPORT void apply_pack(settings_pack const* pack, aux::session_settings& sett + , aux::session_impl* ses = nullptr); + TORRENT_EXTRA_EXPORT void apply_pack_impl(settings_pack const* pack + , aux::session_settings_single_thread& sett + , std::vector* callbacks = nullptr); + TORRENT_EXTRA_EXPORT void run_all_updates(aux::session_impl& ses); + TORRENT_EXTRA_EXPORT int default_int_value(int const name); + + // converts a setting integer (from the enums string_types, int_types or + // bool_types) to a string, and vice versa. + TORRENT_EXPORT int setting_by_name(string_view name); + TORRENT_EXPORT char const* name_for_setting(int s); + + // returns a settings_pack with every setting set to its default value + TORRENT_EXPORT settings_pack default_settings(); + + // The ``settings_pack`` struct, contains the names of all settings as + // enum values. These values are passed in to the ``set_str()``, + // ``set_int()``, ``set_bool()`` functions, to specify the setting to + // change. + // + // .. include:: settings-ref.rst + // + struct TORRENT_EXPORT settings_pack + { + friend TORRENT_EXTRA_EXPORT void apply_pack_impl(settings_pack const* + , aux::session_settings_single_thread& + , std::vector*); + + // hidden + settings_pack() = default; + settings_pack(settings_pack const&) = default; + settings_pack(settings_pack&&) noexcept = default; + settings_pack& operator=(settings_pack const&) = default; + settings_pack& operator=(settings_pack&&) noexcept = default; + + // set a configuration option in the settings_pack. ``name`` is one of + // the enum values from string_types, int_types or bool_types. They must + // match the respective type of the set_* function. + void set_str(int name, std::string val); + void set_int(int name, int val); + void set_bool(int name, bool val); + template + void set_int(int name, flags::bitfield_flag const val) + { set_int(name, static_cast(static_cast(val))); } + + // queries whether the specified configuration option has a value set in + // this pack. ``name`` can be any enumeration value from string_types, + // int_types or bool_types. + bool has_val(int name) const; + + // clear the settings pack from all settings + void clear(); + + // clear a specific setting from the pack + void clear(int name); + + // queries the current configuration option from the settings_pack. + // ``name`` is one of the enumeration values from string_types, int_types + // or bool_types. The enum value must match the type of the get_* + // function. + std::string const& get_str(int name) const; + int get_int(int name) const; + bool get_bool(int name) const; + + // setting names (indices) are 16 bits. The two most significant + // bits indicate what type the setting has. (string, int, bool) + enum type_bases + { + string_type_base = 0x0000, + int_type_base = 0x4000, + bool_type_base = 0x8000, + type_mask = 0xc000, + index_mask = 0x3fff + }; + + // hidden + enum string_types + { + // this is the client identification to the tracker. The recommended + // format of this string is: "client-name/client-version + // libtorrent/libtorrent-version". This name will not only be used when + // making HTTP requests, but also when sending extended headers to + // peers that support that extension. It may not contain \r or \n + user_agent = string_type_base, + + // ``announce_ip`` is the ip address passed along to trackers as the + // ``&ip=`` parameter. If left as the default, that parameter is + // omitted. + // + // .. note:: + // This setting is only meant for very special cases where a seed is + // running on the same host as the tracker, and the tracker accepts + // the IP parameter (which normal trackers don't). Do not set this + // option unless you also control the tracker. + announce_ip, + +#if TORRENT_ABI_VERSION == 1 + // ``mmap_cache`` may be set to a filename where the disk cache will + // be mmapped to. This could be useful, for instance, to map the disk + // cache from regular rotating hard drives onto an SSD drive. Doing + // that effectively introduces a second layer of caching, allowing the + // disk cache to be as big as can fit on an SSD drive (probably about + // one order of magnitude more than the available RAM). The intention + // of this setting is to set it up once at the start up and not change + // it while running. The setting may not be changed as long as there + // are any disk buffers in use. This default to the empty string, + // which means use regular RAM allocations for the disk cache. The + // file specified will be created and truncated to the disk cache size + // (``cache_size``). Any existing file with the same name will be + // replaced. + // + // This feature requires the ``mmap`` system call, on systems that + // don't have ``mmap`` this setting is ignored. + mmap_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_mmap_cache, +#endif + + // this is the client name and version identifier sent to peers in the + // handshake message. If this is an empty string, the user_agent is + // used instead. This string must be a UTF-8 encoded unicode string. + handshake_client_version, + + // This controls which IP address outgoing TCP peer connections are bound + // to, in addition to controlling whether such connections are also + // bound to a specific network interface/adapter (*bind-to-device*). + // This string is a comma-separated list of IP addresses and + // interface names. An empty string will not bind TCP sockets to a + // device, and let the network stack assign the local address. A + // list of names will be used to bind outgoing TCP sockets in a + // round-robin fashion. An IP address will simply be used to `bind()` + // the socket. An interface name will attempt to bind the socket to + // that interface. If that fails, or is unsupported, one of the IP + // addresses configured for that interface is used to `bind()` the + // socket to. If the interface or adapter doesn't exist, the + // outgoing peer connection will fail with an error message suggesting + // the device cannot be found. Adapter names on Unix systems are of + // the form "eth0", "eth1", "tun0", etc. This may be useful for + // clients that are multi-homed. Binding an outgoing connection to a + // local IP does not necessarily make the connection via the + // associated NIC/Adapter. + outgoing_interfaces, + + // a comma-separated list of (IP or device name, port) pairs. These are + // the listen ports that will be opened for accepting incoming uTP and + // TCP peer connections. These are also used for *outgoing* uTP and UDP + // tracker connections and DHT nodes. + // + // It is possible to listen on multiple interfaces and + // multiple ports. Binding to port 0 will make the operating system + // pick the port. + // + // .. note:: + // There are reasons to stick to the same port across sessions, + // which would mean only using port 0 on the first start, and + // recording the port that was picked for subsequent startups. + // Trackers, the DHT and other peers will remember the port they see + // you use and hand that port out to other peers trying to connect + // to you, as well as trying to connect to you themselves. + // + // A port that has an "s" suffix will accept SSL peer connections. (note + // that SSL sockets are only available in builds with SSL support) + // + // A port that has an "l" suffix will be considered a local network. + // i.e. it's assumed to only be able to reach hosts in the same local + // network as the IP address (based on the netmask associated with the + // IP, queried from the operating system). + // + // if binding fails, the listen_failed_alert is posted. Once a + // socket binding succeeds (if it does), the listen_succeeded_alert + // is posted. There may be multiple failures before a success. + // + // If a device name that does not exist is configured, no listen + // socket will be opened for that interface. If this is the only + // interface configured, it will be as if no listen ports are + // configured. + // + // If no listen ports are configured (e.g. listen_interfaces is an + // empty string), networking will be disabled. No DHT will start, no + // outgoing uTP or tracker connections will be made. No incoming TCP + // or uTP connections will be accepted. (outgoing TCP connections + // will still be possible, depending on + // settings_pack::outgoing_interfaces). + // + // For example: + // ``[::1]:8888`` - will only accept connections on the IPv6 loopback + // address on port 8888. + // + // ``eth0:4444,eth1:4444`` - will accept connections on port 4444 on + // any IP address bound to device ``eth0`` or ``eth1``. + // + // ``[::]:0s`` - will accept SSL connections on a port chosen by the + // OS. And not accept non-SSL connections at all. + // + // ``0.0.0.0:6881,[::]:6881`` - binds to all interfaces on port 6881. + // + // ``10.0.1.13:6881l`` - binds to the local IP address, port 6881, but + // only allow talking to peers on the same local network. The netmask + // is queried from the operating system. Interfaces marked ``l`` are + // not announced to trackers, unless the tracker is also on the same + // local network. + // + // Windows OS network adapter device name must be specified with GUID. + // It can be obtained from "netsh lan show interfaces" command output. + // GUID must be uppercased string embraced in curly brackets. + // ``{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777`` - will accept + // connections on port 7777 on adapter with this GUID. + // + // For more information, see the `Multi-homed hosts`_ section. + // + // .. _`Multi-homed hosts`: manual-ref.html#multi-homed-hosts + listen_interfaces, + + // when using a proxy, this is the hostname where the proxy is running + // see proxy_type. Note that when using a proxy, the + // settings_pack::listen_interfaces setting is overridden and only a + // single interface is created, just to contact the proxy. This + // means a proxy cannot be combined with SSL torrents or multiple + // listen interfaces. This proxy listen interface will not accept + // incoming TCP connections, will not map ports with any gateway and + // will not enable local service discovery. All traffic is supposed + // to be channeled through the proxy. + proxy_hostname, + + // when using a proxy, these are the credentials (if any) to use when + // connecting to it. see proxy_type + proxy_username, + proxy_password, + + // sets the i2p_ SAM bridge to connect to. set the port with the + // ``i2p_port`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_hostname, + + // this is the fingerprint for the client. It will be used as the + // prefix to the peer_id. If this is 20 bytes (or longer) it will be + // truncated to 20 bytes and used as the entire peer-id + // + // There is a utility function, generate_fingerprint() that can be used + // to generate a standard client peer ID fingerprint prefix. + peer_fingerprint, + + // This is a comma-separated list of IP port-pairs. They will be added + // to the DHT node (if it's enabled) as back-up nodes in case we don't + // know of any. + // + // Changing these after the DHT has been started may not have any + // effect until the DHT is restarted. + dht_bootstrap_nodes, + + max_string_setting_internal + }; + + // hidden + enum bool_types + { + // determines if connections from the same IP address as existing + // connections should be rejected or not. Rejecting multiple connections + // from the same IP address will prevent abusive + // behavior by peers. The logic for determining whether connections are + // to the same peer is more complicated with this enabled, and more + // likely to fail in some edge cases. It is not recommended to enable + // this feature. + allow_multiple_connections_per_ip = bool_type_base, + +#if TORRENT_ABI_VERSION == 1 + // if set to true, upload, download and unchoke limits are ignored for + // peers on the local network. This option is *DEPRECATED*, please use + // set_peer_class_filter() instead. + ignore_limits_on_local_network TORRENT_DEPRECATED_ENUM, +#else + deprecated_ignore_limits_on_local_network, +#endif + + // ``send_redundant_have`` controls if have messages will be sent to + // peers that already have the piece. This is typically not necessary, + // but it might be necessary for collecting statistics in some cases. + send_redundant_have, + +#if TORRENT_ABI_VERSION == 1 + // if this is true, outgoing bitfields will never be fuil. If the + // client is seed, a few bits will be set to 0, and later filled in + // with have messages. This is to prevent certain ISPs from stopping + // people from seeding. + lazy_bitfields TORRENT_DEPRECATED_ENUM, +#else + deprecated_lazy_bitfield, +#endif + + // ``use_dht_as_fallback`` determines how the DHT is used. If this is + // true, the DHT will only be used for torrents where all trackers in + // its tracker list has failed. Either by an explicit error message or + // a time out. If this is false, the DHT is used regardless of if the + // trackers fail or not. + use_dht_as_fallback, + + // ``upnp_ignore_nonrouters`` indicates whether or not the UPnP + // implementation should ignore any broadcast response from a device + // whose address is not on our subnet. i.e. + // it's a way to not talk to other people's routers by mistake. + upnp_ignore_nonrouters, + + // ``use_parole_mode`` specifies if parole mode should be used. Parole + // mode means that peers that participate in pieces that fail the hash + // check are put in a mode where they are only allowed to download + // whole pieces. If the whole piece a peer in parole mode fails the + // hash check, it is banned. If a peer participates in a piece that + // passes the hash check, it is taken out of parole mode. + use_parole_mode, + + // enable and disable caching of blocks read from disk. the purpose of + // the read cache is partly read-ahead of requests but also to avoid + // reading blocks back from the disk multiple times for popular + // pieces. + use_read_cache, +#if TORRENT_ABI_VERSION == 1 + use_write_cache TORRENT_DEPRECATED_ENUM, + + // this will make the disk cache never flush a write piece if it would + // cause is to have to re-read it once we want to calculate the piece + // hash + dont_flush_write_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_write_cache, + deprecated_dont_flush_write_cache, +#endif + + // allocate separate, contiguous, buffers for read and write calls. + // Only used where writev/readv cannot be used will use more RAM but + // may improve performance + coalesce_reads, + coalesce_writes, + + // if true, prefer seeding torrents when determining which torrents to give + // active slots to. If false, give preference to downloading torrents + auto_manage_prefer_seeds, + + // if ``dont_count_slow_torrents`` is true, torrents without any + // payload transfers are not subject to the ``active_seeds`` and + // ``active_downloads`` limits. This is intended to make it more + // likely to utilize all available bandwidth, and avoid having + // torrents that don't transfer anything block the active slots. + dont_count_slow_torrents, + + // ``close_redundant_connections`` specifies whether libtorrent should + // close connections where both ends have no utility in keeping the + // connection open. For instance if both ends have completed their + // downloads, there's no point in keeping it open. + close_redundant_connections, + + // If ``prioritize_partial_pieces`` is true, partial pieces are picked + // before pieces that are more rare. If false, rare pieces are always + // prioritized, unless the number of partial pieces is growing out of + // proportion. + prioritize_partial_pieces, + + // if set to true, the estimated TCP/IP overhead is drained from the + // rate limiters, to avoid exceeding the limits with the total traffic + rate_limit_ip_overhead, + + // ``announce_to_all_trackers`` controls how multi tracker torrents + // are treated. If this is set to true, all trackers in the same tier + // are announced to in parallel. If all trackers in tier 0 fails, all + // trackers in tier 1 are announced as well. If it's set to false, the + // behavior is as defined by the multi tracker specification. + // + // ``announce_to_all_tiers`` also controls how multi tracker torrents + // are treated. When this is set to true, one tracker from each tier + // is announced to. This is the uTorrent behavior. To be compliant + // with the Multi-tracker specification, set it to false. + announce_to_all_tiers, + announce_to_all_trackers, + + // ``prefer_udp_trackers``: true means that trackers + // may be rearranged in a way that udp trackers are always tried + // before http trackers for the same hostname. Setting this to false + // means that the tracker's tier is respected and there's no + // preference of one protocol over another. + prefer_udp_trackers, + +#if TORRENT_ABI_VERSION == 1 + // ``strict_super_seeding`` when this is set to true, a piece has to + // have been forwarded to a third peer before another one is handed + // out. This is the traditional definition of super seeding. + strict_super_seeding TORRENT_DEPRECATED_ENUM, +#else + deprecated_strict_super_seeding, +#endif + +#if TORRENT_ABI_VERSION == 1 + // if this is set to true, the memory allocated for the disk cache + // will be locked in physical RAM, never to be swapped out. Every time + // a disk buffer is allocated and freed, there will be the extra + // overhead of a system call. + lock_disk_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_lock_disk_cache, +#endif + + // when set to true, all data downloaded from peers will be assumed to + // be correct, and not tested to match the hashes in the torrent this + // is only useful for simulation and testing purposes (typically + // combined with disabled_storage) + disable_hash_checks, + + // if this is true, i2p torrents are allowed to also get peers from + // other sources than the tracker, and connect to regular IPs, not + // providing any anonymization. This may be useful if the user is not + // interested in the anonymization of i2p, but still wants to be able + // to connect to i2p peers. + allow_i2p_mixed, + +#if TORRENT_ABI_VERSION == 1 + // ``low_prio_disk`` determines if the disk I/O should use a normal or + // low priority policy. True, means that it's + // low priority by default. Other processes doing disk I/O will + // normally take priority in this mode. This is meant to improve the + // overall responsiveness of the system while downloading in the + // background. For high-performance server setups, this might not be + // desirable. + low_prio_disk TORRENT_DEPRECATED_ENUM, +#else + deprecated_low_prio_disk, +#endif + + // ``volatile_read_cache``, if this is set to true, read cache blocks + // that are hit by peer read requests are removed from the disk cache + // to free up more space. This is useful if you don't expect the disk + // cache to create any cache hits from other peers than the one who + // triggered the cache line to be read into the cache in the first + // place. + volatile_read_cache, + +#if TORRENT_ABI_VERSION == 1 + // ``guided_read_cache`` enables the disk cache to adjust the size of + // a cache line generated by peers to depend on the upload rate you + // are sending to that peer. The intention is to optimize the RAM + // usage of the cache, to read ahead further for peers that you're + // sending faster to. + guided_read_cache TORRENT_DEPRECATED_ENUM, +#else + deprecated_guided_read_cache, +#endif + + // ``no_atime_storage`` this is a Linux-only option and passes in the + // ``O_NOATIME`` to ``open()`` when opening files. This may lead to + // some disk performance improvements. + no_atime_storage, + + // ``incoming_starts_queued_torrents``. If a torrent + // has been paused by the auto managed feature in libtorrent, i.e. the + // torrent is paused and auto managed, this feature affects whether or + // not it is automatically started on an incoming connection. The main + // reason to queue torrents, is not to make them unavailable, but to + // save on the overhead of announcing to the trackers, the DHT and to + // avoid spreading one's unchoke slots too thin. If a peer managed to + // find us, even though we're no in the torrent anymore, this setting + // can make us start the torrent and serve it. + incoming_starts_queued_torrents, + + // when set to true, the downloaded counter sent to trackers will + // include the actual number of payload bytes downloaded including + // redundant bytes. If set to false, it will not include any redundancy + // bytes + report_true_downloaded, + + // ``strict_end_game_mode`` controls when a + // block may be requested twice. If this is ``true``, a block may only + // be requested twice when there's at least one request to every piece + // that's left to download in the torrent. This may slow down progress + // on some pieces sometimes, but it may also avoid downloading a lot + // of redundant bytes. If this is ``false``, libtorrent attempts to + // use each peer connection to its max, by always requesting + // something, even if it means requesting something that has been + // requested from another peer already. + strict_end_game_mode, + +#if TORRENT_ABI_VERSION == 1 + // if ``broadcast_lsd`` is set to true, the local peer discovery (or + // Local Service Discovery) will not only use IP multicast, but also + // broadcast its messages. This can be useful when running on networks + // that don't support multicast. Since broadcast messages might be + // expensive and disruptive on networks, only every 8th announce uses + // broadcast. + broadcast_lsd TORRENT_DEPRECATED_ENUM, +#else + deprecated_broadcast_lsd, +#endif + + // when set to true, libtorrent will try to make outgoing utp + // connections controls whether libtorrent will accept incoming + // connections or make outgoing connections of specific type. + enable_outgoing_utp, + enable_incoming_utp, + enable_outgoing_tcp, + enable_incoming_tcp, + +#if TORRENT_ABI_VERSION == 1 + // ``ignore_resume_timestamps`` determines if the storage, when + // loading resume data files, should verify that the file modification + // time with the timestamps in the resume data. False, means timestamps + // are taken into account, and resume + // data is less likely to accepted (torrents are more likely to be + // fully checked when loaded). It might be useful to set this to true + // if your network is faster than your disk, and it would be faster to + // redownload potentially missed pieces than to go through the whole + // storage to look for them. + ignore_resume_timestamps TORRENT_DEPRECATED_ENUM, +#else + // hidden + deprecated_ignore_resume_timestamps, +#endif + + // ``no_recheck_incomplete_resume`` determines if the storage should + // check the whole files when resume data is incomplete or missing or + // whether it should simply assume we don't have any of the data. If + // false, any existing files will be checked. + // By setting this setting to true, the files won't be checked, but + // will go straight to download mode. + no_recheck_incomplete_resume, + + // ``anonymous_mode``: When set to true, the client + // tries to hide its identity to a certain degree. The user-agent will be + // reset to an empty string (except for private torrents). Trackers + // will only be used if they are using a proxy server. + // The listen sockets are closed, and incoming + // connections will only be accepted through a SOCKS5 or I2P proxy (if + // a peer proxy is set up and is run on the same machine as the + // tracker proxy). Since no incoming connections are accepted, + // NAT-PMP, UPnP, DHT and local peer discovery are all turned off when + // this setting is enabled. + // + // If you're using I2P, it might make sense to enable anonymous mode + // as well. + anonymous_mode, + + // specifies whether downloads from web seeds is reported to the + // tracker or not. Turning it off also excludes web + // seed traffic from other stats and download rate reporting via the + // libtorrent API. + report_web_seed_downloads, + +#if TORRENT_ABI_VERSION == 1 + // set to true if uTP connections should be rate limited This option + // is *DEPRECATED*, please use set_peer_class_filter() instead. + rate_limit_utp TORRENT_DEPRECATED_ENUM, +#else + deprecated_rate_limit_utp, +#endif + +#if TORRENT_ABI_VERSION == 1 + // if this is true, the ``&ip=`` argument in tracker requests (unless + // otherwise specified) will be set to the intermediate IP address if + // the user is double NATed. If the user is not double NATed, this + // option does not have an affect + announce_double_nat TORRENT_DEPRECATED_ENUM, +#else + deprecated_announce_double_nat, +#endif + + // ``seeding_outgoing_connections`` determines if seeding (and + // finished) torrents should attempt to make outgoing connections or + // not. It may be set to false in very + // specific applications where the cost of making outgoing connections + // is high, and there are no or small benefits of doing so. For + // instance, if no nodes are behind a firewall or a NAT, seeds don't + // need to make outgoing connections. + seeding_outgoing_connections, + + // when this is true, libtorrent will not attempt to make outgoing + // connections to peers whose port is < 1024. This is a safety + // precaution to avoid being part of a DDoS attack + no_connect_privileged_ports, + + // ``smooth_connects`` means the number of + // connection attempts per second may be limited to below the + // ``connection_speed``, in case we're close to bump up against the + // limit of number of connections. The intention of this setting is to + // more evenly distribute our connection attempts over time, instead + // of attempting to connect in batches, and timing them out in + // batches. + smooth_connects, + + // always send user-agent in every web seed request. If false, only + // the first request per http connection will include the user agent + always_send_user_agent, + + // ``apply_ip_filter_to_trackers`` determines + // whether the IP filter applies to trackers as well as peers. If this + // is set to false, trackers are exempt from the IP filter (if there + // is one). If no IP filter is set, this setting is irrelevant. + apply_ip_filter_to_trackers, + +#if TORRENT_ABI_VERSION == 1 + // ``use_disk_read_ahead`` if true will attempt to + // optimize disk reads by giving the operating system heads up of disk + // read requests as they are queued in the disk job queue. + use_disk_read_ahead TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_disk_read_ahead, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``lock_files`` determines whether or not to lock files which + // libtorrent is downloading to or seeding from. This is implemented + // using ``fcntl(F_SETLK)`` on Unix systems and by not passing in + // ``SHARE_READ`` and ``SHARE_WRITE`` on windows. This might prevent + // 3rd party processes from corrupting the files under libtorrent's + // feet. + lock_files TORRENT_DEPRECATED_ENUM, +#else + deprecated_lock_files, +#endif + +#if TORRENT_ABI_VERSION == 1 + // ``contiguous_recv_buffer`` determines whether or not libtorrent + // should receive data from peers into a contiguous intermediate + // buffer, to then copy blocks into disk buffers from, or to make many + // smaller calls to ``read()``, each time passing in the specific + // buffer the data belongs in. When downloading at high rates, the + // latter may save some time copying data. When seeding at high rates, + // all incoming traffic consists of a very large number of tiny + // packets, and enabling ``contiguous_recv_buffer`` will provide + // higher performance. When this is enabled, it will only be used when + // seeding to peers, since that's when it provides performance + // improvements. + contiguous_recv_buffer TORRENT_DEPRECATED_ENUM, +#else + deprecated_contiguous_recv_buffer, +#endif + + // when true, web seeds sending bad data will be banned + ban_web_seeds, + + // when set to false, the ``write_cache_line_size`` will apply across + // piece boundaries. this is a bad idea unless the piece picker also + // is configured to have an affinity to pick pieces belonging to the + // same write cache line as is configured in the disk cache. + allow_partial_disk_writes, + +#if TORRENT_ABI_VERSION == 1 + // If true, disables any communication that's not going over a proxy. + // Enabling this requires a proxy to be configured as well, see + // proxy_type and proxy_hostname settings. The listen sockets are + // closed, and incoming connections will only be accepted through a + // SOCKS5 or I2P proxy (if a peer proxy is set up and is run on the + // same machine as the tracker proxy). + force_proxy TORRENT_DEPRECATED_ENUM, +#else + deprecated_force_proxy, +#endif + + // if false, prevents libtorrent to advertise share-mode support + support_share_mode, + + // if this is false, don't advertise support for the Tribler merkle + // tree piece message + support_merkle_torrents, + + // if this is true, the number of redundant bytes is sent to the + // tracker + report_redundant_bytes, + + // if this is true, libtorrent will fall back to listening on a port + // chosen by the operating system (i.e. binding to port 0). If a + // failure is preferred, set this to false. + listen_system_port_fallback, + +#if TORRENT_ABI_VERSION == 1 + // ``use_disk_cache_pool`` enables using a pool allocator for disk + // cache blocks. Enabling it makes the cache perform better at high + // throughput. It also makes the cache less likely and slower at + // returning memory back to the system, once allocated. + use_disk_cache_pool TORRENT_DEPRECATED_ENUM, +#else + deprecated_use_disk_cache_pool, +#endif + + // when this is true, and incoming encrypted connections are enabled, + // &supportcrypt=1 is included in http tracker announces + announce_crypto_support, + + // Starts and stops the UPnP service. When started, the listen port + // and the DHT port are attempted to be forwarded on local UPnP router + // devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and + // remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + enable_upnp, + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router + // through NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned + // through the portmap_alert and the portmap_error_alert. The object + // will be valid until ``stop_natpmp()`` is called. See + // upnp-and-nat-pmp_. + enable_natpmp, + + // Starts and stops Local Service Discovery. This service will + // broadcast the info-hashes of all the non-private torrents on the + // local network to look for peers on the same swarm within multicast + // reach. + enable_lsd, + + // starts the dht node and makes the trackerless service available to + // torrents. + enable_dht, + + // if the allowed encryption level is both, setting this to true will + // prefer RC4 if both methods are offered, plain text otherwise + prefer_rc4, + + // if true, hostname lookups are done via the configured proxy (if + // any). This is only supported by SOCKS5 and HTTP. + proxy_hostnames, + + // if true, peer connections are made (and accepted) over the + // configured proxy, if any. Web seeds as well as regular bittorrent + // peer connections are considered "peer connections". Anything + // transporting actual torrent payload (trackers and DHT traffic are + // not considered peer connections). + proxy_peer_connections, + + // if this setting is true, torrents with a very high availability of + // pieces (and seeds) are downloaded sequentially. This is more + // efficient for the disk I/O. With many seeds, the download order is + // unlikely to matter anyway + auto_sequential, + + // if true, tracker connections are made over the configured proxy, if + // any. + proxy_tracker_connections, + + // Starts and stops the internal IP table route changes notifier. + // + // The current implementation supports multiple platforms, and it is + // recommended to have it enable, but you may want to disable it if + // it's supported but unreliable, or if you have a better way to + // detect the changes. In the later case, you should manually call + // ``session_handle::reopen_network_sockets`` to ensure network + // changes are taken in consideration. + enable_ip_notifier, + + // when this is true, nodes whose IDs are derived from their source + // IP according to `BEP 42`_ are preferred in the routing table. + dht_prefer_verified_node_ids, + + // when this is true, create an affinity for downloading 4 MiB extents + // of adjacent pieces. This is an attempt to achieve better disk I/O + // throughput by downloading larger extents of bytes, for torrents with + // small piece sizes + piece_extent_affinity, + + // when set to true, the certificate of HTTPS trackers will be + // validated against the system's certificate store (as defined by + // OpenSSL). If the system does not have one, enabling this may cause + // HTTPS trackers to fail. + validate_https_trackers, + + max_bool_setting_internal + }; + + // hidden + enum int_types + { + // ``tracker_completion_timeout`` is the number of seconds the tracker + // connection will wait from when it sent the request until it + // considers the tracker to have timed-out. + tracker_completion_timeout = int_type_base, + + // ``tracker_receive_timeout`` is the number of seconds to wait to + // receive any data from the tracker. If no data is received for this + // number of seconds, the tracker will be considered as having timed + // out. If a tracker is down, this is the kind of timeout that will + // occur. + tracker_receive_timeout, + + // ``stop_tracker_timeout`` is the number of seconds to wait when + // sending a stopped message before considering a tracker to have + // timed out. This is usually shorter, to make the client quit faster. + // If the value is set to 0, the connections to trackers with the + // stopped event are suppressed. + stop_tracker_timeout, + + // this is the maximum number of bytes in a tracker response. If a + // response size passes this number of bytes it will be rejected and + // the connection will be closed. On gzipped responses this size is + // measured on the uncompressed data. So, if you get 20 bytes of gzip + // response that'll expand to 2 megabytes, it will be interrupted + // before the entire response has been uncompressed (assuming the + // limit is lower than 2 MiB). + tracker_maximum_response_length, + + // the number of seconds from a request is sent until it times out if + // no piece response is returned. + piece_timeout, + + // the number of seconds one block (16 kiB) is expected to be received + // within. If it's not, the block is requested from a different peer + request_timeout, + + // the length of the request queue given in the number of seconds it + // should take for the other end to send all the pieces. i.e. the + // actual number of requests depends on the download rate and this + // number. + request_queue_time, + + // the number of outstanding block requests a peer is allowed to queue + // up in the client. If a peer sends more requests than this (before + // the first one has been sent) the last request will be dropped. the + // higher this is, the faster upload speeds the client can get to a + // single peer. + max_allowed_in_request_queue, + + // ``max_out_request_queue`` is the maximum number of outstanding + // requests to send to a peer. This limit takes precedence over + // ``request_queue_time``. i.e. no matter the download speed, the + // number of outstanding requests will never exceed this limit. + max_out_request_queue, + + // if a whole piece can be downloaded in this number of seconds, or + // less, the peer_connection will prefer to request whole pieces at a + // time from this peer. The benefit of this is to better utilize disk + // caches by doing localized accesses and also to make it easier to + // identify bad peers if a piece fails the hash check. + whole_pieces_threshold, + + // ``peer_timeout`` is the number of seconds the peer connection + // should wait (for any activity on the peer connection) before + // closing it due to time out. 120 seconds is + // specified in the protocol specification. After half + // the time out, a keep alive message is sent. + peer_timeout, + + // same as peer_timeout, but only applies to url-seeds. this is + // usually set lower, because web servers are expected to be more + // reliable. + urlseed_timeout, + + // controls the pipelining size of url and http seeds. i.e. the number of HTTP + // request to keep outstanding before waiting for the first one to + // complete. It's common for web servers to limit this to a relatively + // low number, like 5 + urlseed_pipeline_size, + + // number of seconds until a new retry of a url-seed takes place. + // Default retry value for http-seeds that don't provide + // a valid ``retry-after`` header. + urlseed_wait_retry, + + // sets the upper limit on the total number of files this session will + // keep open. The reason why files are left open at all is that some + // anti virus software hooks on every file close, and scans the file + // for viruses. deferring the closing of the files will be the + // difference between a usable system and a completely hogged down + // system. Most operating systems also has a limit on the total number + // of file descriptors a process may have open. + file_pool_size, + + // ``max_failcount`` is the maximum times we try to + // connect to a peer before stop connecting again. If a + // peer succeeds, the failure counter is reset. If a + // peer is retrieved from a peer source (other than DHT) + // the failcount is decremented by one, allowing another + // try. + max_failcount, + + // the number of seconds to wait to reconnect to a peer. this time is + // multiplied with the failcount. + min_reconnect_time, + + // ``peer_connect_timeout`` the number of seconds to wait after a + // connection attempt is initiated to a peer until it is considered as + // having timed out. This setting is especially important in case the + // number of half-open connections are limited, since stale half-open + // connection may delay the connection of other peers considerably. + peer_connect_timeout, + + // ``connection_speed`` is the number of connection attempts that are + // made per second. If a number < 0 is specified, it will default to + // 200 connections per second. If 0 is specified, it means don't make + // outgoing connections at all. + connection_speed, + + // if a peer is uninteresting and uninterested for longer than this + // number of seconds, it will be disconnected. + inactivity_timeout, + + // ``unchoke_interval`` is the number of seconds between + // chokes/unchokes. On this interval, peers are re-evaluated for being + // choked/unchoked. This is defined as 30 seconds in the protocol, and + // it should be significantly longer than what it takes for TCP to + // ramp up to it's max rate. + unchoke_interval, + + // ``optimistic_unchoke_interval`` is the number of seconds between + // each *optimistic* unchoke. On this timer, the currently + // optimistically unchoked peer will change. + optimistic_unchoke_interval, + + // ``num_want`` is the number of peers we want from each tracker + // request. It defines what is sent as the ``&num_want=`` parameter to + // the tracker. + num_want, + + // ``initial_picker_threshold`` specifies the number of pieces we need + // before we switch to rarest first picking. The first + // ``initial_picker_threshold`` pieces in any torrent are picked at random + // , the following pieces are picked in rarest first order. + initial_picker_threshold, + + // the number of allowed pieces to send to peers that supports the + // fast extensions + allowed_fast_set_size, + + // ``suggest_mode`` controls whether or not libtorrent will send out + // suggest messages to create a bias of its peers to request certain + // pieces. The modes are: + // + // * ``no_piece_suggestions`` which will not send out suggest messages. + // * ``suggest_read_cache`` which will send out suggest messages for + // the most recent pieces that are in the read cache. + suggest_mode, + + // ``max_queued_disk_bytes`` is the maximum number of bytes, to + // be written to disk, that can wait in the disk I/O thread queue. + // This queue is only for waiting for the disk I/O thread to receive + // the job and either write it to disk or insert it in the write + // cache. When this limit is reached, the peer connections will stop + // reading data from their sockets, until the disk thread catches up. + // Setting this too low will severely limit your download rate. + max_queued_disk_bytes, + + // the number of seconds to wait for a handshake response from a peer. + // If no response is received within this time, the peer is + // disconnected. + handshake_timeout, + + // ``send_buffer_low_watermark`` the minimum send buffer target size + // (send buffer includes bytes pending being read from disk). For good + // and snappy seeding performance, set this fairly high, to at least + // fit a few blocks. This is essentially the initial window size which + // will determine how fast we can ramp up the send rate + // + // if the send buffer has fewer bytes than ``send_buffer_watermark``, + // we'll read another 16 kiB block onto it. If set too small, upload + // rate capacity will suffer. If set too high, memory will be wasted. + // The actual watermark may be lower than this in case the upload rate + // is low, this is the upper limit. + // + // the current upload rate to a peer is multiplied by this factor to + // get the send buffer watermark. The factor is specified as a + // percentage. i.e. 50 -> 0.5 This product is clamped to the + // ``send_buffer_watermark`` setting to not exceed the max. For high + // speed upload, this should be set to a greater value than 100. For + // high capacity connections, setting this higher can improve upload + // performance and disk throughput. Setting it too high may waste RAM + // and create a bias towards read jobs over write jobs. + send_buffer_low_watermark, + send_buffer_watermark, + send_buffer_watermark_factor, + + // ``choking_algorithm`` specifies which algorithm to use to determine + // how many peers to unchoke. The unchoking algorithm for + // downloading torrents is always "tit-for-tat", i.e. the peers we + // download the fastest from are unchoked. + // + // The options for choking algorithms are defined in the + // choking_algorithm_t enum. + // + // ``seed_choking_algorithm`` controls the seeding unchoke behavior. + // i.e. How we select which peers to unchoke for seeding torrents. + // Since a seeding torrent isn't downloading anything, the + // tit-for-tat mechanism cannot be used. The available options are + // defined in the seed_choking_algorithm_t enum. + choking_algorithm, + seed_choking_algorithm, + + // ``cache_size`` is the disk write and read cache. It is specified + // in units of 16 kiB blocks. Buffers that are part of a peer's send + // or receive buffer also count against this limit. Send and receive + // buffers will never be denied to be allocated, but they will cause + // the actual cached blocks to be flushed or evicted. If this is set + // to -1, the cache size is automatically set based on the amount of + // physical RAM on the machine. If the amount of physical RAM cannot + // be determined, it's set to 1024 (= 16 MiB). + // + // ``cache_expiry`` is the number of seconds from the last cached write + // to a piece in the write cache, to when it's forcefully flushed to + // disk. + // + // On 32 bit builds, the effective cache size will be limited to 3/4 of + // 2 GiB to avoid exceeding the virtual address space limit. + cache_size, +#if TORRENT_ABI_VERSION == 1 + cache_buffer_chunk_size TORRENT_DEPRECATED_ENUM, +#else + deprecated_cache_buffer_chunk_size, +#endif + cache_expiry, + + // determines how files are opened when they're in read only mode + // versus read and write mode. The options are: + // + // enable_os_cache + // Files are opened normally, with the OS caching reads and writes. + // disable_os_cache + // This opens all files in no-cache mode. This corresponds to the + // OS not letting blocks for the files linger in the cache. This + // makes sense in order to avoid the bittorrent client to + // potentially evict all other processes' cache by simply handling + // high throughput and large files. If libtorrent's read cache is + // disabled, enabling this may reduce performance. + // + // One reason to disable caching is that it may help the operating + // system from growing its file cache indefinitely. + disk_io_write_mode, + disk_io_read_mode, + + // this is the first port to use for binding outgoing connections to. + // This is useful for users that have routers that allow QoS settings + // based on local port. when binding outgoing connections to specific + // ports, ``num_outgoing_ports`` is the size of the range. It should + // be more than a few + // + // .. warning:: setting outgoing ports will limit the ability to keep + // multiple connections to the same client, even for different + // torrents. It is not recommended to change this setting. Its main + // purpose is to use as an escape hatch for cheap routers with QoS + // capability but can only classify flows based on port numbers. + // + // It is a range instead of a single port because of the problems with + // failing to reconnect to peers if a previous socket to that peer and + // port is in ``TIME_WAIT`` state. + outgoing_port, + num_outgoing_ports, + + // ``peer_tos`` determines the TOS byte set in the IP header of every + // packet sent to peers (including web seeds). ``0x0`` means no marking, + // ``0x20`` represents the *QBone scavenger service*. For more + // details, see QBSS_. + // + // .. _`QBSS`: http://qbone.internet2.edu/qbss/ + peer_tos, + + // for auto managed torrents, these are the limits they are subject + // to. If there are too many torrents some of the auto managed ones + // will be paused until some slots free up. ``active_downloads`` and + // ``active_seeds`` controls how many active seeding and downloading + // torrents the queuing mechanism allows. The target number of active + // torrents is ``min(active_downloads + active_seeds, active_limit)``. + // ``active_downloads`` and ``active_seeds`` are upper limits on the + // number of downloading torrents and seeding torrents respectively. + // Setting the value to -1 means unlimited. + // + // For example if there are 10 seeding torrents and 10 downloading + // torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4, + // there will be 4 seeds active and 4 downloading torrents. If the + // settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, + // then there will be 2 downloading torrents and 4 seeding torrents + // active. Torrents that are not auto managed are not counted against + // these limits. + // + // ``active_checking`` is the limit of number of simultaneous checking + // torrents. + // + // ``active_limit`` is a hard limit on the number of active (auto + // managed) torrents. This limit also applies to slow torrents. + // + // ``active_dht_limit`` is the max number of torrents to announce to + // the DHT. + // + // ``active_tracker_limit`` is the max number of torrents to announce + // to their trackers. + // + // ``active_lsd_limit`` is the max number of torrents to announce to + // the local network over the local service discovery protocol. + // + // You can have more torrents *active*, even though they are not + // announced to the DHT, lsd or their tracker. If some peer knows + // about you for any reason and tries to connect, it will still be + // accepted, unless the torrent is paused, which means it won't accept + // any connections. + active_downloads, + active_seeds, + active_checking, + active_dht_limit, + active_tracker_limit, + active_lsd_limit, + active_limit, + +#if TORRENT_ABI_VERSION == 1 + // ``active_loaded_limit`` is the number of torrents that are allowed + // to be *loaded* at any given time. Note that a torrent can be active + // even though it's not loaded. If an unloaded torrents finds a peer + // that wants to access it, the torrent will be loaded on demand, + // using a user-supplied callback function. If the feature of + // unloading torrents is not enabled, this setting have no effect. If + // this limit is set to 0, it means unlimited. For more information, + // see dynamic-loading-of-torrent-files_. + active_loaded_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_active_loaded_limit, +#endif + + // ``auto_manage_interval`` is the number of seconds between the + // torrent queue is updated, and rotated. + auto_manage_interval, + + // this is the limit on the time a torrent has been an active seed + // (specified in seconds) before it is considered having met the seed + // limit criteria. See queuing_. + seed_time_limit, + + // ``auto_scrape_interval`` is the number of seconds between scrapes + // of queued torrents (auto managed and paused torrents). Auto managed + // torrents that are paused, are scraped regularly in order to keep + // track of their downloader/seed ratio. This ratio is used to + // determine which torrents to seed and which to pause. + // + // ``auto_scrape_min_interval`` is the minimum number of seconds + // between any automatic scrape (regardless of torrent). In case there + // are a large number of paused auto managed torrents, this puts a + // limit on how often a scrape request is sent. + auto_scrape_interval, + auto_scrape_min_interval, + + // ``max_peerlist_size`` is the maximum number of peers in the list of + // known peers. These peers are not necessarily connected, so this + // number should be much greater than the maximum number of connected + // peers. Peers are evicted from the cache when the list grows passed + // 90% of this limit, and once the size hits the limit, peers are no + // longer added to the list. If this limit is set to 0, there is no + // limit on how many peers we'll keep in the peer list. + // + // ``max_paused_peerlist_size`` is the max peer list size used for + // torrents that are paused. This can be used to save memory for paused + // torrents, since it's not as important for them to keep a large peer + // list. + max_peerlist_size, + max_paused_peerlist_size, + + // this is the minimum allowed announce interval for a tracker. This + // is specified in seconds and is used as a sanity check on what is + // returned from a tracker. It mitigates hammering mis-configured + // trackers. + min_announce_interval, + + // this is the number of seconds a torrent is considered active after + // it was started, regardless of upload and download speed. This is so + // that newly started torrents are not considered inactive until they + // have a fair chance to start downloading. + auto_manage_startup, + + // ``seeding_piece_quota`` is the number of pieces to send to a peer, + // when seeding, before rotating in another peer to the unchoke set. + seeding_piece_quota, + + // ``max_rejects`` is the number of piece requests we will reject in a + // row while a peer is choked before the peer is considered abusive + // and is disconnected. + max_rejects, + + // specifies the buffer sizes set on peer sockets. 0 means the OS + // default (i.e. don't change the buffer sizes). + // The socket buffer sizes are changed using setsockopt() with + // SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER. + recv_socket_buffer_size, + send_socket_buffer_size, + + // the max number of bytes a single peer connection's receive buffer is + // allowed to grow to. + max_peer_recv_buffer_size, + +#if TORRENT_ABI_VERSION == 1 + // ``file_checks_delay_per_block`` is the number of milliseconds to + // sleep in between disk read operations when checking torrents. + // This can be set to higher numbers to slow down the + // rate at which data is read from the disk while checking. This may + // be useful for background tasks that doesn't matter if they take a + // bit longer, as long as they leave disk I/O time for other + // processes. + file_checks_delay_per_block TORRENT_DEPRECATED_ENUM, +#else + deprecated_file_checks_delay_per_block, +#endif + + // ``read_cache_line_size`` is the number of blocks to read into the + // read cache when a read cache miss occurs. Setting this to 0 is + // essentially the same thing as disabling read cache. The number of + // blocks read into the read cache is always capped by the piece + // boundary. + // + // When a piece in the write cache has ``write_cache_line_size`` + // contiguous blocks in it, they will be flushed. Setting this to 1 + // effectively disables the write cache. + read_cache_line_size, + write_cache_line_size, + + // ``optimistic_disk_retry`` is the number of seconds from a disk + // write errors occur on a torrent until libtorrent will take it out + // of the upload mode, to test if the error condition has been fixed. + // + // libtorrent will only do this automatically for auto managed + // torrents. + // + // You can explicitly take a torrent out of upload only mode using + // set_upload_mode(). + optimistic_disk_retry, + + // ``max_suggest_pieces`` is the max number of suggested piece indices + // received from a peer that's remembered. If a peer floods suggest + // messages, this limit prevents libtorrent from using too much RAM. + max_suggest_pieces, + + // ``local_service_announce_interval`` is the time between local + // network announces for a torrent. + // This interval is specified in seconds. + local_service_announce_interval, + + // ``dht_announce_interval`` is the number of seconds between + // announcing torrents to the distributed hash table (DHT). + dht_announce_interval, + + // ``udp_tracker_token_expiry`` is the number of seconds libtorrent + // will keep UDP tracker connection tokens around for. This is + // specified to be 60 seconds. The higher this + // value is, the fewer packets have to be sent to the UDP tracker. In + // order for higher values to work, the tracker needs to be configured + // to match the expiration time for tokens. + udp_tracker_token_expiry, + +#if TORRENT_ABI_VERSION == 1 + // ``default_cache_min_age`` is the minimum number of seconds any read + // cache line is kept in the cache. This + // may be greater if ``guided_read_cache`` is enabled. Having a lower + // bound on the time a cache line stays in the cache is an attempt + // to avoid swapping the same pieces in and out of the cache in case + // there is a shortage of spare cache space. + default_cache_min_age TORRENT_DEPRECATED_ENUM, +#else + deprecated_default_cache_min_age, +#endif + + // ``num_optimistic_unchoke_slots`` is the number of optimistic + // unchoke slots to use. + // Having a higher number of optimistic unchoke slots mean you will + // find the good peers faster but with the trade-off to use up more + // bandwidth. 0 means automatic, where libtorrent opens up 20% of your + // allowed upload slots as optimistic unchoke slots. + num_optimistic_unchoke_slots, + +#if TORRENT_ABI_VERSION == 1 + // ``default_est_reciprocation_rate`` is the assumed reciprocation + // rate from peers when using the BitTyrant choker. If set too high, + // you will over-estimate your peers and be + // more altruistic while finding the true reciprocation rate, if it's + // set too low, you'll be too stingy and waste finding the true + // reciprocation rate. + // + // ``increase_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be increased by each unchoke + // interval a peer is still choking us back. + // This only applies to the BitTyrant choker. + // + // ``decrease_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be decreased by each unchoke + // interval a peer unchokes us. This only applies + // to the BitTyrant choker. + default_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, + increase_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, + decrease_est_reciprocation_rate TORRENT_DEPRECATED_ENUM, +#else + deprecated_default_est_reciprocation_rate, + deprecated_increase_est_reciprocation_rate, + deprecated_decrease_est_reciprocation_rate, +#endif + + // the max number of peers we accept from pex messages from a single + // peer. this limits the number of concurrent peers any of our peers + // claims to be connected to. If they claim to be connected to more + // than this, we'll ignore any peer that exceeds this limit + max_pex_peers, + + // ``tick_interval`` specifies the number of milliseconds between + // internal ticks. This is the frequency with which bandwidth quota is + // distributed to peers. It should not be more than one second (i.e. + // 1000 ms). Setting this to a low value (around 100) means higher + // resolution bandwidth quota distribution, setting it to a higher + // value saves CPU cycles. + tick_interval, + + // ``share_mode_target`` specifies the target share ratio for share + // mode torrents. If set to 3, we'll try to upload 3 + // times as much as we download. Setting this very high, will make it + // very conservative and you might end up not downloading anything + // ever (and not affecting your share ratio). It does not make any + // sense to set this any lower than 2. For instance, if only 3 peers + // need to download the rarest piece, it's impossible to download a + // single piece and upload it more than 3 times. If the + // share_mode_target is set to more than 3, nothing is downloaded. + share_mode_target, + + // ``upload_rate_limit`` and ``download_rate_limit`` sets + // the session-global limits of upload and download rate limits, in + // bytes per second. By default peers on the local network are not rate + // limited. + // + // A value of 0 means unlimited. + // + // For fine grained control over rate limits, including making them apply + // to local peers, see peer-classes_. + upload_rate_limit, + download_rate_limit, +#if TORRENT_ABI_VERSION == 1 + local_upload_rate_limit TORRENT_DEPRECATED_ENUM, + local_download_rate_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_local_upload_rate_limit, + deprecated_local_download_rate_limit, +#endif + + // ``dht_upload_rate_limit`` sets the rate limit on the DHT. This is + // specified in bytes per second. For busy boxes + // with lots of torrents that requires more DHT traffic, this should + // be raised. + dht_upload_rate_limit, + + // ``unchoke_slots_limit`` is the max number of unchoked peers in the + // session. The number of unchoke slots may be ignored depending on + // what ``choking_algorithm`` is set to. Setting this limit to -1 + // means unlimited, i.e. all peers will always be unchoked. + unchoke_slots_limit, + +#if TORRENT_ABI_VERSION == 1 + // ``half_open_limit`` sets the maximum number of half-open + // connections libtorrent will have when connecting to peers. A + // half-open connection is one where connect() has been called, but + // the connection still hasn't been established (nor failed). Windows + // XP Service Pack 2 sets a default, system wide, limit of the number + // of half-open connections to 10. So, this limit can be used to work + // nicer together with other network applications on that system. The + // default is to have no limit, and passing -1 as the limit, means to + // have no limit. When limiting the number of simultaneous connection + // attempts, peers will be put in a queue waiting for their turn to + // get connected. + half_open_limit TORRENT_DEPRECATED_ENUM, +#else + deprecated_half_open_limit, +#endif + + // ``connections_limit`` sets a global limit on the number of + // connections opened. The number of connections is set to a hard + // minimum of at least two per torrent, so if you set a too low + // connections limit, and open too many torrents, the limit will not + // be met. + connections_limit, + + // ``connections_slack`` is the number of incoming connections + // exceeding the connection limit to accept in order to potentially + // replace existing ones. + connections_slack, + + // ``utp_target_delay`` is the target delay for uTP sockets in + // milliseconds. A high value will make uTP connections more + // aggressive and cause longer queues in the upload bottleneck. It + // cannot be too low, since the noise in the measurements would cause + // it to send too slow. + // ``utp_gain_factor`` is the number of bytes the uTP congestion + // window can increase at the most in one RTT. + // If this is set too high, the congestion controller reacts + // too hard to noise and will not be stable, if it's set too low, it + // will react slow to congestion and not back off as fast. + // + // ``utp_min_timeout`` is the shortest allowed uTP socket timeout, + // specified in milliseconds. The + // timeout depends on the RTT of the connection, but is never smaller + // than this value. A connection times out when every packet in a + // window is lost, or when a packet is lost twice in a row (i.e. the + // resent packet is lost as well). + // + // The shorter the timeout is, the faster the connection will recover + // from this situation, assuming the RTT is low enough. + // ``utp_syn_resends`` is the number of SYN packets that are sent (and + // timed out) before giving up and closing the socket. + // ``utp_num_resends`` is the number of times a packet is sent (and + // lost or timed out) before giving up and closing the connection. + // ``utp_connect_timeout`` is the number of milliseconds of timeout + // for the initial SYN packet for uTP connections. For each timed out + // packet (in a row), the timeout is doubled. ``utp_loss_multiplier`` + // controls how the congestion window is changed when a packet loss is + // experienced. It's specified as a percentage multiplier for + // ``cwnd``. Do not change this value unless you know what you're doing. + // Never set it higher than 100. + utp_target_delay, + utp_gain_factor, + utp_min_timeout, + utp_syn_resends, + utp_fin_resends, + utp_num_resends, + utp_connect_timeout, +#if TORRENT_ABI_VERSION == 1 + utp_delayed_ack TORRENT_DEPRECATED_ENUM, +#else + deprecated_utp_delayed_ack, +#endif + utp_loss_multiplier, + + // The ``mixed_mode_algorithm`` determines how to treat TCP + // connections when there are uTP connections. Since uTP is designed + // to yield to TCP, there's an inherent problem when using swarms that + // have both TCP and uTP connections. If nothing is done, uTP + // connections would often be starved out for bandwidth by the TCP + // connections. This mode is ``prefer_tcp``. The ``peer_proportional`` + // mode simply looks at the current throughput and rate limits all TCP + // connections to their proportional share based on how many of the + // connections are TCP. This works best if uTP connections are not + // rate limited by the global rate limiter (which they aren't by + // default). + mixed_mode_algorithm, + + // ``listen_queue_size`` is the value passed in to listen() for the + // listen socket. It is the number of outstanding incoming connections + // to queue up while we're not actively waiting for a connection to be + // accepted. 5 should be sufficient for any + // normal client. If this is a high performance server which expects + // to receive a lot of connections, or used in a simulator or test, it + // might make sense to raise this number. It will not take affect + // until the ``listen_interfaces`` settings is updated. + listen_queue_size, + + // ``torrent_connect_boost`` is the number of peers to try to connect + // to immediately when the first tracker response is received for a + // torrent. This is a boost to given to new torrents to accelerate + // them starting up. The normal connect scheduler is run once every + // second, this allows peers to be connected immediately instead of + // waiting for the session tick to trigger connections. + // This may not be set higher than 255. + torrent_connect_boost, + + // ``alert_queue_size`` is the maximum number of alerts queued up + // internally. If alerts are not popped, the queue will eventually + // fill up to this level. Once the alert queue is full, additional + // alerts will be dropped, and not delivered to the client. Once the + // client drains the queue, new alerts may be delivered again. In order + // to know that alerts have been dropped, see + // session_handle::dropped_alerts(). + alert_queue_size, + + // ``max_metadata_size`` is the maximum allowed size (in bytes) to be + // received by the metadata extension, i.e. magnet links. + max_metadata_size, + +#if TORRENT_ABI_VERSION == 1 + // DEPRECATED: use aio_threads instead + + // ``hashing_threads`` is the number of threads to use for piece hash + // verification. For very high download rates, on + // machines with multiple cores, this could be incremented. Setting it + // higher than the number of CPU cores would presumably not provide + // any benefit of setting it to the number of cores. If it's set to 0, + // hashing is done in the disk thread. + hashing_threads TORRENT_DEPRECATED_ENUM, +#else + deprecated_hashing_threads, +#endif + + // the number of blocks to keep outstanding at any given time when + // checking torrents. Higher numbers give faster re-checks but uses + // more memory. Specified in number of 16 kiB blocks + checking_mem_usage, + + // if set to > 0, pieces will be announced to other peers before they + // are fully downloaded (and before they are hash checked). The + // intention is to gain 1.5 potential round trip times per downloaded + // piece. When non-zero, this indicates how many milliseconds in + // advance pieces should be announced, before they are expected to be + // completed. + predictive_piece_announce, + + // for some aio back-ends, ``aio_threads`` specifies the number of + // io-threads to use. + aio_threads, + +#if TORRENT_ABI_VERSION == 1 + // for some aio back-ends, ``aio_max`` specifies the max number of + // outstanding jobs. + aio_max TORRENT_DEPRECATED_ENUM, + + // .. note:: This is not implemented + // + // ``network_threads`` is the number of threads to use to call + // ``async_write_some`` (i.e. send) on peer connection sockets. When + // seeding at extremely high rates, this may become a bottleneck, and + // setting this to 2 or more may parallelize that cost. When using SSL + // torrents, all encryption for outgoing traffic is done within the + // socket send functions, and this will help parallelizing the cost of + // SSL encryption as well. + network_threads TORRENT_DEPRECATED_ENUM, + + // ``ssl_listen`` sets the listen port for SSL connections. If this is + // set to 0, no SSL listen port is opened. Otherwise a socket is + // opened on this port. This setting is only taken into account when + // opening the regular listen port, and won't re-open the listen + // socket simply by changing this setting. + ssl_listen TORRENT_DEPRECATED_ENUM, +#else + // hidden + deprecated_aio_max, + deprecated_network_threads, + deprecated_ssl_listen, +#endif + + // ``tracker_backoff`` determines how aggressively to back off from + // retrying failing trackers. This value determines *x* in the + // following formula, determining the number of seconds to wait until + // the next retry: + // + // delay = 5 + 5 * x / 100 * fails^2 + // + // This setting may be useful to make libtorrent more or less + // aggressive in hitting trackers. + tracker_backoff, + + // when a seeding torrent reaches either the share ratio (bytes up / + // bytes down) or the seed time ratio (seconds as seed / seconds as + // downloader) or the seed time limit (seconds as seed) it is + // considered done, and it will leave room for other torrents. These + // are specified as percentages. Torrents that are considered done will + // still be allowed to be seeded, they just won't have priority anymore. + // For more, see queuing_. + share_ratio_limit, + seed_time_ratio_limit, + + // peer_turnover is the percentage of peers to disconnect every + // turnover peer_turnover_interval (if we're at the peer limit), this + // is specified in percent when we are connected to more than limit * + // peer_turnover_cutoff peers disconnect peer_turnover fraction of the + // peers. It is specified in percent peer_turnover_interval is the + // interval (in seconds) between optimistic disconnects if the + // disconnects happen and how many peers are disconnected is + // controlled by peer_turnover and peer_turnover_cutoff + peer_turnover, + peer_turnover_cutoff, + peer_turnover_interval, + + // this setting controls the priority of downloading torrents over + // seeding or finished torrents when it comes to making peer + // connections. Peer connections are throttled by the connection_speed + // and the half-open connection limit. This makes peer connections a + // limited resource. Torrents that still have pieces to download are + // prioritized by default, to avoid having many seeding torrents use + // most of the connection attempts and only give one peer every now + // and then to the downloading torrent. libtorrent will loop over the + // downloading torrents to connect a peer each, and every n:th + // connection attempt, a finished torrent is picked to be allowed to + // connect to a peer. This setting controls n. + connect_seed_every_n_download, + + // the max number of bytes to allow an HTTP response to be when + // announcing to trackers or downloading .torrent files via the + // ``url`` provided in ``add_torrent_params``. + max_http_recv_buffer_size, + + // if binding to a specific port fails, should the port be incremented + // by one and tried again? This setting specifies how many times to + // retry a failed port bind + max_retry_port_bind, + + // a bitmask combining flags from alert_category_t defining which + // kinds of alerts to receive + alert_mask, + + // control the settings for incoming and outgoing connections + // respectively. see enc_policy enum for the available options. + // Keep in mind that protocol encryption degrades performance in + // several respects: + // + // 1. It prevents "zero copy" disk buffers being sent to peers, since + // each peer needs to mutate the data (i.e. encrypt it) the data + // must be copied per peer connection rather than sending the same + // buffer to multiple peers. + // 2. The encryption itself requires more CPU than plain bittorrent + // protocol. The highest cost is the Diffie Hellman exchange on + // connection setup. + // 3. The encryption handshake adds several round-trips to the + // connection setup, and delays transferring data. + out_enc_policy, + in_enc_policy, + + // determines the encryption level of the connections. This setting + // will adjust which encryption scheme is offered to the other peer, + // as well as which encryption scheme is selected by the client. See + // enc_level enum for options. + allowed_enc_level, + + // the download and upload rate limits for a torrent to be considered + // active by the queuing mechanism. A torrent whose download rate is + // less than ``inactive_down_rate`` and whose upload rate is less than + // ``inactive_up_rate`` for ``auto_manage_startup`` seconds, is + // considered inactive, and another queued torrent may be started. + // This logic is disabled if ``dont_count_slow_torrents`` is false. + inactive_down_rate, + inactive_up_rate, + + // proxy to use. see proxy_type_t. + proxy_type, + + // the port of the proxy server + proxy_port, + + // sets the i2p_ SAM bridge port to connect to. set the hostname with + // the ``i2p_hostname`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_port, + + // this determines the max number of volatile disk cache blocks. If the + // number of volatile blocks exceed this limit, other volatile blocks + // will start to be evicted. A disk cache block is volatile if it has + // low priority, and should be one of the first blocks to be evicted + // under pressure. For instance, blocks pulled into the cache as the + // result of calculating a piece hash are volatile. These blocks don't + // represent potential interest among peers, so the value of keeping + // them in the cache is limited. + cache_size_volatile, + + // The maximum request range of an url seed in bytes. This value + // defines the largest possible sequential web seed request. Lower values + // are possible but will be ignored if they are lower then piece size. + // This value should be related to your download speed to prevent + // libtorrent from creating too many expensive http requests per + // second. You can select a value as high as you want but keep in mind + // that libtorrent can't create parallel requests if the first request + // did already select the whole file. + // If you combine bittorrent seeds with web seeds and pick strategies + // like rarest first you may find your web seed requests split into + // smaller parts because we don't download already picked pieces + // twice. + urlseed_max_request_bytes, + + // time to wait until a new retry of a web seed name lookup + web_seed_name_lookup_retry, + + // the number of seconds between closing the file opened the longest + // ago. 0 means to disable the feature. The purpose of this is to + // periodically close files to trigger the operating system flushing + // disk cache. Specifically it has been observed to be required on + // windows to not have the disk cache grow indefinitely. + // This defaults to 120 seconds on windows, and disabled on other + // systems. + close_file_interval, + + // When uTP experiences packet loss, it will reduce the congestion + // window, and not reduce it again for this many milliseconds, even if + // experiencing another lost packet. + utp_cwnd_reduce_timer, + + // the max number of web seeds to have connected per torrent at any + // given time. + max_web_seed_connections, + + // the number of seconds before the internal host name resolver + // considers a cache value timed out, negative values are interpreted + // as zero. + resolver_cache_timeout, + + // specify the not-sent low watermark for socket send buffers. This + // corresponds to the, Linux-specific, ``TCP_NOTSENT_LOWAT`` TCP socket + // option. + send_not_sent_low_watermark, + + // the rate based choker compares the upload rate to peers against a + // threshold that increases proportionally by its size for every + // peer it visits, visiting peers in decreasing upload rate. The + // number of upload slots is determined by the number of peers whose + // upload rate exceeds the threshold. This option sets the start + // value for this threshold. A higher value leads to fewer unchoke + // slots, a lower value leads to more. + rate_choker_initial_threshold, + + // The expiration time of UPnP port-mappings, specified in seconds. 0 + // means permanent lease. Some routers do not support expiration times + // on port-maps (nor correctly returning an error indicating lack of + // support). In those cases, set this to 0. Otherwise, don't set it any + // lower than 5 minutes. + upnp_lease_duration, + + // limits the number of concurrent HTTP tracker announces. Once the + // limit is hit, tracker requests are queued and issued when an + // outstanding announce completes. + max_concurrent_http_announces, + + max_int_setting_internal + }; + + // hidden + enum settings_counts_t : int + { + num_string_settings = int(max_string_setting_internal) - int(string_type_base), + num_bool_settings = int(max_bool_setting_internal) - int(bool_type_base), + num_int_settings = int(max_int_setting_internal) - int(int_type_base) + }; + + enum suggest_mode_t : std::uint8_t { no_piece_suggestions = 0, suggest_read_cache = 1 }; + + enum choking_algorithm_t : std::uint8_t + { + // This is the traditional choker with a fixed number of unchoke + // slots (as specified by settings_pack::unchoke_slots_limit). + fixed_slots_choker = 0, + + // This opens up unchoke slots based on the upload rate achieved to + // peers. The more slots that are opened, the marginal upload rate + // required to open up another slot increases. Configure the initial + // threshold with settings_pack::rate_choker_initial_threshold. + // + // For more information, see `rate based choking`_. + rate_based_choker = 2, +#if TORRENT_ABI_VERSION == 1 + bittyrant_choker TORRENT_DEPRECATED_ENUM = 3 +#else + deprecated_bittyrant_choker = 3 +#endif + }; + + enum seed_choking_algorithm_t : std::uint8_t + { + // which round-robins the peers that are unchoked + // when seeding. This distributes the upload bandwidth uniformly and + // fairly. It minimizes the ability for a peer to download everything + // without redistributing it. + round_robin, + + // unchokes the peers we can send to the fastest. This might be a + // bit more reliable in utilizing all available capacity. + fastest_upload, + + // prioritizes peers who have just started or are + // just about to finish the download. The intention is to force + // peers in the middle of the download to trade with each other. + // This does not just take into account the pieces a peer is + // reporting having downloaded, but also the pieces we have sent + // to it. + anti_leech + }; + + enum io_buffer_mode_t : std::uint8_t + { + enable_os_cache = 0, +#if TORRENT_ABI_VERSION == 1 + disable_os_cache_for_aligned_files TORRENT_DEPRECATED_ENUM = 2, +#else + deprecated_disable_os_cache_for_aligned_files = 1, +#endif + disable_os_cache = 2 + }; + + enum bandwidth_mixed_algo_t : std::uint8_t + { + // disables the mixed mode bandwidth balancing + prefer_tcp = 0, + + // does not throttle uTP, throttles TCP to the same proportion + // of throughput as there are TCP connections + peer_proportional = 1 + }; + + // the encoding policy options for use with + // settings_pack::out_enc_policy and settings_pack::in_enc_policy. + enum enc_policy : std::uint8_t + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + pe_forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + pe_enabled, + + // only non-encrypted connections are allowed. + pe_disabled + }; + + // the encryption levels, to be used with + // settings_pack::allowed_enc_level. + enum enc_level : std::uint8_t + { + // use only plain text encryption + pe_plaintext = 1, + // use only RC4 encryption + pe_rc4 = 2, + // allow both + pe_both = 3 + }; + + enum proxy_type_t : std::uint8_t + { + // No proxy server is used and all other fields are ignored. + none, + + // The server is assumed to be a `SOCKS4 server`_ that requires a + // username. + // + // .. _`SOCKS4 server`: http://www.ufasoft.com/doc/socks4_protocol.htm + socks4, + + // The server is assumed to be a SOCKS5 server (`RFC 1928`_) that does + // not require any authentication. The username and password are + // ignored. + // + // .. _`RFC 1928`: http://www.faqs.org/rfcs/rfc1928.html + socks5, + + // The server is assumed to be a SOCKS5 server that supports plain + // text username and password authentication (`RFC 1929`_). The + // username and password specified may be sent to the proxy if it + // requires. + // + // .. _`RFC 1929`: http://www.faqs.org/rfcs/rfc1929.html + socks5_pw, + + // The server is assumed to be an HTTP proxy. If the transport used + // for the connection is non-HTTP, the server is assumed to support + // the CONNECT_ method. i.e. for web seeds and HTTP trackers, a plain + // proxy will suffice. The proxy is assumed to not require + // authorization. The username and password will not be used. + // + // .. _CONNECT: http://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01 + http, + + // The server is assumed to be an HTTP proxy that requires user + // authorization. The username and password will be sent to the proxy. + http_pw, + + // route through a i2p SAM proxy + i2p_proxy + }; + private: + + std::vector> m_strings; + std::vector> m_ints; + std::vector> m_bools; + }; +} + +#endif diff --git a/include/libtorrent/sha1.hpp b/include/libtorrent/sha1.hpp new file mode 100644 index 0000000..0912241 --- /dev/null +++ b/include/libtorrent/sha1.hpp @@ -0,0 +1,42 @@ +/* +SHA-1 C++ conversion + +original version: + +SHA-1 in C +By Steve Reid +100% Public Domain + +changelog at the end of sha1.cpp +*/ + +#ifndef TORRENT_SHA1_HPP_INCLUDED +#define TORRENT_SHA1_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CRYPTOAPI \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha1_ctx + { + std::uint32_t state[5]; + std::uint32_t count[2]; + std::uint8_t buffer[64]; + }; + + // we don't want these to clash with openssl's libcrypto + TORRENT_EXTRA_EXPORT void SHA1_init(sha1_ctx* context); + TORRENT_EXTRA_EXPORT void SHA1_update(sha1_ctx* context + , std::uint8_t const* data, size_t len); + TORRENT_EXTRA_EXPORT void SHA1_final(std::uint8_t* digest, sha1_ctx* context); +} + +#endif +#endif diff --git a/include/libtorrent/sha1_hash.hpp b/include/libtorrent/sha1_hash.hpp new file mode 100644 index 0000000..61a6fe5 --- /dev/null +++ b/include/libtorrent/sha1_hash.hpp @@ -0,0 +1,313 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SHA1_HASH_HPP_INCLUDED +#define TORRENT_SHA1_HASH_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/ffs.hpp" +#include "libtorrent/span.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent { + + // TODO: find a better place for these functions +namespace aux { + + TORRENT_EXTRA_EXPORT void bits_shift_left(span number, int n); + TORRENT_EXTRA_EXPORT void bits_shift_right(span number, int n); + } + + // This type holds an N digest or any other kind of N bits + // sequence. It implements a number of convenience functions, such + // as bit operations, comparison operators etc. + // + // This data structure is 32 bits aligned, like it's the case for + // each SHA-N specification. + template + class digest32 + { + static_assert(N % 32 == 0, "N must be a multiple of 32"); + static constexpr std::ptrdiff_t number_size = N / 32; + static constexpr int bits_in_byte = 8; + public: + + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + // the size of the hash in bytes + static constexpr difference_type size() noexcept { return N / bits_in_byte; } + + // constructs an all-zero digest + digest32() noexcept { clear(); } + + digest32(digest32 const&) noexcept = default; + digest32& operator=(digest32 const&) noexcept = default; + + // returns an all-F digest. i.e. the maximum value + // representable by an N bit number (N/8 bytes). This is + // a static member function. + static digest32 (max)() noexcept + { + digest32 ret; + ret.m_number.fill(0xffffffff); + return ret; + } + + // returns an all-zero digest. i.e. the minimum value + // representable by an N bit number (N/8 bytes). This is + // a static member function. + static digest32 (min)() noexcept + { + digest32 ret; + // all bits are already 0 + return ret; + } + + // copies N/8 bytes from the pointer provided, into the digest. + // The passed in string MUST be at least N/8 bytes. 0-terminators + // are ignored, ``s`` is treated like a raw memory buffer. + explicit digest32(char const* s) noexcept + { + if (s == nullptr) clear(); + else std::memcpy(m_number.data(), s, size()); + } +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + explicit digest32(std::string const& s) + { + assign(s.data()); + } +#endif + explicit digest32(span s) noexcept + { + assign(s); + } + void assign(span s) noexcept + { + TORRENT_ASSERT(s.size() >= N / bits_in_byte); + auto const sl = s.size() < size() ? s.size() : size(); + std::memcpy(m_number.data(), s.data(), static_cast(sl)); + } + void assign(char const* str) noexcept { std::memcpy(m_number.data(), str, size()); } + + char const* data() const noexcept { return reinterpret_cast(m_number.data()); } + char* data() noexcept { return reinterpret_cast(m_number.data()); } + + // set the digest to all zeros. + void clear() noexcept { m_number.fill(0); } + + // return true if the digest is all zero. + bool is_all_zeros() const noexcept + { + return std::all_of(m_number.begin(), m_number.end() + , [](std::uint32_t v) { return v == 0; }); + } + + // shift left ``n`` bits. + digest32& operator<<=(int const n) noexcept + { + aux::bits_shift_left(m_number, n); + return *this; + } + + // shift right ``n`` bits. + digest32& operator>>=(int const n) noexcept + { + aux::bits_shift_right(m_number, n); + return *this; + } + + // standard comparison operators + bool operator==(digest32 const& n) const noexcept + { + return std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin()); + } + bool operator!=(digest32 const& n) const noexcept + { + return !std::equal(n.m_number.begin(), n.m_number.end(), m_number.begin()); + } + bool operator<(digest32 const& n) const noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + { + std::uint32_t const lhs = aux::network_to_host(boost::get<0>(v)); + std::uint32_t const rhs = aux::network_to_host(boost::get<1>(v)); + if (lhs < rhs) return true; + if (lhs > rhs) return false; + } + return false; + } + + int count_leading_zeroes() const noexcept + { + return aux::count_leading_zeros(m_number); + } + + // returns a bit-wise negated copy of the digest + digest32 operator~() const noexcept + { + digest32 ret; + for (auto const v : boost::combine(m_number, ret.m_number)) + boost::get<1>(v) = ~boost::get<0>(v); + return ret; + } + + // returns the bit-wise XOR of the two digests. + digest32 operator^(digest32 const& n) const noexcept + { + digest32 ret = *this; + ret ^= n; + return ret; + } + + // in-place bit-wise XOR with the passed in digest. + digest32& operator^=(digest32 const& n) noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) ^= boost::get<1>(v); + return *this; + } + + // returns the bit-wise AND of the two digests. + digest32 operator&(digest32 const& n) const noexcept + { + digest32 ret = *this; + ret &= n; + return ret; + } + + // in-place bit-wise AND of the passed in digest + digest32& operator&=(digest32 const& n) noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) &= boost::get<1>(v); + return *this; + } + + // in-place bit-wise OR of the two digests. + digest32& operator|=(digest32 const& n) noexcept + { + for (auto const v : boost::combine(m_number, n.m_number)) + boost::get<0>(v) |= boost::get<1>(v); + return *this; + } + + // accessors for specific bytes + std::uint8_t& operator[](index_type i) noexcept + { + TORRENT_ASSERT(i < size()); + return reinterpret_cast(m_number.data())[i]; + } + std::uint8_t const& operator[](index_type i) const noexcept + { + TORRENT_ASSERT(i < size()); + return reinterpret_cast(m_number.data())[i]; + } + + using const_iterator = std::uint8_t const*; + using iterator = std::uint8_t*; + + // start and end iterators for the hash. The value type + // of these iterators is ``std::uint8_t``. + const_iterator begin() const + { return reinterpret_cast(m_number.data()); } + const_iterator end() const + { return reinterpret_cast(m_number.data()) + size(); } + iterator begin() + { return reinterpret_cast(m_number.data()); } + iterator end() + { return reinterpret_cast(m_number.data()) + size(); } + + // return a copy of the N/8 bytes representing the digest as a std::string. + // It's still a binary string with N/8 binary characters. + std::string to_string() const + { + return std::string(reinterpret_cast(m_number.data()), size()); + } + + private: + + std::array m_number; + + }; + + // This type holds a SHA-1 digest or any other kind of 20 byte + // sequence. It implements a number of convenience functions, such + // as bit operations, comparison operators etc. + // + // In libtorrent it is primarily used to hold info-hashes, piece-hashes, + // peer IDs, node IDs etc. + using sha1_hash = digest32<160>; + +#if TORRENT_USE_IOSTREAM + + // print a sha1_hash object to an ostream as 40 hexadecimal digits + TORRENT_EXPORT std::ostream& operator<<(std::ostream& os, sha1_hash const& peer); + + // read 40 hexadecimal digits from an istream into a sha1_hash + TORRENT_EXPORT std::istream& operator>>(std::istream& is, sha1_hash& peer); + +#endif // TORRENT_USE_IOSTREAM +} + +namespace std +{ + template <> + struct hash + { + std::size_t operator()(libtorrent::sha1_hash const& k) const + { + std::size_t ret; + // this is OK because sha1_hash is already a hash + std::memcpy(&ret, k.data(), sizeof(ret)); + return ret; + } + }; +} + +#endif // TORRENT_SHA1_HASH_HPP_INCLUDED diff --git a/include/libtorrent/sha512.hpp b/include/libtorrent/sha512.hpp new file mode 100644 index 0000000..ffe93ff --- /dev/null +++ b/include/libtorrent/sha512.hpp @@ -0,0 +1,30 @@ +#ifndef TORRENT_SHA512_HPP_INCLUDED +#define TORRENT_SHA512_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +#include + +namespace libtorrent { + + struct sha512_ctx + { + std::uint64_t length; + std::uint64_t state[8]; + std::size_t curlen; + std::uint8_t buf[128]; + }; + + TORRENT_EXTRA_EXPORT int SHA512_init(sha512_ctx* context); + TORRENT_EXTRA_EXPORT int SHA512_update(sha512_ctx* context + , std::uint8_t const* data, std::size_t len); + TORRENT_EXTRA_EXPORT int SHA512_final(std::uint8_t* digest, sha512_ctx* context); +} + +#endif +#endif diff --git a/include/libtorrent/sliding_average.hpp b/include/libtorrent/sliding_average.hpp new file mode 100644 index 0000000..aeab362 --- /dev/null +++ b/include/libtorrent/sliding_average.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SLIDING_AVERAGE_HPP_INCLUDED +#define TORRENT_SLIDING_AVERAGE_HPP_INCLUDED + +#include +#include // for std::abs +#include + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// an exponential moving average accumulator. Add samples to it and it keeps +// track of a moving mean value and an average deviation +template +struct sliding_average +{ + static_assert(std::is_integral::value, "template argument must be integral"); + + sliding_average(): m_mean(0), m_average_deviation(0), m_num_samples(0) {} + sliding_average(sliding_average const&) = default; + sliding_average& operator=(sliding_average const&) = default; + + void add_sample(Int s) + { + TORRENT_ASSERT(s < std::numeric_limits::max() / 64); + // fixed point + s *= 64; + Int const deviation = (m_num_samples > 0) ? std::abs(m_mean - s) : 0; + + if (m_num_samples < inverted_gain) + ++m_num_samples; + + m_mean += (s - m_mean) / m_num_samples; + + if (m_num_samples > 1) { + // the exact same thing for deviation off the mean except -1 on + // the samples, because the number of deviation samples always lags + // behind by 1 (you need to actual samples to have a single deviation + // sample). + m_average_deviation += (deviation - m_average_deviation) / (m_num_samples - 1); + } + } + + Int mean() const { return m_num_samples > 0 ? (m_mean + 32) / 64 : 0; } + Int avg_deviation() const { return m_num_samples > 1 ? (m_average_deviation + 32) / 64 : 0; } + int num_samples() const { return m_num_samples; } + +private: + // both of these are fixed point values (* 64) + Int m_mean = 0; + Int m_average_deviation = 0; + // the number of samples we have received, but no more than inverted_gain + // this is the effective inverted_gain + int m_num_samples = 0; +}; + +} + +#endif diff --git a/include/libtorrent/socket.hpp b/include/libtorrent/socket.hpp new file mode 100644 index 0000000..8254a61 --- /dev/null +++ b/include/libtorrent/socket.hpp @@ -0,0 +1,267 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_HPP_INCLUDED +#define TORRENT_SOCKET_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// if building as Objective C++, asio's template +// parameters Protocol has to be renamed to avoid +// colliding with keywords + +#ifdef __OBJC__ +#define Protocol Protocol_ +#endif + +#if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN +// asio assumes that the windows error codes are defined already +#include +#endif + +#include +#include +#include +#include + +#ifdef __OBJC__ +#undef Protocol +#endif + +#if defined TORRENT_BUILD_SIMULATOR +#include "simulator/simulator.hpp" +#endif + +#if TORRENT_USE_NETLINK +#include +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +// NETLINK_NO_ENOBUFS exists at least since android 2.3, but is not exposed +#if defined TORRENT_ANDROID && !defined NETLINK_NO_ENOBUFS +#define NETLINK_NO_ENOBUFS 5 +#endif +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using udp = sim::asio::ip::udp; + using tcp = sim::asio::ip::tcp; + using sim::asio::async_write; + using sim::asio::async_read; + using null_buffers = sim::asio::null_buffers; +#else + using tcp = boost::asio::ip::tcp; + using udp = boost::asio::ip::udp; + using boost::asio::async_write; + using boost::asio::async_read; + using null_buffers = boost::asio::null_buffers; +#endif + + // internal + inline udp::endpoint make_udp(tcp::endpoint const ep) + { return {ep.address(), ep.port()}; } + + // internal + inline tcp::endpoint make_tcp(udp::endpoint const ep) + { return {ep.address(), ep.port()}; } + +#ifdef TORRENT_WINDOWS + +#ifndef PROTECTION_LEVEL_UNRESTRICTED +#define PROTECTION_LEVEL_UNRESTRICTED 10 +#endif + +#ifndef IPV6_PROTECTION_LEVEL +#define IPV6_PROTECTION_LEVEL 30 +#endif + + struct v6_protection_level + { + explicit v6_protection_level(int level): m_value(level) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_PROTECTION_LEVEL; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + + struct exclusive_address_use + { + explicit exclusive_address_use(int enable): m_value(enable) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return SO_EXCLUSIVEADDRUSE; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif // TORRENT_WINDOWS + +#ifdef IPV6_TCLASS + struct traffic_class + { + explicit traffic_class(char val): m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IPV6; } + template + int name(Protocol const&) const { return IPV6_TCLASS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif + + struct type_of_service + { +#ifdef _WIN32 + using tos_t = DWORD; +#else + using tos_t = int; +#endif + explicit type_of_service(char val) : m_value(tos_t(val)) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_TOS; } + template + tos_t const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + tos_t m_value; + }; + +#if defined IP_DONTFRAG || defined IP_MTU_DISCOVER || defined IP_DONTFRAGMENT +#define TORRENT_HAS_DONT_FRAGMENT +#endif + +#ifdef TORRENT_HAS_DONT_FRAGMENT + + // the order of these preprocessor tests matters. Windows defines both + // IP_DONTFRAGMENT and IP_MTU_DISCOVER, but the latter is not supported + // in general, the simple option of just setting the DF bit is preferred, if + // it's available +#if defined IP_DONTFRAG || defined IP_DONTFRAGMENT + + struct dont_fragment + { + explicit dont_fragment(bool val) : m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const +#if defined IP_DONTFRAG + { return IP_DONTFRAG; } +#else // defined IP_DONTFRAGMENT + { return IP_DONTFRAGMENT; } +#endif + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + +#else + + // this is the fallback mechanism using the IP_MTU_DISCOVER option, which + // does a little bit more than we want, it makes the kernel track an estimate + // of the MTU and rejects packets immediately if they are believed to exceed + // it. + struct dont_fragment + { + explicit dont_fragment(bool val) + : m_value(val ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT) {} + template + int level(Protocol const&) const { return IPPROTO_IP; } + template + int name(Protocol const&) const { return IP_MTU_DISCOVER; } + template + int const* data(Protocol const&) const { return &m_value; } + template + size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; + +#endif // IP_DONTFRAG vs. IP_MTU_DISCOVER + +#endif // TORRENT_HAS_DONT_FRAGMENT + +#if TORRENT_USE_NETLINK + struct no_enobufs + { + explicit no_enobufs(bool val) : m_value(val) {} + template + int level(Protocol const&) const { return SOL_NETLINK; } + template + int name(Protocol const&) const { return NETLINK_NO_ENOBUFS; } + template + int const* data(Protocol const&) const { return &m_value; } + template + std::size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif // TORRENT_USE_NETLINK + +#ifdef TCP_NOTSENT_LOWAT + struct tcp_notsent_lowat + { + explicit tcp_notsent_lowat(int val) : m_value(val) {} + template + int level(Protocol const&) const { return IPPROTO_TCP; } + template + int name(Protocol const&) const { return TCP_NOTSENT_LOWAT; } + template + int const* data(Protocol const&) const { return &m_value; } + template + std::size_t size(Protocol const&) const { return sizeof(m_value); } + int m_value; + }; +#endif +} + +#endif // TORRENT_SOCKET_HPP_INCLUDED diff --git a/include/libtorrent/socket_io.hpp b/include/libtorrent/socket_io.hpp new file mode 100644 index 0000000..364d6c0 --- /dev/null +++ b/include/libtorrent/socket_io.hpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKET_IO_HPP_INCLUDED +#define TORRENT_SOCKET_IO_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/string_view.hpp" +#include + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::string print_address(address const& addr); + TORRENT_EXTRA_EXPORT std::string print_endpoint(address const& addr, int port); + TORRENT_EXTRA_EXPORT std::string print_endpoint(tcp::endpoint const& ep); + TORRENT_EXTRA_EXPORT std::string print_endpoint(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT tcp::endpoint parse_endpoint(string_view str, error_code& ec); + + TORRENT_EXTRA_EXPORT std::string address_to_bytes(address const& a); + TORRENT_EXTRA_EXPORT std::string endpoint_to_bytes(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT sha1_hash hash_address(address const& ip); + +namespace detail { + + template + std::size_t address_size(Proto p) + { + if (p == Proto::v6()) + return std::tuple_size::value; + else + return std::tuple_size::value; + } + + template + void write_address(address const& a, OutIt&& out) + { + if (a.is_v4()) + { + write_uint32(a.to_v4().to_ulong(), out); + } + else if (a.is_v6()) + { + for (auto b : a.to_v6().to_bytes()) + write_uint8(b, out); + } + } + + template + address read_v4_address(InIt&& in) + { + std::uint32_t const ip = read_uint32(in); + return address_v4(ip); + } + + template + address read_v6_address(InIt&& in) + { + address_v6::bytes_type bytes; + for (auto& b : bytes) + b = read_uint8(in); + return address_v6(bytes); + } + + template + void write_endpoint(Endpoint const& e, OutIt&& out) + { + write_address(e.address(), out); + write_uint16(e.port(), out); + } + + template + Endpoint read_v4_endpoint(InIt&& in) + { + address addr = read_v4_address(in); + std::uint16_t port = read_uint16(in); + return Endpoint(addr, port); + } + + template + Endpoint read_v6_endpoint(InIt&& in) + { + address addr = read_v6_address(in); + std::uint16_t port = read_uint16(in); + return Endpoint(addr, port); + } + + template + std::vector read_endpoint_list(libtorrent::bdecode_node const& n) + { + std::vector ret; + if (n.type() != bdecode_node::list_t) return ret; + for (int i = 0; i < n.list_size(); ++i) + { + bdecode_node e = n.list_at(i); + if (e.type() != bdecode_node::string_t) return ret; + if (e.string_length() < 6) continue; + char const* in = e.string_ptr(); + if (e.string_length() == 6) + ret.push_back(read_v4_endpoint(in)); + else if (e.string_length() == 18) + ret.push_back(read_v6_endpoint(in)); + } + return ret; + } +} // namespace detail + +} + +#endif diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp new file mode 100644 index 0000000..941a340 --- /dev/null +++ b/include/libtorrent/socks5_stream.hpp @@ -0,0 +1,201 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SOCKS5_STREAM_HPP_INCLUDED +#define TORRENT_SOCKS5_STREAM_HPP_INCLUDED + +#include + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_ip_address +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" // for to_string + +namespace libtorrent { + +namespace socks_error { + + // SOCKS5 error values. If an error_code has the + // socks error category (get_socks_category()), these + // are the error values. + enum socks_error_code + { + no_error = 0, + unsupported_version, + unsupported_authentication_method, + unsupported_authentication_version, + authentication_error, + username_required, + general_failure, + command_not_supported, + no_identd, + identd_error, + + num_errors + }; + + // internal + TORRENT_EXPORT boost::system::error_code make_error_code(socks_error_code e); + +} // namespace socks_error + +// returns the error_category for SOCKS5 errors +TORRENT_EXPORT boost::system::error_category& socks_category(); + +#if TORRENT_ABI_VERSION == 1 +TORRENT_DEPRECATED +inline boost::system::error_category& get_socks_category() +{ return socks_category(); } +#endif + +class socks5_stream : public proxy_base +{ +public: + + // commands + enum { + socks5_connect = 1, + socks5_udp_associate = 3 + }; + + explicit socks5_stream(io_service& io_service) + : proxy_base(io_service) + , m_version(5) + , m_command(socks5_connect) + {} + + void set_version(int v) { m_version = v; } + + void set_command(int c) + { + TORRENT_ASSERT(c == socks5_connect || c == socks5_udp_associate); + m_command = c; + } + + void set_username(std::string const& user + , std::string const& password) + { + m_user = user; + m_password = password; + } + + void set_dst_name(std::string const& host) + { + // if this assert trips, set_dst_name() is called wth an IP address rather + // than a hostname. Instead, resolve the IP into an address and pass it to + // async_connect instead + TORRENT_ASSERT(!is_ip_address(host)); + m_dst_name = host; + if (m_dst_name.size() > 255) + m_dst_name.resize(255); + } + + void close(error_code& ec) + { + m_dst_name.clear(); + proxy_base::close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_dst_name.clear(); + proxy_base::close(); + } +#endif + + // TODO: 2 add async_connect() that takes a hostname and port as well + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + // make sure we don't try to connect to INADDR_ANY. binding is fine, + // and using a hostname is fine on SOCKS version 5. + TORRENT_ASSERT(endpoint.address() != address() + || (!m_dst_name.empty() && m_version == 5)); + + m_remote_endpoint = endpoint; + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to proxy server + // 3. if version == 5: + // 3.1 send SOCKS5 authentication method message + // 3.2 read SOCKS5 authentication response + // 3.3 send username+password + // 4. send SOCKS command message + + using std::placeholders::_1; + using std::placeholders::_2; + ADD_OUTSTANDING_ASYNC("socks5_stream::name_lookup"); + tcp::resolver::query q(m_hostname, to_string(m_port).data()); + m_resolver.async_resolve(q, std::bind( + &socks5_stream::name_lookup, this, _1, _2, handler_type(std::move(handler)))); + } + +private: + + void name_lookup(error_code const& e, tcp::resolver::iterator i + , handler_type h); + void connected(error_code const& e, handler_type h); + void handshake1(error_code const& e, handler_type h); + void handshake2(error_code const& e, handler_type h); + void handshake3(error_code const& e, handler_type h); + void handshake4(error_code const& e, handler_type h); + void socks_connect(handler_type h); + void connect1(error_code const& e, handler_type h); + void connect2(error_code const& e, handler_type h); + void connect3(error_code const& e, handler_type h); + + // send and receive buffer + std::vector m_buffer; + // proxy authentication + std::string m_user; + std::string m_password; + std::string m_dst_name; + + int m_version; + + // the socks command to send for this connection (connect or udp associate) + int m_command; +}; + +} + +namespace boost { namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} } + +#endif diff --git a/include/libtorrent/span.hpp b/include/libtorrent/span.hpp new file mode 100644 index 0000000..982264e --- /dev/null +++ b/include/libtorrent/span.hpp @@ -0,0 +1,191 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SPAN_HPP_INCLUDED +#define TORRENT_SPAN_HPP_INCLUDED + +#include +#include +#include +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +namespace aux { + + template + struct compatible_type + { + // conversions that are OK + // int -> int + // int const -> int const + // int -> int const + // int -> int volatile + // int -> int const volatile + // int volatile -> int const volatile + // int const -> int const volatile + + static const bool value = std::is_same::value + || std::is_same::type>::value + || std::is_same::type>::value + || std::is_same::type>::value + ; + }; +} + + template + struct span + { + using difference_type = std::ptrdiff_t; + using index_type = std::ptrdiff_t; + + span() noexcept : m_ptr(nullptr), m_len(0) {} + + template ::value>::type> + span(span const& v) noexcept // NOLINT + : m_ptr(v.data()), m_len(v.size()) {} + + span(T& p) noexcept : m_ptr(&p), m_len(1) {} // NOLINT + span(T* p, difference_type const l) noexcept : m_ptr(p), m_len(l) // NOLINT + { TORRENT_ASSERT(l >= 0); } + + template + span(std::array& arr) noexcept // NOLINT + : m_ptr(arr.data()), m_len(static_cast(arr.size())) {} + + // this is necessary until C++17, where data() returns a non-const pointer + template + span(std::basic_string& str) noexcept // NOLINT + : m_ptr(&str[0]), m_len(static_cast(str.size())) {} + + template + span(U (&arr)[N]) noexcept // NOLINT + : m_ptr(&arr[0]), m_len(N) {} + + // anything with a .data() member function is considered a container + // but only if the value type is compatible with T + template ().data())>::type + , typename = typename std::enable_if::value>::type> + span(Cont& c) // NOLINT + : m_ptr(c.data()), m_len(static_cast(c.size())) {} + + // allow construction from const containers if T is const + // this allows const spans to be constructed from a temporary container + template ().data())>::type + , typename = typename std::enable_if::value + && std::is_const::value>::type> + span(Cont const& c) // NOLINT + : m_ptr(c.data()), m_len(static_cast(c.size())) {} + + template ::value>::type> + span& operator=(span const& rhs) noexcept + { + m_ptr = rhs.data(); + m_len = rhs.size(); + return *this; + } + + index_type size() const noexcept { return m_len; } + bool empty() const noexcept { return m_len == 0; } + T* data() const noexcept { return m_ptr; } + + using const_iterator = T const*; + using const_reverse_iterator = std::reverse_iterator; + using iterator = T*; + using reverse_iterator = std::reverse_iterator; + + T* begin() const noexcept { return m_ptr; } + T* end() const noexcept { return m_ptr + m_len; } + reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } + reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } + + T& front() const noexcept { TORRENT_ASSERT(m_len > 0); return m_ptr[0]; } + T& back() const noexcept { TORRENT_ASSERT(m_len > 0); return m_ptr[m_len - 1]; } + + span first(difference_type const n) const + { + TORRENT_ASSERT(size() >= n); + return { data(), n }; + } + + span last(difference_type const n) const + { + TORRENT_ASSERT(size() >= n); + return { data() + size() - n, n }; + } + + span subspan(index_type const offset) const + { + TORRENT_ASSERT(size() >= offset); + return { data() + offset, size() - offset }; + } + + span subspan(index_type const offset, difference_type const count) const + { + TORRENT_ASSERT(count >= 0); + TORRENT_ASSERT(size() >= offset); + TORRENT_ASSERT(size() >= offset + count); + return { data() + offset, count }; + } + + T& operator[](index_type const idx) const + { + TORRENT_ASSERT(idx < m_len); + TORRENT_ASSERT(idx >= 0); + return m_ptr[idx]; + } + + private: + T* m_ptr; + difference_type m_len; + }; + + template + inline bool operator==(span const& lhs, span const& rhs) + { + return lhs.size() == rhs.size() + && (lhs.data() == rhs.data() || std::equal(lhs.begin(), lhs.end(), rhs.begin())); + } + + template + inline bool operator!=(span const& lhs, span const& rhs) + { + return lhs.size() != rhs.size() + || (lhs.data() != rhs.data() && !std::equal(lhs.begin(), lhs.end(), rhs.begin())); + } +} + +#endif // TORRENT_SPAN_HPP_INCLUDED diff --git a/include/libtorrent/ssl_stream.hpp b/include/libtorrent/ssl_stream.hpp new file mode 100644 index 0000000..6e7859b --- /dev/null +++ b/include/libtorrent/ssl_stream.hpp @@ -0,0 +1,322 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_SSL_STREAM_HPP_INCLUDED +#define TORRENT_SSL_STREAM_HPP_INCLUDED + +#ifdef TORRENT_USE_OPENSSL + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/aux_/openssl.hpp" + +#include + +namespace libtorrent { + +template +class ssl_stream +{ +public: + + explicit ssl_stream(io_service& io_service, ssl::context& ctx) + : m_sock(io_service, ctx) + { + } + + using sock_type = typename boost::asio::ssl::stream; + using next_layer_type = typename sock_type::next_layer_type; + using lowest_layer_type = typename Stream::lowest_layer_type; + using endpoint_type = typename Stream::endpoint_type; + using protocol_type = typename Stream::protocol_type; +#if BOOST_VERSION >= 106600 + using executor_type = typename sock_type::executor_type; + executor_type get_executor() { return m_sock.get_executor(); } +#endif + + void set_host_name(std::string const& name) + { + aux::openssl_set_tlsext_hostname(m_sock.native_handle(), name.c_str()); + } + + template + void set_verify_callback(T const& fun, error_code& ec) + { m_sock.set_verify_callback(fun, ec); } + + SSL* native_handle() { return m_sock.native_handle(); } + + using handler_type = std::function; + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + // the connect is split up in the following steps: + // 1. connect to peer + // 2. perform SSL client handshake + + // to avoid unnecessary copying of the handler, + // store it in a shared_ptr + auto h = std::make_shared(handler); + + using std::placeholders::_1; + m_sock.next_layer().async_connect(endpoint + , std::bind(&ssl_stream::connected, this, _1, h)); + } + + template + void async_accept_handshake(Handler const& handler) + { + // this is used for accepting SSL connections + auto h = std::make_shared(handler); + using std::placeholders::_1; + m_sock.async_handshake(ssl::stream_base::server + , std::bind(&ssl_stream::handshake, this, _1, h)); + } + + void accept_handshake(error_code& ec) + { + // this is used for accepting SSL connections + m_sock.handshake(ssl::stream_base::server, ec); + } + + template + void async_shutdown(Handler const& handler) + { + error_code ec; + m_sock.next_layer().cancel(ec); + m_sock.async_shutdown(handler); + } + + void shutdown(error_code& ec) + { + m_sock.shutdown(ec); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + m_sock.async_read_some(buffers, handler); + } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + return m_sock.read_some(buffers, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const& opt) + { + m_sock.next_layer().set_option(opt); + } +#endif + + template + error_code set_option(SettableSocketOption const& opt, error_code& ec) + { + return m_sock.next_layer().set_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption& opt) + { + m_sock.next_layer().get_option(opt); + } +#endif + + template + error_code get_option(GettableSocketOption& opt, error_code& ec) + { + return m_sock.next_layer().get_option(opt, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + return m_sock.read_some(buffers); + } + + template + void io_control(IO_Control_Command& ioc) + { + m_sock.next_layer().io_control(ioc); + } +#endif + + template + void io_control(IO_Control_Command& ioc, error_code& ec) + { + m_sock.next_layer().io_control(ioc, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool b) { m_sock.next_layer().non_blocking(b); } +#endif + + error_code non_blocking(bool b, error_code& ec) + { return m_sock.next_layer().non_blocking(b, ec); } + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + m_sock.async_write_some(buffers, handler); + } + + template + std::size_t write_some(Const_Buffers const& buffers, error_code& ec) + { + return m_sock.write_some(buffers, ec); + } + + // the SSL stream may cache 17 kiB internally, and there's no way of + // asking how large its buffer is. 17 kiB isn't very much though, so it + // seems fine to potentially over-estimate the number of bytes available. +#ifndef BOOST_NO_EXCEPTIONS + std::size_t available() const + { return 17 * 1024 + const_cast(m_sock).next_layer().available(); } +#endif + + std::size_t available(error_code& ec) const + { return 17 * 1024 + const_cast(m_sock).next_layer().available(ec); } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& endpoint) + { + m_sock.next_layer().bind(endpoint); + } +#endif + + void bind(endpoint_type const& endpoint, error_code& ec) + { + m_sock.next_layer().bind(endpoint, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + void open(protocol_type const& p) + { + m_sock.next_layer().open(p); + } +#endif + + void open(protocol_type const& p, error_code& ec) + { + m_sock.next_layer().open(p, ec); + } + + bool is_open() const + { + return const_cast(m_sock).next_layer().is_open(); + } + +#ifndef BOOST_NO_EXCEPTIONS + void close() + { + m_sock.next_layer().close(); + } +#endif + + void close(error_code& ec) + { + m_sock.next_layer().close(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type remote_endpoint() const + { + return const_cast(m_sock).next_layer().remote_endpoint(); + } +#endif + + endpoint_type remote_endpoint(error_code& ec) const + { + return const_cast(m_sock).next_layer().remote_endpoint(ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return const_cast(m_sock).next_layer().local_endpoint(); + } +#endif + + endpoint_type local_endpoint(error_code& ec) const + { + return const_cast(m_sock).next_layer().local_endpoint(ec); + } + + io_service& get_io_service() + { + return m_sock.get_io_service(); + } + + lowest_layer_type& lowest_layer() + { + return m_sock.lowest_layer(); + } + + next_layer_type& next_layer() + { + return m_sock.next_layer(); + } + +private: + + void connected(error_code const& e, std::shared_ptr h) + { + if (e) + { + (*h)(e); + return; + } + + using std::placeholders::_1; + m_sock.async_handshake(ssl::stream_base::client + , std::bind(&ssl_stream::handshake, this, _1, h)); + } + + void handshake(error_code const& e, std::shared_ptr h) + { + (*h)(e); + } + + ssl::stream m_sock; +}; + +} + +#endif // TORRENT_USE_OPENSSL + +#endif diff --git a/include/libtorrent/stack_allocator.hpp b/include/libtorrent/stack_allocator.hpp new file mode 100644 index 0000000..8fa7a98 --- /dev/null +++ b/include/libtorrent/stack_allocator.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STACK_ALLOCATOR +#define TORRENT_STACK_ALLOCATOR + +#include "libtorrent/assert.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include // for va_list +#include // for vsnprintf +#include + +namespace libtorrent { namespace aux { + + struct allocation_slot + { + allocation_slot() noexcept : m_idx(-1) {} + allocation_slot(allocation_slot const&) noexcept = default; + allocation_slot(allocation_slot&&) noexcept = default; + allocation_slot& operator=(allocation_slot const&) = default; + allocation_slot& operator=(allocation_slot&&) noexcept = default; + bool operator==(allocation_slot const& s) const { return m_idx == s.m_idx; } + bool operator!=(allocation_slot const& s) const { return m_idx != s.m_idx; } + friend struct stack_allocator; + private: + explicit allocation_slot(int idx) noexcept : m_idx(idx) {} + int val() const { return m_idx; } + int m_idx; + }; + + struct TORRENT_EXTRA_EXPORT stack_allocator + { + stack_allocator() {} + + // non-copyable + stack_allocator(stack_allocator const&) = delete; + stack_allocator& operator=(stack_allocator const&) = delete; + stack_allocator(stack_allocator&&) = default; + stack_allocator& operator=(stack_allocator&&) = default; + + allocation_slot copy_string(string_view str); + allocation_slot copy_string(char const* str); + + allocation_slot format_string(char const* fmt, va_list v); + + allocation_slot copy_buffer(span buf); + allocation_slot allocate(int bytes); + char* ptr(allocation_slot idx); + char const* ptr(allocation_slot idx) const; + void swap(stack_allocator& rhs); + void reset(); + + private: + + vector m_storage; + }; + +} } + +#endif diff --git a/include/libtorrent/stat.hpp b/include/libtorrent/stat.hpp new file mode 100644 index 0000000..f83c6b5 --- /dev/null +++ b/include/libtorrent/stat.hpp @@ -0,0 +1,285 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_HPP_INCLUDED +#define TORRENT_STAT_HPP_INCLUDED + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT stat_channel + { + public: + + stat_channel() + : m_total_counter(0) + , m_counter(0) + , m_5_sec_average(0) + {} + + void operator+=(stat_channel const& s) + { + TORRENT_ASSERT(m_counter < (std::numeric_limits::max)() - s.m_counter); + m_counter += s.m_counter; + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - s.m_counter); + m_total_counter += s.m_counter; + } + + void add(int count) + { + TORRENT_ASSERT(count >= 0); + + TORRENT_ASSERT(m_counter < (std::numeric_limits::max)() - count); + m_counter += count; + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - count); + m_total_counter += count; + } + + // should be called once every second + void second_tick(int tick_interval_ms); + std::int32_t rate() const { return m_5_sec_average; } + std::int32_t low_pass_rate() const { return m_5_sec_average; } + + std::int64_t total() const { return m_total_counter; } + + void offset(std::int64_t c) + { + TORRENT_ASSERT(m_total_counter < (std::numeric_limits::max)() - c); + m_total_counter += c; + } + + std::int32_t counter() const { return m_counter; } + + void clear() + { + m_counter = 0; + m_5_sec_average = 0; + m_total_counter = 0; + } + + private: + + // total counters + std::int64_t m_total_counter; + + // the accumulator for this second. + std::int32_t m_counter; + + // sliding average + std::int32_t m_5_sec_average; + }; + + class TORRENT_EXTRA_EXPORT stat + { + public: + void operator+=(const stat& s) + { + for (int i = 0; i < num_channels; ++i) + m_stat[i] += s.m_stat[i]; + } + + void sent_syn(bool ipv6) + { + m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); + } + + void received_synack(bool ipv6) + { + // we received SYN-ACK and also sent ACK back + m_stat[download_ip_protocol].add(ipv6 ? 60 : 40); + m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); + } + + void received_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + + m_stat[download_payload].add(bytes_payload); + m_stat[download_protocol].add(bytes_protocol); + } + + void sent_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + + m_stat[upload_payload].add(bytes_payload); + m_stat[upload_protocol].add(bytes_protocol); + } + + // and IP packet was received or sent + // account for the overhead caused by it + void trancieve_ip_packet(int bytes_transferred, bool ipv6) + { + // one TCP/IP packet header for the packet + // sent or received, and one for the ACK + // The IPv4 header is 20 bytes + // and IPv6 header is 40 bytes + const int header = (ipv6 ? 40 : 20) + 20; + const int mtu = 1500; + const int packet_size = mtu - header; + const int overhead = (std::max)(1, (bytes_transferred + packet_size - 1) / packet_size) * header; + m_stat[download_ip_protocol].add(overhead); + m_stat[upload_ip_protocol].add(overhead); + } + + int upload_ip_overhead() const { return m_stat[upload_ip_protocol].counter(); } + int download_ip_overhead() const { return m_stat[download_ip_protocol].counter(); } + + // should be called once every second + void second_tick(int tick_interval_ms) + { + for (int i = 0; i < num_channels; ++i) + m_stat[i].second_tick(tick_interval_ms); + } + + int low_pass_upload_rate() const + { + return m_stat[upload_payload].low_pass_rate() + + m_stat[upload_protocol].low_pass_rate() + + m_stat[upload_ip_protocol].low_pass_rate(); + } + + int low_pass_download_rate() const + { + return m_stat[download_payload].low_pass_rate() + + m_stat[download_protocol].low_pass_rate() + + m_stat[download_ip_protocol].low_pass_rate(); + } + + int upload_rate() const + { + return m_stat[upload_payload].rate() + + m_stat[upload_protocol].rate() + + m_stat[upload_ip_protocol].rate(); + } + + int download_rate() const + { + return m_stat[download_payload].rate() + + m_stat[download_protocol].rate() + + m_stat[download_ip_protocol].rate(); + } + + std::int64_t total_upload() const + { + return m_stat[upload_payload].total() + + m_stat[upload_protocol].total() + + m_stat[upload_ip_protocol].total(); + } + + std::int64_t total_download() const + { + return m_stat[download_payload].total() + + m_stat[download_protocol].total() + + m_stat[download_ip_protocol].total(); + } + + int upload_payload_rate() const + { return m_stat[upload_payload].rate(); } + int download_payload_rate() const + { return m_stat[download_payload].rate(); } + + std::int64_t total_payload_upload() const + { return m_stat[upload_payload].total(); } + std::int64_t total_payload_download() const + { return m_stat[download_payload].total(); } + + std::int64_t total_protocol_upload() const + { return m_stat[upload_protocol].total(); } + std::int64_t total_protocol_download() const + { return m_stat[download_protocol].total(); } + + std::int64_t total_transfer(int channel) const + { return m_stat[channel].total(); } + int transfer_rate(int channel) const + { return m_stat[channel].rate(); } + + // this is used to offset the statistics when a + // peer_connection is opened and have some previous + // transfers from earlier connections. + void add_stat(std::int64_t downloaded, std::int64_t uploaded) + { + m_stat[download_payload].offset(downloaded); + m_stat[upload_payload].offset(uploaded); + } + + int last_payload_downloaded() const + { return m_stat[download_payload].counter(); } + int last_payload_uploaded() const + { return m_stat[upload_payload].counter(); } + int last_protocol_downloaded() const + { return m_stat[download_protocol].counter(); } + int last_protocol_uploaded() const + { return m_stat[upload_protocol].counter(); } + + // these are the channels we keep stats for + enum + { + // TODO: 3 everything but payload counters and rates could probably be + // removed from here + upload_payload, + upload_protocol, + download_payload, + download_protocol, + upload_ip_protocol, + download_ip_protocol, + num_channels + }; + + void clear() + { + for (int i = 0; i < num_channels; ++i) + m_stat[i].clear(); + } + + stat_channel const& operator[](int i) const + { + TORRENT_ASSERT(i >= 0 && i < num_channels); + return m_stat[i]; + } + + private: + + stat_channel m_stat[num_channels]; + }; + +} + +#endif // TORRENT_STAT_HPP_INCLUDED diff --git a/include/libtorrent/stat_cache.hpp b/include/libtorrent/stat_cache.hpp new file mode 100644 index 0000000..8afdd68 --- /dev/null +++ b/include/libtorrent/stat_cache.hpp @@ -0,0 +1,108 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STAT_CACHE_HPP +#define TORRENT_STAT_CACHE_HPP + +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT stat_cache + { + stat_cache(); + ~stat_cache(); + + void reserve(int num_files); + + // returns the size of the file unless an error occurs, in which case ec + // is set to indicate the error + std::int64_t get_filesize(file_index_t i, file_storage const& fs + , std::string const& save_path, error_code& ec); + + void set_dirty(file_index_t i); + + void clear(); + + // internal + enum + { + not_in_cache = -1, + file_error = -2 // (first index in m_errors) + }; + + // internal + void set_cache(file_index_t i, std::int64_t size); + void set_error(file_index_t i, error_code const& ec); + + private: + + void set_cache_impl(file_index_t i, std::int64_t size); + void set_error_impl(file_index_t i, error_code const& ec); + + // returns the index to the specified error. Either an existing one or a + // newly added entry + int add_error(error_code const& ec); + + struct stat_cache_t + { + explicit stat_cache_t(std::int64_t s): file_size(s) {} + + // the size of the file. Negative values have special meaning. -1 means + // not-in-cache (i.e. there's no data for this file in the cache). + // lower values (larger negative values) indicate that an error + // occurred while stat()ing the file. The positive value is an index + // into m_errors, that recorded the actual error. + std::int64_t file_size; + }; + + mutable std::mutex m_mutex; + + // one entry per file + aux::vector m_stat_cache; + + // These are the errors that have happened when stating files. Each entry + // that had an error, refers to an index into this vector. + std::vector m_errors; + }; +} + +#endif // TORRENT_STAT_CACHE_HPP diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp new file mode 100644 index 0000000..a94fd57 --- /dev/null +++ b/include/libtorrent/storage.hpp @@ -0,0 +1,415 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_HPP_INCLUDE +#define TORRENT_STORAGE_HPP_INCLUDE + +#include "libtorrent/config.hpp" + +#include +#include +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/disk_job_fence.hpp" +#include "libtorrent/aux_/storage_piece_set.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/aux_/vector.hpp" + +// OVERVIEW +// +// libtorrent provides a customization point for storage of data. By default, +// (``default_storage``) downloaded files are saved to disk according with the +// general conventions of bittorrent clients, mimicking the original file layout +// when the torrent was created. The libtorrent user may define a custom +// storage to store piece data in a different way. +// +// A custom storage implementation must derive from and implement the +// storage_interface. You must also provide a function that constructs the +// custom storage object and provide this function to the add_torrent() call +// via add_torrent_params. Either passed in to the constructor or by setting +// the add_torrent_params::storage field. +// +// This is an example storage implementation that stores all pieces in a +// ``std::map``, i.e. in RAM. It's not necessarily very useful in practice, but +// illustrates the basics of implementing a custom storage. +// +// .. include:: ../examples/custom_storage.cpp +// :code: c++ +// :tab-width: 2 +// :start-after: -- example begin +// :end-before: // -- example end +namespace libtorrent { + + namespace aux { struct session_settings; } + + // The storage interface is a pure virtual class that can be implemented to + // customize how and where data for a torrent is stored. The default storage + // implementation uses regular files in the filesystem, mapping the files in + // the torrent in the way one would assume a torrent is saved to disk. + // Implementing your own storage interface makes it possible to store all + // data in RAM, or in some optimized order on disk (the order the pieces are + // received for instance), or saving multi file torrents in a single file in + // order to be able to take advantage of optimized disk-I/O. + // + // It is also possible to write a thin class that uses the default storage + // but modifies some particular behavior, for instance encrypting the data + // before it's written to disk, and decrypting it when it's read again. + // + // The storage interface is based on pieces. Every read and write operation + // happens in the piece-space. Each piece fits ``piece_size`` number + // of bytes. All access is done by writing and reading whole or partial + // pieces. + // + // libtorrent comes with two built-in storage implementations; + // ``default_storage`` and ``disabled_storage``. Their constructor functions + // are called default_storage_constructor() and + // ``disabled_storage_constructor`` respectively. The disabled storage does + // just what it sounds like. It throws away data that's written, and it + // reads garbage. It's useful mostly for benchmarking and profiling purpose. + // + struct TORRENT_EXPORT storage_interface + : std::enable_shared_from_this + , aux::disk_job_fence + , aux::storage_piece_set + { + explicit storage_interface(file_storage const& fs) : m_files(fs) {} + + storage_interface(storage_interface const&) = delete; + storage_interface& operator=(storage_interface const&) = delete; + + // This function is called when the *storage* on disk is to be + // initialized. The default storage will create directories and empty + // files at this point. If ``allocate_files`` is true, it will also + // ``ftruncate`` all files to their target size. + // + // This function may be called multiple time on a single instance. When a + // torrent is force-rechecked, the storage is re-initialized to trigger + // the re-check from scratch. + // + // The function is not necessarily called before other member functions. + // For instance has_any_files() and verify_resume_data() are + // called early to determine whether we may have to check all files or + // not. If we're doing a full check of the files every piece will be + // hashed, causing readv() to be called as well. + // + // Any required internals that need initialization should be done in the + // constructor. This function is called before the torrent starts to + // download. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + virtual void initialize(storage_error& ec) = 0; + + // These functions should read and write the data in or to the given + // ``piece`` at the given ``offset``. It should read or write + // ``num_bufs`` buffers sequentially, where the size of each buffer is + // specified in the buffer array ``bufs``. The iovec_t type has the + // following members:: + // + // struct iovec_t { void* iov_base; size_t iov_len; }; + // + // These functions may be called simultaneously from multiple threads. + // Make sure they are thread safe. The ``file`` in libtorrent is thread + // safe when it can fall back to ``pread``, ``preadv`` or the windows + // equivalents. On targets where read operations cannot be thread safe + // (i.e one has to seek first and then read), only one disk thread is + // used. + // + // The ``offset`` is aligned to 16 kiB boundaries *most of the time*, but + // there are rare exceptions when it's not. Specifically if the read + // cache is disabled/or full and a peer requests unaligned data. Most + // clients request aligned data. + // + // The number of bytes read or written should be returned, or -1 on + // error. If there's an error, the ``storage_error`` must be filled out + // to represent the error that occurred. + // + // For possible values of ``flags``, see open_mode_t. + virtual int readv(span bufs + , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0; + virtual int writev(span bufs + , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) = 0; + + // This function is called when first checking (or re-checking) the + // storage for a torrent. It should return true if any of the files that + // is used in this storage exists on disk. If so, the storage will be + // checked for existing pieces before starting the download. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + virtual bool has_any_file(storage_error& ec) = 0; + + // change the priorities of files. This is a fenced job and is + // guaranteed to be the only running function on this storage + // when called + virtual void set_file_priority(aux::vector& prio + , storage_error& ec) = 0; + + // This function should move all the files belonging to the storage to + // the new save_path. The default storage moves the single file or the + // directory of the torrent. + // + // Before moving the files, any open file handles may have to be closed, + // like ``release_files()``. + // + //If an error occurs, ``storage_error`` should be set to reflect it. + virtual status_t move_storage(std::string const& save_path + , move_flags_t flags, storage_error& ec) = 0; + + // This function should verify the resume data ``rd`` with the files + // on disk. If the resume data seems to be up-to-date, return true. If + // not, set ``error`` to a description of what mismatched and return false. + // + // The default storage may compare file sizes and time stamps of the files. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + // This function should verify the resume data ``rd`` with the files + // on disk. If the resume data seems to be up-to-date, return true. If + // not, set ``error`` to a description of what mismatched and return false. + // + // If the ``links`` pointer is non-empty, it has the same number + // of elements as there are files. Each element is either empty or contains + // the absolute path to a file identical to the corresponding file in this + // torrent. The storage must create hard links (or copy) those files. If + // any file does not exist or is inaccessible, the disk job must fail. + virtual bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error& ec) = 0; + + // This function should release all the file handles that it keeps open + // to files belonging to this storage. The default implementation just + // calls file_pool::release_files(). + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void release_files(storage_error& ec) = 0; + + // Rename the file with index ``file`` to name ``new_name``. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void rename_file(file_index_t index, std::string const& new_filename + , storage_error& ec) = 0; + + // This function should delete some or all of the storage for this torrent. + // The ``options`` parameter specifies whether to delete all files or just + // the partfile. ``options`` are set to the same value as the options + // passed to session::remove_torrent(). + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + // The ``disk_buffer_pool`` is used to allocate and free disk buffers. It + // has the following members: + // + // .. code:: c++ + // + // struct disk_buffer_pool + // { + // char* allocate_buffer(char const* category); + // void free_buffer(char* buf); + // + // char* allocate_buffers(int blocks, char const* category); + // void free_buffers(char* buf, int blocks); + // + // int block_size() const { return m_block_size; } + // + // }; + virtual void delete_files(remove_flags_t options, storage_error& ec) = 0; + + // called periodically (useful for deferred flushing). When returning + // false, it means no more ticks are necessary. Any disk job submitted + // will re-enable ticking. The default will always turn ticking back + // off again. + virtual bool tick() { return false; } + + file_storage const& files() const { return m_files; } + + bool set_need_tick() + { + bool const prev = m_need_tick; + m_need_tick = true; + return prev; + } + + void do_tick() + { + m_need_tick = false; + tick(); + } + + void set_owner(std::shared_ptr const& tor) { m_torrent = tor; } + + // access global session_settings + aux::session_settings const& settings() const { return *m_settings; } + + // hidden + virtual ~storage_interface() {} + + // initialized in disk_io_thread::perform_async_job + aux::session_settings const* m_settings = nullptr; + + storage_index_t storage_index() const { return m_storage_index; } + void set_storage_index(storage_index_t st) { m_storage_index = st; } + + int dec_refcount() + { + TORRENT_ASSERT(m_references > 0); + return --m_references; + } + void inc_refcount() { ++m_references; } + private: + + bool m_need_tick = false; + file_storage const& m_files; + + // the reason for this to be a void pointer + // is to avoid creating a dependency on the + // torrent. This shared_ptr is here only + // to keep the torrent object alive until + // the storage_interface destructs. This is because + // the file_storage object is owned by the torrent. + std::shared_ptr m_torrent; + + storage_index_t m_storage_index{0}; + + // the number of block_cache_reference objects referencing this storage + std::atomic m_references{1}; + }; + + // The default implementation of storage_interface. Behaves as a normal + // bittorrent client. It is possible to derive from this class in order to + // override some of its behavior, when implementing a custom storage. + class TORRENT_EXPORT default_storage : public storage_interface + { + public: + // constructs the default_storage based on the give file_storage (fs). + // ``mapped`` is an optional argument (it may be nullptr). If non-nullptr it + // represents the file mapping that have been made to the torrent before + // adding it. That's where files are supposed to be saved and looked for + // on disk. ``save_path`` is the root save folder for this torrent. + // ``file_pool`` is the cache of file handles that the storage will use. + // All files it opens will ask the file_pool to open them. ``file_prio`` + // is a vector indicating the priority of files on startup. It may be + // an empty vector. Any file whose index is not represented by the vector + // (because the vector is too short) are assumed to have priority 1. + // this is used to treat files with priority 0 slightly differently. + explicit default_storage(storage_params const& params, file_pool&); + + // hidden + ~default_storage() override; + + bool has_any_file(storage_error& ec) override; + void set_file_priority(aux::vector& prio + , storage_error& ec) override; + void rename_file(file_index_t index, std::string const& new_filename + , storage_error& ec) override; + void release_files(storage_error& ec) override; + void delete_files(remove_flags_t options, storage_error& ec) override; + void initialize(storage_error& ec) override; + status_t move_storage(std::string const& save_path + , move_flags_t flags, storage_error& ec) override; + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error& error) override; + bool tick() override; + + int readv(span bufs + , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) override; + int writev(span bufs + , piece_index_t piece, int offset, open_mode_t flags, storage_error& ec) override; + + // if the files in this storage are mapped, returns the mapped + // file_storage, otherwise returns the original file_storage object. + file_storage const& files() const + { + return m_mapped_files ? *m_mapped_files : storage_interface::files(); + } + + private: + + void need_partfile(); + + std::unique_ptr m_mapped_files; + + // in order to avoid calling stat() on each file multiple times + // during startup, cache the results in here, and clear it all + // out once the torrent starts (to avoid getting stale results) + // each entry represents the size and timestamp of the file + mutable stat_cache m_stat_cache; + + // helper function to open a file in the file pool with the right mode + file_handle open_file(file_index_t file, open_mode_t mode, storage_error& ec) const; + file_handle open_file_impl(file_index_t file, open_mode_t mode, error_code& ec) const; + + bool use_partfile(file_index_t index) const; + void use_partfile(file_index_t index, bool b); + + aux::vector m_file_priority; + std::string m_save_path; + std::string m_part_file_name; + + // this this is an array indexed by file-index. Each slot represents + // whether this file has the part-file enabled for it. This is used for + // backwards compatibility with pre-partfile versions of libtorrent. If + // this vector is empty, the default is that files *do* use the partfile. + // on startup, any 0-priority file that's found in it's original location + // is expected to be an old-style (pre-partfile) torrent storage, and + // those files have their slot set to false in this vector. + // note that the vector is *sparse*, it's only allocated if a file has its + // entry set to false, and only indices up to that entry. + aux::vector m_use_partfile; + + // the file pool is a member of the disk_io_thread + // to make all storage instances share the pool + file_pool& m_pool; + + // used for skipped files + std::unique_ptr m_part_file; + + // this is a bitfield with one bit per file. A bit being set means + // we've written to that file previously. If we do write to a file + // whose bit is 0, we set the file size, to make the file allocated + // on disk (in full allocation mode) and just sparsely allocated in + // case of sparse allocation mode + mutable std::mutex m_file_created_mutex; + mutable typed_bitfield m_file_created; + + bool m_allocate_files; + }; + +} + +#endif // TORRENT_STORAGE_HPP_INCLUDED diff --git a/include/libtorrent/storage_defs.hpp b/include/libtorrent/storage_defs.hpp new file mode 100644 index 0000000..6c1bc04 --- /dev/null +++ b/include/libtorrent/storage_defs.hpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STORAGE_DEFS_HPP_INCLUDE +#define TORRENT_STORAGE_DEFS_HPP_INCLUDE + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/download_priority.hpp" +#include +#include + +namespace libtorrent { + + struct TORRENT_EXPORT storage_interface; + + using storage_index_t = aux::strong_typedef; + + // types of storage allocation used for add_torrent_params::storage_mode. + enum storage_mode_t + { + // All pieces will be written to their final position, all files will be + // allocated in full when the torrent is first started. This is done with + // ``fallocate()`` and similar calls. This mode minimizes fragmentation. + storage_mode_allocate, + + // All pieces will be written to the place where they belong and sparse files + // will be used. This is the recommended, and default mode. + storage_mode_sparse + }; + + // return values from check_fastresume, and move_storage + enum class status_t : std::uint8_t + { + no_error, + fatal_disk_error, + need_full_check, + file_exist + }; + + // flags for async_move_storage + enum class move_flags_t : std::uint8_t + { + // replace any files in the destination when copying + // or moving the storage + always_replace_files, + + // if any files that we want to copy exist in the destination + // exist, fail the whole operation and don't perform + // any copy or move. There is an inherent race condition + // in this mode. The files are checked for existence before + // the operation starts. In between the check and performing + // the copy, the destination files may be created, in which + // case they are replaced. + fail_if_exist, + + // if any file exist in the target, take those files instead + // of the ones we may have in the source. + dont_replace + }; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + enum deprecated_move_flags_t + { + always_replace_files TORRENT_DEPRECATED_ENUM, + fail_if_exist TORRENT_DEPRECATED_ENUM, + dont_replace TORRENT_DEPRECATED_ENUM + }; +#endif + + struct TORRENT_EXPORT storage_params + { + storage_params(file_storage const& f, file_storage const* mf + , std::string const& sp, storage_mode_t const sm + , aux::vector const& prio + , sha1_hash const& ih) + : files(f) + , mapped_files(mf) + , path(sp) + , mode(sm) + , priorities(prio) + , info_hash(ih) + {} + file_storage const& files; + file_storage const* mapped_files = nullptr; // optional + std::string const& path; + storage_mode_t mode{storage_mode_sparse}; + aux::vector const& priorities; + sha1_hash const& info_hash; + }; + + using storage_constructor_type = std::function; + + // the constructor function for the regular file storage. This is the + // default value for add_torrent_params::storage. + TORRENT_EXPORT storage_interface* default_storage_constructor(storage_params const& + , file_pool& p); + + // the constructor function for the disabled storage. This can be used for + // testing and benchmarking. It will throw away any data written to + // it and return garbage for anything read from it. + TORRENT_EXPORT storage_interface* disabled_storage_constructor(storage_params const&, file_pool&); + + // the constructor function for the "zero" storage. This will always read + // zeros and ignore all writes. + TORRENT_EXPORT storage_interface* zero_storage_constructor(storage_params const&, file_pool&); +} + +#endif diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp new file mode 100644 index 0000000..3116a1e --- /dev/null +++ b/include/libtorrent/string_util.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_UTIL_HPP_INCLUDED +#define TORRENT_STRING_UTIL_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/string_view.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/error_code.hpp" + +#include +#include +#include +#include +#include // for std::array + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT bool is_alpha(char c); + + TORRENT_EXTRA_EXPORT + std::array::digits10> + to_string(std::int64_t n); + + // internal + inline bool is_digit(char c) + { return c >= '0' && c <= '9'; } + inline void ensure_trailing_slash(std::string& url) + { + if (url.empty() || url[url.size() - 1] != '/') + url += '/'; + } + + // internal + TORRENT_EXTRA_EXPORT string_view strip_string(string_view in); + + TORRENT_EXTRA_EXPORT bool is_print(char c); + TORRENT_EXTRA_EXPORT bool is_space(char c); + TORRENT_EXTRA_EXPORT char to_lower(char c); + + TORRENT_EXTRA_EXPORT bool string_begins_no_case(char const* s1, char const* s2); + TORRENT_EXTRA_EXPORT bool string_equal_no_case(string_view s1, string_view s2); + + TORRENT_EXTRA_EXPORT void url_random(span dest); + + TORRENT_EXTRA_EXPORT bool string_ends_with(string_view s1, string_view s2); + + // Returns offset at which src matches target. + // If no sync found, return -1 + TORRENT_EXTRA_EXPORT int search(span src, span target); + + struct listen_interface_t + { + std::string device; + int port; + bool ssl; + bool local; + friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs) + { + return lhs.device == rhs.device + && lhs.port == rhs.port + && lhs.ssl == rhs.ssl + && lhs.local == rhs.local; + } + }; + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT std::vector parse_listen_interfaces( + std::string const& in, std::vector& errors); + +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING + TORRENT_EXTRA_EXPORT std::string print_listen_interfaces( + std::vector const& in); +#endif + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string_port( + std::string const& in, std::vector>& out); + + // this parses the string that's used as the outgoing_interfaces setting. + // it is a comma separated list of IPs and device names. For example: + // "eth0, eth1, 127.0.0.1" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string( + std::string const& in, std::vector& out); + + // strdup is not part of the C standard. Some systems + // don't have it and it won't be available when building + // in strict ANSI mode + TORRENT_EXTRA_EXPORT char* allocate_string_copy(string_view str); + + // searches for separator ('sep') in the string 'last'. + // if found, returns the string_view representing the range from the start of + // `last` up to (but not including) the separator. The second return value is + // the remainder of the string, starting one character after the separator. + // if no separator is found, the whole string is returned and the second + // return value is an empty string_view. + TORRENT_EXTRA_EXPORT std::pair split_string(string_view last, char sep); + +#if TORRENT_USE_I2P + + TORRENT_EXTRA_EXPORT bool is_i2p_url(std::string const& url); + +#endif +} + +#endif diff --git a/include/libtorrent/string_view.hpp b/include/libtorrent/string_view.hpp new file mode 100644 index 0000000..26a9487 --- /dev/null +++ b/include/libtorrent/string_view.hpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2016-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_STRING_VIEW_HPP_INCLUDED +#define TORRENT_STRING_VIEW_HPP_INCLUDED + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +// TODO: replace this by the standard string_view in C++17 + +#if BOOST_VERSION < 106100 +#include +#include // for strchr +namespace libtorrent { + +using string_view = boost::string_ref; +using wstring_view = boost::wstring_ref; + +// internal +inline string_view::size_type find_first_of(string_view const v, char const c + , string_view::size_type pos) +{ + while (pos < v.size()) + { + if (v[pos] == c) return pos; + ++pos; + } + return string_view::npos; +} + +// internal +inline string_view::size_type find_first_of(string_view const v, char const* c + , string_view::size_type pos) +{ + while (pos < v.size()) + { + if (std::strchr(c, v[pos]) != nullptr) return pos; + ++pos; + } + return string_view::npos; +} +} +#else +#include +namespace libtorrent { + +using string_view = boost::string_view; +using wstring_view = boost::wstring_view; + +// internal +inline string_view::size_type find_first_of(string_view const v, char const c + , string_view::size_type pos) +{ + return v.find_first_of(c, pos); +} + +// internal +inline string_view::size_type find_first_of(string_view const v, char const* c + , string_view::size_type pos) +{ + return v.find_first_of(c, pos); +} +} +#endif + +namespace libtorrent { + +inline namespace literals { + + constexpr string_view operator "" _sv(char const* str, std::size_t len) + { return string_view(str, len); } +} +} + + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif diff --git a/include/libtorrent/tailqueue.hpp b/include/libtorrent/tailqueue.hpp new file mode 100644 index 0000000..07dca21 --- /dev/null +++ b/include/libtorrent/tailqueue.hpp @@ -0,0 +1,186 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TAILQUEUE_HPP +#define TORRENT_TAILQUEUE_HPP + +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + template + struct tailqueue_node + { + tailqueue_node() : next(nullptr) {} + T* next; + }; + + template + inline N* postinc(N*& e) + { + N* ret = e; + e = static_cast(ret->next); + return ret; + } + + template + struct tailqueue_iterator + { + template friend struct tailqueue; + + T* get() const { return m_current; } + void next() { m_current = m_current->next; } + + private: + explicit tailqueue_iterator(T* cur) + : m_current(cur) {} + // the current element + T* m_current; + }; + + template + struct tailqueue + { + tailqueue(): m_first(nullptr), m_last(nullptr), m_size(0) {} + + tailqueue_iterator iterate() const + { return tailqueue_iterator(m_first); } + + tailqueue_iterator iterate() + { return tailqueue_iterator(m_first); } + + void append(tailqueue& rhs) + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + + if (rhs.m_first == nullptr) return; + + if (m_first == nullptr) + { + swap(rhs); + return; + } + + m_last->next = rhs.m_first; + m_last = rhs.m_last; + m_size += rhs.m_size; + rhs.m_first = nullptr; + rhs.m_last = nullptr; + rhs.m_size = 0; + + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + } + + void prepend(tailqueue& rhs) + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + TORRENT_ASSERT((m_last == nullptr) == (m_first == nullptr)); + TORRENT_ASSERT((rhs.m_last == nullptr) == (rhs.m_first == nullptr)); + + if (rhs.m_first == nullptr) return; + + if (m_first == nullptr) + { + swap(rhs); + return; + } + + swap(rhs); + append(rhs); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + TORRENT_ASSERT(rhs.m_last == nullptr || rhs.m_last->next == nullptr); + } + + T* pop_front() + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + T* e = m_first; + m_first = m_first->next; + if (e == m_last) m_last = nullptr; + e->next = nullptr; + --m_size; + return e; + } + void push_front(T* e) + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + e->next = m_first; + m_first = e; + if (!m_last) m_last = e; + ++m_size; + } + void push_back(T* e) + { + TORRENT_ASSERT(e->next == nullptr); + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + e->next = nullptr; + ++m_size; + } + T* get_all() + { + TORRENT_ASSERT(m_last == nullptr || m_last->next == nullptr); + T* e = m_first; + m_first = nullptr; + m_last = nullptr; + m_size = 0; + return e; + } + void swap(tailqueue& rhs) + { + T* tmp = m_first; + m_first = rhs.m_first; + rhs.m_first = tmp; + tmp = m_last; + m_last = rhs.m_last; + rhs.m_last = tmp; + int tmp2 = m_size; + m_size = rhs.m_size; + rhs.m_size = tmp2; + } + int size() const { TORRENT_ASSERT(m_size >= 0); return m_size; } + bool empty() const { TORRENT_ASSERT(m_size >= 0); return m_size == 0; } + T* first() const { TORRENT_ASSERT(m_size > 0); return m_first; } + T* last() const { TORRENT_ASSERT(m_size > 0); return m_last; } + private: + T* m_first; + T* m_last; + int m_size; + }; +} + +#endif // TAILQUEUE_HPP diff --git a/include/libtorrent/time.hpp b/include/libtorrent/time.hpp new file mode 100644 index 0000000..d037311 --- /dev/null +++ b/include/libtorrent/time.hpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TIME_HPP_INCLUDED +#define TORRENT_TIME_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include + +#if defined TORRENT_BUILD_SIMULATOR +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { + +#if defined TORRENT_BUILD_SIMULATOR + using clock_type = sim::chrono::high_resolution_clock; +#else + using clock_type = std::chrono::high_resolution_clock; +#endif + + using time_point = clock_type::time_point; + using time_duration = clock_type::duration; + + // 32 bit versions of time_point and duration, with second resolution + using seconds32 = std::chrono::duration; + using minutes32 = std::chrono::duration>; + using time_point32 = std::chrono::time_point; + + using seconds = std::chrono::seconds; + using milliseconds = std::chrono::milliseconds; + using microseconds = std::chrono::microseconds; + using minutes = std::chrono::minutes; + using hours = std::chrono::hours; + using std::chrono::duration_cast; + using std::chrono::time_point_cast; + + // internal + inline time_point min_time() { return (time_point::min)(); } + + // internal + inline time_point max_time() { return (time_point::max)(); } + + template + std::int64_t total_seconds(T td) + { return duration_cast(td).count(); } + + template + std::int64_t total_milliseconds(T td) + { return duration_cast(td).count(); } + + template + std::int64_t total_microseconds(T td) + { return duration_cast(td).count(); } + +} + +#endif // TORRENT_TIME_HPP_INCLUDED diff --git a/include/libtorrent/timestamp_history.hpp b/include/libtorrent/timestamp_history.hpp new file mode 100644 index 0000000..a78e7be --- /dev/null +++ b/include/libtorrent/timestamp_history.hpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TIMESTAMP_HISTORY_HPP +#define TIMESTAMP_HISTORY_HPP + +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + +// timestamp history keeps a history of the lowest timestamps we've +// seen in the last 20 minutes +struct TORRENT_EXTRA_EXPORT timestamp_history +{ + static constexpr int history_size = 20; + + timestamp_history() = default; + bool initialized() const { return m_num_samples != not_initialized; } + + // add a sample to the timestamp history. If step is true, it's been + // a minute since the last step + std::uint32_t add_sample(std::uint32_t sample, bool step); + std::uint32_t base() const { TORRENT_ASSERT(initialized()); return m_base; } + void adjust_base(int change); + +private: + + // this is a circular buffer + std::array m_history; + + // this is the lowest sample seen in the + // last 'history_size' minutes + std::uint32_t m_base = 0; + + // and this is the index we're currently at + // in the circular buffer + std::uint16_t m_index = 0; + + static constexpr std::uint16_t not_initialized = 0xffff; + + // this is the number of samples since the + // last time we stepped one minute. If we + // don't have enough samples, we won't step + // if this is set to 'not_initialized' we + // have bit seen any samples at all yet + // and m_base is not initialized yet + std::uint16_t m_num_samples = not_initialized; +}; + +} + +#endif + diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp new file mode 100644 index 0000000..38a9063 --- /dev/null +++ b/include/libtorrent/torrent.hpp @@ -0,0 +1,1767 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HPP_INCLUDE +#define TORRENT_TORRENT_HPP_INCLUDE + +#include +#include +#include +#include +#include +#include // for numeric_limits +#include // for unique_ptr + +#include "libtorrent/fwd.hpp" +#include "libtorrent/optional.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/aux_/suggest_piece.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/deferred_handler.hpp" +#include "libtorrent/aux_/allocating_handler.hpp" +#include "libtorrent/extensions.hpp" // for add_peer_flags_t + +#ifdef TORRENT_USE_OPENSSL +// there is no forward declaration header for asio +namespace boost { +namespace asio { +namespace ssl { + class context; + class verify_context; +} +} +} +#endif + +#if TORRENT_COMPLETE_TYPES_REQUIRED +#include "libtorrent/peer_connection.hpp" +#endif + +// define as 0 to disable. 1 enables debug output of the pieces and requested +// blocks. 2 also enables trace output of the time critical piece picking +// logic +#define TORRENT_DEBUG_STREAMING 0 + +namespace libtorrent { + + class http_parser; + struct tracker_request; + class bt_peer_connection; + + using web_seed_flag_t = flags::bitfield_flag; + + // internal + enum class waste_reason + { + piece_timed_out, piece_cancelled, piece_unknown, piece_seed + , piece_end_game, piece_closing + , max + }; + + TORRENT_EXTRA_EXPORT std::int64_t calc_bytes(file_storage const& fs, piece_count const& pc); + +#ifndef TORRENT_DISABLE_STREAMING + struct time_critical_piece + { + // when this piece was first requested + time_point first_requested; + // when this piece was last requested + time_point last_requested; + // by what time we want this piece + time_point deadline; + // 1 = send alert with piece data when available + deadline_flags_t flags; + // how many peers it's been requested from + int peers; + // the piece index + piece_index_t piece; +#if TORRENT_DEBUG_STREAMING > 0 + // the number of multiple requests are allowed + // to blocks still not downloaded (debugging only) + int timed_out; +#endif + bool operator<(time_critical_piece const& rhs) const + { return deadline < rhs.deadline; } + }; +#endif // TORRENT_DISABLE_STREAMING + + // this is the internal representation of web seeds + struct web_seed_t : web_seed_entry + { + explicit web_seed_t(web_seed_entry const& wse); + web_seed_t(std::string const& url_, web_seed_entry::type_t type_ + , std::string const& auth_ = std::string() + , web_seed_entry::headers_t const& extra_headers_ = web_seed_entry::headers_t()); + + // if this is > now, we can't reconnect yet + time_point32 retry = aux::time_now32(); + + // if the hostname of the web seed has been resolved, + // these are its IP addresses + std::vector endpoints; + + // this is the peer_info field used for the + // connection, just to count hash failures + // it's also used to hold the peer_connection + // pointer, when the web seed is connected + ipv4_peer peer_info{tcp::endpoint(), true, {}}; + + // this is initialized to true, but if we discover the + // server not to support it, it's set to false, and we + // make larger requests. + bool supports_keepalive = true; + + // this indicates whether or not we're resolving the + // hostname of this URL + bool resolving = false; + + // if the user wanted to remove this while + // we were resolving it. In this case, we set + // the removed flag to true, to make the resolver + // callback remove it + bool removed = false; + + // this indicates whether this web seed has any files. A server that only + // redirects to other servers for instance, may not have any files and + // once we've seen all redirects, there's no point in connecting to it + // again. + bool interesting = true; + + // if this is true, this URL was created by a redirect and should not be + // saved in the resume data + bool ephemeral = false; + + // if the web server doesn't support keepalive or a block request was + // interrupted, the block received so far is kept here for the next + // connection to pick up + peer_request restart_request = { piece_index_t(-1), -1, -1}; + std::vector restart_piece; + + // this maps file index to a URL it has been redirected to. If an entry is + // missing, it means it has not been redirected and the full path should + // be constructed normally based on the filename. All redirections are + // relative to the web seed hostname root. + std::map redirects; + + // if this bitfield is non-empty, it represents the files this web server + // has. + typed_bitfield have_files; +#if defined __GNUC__ && defined _GLIBCXX_DEBUG + // this works around a bug in libstdc++'s checked iterators + // http://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle + web_seed_t& operator=(web_seed_t&& rhs) noexcept + { + if (&rhs == this) return *this; + + web_seed_entry::operator=(std::move(rhs)); + retry = std::move(rhs.retry); + endpoints = std::move(rhs.endpoints); + peer_info = std::move(rhs.peer_info); + supports_keepalive = std::move(rhs.supports_keepalive); + resolving = std::move(rhs.resolving); + removed = std::move(rhs.removed); + ephemeral = std::move(rhs.ephemeral); + restart_request = std::move(rhs.restart_request); + restart_piece = std::move(rhs.restart_piece); + redirects = std::move(rhs.redirects); + have_files = std::move(rhs.have_files); + return *this; + } + + web_seed_t& operator=(web_seed_t const&) = default; + web_seed_t(web_seed_t const&) = default; +#endif + }; + + struct TORRENT_EXTRA_EXPORT torrent_hot_members + { + torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, bool session_paused); + + protected: + // the piece picker. This is allocated lazily. When we don't + // have anything in the torrent (for instance, if it hasn't + // been started yet) or if we have everything, there is no + // picker. It's allocated on-demand the first time we need + // it in torrent::need_picker(). In order to tell the + // difference between having everything and nothing in + // the case there is no piece picker, see m_have_all. + std::unique_ptr m_picker; + + // TODO: make this a raw pointer. perhaps keep the shared_ptr + // around further down the object to maintain an owner + std::shared_ptr m_torrent_file; + + // a back reference to the session + // this torrent belongs to. + aux::session_interface& m_ses; + + // this vector is sorted at all times, by the pointer value. + // use sorted_insert() and sorted_find() on it. The GNU STL + // implementation on Darwin uses significantly less memory to + // represent a vector than a set, and this set is typically + // relatively small, and it's cheap to copy pointers. + aux::vector m_connections; + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_complete:24; + + // set to true when this torrent may not download anything + bool m_upload_mode:1; + + // this is set to false as long as the connections + // of this torrent hasn't been initialized. If we + // have metadata from the start, connections are + // initialized immediately, if we didn't have metadata, + // they are initialized right after files_checked(). + // valid_resume_data() will return false as long as + // the connections aren't initialized, to avoid + // them from altering the piece-picker before it + // has been initialized with files_checked(). + bool m_connections_initialized:1; + + // is set to true when the torrent has + // been aborted. + bool m_abort:1; + + // is true if this torrent has allows having peers + bool m_paused:1; + + // is true if the session is paused, in which case the torrent is + // effectively paused as well. + bool m_session_paused:1; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // this is set when the torrent is in share-mode + bool m_share_mode:1; +#endif + + // this is true if we have all pieces. If it's false, + // it means we either don't have any pieces, or, if + // there is a piece_picker object present, it contains + // the state of how many pieces we have + bool m_have_all:1; + + // set to true when this torrent has been paused but + // is waiting to finish all current download requests + // before actually closing all connections, when in graceful pause mode, + // m_paused is also true. + bool m_graceful_pause_mode:1; + + // state subscription. If set, a pointer to this torrent will be added + // to the session_impl::m_torrent_lists[torrent_state_updates] + // whenever this torrent's state changes (any state). + bool m_state_subscription:1; + + // the maximum number of connections for this torrent + std::uint32_t m_max_connections:24; + + // the state of this torrent (queued, checking, downloading, etc.) + std::uint32_t m_state:3; + + std::unique_ptr m_peer_list; + }; + + // a torrent is a class that holds information + // for a specific download. It updates itself against + // the tracker + class TORRENT_EXTRA_EXPORT torrent + : private single_threaded + , private torrent_hot_members + , public request_callback + , public peer_class_set + , public aux::error_handler_interface + , public std::enable_shared_from_this + { + public: + + torrent(aux::session_interface& ses + , bool session_paused, add_torrent_params const& p); + ~torrent() override; + + // This may be called from multiple threads + sha1_hash const& info_hash() const { return m_info_hash; } + + bool is_deleted() const { return m_deleted; } + + // starts the announce timer + void start(); + + void added() + { + TORRENT_ASSERT(m_added == false); + m_added = true; + update_gauge(); + } + + void removed() + { + TORRENT_ASSERT(m_added == true); + m_added = false; + set_queue_position(no_pos); + // make sure we decrement the gauge counter for this torrent + update_gauge(); + } + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + void start_download_url(); +#endif + + // returns which stats gauge this torrent currently + // has incremented. + int current_stats_state() const; + +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extension(std::shared_ptr); + void remove_extension(std::shared_ptr); + void add_extension_fun(std::function(torrent_handle const&, void*)> const& ext + , void* userdata); + void notify_extension_add_peer(tcp::endpoint const& ip + , peer_source_flags_t src, add_peer_flags_t flags); +#endif + + peer_connection* find_lowest_ranking_peer() const; + +#if TORRENT_USE_ASSERTS + bool has_peer(peer_connection const* p) const + { return sorted_find(m_connections, p) != m_connections.end(); } + bool is_single_thread() const { return single_threaded::is_single_thread(); } +#endif + + // this is called when the torrent has metadata. + // it will initialize the storage and the piece-picker + void init(); + + // find the peer that introduced us to the given endpoint. This is + // used when trying to holepunch. We need the introducer so that we + // can send a rendezvous connect message + bt_peer_connection* find_introducer(tcp::endpoint const& ep) const; + + // if we're connected to a peer at ep, return its peer connection + // only count BitTorrent peers + bt_peer_connection* find_peer(tcp::endpoint const& ep) const; + peer_connection* find_peer(peer_id const& pid); + + // checks to see if this peer id is used in one of our own outgoing + // connections. + bool is_self_connection(peer_id const& pid) const; + + void on_resume_data_checked(status_t status, storage_error const& error); + void on_force_recheck(status_t status, storage_error const& error); + void on_piece_hashed(piece_index_t piece, sha1_hash const& piece_hash + , storage_error const& error); + void files_checked(); + void start_checking(); + + void start_announcing(); + void stop_announcing(); + + void send_upload_only(); + +#ifndef TORRENT_DISABLE_SHARE_MODE + void send_share_mode(); + void set_share_mode(bool s); + bool share_mode() const { return m_share_mode; } +#endif + + // TODO: make graceful pause also finish all sending blocks + // before disconnecting + bool graceful_pause() const { return m_graceful_pause_mode; } + + torrent_flags_t flags() const; + void set_flags(torrent_flags_t flags, torrent_flags_t mask); + + void set_upload_mode(bool b); + bool upload_mode() const { return m_upload_mode || m_graceful_pause_mode; } + bool is_upload_only() const { return is_finished() || upload_mode(); } + + int seed_rank(aux::session_settings const& s) const; + + void add_piece(piece_index_t piece, char const* data, add_piece_flags_t flags); + void on_disk_write_complete(storage_error const& error + , peer_request const& p); + + void set_progress_ppm(int p) { m_progress_ppm = std::uint32_t(p); } + struct read_piece_struct + { + boost::shared_array piece_data; + int blocks_left; + bool fail; + error_code error; + }; + void read_piece(piece_index_t piece); + void on_disk_read_complete(disk_buffer_holder block, disk_job_flags_t, storage_error const& se + , peer_request const& r, std::shared_ptr rp); + + storage_mode_t storage_mode() const; + + // this will flag the torrent as aborted. The main + // loop in session_impl will check for this state + // on all torrents once every second, and take + // the necessary actions then. + void abort(); + bool is_aborted() const { return m_abort; } + void panic(); + + void new_external_ip(); + + torrent_status::state_t state() const + { return torrent_status::state_t(m_state); } + void set_state(torrent_status::state_t s); + + aux::session_settings const& settings() const; + aux::session_interface& session() { return m_ses; } + + void set_sequential_download(bool sd); + bool is_sequential_download() const + { return m_sequential_download || m_auto_sequential; } + + void queue_up(); + void queue_down(); + void set_queue_position(queue_position_t p); + queue_position_t queue_position() const { return m_sequence_number; } + // used internally + void set_queue_position_impl(queue_position_t const p) + { + if (m_sequence_number == p) return; + m_sequence_number = p; + state_updated(); + } + + void second_tick(int tick_interval_ms); + + // see if we need to connect to web seeds, and if so, + // connect to them + void maybe_connect_web_seeds(); + + std::string name() const; + + stat statistics() const { return m_stat; } + boost::optional bytes_left() const; + + void bytes_done(torrent_status& st, status_flags_t) const; + + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); + + void set_ip_filter(std::shared_ptr ipf); + void port_filter_updated(); + ip_filter const* get_ip_filter() { return m_ip_filter.get(); } + + std::string resolve_filename(file_index_t file) const; + void handle_exception(); + + enum class disk_class { none, write }; + void handle_disk_error(string_view job_name + , storage_error const& error, peer_connection* c = nullptr + , disk_class rw = disk_class::none); + void clear_error(); + + void set_error(error_code const& ec, file_index_t file); + bool has_error() const { return !!m_error; } + error_code error() const { return m_error; } + + void flush_cache(); + void pause(pause_flags_t flags = {}); + void resume(); + + void set_session_paused(bool b); + void set_paused(bool b, pause_flags_t flags = torrent_handle::clear_disk_cache); + void set_announce_to_dht(bool b) { m_announce_to_dht = b; } + void set_announce_to_trackers(bool b) { m_announce_to_trackers = b; } + void set_announce_to_lsd(bool b) { m_announce_to_lsd = b; } + + void stop_when_ready(bool b); + + time_point32 started() const { return m_started; } + void step_session_time(int seconds); + void do_pause(pause_flags_t flags = torrent_handle::clear_disk_cache); + void do_resume(); + + seconds32 finished_time() const; + seconds32 active_time() const; + seconds32 seeding_time() const; + seconds32 upload_mode_time() const; + + bool is_paused() const; + bool is_torrent_paused() const { return m_paused; } + void force_recheck(); + void save_resume_data(resume_data_flags_t flags); + + bool need_save_resume_data() const { return m_need_save_resume_data; } + + void set_need_save_resume() + { + m_need_save_resume_data = true; + } + + bool is_auto_managed() const { return m_auto_managed; } + void auto_managed(bool a); + + bool should_check_files() const; + + bool delete_files(remove_flags_t options); + void peers_erased(std::vector const& peers); + +#if TORRENT_ABI_VERSION == 1 +#if !TORRENT_NO_FPU + void file_progress_float(aux::vector& fp); +#endif +#endif // TORRENT_ABI_VERSION + + void piece_availability(aux::vector& avail) const; + + void set_piece_priority(piece_index_t index, download_priority_t priority); + download_priority_t piece_priority(piece_index_t index) const; + + void prioritize_pieces(aux::vector const& pieces); + void prioritize_piece_list(std::vector> const& pieces); + void piece_priorities(aux::vector*) const; + + void set_file_priority(file_index_t index, download_priority_t priority); + download_priority_t file_priority(file_index_t index) const; + + void on_file_priority(storage_error const& err, aux::vector prios); + void prioritize_files(aux::vector files); + void file_priorities(aux::vector*) const; + +#ifndef TORRENT_DISABLE_STREAMING + void cancel_non_critical(); + void set_piece_deadline(piece_index_t piece, int t, deadline_flags_t flags); + void reset_piece_deadline(piece_index_t piece); + void clear_time_critical(); +#endif // TORRENT_DISABLE_STREAMING + + void update_piece_priorities( + aux::vector const& file_prios); + + void status(torrent_status* st, status_flags_t flags); + + // this torrent changed state, if the user is subscribing to + // it, add it to the m_state_updates list in session_impl + void state_updated(); + + void file_progress(aux::vector& fp, int flags = 0); + +#if TORRENT_ABI_VERSION == 1 + void use_interface(std::string net_interface); +#endif + + void connect_to_url_seed(std::list::iterator url); + bool connect_to_peer(torrent_peer* peerinfo, bool ignore_limit = false); + + int priority() const; +#if TORRENT_ABI_VERSION == 1 + void set_priority(int prio); +#endif // TORRENT_ABI_VERSION + +// -------------------------------------------- + // BANDWIDTH MANAGEMENT + + void set_upload_limit(int limit); + int upload_limit() const; + void set_download_limit(int limit); + int download_limit() const; + + peer_class_t peer_class() const { return m_peer_class; } + + void set_max_uploads(int limit, bool state_update = true); + int max_uploads() const { return int(m_max_uploads); } + void set_max_connections(int limit, bool state_update = true); + int max_connections() const { return int(m_max_connections); } + +// -------------------------------------------- + // PEER MANAGEMENT + + static constexpr web_seed_flag_t ephemeral = 0_bit; + + // add_web_seed won't add duplicates. If we have already added an entry + // with this URL, we'll get back the existing entry + web_seed_t* add_web_seed(std::string const& url + , web_seed_t::type_t type + , std::string const& auth = std::string() + , web_seed_t::headers_t const& extra_headers = web_seed_entry::headers_t() + , web_seed_flag_t flags = {}); + + void remove_web_seed(std::string const& url, web_seed_t::type_t type); + void disconnect_web_seed(peer_connection* p); + + void retry_web_seed(peer_connection* p, boost::optional retry = boost::none); + + void remove_web_seed_conn(peer_connection* p, error_code const& ec + , operation_t op, disconnect_severity_t error = peer_connection_interface::normal); + + std::set web_seeds(web_seed_entry::type_t type) const; + + bool free_upload_slots() const + { return m_num_uploads < m_max_uploads; } + + bool choke_peer(peer_connection& c); + bool unchoke_peer(peer_connection& c, bool optimistic = false); + + void trigger_unchoke() noexcept; + void trigger_optimistic_unchoke() noexcept; + + // used by peer_connection to attach itself to a torrent + // since incoming connections don't know what torrent + // they're a part of until they have received an info_hash. + // false means attach failed + bool attach_peer(peer_connection* p); + + // this will remove the peer and make sure all + // the pieces it had have their reference counter + // decreased in the piece_picker + void remove_peer(std::shared_ptr p) noexcept; + + // cancel requests to this block from any peer we're + // connected to on this torrent + void cancel_block(piece_block block); + + bool want_tick() const; + void update_want_tick(); + void update_state_list(); + + bool want_peers() const; + bool want_peers_download() const; + bool want_peers_finished() const; + + void update_want_peers(); + void update_want_scrape(); + void update_gauge(); + + bool try_connect_peer(); + torrent_peer* add_peer(tcp::endpoint const& adr + , peer_source_flags_t source, pex_flags_t flags = {}); + bool ban_peer(torrent_peer* tp); + void update_peer_port(int port, torrent_peer* p, peer_source_flags_t src); + void set_seed(torrent_peer* p, bool s); + void clear_failcount(torrent_peer* p); + std::pair find_peers(address const& a); + + // the number of peers that belong to this torrent + int num_peers() const { return int(m_connections.size() - m_peers_to_disconnect.size()); } + int num_seeds() const; + int num_downloaders() const; + + using peer_iterator = std::vector::iterator; + using const_peer_iterator = std::vector::const_iterator; + + const_peer_iterator begin() const { return m_connections.begin(); } + const_peer_iterator end() const { return m_connections.end(); } + + peer_iterator begin() { return m_connections.begin(); } + peer_iterator end() { return m_connections.end(); } + +#if TORRENT_ABI_VERSION == 1 + void get_full_peer_list(std::vector* v) const; +#endif + void get_peer_info(std::vector* v); + void get_download_queue(std::vector* queue) const; + + void update_auto_sequential(); + private: + void remove_connection(peer_connection const* p); + public: +// -------------------------------------------- + // TRACKER MANAGEMENT + + // these are callbacks called by the tracker_connection instance + // (either http_tracker_connection or udp_tracker_connection) + // when this torrent got a response from its tracker request + // or when a failure occurred + void tracker_response( + tracker_request const& r + , address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& resp) override; + void tracker_request_error(tracker_request const& r + , error_code const& ec, const std::string& msg + , seconds32 retry_interval) override; + void tracker_warning(tracker_request const& req + , std::string const& msg) override; + void tracker_scrape_response(tracker_request const& req + , int complete, int incomplete, int downloaded, int downloaders) override; + + void update_scrape_state(); + +#if TORRENT_ABI_VERSION == 1 + // if no password and username is set + // this will return an empty string, otherwise + // it will concatenate the login and password + // ready to be sent over http (but without + // base64 encoding). + std::string tracker_login() const; +#endif + + // generate the tracker key for this torrent. + // The key is passed to http trackers as ``&key=``. + std::uint32_t tracker_key() const; + + // if we need a connect boost, connect some peers + // immediately + void do_connect_boost(); + + // forcefully sets next_announce to the current time + void force_tracker_request(time_point, int tracker_idx, reannounce_flags_t flags); + void scrape_tracker(int idx, bool user_triggered); + void announce_with_tracker(std::uint8_t e + = tracker_request::none); + +#ifndef TORRENT_DISABLE_DHT + void dht_announce(); +#endif + +#if TORRENT_ABI_VERSION == 1 + // sets the username and password that will be sent to + // the tracker + void set_tracker_login(std::string const& name, std::string const& pw); +#endif + + announce_entry* find_tracker(std::string const& url); +// -------------------------------------------- + // PIECE MANAGEMENT + +#ifndef TORRENT_DISABLE_SHARE_MODE + void recalc_share_mode(); +#endif + +#ifndef TORRENT_DISABLE_SUPERSEEDING + bool super_seeding() const + { + // we're not super seeding if we're not a seed + return m_super_seeding; + } + + void set_super_seeding(bool on); + piece_index_t get_piece_to_super_seed(typed_bitfield const&); +#endif + + // returns true if we have downloaded the given piece + bool have_piece(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (!has_picker()) return m_have_all; + return m_picker->have_piece(index); + } + + // returns true if we have downloaded the given piece + bool user_have_piece(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (index < piece_index_t{0} || index >= m_torrent_file->end_piece()) return false; + if (!has_picker()) return m_have_all; + return m_picker->have_piece(index); + } + + // returns true if we have downloaded the given piece + bool has_piece_passed(piece_index_t index) const + { + if (!valid_metadata()) return false; + if (index < piece_index_t(0) || index >= torrent_file().end_piece()) return false; + if (!has_picker()) return m_have_all; + return m_picker->has_piece_passed(index); + } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // a predictive piece is a piece that we might + // not have yet, but still announced to peers, anticipating that + // we'll have it very soon + bool is_predictive_piece(piece_index_t index) const + { + return std::binary_search(m_predictive_pieces.begin(), m_predictive_pieces.end(), index); + } +#endif // TORRENT_DISABLE_PREDICTIVE_PIECES + + private: + + // called when we learn that we have a piece + // only once per piece + void we_have(piece_index_t index); + + public: + + int num_have() const + { + // pretend we have every piece when in seed mode + if (m_seed_mode) return m_torrent_file->num_pieces(); + if (has_picker()) return m_picker->have().num_pieces; + if (m_have_all) return m_torrent_file->num_pieces(); + return 0; + } + + // the number of pieces that have passed + // hash check, but aren't necessarily + // flushed to disk yet + int num_passed() const + { + if (has_picker()) return m_picker->num_passed(); + if (m_have_all) return m_torrent_file->num_pieces(); + return 0; + } + + // when we get a have message, this is called for that piece + void peer_has(piece_index_t index, peer_connection const* peer); + + // when we get a bitfield message, this is called for that piece + void peer_has(typed_bitfield const& bits, peer_connection const* peer); + + void peer_has_all(peer_connection const* peer); + + void peer_lost(piece_index_t index, peer_connection const* peer); + void peer_lost(typed_bitfield const& bits + , peer_connection const* peer); + + int block_size() const + { + return valid_metadata() + ? (std::min)(m_torrent_file->piece_length(), default_block_size) + : default_block_size; + } + peer_request to_req(piece_block const& p) const; + + void disconnect_all(error_code const& ec, operation_t op); + int disconnect_peers(int num, error_code const& ec); + + // called every time a block is marked as finished in the + // piece picker. We might have completed the torrent and + // we can delete the piece picker + void maybe_done_flushing(); + + // this is called when the torrent has completed + // the download. It will post an event, disconnect + // all seeds and let the tracker know we're finished. + void completed(); + +#if TORRENT_USE_I2P + void on_i2p_resolve(error_code const& ec, char const* dest); + bool is_i2p() const { return m_torrent_file && m_torrent_file->is_i2p(); } +#endif + + // this is the asio callback that is called when a name + // lookup for a PEER is completed. + void on_peer_name_lookup(error_code const& e + , std::vector
    const& addrs + , int port); + + // this is the asio callback that is called when a name + // lookup for a WEB SEED is completed. + void on_name_lookup(error_code const& e + , std::vector
    const& addrs + , int port + , std::list::iterator web); + + void connect_web_seed(std::list::iterator web, tcp::endpoint a); + + // this is the asio callback that is called when a name + // lookup for a proxy for a web seed is completed. + void on_proxy_name_lookup(error_code const& e + , std::vector
    const& addrs + , std::list::iterator web, int port); + + // re-evaluates whether this torrent should be considered inactive or not + void on_inactivity_tick(error_code const& ec); + + + // calculate the instantaneous inactive state (the externally facing + // inactive state is not instantaneous, but low-pass filtered) + bool is_inactive_internal() const; + + // remove a web seed, or schedule it for removal in case there + // are outstanding operations on it + void remove_web_seed_iter(std::list::iterator web); + + // this is called when the torrent has finished. i.e. + // all the pieces we have not filtered have been downloaded. + // If no pieces are filtered, this is called first and then + // completed() is called immediately after it. + void finished(); + + // This is the opposite of finished. It is called if we used + // to be finished but enabled some files for download so that + // we wasn't finished anymore. + void resume_download(); + + void verify_piece(piece_index_t piece); + void on_piece_verified(piece_index_t piece + , sha1_hash const& piece_hash, storage_error const& error); + + // this is called whenever a peer in this swarm becomes interesting + // it is responsible for issuing a block request, if appropriate + void peer_is_interesting(peer_connection& c); + + // piece_passed is called when a piece passes the hash check + // this will tell all peers that we just got his piece + // and also let the piece picker know that we have this piece + // so it wont pick it for download + void piece_passed(piece_index_t index); + + // piece_failed is called when a piece fails the hash check + void piece_failed(piece_index_t index); + + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(piece_index_t piece); + + // this is the handler for write failure piece synchronization + void on_piece_fail_sync(piece_index_t piece, piece_block b); + + void add_redundant_bytes(int b, waste_reason reason); + void add_failed_bytes(int b); + + // this is true if we have all the pieces, but not necessarily flushed them to disk + bool is_seed() const; + + // this is true if we have all the pieces that we want + // the pieces don't necessarily need to be flushed to disk + bool is_finished() const; + + bool is_inactive() const; + + std::string save_path() const; + alert_manager& alerts() const; + piece_picker& picker() + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + piece_picker const& picker() const + { + TORRENT_ASSERT(m_picker.get()); + return *m_picker; + } + void need_picker(); + bool has_picker() const + { + return m_picker.get() != nullptr; + } + + void update_max_failcount() + { + if (!m_peer_list) return; + torrent_state st = get_peer_list_state(); + m_peer_list->set_max_failcount(&st); + } + int num_known_peers() const { return m_peer_list ? m_peer_list->num_peers() : 0; } + int num_connect_candidates() const { return m_peer_list ? m_peer_list->num_connect_candidates() : 0; } + + void clear_peers(); + + bool has_storage() const { return bool(m_storage); } + storage_index_t storage() const { return m_storage; } + storage_interface* get_storage_impl() const; + + torrent_info const& torrent_file() const + { return *m_torrent_file; } + + std::shared_ptr get_torrent_copy(); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + std::string const& uuid() const { return m_uuid; } + void set_uuid(std::string const& s) { m_uuid = s; } + std::string const& url() const { return m_url; } + void set_url(std::string const& s) { m_url = s; } + std::string const& source_feed_url() const { return m_source_feed_url; } + void set_source_feed_url(std::string const& s) { m_source_feed_url = s; } +#endif + + std::vector const& trackers() const + { return m_trackers; } + + // this sets all the "enabled" states on all trackers, giving them + // all one more chance of being tried + void enable_all_trackers(); + + void replace_trackers(std::vector const& urls); + + // returns true if the tracker was added, and false if it was already + // in the tracker list (in which case the source was added to the + // entry in the list) + bool add_tracker(announce_entry const& url); + + torrent_handle get_handle(); + + void write_resume_data(add_torrent_params& atp) const; + + void seen_complete() { m_last_seen_complete = ::time(nullptr); } + int time_since_complete() const { return int(::time(nullptr) - m_last_seen_complete); } + time_t last_seen_complete() const { return m_last_seen_complete; } + + template + void wrap(Fun f, Args&&... a); + + // LOGGING +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const override; + void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3); + + void log_to_all_peers(char const* message); + time_point m_dht_start_time; +#endif + + // DEBUG +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + +// -------------------------------------------- + // RESOURCE MANAGEMENT + + // flags are defined in storage.hpp + void move_storage(std::string const& save_path, move_flags_t flags); + + // renames the file with the given index to the new name + // the name may include a directory path + // posts alert to indicate success or failure + void rename_file(file_index_t index, std::string name); + + // unless this returns true, new connections must wait + // with their initialization. + bool ready_for_connections() const + { return m_connections_initialized; } + bool valid_metadata() const + { return m_torrent_file->is_valid(); } + bool are_files_checked() const + { return m_files_checked; } + + // parses the info section from the given + // bencoded tree and moves the torrent + // to the checker thread for initial checking + // of the storage. + // a return value of false indicates an error + bool set_metadata(span metadata); + +#if TORRENT_ABI_VERSION == 1 + void on_torrent_download(error_code const& ec, http_parser const& parser + , span data); +#endif + + queue_position_t sequence_number() const { return m_sequence_number; } + + bool seed_mode() const { return m_seed_mode; } + + enum class seed_mode_t { check_files, skip_checking }; + + void leave_seed_mode(seed_mode_t checking); + + bool all_verified() const + { return int(m_num_verified) == m_torrent_file->num_pieces(); } + bool verifying_piece(piece_index_t const piece) const + { return m_verifying.get_bit(piece); } + void verifying(piece_index_t const piece) + { + TORRENT_ASSERT(m_verifying.get_bit(piece) == false); + m_verifying.set_bit(piece); + } + bool verified_piece(piece_index_t piece) const + { return m_verified.get_bit(piece); } + void verified(piece_index_t piece); + + bool add_merkle_nodes(std::map const& n, piece_index_t piece); + + // this is called once periodically for torrents + // that are not private + void lsd_announce(); + + void update_last_upload() { m_last_upload = aux::time_now32(); } + + void set_apply_ip_filter(bool b); + bool apply_ip_filter() const { return m_apply_ip_filter; } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + std::vector const& predictive_pieces() const + { return m_predictive_pieces; } + + // this is called whenever we predict to have this piece + // within one second + void predicted_have_piece(piece_index_t index, int milliseconds); +#endif + + void clear_in_state_update() + { + TORRENT_ASSERT(m_links[aux::session_interface::torrent_state_updates].in_list()); + m_links[aux::session_interface::torrent_state_updates].clear(); + } + + void inc_num_connecting(torrent_peer* pp) + { + ++m_num_connecting; + if (pp->seed) ++m_num_connecting_seeds; + } + void dec_num_connecting(torrent_peer* pp) + { + TORRENT_ASSERT(m_num_connecting > 0); + --m_num_connecting; + if (pp->seed) + { + TORRENT_ASSERT(m_num_connecting_seeds > 0); + --m_num_connecting_seeds; + } + TORRENT_ASSERT(m_num_connecting <= int(m_connections.size())); + } + + bool is_ssl_torrent() const { return m_ssl_torrent; } +#ifdef TORRENT_USE_OPENSSL + void set_ssl_cert(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase); + void set_ssl_cert_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + boost::asio::ssl::context* ssl_ctx() const { return m_ssl_ctx.get(); } +#endif + + int num_time_critical_pieces() const + { +#ifndef TORRENT_DISABLE_STREAMING + return int(m_time_critical_pieces.size()); +#else + return 0; +#endif + } + + int get_suggest_pieces(std::vector& p + , typed_bitfield const& bits + , int const n) + { + return m_suggest_pieces.get_pieces(p, bits, n); + } + void add_suggest_piece(piece_index_t index); + + static constexpr int no_gauge_state = 0xf; + + private: + + void on_exception(std::exception const& e) override; + void on_error(error_code const& ec) override; + + // trigger deferred disconnection of peers + void on_remove_peers() noexcept; + + void ip_filter_updated(); + + void inc_stats_counter(int c, int value = 1); + + // initialize the torrent_state structure passed to peer_list + // member functions. Don't forget to also call peers_erased() + // on the erased member after the peer_list call + torrent_state get_peer_list_state(); + + void construct_storage(); + void update_list(torrent_list_index_t list, bool in); + + void on_files_deleted(storage_error const& error); + void on_torrent_paused(); + void on_storage_moved(status_t status, std::string const& path + , storage_error const& error); + void on_file_renamed(std::string const& filename + , file_index_t file_idx + , storage_error const& error); + void on_cache_flushed(bool manually_triggered); + + // this is used when a torrent is being removed.It synchronizes with the + // disk thread + void on_torrent_aborted(); + + // upload and download rate limits for the torrent + void set_limit_impl(int limit, int channel, bool state_update = true); + int limit_impl(int channel) const; + + int deprioritize_tracker(int tracker_index); + + void update_peer_interest(bool was_finished); + void prioritize_udp_trackers(); + + void update_tracker_timer(time_point32 now); + + void on_tracker_announce(error_code const& ec); + +#ifndef TORRENT_DISABLE_DHT + static void on_dht_announce_response_disp(std::weak_ptr t + , std::vector const& peers); + void on_dht_announce_response(std::vector const& peers); + bool should_announce_dht() const; +#endif + +#ifndef TORRENT_DISABLE_STREAMING + void remove_time_critical_piece(piece_index_t piece, bool finished = false); + void remove_time_critical_pieces(aux::vector const& priority); + void request_time_critical_pieces(); +#endif // TORRENT_DISABLE_STREAMING + + void need_peer_list(); + + std::shared_ptr m_ip_filter; + + // all time totals of uploaded and downloaded payload + // stored in resume data + std::int64_t m_total_uploaded = 0; + std::int64_t m_total_downloaded = 0; + + // this is a handle that keeps the storage object in the disk io subsystem + // alive, as well as the index referencing the storage/torrent in the disk + // I/O. When this destructs, the torrent will be removed from the disk + // subsystem. + storage_holder m_storage; + +#ifdef TORRENT_USE_OPENSSL + std::unique_ptr m_ssl_ctx; + + bool verify_peer_cert(bool preverified, boost::asio::ssl::verify_context& ctx); + + void init_ssl(string_view cert); +#endif + + void setup_peer_class(); + + // The list of web seeds in this torrent. Seeds with fatal errors are + // removed from the set. It's important that iterators are not + // invalidated as entries are added and removed from this list, hence the + // std::list + std::list m_web_seeds; + +#ifndef TORRENT_DISABLE_EXTENSIONS + std::list> m_extensions; +#endif + + // used for tracker announces + deadline_timer m_tracker_timer; + + // used to detect when we are active or inactive for long enough + // to trigger the auto-manage logic + deadline_timer m_inactivity_timer; + + // this is the upload and download statistics for the whole torrent. + // it's updated from all its peers once every second. + libtorrent::stat m_stat; + + // ----------------------------- + + // this vector is allocated lazily. If no file priorities are + // ever changed, this remains empty. Any unallocated slot + // implicitly means the file has priority 4. + // TODO: this wastes 5 bits per file + aux::vector m_file_priority; + + // any file priority updates attempted while another file priority update + // is in-progress/outstanding with the disk I/O thread, are queued up in + // this dictionary. Once the outstanding update comes back, all of these + // are applied in one batch + std::map m_deferred_file_priorities; + + // this object is used to track download progress of individual files + aux::file_progress m_file_progress; + + // a queue of the most recent low-availability pieces we accessed on disk. + // These are good candidates for suggesting other peers to request from + // us. + aux::suggest_piece m_suggest_pieces; + + aux::vector m_trackers; + +#ifndef TORRENT_DISABLE_STREAMING + // this list is sorted by time_critical_piece::deadline + std::vector m_time_critical_pieces; +#endif + + std::string m_trackerid; +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.1 + std::string m_username; + std::string m_password; +#endif + + std::string m_save_path; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // if we don't have the metadata, this is a url to + // the torrent file + std::string m_url; + + // if this was added from an RSS feed, this is the unique + // identifier in the feed. + std::string m_uuid; + + // if this torrent was added by an RSS feed, this is the + // URL to that feed + std::string m_source_feed_url; +#endif + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // this is a list of all pieces that we have announced + // as having, without actually having yet. If we receive + // a request for a piece in this list, we need to hold off + // on responding until we have completed the piece and + // verified its hash. If the hash fails, send reject to + // peers with outstanding requests, and dont_have to other + // peers. This vector is ordered, to make lookups fast. + + // TODO: 3 factor out predictive pieces and all operations on it into a + // separate class (to use as memeber here instead) + std::vector m_predictive_pieces; +#endif + + // the performance counters of this session + counters& m_stats_counters; + + // each bit represents a piece. a set bit means + // the piece has had its hash verified. This + // is only used in seed mode (when m_seed_mode + // is true) + typed_bitfield m_verified; + + // this means there is an outstanding, async, operation + // to verify each piece that has a 1 + typed_bitfield m_verifying; + + // set if there's an error on this torrent + error_code m_error; + + // used if there is any resume data. Some of the information from the + // add_torrent_params struct are needed later in the torrent object's life + // cycle, and not in the constructor. So we need to save if away here + std::unique_ptr m_add_torrent_params; + + // if the torrent is started without metadata, it may + // still be given a name until the metadata is received + // once the metadata is received this field will no + // longer be used and will be reset + std::unique_ptr m_name; + + storage_constructor_type m_storage_constructor; + + // the posix time this torrent was added and when + // it was completed. If the torrent isn't yet + // completed, m_completed_time is 0 + std::time_t m_added_time; + std::time_t m_completed_time; + + // this was the last time _we_ saw a seed in this swarm + std::time_t m_last_seen_complete = 0; + + // this is the time last any of our peers saw a seed + // in this swarm + std::time_t m_swarm_last_seen_complete = 0; + + // keep a copy if the info-hash here, so it can be accessed from multiple + // threads, and be cheap to access from the client + sha1_hash m_info_hash; + + public: + // these are the lists this torrent belongs to. For more + // details about each list, see session_impl.hpp. Each list + // represents a group this torrent belongs to and makes it + // efficient to enumerate only torrents belonging to a specific + // group. Such as torrents that want peer connections or want + // to be ticked etc. + + // TODO: 3 factor out the links (as well as update_list() to a separate + // class that torrent can inherit) + aux::array + m_links; + + private: + + // m_num_verified = m_verified.count() + std::uint32_t m_num_verified = 0; + + // if this torrent is running, this was the time + // when it was started. This is used to have a + // bias towards keeping seeding torrents that + // recently was started, to avoid oscillation + // this is specified at a second granularity + // in session-time. see session_impl for details. + // the reference point is stepped forward every 4 + // hours to keep the timestamps fit in 16 bits + time_point32 m_started = aux::time_now32(); + + // if we're a seed, this is the session time + // timestamp of when we became one + time_point32 m_became_seed = aux::time_now32(); + + // if we're finished, this is the session time + // timestamp of when we finished + time_point32 m_became_finished = aux::time_now32(); + + // when checking, this is the first piece we have not + // issued a hash job for + piece_index_t m_checking_piece{0}; + + // the number of pieces we completed the check of + piece_index_t m_num_checked_pieces{0}; + + // if the error occurred on a file, this is the index of that file + // there are a few special cases, when this is negative. See + // set_error() + file_index_t m_error_file; + + // the average time it takes to download one time critical piece + std::int32_t m_average_piece_time = 0; + + // the average piece download time deviation + std::int32_t m_piece_time_deviation = 0; + + // the number of bytes that has been + // downloaded that failed the hash-test + std::int32_t m_total_failed_bytes = 0; + std::int64_t m_total_redundant_bytes = 0; + + // the sequence number for this torrent, this is a + // monotonically increasing number for each added torrent + queue_position_t m_sequence_number; + + // used to post a message to defer disconnecting peers + std::vector> m_peers_to_disconnect; + aux::deferred_handler m_deferred_disconnect; +#ifdef _M_AMD64 + aux::handler_storage<96> m_deferred_handler_storage; +#else + aux::handler_storage<64> m_deferred_handler_storage; +#endif + + // these are the peer IDs we've used for our outgoing peer connections for + // this torrent. If we get an incoming peer claiming to have one of these, + // it's a connection to ourself, and we should reject it. + std::set m_outgoing_pids; + + // for torrents who have a bandwidth limit, this is != 0 + // and refers to a peer_class in the session. + peer_class_t m_peer_class{0}; + + // of all peers in m_connections, this is the number + // of peers that are outgoing and still waiting to + // complete the connection. This is used to possibly + // kick out these connections when we get incoming + // connections (if we've reached the connection limit) + std::uint16_t m_num_connecting = 0; + + // this is the peer id we generate when we add the torrent. Peers won't + // use this (they generate their own peer ids) but this is used in case + // the tracker returns peer IDs, to identify ourself in the peer list to + // avoid connecting back to it. + peer_id m_peer_id; + + // ============================== + // The following members are specifically + // ordered to make the 24 bit members + // properly 32 bit aligned by inserting + // 8 bits after each one + // ============================== + + // the session time timestamp of when we entered upload mode + // if we're currently in upload-mode + time_point32 m_upload_mode_time = aux::time_now32(); + + // true when this torrent should announce to + // trackers + bool m_announce_to_trackers:1; + + // true when this torrent should announce to + // the local network + bool m_announce_to_lsd:1; + + // is set to true every time there is an incoming + // connection to this torrent + bool m_has_incoming:1; + + // this is set to true when the files are checked + // before the files are checked, we don't try to + // connect to peers + bool m_files_checked:1; + + // determines the storage state for this torrent. + unsigned int m_storage_mode:2; + + // this is true while tracker announcing is enabled + // is is disabled while paused and checking files + bool m_announcing:1; + + // this is true when the torrent has been added to the session. Before + // then, it isn't included in the counters (session_stats) + bool m_added:1; + + // this is > 0 while the tracker deadline timer + // is in use. i.e. one or more trackers are waiting + // for a reannounce + std::int8_t m_waiting_tracker = 0; + +// ---- + + // total time we've been active on this torrent. i.e. either (trying to) + // download or seed. does not count time when the torrent is stopped or + // paused. specified in seconds. This only track time _before_ we started + // the torrent this last time. When the torrent is paused, this counter is + // incremented to include this current session. + seconds32 m_active_time{0}; + + // the index to the last tracker that worked + std::int8_t m_last_working_tracker = -1; + +// ---- + + // total time we've been finished with this torrent. + // does not count when the torrent is stopped or paused. + seconds32 m_finished_time{0}; + + // in case the piece picker hasn't been constructed + // when this settings is set, this variable will keep + // its value until the piece picker is created + bool m_sequential_download:1; + + // this is set if the auto_sequential setting is true and this swarm + // satisfies the criteria to be considered high-availability. i.e. if + // there's mostly seeds in the swarm, download the files sequentially + // for improved disk I/O performance. + bool m_auto_sequential:1; + + // this means we haven't verified the file content + // of the files we're seeding. the m_verified bitfield + // indicates which pieces have been verified and which + // haven't + bool m_seed_mode:1; + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // if this is true, we're currently super seeding this + // torrent. + bool m_super_seeding:1; +#endif + + // if this is set, whenever transitioning into a downloading/seeding state + // from a non-downloading/seeding state, the torrent is paused. + bool m_stop_when_ready:1; + + // set to false when saving resume data. Set to true + // whenever something is downloaded + bool m_need_save_resume_data:1; + + // when this is true, this torrent participates in the DHT + bool m_enable_dht:1; + + // when this is true, this torrent participates in local service discovery + bool m_enable_lsd:1; + +// ---- + + // total time we've been available as a seed on this torrent. + // does not count when the torrent is stopped or paused. This value only + // accounts for the time prior to the current start of the torrent. When + // the torrent is paused, this counter is incremented to account for the + // additional seeding time. + seconds32 m_seeding_time{0}; + +// ---- + + // the maximum number of uploads for this torrent + std::uint32_t m_max_uploads:24; + + // these are the flags sent in on a call to save_resume_data + // we need to save them to check them in write_resume_data + resume_data_flags_t m_save_resume_flags; + +// ---- + + // the number of unchoked peers in this torrent + unsigned int m_num_uploads:24; + + // 3 unused bits + + // when this is true, this torrent supports peer exchange + bool m_enable_pex:1; + + // this is set to true if the torrent was started without + // metadata. It is used to save metadata in the resume file + // by default for such torrents. It does not necessarily + // have to be a magnet link. + bool m_magnet_link:1; + + // set to true if the session IP filter applies to this + // torrent or not. Defaults to true. + bool m_apply_ip_filter:1; + + // this is true when our effective inactive state is different from our + // actual inactive state. Whenever this state changes, there is a + // quarantine period until we change the effective state. This is to avoid + // flapping. If the state changes back during this period, we cancel the + // quarantine + bool m_pending_active_change:1; + +// ---- + + // the number of (16kiB) blocks that fall entirely in pad files + // i.e. blocks that we consider we have on start-up + std::uint16_t m_padding_blocks = 0; + + // this is set to the connect boost quota for this torrent. + // After having received this many priority peer connection attempts, it + // falls back onto the steady state peer connection logic, driven by the + // session tick. Each tracker response, as long as this is non-zero, will + // attempt to connect to peers immediately and decrement the counter. + // We give torrents a connect boost when they are first added and then + // every time they resume from being paused. + std::uint8_t m_connect_boost_counter; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_incomplete:24; + + // true when the torrent should announce to + // the DHT + bool m_announce_to_dht:1; + + // even if we're not built to support SSL torrents, + // remember that this is an SSL torrent, so that we don't + // accidentally start seeding it without any authentication. + bool m_ssl_torrent:1; + + // this is set to true if we're trying to delete the + // files belonging to it. When set, don't write any + // more blocks to disk! + bool m_deleted:1; + +// ---- + + // the timestamp of the last piece passed for this torrent specified in + // seconds since epoch. + time_point32 m_last_download{seconds32(0)}; + + // the number of peer connections to seeds. This should be the same as + // counting the peer connections that say true for is_seed() + std::uint16_t m_num_seeds = 0; + + // this is the number of peers that are seeds, and count against + // m_num_seeds, but have not yet been connected + std::uint16_t m_num_connecting_seeds = 0; + + // the timestamp of the last byte uploaded from this torrent specified in + // seconds since epoch. + time_point32 m_last_upload{seconds32(0)}; + +// ---- + + // if this is true, libtorrent may pause and resume + // this torrent depending on queuing rules. Torrents + // started with auto_managed flag set may be added in + // a paused state in case there are no available + // slots. + bool m_auto_managed:1; + + // the current stats gauge this torrent counts against + std::uint32_t m_current_gauge_state:4; + + // set to true while moving the storage + bool m_moving_storage:1; + + // this is true if this torrent is considered inactive from the + // queuing mechanism's point of view. If a torrent doesn't transfer + // at high enough rates, it's inactive. + bool m_inactive:1; + +// ---- + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + std::uint32_t m_downloaded:24; + +#if TORRENT_ABI_VERSION == 1 + // the timestamp of the last scrape request to one of the trackers in + // this torrent specified in session_time. This is signed because it must + // be able to represent time before the session started + time_point32 m_last_scrape{seconds32(0)}; +#endif + +// ---- + + // progress parts per million (the number of + // millionths of completeness) + std::uint32_t m_progress_ppm:20; + + // set to true once init() completes successfully. This is important to + // track in case it fails and need to be retried if the client clears + // the torrent error + bool m_torrent_initialized:1; + + // this is set to true while waiting for an async_set_file_priority + bool m_outstanding_file_priority:1; + + // set to true if we've sent an event=completed to any tracker. This will + // prevent us from sending it again to anyone + bool m_complete_sent:1; + +#if TORRENT_USE_ASSERTS + // set to true when torrent is start()ed. It may only be started once + bool m_was_started = false; + bool m_outstanding_check_files = false; + + // this is set to true while we're looping over m_connections. We may not + // mutate the list while doing this + mutable int m_iterating_connections = 0; +#endif + }; +} + +#endif // TORRENT_TORRENT_HPP_INCLUDED diff --git a/include/libtorrent/torrent_flags.hpp b/include/libtorrent/torrent_flags.hpp new file mode 100644 index 0000000..ba02eda --- /dev/null +++ b/include/libtorrent/torrent_flags.hpp @@ -0,0 +1,303 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_FLAGS_HPP +#define TORRENT_TORRENT_FLAGS_HPP + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/flags.hpp" + +namespace libtorrent { + +using torrent_flags_t = flags::bitfield_flag; + +namespace torrent_flags { + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" +#endif + + // If ``seed_mode`` is set, libtorrent will assume that all files + // are present for this torrent and that they all match the hashes in + // the torrent file. Each time a peer requests to download a block, + // the piece is verified against the hash, unless it has been verified + // already. If a hash fails, the torrent will automatically leave the + // seed mode and recheck all the files. The use case for this mode is + // if a torrent is created and seeded, or if the user already know + // that the files are complete, this is a way to avoid the initial + // file checks, and significantly reduce the startup time. + // + // Setting ``seed_mode`` on a torrent without metadata (a + // .torrent file) is a no-op and will be ignored. + // + // It is not possible to *set* the ``seed_mode`` flag on a torrent after it has + // been added to a session. It is possible to *clear* it though. + constexpr torrent_flags_t seed_mode = 0_bit; + + // If ``upload_mode`` is set, the torrent will be initialized in + // upload-mode, which means it will not make any piece requests. This + // state is typically entered on disk I/O errors, and if the torrent + // is also auto managed, it will be taken out of this state + // periodically (see ``settings_pack::optimistic_disk_retry``). + // + // This mode can be used to avoid race conditions when + // adjusting priorities of pieces before allowing the torrent to start + // downloading. + // + // If the torrent is auto-managed (``auto_managed``), the torrent + // will eventually be taken out of upload-mode, regardless of how it + // got there. If it's important to manually control when the torrent + // leaves upload mode, don't make it auto managed. + constexpr torrent_flags_t upload_mode = 1_bit; + + // determines if the torrent should be added in *share mode* or not. + // Share mode indicates that we are not interested in downloading the + // torrent, but merely want to improve our share ratio (i.e. increase + // it). A torrent started in share mode will do its best to never + // download more than it uploads to the swarm. If the swarm does not + // have enough demand for upload capacity, the torrent will not + // download anything. This mode is intended to be safe to add any + // number of torrents to, without manual screening, without the risk + // of downloading more than is uploaded. + // + // A torrent in share mode sets the priority to all pieces to 0, + // except for the pieces that are downloaded, when pieces are decided + // to be downloaded. This affects the progress bar, which might be set + // to "100% finished" most of the time. Do not change file or piece + // priorities for torrents in share mode, it will make it not work. + // + // The share mode has one setting, the share ratio target, see + // ``settings_pack::share_mode_target`` for more info. + constexpr torrent_flags_t share_mode = 2_bit; + + // determines if the IP filter should apply to this torrent or not. By + // default all torrents are subject to filtering by the IP filter + // (i.e. this flag is set by default). This is useful if certain + // torrents needs to be exempt for some reason, being an auto-update + // torrent for instance. + constexpr torrent_flags_t apply_ip_filter = 3_bit; + + // specifies whether or not the torrent is paused. i.e. it won't connect to the tracker or any of the peers + // until it's resumed. Note that a paused torrent that also has the + // auto_managed flag set can be started at any time by libtorrent's queuing + // logic. See queuing_. + constexpr torrent_flags_t paused = 4_bit; + + // If the torrent is auto-managed (``auto_managed``), the torrent + // may be resumed at any point, regardless of how it paused. If it's + // important to manually control when the torrent is paused and + // resumed, don't make it auto managed. + // + // If ``auto_managed`` is set, the torrent will be queued, + // started and seeded automatically by libtorrent. When this is set, + // the torrent should also be started as paused. The default queue + // order is the order the torrents were added. They are all downloaded + // in that order. For more details, see queuing_. + constexpr torrent_flags_t auto_managed = 5_bit; + + // used in add_torrent_params to indicate that it's an error to attempt + // to add a torrent that's already in the session. If it's not considered an + // error, a handle to the existing torrent is returned. + // This flag is not saved by write_resume_data(), since it is only meant for + // adding torrents. + constexpr torrent_flags_t duplicate_is_error = 6_bit; + + // on by default and means that this torrent will be part of state + // updates when calling post_torrent_updates(). + // This flag is not saved by write_resume_data(). + constexpr torrent_flags_t update_subscribe = 7_bit; + + // sets the torrent into super seeding/initial seeding mode. If the torrent + // is not a seed, this flag has no effect. + constexpr torrent_flags_t super_seeding = 8_bit; + + // sets the sequential download state for the torrent. In this mode the + // piece picker will pick pieces with low index numbers before pieces with + // high indices. The actual pieces that are picked depend on other factors + // still, such as which pieces a peer has and whether it is in parole mode + // or "prefer whole pieces"-mode. Sequential mode is not ideal for streaming + // media. For that, see set_piece_deadline() instead. + constexpr torrent_flags_t sequential_download = 9_bit; + + // When this flag is set, the torrent will *force stop* whenever it + // transitions from a non-data-transferring state into a data-transferring + // state (referred to as being ready to download or seed). This is useful + // for torrents that should not start downloading or seeding yet, but want + // to be made ready to do so. A torrent may need to have its files checked + // for instance, so it needs to be started and possibly queued for checking + // (auto-managed and started) but as soon as it's done, it should be + // stopped. + // + // *Force stopped* means auto-managed is set to false and it's paused. As + // if the auto_manages flag is cleared and the paused flag is set on the torrent. + // + // Note that the torrent may transition into a downloading state while + // setting this flag, and since the logic is edge triggered you may + // miss the edge. To avoid this race, if the torrent already is in a + // downloading state when this call is made, it will trigger the + // stop-when-ready immediately. + // + // When the stop-when-ready logic fires, the flag is cleared. Any + // subsequent transitions between downloading and non-downloading states + // will not be affected, until this flag is set again. + // + // The behavior is more robust when setting this flag as part of adding + // the torrent. See add_torrent_params. + // + // The stop-when-ready flag fixes the inherent race condition of waiting + // for the state_changed_alert and then call pause(). The download/seeding + // will most likely start in between posting the alert and receiving the + // call to pause. + // + // A downloading state is one where peers are being connected. Which means + // just downloading the metadata via the ``ut_metadata`` extension counts + // as a downloading state. In order to stop a torrent once the metadata + // has been downloaded, instead set all file priorities to dont_download + constexpr torrent_flags_t stop_when_ready = 10_bit; + + // when this flag is set, the tracker list in the add_torrent_params + // object override any trackers from the torrent file. If the flag is + // not set, the trackers from the add_torrent_params object will be + // added to the list of trackers used by the torrent. + // This flag is set by read_resume_data() if there are trackers present in + // the resume data file. This effectively makes the trackers saved in the + // resume data take precedence over the original trackers. This includes if + // there's an empty list of trackers, to support the case where they were + // explicitly removed in the previous session. + // This flag is not saved by write_resume_data() + constexpr torrent_flags_t override_trackers = 11_bit; + + // If this flag is set, the web seeds from the add_torrent_params + // object will override any web seeds in the torrent file. If it's not + // set, web seeds in the add_torrent_params object will be added to the + // list of web seeds used by the torrent. + // This flag is set by read_resume_data() if there are web seeds present in + // the resume data file. This effectively makes the web seeds saved in the + // resume data take precedence over the original ones. This includes if + // there's an empty list of web seeds, to support the case where they were + // explicitly removed in the previous session. + // This flag is not saved by write_resume_data() + constexpr torrent_flags_t override_web_seeds = 12_bit; + + // if this flag is set (which it is by default) the torrent will be + // considered needing to save its resume data immediately as it's + // added. New torrents that don't have any resume data should do that. + // This flag is cleared by a successful call to save_resume_data() + // This flag is not saved by write_resume_data(), since it represents an + // ephemeral state of a running torrent. + constexpr torrent_flags_t need_save_resume = 13_bit; + +#if TORRENT_ABI_VERSION == 1 + // indicates that this torrent should never be unloaded from RAM, even + // if unloading torrents are allowed in general. Setting this makes + // the torrent exempt from loading/unloading management. + constexpr torrent_flags_t TORRENT_DEPRECATED_MEMBER pinned = 14_bit; + + // If ``override_resume_data`` is set, flags set for this torrent + // in this ``add_torrent_params`` object will take precedence over + // whatever states are saved in the resume data. For instance, the + // ``paused``, ``auto_managed``, ``sequential_download``, ``seed_mode``, + // ``super_seeding``, ``max_uploads``, ``max_connections``, + // ``upload_limit`` and ``download_limit`` are all affected by this + // flag. The intention of this flag is to have any field in + // add_torrent_params configuring the torrent override the corresponding + // configuration from the resume file, with the one exception of save + // resume data, which has its own flag (for historic reasons). + // "file_priorities" and "save_path" are not affected by this flag. + constexpr torrent_flags_t TORRENT_DEPRECATED_MEMBER override_resume_data = 15_bit; + + // defaults to on and specifies whether tracker URLs loaded from + // resume data should be added to the trackers in the torrent or + // replace the trackers. When replacing trackers (i.e. this flag is not + // set), any trackers passed in via add_torrent_params are also + // replaced by any trackers in the resume data. The default behavior is + // to have the resume data override the .torrent file _and_ the + // trackers added in add_torrent_params. + constexpr torrent_flags_t TORRENT_DEPRECATED_MEMBER merge_resume_trackers = 16_bit; + + // if this flag is set, the save path from the resume data file, if + // present, is honored. This defaults to not being set, in which + // case the save_path specified in add_torrent_params is always used. + constexpr torrent_flags_t TORRENT_DEPRECATED_MEMBER use_resume_save_path = 17_bit; + + // defaults to on and specifies whether web seed URLs loaded from + // resume data should be added to the ones in the torrent file or + // replace them. No distinction is made between the two different kinds + // of web seeds (`BEP 17`_ and `BEP 19`_). When replacing web seeds + // (i.e. when this flag is not set), any web seeds passed in via + // add_torrent_params are also replaced. The default behavior is to + // have any web seeds in the resume data take precedence over whatever + // is passed in here as well as the .torrent file. + constexpr torrent_flags_t TORRENT_DEPRECATED_MEMBER merge_resume_http_seeds = 18_bit; +#endif + + // set this flag to disable DHT for this torrent. This lets you have the DHT + // enabled for the whole client, and still have specific torrents not + // participating in it. i.e. not announcing to the DHT nor picking up peers + // from it. + constexpr torrent_flags_t disable_dht = 19_bit; + + // set this flag to disable local service discovery for this torrent. + constexpr torrent_flags_t disable_lsd = 20_bit; + + // set this flag to disable peer exchange for this torrent. + constexpr torrent_flags_t disable_pex = 21_bit; + + // all torrent flags combined. Can conveniently be used when creating masks + // for flags + constexpr torrent_flags_t all = torrent_flags_t::all(); + + // internal + constexpr torrent_flags_t default_flags = + torrent_flags::update_subscribe + | torrent_flags::auto_managed + | torrent_flags::paused + | torrent_flags::apply_ip_filter + | torrent_flags::need_save_resume +#if TORRENT_ABI_VERSION == 1 + | torrent_flags::pinned + | torrent_flags::merge_resume_http_seeds + | torrent_flags::merge_resume_trackers +#endif + ; + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +} // torrent_flags +} // libtorrent + +#endif + diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp new file mode 100644 index 0000000..9144591 --- /dev/null +++ b/include/libtorrent/torrent_handle.hpp @@ -0,0 +1,1298 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_HANDLE_HPP_INCLUDED +#define TORRENT_TORRENT_HANDLE_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#if TORRENT_ABI_VERSION == 1 +// for deprecated force_reannounce +#include +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" // tcp::endpoint +#include "libtorrent/span.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/storage_defs.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/download_priority.hpp" +#include "libtorrent/pex_flags.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_v6 + +namespace libtorrent { +namespace aux { + struct session_impl; +} + +#if TORRENT_ABI_VERSION == 1 + struct peer_list_entry; +#endif + class torrent; + +#ifndef BOOST_NO_EXCEPTIONS + [[noreturn]] void throw_invalid_handle(); +#endif + + using status_flags_t = flags::bitfield_flag; + using add_piece_flags_t = flags::bitfield_flag; + using pause_flags_t = flags::bitfield_flag; + using deadline_flags_t = flags::bitfield_flag; + using resume_data_flags_t = flags::bitfield_flag; + using reannounce_flags_t = flags::bitfield_flag; + using queue_position_t = aux::strong_typedef; + + // holds the state of a block in a piece. Who we requested + // it from and how far along we are at downloading it. + struct TORRENT_EXPORT block_info + { + // this is the enum used for the block_info::state field. + enum block_state_t + { + // This block has not been downloaded or requested form any peer. + none, + // The block has been requested, but not completely downloaded yet. + requested, + // The block has been downloaded and is currently queued for being + // written to disk. + writing, + // The block has been written to disk. + finished + }; + + private: + union addr_t + { + address_v4::bytes_type v4; + address_v6::bytes_type v6; + }; + addr_t addr; + + std::uint16_t port; + public: + + // The peer is the ip address of the peer this block was downloaded from. + void set_peer(tcp::endpoint const& ep) + { + is_v6_addr = is_v6(ep); + if (is_v6_addr) + addr.v6 = ep.address().to_v6().to_bytes(); + else + addr.v4 = ep.address().to_v4().to_bytes(); + port = ep.port(); + } + tcp::endpoint peer() const + { + if (is_v6_addr) + return tcp::endpoint(address_v6(addr.v6), port); + else + return tcp::endpoint(address_v4(addr.v4), port); + } + + // the number of bytes that have been received for this block + unsigned bytes_progress:15; + + // the total number of bytes in this block. + unsigned block_size:15; + + // the state this block is in (see block_state_t) + unsigned state:2; + + // the number of peers that is currently requesting this block. Typically + // this is 0 or 1, but at the end of the torrent blocks may be requested + // by more peers in parallel to speed things up. + unsigned num_peers:14; + private: + // the type of the addr union + bool is_v6_addr:1; + }; + + // This class holds information about pieces that have outstanding requests + // or outstanding writes + struct TORRENT_EXPORT partial_piece_info + { +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/aux_/disable_warnings_push.hpp" + partial_piece_info() = default; + partial_piece_info(partial_piece_info&&) noexcept = default; + partial_piece_info(partial_piece_info const&) = default; + partial_piece_info& operator=(partial_piece_info const&) = default; + partial_piece_info& operator=(partial_piece_info&&) noexcept = default; +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + // the index of the piece in question. ``blocks_in_piece`` is the number + // of blocks in this particular piece. This number will be the same for + // most pieces, but + // the last piece may have fewer blocks than the standard pieces. + piece_index_t piece_index; + + // the number of blocks in this piece + int blocks_in_piece; + + // the number of blocks that are in the finished state + int finished; + + // the number of blocks that are in the writing state + int writing; + + // the number of blocks that are in the requested state + int requested; + + // this is an array of ``blocks_in_piece`` number of + // items. One for each block in the piece. + // + // .. warning:: This is a pointer that points to an array + // that's owned by the session object. The next time + // get_download_queue() is called, it will be invalidated. + block_info* blocks; + +#if TORRENT_ABI_VERSION == 1 + // the speed classes. These may be used by the piece picker to + // coalesce requests of similar download rates + enum state_t { none, slow, medium, fast }; + + // the download speed class this piece falls into. + // this is used internally to cluster peers of the same + // speed class together when requesting blocks. + // + // set to either ``fast``, ``medium``, ``slow`` or ``none``. It tells + // which download rate category the peers downloading this piece falls + // into. ``none`` means that no peer is currently downloading any part of + // the piece. Peers prefer picking pieces from the same category as + // themselves. The reason for this is to keep the number of partially + // downloaded pieces down. Pieces set to ``none`` can be converted into + // any of ``fast``, ``medium`` or ``slow`` as soon as a peer want to + // download from it. + state_t TORRENT_DEPRECATED_MEMBER piece_state; +#endif + }; + + // for std::hash (and to support using this type in unordered_map etc.) + TORRENT_EXPORT std::size_t hash_value(torrent_handle const& h); + + // You will usually have to store your torrent handles somewhere, since it's + // the object through which you retrieve information about the torrent and + // aborts the torrent. + // + // .. warning:: + // Any member function that returns a value or fills in a value has to be + // made synchronously. This means it has to wait for the main thread to + // complete the query before it can return. This might potentially be + // expensive if done from within a GUI thread that needs to stay + // responsive. Try to avoid querying for information you don't need, and + // try to do it in as few calls as possible. You can get most of the + // interesting information about a torrent from the + // torrent_handle::status() call. + // + // The default constructor will initialize the handle to an invalid state. + // Which means you cannot perform any operation on it, unless you first + // assign it a valid handle. If you try to perform any operation on an + // uninitialized handle, it will throw ``invalid_handle``. + // + // .. warning:: + // All operations on a torrent_handle may throw system_error + // exception, in case the handle is no longer referring to a torrent. + // There is one exception is_valid() will never throw. Since the torrents + // are processed by a background thread, there is no guarantee that a + // handle will remain valid between two calls. + // + struct TORRENT_EXPORT torrent_handle + { + friend struct aux::session_impl; + friend struct session_handle; + friend class torrent; + friend TORRENT_EXPORT std::size_t hash_value(torrent_handle const& th); + + // constructs a torrent handle that does not refer to a torrent. + // i.e. is_valid() will return false. + torrent_handle() noexcept = default; + + // hidden + torrent_handle(torrent_handle const& t) = default; + torrent_handle(torrent_handle&& t) noexcept = default; + torrent_handle& operator=(torrent_handle const&) = default; + torrent_handle& operator=(torrent_handle&&) noexcept = default; + + +#if TORRENT_ABI_VERSION == 1 + using flags_t = add_piece_flags_t; + using status_flags_t = libtorrent::status_flags_t; + using pause_flags_t = libtorrent::pause_flags_t; + using save_resume_flags_t = libtorrent::resume_data_flags_t; + using reannounce_flags_t = libtorrent::reannounce_flags_t; +#endif + + // instruct libtorrent to overwrite any data that may already have been + // downloaded with the data of the new piece being added. + static constexpr add_piece_flags_t overwrite_existing = 0_bit; + + // This function will write ``data`` to the storage as piece ``piece``, + // as if it had been downloaded from a peer. ``data`` is expected to + // point to a buffer of as many bytes as the size of the specified piece. + // The data in the buffer is copied and passed on to the disk IO thread + // to be written at a later point. + // + // By default, data that's already been downloaded is not overwritten by + // this buffer. If you trust this data to be correct (and pass the piece + // hash check) you may pass the overwrite_existing flag. This will + // instruct libtorrent to overwrite any data that may already have been + // downloaded with this data. + // + // Since the data is written asynchronously, you may know that is passed + // or failed the hash check by waiting for piece_finished_alert or + // hash_failed_alert. + // + // Adding pieces while the torrent is being checked (i.e. in + // torrent_status::checking_files state) is not supported. + void add_piece(piece_index_t piece, char const* data, add_piece_flags_t flags = {}) const; + + // This function starts an asynchronous read operation of the specified + // piece from this torrent. You must have completed the download of the + // specified piece before calling this function. + // + // When the read operation is completed, it is passed back through an + // alert, read_piece_alert. Since this alert is a response to an explicit + // call, it will always be posted, regardless of the alert mask. + // + // Note that if you read multiple pieces, the read operations are not + // guaranteed to finish in the same order as you initiated them. + void read_piece(piece_index_t piece) const; + + // Returns true if this piece has been completely downloaded and written + // to disk, and false otherwise. + bool have_piece(piece_index_t piece) const; + +#if TORRENT_ABI_VERSION == 1 + // internal + TORRENT_DEPRECATED + void get_full_peer_list(std::vector& v) const; +#endif + + // takes a reference to a vector that will be cleared and filled with one + // entry for each peer connected to this torrent, given the handle is + // valid. If the torrent_handle is invalid, it will throw + // system_error exception. Each entry in the vector contains + // information about that particular peer. See peer_info. + void get_peer_info(std::vector& v) const; + + // calculates ``distributed_copies``, ``distributed_full_copies`` and + // ``distributed_fraction``. + static constexpr status_flags_t query_distributed_copies = 0_bit; + + // includes partial downloaded blocks in ``total_done`` and + // ``total_wanted_done``. + static constexpr status_flags_t query_accurate_download_counters = 1_bit; + + // includes ``last_seen_complete``. + static constexpr status_flags_t query_last_seen_complete = 2_bit; + // populate the ``pieces`` field in torrent_status. + static constexpr status_flags_t query_pieces = 3_bit; + // includes ``verified_pieces`` (only applies to torrents in *seed + // mode*). + static constexpr status_flags_t query_verified_pieces = 4_bit; + // includes ``torrent_file``, which is all the static information from + // the .torrent file. + static constexpr status_flags_t query_torrent_file = 5_bit; + // includes ``name``, the name of the torrent. This is either derived + // from the .torrent file, or from the ``&dn=`` magnet link argument + // or possibly some other source. If the name of the torrent is not + // known, this is an empty string. + static constexpr status_flags_t query_name = 6_bit; + // includes ``save_path``, the path to the directory the files of the + // torrent are saved to. + static constexpr status_flags_t query_save_path = 7_bit; + + // ``status()`` will return a structure with information about the status + // of this torrent. If the torrent_handle is invalid, it will throw + // system_error exception. See torrent_status. The ``flags`` + // argument filters what information is returned in the torrent_status. + // Some information in there is relatively expensive to calculate, and if + // you're not interested in it (and see performance issues), you can + // filter them out. + // + // By default everything is included. The flags you can use to decide + // what to *include* are defined in this class. + torrent_status status(status_flags_t flags = status_flags_t::all()) const; + + // ``get_download_queue()`` takes a non-const reference to a vector which + // it will fill with information about pieces that are partially + // downloaded or not downloaded at all but partially requested. See + // partial_piece_info for the fields in the returned vector. + void get_download_queue(std::vector& queue) const; + + // used to ask libtorrent to send an alert once the piece has been + // downloaded, by passing alert_when_available. When set, the + // read_piece_alert alert will be delivered, with the piece data, when + // it's downloaded. + static constexpr deadline_flags_t alert_when_available = 0_bit; + + // This function sets or resets the deadline associated with a specific + // piece index (``index``). libtorrent will attempt to download this + // entire piece before the deadline expires. This is not necessarily + // possible, but pieces with a more recent deadline will always be + // prioritized over pieces with a deadline further ahead in time. The + // deadline (and flags) of a piece can be changed by calling this + // function again. + // + // If the piece is already downloaded when this call is made, nothing + // happens, unless the alert_when_available flag is set, in which case it + // will have the same effect as calling read_piece() for ``index``. + // + // ``deadline`` is the number of milliseconds until this piece should be + // completed. + // + // ``reset_piece_deadline`` removes the deadline from the piece. If it + // hasn't already been downloaded, it will no longer be considered a + // priority. + // + // ``clear_piece_deadlines()`` removes deadlines on all pieces in + // the torrent. As if reset_piece_deadline() was called on all pieces. + void set_piece_deadline(piece_index_t index, int deadline, deadline_flags_t flags = {}) const; + void reset_piece_deadline(piece_index_t index) const; + void clear_piece_deadlines() const; + +#if TORRENT_ABI_VERSION == 1 + // This sets the bandwidth priority of this torrent. The priority of a + // torrent determines how much bandwidth its peers are assigned when + // distributing upload and download rate quotas. A high number gives more + // bandwidth. The priority must be within the range [0, 255]. + // + // The default priority is 0, which is the lowest priority. + // + // To query the priority of a torrent, use the + // ``torrent_handle::status()`` call. + // + // Torrents with higher priority will not necessarily get as much + // bandwidth as they can consume, even if there's is more quota. Other + // peers will still be weighed in when bandwidth is being distributed. + // With other words, bandwidth is not distributed strictly in order of + // priority, but the priority is used as a weight. + // + // Peers whose Torrent has a higher priority will take precedence when + // distributing unchoke slots. This is a strict prioritisation where + // every interested peer on a high priority torrent will be unchoked + // before any other, lower priority, torrents have any peers unchoked. + // deprecated in 1.2 + TORRENT_DEPRECATED + void set_priority(int prio) const; + +#if !TORRENT_NO_FPU + // fills the specified vector with the download progress [0, 1] + // of each file in the torrent. The files are ordered as in + // the torrent_info. + TORRENT_DEPRECATED + void file_progress(std::vector& progress) const; +#endif + + TORRENT_DEPRECATED + void file_status(std::vector& status) const; +#endif + + // flags to be passed in file_progress(). + enum file_progress_flags_t + { + // only calculate file progress at piece granularity. This makes + // the file_progress() call cheaper and also only takes bytes that + // have passed the hash check into account, so progress cannot + // regress in this mode. + piece_granularity = 1 + }; + + // This function fills in the supplied vector with the number of + // bytes downloaded of each file in this torrent. The progress values are + // ordered the same as the files in the torrent_info. This operation is + // not very cheap. Its complexity is *O(n + mj)*. Where *n* is the number + // of files, *m* is the number of downloading pieces and *j* is the + // number of blocks in a piece. + // + // The ``flags`` parameter can be used to specify the granularity of the + // file progress. If left at the default value of 0, the progress will be + // as accurate as possible, but also more expensive to calculate. If + // ``torrent_handle::piece_granularity`` is specified, the progress will + // be specified in piece granularity. i.e. only pieces that have been + // fully downloaded and passed the hash check count. When specifying + // piece granularity, the operation is a lot cheaper, since libtorrent + // already keeps track of this internally and no calculation is required. + void file_progress(std::vector& progress, int flags = 0) const; + + // This function returns a vector with status about files + // that are open for this torrent. Any file that is not open + // will not be reported in the vector, i.e. it's possible that + // the vector is empty when returning, if none of the files in the + // torrent are currently open. + // + // See open_file_state + std::vector file_status() const; + + // If the torrent is in an error state (i.e. ``torrent_status::error`` is + // non-empty), this will clear the error and start the torrent again. + void clear_error() const; + + // ``trackers()`` will return the list of trackers for this torrent. The + // announce entry contains both a string ``url`` which specify the + // announce url for the tracker as well as an int ``tier``, which is + // specifies the order in which this tracker is tried. If you want + // libtorrent to use another list of trackers for this torrent, you can + // use ``replace_trackers()`` which takes a list of the same form as the + // one returned from ``trackers()`` and will replace it. If you want an + // immediate effect, you have to call force_reannounce(). See + // announce_entry. + // + // ``add_tracker()`` will look if the specified tracker is already in the + // set. If it is, it doesn't do anything. If it's not in the current set + // of trackers, it will insert it in the tier specified in the + // announce_entry. + // + // The updated set of trackers will be saved in the resume data, and when + // a torrent is started with resume data, the trackers from the resume + // data will replace the original ones. + std::vector trackers() const; + void replace_trackers(std::vector const&) const; + void add_tracker(announce_entry const&) const; + + // TODO: 3 unify url_seed and http_seed with just web_seed, using the + // web_seed_entry. + + // ``add_url_seed()`` adds another url to the torrent's list of url + // seeds. If the given url already exists in that list, the call has no + // effect. The torrent will connect to the server and try to download + // pieces from it, unless it's paused, queued, checking or seeding. + // ``remove_url_seed()`` removes the given url if it exists already. + // ``url_seeds()`` return a set of the url seeds currently in this + // torrent. Note that URLs that fails may be removed automatically from + // the list. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url) const; + void remove_url_seed(std::string const& url) const; + std::set url_seeds() const; + + // These functions are identical as the ``*_url_seed()`` variants, but + // they operate on `BEP 17`_ web seeds instead of `BEP 19`_. + // + // See http-seeding_ for more information. + void add_http_seed(std::string const& url) const; + void remove_http_seed(std::string const& url) const; + std::set http_seeds() const; + + // add the specified extension to this torrent. The ``ext`` argument is + // a function that will be called from within libtorrent's context + // passing in the internal torrent object and the specified userdata + // pointer. The function is expected to return a shared pointer to + // a torrent_plugin instance. + void add_extension( + std::function(torrent_handle const&, void*)> const& ext + , void* userdata = nullptr); + + // ``set_metadata`` expects the *info* section of metadata. i.e. The + // buffer passed in will be hashed and verified against the info-hash. If + // it fails, a ``metadata_failed_alert`` will be generated. If it passes, + // a ``metadata_received_alert`` is generated. The function returns true + // if the metadata is successfully set on the torrent, and false + // otherwise. If the torrent already has metadata, this function will not + // affect the torrent, and false will be returned. + bool set_metadata(span metadata) const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + bool set_metadata(char const* metadata, int size) const + { return set_metadata({metadata, size}); } +#endif + + // Returns true if this handle refers to a valid torrent and false if it + // hasn't been initialized or if the torrent it refers to has been + // aborted. Note that a handle may become invalid after it has been added + // to the session. Usually this is because the storage for the torrent is + // somehow invalid or if the filenames are not allowed (and hence cannot + // be opened/created) on your filesystem. If such an error occurs, a + // file_error_alert is generated and all handles that refers to that + // torrent will become invalid. + bool is_valid() const; + + // will delay the disconnect of peers that we're still downloading + // outstanding requests from. The torrent will not accept any more + // requests and will disconnect all idle peers. As soon as a peer is done + // transferring the blocks that were requested from it, it is + // disconnected. This is a graceful shut down of the torrent in the sense + // that no downloaded bytes are wasted. + static constexpr pause_flags_t graceful_pause = 0_bit; + static constexpr pause_flags_t clear_disk_cache = 1_bit; + + // ``pause()``, and ``resume()`` will disconnect all peers and reconnect + // all peers respectively. When a torrent is paused, it will however + // remember all share ratios to all peers and remember all potential (not + // connected) peers. Torrents may be paused automatically if there is a + // file error (e.g. disk full) or something similar. See + // file_error_alert. + // + // To know if a torrent is paused or not, call + // ``torrent_handle::status()`` and inspect ``torrent_status::paused``. + // + // .. note:: + // Torrents that are auto-managed may be automatically resumed again. It + // does not make sense to pause an auto-managed torrent without making it + // not auto-managed first. Torrents are auto-managed by default when added + // to the session. For more information, see queuing_. + // + void pause(pause_flags_t flags = {}) const; + void resume() const; + + // sets and gets the torrent state flags. See torrent_flags_t. + // The ``set_flags`` overload that take a mask will affect all + // flags part of the mask, and set their values to what the + // ``flags`` argument is set to. This allows clearing and + // setting flags in a single function call. + // The ``set_flags`` overload that just takes flags, sets all + // the specified flags and leave any other flags unchanged. + // ``unset_flags`` clears the specified flags, while leaving + // any other flags unchanged. + // + // The `seed_mode` flag is special, it can only be cleared once the + // torrent has been added, and it can only be set as part of the + // add_torrent_params flags, when adding the torrent. + torrent_flags_t flags() const; + void set_flags(torrent_flags_t flags, torrent_flags_t mask) const; + void set_flags(torrent_flags_t flags) const; + void unset_flags(torrent_flags_t flags) const; + + // Instructs libtorrent to flush all the disk caches for this torrent and + // close all file handles. This is done asynchronously and you will be + // notified that it's complete through cache_flushed_alert. + // + // Note that by the time you get the alert, libtorrent may have cached + // more data for the torrent, but you are guaranteed that whatever cached + // data libtorrent had by the time you called + // ``torrent_handle::flush_cache()`` has been written to disk. + void flush_cache() const; + + // ``force_recheck`` puts the torrent back in a state where it assumes to + // have no resume data. All peers will be disconnected and the torrent + // will stop announcing to the tracker. The torrent will be added to the + // checking queue, and will be checked (all the files will be read and + // compared to the piece hashes). Once the check is complete, the torrent + // will start connecting to peers again, as normal. + // The torrent will be placed last in queue, i.e. its queue position + // will be the highest of all torrents in the session. + void force_recheck() const; + + // the disk cache will be flushed before creating the resume data. + // This avoids a problem with file timestamps in the resume data in + // case the cache hasn't been flushed yet. + static constexpr resume_data_flags_t flush_disk_cache = 0_bit; + + // the resume data will contain the metadata from the torrent file as + // well. This is default for any torrent that's added without a + // torrent file (such as a magnet link or a URL). + static constexpr resume_data_flags_t save_info_dict = 1_bit; + + // if nothing significant has changed in the torrent since the last + // time resume data was saved, fail this attempt. Significant changes + // primarily include more data having been downloaded, file or piece + // priorities having changed etc. If the resume data doesn't need + // saving, a save_resume_data_failed_alert is posted with the error + // resume_data_not_modified. + static constexpr resume_data_flags_t only_if_modified = 2_bit; + + // ``save_resume_data()`` asks libtorrent to generate fast-resume data for + // this torrent. + // + // This operation is asynchronous, ``save_resume_data`` will return + // immediately. The resume data is delivered when it's done through an + // save_resume_data_alert. + // + // The fast resume data will be empty in the following cases: + // + // 1. The torrent handle is invalid. + // 2. The torrent hasn't received valid metadata and was started without + // metadata (see libtorrent's metadata-from-peers_ extension) + // + // Note that by the time you receive the fast resume data, it may already + // be invalid if the torrent is still downloading! The recommended + // practice is to first pause the session, then generate the fast resume + // data, and then close it down. Make sure to not remove_torrent() before + // you receive the save_resume_data_alert though. There's no need to + // pause when saving intermittent resume data. + // + //.. warning:: + // If you pause every torrent individually instead of pausing the + // session, every torrent will have its paused state saved in the + // resume data! + // + //.. note:: + // It is typically a good idea to save resume data whenever a torrent + // is completed or paused. In those cases you don't need to pause the + // torrent or the session, since the torrent will do no more writing to + // its files. If you save resume data for torrents when they are + // paused, you can accelerate the shutdown process by not saving resume + // data again for paused torrents. Completed torrents should have their + // resume data saved when they complete and on exit, since their + // statistics might be updated. + // + // In full allocation mode the resume data is never invalidated by + // subsequent writes to the files, since pieces won't move around. This + // means that you don't need to pause before writing resume data in full + // or sparse mode. If you don't, however, any data written to disk after + // you saved resume data and before the session closed is lost. + // + // It also means that if the resume data is out dated, libtorrent will + // not re-check the files, but assume that it is fairly recent. The + // assumption is that it's better to loose a little bit than to re-check + // the entire file. + // + // It is still a good idea to save resume data periodically during + // download as well as when closing down. + // + // Example code to pause and save resume data for all torrents and wait + // for the alerts: + // + // .. code:: c++ + // + // extern int outstanding_resume_data; // global counter of outstanding resume data + // std::vector handles = ses.get_torrents(); + // ses.pause(); + // for (torrent_handle const& h : handles) + // { + // if (!h.is_valid()) continue; + // torrent_status s = h.status(); + // if (!s.has_metadata || !s.need_save_resume_data()) continue; + // + // h.save_resume_data(); + // ++outstanding_resume_data; + // } + // + // while (outstanding_resume_data > 0) + // { + // alert const* a = ses.wait_for_alert(seconds(10)); + // + // // if we don't get an alert within 10 seconds, abort + // if (a == nullptr) break; + // + // std::vector alerts; + // ses.pop_alerts(&alerts); + // + // for (alert* i : alerts) + // { + // if (alert_cast(i)) + // { + // process_alert(i); + // --outstanding_resume_data; + // continue; + // } + // + // save_resume_data_alert const* rd = alert_cast(i); + // if (rd == nullptr) + // { + // process_alert(i); + // continue; + // } + // + // torrent_handle h = rd->handle; + // torrent_status st = h.status(torrent_handle::query_save_path + // | torrent_handle::query_name); + // std::ofstream out((st.save_path + // + "/" + st.name + ".fastresume").c_str() + // , std::ios_base::binary); + // std::vector buf = write_resume_data_buf(rd->params); + // out.write(buf.data(), buf.size()); + // --outstanding_resume_data; + // } + // } + // + //.. note:: + // Note how ``outstanding_resume_data`` is a global counter in this + // example. This is deliberate, otherwise there is a race condition for + // torrents that was just asked to save their resume data, they posted + // the alert, but it has not been received yet. Those torrents would + // report that they don't need to save resume data again, and skipped by + // the initial loop, and thwart the counter otherwise. + void save_resume_data(resume_data_flags_t flags = {}) const; + + // This function returns true if any whole chunk has been downloaded + // since the torrent was first loaded or since the last time the resume + // data was saved. When saving resume data periodically, it makes sense + // to skip any torrent which hasn't downloaded anything since the last + // time. + // + //.. note:: + // A torrent's resume data is considered saved as soon as the + // save_resume_data_alert is posted. It is important to make sure this + // alert is received and handled in order for this function to be + // meaningful. + bool need_save_resume_data() const; + + // Every torrent that is added is assigned a queue position exactly one + // greater than the greatest queue position of all existing torrents. + // Torrents that are being seeded have -1 as their queue position, since + // they're no longer in line to be downloaded. + // + // When a torrent is removed or turns into a seed, all torrents with + // greater queue positions have their positions decreased to fill in the + // space in the sequence. + // + // ``queue_position()`` returns the torrent's position in the download + // queue. The torrents with the smallest numbers are the ones that are + // being downloaded. The smaller number, the closer the torrent is to the + // front of the line to be started. + // + // The queue position is also available in the torrent_status. + // + // The ``queue_position_*()`` functions adjust the torrents position in + // the queue. Up means closer to the front and down means closer to the + // back of the queue. Top and bottom refers to the front and the back of + // the queue respectively. + queue_position_t queue_position() const; + void queue_position_up() const; + void queue_position_down() const; + void queue_position_top() const; + void queue_position_bottom() const; + + // updates the position in the queue for this torrent. The relative order + // of all other torrents remain intact but their numerical queue position + // shifts to make space for this torrent's new position + void queue_position_set(queue_position_t p) const; + + // For SSL torrents, use this to specify a path to a .pem file to use as + // this client's certificate. The certificate must be signed by the + // certificate in the .torrent file to be valid. + // + // The set_ssl_certificate_buffer() overload takes the actual certificate, + // private key and DH params as strings, rather than paths to files. + // + // ``cert`` is a path to the (signed) certificate in .pem format + // corresponding to this torrent. + // + // ``private_key`` is a path to the private key for the specified + // certificate. This must be in .pem format. + // + // ``dh_params`` is a path to the Diffie-Hellman parameter file, which + // needs to be in .pem format. You can generate this file using the + // openssl command like this: ``openssl dhparam -outform PEM -out + // dhparams.pem 512``. + // + // ``passphrase`` may be specified if the private key is encrypted and + // requires a passphrase to be decrypted. + // + // Note that when a torrent first starts up, and it needs a certificate, + // it will suspend connecting to any peers until it has one. It's + // typically desirable to resume the torrent after setting the SSL + // certificate. + // + // If you receive a torrent_need_cert_alert, you need to call this to + // provide a valid cert. If you don't have a cert you won't be allowed to + // connect to any peers. + void set_ssl_certificate(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase = ""); + void set_ssl_certificate_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params); + + // Returns the storage implementation for this torrent. This depends on the + // storage constructor function that was passed to add_torrent. + storage_interface* get_storage_impl() const; + + // Returns a pointer to the torrent_info object associated with this + // torrent. The torrent_info object may be a copy of the internal object. + // If the torrent doesn't have metadata, the pointer will not be + // initialized (i.e. a nullptr). The torrent may be in a state + // without metadata only if it was started without a .torrent file, e.g. + // by being added by magnet link + std::shared_ptr torrent_file() const; + +#if TORRENT_ABI_VERSION == 1 + + // ================ start deprecation ============ + + // deprecated in 1.2 + // use set_flags() and unset_flags() instead + TORRENT_DEPRECATED + void stop_when_ready(bool b) const; + TORRENT_DEPRECATED + void set_upload_mode(bool b) const; + TORRENT_DEPRECATED + void set_share_mode(bool b) const; + TORRENT_DEPRECATED + void apply_ip_filter(bool b) const; + TORRENT_DEPRECATED + void auto_managed(bool m) const; + TORRENT_DEPRECATED + void set_pinned(bool p) const; + TORRENT_DEPRECATED + void set_sequential_download(bool sd) const; + + + // deprecated in 1.0 + // use status() instead (with query_save_path) + TORRENT_DEPRECATED + std::string save_path() const; + + // deprecated in 1.0 + // use status() instead (with query_name) + // returns the name of this torrent, in case it doesn't + // have metadata it returns the name assigned to it + // when it was added. + TORRENT_DEPRECATED + std::string name() const; + + // use torrent_file() instead + TORRENT_DEPRECATED + const torrent_info& get_torrent_info() const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + int get_peer_upload_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + int get_peer_download_limit(tcp::endpoint ip) const; + TORRENT_DEPRECATED + void set_peer_upload_limit(tcp::endpoint ip, int limit) const; + TORRENT_DEPRECATED + void set_peer_download_limit(tcp::endpoint ip, int limit) const; + + // deprecated in 0.16, feature will be removed + TORRENT_DEPRECATED + void set_ratio(float up_down_ratio) const; + + // deprecated in 0.16. use status() instead, and inspect the + // torrent_status::flags field. Alternatively, call flags() directly on + // the torrent_handle + TORRENT_DEPRECATED + bool is_seed() const; + TORRENT_DEPRECATED + bool is_finished() const; + TORRENT_DEPRECATED + bool is_paused() const; + TORRENT_DEPRECATED + bool is_auto_managed() const; + TORRENT_DEPRECATED + bool is_sequential_download() const; + TORRENT_DEPRECATED + bool has_metadata() const; + TORRENT_DEPRECATED + bool super_seeding() const; + + // deprecated in 0.14 + // use save_resume_data() instead. It is async. and + // will return the resume data in an alert + TORRENT_DEPRECATED + entry write_resume_data() const; + + // ``use_interface()`` sets the network interface this torrent will use + // when it opens outgoing connections. By default, it uses the same + // interface as the session uses to listen on. The parameter must be a + // string containing one or more, comma separated, ip-address (either an + // IPv4 or IPv6 address). When specifying multiple interfaces, the + // torrent will round-robin which interface to use for each outgoing + // connection. This is useful for clients that are multi-homed. + TORRENT_DEPRECATED + void use_interface(const char* net_interface) const; + // ================ end deprecation ============ +#endif + + // Fills the specified ``std::vector`` with the availability for + // each piece in this torrent. libtorrent does not keep track of + // availability for seeds, so if the torrent is seeding the availability + // for all pieces is reported as 0. + // + // The piece availability is the number of peers that we are connected + // that has advertised having a particular piece. This is the information + // that libtorrent uses in order to prefer picking rare pieces. + void piece_availability(std::vector& avail) const; + + // These functions are used to set and get the priority of individual + // pieces. By default all pieces have priority 4. That means that the + // random rarest first algorithm is effectively active for all pieces. + // You may however change the priority of individual pieces. There are 8 + // priority levels. 0 means not to download the piece at all. Otherwise, + // lower priority values means less likely to be picked. Piece priority + // takes precedence over piece availability. Every piece with priority 7 + // will be attempted to be picked before a priority 6 piece and so on. + // + // The default priority of pieces is 4. + // + // Piece priorities can not be changed for torrents that have not + // downloaded the metadata yet. Magnet links won't have metadata + // immediately. see the metadata_received_alert. + // + // ``piece_priority`` sets or gets the priority for an individual piece, + // specified by ``index``. + // + // ``prioritize_pieces`` takes a vector of integers, one integer per + // piece in the torrent. All the piece priorities will be updated with + // the priorities in the vector. + // The second overload of ``prioritize_pieces`` that takes a vector of pairs + // will update the priorities of only select pieces, and leave all other + // unaffected. Each pair is (piece, priority). That is, the first item is + // the piece index and the second item is the priority of that piece. + // Invalid entries, where the piece index or priority is out of range, are + // not allowed. + // + // ``get_piece_priorities`` returns a vector with one element for each piece + // in the torrent. Each element is the current priority of that piece. + // + // It's possible to cancel the effect of *file* priorities by setting the + // priorities for the affected pieces. Care has to be taken when mixing + // usage of file- and piece priorities. + void piece_priority(piece_index_t index, download_priority_t priority) const; + download_priority_t piece_priority(piece_index_t index) const; + void prioritize_pieces(std::vector const& pieces) const; + void prioritize_pieces(std::vector> const& pieces) const; + std::vector get_piece_priorities() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void prioritize_pieces(std::vector const& pieces) const; + TORRENT_DEPRECATED + void prioritize_pieces(std::vector> const& pieces) const; + TORRENT_DEPRECATED + std::vector piece_priorities() const; +#endif + + // ``index`` must be in the range [0, number_of_files). + // + // ``file_priority()`` queries or sets the priority of file ``index``. + // + // ``prioritize_files()`` takes a vector that has at as many elements as + // there are files in the torrent. Each entry is the priority of that + // file. The function sets the priorities of all the pieces in the + // torrent based on the vector. + // + // ``get_file_priorities()`` returns a vector with the priorities of all + // files. + // + // The priority values are the same as for piece_priority(). See + // download_priority_t. + // + // Whenever a file priority is changed, all other piece priorities are + // reset to match the file priorities. In order to maintain special + // priorities for particular pieces, piece_priority() has to be called + // again for those pieces. + // + // You cannot set the file priorities on a torrent that does not yet have + // metadata or a torrent that is a seed. ``file_priority(int, int)`` and + // prioritize_files() are both no-ops for such torrents. + // + // Since changing file priorities may involve disk operations (of moving + // files in- and out of the part file), the internal accounting of file + // priorities happen asynchronously. i.e. setting file priorities and then + // immediately querying them may not yield the same priorities just set. + // However, the *piece* priorities are updated immediately. + // + // when combining file- and piece priorities, the resume file will record + // both. When loading the resume data, the file priorities will be applied + // first, then the piece priorities. + void file_priority(file_index_t index, download_priority_t priority) const; + download_priority_t file_priority(file_index_t index) const; + void prioritize_files(std::vector const& files) const; + std::vector get_file_priorities() const; + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + void prioritize_files(std::vector const& files) const; + TORRENT_DEPRECATED + std::vector file_priorities() const; +#endif + + // by default, force-reannounce will still honor the min-interval + // published by the tracker. If this flag is set, it will be ignored + // and the tracker is announced immediately. + static constexpr reannounce_flags_t ignore_min_interval = 0_bit; + + // ``force_reannounce()`` will force this torrent to do another tracker + // request, to receive new peers. The ``seconds`` argument specifies how + // many seconds from now to issue the tracker announces. + // + // If the tracker's ``min_interval`` has not passed since the last + // announce, the forced announce will be scheduled to happen immediately + // as the ``min_interval`` expires. This is to honor trackers minimum + // re-announce interval settings. + // + // The ``tracker_index`` argument specifies which tracker to re-announce. + // If set to -1 (which is the default), all trackers are re-announce. + // + // The ``flags`` argument can be used to affect the re-announce. See + // ignore_min_interval. + // + // ``force_dht_announce`` will announce the torrent to the DHT + // immediately. + void force_reannounce(int seconds = 0, int tracker_index = -1, reannounce_flags_t = {}) const; + void force_dht_announce() const; + +#if TORRENT_ABI_VERSION == 1 + // forces a reannounce in the specified amount of time. + // This overrides the default announce interval, and no + // announce will take place until the given time has + // timed out. + TORRENT_DEPRECATED + void force_reannounce(boost::posix_time::time_duration) const; +#endif + + // ``scrape_tracker()`` will send a scrape request to a tracker. By + // default (``idx`` = -1) it will scrape the last working tracker. If + // ``idx`` is >= 0, the tracker with the specified index will scraped. + // + // A scrape request queries the tracker for statistics such as total + // number of incomplete peers, complete peers, number of downloads etc. + // + // This request will specifically update the ``num_complete`` and + // ``num_incomplete`` fields in the torrent_status struct once it + // completes. When it completes, it will generate a scrape_reply_alert. + // If it fails, it will generate a scrape_failed_alert. + void scrape_tracker(int idx = -1) const; + + // ``set_upload_limit`` will limit the upload bandwidth used by this + // particular torrent to the limit you set. It is given as the number of + // bytes per second the torrent is allowed to upload. + // ``set_download_limit`` works the same way but for download bandwidth + // instead of upload bandwidth. Note that setting a higher limit on a + // torrent then the global limit + // (``settings_pack::upload_rate_limit``) will not override the global + // rate limit. The torrent can never upload more than the global rate + // limit. + // + // ``upload_limit`` and ``download_limit`` will return the current limit + // setting, for upload and download, respectively. + // + // Local peers are not rate limited by default. see peer-classes_. + void set_upload_limit(int limit) const; + int upload_limit() const; + void set_download_limit(int limit) const; + int download_limit() const; + + // ``connect_peer()`` is a way to manually connect to peers that one + // believe is a part of the torrent. If the peer does not respond, or is + // not a member of this torrent, it will simply be disconnected. No harm + // can be done by using this other than an unnecessary connection attempt + // is made. If the torrent is uninitialized or in queued or checking + // mode, this will throw system_error. The second (optional) + // argument will be bitwise ORed into the source mask of this peer. + // Typically this is one of the source flags in peer_info. i.e. + // ``tracker``, ``pex``, ``dht`` etc. + // + // For possible values of ``flags``, see pex_flags_t. + void connect_peer(tcp::endpoint const& adr, peer_source_flags_t source = {} + , pex_flags_t flags = pex_encryption | pex_utp | pex_holepunch) const; + + // This will disconnect all peers and clear the peer list for this + // torrent. New peers will have to be acquired before resuming, from + // trackers, DHT or local service discovery, for example. + void clear_peers(); + + // ``set_max_uploads()`` sets the maximum number of peers that's unchoked + // at the same time on this torrent. If you set this to -1, there will be + // no limit. This defaults to infinite. The primary setting controlling + // this is the global unchoke slots limit, set by unchoke_slots_limit in + // settings_pack. + // + // ``max_uploads()`` returns the current settings. + void set_max_uploads(int max_uploads) const; + int max_uploads() const; + + // ``set_max_connections()`` sets the maximum number of connection this + // torrent will open. If all connections are used up, incoming + // connections may be refused or poor connections may be closed. This + // must be at least 2. The default is unlimited number of connections. If + // -1 is given to the function, it means unlimited. There is also a + // global limit of the number of connections, set by + // ``connections_limit`` in settings_pack. + // + // ``max_connections()`` returns the current settings. + void set_max_connections(int max_connections) const; + int max_connections() const; + +#if TORRENT_ABI_VERSION == 1 + // sets a username and password that will be sent along in the HTTP-request + // of the tracker announce. Set this if the tracker requires authorization. + TORRENT_DEPRECATED + void set_tracker_login(std::string const& name + , std::string const& password) const; +#endif + + // Moves the file(s) that this torrent are currently seeding from or + // downloading to. If the given ``save_path`` is not located on the same + // drive as the original save path, the files will be copied to the new + // drive and removed from their original location. This will block all + // other disk IO, and other torrents download and upload rates may drop + // while copying the file. + // + // Since disk IO is performed in a separate thread, this operation is + // also asynchronous. Once the operation completes, the + // ``storage_moved_alert`` is generated, with the new path as the + // message. If the move fails for some reason, + // ``storage_moved_failed_alert`` is generated instead, containing the + // error message. + // + // The ``flags`` argument determines the behavior of the copying/moving + // of the files in the torrent. see move_flags_t. + // + // ``always_replace_files`` is the default and replaces any file that + // exist in both the source directory and the target directory. + // + // ``fail_if_exist`` first check to see that none of the copy operations + // would cause an overwrite. If it would, it will fail. Otherwise it will + // proceed as if it was in ``always_replace_files`` mode. Note that there + // is an inherent race condition here. If the files in the target + // directory appear after the check but before the copy or move + // completes, they will be overwritten. When failing because of files + // already existing in the target path, the ``error`` of + // ``move_storage_failed_alert`` is set to + // ``boost::system::errc::file_exists``. + // + // The intention is that a client may use this as a probe, and if it + // fails, ask the user which mode to use. The client may then re-issue + // the ``move_storage`` call with one of the other modes. + // + // ``dont_replace`` always keeps the existing file in the target + // directory, if there is one. The source files will still be removed in + // that case. Note that it won't automatically re-check files. If an + // incomplete torrent is moved into a directory with the complete files, + // pause, move, force-recheck and resume. Without the re-checking, the + // torrent will keep downloading and files in the new download directory + // will be overwritten. + // + // Files that have been renamed to have absolute paths are not moved by + // this function. Keep in mind that files that don't belong to the + // torrent but are stored in the torrent's directory may be moved as + // well. This goes for files that have been renamed to absolute paths + // that still end up inside the save path. + void move_storage(std::string const& save_path + , move_flags_t flags = move_flags_t::always_replace_files + ) const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + TORRENT_DEPRECATED + void move_storage(std::string const& save_path, int flags) const; +#endif + + // Renames the file with the given index asynchronously. The rename + // operation is complete when either a file_renamed_alert or + // file_rename_failed_alert is posted. + void rename_file(file_index_t index, std::string const& new_name) const; + +#if TORRENT_ABI_VERSION == 1 + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings +#ifdef TORRENT_WINDOWS + TORRENT_DEPRECATED + void move_storage(std::wstring const& save_path, int flags = 0) const; + TORRENT_DEPRECATED + void rename_file(file_index_t index, std::wstring const& new_name) const; +#endif // TORRENT_WINDOWS + + // Enables or disabled super seeding/initial seeding for this torrent. + // The torrent needs to be a seed for this to take effect. + TORRENT_DEPRECATED + void super_seeding(bool on) const; +#endif // TORRENT_ABI_VERSION + + // ``info_hash()`` returns the info-hash of the torrent. If this handle + // is to a torrent that hasn't loaded yet (for instance by being added) + // by a URL, the returned value is undefined. + sha1_hash info_hash() const; + + // comparison operators. The order of the torrents is unspecified + // but stable. + bool operator==(const torrent_handle& h) const + { return !m_torrent.owner_before(h.m_torrent) && !h.m_torrent.owner_before(m_torrent); } + bool operator!=(const torrent_handle& h) const + { return m_torrent.owner_before(h.m_torrent) || h.m_torrent.owner_before(m_torrent); } + bool operator<(const torrent_handle& h) const + { return m_torrent.owner_before(h.m_torrent); } + + // returns a unique identifier for this torrent. It's not a dense index. + // It's not preserved across sessions. + std::uint32_t id() const + { + uintptr_t ret = reinterpret_cast(m_torrent.lock().get()); + // a torrent object is about 1024 (2^10) bytes, so + // it's safe to shift 10 bits + return std::uint32_t(ret >> 10); + } + + // This function is intended only for use by plugins and the alert + // dispatch function. This type does not have a stable ABI and should + // be relied on as little as possible. Accessing the handle returned by + // this function is not thread safe outside of libtorrent's internal + // thread (which is used to invoke plugin callbacks). + // The ``torrent`` class is not only eligible for changing ABI across + // minor versions of libtorrent, its layout is also dependent on build + // configuration. This adds additional requirements on a client to be + // built with the exact same build configuration as libtorrent itself. + // i.e. the ``TORRENT_`` macros must match between libtorrent and the + // client builds. + std::shared_ptr native_handle() const; + + private: + + template + void async_call(Fun f, Args&&... a) const; + + template + void sync_call(Fun f, Args&&... a) const; + + template + Ret sync_call_ret(Ret def, Fun f, Args&&... a) const; + + explicit torrent_handle(std::weak_ptr const& t) + { if (!t.expired()) m_torrent = t; } + + std::weak_ptr m_torrent; + }; +} + +namespace std +{ + template <> + struct hash + { + std::size_t operator()(libtorrent::torrent_handle const& th) const + { + return libtorrent::hash_value(th); + } + }; +} + +#endif // TORRENT_TORRENT_HANDLE_HPP_INCLUDED diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp new file mode 100644 index 0000000..1f1bae1 --- /dev/null +++ b/include/libtorrent/torrent_info.hpp @@ -0,0 +1,736 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_INFO_HPP_INCLUDED +#define TORRENT_TORRENT_INFO_HPP_INCLUDED + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/copy_ptr.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/announce_entry.hpp" + +namespace libtorrent { + + struct lazy_entry; + + // internal, exposed for the unit test + TORRENT_EXTRA_EXPORT void sanitize_append_path_element(std::string& path + , string_view element); + TORRENT_EXTRA_EXPORT bool verify_encoding(std::string& target); + + // the web_seed_entry holds information about a web seed (also known + // as URL seed or HTTP seed). It is essentially a URL with some state + // associated with it. For more information, see `BEP 17`_ and `BEP 19`_. + struct TORRENT_EXPORT web_seed_entry + { + // http seeds are different from url seeds in the + // protocol they use. http seeds follows the original + // http seed spec. by John Hoffman + enum type_t { url_seed, http_seed }; + + using headers_t = std::vector>; + + // hidden + web_seed_entry(std::string const& url_, type_t type_ + , std::string const& auth_ = std::string() + , headers_t const& extra_headers_ = headers_t()); + + // URL and type comparison + bool operator==(web_seed_entry const& e) const + { return type == e.type && url == e.url; } + + // URL and type less-than comparison + bool operator<(web_seed_entry const& e) const + { + if (url < e.url) return true; + if (url > e.url) return false; + return type < e.type; + } + + // The URL of the web seed + std::string url; + + // Optional authentication. If this is set, it's passed + // in as HTTP basic auth to the web seed. The format is: + // username:password. + std::string auth; + + // Any extra HTTP headers that need to be passed to the web seed + headers_t extra_headers; + + // The type of web seed (see type_t) + std::uint8_t type; + }; + + // hidden + class from_span_t {}; + + // used to disambiguate a bencoded buffer and a filename + extern TORRENT_EXPORT from_span_t from_span; + + // this object holds configuration options for limits to use when loading + // torrents. They are meant to prevent loading potentially malicious torrents + // that cause excessive memory allocations. + struct TORRENT_EXPORT load_torrent_limits + { + // the max size of a .torrent file to load into RAM + int max_buffer_size = 10000000; + + // the max number of pieces allowed in the torrent + int max_pieces = 0x200000; + + // the max recursion depth in the bdecoded structure + int max_decode_depth = 100; + + // the max number of bdecode tokens + int max_decode_tokens = 3000000; + }; + + // the torrent_info class holds the information found in a .torrent file. + class TORRENT_EXPORT torrent_info + { + public: + + // The constructor that takes an info-hash will initialize the info-hash + // to the given value, but leave all other fields empty. This is used + // internally when downloading torrents without the metadata. The + // metadata will be created by libtorrent as soon as it has been + // downloaded from the swarm. + // + // The constructor that takes a bdecode_node will create a torrent_info + // object from the information found in the given torrent_file. The + // bdecode_node represents a tree node in an bencoded file. To load an + // ordinary .torrent file into a bdecode_node, use bdecode(). + // + // The version that takes a buffer pointer and a size will decode it as a + // .torrent file and initialize the torrent_info object for you. + // + // The version that takes a filename will simply load the torrent file + // and decode it inside the constructor, for convenience. This might not + // be the most suitable for applications that want to be able to report + // detailed errors on what might go wrong. + // + // There is an upper limit on the size of the torrent file that will be + // loaded by the overload taking a filename. If it's important that even + // very large torrent files are loaded, use one of the other overloads. + // + // The overloads that takes an ``error_code const&`` never throws if an + // error occur, they will simply set the error code to describe what went + // wrong and not fully initialize the torrent_info object. The overloads + // that do not take the extra error_code parameter will always throw if + // an error occurs. These overloads are not available when building + // without exception support. + // + // The overload that takes a ``span`` also needs an extra parameter of + // type ``from_span_t`` to disambiguate the ``std::string`` overload for + // string literals. There is an object in the libtorrent namespace of this + // type called ``from_span``. +#ifndef BOOST_NO_EXCEPTIONS + explicit torrent_info(bdecode_node const& torrent_file); + torrent_info(char const* buffer, int size) + : torrent_info(span{buffer, size}, from_span) {} + explicit torrent_info(span buffer, from_span_t); + explicit torrent_info(std::string const& filename); + torrent_info(std::string const& filename, load_torrent_limits const& cfg); + torrent_info(span buffer, load_torrent_limits const& cfg, from_span_t); + torrent_info(bdecode_node const& torrent_file, load_torrent_limits const& cfg); +#endif // BOOST_NO_EXCEPTIONS + torrent_info(torrent_info const& t); + explicit torrent_info(sha1_hash const& info_hash); + torrent_info(bdecode_node const& torrent_file, error_code& ec); + torrent_info(char const* buffer, int size, error_code& ec) + : torrent_info(span{buffer, size}, ec, from_span) {} + torrent_info(span buffer, error_code& ec, from_span_t); + torrent_info(std::string const& filename, error_code& ec); + +#if TORRENT_ABI_VERSION == 1 +#ifndef BOOST_NO_EXCEPTIONS + TORRENT_DEPRECATED + torrent_info(char const* buffer, int size, int) + : torrent_info(span{buffer, size}, from_span) {} +#endif + TORRENT_DEPRECATED + torrent_info(bdecode_node const& torrent_file, error_code& ec, int) + : torrent_info(torrent_file, ec) {} + TORRENT_DEPRECATED + torrent_info(std::string const& filename, error_code& ec, int) + : torrent_info(filename, ec) {} + TORRENT_DEPRECATED + torrent_info(char const* buffer, int size, error_code& ec, int) + : torrent_info(span{buffer, size}, ec, from_span) {} + TORRENT_DEPRECATED + explicit torrent_info(lazy_entry const& torrent_file); + + TORRENT_DEPRECATED + torrent_info(lazy_entry const& torrent_file, error_code& eca); + // all wstring APIs are deprecated since 0.16.11 instead, use the wchar + // -> utf8 conversion functions and pass in utf8 strings +#ifdef TORRENT_WINDOWS + TORRENT_DEPRECATED + torrent_info(std::wstring const& filename, error_code& ec); + TORRENT_DEPRECATED + explicit torrent_info(std::wstring const& filename); +#endif +#endif // TORRENT_ABI_VERSION + + // frees all storage associated with this torrent_info object + ~torrent_info(); + + // The file_storage object contains the information on how to map the + // pieces to files. It is separated from the torrent_info object because + // when creating torrents a storage object needs to be created without + // having a torrent file. When renaming files in a storage, the storage + // needs to make its own copy of the file_storage in order to make its + // mapping differ from the one in the torrent file. + // + // ``orig_files()`` returns the original (unmodified) file storage for + // this torrent. This is used by the web server connection, which needs + // to request files with the original names. Filename may be changed using + // ``torrent_info::rename_file()``. + // + // For more information on the file_storage object, see the separate + // document on how to create torrents. + file_storage const& files() const { return m_files; } + file_storage const& orig_files() const + { + TORRENT_ASSERT(is_loaded()); + return m_orig_files ? *m_orig_files : m_files; + } + + // Renames the file with the specified index to the new name. The new + // filename is reflected by the ``file_storage`` returned by ``files()`` + // but not by the one returned by ``orig_files()``. + // + // If you want to rename the base name of the torrent (for a multi file + // torrent), you can copy the ``file_storage`` (see files() and + // orig_files() ), change the name, and then use `remap_files()`_. + // + // The ``new_filename`` can both be a relative path, in which case the + // file name is relative to the ``save_path`` of the torrent. If the + // ``new_filename`` is an absolute path (i.e. ``is_complete(new_filename) + // == true``), then the file is detached from the ``save_path`` of the + // torrent. In this case the file is not moved when move_storage() is + // invoked. + void rename_file(file_index_t index, std::string const& new_filename) + { + TORRENT_ASSERT(is_loaded()); + if (m_files.file_path(index) == new_filename) return; + copy_on_write(); + m_files.rename_file(index, new_filename); + } +#if TORRENT_ABI_VERSION == 1 + // all wstring APIs are deprecated since 0.16.11 + // instead, use the wchar -> utf8 conversion functions + // and pass in utf8 strings + TORRENT_DEPRECATED + void rename_file(file_index_t index, std::wstring const& new_filename); +#endif // TORRENT_ABI_VERSION + + // Remaps the file storage to a new file layout. This can be used to, for + // instance, download all data in a torrent to a single file, or to a + // number of fixed size sector aligned files, regardless of the number + // and sizes of the files in the torrent. + // + // The new specified ``file_storage`` must have the exact same size as + // the current one. + void remap_files(file_storage const& f); + + // ``add_tracker()`` adds a tracker to the announce-list. The ``tier`` + // determines the order in which the trackers are to be tried. + // The ``trackers()`` function will return a sorted vector of + // ``announce_entry``. Each announce entry contains a string, which is + // the tracker url, and a tier index. The tier index is the high-level + // priority. No matter which trackers that works or not, the ones with + // lower tier will always be tried before the one with higher tier + // number. For more information, see announce_entry_. + void add_tracker(std::string const& url, int tier = 0); + void add_tracker(std::string const& url, int tier + , announce_entry::tracker_source source); + std::vector const& trackers() const { return m_urls; } + + // These two functions are related to `BEP 38`_ (mutable torrents). The + // vectors returned from these correspond to the "similar" and + // "collections" keys in the .torrent file. Both info-hashes and + // collections from within the info-dict and from outside of it are + // included. + // + // .. _`BEP 38`: http://www.bittorrent.org/beps/bep_0038.html + std::vector similar_torrents() const; + std::vector collections() const; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 0.16. Use web_seeds() instead + TORRENT_DEPRECATED + std::vector url_seeds() const; + TORRENT_DEPRECATED + std::vector http_seeds() const; + + // deprecated in 1.1 + TORRENT_DEPRECATED + bool parse_info_section(lazy_entry const& e, error_code& ec); +#endif // TORRENT_ABI_VERSION + + // ``web_seeds()`` returns all url seeds and http seeds in the torrent. + // Each entry is a ``web_seed_entry`` and may refer to either a url seed + // or http seed. + // + // ``add_url_seed()`` and ``add_http_seed()`` adds one url to the list of + // url/http seeds. Currently, the only transport protocol supported for + // the url is http. + // + // ``set_web_seeds()`` replaces all web seeds with the ones specified in + // the ``seeds`` vector. + // + // The ``extern_auth`` argument can be used for other authorization + // schemes than basic HTTP authorization. If set, it will override any + // username and password found in the URL itself. The string will be sent + // as the HTTP authorization header's value (without specifying "Basic"). + // + // The ``extra_headers`` argument defaults to an empty list, but can be + // used to insert custom HTTP headers in the requests to a specific web + // seed. + // + // See http-seeding_ for more information. + void add_url_seed(std::string const& url + , std::string const& extern_auth = std::string() + , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t()); + void add_http_seed(std::string const& url + , std::string const& extern_auth = std::string() + , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t()); + std::vector const& web_seeds() const { return m_web_seeds; } + void set_web_seeds(std::vector seeds); + + // ``total_size()``, ``piece_length()`` and ``num_pieces()`` returns the + // total number of bytes the torrent-file represents (all the files in + // it), the number of byte for each piece and the total number of pieces, + // respectively. The difference between ``piece_size()`` and + // ``piece_length()`` is that ``piece_size()`` takes the piece index as + // argument and gives you the exact size of that piece. It will always be + // the same as ``piece_length()`` except in the case of the last piece, + // which may be smaller. + std::int64_t total_size() const { return m_files.total_size(); } + int piece_length() const { return m_files.piece_length(); } + int num_pieces() const { return m_files.num_pieces(); } + + // ``last_piece()`` returns the index to the last piece in the torrent and + // ``end_piece()`` returns the index to the one-past-end piece in the + // torrent + // ``piece_range()`` returns an implementation-defined type that can be + // used as the container in a range-for loop. Where the values are the + // indices of all pieces in the file_storage. + piece_index_t last_piece() const { return m_files.last_piece(); } + piece_index_t end_piece() const + { + TORRENT_ASSERT(m_files.num_pieces() > 0); + return m_files.end_piece(); + } + index_range piece_range() const + { return m_files.piece_range(); } + + // returns the info-hash of the torrent + const sha1_hash& info_hash() const { return m_info_hash; } + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.0. Use the variants that take an index instead + // internal_file_entry is no longer exposed in the API + using file_iterator = file_storage::iterator; + using reverse_file_iterator = file_storage::reverse_iterator; + + // This class will need some explanation. First of all, to get a list of + // all files in the torrent, you can use ``begin_files()``, + // ``end_files()``, ``rbegin_files()`` and ``rend_files()``. These will + // give you standard vector iterators with the type + // ``internal_file_entry``, which is an internal type. + // + // You can resolve it into the public representation of a file + // (``file_entry``) using the ``file_storage::at`` function, which takes + // an index and an iterator. + TORRENT_DEPRECATED + file_iterator begin_files() const { return m_files.begin_deprecated(); } + TORRENT_DEPRECATED + file_iterator end_files() const { return m_files.end_deprecated(); } + reverse_file_iterator rbegin_files() const { return m_files.rbegin_deprecated(); } + TORRENT_DEPRECATED + reverse_file_iterator rend_files() const { return m_files.rend_deprecated(); } + + TORRENT_DEPRECATED + file_iterator file_at_offset(std::int64_t offset) const + { return m_files.file_at_offset_deprecated(offset); } + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#if defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + TORRENT_DEPRECATED + file_entry file_at(int index) const { return m_files.at_deprecated(index); } + +#if defined __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // TORRENT_ABI_VERSION + + // If you need index-access to files you can use the ``num_files()`` along + // with the ``file_path()``, ``file_size()``-family of functions to access + // files using indices. + int num_files() const { return m_files.num_files(); } + + // This function will map a piece index, a byte offset within that piece + // and a size (in bytes) into the corresponding files with offsets where + // that data for that piece is supposed to be stored. See file_slice. + std::vector map_block(piece_index_t const piece + , std::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_block(piece, offset, size); + } + + // This function will map a range in a specific file into a range in the + // torrent. The ``file_offset`` parameter is the offset in the file, + // given in bytes, where 0 is the start of the file. See peer_request. + // + // The input range is assumed to be valid within the torrent. + // ``file_offset`` + ``size`` is not allowed to be greater than the file + // size. ``file_index`` must refer to a valid file, i.e. it cannot be >= + // ``num_files()``. + peer_request map_file(file_index_t const file, std::int64_t offset, int size) const + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_file(file, offset, size); + } + +#if TORRENT_ABI_VERSION == 1 +// ------- start deprecation ------- + // deprecated in 1.2 + void load(char const*, int, error_code&) {} + void unload() {} + + TORRENT_DEPRECATED + explicit torrent_info(entry const& torrent_file); +// ------- end deprecation ------- +#endif + + // Returns the SSL root certificate for the torrent, if it is an SSL + // torrent. Otherwise returns an empty string. The certificate is + // the public certificate in x509 format. + string_view ssl_cert() const; + + // returns true if this torrent_info object has a torrent loaded. + // This is primarily used to determine if a magnet link has had its + // metadata resolved yet or not. + bool is_valid() const { return m_files.is_valid(); } + + // returns true if this torrent is private. i.e., the client should not + // advertise itself on the trackerless network (the Kademlia DHT) for this torrent. + bool priv() const { return (m_flags & private_torrent) != 0; } + + // returns true if this is an i2p torrent. This is determined by whether + // or not it has a tracker whose URL domain name ends with ".i2p". i2p + // torrents disable the DHT and local peer discovery as well as talking + // to peers over anything other than the i2p network. + bool is_i2p() const { return (m_flags & i2p) != 0; } + + // returns the piece size of file with ``index``. This will be the same as piece_length(), + // except for the last piece, which may be shorter. + int piece_size(piece_index_t index) const { return m_files.piece_size(index); } + + // ``hash_for_piece()`` takes a piece-index and returns the 20-bytes + // sha1-hash for that piece and ``info_hash()`` returns the 20-bytes + // sha1-hash for the info-section of the torrent file. + // ``hash_for_piece_ptr()`` returns a pointer to the 20 byte sha1 digest + // for the piece. Note that the string is not 0-terminated. + sha1_hash hash_for_piece(piece_index_t index) const; + char const* hash_for_piece_ptr(piece_index_t const index) const + { + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_files.end_piece()); + TORRENT_ASSERT(is_loaded()); + int const idx = static_cast(index); + if (is_merkle_torrent()) + { + TORRENT_ASSERT(idx < m_merkle_tree.end_index() - m_merkle_first_leaf); + return m_merkle_tree[m_merkle_first_leaf + idx].data(); + } + else + { + TORRENT_ASSERT(m_piece_hashes); + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + TORRENT_ASSERT(idx < int(m_info_section_size / 20)); + return &m_piece_hashes[idx * 20]; + } + } + + bool is_loaded() const { return m_piece_hashes || !m_merkle_tree.empty(); } + + // ``merkle_tree()`` returns a reference to the merkle tree for this + // torrent, if any. + // ``set_merkle_tree()`` moves the passed in merkle tree into the + // torrent_info object. i.e. ``h`` will not be identical after the call. + // You need to set the merkle tree for a torrent that you've just created + // (as a merkle torrent). The merkle tree is retrieved from the + // ``create_torrent::merkle_tree()`` function, and need to be saved + // separately from the torrent file itself. Once it's added to + // libtorrent, the merkle tree will be persisted in the resume data. + std::vector const& merkle_tree() const { return m_merkle_tree; } + void set_merkle_tree(std::vector& h) + { TORRENT_ASSERT(h.size() == m_merkle_tree.size() ); m_merkle_tree.swap(h); } + + // ``name()`` returns the name of the torrent. + // name contains UTF-8 encoded string. + const std::string& name() const { return m_files.name(); } + + // ``creation_date()`` returns the creation date of the torrent as time_t + // (`posix time`_). If there's no time stamp in the torrent file, the + // optional object will be uninitialized. + // .. _`posix time`: http://www.opengroup.org/onlinepubs/009695399/functions/time.html + std::time_t creation_date() const + { return m_creation_date; } + + // ``creator()`` returns the creator string in the torrent. If there is + // no creator string it will return an empty string. + const std::string& creator() const + { return m_created_by; } + + // ``comment()`` returns the comment associated with the torrent. If + // there's no comment, it will return an empty string. + // comment contains UTF-8 encoded string. + const std::string& comment() const + { return m_comment; } + + // If this torrent contains any DHT nodes, they are put in this vector in + // their original form (host name and port number). + std::vector> const& nodes() const + { return m_nodes; } + + // This is used when creating torrent. Use this to add a known DHT node. + // It may be used, by the client, to bootstrap into the DHT network. + void add_node(std::pair const& node) + { m_nodes.push_back(node); } + + // populates the torrent_info by providing just the info-dict buffer. + // This is used when loading a torrent from a magnet link for instance, + // where we only have the info-dict. The bdecode_node ``e`` points to a + // parsed info-dictionary. ``ec`` returns an error code if something + // fails (typically if the info dictionary is malformed). + // the `piece_limit` parameter allows limiting the amount of memory + // dedicated to loading the torrent, and fails for torrents that exceed + // the limit + bool parse_info_section(bdecode_node const& e, error_code& ec); + bool parse_info_section(bdecode_node const& e, error_code& ec, int piece_limit); + + // This function looks up keys from the info-dictionary of the loaded + // torrent file. It can be used to access extension values put in the + // .torrent file. If the specified key cannot be found, it returns nullptr. + bdecode_node info(char const* key) const; + + // swap the content of this and ``ti``. + void swap(torrent_info& ti); + + // ``metadata()`` returns a the raw info section of the torrent file. The size + // of the metadata is returned by ``metadata_size()``. + int metadata_size() const { return m_info_section_size; } + boost::shared_array metadata() const + { return m_info_section; } + + // internal + bool add_merkle_nodes(std::map const& subtree + , piece_index_t piece); + std::map build_merkle_list(piece_index_t piece) const; + + // internal + void internal_set_creator(string_view const); + void internal_set_creation_date(std::time_t); + void internal_set_comment(string_view const); + + // returns whether or not this is a merkle torrent. + // see `BEP 30`__. + // + // __ https://www.bittorrent.org/beps/bep_0030.html + bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } + + private: + + // TODO: there may be some opportunities to optimize the size if torrent_info. + // specifically to turn some std::string and std::vector into pointers + + bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec); + bool parse_torrent_file(bdecode_node const& libtorrent, error_code& ec, int piece_limit); + + void resolve_duplicate_filenames(); + + // the slow path, in case we detect/suspect a name collision + void resolve_duplicate_filenames_slow(); + +#if TORRENT_USE_INVARIANT_CHECKS + friend class invariant_access; + void check_invariant() const; +#endif + + // not assignable + torrent_info const& operator=(torrent_info const&); + + void copy_on_write(); + + file_storage m_files; + + // if m_files is modified, it is first copied into + // m_orig_files so that the original name and + // filenames are preserved. + // the original filenames are required to build URLs for web seeds for + // instance + copy_ptr m_orig_files; + + // the URLs to the trackers + aux::vector m_urls; + std::vector m_web_seeds; + // dht nodes to add to the routing table/bootstrap from + std::vector> m_nodes; + + // the info-hashes (20 bytes each) in the "similar" key. The pointers + // point directly into the info_section. When copied, these pointers must + // be corrected to point into the copied-to buffer + std::vector m_similar_torrents; + + // these are similar torrents from outside of the info-dict. We can't + // have non-owning pointers to those, as we only keep the info-dict + // around. + std::vector m_owned_similar_torrents; + + // these or strings of the "collections" key from the torrent file. The + // pointers point directly into the info_section buffer and when copied, + // these pointers must be corrected to point into the new buffer. The + // int is the length of the string. Strings are not 0-terminated. + std::vector> m_collections; + + // these are the collections from outside of the info-dict. These are + // owning strings, since we only keep the info-section around, these + // cannot be pointers into that buffer. + std::vector m_owned_collections; + + // if this is a merkle torrent, this is the merkle + // tree. It has space for merkle_num_nodes(merkle_num_leafs(num_pieces)) + // hashes + aux::vector m_merkle_tree; + + // this is a copy of the info section from the torrent. + // it use maintained in this flat format in order to + // make it available through the metadata extension + // TODO: change the type to std::shared_ptr in C++17 + boost::shared_array m_info_section; + + // this is a pointer into the m_info_section buffer + // pointing to the first byte of the first SHA-1 hash + char const* m_piece_hashes = nullptr; + + // if a comment is found in the torrent file + // this will be set to that comment + std::string m_comment; + + // an optional string naming the software used + // to create the torrent file + std::string m_created_by; + + // the info section parsed. points into m_info_section + // parsed lazily + mutable bdecode_node m_info_dict; + + // if a creation date is found in the torrent file + // this will be set to that, otherwise it'll be + // 1970, Jan 1 + std::time_t m_creation_date = 0; + + // the hash that identifies this torrent + sha1_hash m_info_hash; + + // the number of bytes in m_info_section + std::int32_t m_info_section_size = 0; + + // the index to the first leaf. This is where the hash for the + // first piece is stored + std::int32_t m_merkle_first_leaf = 0; + + enum flags_t : std::uint8_t + { + // this is used when creating a torrent. If there's + // only one file there are cases where it's impossible + // to know if it should be written as a multi file torrent + // or not. e.g. test/test there's one file and one directory + // and they have the same name. + multifile = 1, + + // this is true if the torrent is private. i.e., is should not + // be announced on the dht + private_torrent = 2, + + // this is true if one of the trackers has an .i2p top + // domain in its hostname. This means the DHT and LSD + // features are disabled for this torrent (unless the + // settings allows mixing i2p peers with regular peers) + i2p = 4, + + // this flag is set if we found an ssl-cert field in the info + // dictionary + ssl_torrent = 8, + }; + + // any combination of values from flags_t enum + std::uint8_t m_flags = 0; + }; + +} + +#endif // TORRENT_TORRENT_INFO_HPP_INCLUDED diff --git a/include/libtorrent/torrent_peer.hpp b/include/libtorrent/torrent_peer.hpp new file mode 100644 index 0000000..cdc7d93 --- /dev/null +++ b/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,274 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_PEER_HPP_INCLUDED +#define TORRENT_TORRENT_PEER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/peer_info.hpp" // for peer_source_flags_t +#include "libtorrent/aux_/string_ptr.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + struct peer_connection_interface; + struct external_ip; + + // calculate the priority of a peer based on its address. One of the + // endpoint should be our own. The priority is symmetric, so it doesn't + // matter which is which + TORRENT_EXTRA_EXPORT std::uint32_t peer_priority( + tcp::endpoint e1, tcp::endpoint e2); + + struct TORRENT_EXTRA_EXPORT torrent_peer + { + torrent_peer(std::uint16_t port, bool connectable, peer_source_flags_t src); +#if TORRENT_USE_ASSERTS + torrent_peer(torrent_peer const&) = default; + torrent_peer& operator=(torrent_peer const&) = default; + ~torrent_peer() { TORRENT_ASSERT(in_use); in_use = false; } +#endif + + std::int64_t total_download() const; + std::int64_t total_upload() const; + + std::uint32_t rank(external_ip const& external, int external_port) const; + + libtorrent::address address() const; + string_view dest() const; + + tcp::endpoint ip() const { return tcp::endpoint(address(), port); } + +#ifndef TORRENT_DISABLE_LOGGING + std::string to_string() const; +#endif + + // this is the accumulated amount of + // uploaded and downloaded data to this + // torrent_peer. It only accounts for what was + // shared during the last connection to + // this torrent_peer. i.e. These are only updated + // when the connection is closed. For the + // total amount of upload and download + // we'll have to add these figures with the + // statistics from the peer_connection. + // since these values don't need to be stored + // with byte-precision, they specify the number + // of kiB. i.e. shift left 10 bits to compare to + // byte counters. + std::uint32_t prev_amount_upload; + std::uint32_t prev_amount_download; + + // if the torrent_peer is connected now, this + // will refer to a valid peer_connection + peer_connection_interface* connection; + + // as computed by hashing our IP with the remote + // IP of this peer + // calculated lazily + mutable std::uint32_t peer_rank; + + // the time when this torrent_peer was optimistically unchoked + // the last time. in seconds since session was created + // 16 bits is enough to last for 18.2 hours + // when the session time reaches 18 hours, it jumps back by + // 9 hours, and all peers' times are updated to be + // relative to that new time offset + std::uint16_t last_optimistically_unchoked; + + // the time when the torrent_peer connected to us + // or disconnected if it isn't connected right now + // in number of seconds since session was created + std::uint16_t last_connected; + + // the port this torrent_peer is or was connected on + std::uint16_t port; + + // the number of times this torrent_peer has been + // part of a piece that failed the hash check + std::uint8_t hashfails; + + // the number of failed connection attempts + // this torrent_peer has + std::uint32_t failcount:5; // [0, 31] + + // incoming peers (that don't advertise their listen port) + // will not be considered connectable. Peers that + // we have a listen port for will be assumed to be. + bool connectable:1; + + // true if this torrent_peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another torrent_peer, this torrent_peer will be choked + // if this is true + bool optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed + bool seed:1; + + // the number of times we have allowed a fast + // reconnect for this torrent_peer. + std::uint32_t fast_reconnects:4; + + // for every valid piece we receive where this + // torrent_peer was one of the participants, we increase + // this value. For every invalid piece we receive + // where this torrent_peer was a participant, we decrease + // this value. If it sinks below a threshold, its + // considered a bad torrent_peer and will be banned. + signed trust_points:4; // [-7, 8] + + // a bitmap combining the peer_source flags + // from peer_info. + std::uint32_t source:6; + + peer_source_flags_t peer_source() const + { return peer_source_flags_t(source); } + +#if !defined TORRENT_DISABLE_ENCRYPTION + // Hints encryption support of torrent_peer. Only effective + // for and when the outgoing encryption policy + // allows both encrypted and non encrypted + // connections (pe_settings::out_enc_policy + // == enabled). The initial state of this flag + // determines the initial connection attempt + // type (true = encrypted, false = standard). + // This will be toggled everytime either an + // encrypted or non-encrypted handshake fails. + bool pe_support:1; +#endif + + // this is true if the v6 union member in addr is + // the one to use, false if it's the v4 one + bool is_v6_addr:1; +#if TORRENT_USE_I2P + // set if the i2p_destination is in use in the addr union + bool is_i2p_addr:1; +#endif + + // if this is true, the torrent_peer has previously + // participated in a piece that failed the piece + // hash check. This will put the torrent_peer on parole + // and only request entire pieces. If a piece pass + // that was partially requested from this torrent_peer it + // will leave parole mode and continue download + // pieces as normal peers. + bool on_parole:1; + + // is set to true if this torrent_peer has been banned + bool banned:1; + + // we think this torrent_peer supports uTP + bool supports_utp:1; + // we have been connected via uTP at least once + bool confirmed_supports_utp:1; + bool supports_holepunch:1; + // this is set to one for web seeds. Web seeds + // are not stored in the policy m_peers list, + // and are exempt from connect candidate bookkeeping + // so, any torrent_peer with the web_seed bit set, is + // never considered a connect candidate + bool web_seed:1; +#if TORRENT_USE_ASSERTS + bool in_use = true; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ip, bool connectable, peer_source_flags_t src); + ipv4_peer(ipv4_peer const& p); + ipv4_peer& operator=(ipv4_peer const& p); + + address_v4 addr; + }; + +#if TORRENT_USE_I2P + struct TORRENT_EXTRA_EXPORT i2p_peer : torrent_peer + { + i2p_peer(string_view dst, bool connectable, peer_source_flags_t src); + i2p_peer(i2p_peer const&) = delete; + i2p_peer& operator=(i2p_peer const&) = delete; + i2p_peer(i2p_peer&&) = default; + i2p_peer& operator=(i2p_peer&&) = default; + + aux::string_ptr destination; + }; +#endif + + struct TORRENT_EXTRA_EXPORT ipv6_peer : torrent_peer + { + ipv6_peer(tcp::endpoint const& ip, bool connectable, peer_source_flags_t src); + ipv6_peer(ipv6_peer const& p); + + const address_v6::bytes_type addr; + }; + + struct peer_address_compare + { + bool operator()(torrent_peer const* lhs, address const& rhs) const + { + return lhs->address() < rhs; + } + + bool operator()(address const& lhs, torrent_peer const* rhs) const + { + return lhs < rhs->address(); + } + +#if TORRENT_USE_I2P + bool operator()(torrent_peer const* lhs, string_view rhs) const + { + return lhs->dest().compare(rhs) < 0; + } + + bool operator()(string_view lhs, torrent_peer const* rhs) const + { + return lhs.compare(rhs->dest()) < 0; + } +#endif + + bool operator()(torrent_peer const* lhs, torrent_peer const* rhs) const + { +#if TORRENT_USE_I2P + if (rhs->is_i2p_addr == lhs->is_i2p_addr) + return lhs->dest().compare(rhs->dest()) < 0; +#endif + return lhs->address() < rhs->address(); + } + }; +} + +#endif diff --git a/include/libtorrent/torrent_peer_allocator.hpp b/include/libtorrent/torrent_peer_allocator.hpp new file mode 100644 index 0000000..fbe7c03 --- /dev/null +++ b/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator_interface + { + enum peer_type_t + { + ipv4_peer_type, + ipv6_peer_type, + i2p_peer_type + }; + + virtual torrent_peer* allocate_peer_entry(int type) = 0; + virtual void free_peer_entry(torrent_peer* p) = 0; + protected: + ~torrent_peer_allocator_interface() {} + }; + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator final + : torrent_peer_allocator_interface + { +#if TORRENT_USE_ASSERTS + ~torrent_peer_allocator() { + m_in_use = false; + } +#endif + + torrent_peer* allocate_peer_entry(int type) override; + void free_peer_entry(torrent_peer* p) override; + + std::uint64_t total_bytes() const { return m_total_bytes; } + std::uint64_t total_allocations() const { return m_total_allocations; } + int live_bytes() const { return m_live_bytes; } + int live_allocations() const { return m_live_allocations; } + + private: + + // this is a shared pool where torrent_peer objects + // are allocated. It's a pool since we're likely + // to have tens of thousands of peers, and a pool + // saves significant overhead + + boost::pool<> m_ipv4_peer_pool{sizeof(libtorrent::ipv4_peer), 500}; + boost::pool<> m_ipv6_peer_pool{sizeof(libtorrent::ipv6_peer), 500}; +#if TORRENT_USE_I2P + boost::pool<> m_i2p_peer_pool{sizeof(libtorrent::i2p_peer), 500}; +#endif + + // the total number of bytes allocated (cumulative) + std::uint64_t m_total_bytes = 0; + // the total number of allocations (cumulative) + std::uint64_t m_total_allocations = 0; + // the number of currently live bytes + int m_live_bytes = 0; + // the number of currently live allocations + int m_live_allocations = 0; +#if TORRENT_USE_ASSERTS + bool m_in_use = true; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/torrent_status.hpp b/include/libtorrent/torrent_status.hpp new file mode 100644 index 0000000..ede0e2e --- /dev/null +++ b/include/libtorrent/torrent_status.hpp @@ -0,0 +1,605 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TORRENT_STATUS_HPP_INCLUDED +#define TORRENT_TORRENT_STATUS_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/time.hpp" // for time_duration +#include "libtorrent/storage_defs.hpp" // for storage_mode_t +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/noexcept_movable.hpp" + +#include +#include +#include + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#endif + +TORRENT_VERSION_NAMESPACE_2 + + // holds a snapshot of the status of a torrent, as queried by + // torrent_handle::status(). + struct TORRENT_EXPORT torrent_status + { +#if TORRENT_ABI_VERSION == 1 +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#endif + // hidden + torrent_status() noexcept; + ~torrent_status(); + torrent_status(torrent_status const&); + torrent_status& operator=(torrent_status const&); + torrent_status(torrent_status&&) noexcept; + torrent_status& operator=(torrent_status&&); + + // compares if the torrent status objects come from the same torrent. i.e. + // only the torrent_handle field is compared. + bool operator==(torrent_status const& st) const + { return handle == st.handle; } + + // a handle to the torrent whose status the object represents. + torrent_handle handle; + + // the different overall states a torrent can be in + enum state_t + { +#if TORRENT_ABI_VERSION == 1 + // The torrent is in the queue for being checked. But there + // currently is another torrent that are being checked. + // This torrent will wait for its turn. + queued_for_checking TORRENT_DEPRECATED_ENUM, +#else + // internal + unused_enum_for_backwards_compatibility, +#endif + + // The torrent has not started its download yet, and is + // currently checking existing files. + checking_files, + + // The torrent is trying to download metadata from peers. + // This implies the ut_metadata extension is in use. + downloading_metadata, + + // The torrent is being downloaded. This is the state + // most torrents will be in most of the time. The progress + // meter will tell how much of the files that has been + // downloaded. + downloading, + + // In this state the torrent has finished downloading but + // still doesn't have the entire torrent. i.e. some pieces + // are filtered and won't get downloaded. + finished, + + // In this state the torrent has finished downloading and + // is a pure seeder. + seeding, + + // If the torrent was started in full allocation mode, this + // indicates that the (disk) storage for the torrent is + // allocated. + allocating, + + // The torrent is currently checking the fast resume data and + // comparing it to the files on disk. This is typically + // completed in a fraction of a second, but if you add a + // large number of torrents at once, they will queue up. + checking_resume_data + }; + +#if TORRENT_ABI_VERSION == 1 + std::string TORRENT_DEPRECATED_MEMBER error; +#endif + + // may be set to an error code describing why the torrent was paused, in + // case it was paused by an error. If the torrent is not paused or if it's + // paused but not because of an error, this error_code is not set. + // if the error is attributed specifically to a file, error_file is set to + // the index of that file in the .torrent file. + error_code errc; + + // if the torrent is stopped because of an disk I/O error, this field + // contains the index of the file in the torrent that encountered the + // error. If the error did not originate in a file in the torrent, there + // are a few special values this can be set to: error_file_none, + // error_file_ssl_ctx, error_file_exception, error_file_partfile or + // error_file_metadata; + file_index_t error_file = torrent_status::error_file_none; + + // special values for error_file to describe which file or component + // encountered the error (``errc``). + // the error did not occur on a file + static constexpr file_index_t error_file_none{-1}; + + // the error occurred setting up the SSL context + static constexpr file_index_t error_file_ssl_ctx{-3}; + + // the error occurred while loading the .torrent file via the user + // supplied load function + static constexpr file_index_t error_file_metadata{-4}; + +#if TORRENT_ABI_VERSION == 1 + // the error occurred on m_url + static constexpr file_index_t TORRENT_DEPRECATED error_file_url{-2}; +#endif + + // there was a serious error reported in this torrent. The error code + // or a torrent log alert may provide more information. + static constexpr file_index_t error_file_exception{-5}; + + // the error occurred with the partfile + static constexpr file_index_t error_file_partfile{-6}; + + // the path to the directory where this torrent's files are stored. + // It's typically the path as was given to async_add_torrent() or + // add_torrent() when this torrent was started. This field is only + // included if the torrent status is queried with + // ``torrent_handle::query_save_path``. + std::string save_path; + + // the name of the torrent. Typically this is derived from the + // .torrent file. In case the torrent was started without metadata, + // and hasn't completely received it yet, it returns the name given + // to it when added to the session. See ``session::add_torrent``. + // This field is only included if the torrent status is queried + // with ``torrent_handle::query_name``. + std::string name; + + // set to point to the ``torrent_info`` object for this torrent. It's + // only included if the torrent status is queried with + // ``torrent_handle::query_torrent_file``. + std::weak_ptr torrent_file; + + // the time until the torrent will announce itself to the tracker. + time_duration next_announce = seconds(0); + +#if TORRENT_ABI_VERSION == 1 + // the time the tracker want us to wait until we announce ourself + // again the next time. + time_duration TORRENT_DEPRECATED_MEMBER announce_interval; +#endif + + // the URL of the last working tracker. If no tracker request has + // been successful yet, it's set to an empty string. + std::string current_tracker; + + // the number of bytes downloaded and uploaded to all peers, accumulated, + // *this session* only. The session is considered to restart when a + // torrent is paused and restarted again. When a torrent is paused, these + // counters are reset to 0. If you want complete, persistent, stats, see + // ``all_time_upload`` and ``all_time_download``. + std::int64_t total_download = 0; + std::int64_t total_upload = 0; + + // counts the amount of bytes send and received this session, but only + // the actual payload data (i.e the interesting data), these counters + // ignore any protocol overhead. The session is considered to restart + // when a torrent is paused and restarted again. When a torrent is + // paused, these counters are reset to 0. + std::int64_t total_payload_download = 0; + std::int64_t total_payload_upload = 0; + + // the number of bytes that has been downloaded and that has failed the + // piece hash test. In other words, this is just how much crap that has + // been downloaded since the torrent was last started. If a torrent is + // paused and then restarted again, this counter will be reset. + std::int64_t total_failed_bytes = 0; + + // the number of bytes that has been downloaded even though that data + // already was downloaded. The reason for this is that in some situations + // the same data can be downloaded by mistake. When libtorrent sends + // requests to a peer, and the peer doesn't send a response within a + // certain timeout, libtorrent will re-request that block. Another + // situation when libtorrent may re-request blocks is when the requests + // it sends out are not replied in FIFO-order (it will re-request blocks + // that are skipped by an out of order block). This is supposed to be as + // low as possible. This only counts bytes since the torrent was last + // started. If a torrent is paused and then restarted again, this counter + // will be reset. + std::int64_t total_redundant_bytes = 0; + + // a bitmask that represents which pieces we have (set to true) and the + // pieces we don't have. It's a pointer and may be set to 0 if the + // torrent isn't downloading or seeding. + typed_bitfield pieces; + + // a bitmask representing which pieces has had their hash checked. This + // only applies to torrents in *seed mode*. If the torrent is not in seed + // mode, this bitmask may be empty. + typed_bitfield verified_pieces; + + // the total number of bytes of the file(s) that we have. All this does + // not necessarily has to be downloaded during this session (that's + // ``total_payload_download``). + std::int64_t total_done = 0; + + // the total number of bytes to download for this torrent. This + // may be less than the size of the torrent in case there are + // pad files. This number only counts bytes that will actually + // be requested from peers. + std::int64_t total = 0; + + // the number of bytes we have downloaded, only counting the pieces that + // we actually want to download. i.e. excluding any pieces that we have + // but have priority 0 (i.e. not wanted). + std::int64_t total_wanted_done = 0; + + // The total number of bytes we want to download. This may be smaller + // than the total torrent size in case any pieces are prioritized to 0, + // i.e. not wanted + std::int64_t total_wanted = 0; + + // are accumulated upload and download payload byte counters. They are + // saved in and restored from resume data to keep totals across sessions. + std::int64_t all_time_upload = 0; + std::int64_t all_time_download = 0; + + // the posix-time when this torrent was added. i.e. what ``time(nullptr)`` + // returned at the time. + std::time_t added_time = 0; + + // the posix-time when this torrent was finished. If the torrent is not + // yet finished, this is 0. + std::time_t completed_time = 0; + + // the time when we, or one of our peers, last saw a complete copy of + // this torrent. + std::time_t last_seen_complete = 0; + + // The allocation mode for the torrent. See storage_mode_t for the + // options. For more information, see storage-allocation_. + storage_mode_t storage_mode = storage_mode_sparse; + + // a value in the range [0, 1], that represents the progress of the + // torrent's current task. It may be checking files or downloading. + float progress = 0.f; + + // progress parts per million (progress * 1000000) when disabling + // floating point operations, this is the only option to query progress + // + // reflects the same value as ``progress``, but instead in a range [0, + // 1000000] (ppm = parts per million). When floating point operations are + // disabled, this is the only alternative to the floating point value in + // progress. + int progress_ppm = 0; + + // the position this torrent has in the download + // queue. If the torrent is a seed or finished, this is -1. + queue_position_t queue_position{}; + + // the total rates for all peers for this torrent. These will usually + // have better precision than summing the rates from all peers. The rates + // are given as the number of bytes per second. + int download_rate = 0; + int upload_rate = 0; + + // the total transfer rate of payload only, not counting protocol + // chatter. This might be slightly smaller than the other rates, but if + // projected over a long time (e.g. when calculating ETA:s) the + // difference may be noticeable. + int download_payload_rate = 0; + int upload_payload_rate = 0; + + // the number of peers that are seeding that this client is + // currently connected to. + int num_seeds = 0; + + // the number of peers this torrent currently is connected to. Peer + // connections that are in the half-open state (is attempting to connect) + // or are queued for later connection attempt do not count. Although they + // are visible in the peer list when you call get_peer_info(). + int num_peers = 0; + + // if the tracker sends scrape info in its announce reply, these fields + // will be set to the total number of peers that have the whole file and + // the total number of peers that are still downloading. set to -1 if the + // tracker did not send any scrape data in its announce reply. + int num_complete = -1; + int num_incomplete = -1; + + // the number of seeds in our peer list and the total number of peers + // (including seeds). We are not necessarily connected to all the peers + // in our peer list. This is the number of peers we know of in total, + // including banned peers and peers that we have failed to connect to. + int list_seeds = 0; + int list_peers = 0; + + // the number of peers in this torrent's peer list that is a candidate to + // be connected to. i.e. It has fewer connect attempts than the max fail + // count, it is not a seed if we are a seed, it is not banned etc. If + // this is 0, it means we don't know of any more peers that we can try. + int connect_candidates = 0; + + // the number of pieces that has been downloaded. It is equivalent to: + // ``std::accumulate(pieces->begin(), pieces->end())``. So you don't have + // to count yourself. This can be used to see if anything has updated + // since last time if you want to keep a graph of the pieces up to date. + int num_pieces = 0; + + // the number of distributed copies of the torrent. Note that one copy + // may be spread out among many peers. It tells how many copies there are + // currently of the rarest piece(s) among the peers this client is + // connected to. + int distributed_full_copies = 0; + + // tells the share of pieces that have more copies than the rarest + // piece(s). Divide this number by 1000 to get the fraction. + // + // For example, if ``distributed_full_copies`` is 2 and + // ``distributed_fraction`` is 500, it means that the rarest pieces have + // only 2 copies among the peers this torrent is connected to, and that + // 50% of all the pieces have more than two copies. + // + // If we are a seed, the piece picker is deallocated as an optimization, + // and piece availability is no longer tracked. In this case the + // distributed copies members are set to -1. + int distributed_fraction = 0; + + // the number of distributed copies of the file. note that one copy may + // be spread out among many peers. This is a floating point + // representation of the distributed copies. + // + // the integer part tells how many copies + // there are of the rarest piece(s) + // + // the fractional part tells the fraction of pieces that + // have more copies than the rarest piece(s). + float distributed_copies = 0.f; + + // the size of a block, in bytes. A block is a sub piece, it is the + // number of bytes that each piece request asks for and the number of + // bytes that each bit in the ``partial_piece_info``'s bitset represents, + // see get_download_queue(). This is typically 16 kB, but it may be + // smaller, if the pieces are smaller. + int block_size = 0; + + // the number of unchoked peers in this torrent. + int num_uploads = 0; + + // the number of peer connections this torrent has, including half-open + // connections that hasn't completed the bittorrent handshake yet. This + // is always >= ``num_peers``. + int num_connections = 0; + + // the set limit of upload slots (unchoked peers) for this torrent. + int uploads_limit = 0; + + // the set limit of number of connections for this torrent. + int connections_limit = 0; + + // the number of peers in this torrent that are waiting for more + // bandwidth quota from the torrent rate limiter. This can determine if + // the rate you get from this torrent is bound by the torrents limit or + // not. If there is no limit set on this torrent, the peers might still + // be waiting for bandwidth quota from the global limiter, but then they + // are counted in the ``session_status`` object. + int up_bandwidth_queue = 0; + int down_bandwidth_queue = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // use last_upload, last_download or + // seeding_duration, finished_duration and active_duration + // instead + + // the number of seconds since any peer last uploaded from this torrent + // and the last time a downloaded piece passed the hash check, + // respectively. Note, when starting up a torrent that needs its files + // checked, piece may pass and that will be considered downloading for the + // purpose of this counter. -1 means there either hasn't been any + // uploading/downloading, or it was too long ago for libtorrent to + // remember (currently forgetting happens after about 18 hours) + int TORRENT_DEPRECATED_MEMBER time_since_upload = 0; + int TORRENT_DEPRECATED_MEMBER time_since_download = 0; + + // These keep track of the number of seconds this torrent has been active + // (not paused) and the number of seconds it has been active while being + // finished and active while being a seed. ``seeding_time`` should be <= + // ``finished_time`` which should be <= ``active_time``. They are all + // saved in and restored from resume data, to keep totals across + // sessions. + int TORRENT_DEPRECATED_MEMBER active_time = 0; + int TORRENT_DEPRECATED_MEMBER finished_time = 0; + int TORRENT_DEPRECATED_MEMBER seeding_time = 0; +#endif + + // A rank of how important it is to seed the torrent, it is used to + // determine which torrents to seed and which to queue. It is based on + // the peer to seed ratio from the tracker scrape. For more information, + // see queuing_. Higher value means more important to seed + int seed_rank = 0; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + + // the number of seconds since this torrent acquired scrape data. + // If it has never done that, this value is -1. + int TORRENT_DEPRECATED_MEMBER last_scrape = 0; + + // the priority of this torrent + int TORRENT_DEPRECATED_MEMBER priority = 0; +#endif + + // the main state the torrent is in. See torrent_status::state_t. + state_t state = checking_resume_data; + + // true if this torrent has unsaved changes + // to its download state and statistics since the last resume data + // was saved. + bool need_save_resume = false; + +#if TORRENT_ABI_VERSION == 1 + // true if the session global IP filter applies + // to this torrent. This defaults to true. + bool TORRENT_DEPRECATED_MEMBER ip_filter_applies = false; + + // true if the torrent is blocked from downloading. This typically + // happens when a disk write operation fails. If the torrent is + // auto-managed, it will periodically be taken out of this state, in the + // hope that the disk condition (be it disk full or permission errors) + // has been resolved. If the torrent is not auto-managed, you have to + // explicitly take it out of the upload mode by calling set_upload_mode() + // on the torrent_handle. + bool TORRENT_DEPRECATED_MEMBER upload_mode = false; + + // true if the torrent is currently in share-mode, i.e. not downloading + // the torrent, but just helping the swarm out. + bool TORRENT_DEPRECATED_MEMBER share_mode = false; + + // true if the torrent is in super seeding mode + bool TORRENT_DEPRECATED_MEMBER super_seeding = false; + + // set to true if the torrent is paused and false otherwise. It's only + // true if the torrent itself is paused. If the torrent is not running + // because the session is paused, this is still false. To know if a + // torrent is active or not, you need to inspect both + // ``torrent_status::paused`` and ``session::is_paused()``. + bool TORRENT_DEPRECATED_MEMBER paused = false; + + // set to true if the torrent is auto managed, i.e. libtorrent is + // responsible for determining whether it should be started or queued. + // For more info see queuing_ + bool TORRENT_DEPRECATED_MEMBER auto_managed = false; + + // true when the torrent is in sequential download mode. In this mode + // pieces are downloaded in order rather than rarest first. + bool TORRENT_DEPRECATED_MEMBER sequential_download = false; +#endif + + // true if all pieces have been downloaded. + bool is_seeding = false; + + // true if all pieces that have a priority > 0 are downloaded. There is + // only a distinction between finished and seeding if some pieces or + // files have been set to priority 0, i.e. are not downloaded. + bool is_finished = false; + + // true if this torrent has metadata (either it was started from a + // .torrent file or the metadata has been downloaded). The only scenario + // where this can be false is when the torrent was started torrent-less + // (i.e. with just an info-hash and tracker ip, a magnet link for + // instance). + bool has_metadata = false; + + // true if there has ever been an incoming connection attempt to this + // torrent. + bool has_incoming = false; + +#if TORRENT_ABI_VERSION == 1 + // true if the torrent is in seed_mode. If the torrent was started in + // seed mode, it will leave seed mode once all pieces have been checked + // or as soon as one piece fails the hash check. + bool TORRENT_DEPRECATED_MEMBER seed_mode = false; +#endif + + // this is true if this torrent's storage is currently being moved from + // one location to another. This may potentially be a long operation + // if a large file ends up being copied from one drive to another. + bool moving_storage = false; + +#if TORRENT_ABI_VERSION == 1 + // true if this torrent is loaded into RAM. A torrent can be started + // and still not loaded into RAM, in case it has not had any peers interested in it + // yet. Torrents are loaded on demand. + bool TORRENT_DEPRECATED_MEMBER is_loaded = false; +#endif + + // these are set to true if this torrent is allowed to announce to the + // respective peer source. Whether they are true or false is determined by + // the queue logic/auto manager. Torrents that are not auto managed will + // always be allowed to announce to all peer sources. + bool announcing_to_trackers = false; + bool announcing_to_lsd = false; + bool announcing_to_dht = false; + +#if TORRENT_ABI_VERSION == 1 + // this reflects whether the ``stop_when_ready`` flag is currently enabled + // on this torrent. For more information, see + // torrent_handle::stop_when_ready(). + bool TORRENT_DEPRECATED_MEMBER stop_when_ready = false; +#endif + + // the info-hash for this torrent + sha1_hash info_hash; + + // the timestamps of the last time this torrent uploaded or downloaded + // payload to any peer. + time_point last_upload; + time_point last_download; + + // these are cumulative counters of for how long the torrent has been in + // different states. active means not paused and added to session. Whether + // it has found any peers or not is not relevant. + // finished means all selected files/pieces were downloaded and available + // to other peers (this is always a subset of active time). + // seeding means all files/pieces were downloaded and available to + // peers. Being available to peers does not imply there are other peers + // asking for the payload. + seconds active_duration; + seconds finished_duration; + seconds seeding_duration; + + // reflects several of the torrent's flags. For more + // information, see ``torrent_handle::flags()``. + torrent_flags_t flags{}; + }; + +TORRENT_VERSION_NAMESPACE_2_END +} + +namespace std +{ + template <> + struct hash + { + std::size_t operator()(libtorrent::torrent_status const& ts) const + { + return libtorrent::hash_value(ts.handle); + } + }; +} + +#endif // TORRENT_TORRENT_STATUS_HPP_INCLUDED diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp new file mode 100644 index 0000000..a261bc9 --- /dev/null +++ b/include/libtorrent/tracker_manager.hpp @@ -0,0 +1,429 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_TRACKER_MANAGER_HPP_INCLUDED +#define TORRENT_TRACKER_MANAGER_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TORRENT_USE_OPENSSL +// there is no forward declaration header for asio +namespace boost { +namespace asio { +namespace ssl { + class context; +} +} +} +#endif + +#include "libtorrent/socket.hpp" +#include "libtorrent/fwd.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/peer.hpp" // peer_entry +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +namespace libtorrent { + + class tracker_manager; + struct timeout_handler; + class udp_tracker_connection; + class http_tracker_connection; + struct resolver_interface; + struct counters; +#if TORRENT_USE_I2P + class i2p_connection; +#endif + namespace aux { struct session_logger; struct session_settings; } + + struct TORRENT_EXTRA_EXPORT tracker_request + { + tracker_request() + : downloaded(-1) + , uploaded(-1) + , left(-1) + , corrupt(0) + , redundant(0) + , listen_port(0) + , event(none) + , kind(announce_request) + , key(0) + , num_want(0) + , private_torrent(false) + , triggered_manually(false) + {} + + enum event_t + { + none, + completed, + started, + stopped, + paused + }; + + enum kind_t + { + // do not compare against announce_request ! check if not scrape instead + announce_request = 0, + scrape_request = 1, + // affects interpretation of peers string in HTTP response + // see parse_tracker_response() + i2p = 2 + }; + + std::string url; + std::string trackerid; +#if TORRENT_ABI_VERSION == 1 + std::string auth; +#endif + + std::shared_ptr filter; + + std::int64_t downloaded; + std::int64_t uploaded; + std::int64_t left; + std::int64_t corrupt; + std::int64_t redundant; + std::uint16_t listen_port; + + // values from event_t + std::uint8_t event; + + // values from kind_t + std::uint8_t kind; + + std::uint32_t key; + int num_want; + std::vector ipv6; + std::vector ipv4; + sha1_hash info_hash; + peer_id pid; + + aux::listen_socket_handle outgoing_socket; + + // set to true if the .torrent file this tracker announce is for is marked + // as private (i.e. has the "priv": 1 key) + bool private_torrent; + + // this is set to true if this request was triggered by a "manual" call to + // scrape_tracker() or force_reannounce() + bool triggered_manually; + +#ifdef TORRENT_USE_OPENSSL + boost::asio::ssl::context* ssl_ctx = nullptr; +#endif +#if TORRENT_USE_I2P + i2p_connection* i2pconn = nullptr; +#endif + }; + + struct tracker_response + { + tracker_response() + : interval(1800) + , min_interval(1) + , complete(-1) + , incomplete(-1) + , downloaders(-1) + , downloaded(-1) + {} + + // peers from the tracker, in various forms + std::vector peers; + std::vector peers4; + std::vector peers6; + // our external IP address (if the tracker responded with ti, otherwise + // INADDR_ANY) + address external_ip; + + // the tracker id, if it was included in the response, otherwise + // an empty string + std::string trackerid; + + // if the tracker returned an error, this is set to that error + std::string failure_reason; + + // contains a warning message from the tracker, if included in + // the response + std::string warning_message; + + // re-announce interval, in seconds + seconds32 interval; + + // the lowest force-announce interval + seconds32 min_interval; + + // the number of seeds in the swarm + int complete; + + // the number of downloaders in the swarm + int incomplete; + + // if supported by the tracker, the number of actively downloading peers. + // i.e. partial seeds. If not supported, -1 + int downloaders; + + // the number of times the torrent has been downloaded + int downloaded; + }; + + struct TORRENT_EXTRA_EXPORT request_callback + { + friend class tracker_manager; + request_callback() {} + virtual ~request_callback() {} + virtual void tracker_warning(tracker_request const& req + , std::string const& msg) = 0; + virtual void tracker_scrape_response(tracker_request const& /*req*/ + , int /*complete*/, int /*incomplete*/, int /*downloads*/ + , int /*downloaders*/) {} + virtual void tracker_response( + tracker_request const& req + , address const& tracker_ip + , std::list
    const& ip_list + , struct tracker_response const& response) = 0; + virtual void tracker_request_error( + tracker_request const& req + , error_code const& ec + , const std::string& msg + , seconds32 retry_interval) = 0; + +#ifndef TORRENT_DISABLE_LOGGING + virtual bool should_log() const = 0; + virtual void debug_log(const char* fmt, ...) const noexcept TORRENT_FORMAT(2,3) = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT timeout_handler + : std::enable_shared_from_this + { + explicit timeout_handler(io_service& str); + + timeout_handler(timeout_handler const&) = delete; + timeout_handler& operator=(timeout_handler const&) = delete; + + void set_timeout(int completion_timeout, int read_timeout); + void restart_read_timeout(); + void cancel(); + bool cancelled() const { return m_abort; } + + virtual void on_timeout(error_code const& ec) = 0; + virtual ~timeout_handler(); + + io_service& get_io_service() { return lt::get_io_service(m_timeout); } + + private: + + void timeout_callback(error_code const&); + + int m_completion_timeout = 0; + + // used for timeouts + // this is set when the request has been sent + time_point m_start_time; + + // this is set every time something is received + time_point m_read_time; + + // the asio async operation + deadline_timer m_timeout; + + int m_read_timeout = 0; + + bool m_abort = false; +#if TORRENT_USE_ASSERTS + int m_outstanding_timer_wait = 0; +#endif + }; + + struct TORRENT_EXTRA_EXPORT tracker_connection + : timeout_handler + { + tracker_connection(tracker_manager& man + , tracker_request const& req + , io_service& ios + , std::weak_ptr r); + + std::shared_ptr requester() const; + ~tracker_connection() override {} + + tracker_request const& tracker_req() const { return m_req; } + + void fail(error_code const& ec, char const* msg = "" + , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); + virtual void start() = 0; + virtual void close() = 0; + address bind_interface() const; + aux::listen_socket_handle const& bind_socket() const { return m_req.outgoing_socket; } + void sent_bytes(int bytes); + void received_bytes(int bytes); + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + timeout_handler::shared_from_this()); + } + + private: + + const tracker_request m_req; + + protected: + + void fail_impl(error_code const& ec, std::string msg = std::string() + , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); + + std::weak_ptr m_requester; + + tracker_manager& m_man; + }; + + class TORRENT_EXTRA_EXPORT tracker_manager final + : single_threaded + { + public: + + using send_fun_t = std::function + , error_code&, udp_send_flags_t)>; + using send_fun_hostname_t = std::function + , error_code&, udp_send_flags_t)>; + + tracker_manager(send_fun_t const& send_fun + , send_fun_hostname_t const& send_fun_hostname + , counters& stats_counters + , resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ); + + ~tracker_manager(); + + tracker_manager(tracker_manager const&) = delete; + tracker_manager& operator=(tracker_manager const&) = delete; + + void queue_request( + io_service& ios + , tracker_request&& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()); + void queue_request( + io_service& ios + , tracker_request const& r + , aux::session_settings const& sett + , std::weak_ptr c + = std::weak_ptr()) = delete; + void abort_all_requests(bool all = false); + + void remove_request(http_tracker_connection const* c); + void remove_request(udp_tracker_connection const* c); + bool empty() const; + int num_requests() const; + + void sent_bytes(int bytes); + void received_bytes(int bytes); + + void incoming_error(error_code const& ec, udp::endpoint const& ep); + bool incoming_packet(udp::endpoint const& ep, span buf); + + // this is only used for SOCKS packets, since + // they may be addressed to hostname + // TODO: 3 make sure the udp_socket supports passing on string-hostnames + // too, and that this function is used + bool incoming_packet(char const* hostname, span buf); + + void update_transaction_id( + std::shared_ptr c + , std::uint32_t tid); + + aux::session_settings const& settings() const { return m_settings; } + resolver_interface& host_resolver() { return m_host_resolver; } + + void send_hostname(aux::listen_socket_handle const& sock + , char const* hostname, int port, span p + , error_code& ec, udp_send_flags_t flags = {}); + + void send(aux::listen_socket_handle const& sock + , udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t flags = {}); + + private: + + // maps transactionid to the udp_tracker_connection + // These must use shared_ptr to avoid a dangling reference + // if a connection is erased while a timeout event is in the queue + std::unordered_map> m_udp_conns; + + std::vector> m_http_conns; + std::deque> m_queued; + + send_fun_t m_send_fun; + send_fun_hostname_t m_send_fun_hostname; + resolver_interface& m_host_resolver; + aux::session_settings const& m_settings; + counters& m_stats_counters; + bool m_abort = false; +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + aux::session_logger& m_ses; +#endif + }; +} + +#endif // TORRENT_TRACKER_MANAGER_HPP_INCLUDED diff --git a/include/libtorrent/udp_socket.hpp b/include/libtorrent/udp_socket.hpp new file mode 100644 index 0000000..b6d7506 --- /dev/null +++ b/include/libtorrent/udp_socket.hpp @@ -0,0 +1,172 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_SOCKET_HPP_INCLUDED +#define TORRENT_UDP_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/flags.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" + +#include +#include + +namespace libtorrent { + + class alert_manager; + struct socks5; + + using udp_send_flags_t = flags::bitfield_flag; + + class TORRENT_EXTRA_EXPORT udp_socket : single_threaded + { + public: + explicit udp_socket(io_service& ios, aux::listen_socket_handle ls); + + static constexpr udp_send_flags_t peer_connection = 0_bit; + static constexpr udp_send_flags_t tracker_connection = 1_bit; + static constexpr udp_send_flags_t dont_queue = 2_bit; + static constexpr udp_send_flags_t dont_fragment = 3_bit; + + bool is_open() const { return m_abort == false; } + io_service& get_io_service() { return lt::get_io_service(m_socket); } + + template + void async_read(Handler&& h) + { + m_socket.async_receive(null_buffers(), std::forward(h)); + } + + template + void async_write(Handler&& h) + { + m_socket.async_send(null_buffers(), std::forward(h)); + } + + struct packet + { + span data; + udp::endpoint from; + error_code error; + }; + + int read(span pkts, error_code& ec); + + // this is only valid when using a socks5 proxy + void send_hostname(char const* hostname, int port, span p + , error_code& ec, udp_send_flags_t flags = {}); + + void send(udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t flags = {}); + void open(udp const& protocol, error_code& ec); + void bind(udp::endpoint const& ep, error_code& ec); + void close(); + int local_port() const { return m_bind_port; } + + void set_proxy_settings(aux::proxy_settings const& ps, alert_manager& alerts); + aux::proxy_settings const& get_proxy_settings() { return m_proxy_settings; } + + bool is_closed() const { return m_abort; } + udp::endpoint local_endpoint(error_code& ec) const + { return m_socket.local_endpoint(ec); } + // best effort, if you want to know the error, use + // ``local_endpoint(error_code& ec)`` + udp::endpoint local_endpoint() const + { + error_code ec; + return local_endpoint(ec); + } + + using receive_buffer_size = udp::socket::receive_buffer_size; + using send_buffer_size = udp::socket::send_buffer_size; + + template + void get_option(SocketOption const& opt, error_code& ec) + { + m_socket.get_option(opt, ec); + } + + template + void set_option(SocketOption const& opt, error_code& ec) + { + m_socket.set_option(opt, ec); + } + +#ifdef TCP_NOTSENT_LOWAT + void set_option(tcp_notsent_lowat const&, error_code&) {} +#endif + + template + void get_option(SocketOption& opt, error_code& ec) + { + m_socket.get_option(opt, ec); + } + + bool active_socks5() const; + + private: + + // non-copyable + udp_socket(udp_socket const&); + udp_socket& operator=(udp_socket const&); + + void wrap(udp::endpoint const& ep, span p, error_code& ec, udp_send_flags_t flags); + void wrap(char const* hostname, int port, span p, error_code& ec, udp_send_flags_t flags); + bool unwrap(udp::endpoint& from, span& buf); + + udp::socket m_socket; + + using receive_buffer = std::array; + std::unique_ptr m_buf; + aux::listen_socket_handle m_listen_socket; + + std::uint16_t m_bind_port; + + aux::proxy_settings m_proxy_settings; + + std::shared_ptr m_socks5_connection; + + bool m_abort:1; + +#if TORRENT_USE_ASSERTS + bool m_started; + int m_magic; +#endif + }; +} + +#endif diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp new file mode 100644 index 0000000..99c337d --- /dev/null +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED +#define TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT udp_tracker_connection: public tracker_connection + { + friend class tracker_manager; + public: + + udp_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , std::weak_ptr c); + + void start() override; + void close() override; + + std::uint32_t transaction_id() const { return m_transaction_id; } + + private: + + enum class action_t : std::uint8_t + { + connect, + announce, + scrape, + error + }; + + std::shared_ptr shared_from_this() + { + return std::static_pointer_cast( + tracker_connection::shared_from_this()); + } + + void update_transaction_id(); + + void name_lookup(error_code const& error + , std::vector
    const& addresses, int port); + void start_announce(); + + bool on_receive(udp::endpoint const& ep, span buf); + bool on_receive_hostname(char const* hostname, span buf); + bool on_connect_response(span buf); + bool on_announce_response(span buf); + bool on_scrape_response(span buf); + + // wraps tracker_connection::fail + void fail(error_code const& ec + , char const* msg = "" + , seconds32 interval = seconds32(0) + , seconds32 min_interval = seconds32(30)); + + void send_udp_connect(); + void send_udp_announce(); + void send_udp_scrape(); + + void on_timeout(error_code const& ec) override; + + std::string m_hostname; + std::vector m_endpoints; + + struct connection_cache_entry + { + std::int64_t connection_id; + time_point expires; + }; + + static std::map m_connection_cache; + static std::mutex m_cache_mutex; + + udp::endpoint m_target; + + std::uint32_t m_transaction_id; + int m_attempts; + + action_t m_state; + + bool m_abort; + }; + +} + +#endif // TORRENT_UDP_TRACKER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/union_endpoint.hpp b/include/libtorrent/union_endpoint.hpp new file mode 100644 index 0000000..a489a8c --- /dev/null +++ b/include/libtorrent/union_endpoint.hpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNION_ENDPOINT_HPP_INCLUDED +#define TORRENT_UNION_ENDPOINT_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { + + struct union_address + { + union_address() { *this = address(); } + explicit union_address(address const& a) { *this = a; } + union_address& operator=(address const& a) & + { + v4 = a.is_v4(); + if (v4) + addr.v4 = a.to_v4().to_bytes(); + else + addr.v6 = a.to_v6().to_bytes(); + return *this; + } + + bool operator==(union_address const& rh) const + { + if (v4 != rh.v4) return false; + if (v4) + return addr.v4 == rh.addr.v4; + else + return addr.v6 == rh.addr.v6; + } + + bool operator!=(union_address const& rh) const + { + return !(*this == rh); + } + + operator address() const + { + if (v4) return address(address_v4(addr.v4)); + else return address(address_v6(addr.v6)); + } + + union addr_t + { + address_v4::bytes_type v4; + address_v6::bytes_type v6; + }; + addr_t addr; + bool v4:1; + }; + + struct union_endpoint + { + explicit union_endpoint(tcp::endpoint const& ep) { *this = ep; } + explicit union_endpoint(udp::endpoint const& ep) { *this = ep; } + union_endpoint() { *this = tcp::endpoint(); } + + union_endpoint& operator=(udp::endpoint const& ep) & + { + addr = ep.address(); + port = ep.port(); + return *this; + } + + operator udp::endpoint() const { return udp::endpoint(addr, port); } + + union_endpoint& operator=(tcp::endpoint const& ep) & + { + addr = ep.address(); + port = ep.port(); + return *this; + } + + libtorrent::address address() const { return addr; } + operator tcp::endpoint() const { return tcp::endpoint(addr, port); } + + union_address addr; + std::uint16_t port; + }; +} + +#endif diff --git a/include/libtorrent/units.hpp b/include/libtorrent/units.hpp new file mode 100644 index 0000000..d6a1848 --- /dev/null +++ b/include/libtorrent/units.hpp @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UNITS_HPP +#define TORRENT_UNITS_HPP + +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" + +namespace libtorrent { namespace aux { + template + struct difference_tag; + + template::value>::type> + struct strong_typedef + { + using underlying_type = UnderlyingType; + using diff_type = strong_typedef>; + + constexpr strong_typedef(strong_typedef const& rhs) noexcept = default; + constexpr strong_typedef(strong_typedef&& rhs) noexcept = default; + strong_typedef() noexcept = default; +#if TORRENT_ABI_VERSION == 1 + constexpr strong_typedef(UnderlyingType val) : m_val(val) {} + constexpr operator UnderlyingType() const { return m_val; } +#else + constexpr explicit strong_typedef(UnderlyingType val) : m_val(val) {} + constexpr explicit operator UnderlyingType() const { return m_val; } + constexpr bool operator==(strong_typedef const& rhs) const { return m_val == rhs.m_val; } + constexpr bool operator!=(strong_typedef const& rhs) const { return m_val != rhs.m_val; } + constexpr bool operator<(strong_typedef const& rhs) const { return m_val < rhs.m_val; } + constexpr bool operator>(strong_typedef const& rhs) const { return m_val > rhs.m_val; } + constexpr bool operator>=(strong_typedef const& rhs) const { return m_val >= rhs.m_val; } + constexpr bool operator<=(strong_typedef const& rhs) const { return m_val <= rhs.m_val; } +#endif + strong_typedef& operator++() { ++m_val; return *this; } + strong_typedef& operator--() { --m_val; return *this; } + + strong_typedef operator++(int) & { return strong_typedef{m_val++}; } + strong_typedef operator--(int) & { return strong_typedef{m_val--}; } + + friend diff_type operator-(strong_typedef lhs, strong_typedef rhs) + { return diff_type{lhs.m_val - rhs.m_val}; } + friend strong_typedef operator+(strong_typedef lhs, diff_type rhs) + { return strong_typedef{lhs.m_val + static_cast(rhs)}; } + friend strong_typedef operator+(diff_type lhs, strong_typedef rhs) + { return strong_typedef{static_cast(lhs) + rhs.m_val}; } + friend strong_typedef operator-(strong_typedef lhs, diff_type rhs) + { return strong_typedef{lhs.m_val - static_cast(rhs)}; } + + strong_typedef& operator+=(diff_type rhs) & + { m_val += static_cast(rhs); return *this; } + strong_typedef& operator-=(diff_type rhs) & + { m_val -= static_cast(rhs); return *this; } + + strong_typedef& operator=(strong_typedef const& rhs) & noexcept = default; + strong_typedef& operator=(strong_typedef&& rhs) & noexcept = default; + private: + UnderlyingType m_val; + }; + + // meta function to return the underlying type of a strong_typedef or enumeration + // , or the type itself if it isn't a strong_typedef + template + struct underlying_index_t { using type = T; }; + + template + struct underlying_index_t::value>::type> + { using type = typename std::underlying_type::type; }; + + template + struct underlying_index_t> { using type = U; }; + + struct piece_index_tag; + struct file_index_tag; + + template + std::string to_string(strong_typedef const t) + { return std::to_string(static_cast(t)); } + + template + strong_typedef next(strong_typedef v) + { return ++v;} + + template + strong_typedef prev(strong_typedef v) + { return --v;} + +#if TORRENT_USE_IOSTREAM + template + std::ostream& operator<<(std::ostream& os, strong_typedef val) + { return os << static_cast(val); } +#endif + +} // namespace libtorrent::aux + + // this type represents a piece index in a torrent. + using piece_index_t = aux::strong_typedef; + + // this type represents an index to a file in a torrent + using file_index_t = aux::strong_typedef; + +} // namespace libtorrent + +namespace std { + + template + class numeric_limits> : public std::numeric_limits + { + using type = libtorrent::aux::strong_typedef; + public: + + static constexpr type (min)() + { return type((std::numeric_limits::min)()); } + + static constexpr type (max)() + { return type((std::numeric_limits::max)()); } + }; + + template + struct hash> : std::hash + { + using base = std::hash; + using argument_type = libtorrent::aux::strong_typedef; +#if __cplusplus < 201402 + // this was deprecated in C++17 + using result_type = typename base::result_type; +#else + using result_type = std::size_t; +#endif + result_type operator()(argument_type const& s) const + { return this->base::operator()(static_cast(s)); } + }; +} + +#endif diff --git a/include/libtorrent/upnp.hpp b/include/libtorrent/upnp.hpp new file mode 100644 index 0000000..9a74864 --- /dev/null +++ b/include/libtorrent/upnp.hpp @@ -0,0 +1,378 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UPNP_HPP +#define TORRENT_UPNP_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/portmap.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/openssl.hpp" // for ssl::context + +#include +#include +#include + +namespace libtorrent { + struct http_connection; + class http_parser; + + namespace upnp_errors + { + // error codes for the upnp_error_category. They hold error codes + // returned by UPnP routers when mapping ports + enum error_code_enum + { + // No error + no_error = 0, + // One of the arguments in the request is invalid + invalid_argument = 402, + // The request failed + action_failed = 501, + // The specified value does not exist in the array + value_not_in_array = 714, + // The source IP address cannot be wild-carded, but + // must be fully specified + source_ip_cannot_be_wildcarded = 715, + // The external port cannot be a wildcard, but must + // be specified + external_port_cannot_be_wildcarded = 716, + // The port mapping entry specified conflicts with a + // mapping assigned previously to another client + port_mapping_conflict = 718, + // Internal and external port value must be the same + internal_port_must_match_external = 724, + // The NAT implementation only supports permanent + // lease times on port mappings + only_permanent_leases_supported = 725, + // RemoteHost must be a wildcard and cannot be a + // specific IP address or DNS name + remote_host_must_be_wildcard = 726, + // ExternalPort must be a wildcard and cannot be a + // specific port + external_port_must_be_wildcard = 727 + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + } + + // the boost.system error category for UPnP errors + TORRENT_EXPORT boost::system::error_category& upnp_category(); + +#if TORRENT_ABI_VERSION == 1 + TORRENT_DEPRECATED + inline boost::system::error_category& get_upnp_category() + { return upnp_category(); } +#endif + +struct parse_state +{ + bool in_service = false; + std::vector tag_stack; + std::string control_url; + std::string service_type; + std::string model; + std::string url_base; + bool top_tags(string_view str1, string_view str2) + { + auto i = tag_stack.rbegin(); + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(*i, str2)) return false; + ++i; + if (i == tag_stack.rend()) return false; + if (!string_equal_no_case(*i, str1)) return false; + return true; + } +}; + +struct error_code_parse_state +{ + bool in_error_code = false; + bool exit = false; + int error_code = -1; +}; + +struct ip_address_parse_state: error_code_parse_state +{ + bool in_ip_address = false; + std::string ip_address; +}; + +TORRENT_EXTRA_EXPORT void find_control_url(int type, string_view, parse_state& state); + +TORRENT_EXTRA_EXPORT void find_error_code(int type, string_view string + , error_code_parse_state& state); + +TORRENT_EXTRA_EXPORT void find_ip_address(int type, string_view string + , ip_address_parse_state& state); + +// TODO: support using the windows API for UPnP operations as well +struct TORRENT_EXTRA_EXPORT upnp final + : std::enable_shared_from_this + , single_threaded +{ + upnp(io_service& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 const& listen_address + , address_v4 const& netmask + , std::string listen_device); + ~upnp(); + + void start(); + + // Attempts to add a port mapping for the specified protocol. Valid protocols are + // ``upnp::tcp`` and ``upnp::udp`` for the UPnP class and ``natpmp::tcp`` and + // ``natpmp::udp`` for the NAT-PMP class. + // + // ``external_port`` is the port on the external address that will be mapped. This + // is a hint, you are not guaranteed that this port will be available, and it may + // end up being something else. In the portmap_alert_ notification, the actual + // external port is reported. + // + // ``local_port`` is the port in the local machine that the mapping should forward + // to. + // + // The return value is an index that identifies this port mapping. This is used + // to refer to mappings that fails or succeeds in the portmap_error_alert_ and + // portmap_alert_ respectively. If The mapping fails immediately, the return value + // is -1, which means failure. There will not be any error alert notification for + // mappings that fail with a -1 return value. + port_mapping_t add_mapping(portmap_protocol p, int external_port, tcp::endpoint local_ep); + + // This function removes a port mapping. ``mapping_index`` is the index that refers + // to the mapping you want to remove, which was returned from add_mapping(). + void delete_mapping(port_mapping_t mapping_index); + + bool get_mapping(port_mapping_t mapping_index, tcp::endpoint& local_ep, int& external_port + , portmap_protocol& protocol) const; + + void close(); + + // This is only available for UPnP routers. If the model is advertised by + // the router, it can be queried through this function. + std::string router_model() + { + TORRENT_ASSERT(is_single_thread()); + return m_model; + } + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void open_multicast_socket(udp::socket& s, error_code& ec); + void open_unicast_socket(udp::socket& s, error_code& ec); + + void map_timer(error_code const& ec); + void try_map_upnp(); + void discover_device_impl(); + + void resend_request(error_code const& e); + void on_reply(udp::socket& s, error_code const& ec); + + struct rootdevice; + void next(rootdevice& d, port_mapping_t i); + void update_map(rootdevice& d, port_mapping_t i); + + int lease_duration(rootdevice const& d) const; + + void connect(rootdevice& d); + + void on_upnp_xml(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c); + void on_upnp_get_ip_address_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c); + void on_upnp_map_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t mapping, http_connection& c); + void on_upnp_unmap_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t mapping, http_connection& c); + void on_expire(error_code const& e); + + void disable(error_code const& ec); + void return_error(port_mapping_t mapping, int code); +#ifndef TORRENT_DISABLE_LOGGING + bool should_log() const; + void log(char const* msg, ...) const TORRENT_FORMAT(2,3); +#endif + + void get_ip_address(rootdevice& d); + void delete_port_mapping(rootdevice& d, port_mapping_t i); + void create_port_mapping(http_connection& c, rootdevice& d, port_mapping_t i); + void post(upnp::rootdevice const& d, char const* soap + , char const* soap_action); + + int num_mappings() const { return int(m_mappings.size()); } + + struct global_mapping_t + { + portmap_protocol protocol = portmap_protocol::none; + int external_port = 0; + tcp::endpoint local_ep; + }; + + struct mapping_t : aux::base_mapping + { + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + tcp::endpoint local_ep; + + // the number of times this mapping has failed + int failcount = 0; + }; + + struct rootdevice + { + rootdevice(); + ~rootdevice(); + rootdevice(rootdevice const&); + rootdevice& operator=(rootdevice const&); + rootdevice(rootdevice&&); + rootdevice& operator=(rootdevice&&); + + // the interface url, through which the list of + // supported interfaces are fetched + std::string url; + + // the url to the WANIP or WANPPP interface + std::string control_url; + // either the WANIP namespace or the WANPPP namespace + std::string service_namespace; + + aux::vector mapping; + + // this is the hostname, port and path + // component of the url or the control_url + // if it has been found + std::string hostname; + int port = 0; + std::string path; + address external_ip; + + // set to false if the router doesn't support lease durations + bool use_lease_duration = true; + + // true if the device supports specifying a + // specific external port, false if it doesn't + bool supports_specific_external = true; + + bool disabled = false; + + mutable std::shared_ptr upnp_connection; + +#if TORRENT_USE_ASSERTS + int magic = 1337; +#endif + + bool operator<(rootdevice const& rhs) const + { return url < rhs.url; } + }; + + struct upnp_state_t + { + aux::vector mappings; + std::set devices; + }; + + aux::vector m_mappings; + + aux::session_settings const& m_settings; + + // the set of devices we've found + std::set m_devices; + + aux::portmap_callback& m_callback; + + // current retry count + int m_retry_count = 0; + + io_service& m_io_service; + + resolver m_resolver; + + // the udp socket used to send and receive + // multicast messages on the network + udp::socket m_multicast_socket; + udp::socket m_unicast_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_broadcast_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + // this timer fires one second after the last UPnP response. This is the + // point where we assume we have received most or all SSDP responses. If we + // are ignoring non-routers and at this point we still haven't received a + // response from a router UPnP device, we override the ignoring behavior and + // map them anyway. + deadline_timer m_map_timer; + + bool m_disabled = false; + bool m_closing = false; + + std::string m_model; + + // the network this UPnP mapper is associated with. Don't talk to any other + // network + address_v4 m_listen_address; + address_v4 m_netmask; + std::string m_device; + +#ifdef TORRENT_USE_OPENSSL + ssl::context m_ssl_ctx; +#endif +}; + +} + +namespace boost { namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} } + +#endif diff --git a/include/libtorrent/utf8.hpp b/include/libtorrent/utf8.hpp new file mode 100644 index 0000000..c1a05cd --- /dev/null +++ b/include/libtorrent/utf8.hpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTF8_HPP_INCLUDED +#define TORRENT_UTF8_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +#include +#include + +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + TORRENT_EXTRA_EXPORT std::pair + parse_utf8_codepoint(string_view str); + + TORRENT_EXTRA_EXPORT void append_utf8_codepoint(std::string&, std::int32_t); + +} // namespace libtorrent + +#endif diff --git a/include/libtorrent/utp_socket_manager.hpp b/include/libtorrent/utp_socket_manager.hpp new file mode 100644 index 0000000..ba301f8 --- /dev/null +++ b/include/libtorrent/utp_socket_manager.hpp @@ -0,0 +1,195 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED +#define TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/packet_pool.hpp" + +namespace libtorrent { + + struct utp_stream; + struct utp_socket_impl; + struct counters; + + // interface/handle to the underlying udp socket + struct TORRENT_EXTRA_EXPORT utp_socket_interface + { + virtual udp::endpoint get_local_endpoint() = 0; + protected: + virtual ~utp_socket_interface() = default; + }; + + struct utp_socket_manager + { + using send_fun_t = std::function + , udp::endpoint const& + , span + , error_code&, udp_send_flags_t)>; + + using incoming_utp_callback_t = std::function const&)>; + + utp_socket_manager(send_fun_t const& send_fun + , incoming_utp_callback_t const& cb + , io_service& ios + , aux::session_settings const& sett + , counters& cnt, void* ssl_context); + ~utp_socket_manager(); + + // return false if this is not a uTP packet + bool incoming_packet(std::weak_ptr socket + , udp::endpoint const& ep, span p); + + // if the UDP socket failed with an EAGAIN or EWOULDBLOCK, this will be + // called once the socket is writeable again + void writable(); + + // when the upper layer has drained the underlying UDP socket, this is + // called, and uTP sockets will send their ACKs. This ensures ACKs at + // least coalesce packets returned during the same wakeup + void socket_drained(); + + void tick(time_point now); + + void send_packet(std::weak_ptr sock, udp::endpoint const& ep + , char const* p, int len + , error_code& ec, udp_send_flags_t flags = {}); + void subscribe_writable(utp_socket_impl* s); + + void remove_udp_socket(std::weak_ptr sock); + + // internal, used by utp_stream + void remove_socket(std::uint16_t id); + + utp_socket_impl* new_utp_socket(utp_stream* str); + int gain_factor() const { return m_sett.get_int(settings_pack::utp_gain_factor); } + int target_delay() const { return m_sett.get_int(settings_pack::utp_target_delay) * 1000; } + int syn_resends() const { return m_sett.get_int(settings_pack::utp_syn_resends); } + int fin_resends() const { return m_sett.get_int(settings_pack::utp_fin_resends); } + int num_resends() const { return m_sett.get_int(settings_pack::utp_num_resends); } + int connect_timeout() const { return m_sett.get_int(settings_pack::utp_connect_timeout); } + int min_timeout() const { return m_sett.get_int(settings_pack::utp_min_timeout); } + int loss_multiplier() const { return m_sett.get_int(settings_pack::utp_loss_multiplier); } + int cwnd_reduce_timer() const { return m_sett.get_int(settings_pack::utp_cwnd_reduce_timer); } + + std::pair mtu_for_dest(address const& addr); + int num_sockets() const { return int(m_utp_sockets.size()); } + + void defer_ack(utp_socket_impl* s); + void subscribe_drained(utp_socket_impl* s); + + void restrict_mtu(int const mtu) + { + m_restrict_mtu[std::size_t(m_mtu_idx)] = mtu; + m_mtu_idx = (m_mtu_idx + 1) % int(m_restrict_mtu.size()); + } + + int restrict_mtu() const + { + return *std::max_element(m_restrict_mtu.begin(), m_restrict_mtu.end()); + } + + // used to keep stats of uTP events + // the counter is the enum from ``counters``. + void inc_stats_counter(int counter, int delta = 1); + + packet_ptr acquire_packet(int const allocate) { return m_packet_pool.acquire(allocate); } + void release_packet(packet_ptr p) { m_packet_pool.release(std::move(p)); } + void decay() { m_packet_pool.decay(); } + + // explicitly disallow assignment, to silence msvc warning + utp_socket_manager& operator=(utp_socket_manager const&) = delete; + + private: + + send_fun_t m_send_fun; + incoming_utp_callback_t m_cb; + + // replace with a hash-map + using socket_map_t = std::multimap; + socket_map_t m_utp_sockets; + + using socket_vector_t = std::vector; + + // if this is set, it means this socket still needs to send an ACK. Once + // we exit the loop processing packets, or switch to processing packets + // for a different socket, issue the ACK packet and clear this. + utp_socket_impl* m_deferred_ack = nullptr; + + // storage used for saving cpu time on "push_back" + // by using already pre-allocated vector + socket_vector_t m_temp_sockets; + + // sockets that have received or sent packets this + // round, may subscribe to the event of draining the + // UDP socket. At that point they may call the + // user callback function to indicate bytes have been + // sent or received. + socket_vector_t m_drained_event; + + // list of sockets that received EWOULDBLOCK from the + // underlying socket. They are notified when the socket + // becomes writable again + socket_vector_t m_stalled_sockets; + + // the last socket we received a packet on + utp_socket_impl* m_last_socket = nullptr; + + int m_new_connection = -1; + + aux::session_settings const& m_sett; + + // stats counters + counters& m_counters; + + io_service& m_ios; + + std::array m_restrict_mtu; + int m_mtu_idx = 0; + + // this is passed on to the instantiate connection + // if this is non-nullptr it will create SSL connections over uTP + void* m_ssl_context; + + packet_pool m_packet_pool; + }; +} + +#endif diff --git a/include/libtorrent/utp_stream.hpp b/include/libtorrent/utp_stream.hpp new file mode 100644 index 0000000..5cac2e2 --- /dev/null +++ b/include/libtorrent/utp_stream.hpp @@ -0,0 +1,515 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_UTP_STREAM_HPP_INCLUDED +#define TORRENT_UTP_STREAM_HPP_INCLUDED + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/packet_buffer.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/close_reason.hpp" + +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { + +#ifndef TORRENT_UTP_LOG_ENABLE + #define TORRENT_UTP_LOG 0 + #define TORRENT_VERBOSE_UTP_LOG 0 +#else + #define TORRENT_UTP_LOG 1 + #define TORRENT_VERBOSE_UTP_LOG 1 +#endif + +#if TORRENT_UTP_LOG + TORRENT_FORMAT(1, 2) + void utp_log(char const* fmt, ...); + TORRENT_EXPORT bool is_utp_stream_logging(); + + // This function should be used at the very beginning and very end of your program. + TORRENT_EXPORT void set_utp_stream_logging(bool enable); +#endif + + TORRENT_EXTRA_EXPORT bool compare_less_wrap(std::uint32_t lhs + , std::uint32_t rhs, std::uint32_t mask); + + struct utp_socket_manager; + + // internal: the point of the bif_endian_int is two-fold + // one purpose is to not have any alignment requirements + // so that any buffer received from the network can be cast + // to it and read as an integer of various sizes without + // triggering a bus error. The other purpose is to convert + // from network byte order to host byte order when read and + // written, to offer a convenient interface to both interpreting + // and writing network packets + template struct big_endian_int + { + big_endian_int& operator=(T v) + { + char* p = m_storage; + detail::write_impl(v, p); + return *this; + } + operator T() const + { + const char* p = m_storage; + return detail::read_impl(p, detail::type()); + } + private: + char m_storage[sizeof(T)]; + }; + + using be_uint64 = big_endian_int; + using be_uint32 = big_endian_int; + using be_uint16 = big_endian_int; + using be_int64 = big_endian_int; + using be_int32 = big_endian_int; + using be_int16 = big_endian_int; + +/* + uTP header from BEP 29 + + 0 4 8 16 24 32 + +-------+-------+---------------+---------------+---------------+ + | type | ver | extension | connection_id | + +-------+-------+---------------+---------------+---------------+ + | timestamp_microseconds | + +---------------+---------------+---------------+---------------+ + | timestamp_difference_microseconds | + +---------------+---------------+---------------+---------------+ + | wnd_size | + +---------------+---------------+---------------+---------------+ + | seq_nr | ack_nr | + +---------------+---------------+---------------+---------------+ + +*/ + +// internal: the different kinds of uTP packets +enum utp_socket_state_t +{ ST_DATA, ST_FIN, ST_STATE, ST_RESET, ST_SYN, NUM_TYPES }; + +// internal: extension headers. 2 is skipped because there is a deprecated +// extension with that number in the wild +enum utp_extensions_t +{ utp_no_extension = 0, utp_sack = 1, utp_close_reason = 3 }; + +struct utp_header +{ + std::uint8_t type_ver; + std::uint8_t extension; + be_uint16 connection_id; + be_uint32 timestamp_microseconds; + be_uint32 timestamp_difference_microseconds; + be_uint32 wnd_size; + be_uint16 seq_nr; + be_uint16 ack_nr; + + int get_type() const { return type_ver >> 4; } + int get_version() const { return type_ver & 0xf; } +}; + +struct utp_socket_impl; +struct utp_socket_interface; + +utp_socket_impl* construct_utp_impl(std::uint16_t recv_id + , std::uint16_t send_id, void* userdata + , utp_socket_manager& sm); +void detach_utp_impl(utp_socket_impl* s); +void delete_utp_impl(utp_socket_impl* s); +void utp_abort(utp_socket_impl* s); +bool should_delete(utp_socket_impl* s); +bool bound_to_udp_socket(utp_socket_impl* s, std::weak_ptr sock); +void tick_utp_impl(utp_socket_impl* s, time_point now); +void utp_init_mtu(utp_socket_impl* s, int link_mtu, int utp_mtu); +void utp_init_socket(utp_socket_impl* s, std::weak_ptr sock); +bool utp_incoming_packet(utp_socket_impl* s, span p + , udp::endpoint const& ep, time_point receive_time); +bool utp_match(utp_socket_impl* s, udp::endpoint const& ep, std::uint16_t id); +udp::endpoint utp_remote_endpoint(utp_socket_impl* s); +std::uint16_t utp_receive_id(utp_socket_impl* s); +int utp_socket_state(utp_socket_impl const* s); +void utp_send_ack(utp_socket_impl* s); +void utp_socket_drained(utp_socket_impl* s); +void utp_writable(utp_socket_impl* s); + +// this is the user-level stream interface to utp sockets. +// the reason why it's split up in a utp_stream class and +// an implementation class is because the socket state has +// to be able to out-live the user level socket. For instance +// when sending data on a stream and then closing it, the +// state holding the send buffer has to be kept around until +// it has been flushed, which may be longer than the client +// will keep the utp_stream object around for. +// for more details, see utp_socket_impl, which is analogous +// to the kernel state for a socket. It's defined in utp_stream.cpp +struct TORRENT_EXTRA_EXPORT utp_stream +{ + using lowest_layer_type = utp_stream ; + using endpoint_type = tcp::socket::endpoint_type; + using protocol_type = tcp::socket::protocol_type; + +#if BOOST_VERSION >= 106600 + using executor_type = tcp::socket::executor_type; + executor_type get_executor() { return m_io_service.get_executor(); } +#endif + + explicit utp_stream(io_service& io_service); + ~utp_stream(); + utp_stream& operator=(utp_stream const&) = delete; + utp_stream(utp_stream const&) = delete; + utp_stream& operator=(utp_stream&&) noexcept = delete; + utp_stream(utp_stream&&) noexcept = delete; + + lowest_layer_type& lowest_layer() { return *this; } + + // used for incoming connections + void set_impl(utp_socket_impl* s); + utp_socket_impl* get_impl(); + +#ifndef BOOST_NO_EXCEPTIONS + template + void io_control(IO_Control_Command&) {} +#endif + + template + void io_control(IO_Control_Command&, error_code&) {} + +#ifndef BOOST_NO_EXCEPTIONS + void non_blocking(bool) {} +#endif + + error_code non_blocking(bool, error_code&) { return error_code(); } + +#ifndef BOOST_NO_EXCEPTIONS + void bind(endpoint_type const& /*endpoint*/) {} +#endif + + void bind(endpoint_type const&, error_code&); + +#ifndef BOOST_NO_EXCEPTIONS + template + void set_option(SettableSocketOption const&) {} +#endif + + template + error_code set_option(SettableSocketOption const&, error_code& ec) { return ec; } + +#ifndef BOOST_NO_EXCEPTIONS + template + void get_option(GettableSocketOption&) {} +#endif + + template + error_code get_option(GettableSocketOption&, error_code& ec) + { return ec; } + + error_code cancel(error_code&) + { + cancel_handlers(boost::asio::error::operation_aborted); + return error_code(); + } + + void close(); + void close(error_code const&) { close(); } + + void set_close_reason(close_reason_t code); + close_reason_t get_close_reason(); + + bool is_open() const { return m_open; } + + int read_buffer_size() const; + static void on_read(void* self, std::size_t bytes_transferred + , error_code const& ec, bool kill); + static void on_write(void* self, std::size_t bytes_transferred + , error_code const& ec, bool kill); + static void on_connect(void* self, error_code const& ec, bool kill); + static void on_close_reason(void* self, close_reason_t reason); + + void add_read_buffer(void* buf, std::size_t len); + void issue_read(); + void add_write_buffer(void const* buf, std::size_t len); + void issue_write(); + std::size_t read_some(bool clear_buffers); + + int send_delay() const; + int recv_delay() const; + + void do_connect(tcp::endpoint const& ep); + + endpoint_type local_endpoint() const + { + error_code ec; + return local_endpoint(ec); + } + + endpoint_type local_endpoint(error_code& ec) const; + + endpoint_type remote_endpoint() const + { + error_code ec; + return remote_endpoint(ec); + } + + endpoint_type remote_endpoint(error_code& ec) const; + + std::size_t available() const; + std::size_t available(error_code& /*ec*/) const { return available(); } + + io_service& get_io_service() { return m_io_service; } + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + if (m_impl == nullptr) + { + m_io_service.post(std::bind(handler, boost::asio::error::not_connected)); + return; + } + + m_connect_handler = handler; + do_connect(endpoint); + } + + template + void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) + { + if (m_impl == nullptr) + { + m_io_service.post(std::bind(handler, boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + m_io_service.post(std::bind(handler, boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + std::size_t bytes_added = 0; +#if BOOST_VERSION >= 106600 + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) +#else + for (typename Mutable_Buffers::const_iterator i = buffers.begin() + , end(buffers.end()); i != end; ++i) +#endif + { + if (buffer_size(*i) == 0) continue; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + add_read_buffer(buffer_cast(*i), buffer_size(*i)); + bytes_added += buffer_size(*i); + } + if (bytes_added == 0) + { + // if we're reading 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + m_io_service.post(std::bind(handler, error_code(), std::size_t(0))); + return; + } + + m_read_handler = handler; + issue_read(); + } + + template + void async_read_some(null_buffers const&, Handler const& handler) + { + if (m_impl == nullptr) + { + m_io_service.post(std::bind(handler, boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + TORRENT_ASSERT_FAIL(); // we should never do this! + m_io_service.post(std::bind(handler, boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + m_read_handler = handler; + issue_read(); + } + + template + void open(Protocol const&, error_code&) + { m_open = true; } + + template + void open(Protocol const&) + { m_open = true; } + + template + std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec) + { + TORRENT_ASSERT(!m_read_handler); + if (m_impl == nullptr) + { + ec = boost::asio::error::not_connected; + return 0; + } + + if (read_buffer_size() == 0) + { + ec = boost::asio::error::would_block; + return 0; + } +#if TORRENT_USE_ASSERTS + size_t buf_size = 0; +#endif + +#if BOOST_VERSION >= 106600 + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) +#else + for (typename Mutable_Buffers::const_iterator i = buffers.begin() + , end(buffers.end()); i != end; ++i) +#endif + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + add_read_buffer(buffer_cast(*i), buffer_size(*i)); +#if TORRENT_USE_ASSERTS + buf_size += buffer_size(*i); +#endif + } + std::size_t ret = read_some(true); + TORRENT_ASSERT(ret <= buf_size); + TORRENT_ASSERT(ret > 0); + return ret; + } + + template + std::size_t write_some(Const_Buffers const& /* buffers */, error_code& /* ec */) + { + TORRENT_ASSERT(false && "not implemented!"); + // TODO: implement blocking write. Low priority since it's not used (yet) + return 0; + } + +#ifndef BOOST_NO_EXCEPTIONS + template + std::size_t read_some(Mutable_Buffers const& buffers) + { + error_code ec; + std::size_t ret = read_some(buffers, ec); + if (ec) + boost::throw_exception(boost::system::system_error(ec)); + return ret; + } + + template + std::size_t write_some(Const_Buffers const& buffers) + { + error_code ec; + std::size_t ret = write_some(buffers, ec); + if (ec) + boost::throw_exception(boost::system::system_error(ec)); + return ret; + } +#endif + + template + void async_write_some(Const_Buffers const& buffers, Handler const& handler) + { + if (m_impl == nullptr) + { + m_io_service.post(std::bind(handler + , boost::asio::error::not_connected, std::size_t(0))); + return; + } + + TORRENT_ASSERT(!m_write_handler); + if (m_write_handler) + { + m_io_service.post(std::bind(handler + , boost::asio::error::operation_not_supported, std::size_t(0))); + return; + } + + std::size_t bytes_added = 0; +#if BOOST_VERSION >= 106600 + for (auto i = buffer_sequence_begin(buffers) + , end(buffer_sequence_end(buffers)); i != end; ++i) +#else + for (typename Const_Buffers::const_iterator i = buffers.begin() + , end(buffers.end()); i != end; ++i) +#endif + { + if (buffer_size(*i) == 0) continue; + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + add_write_buffer(buffer_cast(*i), buffer_size(*i)); + bytes_added += buffer_size(*i); + } + if (bytes_added == 0) + { + // if we're writing 0 bytes, post handler immediately + // asio's SSL layer depends on this behavior + m_io_service.post(std::bind(handler, error_code(), std::size_t(0))); + return; + } + m_write_handler = handler; + issue_write(); + } + +private: + + void cancel_handlers(error_code const&); + + std::function m_connect_handler; + std::function m_read_handler; + std::function m_write_handler; + + io_service& m_io_service; + utp_socket_impl* m_impl; + + close_reason_t m_incoming_close_reason = close_reason_t::none; + + // this field requires another 8 bytes (including padding) + bool m_open; +}; + +} + +#endif diff --git a/include/libtorrent/vector_utils.hpp b/include/libtorrent/vector_utils.hpp new file mode 100644 index 0000000..3d392ce --- /dev/null +++ b/include/libtorrent/vector_utils.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VECTOR_UTILS_HPP_INCLUDE +#define TORRENT_VECTOR_UTILS_HPP_INCLUDE + +#include +#include + +namespace libtorrent { + + template + auto sorted_find(Container& container, T const& v) + -> decltype(container.begin()) + { + auto i = std::lower_bound(container.begin(), container.end(), v); + if (i == container.end()) return container.end(); + if (*i != v) return container.end(); + return i; + } + + template + void sorted_insert(std::vector& container, U v) + { + auto i = std::lower_bound(container.begin(), container.end(), v); + container.insert(i, v); + } +} + +#endif diff --git a/include/libtorrent/version.hpp b/include/libtorrent/version.hpp new file mode 100644 index 0000000..5f831e6 --- /dev/null +++ b/include/libtorrent/version.hpp @@ -0,0 +1,59 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_VERSION_HPP_INCLUDED +#define TORRENT_VERSION_HPP_INCLUDED + +#include "libtorrent/aux_/export.hpp" + +#define LIBTORRENT_VERSION_MAJOR 1 +#define LIBTORRENT_VERSION_MINOR 2 +#define LIBTORRENT_VERSION_TINY 9 + +// the format of this version is: MMmmtt +// M = Major version, m = minor version, t = tiny version +#define LIBTORRENT_VERSION_NUM ((LIBTORRENT_VERSION_MAJOR * 10000) + (LIBTORRENT_VERSION_MINOR * 100) + LIBTORRENT_VERSION_TINY) + +#define LIBTORRENT_VERSION "1.2.9.0" +#define LIBTORRENT_REVISION "1f9c4545c" + +namespace libtorrent { + + // returns the libtorrent version as string form in this format: + // "..." + TORRENT_EXPORT char const* version(); + +} + +namespace lt = libtorrent; + +#endif diff --git a/include/libtorrent/web_connection_base.hpp b/include/libtorrent/web_connection_base.hpp new file mode 100644 index 0000000..6ceb282 --- /dev/null +++ b/include/libtorrent/web_connection_base.hpp @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef WEB_CONNECTION_BASE_HPP_INCLUDED +#define WEB_CONNECTION_BASE_HPP_INCLUDED + +#include +#include +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_request.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/http_parser.hpp" + +namespace libtorrent { + + class torrent; + + class TORRENT_EXTRA_EXPORT web_connection_base + : public peer_connection + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + web_connection_base(peer_connection_args const& pack + , web_seed_t& web); + + int timeout() const override; + void start() override; + + ~web_connection_base() override; + + // called from the main loop when this connection has any + // work to do. + void on_sent(error_code const& error + , std::size_t bytes_transferred) override; + + virtual std::string const& url() const = 0; + + bool in_handshake() const override; + + peer_id our_pid() const override { return peer_id(); } + + // the following functions appends messages + // to the send buffer + void write_choke() override {} + void write_unchoke() override {} + void write_interested() override {} + void write_not_interested() override {} + void write_request(peer_request const&) override = 0; + void write_cancel(peer_request const&) override {} + void write_have(piece_index_t) override {} + void write_dont_have(piece_index_t) override {} + void write_piece(peer_request const&, disk_buffer_holder) override + { TORRENT_ASSERT_FAIL(); } + void write_keepalive() override {} + void on_connected() override; + void write_reject_request(peer_request const&) override {} + void write_allow_fast(piece_index_t) override {} + void write_suggest(piece_index_t) override {} + void write_bitfield() override {} + void write_upload_only(bool) override {} + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + void get_specific_peer_info(peer_info& p) const override; + + protected: + + virtual void add_headers(std::string& request + , aux::session_settings const& sett, bool using_proxy) const; + + // the first request will contain a little bit more data + // than subsequent ones, things that aren't critical are left + // out to save bandwidth. + bool m_first_request; + + // true if we're using ssl + bool m_ssl; + + // this has one entry per bittorrent request + std::deque m_requests; + + std::string m_server_string; + std::string m_basic_auth; + std::string m_host; + std::string m_path; + + std::string m_external_auth; + web_seed_entry::headers_t m_extra_headers; + + http_parser m_parser; + + int m_port; + + // the number of bytes into the receive buffer where + // current read cursor is. + int m_body_start; + }; +} + +#endif // TORRENT_WEB_CONNECTION_BASE_HPP_INCLUDED diff --git a/include/libtorrent/web_peer_connection.hpp b/include/libtorrent/web_peer_connection.hpp new file mode 100644 index 0000000..ee99f7e --- /dev/null +++ b/include/libtorrent/web_peer_connection.hpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED +#define TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/piece_block_progress.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum +#include "libtorrent/aux_/vector.hpp" + +namespace libtorrent { + + class TORRENT_EXTRA_EXPORT web_peer_connection + : public web_connection_base + { + friend class invariant_access; + public: + + // this is the constructor where the we are the active part. + // The peer_connection should handshake and verify that the + // other end has the correct id + web_peer_connection(peer_connection_args const& pack + , web_seed_t& web); + + void on_connected() override; + + connection_type type() const override + { return connection_type::url_seed; } + + // called from the main loop when this connection has any + // work to do. + void on_receive(error_code const& error + , std::size_t bytes_transferred) override; + + std::string const& url() const override { return m_url; } + + void get_specific_peer_info(peer_info& p) const override; + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t error = peer_connection_interface::normal) override; + + void write_request(peer_request const& r) override; + + bool received_invalid_data(piece_index_t index, bool single_peer) override; + + private: + + void on_receive_padfile(); + void incoming_payload(char const* buf, int len); + void incoming_zeroes(int len); + void handle_redirect(int bytes_left); + void handle_error(int bytes_left); + void maybe_harvest_piece(); + + // returns the block currently being + // downloaded. And the progress of that + // block. If the peer isn't downloading + // a piece for the moment, the boost::optional + // will be invalid. + piece_block_progress downloading_piece_progress() const override; + + void handle_padfile(); + + // this has one entry per http-request + // (might be more than the bt requests) + struct file_request_t + { + file_index_t file_index; + int length; + std::int64_t start; + }; + std::deque m_file_requests; + + std::string m_url; + + web_seed_t* m_web; + + // this is used for intermediate storage of pieces to be delivered to the + // bittorrent engine + // TODO: 3 if we make this be a disk_buffer_holder instead + // we would save a copy + // use allocate_disk_receive_buffer and release_disk_receive_buffer + aux::vector m_piece; + + // the number of bytes we've forwarded to the incoming_payload() function + // in the current HTTP response. used to know where in the buffer the + // next response starts + int m_received_body; + + // this is the offset inside the current receive + // buffer where the next chunk header will be. + // this is updated for each chunk header that's + // parsed. It does not necessarily point to a valid + // offset in the receive buffer, if we haven't received + // it yet. This offset never includes the HTTP header + int m_chunk_pos; + + // this is the number of bytes we've already received + // from the next chunk header we're waiting for + int m_partial_chunk_header; + + // the number of responses we've received so far on + // this connection + int m_num_responses; + }; +} + +#endif // TORRENT_WEB_PEER_CONNECTION_HPP_INCLUDED diff --git a/include/libtorrent/write_resume_data.hpp b/include/libtorrent/write_resume_data.hpp new file mode 100644 index 0000000..c55cd8d --- /dev/null +++ b/include/libtorrent/write_resume_data.hpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_WRITE_RESUME_DATA_HPP_INCLUDE +#define TORRENT_WRITE_RESUME_DATA_HPP_INCLUDE + +#include + +#include "libtorrent/fwd.hpp" +#include "libtorrent/aux_/export.hpp" +#include "libtorrent/bencode.hpp" + +namespace libtorrent { + + // this function turns the resume data in an ``add_torrent_params`` object + // into a bencoded structure + TORRENT_EXPORT entry write_resume_data(add_torrent_params const& atp); + TORRENT_EXPORT std::vector write_resume_data_buf(add_torrent_params const& atp); +} + +#endif diff --git a/include/libtorrent/xml_parse.hpp b/include/libtorrent/xml_parse.hpp new file mode 100644 index 0000000..be0a12c --- /dev/null +++ b/include/libtorrent/xml_parse.hpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_XML_PARSE_HPP +#define TORRENT_XML_PARSE_HPP + +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/string_view.hpp" + +namespace libtorrent { + + enum + { + xml_start_tag, + xml_end_tag, + xml_empty_tag, + xml_declaration_tag, + xml_string, + xml_attribute, + xml_comment, + xml_parse_error, + // used for tags that don't follow the convention of + // key-value pairs inside the tag brackets. Like !DOCTYPE + xml_tag_content + }; + + // callback(int type, char const* name, int name_len + // , char const* val, int val_len) + // name is element or attribute name + // val is attribute value + // neither string is 0-terminated, but their lengths are specified via + // name_len and val_len respectively + TORRENT_EXTRA_EXPORT void xml_parse(string_view input + , std::function callback); +} + + +#endif diff --git a/libtorrent-rasterbar.pc.in b/libtorrent-rasterbar.pc.in new file mode 100644 index 0000000..dc600c2 --- /dev/null +++ b/libtorrent-rasterbar.pc.in @@ -0,0 +1,16 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +libdir=@libdir@ +datarootdir=@datarootdir@ +datadir=@datadir@ +sysconfdir=@sysconfdir@ +includedir=@includedir@ +package=@PACKAGE@ + +Name: libtorrent-rasterbar +Description: Bittorrent library. +Version: @VERSION@ +Libs: -L${libdir} -ltorrent-rasterbar @BOOST_SYSTEM_LIB@ +Libs.private: @LIBS@ @PTHREAD_LIBS@ @OPENSSL_LIBS@ +Cflags: -I${includedir} -I${includedir}/libtorrent @COMPILETIME_OPTIONS@ diff --git a/m4/ax_boost_base.m4 b/m4/ax_boost_base.m4 new file mode 100644 index 0000000..16fa69b --- /dev/null +++ b/m4/ax_boost_base.m4 @@ -0,0 +1,302 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_boost_base.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# DESCRIPTION +# +# Test for the Boost C++ libraries of a particular version (or newer) +# +# If no path to the installed boost library is given the macro searchs +# under /usr, /usr/local, /opt and /opt/local and evaluates the +# $BOOST_ROOT environment variable. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) +# +# And sets: +# +# HAVE_BOOST +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2009 Peter Adolphs +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 47 + +# example boost program (need to pass version) +m4_define([_AX_BOOST_BASE_PROGRAM], + [AC_LANG_PROGRAM([[ +#include +]],[[ +(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))])); +]])]) + +AC_DEFUN([AX_BOOST_BASE], +[ +AC_ARG_WITH([boost], + [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], + [use Boost library from a standard location (ARG=yes), + from the specified location (ARG=), + or disable it (ARG=no) + @<:@ARG=yes@:>@ ])], + [ + AS_CASE([$withval], + [no],[want_boost="no";_AX_BOOST_BASE_boost_path=""], + [yes],[want_boost="yes";_AX_BOOST_BASE_boost_path=""], + [want_boost="yes";_AX_BOOST_BASE_boost_path="$withval"]) + ], + [want_boost="yes"]) + + +AC_ARG_WITH([boost-libdir], + [AS_HELP_STRING([--with-boost-libdir=LIB_DIR], + [Force given directory for boost libraries. + Note that this will override library path detection, + so use this parameter only if default library detection fails + and you know exactly where your boost libraries are located.])], + [ + AS_IF([test -d "$withval"], + [_AX_BOOST_BASE_boost_lib_path="$withval"], + [AC_MSG_ERROR([--with-boost-libdir expected directory name])]) + ], + [_AX_BOOST_BASE_boost_lib_path=""]) + +BOOST_LDFLAGS="" +BOOST_CPPFLAGS="" +AS_IF([test "x$want_boost" = "xyes"], + [_AX_BOOST_BASE_RUNDETECT([$1],[$2],[$3])]) +AC_SUBST(BOOST_CPPFLAGS) +AC_SUBST(BOOST_LDFLAGS) +]) + + +# convert a version string in $2 to numeric and affect to polymorphic var $1 +AC_DEFUN([_AX_BOOST_BASE_TONUMERICVERSION],[ + AS_IF([test "x$2" = "x"],[_AX_BOOST_BASE_TONUMERICVERSION_req="1.20.0"],[_AX_BOOST_BASE_TONUMERICVERSION_req="$2"]) + _AX_BOOST_BASE_TONUMERICVERSION_req_shorten=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([[0-9]]*\.[[0-9]]*\)'` + _AX_BOOST_BASE_TONUMERICVERSION_req_major=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([[0-9]]*\)'` + AS_IF([test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_major" = "x"], + [AC_MSG_ERROR([You should at least specify libboost major version])]) + _AX_BOOST_BASE_TONUMERICVERSION_req_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\.\([[0-9]]*\)'` + AS_IF([test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_minor" = "x"], + [_AX_BOOST_BASE_TONUMERICVERSION_req_minor="0"]) + _AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` + AS_IF([test "X$_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor" = "X"], + [_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor="0"]) + _AX_BOOST_BASE_TONUMERICVERSION_RET=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req_major \* 100000 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_minor \* 100 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor` + AS_VAR_SET($1,$_AX_BOOST_BASE_TONUMERICVERSION_RET) +]) + +dnl Run the detection of boost should be run only if $want_boost +AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[ + _AX_BOOST_BASE_TONUMERICVERSION(WANT_BOOST_VERSION,[$1]) + succeeded=no + + + AC_REQUIRE([AC_CANONICAL_HOST]) + dnl On 64-bit systems check for system libraries in both lib64 and lib. + dnl The former is specified by FHS, but e.g. Debian does not adhere to + dnl this (as it rises problems for generic multi-arch support). + dnl The last entry in the list is chosen by default when no libraries + dnl are found, e.g. when only header-only libraries are installed! + AS_CASE([${host_cpu}], + [x86_64],[libsubdirs="lib64 libx32 lib lib64"], + [mips*64*],[libsubdirs="lib64 lib32 lib lib64"], + [ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64],[libsubdirs="lib64 lib lib64"], + [libsubdirs="lib"] + ) + + dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give + dnl them priority over the other paths since, if libs are found there, they + dnl are almost assuredly the ones desired. + AS_CASE([${host_cpu}], + [i?86],[multiarch_libsubdir="lib/i386-${host_os}"], + [multiarch_libsubdir="lib/${host_cpu}-${host_os}"] + ) + + dnl first we check the system location for boost libraries + dnl this location ist chosen if boost libraries are installed with the --layout=system option + dnl or if you install boost with RPM + AS_IF([test "x$_AX_BOOST_BASE_boost_path" != "x"],[ + AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) includes in "$_AX_BOOST_BASE_boost_path/include"]) + AS_IF([test -d "$_AX_BOOST_BASE_boost_path/include" && test -r "$_AX_BOOST_BASE_boost_path/include"],[ + AC_MSG_RESULT([yes]) + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include" + for _AX_BOOST_BASE_boost_path_tmp in $multiarch_libsubdir $libsubdirs; do + AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) lib path in "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp"]) + AS_IF([test -d "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" && test -r "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" ],[ + AC_MSG_RESULT([yes]) + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp"; + break; + ], + [AC_MSG_RESULT([no])]) + done],[ + AC_MSG_RESULT([no])]) + ],[ + if test X"$cross_compiling" = Xyes; then + search_libsubdirs=$multiarch_libsubdir + else + search_libsubdirs="$multiarch_libsubdir $libsubdirs" + fi + for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local ; do + if test -d "$_AX_BOOST_BASE_boost_path_tmp/include/boost" && test -r "$_AX_BOOST_BASE_boost_path_tmp/include/boost" ; then + for libsubdir in $search_libsubdirs ; do + if ls "$_AX_BOOST_BASE_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path_tmp/$libsubdir" + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path_tmp/include" + break; + fi + done + ]) + + dnl overwrite ld flags if we have required special directory with + dnl --with-boost-libdir parameter + AS_IF([test "x$_AX_BOOST_BASE_boost_lib_path" != "x"], + [BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_lib_path"]) + + AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION)]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_REQUIRE([AC_PROG_CXX]) + AC_LANG_PUSH(C++) + AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[ + AC_MSG_RESULT(yes) + succeeded=yes + found_system=yes + ],[ + ]) + AC_LANG_POP([C++]) + + + + dnl if we found no boost with system layout we search for boost libraries + dnl built and installed without the --layout=system option or for a staged(not installed) version + if test "x$succeeded" != "xyes" ; then + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + BOOST_CPPFLAGS= + if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then + BOOST_LDFLAGS= + fi + _version=0 + if test -n "$_AX_BOOST_BASE_boost_path" ; then + if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path"; then + for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "x$V_CHECK" = "x1" ; then + _version=$_version_tmp + fi + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include/boost-$VERSION_UNDERSCORE" + done + dnl if nothing found search for layout used in Windows distributions + if test -z "$BOOST_CPPFLAGS"; then + if test -d "$_AX_BOOST_BASE_boost_path/boost" && test -r "$_AX_BOOST_BASE_boost_path/boost"; then + BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path" + fi + fi + dnl if we found something and BOOST_LDFLAGS was unset before + dnl (because "$_AX_BOOST_BASE_boost_lib_path" = ""), set it here. + if test -n "$BOOST_CPPFLAGS" && test -z "$BOOST_LDFLAGS"; then + for libsubdir in $libsubdirs ; do + if ls "$_AX_BOOST_BASE_boost_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$libsubdir" + fi + fi + else + if test "x$cross_compiling" != "xyes" ; then + for _AX_BOOST_BASE_boost_path in /usr /usr/local /opt /opt/local ; do + if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path" ; then + for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "x$V_CHECK" = "x1" ; then + _version=$_version_tmp + best_path=$_AX_BOOST_BASE_boost_path + fi + done + fi + done + + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" + if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then + for libsubdir in $libsubdirs ; do + if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$best_path/$libsubdir" + fi + fi + + if test -n "$BOOST_ROOT" ; then + for libsubdir in $libsubdirs ; do + if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then + version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` + stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` + stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` + V_CHECK=`expr $stage_version_shorten \>\= $_version` + if test "x$V_CHECK" = "x1" && test -z "$_AX_BOOST_BASE_boost_lib_path" ; then + AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) + BOOST_CPPFLAGS="-I$BOOST_ROOT" + BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" + fi + fi + fi + fi + + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_LANG_PUSH(C++) + AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[ + AC_MSG_RESULT(yes) + succeeded=yes + found_system=yes + ],[ + ]) + AC_LANG_POP([C++]) + fi + + if test "x$succeeded" != "xyes" ; then + if test "x$_version" = "x0" ; then + AC_MSG_NOTICE([[We could not detect the boost libraries (version $1 or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) + else + AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) + fi + # execute ACTION-IF-NOT-FOUND (if present): + ifelse([$3], , :, [$3]) + else + AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) + # execute ACTION-IF-FOUND (if present): + ifelse([$2], , :, [$2]) + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + +]) diff --git a/m4/ax_boost_python.m4 b/m4/ax_boost_python.m4 new file mode 100644 index 0000000..28e35b6 --- /dev/null +++ b/m4/ax_boost_python.m4 @@ -0,0 +1,121 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_boost_python.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_PYTHON +# +# DESCRIPTION +# +# This macro checks to see if the Boost.Python library is installed. It +# also attempts to guess the correct library name using several attempts. +# It tries to build the library name using a user supplied name or suffix +# and then just the raw library. +# +# If the library is found, HAVE_BOOST_PYTHON is defined and +# BOOST_PYTHON_LIB is set to the name of the library. +# +# This macro calls AC_SUBST(BOOST_PYTHON_LIB). +# +# In order to ensure that the Python headers and the Boost libraries are +# specified on the include path, this macro requires AX_PYTHON_DEVEL and +# AX_BOOST_BASE to be called. +# +# LICENSE +# +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2013 Daniel M"ullner +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 23 + +AC_DEFUN([AX_BOOST_PYTHON], +[AC_REQUIRE([AX_PYTHON_DEVEL])dnl +AC_REQUIRE([AX_BOOST_BASE])dnl +AC_LANG_PUSH([C++]) +ax_boost_python_save_CPPFLAGS="$CPPFLAGS" +ax_boost_python_save_LDFLAGS="$LDFLAGS" +ax_boost_python_save_LIBS="$LIBS" +if test "x$PYTHON_CPPFLAGS" != "x"; then + CPPFLAGS="$PYTHON_CPPFLAGS $CPPFLAGS" +fi + +# Versions of AX_PYTHON_DEVEL() before serial 18 provided PYTHON_LDFLAGS +# instead of PYTHON_LIBS, so this is just here for compatibility. +if test "x$PYTHON_LDFLAGS" != "x"; then + LDFLAGS="$PYTHON_LDFLAGS $LDFLAGS" +fi + +# Note: Only versions of AX_PYTHON_DEVEL() since serial 18 provide PYTHON_LIBS +# instead of PYTHON_LDFLAGS. +if test "x$PYTHON_LIBS" != "x"; then + LIBS="$PYTHON_LIBS $LIBS" +fi + +if test "x$BOOST_CPPFLAGS" != "x"; then + CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS" +fi +if test "x$BOOST_LDFLAGS" != "x"; then + LDFLAGS="$BOOST_LDFLAGS $LDFLAGS" +fi +AC_CACHE_CHECK(whether the Boost::Python library is available, +ac_cv_boost_python, +[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include +BOOST_PYTHON_MODULE(test) { throw "Boost::Python test."; }]], [])], + ac_cv_boost_python=yes, ac_cv_boost_python=no) +]) +if test "$ac_cv_boost_python" = "yes"; then + AC_DEFINE(HAVE_BOOST_PYTHON,,[define if the Boost::Python library is available]) + ax_python_lib=boost_python + AC_ARG_WITH([boost-python],AS_HELP_STRING([--with-boost-python],[specify yes/no or the boost python library or suffix to use]), + [if test "x$with_boost_python" != "xno" -a "x$with_boost_python" != "xyes"; then + ax_python_lib=$with_boost_python + ax_boost_python_lib=boost_python-$with_boost_python + fi]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + for ax_lib in $ax_python_lib $ax_boost_python_lib `ls $BOOSTLIBDIR/libboost_python*.so* $BOOSTLIBDIR/libboost_python*.dylib* $BOOSTLIBDIR/libboost_python*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_python.*\)\.so.*$;\1;' -e 's;^lib\(boost_python.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_python.*\)\.a.*$;\1;' ` boost_python boost_python3; do + AS_VAR_PUSHDEF([ax_Lib], [ax_cv_lib_$ax_lib''_BOOST_PYTHON_MODULE])dnl + AC_CACHE_CHECK([whether $ax_lib is the correct library], [ax_Lib], + [LIBS="-l$ax_lib $ax_boost_python_save_LIBS $PYTHON_LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include +BOOST_PYTHON_MODULE(test) { throw "Boost::Python test."; }]], [])], + [AS_VAR_SET([ax_Lib], [yes])], + [AS_VAR_SET([ax_Lib], [no])])]) + AS_VAR_IF([ax_Lib], [yes], [BOOST_PYTHON_LIB=-l$ax_lib break], []) + AS_VAR_POPDEF([ax_Lib])dnl + done + AC_SUBST(BOOST_PYTHON_LIB) +fi +CPPFLAGS="$ax_boost_python_save_CPPFLAGS" +LDFLAGS="$ax_boost_python_save_LDFLAGS" +LIBS="$ax_boost_python_save_LIBS" +AC_LANG_POP([C++]) +])dnl diff --git a/m4/ax_boost_system.m4 b/m4/ax_boost_system.m4 new file mode 100644 index 0000000..323e2a6 --- /dev/null +++ b/m4/ax_boost_system.m4 @@ -0,0 +1,121 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_boost_system.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_SYSTEM +# +# DESCRIPTION +# +# Test for System library from the Boost C++ libraries. The macro requires +# a preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_SYSTEM_LIB) +# +# And sets: +# +# HAVE_BOOST_SYSTEM +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2008 Daniel Casimiro +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 20 + +AC_DEFUN([AX_BOOST_SYSTEM], +[ + AC_ARG_WITH([boost-system], + AS_HELP_STRING([--with-boost-system@<:@=special-lib@:>@], + [use the System library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-system=boost_system-gcc-mt ]), + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_system_lib="" + else + want_boost="yes" + ax_boost_user_system_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::System library is available, + ax_cv_boost_system, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + CXXFLAGS= + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], + [[boost::system::error_category *a = 0;]])], + ax_cv_boost_system=yes, ax_cv_boost_system=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_system" = "xyes"; then + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_SYSTEM,,[define if the Boost::System library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_system_lib" = "x"; then + for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + if test "x$link_system" != "xyes"; then + for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + + fi + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the Boost::System library!) + fi + if test "x$link_system" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/m4/ax_check_openssl.m4 b/m4/ax_check_openssl.m4 new file mode 100644 index 0000000..ce1617c --- /dev/null +++ b/m4/ax_check_openssl.m4 @@ -0,0 +1,124 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_openssl.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_OPENSSL([action-if-found[, action-if-not-found]]) +# +# DESCRIPTION +# +# Look for OpenSSL in a number of default spots, or in a user-selected +# spot (via --with-openssl). Sets +# +# OPENSSL_INCLUDES to the include directives required +# OPENSSL_LIBS to the -l directives required +# OPENSSL_LDFLAGS to the -L or -R flags required +# +# and calls ACTION-IF-FOUND or ACTION-IF-NOT-FOUND appropriately +# +# This macro sets OPENSSL_INCLUDES such that source files should use the +# openssl/ directory in include directives: +# +# #include +# +# LICENSE +# +# Copyright (c) 2009,2010 Zmanda Inc. +# Copyright (c) 2009,2010 Dustin J. Mitchell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +AU_ALIAS([CHECK_SSL], [AX_CHECK_OPENSSL]) +AC_DEFUN([AX_CHECK_OPENSSL], [ + found=false + AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl=DIR], + [root of the OpenSSL directory])], + [ + case "$withval" in + "" | y | ye | yes | n | no) + AC_MSG_ERROR([Invalid --with-openssl value]) + ;; + *) ssldirs="$withval" + ;; + esac + ], [ + # if pkg-config is installed and openssl has installed a .pc file, + # then use that information and don't search ssldirs + AC_CHECK_TOOL([PKG_CONFIG], [pkg-config]) + if test x"$PKG_CONFIG" != x""; then + OPENSSL_LDFLAGS=`$PKG_CONFIG openssl --libs-only-L 2>/dev/null` + if test $? = 0; then + OPENSSL_LIBS=`$PKG_CONFIG openssl --libs-only-l 2>/dev/null` + OPENSSL_INCLUDES=`$PKG_CONFIG openssl --cflags-only-I 2>/dev/null` + found=true + fi + fi + + # no such luck; use some default ssldirs + if ! $found; then + ssldirs="/usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr" + fi + ] + ) + + + # note that we #include , so the OpenSSL headers have to be in + # an 'openssl' subdirectory + + if ! $found; then + OPENSSL_INCLUDES= + for ssldir in $ssldirs; do + AC_MSG_CHECKING([for include/openssl/ssl.h in $ssldir]) + if test -f "$ssldir/include/openssl/ssl.h"; then + OPENSSL_INCLUDES="-I$ssldir/include" + OPENSSL_LDFLAGS="-L$ssldir/lib" + OPENSSL_LIBS="-lssl -lcrypto" + found=true + AC_MSG_RESULT([yes]) + break + else + AC_MSG_RESULT([no]) + fi + done + + # if the file wasn't found, well, go ahead and try the link anyway -- maybe + # it will just work! + fi + + # try the preprocessor and linker with our new flags, + # being careful not to pollute the global LIBS, LDFLAGS, and CPPFLAGS + + AC_MSG_CHECKING([whether compiling and linking against OpenSSL works]) + echo "Trying link with OPENSSL_LDFLAGS=$OPENSSL_LDFLAGS;" \ + "OPENSSL_LIBS=$OPENSSL_LIBS; OPENSSL_INCLUDES=$OPENSSL_INCLUDES" >&AS_MESSAGE_LOG_FD + + save_LIBS="$LIBS" + save_LDFLAGS="$LDFLAGS" + save_CPPFLAGS="$CPPFLAGS" + LDFLAGS="$LDFLAGS $OPENSSL_LDFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + CPPFLAGS="$OPENSSL_INCLUDES $CPPFLAGS" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ], [SSL_new(NULL)])], + [ + AC_MSG_RESULT([yes]) + $1 + ], [ + AC_MSG_RESULT([no]) + $2 + ]) + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" + + AC_SUBST([OPENSSL_INCLUDES]) + AC_SUBST([OPENSSL_LIBS]) + AC_SUBST([OPENSSL_LDFLAGS]) +]) diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..43087b2 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,951 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) diff --git a/m4/ax_cxx_compile_stdcxx_11.m4 b/m4/ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 0000000..1733fd8 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,39 @@ +# ============================================================================= +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================= +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXX and CXXCPP to enable +# support. +# +# This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX +# macro with the version set to C++11. The two optional arguments are +# forwarded literally as the second and third argument respectively. +# Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for +# more information. If you want to use this macro, you also need to +# download the ax_cxx_compile_stdcxx.m4 file. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 18 + +AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX]) +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [AX_CXX_COMPILE_STDCXX([11], [$1], [$2])]) diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..1598d07 --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,507 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# Copyright (c) 2019 Marc Stevens +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 27 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items with a "," contain both +# C compiler flags (before ",") and linker flags (after ","). Other items +# starting with a "-" are C compiler flags, and remaining items are +# library names, except for "none" which indicates that we try without +# any flags at all, and "pthread-config" which is a program returning +# the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" + ;; +esac + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +# Note that for GCC and Clang -pthread generally implies -lpthread, +# except when -nostdlib is passed. +# This is problematic using libtool to build C++ shared libraries with pthread: +# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 +# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 +# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 +# To solve this, first try -pthread together with -lpthread for GCC + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) + +# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first + +AS_IF([test "x$ax_pthread_clang" = "xyes"], + [ax_pthread_flags="-pthread,-lpthread -pthread"]) + + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + *,*) + PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` + PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` + AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void *some_global = NULL; + static void routine(void *a) + { + /* To avoid any unused-parameter or + unused-but-set-parameter warning. */ + some_global = a; + } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + + + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT; + return i;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4 new file mode 100644 index 0000000..44dbd83 --- /dev/null +++ b/m4/ax_python_devel.m4 @@ -0,0 +1,327 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_python_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_DEVEL([version]) +# +# DESCRIPTION +# +# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it +# in your configure.ac. +# +# This macro checks for Python and tries to get the include path to +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output +# variables. It also exports $(PYTHON_EXTRA_LIBS) and +# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. +# +# You can search for some particular version of Python by passing a +# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please +# note that you *have* to pass also an operator along with the version to +# match, and pay special attention to the single quotes surrounding the +# version number. Don't use "PYTHON_VERSION" for this: that environment +# variable is declared as precious and thus reserved for the end-user. +# +# This macro should work for all versions of Python >= 2.1.0. As an end +# user, you can disable the check for the python version by setting the +# PYTHON_NOVERSIONCHECK environment variable to something else than the +# empty string. +# +# If you need to use this macro for an older Python version, please +# contact the authors. We're always open for feedback. +# +# LICENSE +# +# Copyright (c) 2009 Sebastian Huber +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini +# Copyright (c) 2009 Horst Knorr +# Copyright (c) 2013 Daniel Mullner +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 21 + +AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) +AC_DEFUN([AX_PYTHON_DEVEL],[ + # + # Allow the use of a (user set) custom python version + # + AC_ARG_VAR([PYTHON_VERSION],[The installed Python + version to use, for example '2.3'. This string + will be appended to the Python interpreter + canonical name.]) + + AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) + if test -z "$PYTHON"; then + AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) + PYTHON_VERSION="" + fi + + # + # Check for a version of Python >= 2.1.0 + # + AC_MSG_CHECKING([for a version of Python >= '2.1.0']) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([ +This version of the AC@&t@_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. +]) + else + AC_MSG_RESULT([skip at user request]) + fi + else + AC_MSG_RESULT([yes]) + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n "$1"; then + AC_MSG_CHECKING([for a version of Python $1]) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver $1)"` + if test "$ac_supports_python_ver" = "True"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([this package requires Python $1. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See ``configure --help'' for reference. +]) + PYTHON_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + AC_MSG_CHECKING([for the distutils Python package]) + ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` + if test $? -eq 0; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot import Python module "distutils". +Please check your Python installation. The error was: +$ac_distutils_result]) + PYTHON_VERSION="" + fi + + # + # Check for Python include path + # + AC_MSG_CHECKING([for Python include path]) + if test -z "$PYTHON_CPPFLAGS"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + plat_python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc (plat_specific=1));"` + if test -n "${python_path}"; then + if test "${plat_python_path}" != "${python_path}"; then + python_path="-I$python_path -I$plat_python_path" + else + python_path="-I$python_path" + fi + fi + PYTHON_CPPFLAGS=$python_path + fi + AC_MSG_RESULT([$PYTHON_CPPFLAGS]) + AC_SUBST([PYTHON_CPPFLAGS]) + + # + # Check for Python library path + # + AC_MSG_CHECKING([for Python library path]) + if test -z "$PYTHON_LIBS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + LDFLAGS="$ac_save_LDFLAGS" + + AC_MSG_RESULT([$pythonexists]) + + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_FAILURE([ + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LIBS environment variable. + Example: ./configure LIBS="-L/usr/non-standard-path/python/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + PYTHON_VERSION="" + fi + + # + # all done! + # +]) diff --git a/m4/gettext-lib.m4 b/m4/gettext-lib.m4 new file mode 100644 index 0000000..fe25565 --- /dev/null +++ b/m4/gettext-lib.m4 @@ -0,0 +1,1123 @@ +## concatentation of files in gettext-0.19.5.1/gettext-runtime/gnulib-m4 +## cat lib-ld.m4 lib-link.m4 lib-prefix.m4 > gettext-lib.m4 + +# lib-ld.m4 serial 6 +dnl Copyright (C) 1996-2003, 2009-2015 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Subroutines of libtool.m4, +dnl with replacements s/_*LT_PATH/AC_LIB_PROG/ and s/lt_/acl_/ to avoid +dnl collision with libtool.m4. + +dnl From libtool-2.4. Sets the variable with_gnu_ld to yes or no. +AC_DEFUN([AC_LIB_PROG_LD_GNU], +[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], [acl_cv_prog_gnu_ld], +[# I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 /dev/null 2>&1 \ + && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \ + || PATH_SEPARATOR=';' + } +fi + +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`echo "$ac_prog"| sed 's%\\\\%/%g'` + while echo "$ac_prog" | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL([acl_cv_path_LD], +[if test -z "$LD"; then + acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$acl_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + acl_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$acl_cv_path_LD" -v 2>&1 = 1.10 to complain if config.rpath is missing. + m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])]) + AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS + AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld + AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host + AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir + AC_CACHE_CHECK([for shared library run path origin], [acl_cv_rpath], [ + CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ + ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh + . ./conftest.sh + rm -f ./conftest.sh + acl_cv_rpath=done + ]) + wl="$acl_cv_wl" + acl_libext="$acl_cv_libext" + acl_shlibext="$acl_cv_shlibext" + acl_libname_spec="$acl_cv_libname_spec" + acl_library_names_spec="$acl_cv_library_names_spec" + acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" + acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" + acl_hardcode_direct="$acl_cv_hardcode_direct" + acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" + dnl Determine whether the user wants rpath handling at all. + AC_ARG_ENABLE([rpath], + [ --disable-rpath do not hardcode runtime library paths], + :, enable_rpath=yes) +]) + +dnl AC_LIB_FROMPACKAGE(name, package) +dnl declares that libname comes from the given package. The configure file +dnl will then not have a --with-libname-prefix option but a +dnl --with-package-prefix option. Several libraries can come from the same +dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar +dnl macro call that searches for libname. +AC_DEFUN([AC_LIB_FROMPACKAGE], +[ + pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + define([acl_frompackage_]NAME, [$2]) + popdef([NAME]) + pushdef([PACK],[$2]) + pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + define([acl_libsinpackage_]PACKUP, + m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1]) + popdef([PACKUP]) + popdef([PACK]) +]) + +dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and +dnl the libraries corresponding to explicit and implicit dependencies. +dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. +dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found +dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. +AC_DEFUN([AC_LIB_LINKFLAGS_BODY], +[ + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])]) + pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])]) + dnl Autoconf >= 2.61 supports dots in --with options. + pushdef([P_A_C_K],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[m4_translit(PACK,[.],[_])],PACK)]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_ARG_WITH(P_A_C_K[-prefix], +[[ --with-]]P_A_C_K[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib + --without-]]P_A_C_K[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + if test "$acl_libdirstem2" != "$acl_libdirstem" \ + && ! test -d "$withval/$acl_libdirstem"; then + additional_libdir="$withval/$acl_libdirstem2" + fi + fi + fi +]) + dnl Search the library and its dependencies in $additional_libdir and + dnl $LDFLAGS. Using breadth-first-seach. + LIB[]NAME= + LTLIB[]NAME= + INC[]NAME= + LIB[]NAME[]_PREFIX= + dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been + dnl computed. So it has to be reset here. + HAVE_LIB[]NAME= + rpathdirs= + ltrpathdirs= + names_already_handled= + names_next_round='$1 $2' + while test -n "$names_next_round"; do + names_this_round="$names_next_round" + names_next_round= + for name in $names_this_round; do + already_handled= + for n in $names_already_handled; do + if test "$n" = "$name"; then + already_handled=yes + break + fi + done + if test -z "$already_handled"; then + names_already_handled="$names_already_handled $name" + dnl See if it was already located by an earlier AC_LIB_LINKFLAGS + dnl or AC_LIB_HAVE_LINKFLAGS call. + uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'` + eval value=\"\$HAVE_LIB$uppername\" + if test -n "$value"; then + if test "$value" = yes; then + eval value=\"\$LIB$uppername\" + test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" + eval value=\"\$LTLIB$uppername\" + test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" + else + dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined + dnl that this library doesn't exist. So just drop it. + : + fi + else + dnl Search the library lib$name in $additional_libdir and $LDFLAGS + dnl and the already constructed $LIBNAME/$LTLIBNAME. + found_dir= + found_la= + found_so= + found_a= + eval libname=\"$acl_libname_spec\" # typically: libname=lib$name + if test -n "$acl_shlibext"; then + shrext=".$acl_shlibext" # typically: shrext=.so + else + shrext= + fi + if test $use_additional = yes; then + dir="$additional_libdir" + dnl The same code as in the loop below: + dnl First look for a shared library. + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + dnl Then look for a static library. + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext"; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + fi + if test "X$found_dir" = "X"; then + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + case "$x" in + -L*) + dir=`echo "X$x" | sed -e 's/^X-L//'` + dnl First look for a shared library. + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + dnl Then look for a static library. + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext"; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + ;; + esac + if test "X$found_dir" != "X"; then + break + fi + done + fi + if test "X$found_dir" != "X"; then + dnl Found the library. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" + if test "X$found_so" != "X"; then + dnl Linking with a shared library. We attempt to hardcode its + dnl directory into the executable's runpath, unless it's the + dnl standard /usr/lib. + if test "$enable_rpath" = no \ + || test "X$found_dir" = "X/usr/$acl_libdirstem" \ + || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then + dnl No hardcoding is needed. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $found_dir" + fi + dnl The hardcoding into $LIBNAME is system dependent. + if test "$acl_hardcode_direct" = yes; then + dnl Using DIR/libNAME.so during linking hardcodes DIR into the + dnl resulting binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $found_dir" + fi + else + dnl Rely on "-L$found_dir". + dnl But don't add it if it's already contained in the LDFLAGS + dnl or the already constructed $LIBNAME + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" + fi + if test "$acl_hardcode_minus_L" != no; then + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH + dnl here, because this doesn't fit in flags passed to the + dnl compiler. So give up. No hardcoding. This affects only + dnl very old systems. + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + fi + fi + fi + fi + else + if test "X$found_a" != "X"; then + dnl Linking with a static library. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" + else + dnl We shouldn't come here, but anyway it's good to have a + dnl fallback. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" + fi + fi + dnl Assume the include files are nearby. + additional_includedir= + case "$found_dir" in + */$acl_libdirstem | */$acl_libdirstem/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + */$acl_libdirstem2 | */$acl_libdirstem2/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + esac + if test "X$additional_includedir" != "X"; then + dnl Potentially add $additional_includedir to $INCNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's /usr/local/include and we are using GCC on Linux, + dnl 3. if it's already present in $CPPFLAGS or the already + dnl constructed $INCNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + for x in $CPPFLAGS $INC[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $INCNAME. + INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" + fi + fi + fi + fi + fi + dnl Look for dependencies. + if test -n "$found_la"; then + dnl Read the .la file. It defines the variables + dnl dlname, library_names, old_library, dependency_libs, current, + dnl age, revision, installed, dlopen, dlpreopen, libdir. + save_libdir="$libdir" + case "$found_la" in + */* | *\\*) . "$found_la" ;; + *) . "./$found_la" ;; + esac + libdir="$save_libdir" + dnl We use only dependency_libs. + for dep in $dependency_libs; do + case "$dep" in + -L*) + additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` + dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's /usr/local/lib and we are using GCC on Linux, + dnl 3. if it's already present in $LDFLAGS or the already + dnl constructed $LIBNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \ + && test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then + haveit= + if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \ + || test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LIBNAME. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" + fi + fi + haveit= + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LTLIBNAME. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" + fi + fi + fi + fi + ;; + -R*) + dir=`echo "X$dep" | sed -e 's/^X-R//'` + if test "$enable_rpath" != no; then + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $dir" + fi + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $dir" + fi + fi + ;; + -l*) + dnl Handle this in the next round. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` + ;; + *.la) + dnl Handle this in the next round. Throw away the .la's + dnl directory; it is already contained in a preceding -L + dnl option. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` + ;; + *) + dnl Most likely an immediate library name. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" + ;; + esac + done + fi + else + dnl Didn't find the library; assume it is in the system directories + dnl known to the linker and runtime loader. (All the system + dnl directories known to the linker should also be known to the + dnl runtime loader, otherwise the system is severely misconfigured.) + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" + fi + fi + fi + done + done + if test "X$rpathdirs" != "X"; then + if test -n "$acl_hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user must + dnl pass all path elements in one option. We can arrange that for a + dnl single library, but not when more than one $LIBNAMEs are used. + alldirs= + for found_dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" + done + dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + else + dnl The -rpath options are cumulative. + for found_dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$found_dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + done + fi + fi + if test "X$ltrpathdirs" != "X"; then + dnl When using libtool, the option that works for both libraries and + dnl executables is -R. The -R options are cumulative. + for found_dir in $ltrpathdirs; do + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" + done + fi + popdef([P_A_C_K]) + popdef([PACKLIBS]) + popdef([PACKUP]) + popdef([PACK]) + popdef([NAME]) +]) + +dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, +dnl unless already present in VAR. +dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes +dnl contains two or three consecutive elements that belong together. +AC_DEFUN([AC_LIB_APPENDTOVAR], +[ + for element in [$2]; do + haveit= + for x in $[$1]; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X$element"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + [$1]="${[$1]}${[$1]:+ }$element" + fi + done +]) + +dnl For those cases where a variable contains several -L and -l options +dnl referring to unknown libraries and directories, this macro determines the +dnl necessary additional linker options for the runtime path. +dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) +dnl sets LDADDVAR to linker options needed together with LIBSVALUE. +dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, +dnl otherwise linking without libtool is assumed. +AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], +[ + AC_REQUIRE([AC_LIB_RPATH]) + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + $1= + if test "$enable_rpath" != no; then + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode directories into the resulting + dnl binary. + rpathdirs= + next= + for opt in $2; do + if test -n "$next"; then + dir="$next" + dnl No need to hardcode the standard /usr/lib. + if test "X$dir" != "X/usr/$acl_libdirstem" \ + && test "X$dir" != "X/usr/$acl_libdirstem2"; then + rpathdirs="$rpathdirs $dir" + fi + next= + else + case $opt in + -L) next=yes ;; + -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` + dnl No need to hardcode the standard /usr/lib. + if test "X$dir" != "X/usr/$acl_libdirstem" \ + && test "X$dir" != "X/usr/$acl_libdirstem2"; then + rpathdirs="$rpathdirs $dir" + fi + next= ;; + *) next= ;; + esac + fi + done + if test "X$rpathdirs" != "X"; then + if test -n ""$3""; then + dnl libtool is used for linking. Use -R options. + for dir in $rpathdirs; do + $1="${$1}${$1:+ }-R$dir" + done + else + dnl The linker is used for linking directly. + if test -n "$acl_hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user + dnl must pass all path elements in one option. + alldirs= + for dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" + done + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + $1="$flag" + else + dnl The -rpath options are cumulative. + for dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + $1="${$1}${$1:+ }$flag" + done + fi + fi + fi + fi + fi + AC_SUBST([$1]) +]) +# lib-prefix.m4 serial 7 (gettext-0.18) +dnl Copyright (C) 2001-2005, 2008-2015 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and +dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't +dnl require excessive bracketing. +ifdef([AC_HELP_STRING], +[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])], +[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])]) + +dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed +dnl to access previously installed libraries. The basic assumption is that +dnl a user will want packages to use other packages he previously installed +dnl with the same --prefix option. +dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate +dnl libraries, but is otherwise very convenient. +AC_DEFUN([AC_LIB_PREFIX], +[ + AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_LIB_ARG_WITH([lib-prefix], +[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib + --without-lib-prefix don't search for libraries in includedir and libdir], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + fi + fi +]) + if test $use_additional = yes; then + dnl Potentially add $additional_includedir to $CPPFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's already present in $CPPFLAGS, + dnl 3. if it's /usr/local/include and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + for x in $CPPFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $CPPFLAGS. + CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" + fi + fi + fi + fi + dnl Potentially add $additional_libdir to $LDFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's already present in $LDFLAGS, + dnl 3. if it's /usr/local/lib and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then + haveit= + for x in $LDFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then + if test -n "$GCC"; then + case $host_os in + linux*) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LDFLAGS. + LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" + fi + fi + fi + fi + fi +]) + +dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, +dnl acl_final_exec_prefix, containing the values to which $prefix and +dnl $exec_prefix will expand at the end of the configure script. +AC_DEFUN([AC_LIB_PREPARE_PREFIX], +[ + dnl Unfortunately, prefix and exec_prefix get only finally determined + dnl at the end of configure. + if test "X$prefix" = "XNONE"; then + acl_final_prefix="$ac_default_prefix" + else + acl_final_prefix="$prefix" + fi + if test "X$exec_prefix" = "XNONE"; then + acl_final_exec_prefix='${prefix}' + else + acl_final_exec_prefix="$exec_prefix" + fi + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the +dnl variables prefix and exec_prefix bound to the values they will have +dnl at the end of the configure script. +AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], +[ + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + $1 + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_PREPARE_MULTILIB creates +dnl - a variable acl_libdirstem, containing the basename of the libdir, either +dnl "lib" or "lib64" or "lib/64", +dnl - a variable acl_libdirstem2, as a secondary possible value for +dnl acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or +dnl "lib/amd64". +AC_DEFUN([AC_LIB_PREPARE_MULTILIB], +[ + dnl There is no formal standard regarding lib and lib64. + dnl On glibc systems, the current practice is that on a system supporting + dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under + dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine + dnl the compiler's default mode by looking at the compiler's library search + dnl path. If at least one of its elements ends in /lib64 or points to a + dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI. + dnl Otherwise we use the default, namely "lib". + dnl On Solaris systems, the current practice is that on a system supporting + dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under + dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or + dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib. + AC_REQUIRE([AC_CANONICAL_HOST]) + acl_libdirstem=lib + acl_libdirstem2= + case "$host_os" in + solaris*) + dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment + dnl . + dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link." + dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the + dnl symlink is missing, so we set acl_libdirstem2 too. + AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit], + [AC_EGREP_CPP([sixtyfour bits], [ +#ifdef _LP64 +sixtyfour bits +#endif + ], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no]) + ]) + if test $gl_cv_solaris_64bit = yes; then + acl_libdirstem=lib/64 + case "$host_cpu" in + sparc*) acl_libdirstem2=lib/sparcv9 ;; + i*86 | x86_64) acl_libdirstem2=lib/amd64 ;; + esac + fi + ;; + *) + searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` + if test -n "$searchpath"; then + acl_save_IFS="${IFS= }"; IFS=":" + for searchdir in $searchpath; do + if test -d "$searchdir"; then + case "$searchdir" in + */lib64/ | */lib64 ) acl_libdirstem=lib64 ;; + */../ | */.. ) + # Better ignore directories of this form. They are misleading. + ;; + *) searchdir=`cd "$searchdir" && pwd` + case "$searchdir" in + */lib64 ) acl_libdirstem=lib64 ;; + esac ;; + esac + fi + done + IFS="$acl_save_IFS" + fi + ;; + esac + test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" +]) diff --git a/m4/iconv.m4 b/m4/iconv.m4 new file mode 100644 index 0000000..e593b72 --- /dev/null +++ b/m4/iconv.m4 @@ -0,0 +1,288 @@ +# iconv.m4 serial 21 +dnl Copyright (C) 2000-2002, 2007-2014, 2016-2020 Free Software Foundation, +dnl Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +AC_DEFUN([AM_ICONV_LINKFLAGS_BODY], +[ + dnl Prerequisites of AC_LIB_LINKFLAGS_BODY. + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + AC_REQUIRE([AC_LIB_RPATH]) + + dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV + dnl accordingly. + AC_LIB_LINKFLAGS_BODY([iconv]) +]) + +AC_DEFUN([AM_ICONV_LINK], +[ + dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and + dnl those with the standalone portable GNU libiconv installed). + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + + dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV + dnl accordingly. + AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY]) + + dnl Add $INCICONV to CPPFLAGS before performing the following checks, + dnl because if the user has installed libiconv and not disabled its use + dnl via --without-libiconv-prefix, he wants to use it. The first + dnl AC_LINK_IFELSE will then fail, the second AC_LINK_IFELSE will succeed. + am_save_CPPFLAGS="$CPPFLAGS" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV]) + + AC_CACHE_CHECK([for iconv], [am_cv_func_iconv], [ + am_cv_func_iconv="no, consider installing GNU libiconv" + am_cv_lib_iconv=no + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include +#include + ]], + [[iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd);]])], + [am_cv_func_iconv=yes]) + if test "$am_cv_func_iconv" != yes; then + am_save_LIBS="$LIBS" + LIBS="$LIBS $LIBICONV" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include +#include + ]], + [[iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd);]])], + [am_cv_lib_iconv=yes] + [am_cv_func_iconv=yes]) + LIBS="$am_save_LIBS" + fi + ]) + if test "$am_cv_func_iconv" = yes; then + AC_CACHE_CHECK([for working iconv], [am_cv_func_iconv_works], [ + dnl This tests against bugs in AIX 5.1, AIX 6.1..7.1, HP-UX 11.11, + dnl Solaris 10. + am_save_LIBS="$LIBS" + if test $am_cv_lib_iconv = yes; then + LIBS="$LIBS $LIBICONV" + fi + am_cv_func_iconv_works=no + for ac_iconv_const in '' 'const'; do + AC_RUN_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include +#include + +#ifndef ICONV_CONST +# define ICONV_CONST $ac_iconv_const +#endif + ]], + [[int result = 0; + /* Test against AIX 5.1 bug: Failures are not distinguishable from successful + returns. */ + { + iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8"); + if (cd_utf8_to_88591 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */ + char buf[10]; + ICONV_CONST char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_utf8_to_88591, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res == 0) + result |= 1; + iconv_close (cd_utf8_to_88591); + } + } + /* Test against Solaris 10 bug: Failures are not distinguishable from + successful returns. */ + { + iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646"); + if (cd_ascii_to_88591 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\263"; + char buf[10]; + ICONV_CONST char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_ascii_to_88591, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res == 0) + result |= 2; + iconv_close (cd_ascii_to_88591); + } + } + /* Test against AIX 6.1..7.1 bug: Buffer overrun. */ + { + iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1"); + if (cd_88591_to_utf8 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\304"; + static char buf[2] = { (char)0xDE, (char)0xAD }; + ICONV_CONST char *inptr = input; + size_t inbytesleft = 1; + char *outptr = buf; + size_t outbytesleft = 1; + size_t res = iconv (cd_88591_to_utf8, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res != (size_t)(-1) || outptr - buf > 1 || buf[1] != (char)0xAD) + result |= 4; + iconv_close (cd_88591_to_utf8); + } + } +#if 0 /* This bug could be worked around by the caller. */ + /* Test against HP-UX 11.11 bug: Positive return value instead of 0. */ + { + iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591"); + if (cd_88591_to_utf8 != (iconv_t)(-1)) + { + static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337"; + char buf[50]; + ICONV_CONST char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_88591_to_utf8, + &inptr, &inbytesleft, + &outptr, &outbytesleft); + if ((int)res > 0) + result |= 8; + iconv_close (cd_88591_to_utf8); + } + } +#endif + /* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is + provided. */ + { + /* Try standardized names. */ + iconv_t cd1 = iconv_open ("UTF-8", "EUC-JP"); + /* Try IRIX, OSF/1 names. */ + iconv_t cd2 = iconv_open ("UTF-8", "eucJP"); + /* Try AIX names. */ + iconv_t cd3 = iconv_open ("UTF-8", "IBM-eucJP"); + /* Try HP-UX names. */ + iconv_t cd4 = iconv_open ("utf8", "eucJP"); + if (cd1 == (iconv_t)(-1) && cd2 == (iconv_t)(-1) + && cd3 == (iconv_t)(-1) && cd4 == (iconv_t)(-1)) + result |= 16; + if (cd1 != (iconv_t)(-1)) + iconv_close (cd1); + if (cd2 != (iconv_t)(-1)) + iconv_close (cd2); + if (cd3 != (iconv_t)(-1)) + iconv_close (cd3); + if (cd4 != (iconv_t)(-1)) + iconv_close (cd4); + } + return result; +]])], + [am_cv_func_iconv_works=yes], , + [case "$host_os" in + aix* | hpux*) am_cv_func_iconv_works="guessing no" ;; + *) am_cv_func_iconv_works="guessing yes" ;; + esac]) + test "$am_cv_func_iconv_works" = no || break + done + LIBS="$am_save_LIBS" + ]) + case "$am_cv_func_iconv_works" in + *no) am_func_iconv=no am_cv_lib_iconv=no ;; + *) am_func_iconv=yes ;; + esac + else + am_func_iconv=no am_cv_lib_iconv=no + fi + if test "$am_func_iconv" = yes; then + AC_DEFINE([HAVE_ICONV], [1], + [Define if you have the iconv() function and it works.]) + fi + if test "$am_cv_lib_iconv" = yes; then + AC_MSG_CHECKING([how to link with libiconv]) + AC_MSG_RESULT([$LIBICONV]) + else + dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV + dnl either. + CPPFLAGS="$am_save_CPPFLAGS" + LIBICONV= + LTLIBICONV= + fi + AC_SUBST([LIBICONV]) + AC_SUBST([LTLIBICONV]) +]) + +dnl Define AM_ICONV using AC_DEFUN_ONCE for Autoconf >= 2.64, in order to +dnl avoid warnings like +dnl "warning: AC_REQUIRE: `AM_ICONV' was expanded before it was required". +dnl This is tricky because of the way 'aclocal' is implemented: +dnl - It requires defining an auxiliary macro whose name ends in AC_DEFUN. +dnl Otherwise aclocal's initial scan pass would miss the macro definition. +dnl - It requires a line break inside the AC_DEFUN_ONCE and AC_DEFUN expansions. +dnl Otherwise aclocal would emit many "Use of uninitialized value $1" +dnl warnings. +m4_define([gl_iconv_AC_DEFUN], + m4_version_prereq([2.64], + [[AC_DEFUN_ONCE( + [$1], [$2])]], + [m4_ifdef([gl_00GNULIB], + [[AC_DEFUN_ONCE( + [$1], [$2])]], + [[AC_DEFUN( + [$1], [$2])]])])) +gl_iconv_AC_DEFUN([AM_ICONV], +[ + AM_ICONV_LINK + if test "$am_cv_func_iconv" = yes; then + AC_MSG_CHECKING([for iconv declaration]) + AC_CACHE_VAL([am_cv_proto_iconv], [ + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include +#include +extern +#ifdef __cplusplus +"C" +#endif +#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) +size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft); +#else +size_t iconv(); +#endif + ]], + [[]])], + [am_cv_proto_iconv_arg1=""], + [am_cv_proto_iconv_arg1="const"]) + am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"]) + am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'` + AC_MSG_RESULT([ + $am_cv_proto_iconv]) + else + dnl When compiling GNU libiconv on a system that does not have iconv yet, + dnl pick the POSIX compliant declaration without 'const'. + am_cv_proto_iconv_arg1="" + fi + AC_DEFINE_UNQUOTED([ICONV_CONST], [$am_cv_proto_iconv_arg1], + [Define as const if the declaration of iconv() needs const.]) + dnl Also substitute ICONV_CONST in the gnulib generated . + m4_ifdef([gl_ICONV_H_DEFAULTS], + [AC_REQUIRE([gl_ICONV_H_DEFAULTS]) + if test -n "$am_cv_proto_iconv_arg1"; then + ICONV_CONST="const" + fi + ]) +]) diff --git a/m4/libtool.m4 b/m4/libtool.m4 new file mode 100644 index 0000000..a6d21ae --- /dev/null +++ b/m4/libtool.m4 @@ -0,0 +1,8394 @@ +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +]) + +# serial 58 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK +AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_PREPARE_CC_BASENAME +# ----------------------- +m4_defun([_LT_PREPARE_CC_BASENAME], [ +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in @S|@*""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} +])# _LT_PREPARE_CC_BASENAME + + +# _LT_CC_BASENAME(CC) +# ------------------- +# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, +# but that macro is also expanded into generated libtool script, which +# arranges for $SED and $ECHO to be set by different means. +m4_defun([_LT_CC_BASENAME], +[m4_require([_LT_PREPARE_CC_BASENAME])dnl +AC_REQUIRE([_LT_DECL_SED])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl +func_cc_basename $1 +cc_basename=$func_cc_basename_result +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl + +_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl +dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_WITH_SYSROOT])dnl +m4_require([_LT_CMD_TRUNCATE])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PREPARE_SED_QUOTE_VARS +# -------------------------- +# Define a few sed substitution that help us do robust quoting. +m4_defun([_LT_PREPARE_SED_QUOTE_VARS], +[# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' +]) + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from 'configure', and 'config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# 'config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain=$ac_aux_dir/ltmain.sh +])# _LT_PROG_LTMAIN + + +## ------------------------------------- ## +## Accumulate code for creating libtool. ## +## ------------------------------------- ## + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the 'libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + +## ------------------------ ## +## FIXME: Eliminate VARNAME ## +## ------------------------ ## + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to 'config.status' so that its +# declaration there will have the same value as in 'configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags='_LT_TAGS'dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into 'config.status', and then the shell code to quote escape them in +# for loops in 'config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$[]1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +_LT_OUTPUT_LIBTOOL_INIT +]) + +# _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) +# ------------------------------------ +# Generate a child script FILE with all initialization necessary to +# reuse the environment learned by the parent script, and make the +# file executable. If COMMENT is supplied, it is inserted after the +# '#!' sequence but before initialization text begins. After this +# macro, additional text can be appended to FILE to form the body of +# the child script. The macro ends with non-zero status if the +# file could not be fully written (such as if the disk is full). +m4_ifdef([AS_INIT_GENERATED], +[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], +[m4_defun([_LT_GENERATED_FILE_INIT], +[m4_require([AS_PREPARE])]dnl +[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl +[lt_write_fail=0 +cat >$1 <<_ASEOF || lt_write_fail=1 +#! $SHELL +# Generated by $as_me. +$2 +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$1 <<\_ASEOF || lt_write_fail=1 +AS_SHELL_SANITIZE +_AS_PREPARE +exec AS_MESSAGE_FD>&1 +_ASEOF +test 0 = "$lt_write_fail" && chmod +x $1[]dnl +m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +_LT_GENERATED_FILE_INIT(["$CONFIG_LT"], +[# Run this file to recreate a libtool stub with the current configuration.]) + +cat >>"$CONFIG_LT" <<\_LTEOF +lt_cl_silent=false +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +'$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to ." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2011 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test 0 != $[#] +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try '$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try '$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +lt_cl_success=: +test yes = "$silent" && + lt_config_lt_args="$lt_config_lt_args --quiet" +exec AS_MESSAGE_LOG_FD>/dev/null +$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false +exec AS_MESSAGE_LOG_FD>>config.log +$lt_cl_success || AS_EXIT(1) +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +_LT_COPYING +_LT_LIBTOOL_TAGS + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +_LT_PREPARE_MUNGE_PATH_LIST +_LT_PREPARE_CC_BASENAME + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Go], [_LT_LANG(GO)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +m4_ifndef([AC_PROG_GO], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_GO. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ +m4_defun([AC_PROG_GO], +[AC_LANG_PUSH(Go)dnl +AC_ARG_VAR([GOC], [Go compiler command])dnl +AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl +_AC_ARG_VAR_LDFLAGS()dnl +AC_CHECK_TOOL(GOC, gccgo) +if test -z "$GOC"; then + if test -n "$ac_tool_prefix"; then + AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) + fi +fi +if test -z "$GOC"; then + AC_CHECK_PROG(GOC, gccgo, gccgo, false) +fi +])#m4_defun +])#m4_ifndef + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([AC_PROG_GO], + [LT_LANG(GO)], + [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) +dnl AC_DEFUN([AC_LIBTOOL_RC], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS=$save_LDFLAGS + ]) + + AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], + [lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD + echo "$AR cr libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cr libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD + $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[[012]][[,.]]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES([TAG]) +# --------------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + if test yes = "$lt_cv_ld_force_load"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], + [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + m4_if([$1], [CXX], +[ if test yes != "$lt_cv_apple_cc_single_mod"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX([TAGNAME]) +# ---------------------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +# Store the results from the different compilers for each TAGNAME. +# Allow to override them for all tags through lt_cv_aix_libpath. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ + lt_aix_libpath_sed='[ + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }]' + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi],[]) + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib + fi + ]) + aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) +fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[m4_divert_text([M4SH-INIT], [$1 +])])# _LT_SHELL_INIT + + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Find how we can fake an echo command that does not interpret backslash. +# In particular, with Autoconf 2.60 or later we add some code to the start +# of the generated configure script that will find a shell with a builtin +# printf (that we can use as an echo command). +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +AC_MSG_CHECKING([how to print strings]) +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$[]1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + +case $ECHO in + printf*) AC_MSG_RESULT([printf]) ;; + print*) AC_MSG_RESULT([print -r]) ;; + *) AC_MSG_RESULT([cat]) ;; +esac + +m4_ifdef([_AS_DETECT_SUGGESTED], +[_AS_DETECT_SUGGESTED([ + test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test "X`printf %s $ECHO`" = "X$ECHO" \ + || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) + +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_WITH_SYSROOT +# ---------------- +AC_DEFUN([_LT_WITH_SYSROOT], +[AC_MSG_CHECKING([for sysroot]) +AC_ARG_WITH([sysroot], +[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], + [Search for dependent libraries within DIR (or the compiler's sysroot + if not specified).])], +[], [with_sysroot=no]) + +dnl lt_sysroot will always be passed unquoted. We quote it here +dnl in case the user passed a directory name. +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + AC_MSG_RESULT([$with_sysroot]) + AC_MSG_ERROR([The sysroot must be an absolute path.]) + ;; +esac + + AC_MSG_RESULT([${lt_sysroot:-no}]) +_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl +[dependent libraries, and where our libraries should be installed.])]) + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock +])# _LT_ENABLE_LOCK + + +# _LT_PROG_AR +# ----------- +m4_defun([_LT_PROG_AR], +[AC_CHECK_TOOLS(AR, [ar], false) +: ${AR=ar} +: ${AR_FLAGS=cr} +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) + +AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], + [lt_cv_ar_at_file=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM], + [echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' + AC_TRY_EVAL([lt_ar_try]) + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + AC_TRY_EVAL([lt_ar_try]) + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + ]) + ]) + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi +_LT_DECL([], [archiver_list_spec], [1], + [How to feed a file listing to the archiver]) +])# _LT_PROG_AR + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[_LT_PROG_AR + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +_LT_DECL([], [lock_old_archive_extraction], [0], + [Whether to use a lock for old archive extraction]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test yes = "[$]$2"; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS +]) + +if test yes = "[$]$2"; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n "$lt_cv_sys_max_cmd_len"; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes = "$cross_compiling"; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen=shl_load], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen=dlopen], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links=nottested +if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test no = "$hard_links"; then + AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", + [Define to the sub-directory where libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then + + # We can hardcode non-existent directories. + if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && + test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || + test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_PREPARE_MUNGE_PATH_LIST +# --------------------------- +# Make sure func_munge_path_list() is defined correctly. +m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], +[[# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x@S|@2 in + x) + ;; + *:) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" + ;; + x:*) + eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" + ;; + *) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + esac +} +]])# _LT_PREPARE_PATH_LIST + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +AC_ARG_VAR([LT_SYS_LIBRARY_PATH], +[User-defined run-time library search path.]) + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[[4-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a[(]lib.so.V[)]' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[23]].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[[3-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], + [lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [lt_cv_shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + ]) + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [install_override_mode], [1], + [Permission mode override for installation of shared libraries]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], + [Detected run-time system search path for libraries]) +_LT_DECL([], [configure_time_lt_sys_library_path], [2], + [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program that can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$1"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac]) +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program that can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test no = "$withval" || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], +[if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi]) +rm -f conftest.i conftest2.i conftest.out]) +])# _LT_PATH_DD + + +# _LT_CMD_TRUNCATE +# ---------------- +# find command to truncate a binary pipe +m4_defun([_LT_CMD_TRUNCATE], +[m4_require([_LT_PATH_DD]) +AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) +_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], + [Command to truncate a binary pipe]) +])# _LT_CMD_TRUNCATE + + +# _LT_CHECK_MAGIC_METHOD +# ---------------------- +# how to check for library dependencies +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_MAGIC_METHOD], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +AC_CACHE_CHECK([how to recognize dependent libraries], +lt_cv_deplibs_check_method, +[lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[[4-9]]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[[45]]*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method = "file_magic"]) +_LT_DECL([], [file_magic_glob], [1], + [How to find potential files when deplibs_check_method = "file_magic"]) +_LT_DECL([], [want_nocaseglob], [1], + [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi]) +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + AC_SUBST([DUMPBIN]) + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + +# _LT_CHECK_SHAREDLIB_FROM_LINKLIB +# -------------------------------- +# how to determine the name of the shared library +# associated with a specific link library. +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +m4_require([_LT_DECL_DLLTOOL]) +AC_CACHE_CHECK([how to associate runtime and link libraries], +lt_cv_sharedlib_from_linklib_cmd, +[lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac +]) +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + +_LT_DECL([], [sharedlib_from_linklib_cmd], [1], + [Command to associate shared and link libraries]) +])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB + + +# _LT_PATH_MANIFEST_TOOL +# ---------------------- +# locate the manifest tool +m4_defun([_LT_PATH_MANIFEST_TOOL], +[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], + [lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&AS_MESSAGE_LOG_FD + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest*]) +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi +_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl +])# _LT_PATH_MANIFEST_TOOL + + +# _LT_DLL_DEF_P([FILE]) +# --------------------- +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with func_dll_def_p in the libtool script +AC_DEFUN([_LT_DLL_DEF_P], +[dnl + test DEF = "`$SED -n dnl + -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace + -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments + -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl + -e q dnl Only consider the first "real" line + $1`" dnl +])# _LT_DLL_DEF_P + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM=-lm) + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; + *) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; + esac + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&AS_MESSAGE_LOG_FD + if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&AS_MESSAGE_LOG_FD && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT@&t@_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT@&t@_DLSYM_CONST +#else +# define LT@&t@_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT@&t@_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], + [Transform the output of nm into a list of symbols to manually relocate]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +_LT_DECL([nm_interface], [lt_cv_nm_interface], [1], + [The name lister interface]) +_LT_DECL([], [nm_file_list_spec], [1], + [Specify filename containing input files for $NM]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd* | netbsdelf*-gnu) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test yes = "$GCC"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # flang / f18. f95 an alias for gfortran or flang on Debian + flang* | f18* | f95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + *Sun\ F* | *Sun*Fortran*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Intel*\ [[CF]]*Compiler*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + *Portland\ Group*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +AC_CACHE_CHECK([for $compiler option to produce PIC], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + ;; + esac + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; + *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + esac + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + m4_if($1, [], [ + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + _LT_LINKER_OPTION([if $CC understands -b], + _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], + [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) + ;; + esac + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], + [lt_cv_irix_exported_symbol], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], + [C++], [[int foo (void) { return 0; }]], + [Fortran 77], [[ + subroutine foo + end]], + [Fortran], [[ + subroutine foo + end]])])], + [lt_cv_irix_exported_symbol=yes], + [lt_cv_irix_exported_symbol=no]) + LDFLAGS=$save_LDFLAGS]) + if test yes = "$lt_cv_irix_exported_symbol"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + _LT_TAGVAR(link_all_deplibs, $1)=no + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(ld_shlibs, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + osf3*) + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_CACHE_CHECK([whether -lc should be explicitly linked in], + [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), + [$RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + ]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting $shlibpath_var if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [postlink_cmds], [2], + [Commands necessary for finishing linking programs]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC=$CC +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report what library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC=$lt_save_CC +])# _LT_LANG_C_CONFIG + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ + '"$_LT_TAGVAR(old_archive_cmds, $1)" + _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ + '"$_LT_TAGVAR(reload_cmds, $1)" + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + + _LT_TAGVAR(GCC, $1)=$GXX + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_FUNC_STRIPNAME_CNF +# ---------------------- +# func_stripname_cnf prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# +# This function is identical to the (non-XSI) version of func_stripname, +# except this one can be used by m4 code that may be executed by configure, +# rather than the libtool script. +m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl +AC_REQUIRE([_LT_DECL_SED]) +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) +func_stripname_cnf () +{ + case @S|@2 in + .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; + *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; + esac +} # func_stripname_cnf +])# _LT_FUNC_STRIPNAME_CNF + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF +package foo +func foo() { +} +_LT_EOF +]) + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)=$prev$p + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)=$p + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)=$p + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_LANG_PUSH(Fortran 77) +if test -z "$F77" || test no = "$F77"; then + _lt_disable_F77=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_F77"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${F77-"f77"} + CFLAGS=$FFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$G77 + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_F77" + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_LANG_PUSH(Fortran) + +if test -z "$FC" || test no = "$FC"; then + _lt_disable_FC=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_FC"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${FC-"f95"} + CFLAGS=$FCFLAGS + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_FC" + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +CFLAGS=$GCJFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_GO_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Go compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GO_CONFIG], +[AC_REQUIRE([LT_PROG_GO])dnl +AC_LANG_SAVE + +# Source file extension for Go test sources. +ac_ext=go + +# Object file extension for compiled Go test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="package main; func main() { }" + +# Code to be used in simple link tests +lt_simple_link_test_code='package main; func main() { }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GOC-"gccgo"} +CFLAGS=$GOFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# Go did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GO_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code=$lt_simple_compile_test_code + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +CFLAGS= +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_GO +# ---------- +AC_DEFUN([LT_PROG_GO], +[AC_CHECK_TOOL(GOC, gccgo,) +]) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + +# _LT_DECL_DLLTOOL +# ---------------- +# Ensure DLLTOOL variable is set. +m4_defun([_LT_DECL_DLLTOOL], +[AC_CHECK_TOOL(DLLTOOL, dlltool, false) +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program]) +AC_SUBST([DLLTOOL]) +]) + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f "$lt_ac_sed" && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test 10 -lt "$lt_ac_count" && break + lt_ac_count=`expr $lt_ac_count + 1` + if test "$lt_ac_count" -gt "$lt_ac_max"; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PATH_CONVERSION_FUNCTIONS +# ----------------------------- +# Determine what file name conversion functions should be used by +# func_to_host_file (and, implicitly, by func_to_host_path). These are needed +# for certain cross-compile configurations and native mingw. +m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_MSG_CHECKING([how to convert $build file names to $host format]) +AC_CACHE_VAL(lt_cv_to_host_file_cmd, +[case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac +]) +to_host_file_cmd=$lt_cv_to_host_file_cmd +AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) +_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], + [0], [convert $build file names to $host format])dnl + +AC_MSG_CHECKING([how to convert $build file names to toolchain format]) +AC_CACHE_VAL(lt_cv_to_tool_file_cmd, +[#assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac +]) +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) +_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], + [0], [convert $build files to toolchain format])dnl +])# _LT_PATH_CONVERSION_FUNCTIONS diff --git a/m4/ltoptions.m4 b/m4/ltoptions.m4 new file mode 100644 index 0000000..94b0829 --- /dev/null +++ b/m4/ltoptions.m4 @@ -0,0 +1,437 @@ +# Helper functions for option handling. -*- Autoconf -*- +# +# Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 8 ltoptions.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) + + +# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) +# ------------------------------------------ +m4_define([_LT_MANGLE_OPTION], +[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) + + +# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) +# --------------------------------------- +# Set option OPTION-NAME for macro MACRO-NAME, and if there is a +# matching handler defined, dispatch to it. Other OPTION-NAMEs are +# saved as a flag. +m4_define([_LT_SET_OPTION], +[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl +m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), + _LT_MANGLE_DEFUN([$1], [$2]), + [m4_warning([Unknown $1 option '$2'])])[]dnl +]) + + +# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) +# ------------------------------------------------------------ +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +m4_define([_LT_IF_OPTION], +[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) + + +# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) +# ------------------------------------------------------- +# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME +# are set. +m4_define([_LT_UNLESS_OPTIONS], +[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), + [m4_define([$0_found])])])[]dnl +m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 +])[]dnl +]) + + +# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) +# ---------------------------------------- +# OPTION-LIST is a space-separated list of Libtool options associated +# with MACRO-NAME. If any OPTION has a matching handler declared with +# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about +# the unknown option and exit. +m4_defun([_LT_SET_OPTIONS], +[# Set options +m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [_LT_SET_OPTION([$1], _LT_Option)]) + +m4_if([$1],[LT_INIT],[ + dnl + dnl Simply set some default values (i.e off) if boolean options were not + dnl specified: + _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no + ]) + _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no + ]) + dnl + dnl If no reference was made to various pairs of opposing options, then + dnl we run the default mode handler for the pair. For example, if neither + dnl 'shared' nor 'disable-shared' was passed, we enable building of shared + dnl archives by default: + _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) + _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], + [_LT_ENABLE_FAST_INSTALL]) + _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], + [_LT_WITH_AIX_SONAME([aix])]) + ]) +])# _LT_SET_OPTIONS + + +## --------------------------------- ## +## Macros to handle LT_INIT options. ## +## --------------------------------- ## + +# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) +# ----------------------------------------- +m4_define([_LT_MANGLE_DEFUN], +[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) + + +# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) +# ----------------------------------------------- +m4_define([LT_OPTION_DEFINE], +[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl +])# LT_OPTION_DEFINE + + +# dlopen +# ------ +LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes +]) + +AU_DEFUN([AC_LIBTOOL_DLOPEN], +[_LT_SET_OPTION([LT_INIT], [dlopen]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'dlopen' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) + + +# win32-dll +# --------- +# Declare package support for building win32 dll's. +LT_OPTION_DEFINE([LT_INIT], [win32-dll], +[enable_win32_dll=yes + +case $host in +*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; +esac + +test -z "$AS" && AS=as +_LT_DECL([], [AS], [1], [Assembler program])dnl + +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl + +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl +])# win32-dll + +AU_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +_LT_SET_OPTION([LT_INIT], [win32-dll]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'win32-dll' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) + + +# _LT_ENABLE_SHARED([DEFAULT]) +# ---------------------------- +# implement the --enable-shared flag, and supports the 'shared' and +# 'disable-shared' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_SHARED], +[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([shared], + [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) + + _LT_DECL([build_libtool_libs], [enable_shared], [0], + [Whether or not to build shared libraries]) +])# _LT_ENABLE_SHARED + +LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) +]) + +AC_DEFUN([AC_DISABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], [disable-shared]) +]) + +AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_SHARED], []) +dnl AC_DEFUN([AM_DISABLE_SHARED], []) + + + +# _LT_ENABLE_STATIC([DEFAULT]) +# ---------------------------- +# implement the --enable-static flag, and support the 'static' and +# 'disable-static' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_STATIC], +[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_static=]_LT_ENABLE_STATIC_DEFAULT) + + _LT_DECL([build_old_libs], [enable_static], [0], + [Whether or not to build static libraries]) +])# _LT_ENABLE_STATIC + +LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) +]) + +AC_DEFUN([AC_DISABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], [disable-static]) +]) + +AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_STATIC], []) +dnl AC_DEFUN([AM_DISABLE_STATIC], []) + + + +# _LT_ENABLE_FAST_INSTALL([DEFAULT]) +# ---------------------------------- +# implement the --enable-fast-install flag, and support the 'fast-install' +# and 'disable-fast-install' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_FAST_INSTALL], +[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([fast-install], + [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) + +_LT_DECL([fast_install], [enable_fast_install], [0], + [Whether or not to optimize for fast installation])dnl +])# _LT_ENABLE_FAST_INSTALL + +LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) + +# Old names: +AU_DEFUN([AC_ENABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'fast-install' option into LT_INIT's first parameter.]) +]) + +AU_DEFUN([AC_DISABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'disable-fast-install' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) +dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) + + +# _LT_WITH_AIX_SONAME([DEFAULT]) +# ---------------------------------- +# implement the --with-aix-soname flag, and support the `aix-soname=aix' +# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT +# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. +m4_define([_LT_WITH_AIX_SONAME], +[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl +shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[[5-9]]*,yes) + AC_MSG_CHECKING([which variant of shared library versioning to provide]) + AC_ARG_WITH([aix-soname], + [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], + [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], + [case $withval in + aix|svr4|both) + ;; + *) + AC_MSG_ERROR([Unknown argument to --with-aix-soname]) + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname], + [AC_CACHE_VAL([lt_cv_with_aix_soname], + [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) + with_aix_soname=$lt_cv_with_aix_soname]) + AC_MSG_RESULT([$with_aix_soname]) + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + +_LT_DECL([], [shared_archive_member_spec], [0], + [Shared archive member basename, for filename based shared library versioning on AIX])dnl +])# _LT_WITH_AIX_SONAME + +LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) + + +# _LT_WITH_PIC([MODE]) +# -------------------- +# implement the --with-pic flag, and support the 'pic-only' and 'no-pic' +# LT_INIT options. +# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. +m4_define([_LT_WITH_PIC], +[AC_ARG_WITH([pic], + [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [pic_mode=m4_default([$1], [default])]) + +_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl +])# _LT_WITH_PIC + +LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) + +# Old name: +AU_DEFUN([AC_LIBTOOL_PICMODE], +[_LT_SET_OPTION([LT_INIT], [pic-only]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'pic-only' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) + +## ----------------- ## +## LTDL_INIT Options ## +## ----------------- ## + +m4_define([_LTDL_MODE], []) +LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], + [m4_define([_LTDL_MODE], [nonrecursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [recursive], + [m4_define([_LTDL_MODE], [recursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [subproject], + [m4_define([_LTDL_MODE], [subproject])]) + +m4_define([_LTDL_TYPE], []) +LT_OPTION_DEFINE([LTDL_INIT], [installable], + [m4_define([_LTDL_TYPE], [installable])]) +LT_OPTION_DEFINE([LTDL_INIT], [convenience], + [m4_define([_LTDL_TYPE], [convenience])]) diff --git a/m4/ltsugar.m4 b/m4/ltsugar.m4 new file mode 100644 index 0000000..48bc934 --- /dev/null +++ b/m4/ltsugar.m4 @@ -0,0 +1,124 @@ +# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltsugar.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) + + +# lt_join(SEP, ARG1, [ARG2...]) +# ----------------------------- +# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their +# associated separator. +# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier +# versions in m4sugar had bugs. +m4_define([lt_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) +m4_define([_lt_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) + + +# lt_car(LIST) +# lt_cdr(LIST) +# ------------ +# Manipulate m4 lists. +# These macros are necessary as long as will still need to support +# Autoconf-2.59, which quotes differently. +m4_define([lt_car], [[$1]]) +m4_define([lt_cdr], +[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], + [$#], 1, [], + [m4_dquote(m4_shift($@))])]) +m4_define([lt_unquote], $1) + + +# lt_append(MACRO-NAME, STRING, [SEPARATOR]) +# ------------------------------------------ +# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. +# Note that neither SEPARATOR nor STRING are expanded; they are appended +# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). +# No SEPARATOR is output if MACRO-NAME was previously undefined (different +# than defined and empty). +# +# This macro is needed until we can rely on Autoconf 2.62, since earlier +# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. +m4_define([lt_append], +[m4_define([$1], + m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) + + + +# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) +# ---------------------------------------------------------- +# Produce a SEP delimited list of all paired combinations of elements of +# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list +# has the form PREFIXmINFIXSUFFIXn. +# Needed until we can rely on m4_combine added in Autoconf 2.62. +m4_define([lt_combine], +[m4_if(m4_eval([$# > 3]), [1], + [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl +[[m4_foreach([_Lt_prefix], [$2], + [m4_foreach([_Lt_suffix], + ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, + [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) + + +# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) +# ----------------------------------------------------------------------- +# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited +# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. +m4_define([lt_if_append_uniq], +[m4_ifdef([$1], + [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], + [lt_append([$1], [$2], [$3])$4], + [$5])], + [lt_append([$1], [$2], [$3])$4])]) + + +# lt_dict_add(DICT, KEY, VALUE) +# ----------------------------- +m4_define([lt_dict_add], +[m4_define([$1($2)], [$3])]) + + +# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) +# -------------------------------------------- +m4_define([lt_dict_add_subkey], +[m4_define([$1($2:$3)], [$4])]) + + +# lt_dict_fetch(DICT, KEY, [SUBKEY]) +# ---------------------------------- +m4_define([lt_dict_fetch], +[m4_ifval([$3], + m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), + m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) + + +# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) +# ----------------------------------------------------------------- +m4_define([lt_if_dict_fetch], +[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], + [$5], + [$6])]) + + +# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) +# -------------------------------------------------------------- +m4_define([lt_dict_filter], +[m4_if([$5], [], [], + [lt_join(m4_quote(m4_default([$4], [[, ]])), + lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), + [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl +]) diff --git a/m4/ltversion.m4 b/m4/ltversion.m4 new file mode 100644 index 0000000..fa04b52 --- /dev/null +++ b/m4/ltversion.m4 @@ -0,0 +1,23 @@ +# ltversion.m4 -- version numbers -*- Autoconf -*- +# +# Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# @configure_input@ + +# serial 4179 ltversion.m4 +# This file is part of GNU Libtool + +m4_define([LT_PACKAGE_VERSION], [2.4.6]) +m4_define([LT_PACKAGE_REVISION], [2.4.6]) + +AC_DEFUN([LTVERSION_VERSION], +[macro_version='2.4.6' +macro_revision='2.4.6' +_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) +_LT_DECL(, macro_revision, 0) +]) diff --git a/m4/lt~obsolete.m4 b/m4/lt~obsolete.m4 new file mode 100644 index 0000000..c6b26f8 --- /dev/null +++ b/m4/lt~obsolete.m4 @@ -0,0 +1,99 @@ +# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Scott James Remnant, 2004. +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 5 lt~obsolete.m4 + +# These exist entirely to fool aclocal when bootstrapping libtool. +# +# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), +# which have later been changed to m4_define as they aren't part of the +# exported API, or moved to Autoconf or Automake where they belong. +# +# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN +# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us +# using a macro with the same name in our local m4/libtool.m4 it'll +# pull the old libtool.m4 in (it doesn't see our shiny new m4_define +# and doesn't know about Autoconf macros at all.) +# +# So we provide this file, which has a silly filename so it's always +# included after everything else. This provides aclocal with the +# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything +# because those macros already exist, or will be overwritten later. +# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. +# +# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. +# Yes, that means every name once taken will need to remain here until +# we give up compatibility with versions before 1.7, at which point +# we need to keep only those names which we still refer to. + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) + +m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) +m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) +m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) +m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) +m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) +m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) +m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) +m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) +m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) +m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) +m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) +m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) +m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) +m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) +m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) +m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) +m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) +m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) +m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) +m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) +m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) +m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) +m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) +m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) +m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) +m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) +m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) +m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) +m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) +m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) +m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) +m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) +m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) +m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) +m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) +m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) +m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) +m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) +m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) +m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) +m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) +m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) +m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) +m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) +m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) +m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) +m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) +m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) +m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) +m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) diff --git a/m4/pkgconfig.m4 b/m4/pkgconfig.m4 new file mode 100644 index 0000000..2326f4a --- /dev/null +++ b/m4/pkgconfig.m4 @@ -0,0 +1,343 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# serial 11 (pkg-config-0.29.1) + +dnl Copyright © 2004 Scott James Remnant . +dnl Copyright © 2012-2015 Dan Nicholson +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +dnl 02111-1307, USA. +dnl +dnl As a special exception to the GNU General Public License, if you +dnl distribute this file as part of a program that contains a +dnl configuration script generated by Autoconf, you may include it under +dnl the same distribution terms that you use for the rest of that +dnl program. + +dnl PKG_PREREQ(MIN-VERSION) +dnl ----------------------- +dnl Since: 0.29 +dnl +dnl Verify that the version of the pkg-config macros are at least +dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's +dnl installed version of pkg-config, this checks the developer's version +dnl of pkg.m4 when generating configure. +dnl +dnl To ensure that this macro is defined, also add: +dnl m4_ifndef([PKG_PREREQ], +dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) +dnl +dnl See the "Since" comment for each macro you use to see what version +dnl of the macros you require. +m4_defun([PKG_PREREQ], +[m4_define([PKG_MACROS_VERSION], [0.29.1]) +m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, + [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) +])dnl PKG_PREREQ + +dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) +dnl ---------------------------------- +dnl Since: 0.16 +dnl +dnl Search for the pkg-config tool and set the PKG_CONFIG variable to +dnl first found in the path. Checks that the version of pkg-config found +dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is +dnl used since that's the first version where most current features of +dnl pkg-config existed. +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])dnl PKG_PROG_PKG_CONFIG + +dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------------------------------- +dnl Since: 0.18 +dnl +dnl Check to see whether a particular set of modules exists. Similar to +dnl PKG_CHECK_MODULES(), but does not set variables or print errors. +dnl +dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +dnl only at the first occurence in configure.ac, so if the first place +dnl it's called might be skipped (such as if it is within an "if", you +dnl have to call PKG_CHECK_EXISTS manually +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +dnl --------------------------------------------- +dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting +dnl pkg_failed based on the result. +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])dnl _PKG_CONFIG + +dnl _PKG_SHORT_ERRORS_SUPPORTED +dnl --------------------------- +dnl Internal check to see if pkg-config supports short errors. +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])dnl _PKG_SHORT_ERRORS_SUPPORTED + + +dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl -------------------------------------------------------------- +dnl Since: 0.4.0 +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES might not happen, you should be sure to include an +dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])dnl PKG_CHECK_MODULES + + +dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl --------------------------------------------------------------------- +dnl Since: 0.29 +dnl +dnl Checks for existence of MODULES and gathers its build flags with +dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags +dnl and VARIABLE-PREFIX_LIBS from --libs. +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to +dnl include an explicit call to PKG_PROG_PKG_CONFIG in your +dnl configure.ac. +AC_DEFUN([PKG_CHECK_MODULES_STATIC], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +_save_PKG_CONFIG=$PKG_CONFIG +PKG_CONFIG="$PKG_CONFIG --static" +PKG_CHECK_MODULES($@) +PKG_CONFIG=$_save_PKG_CONFIG[]dnl +])dnl PKG_CHECK_MODULES_STATIC + + +dnl PKG_INSTALLDIR([DIRECTORY]) +dnl ------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable pkgconfigdir as the location where a module +dnl should install pkg-config .pc files. By default the directory is +dnl $libdir/pkgconfig, but the default can be changed by passing +dnl DIRECTORY. The user can override through the --with-pkgconfigdir +dnl parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_INSTALLDIR + + +dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) +dnl -------------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable noarch_pkgconfigdir as the location where a +dnl module should install arch-independent pkg-config .pc files. By +dnl default the directory is $datadir/pkgconfig, but the default can be +dnl changed by passing DIRECTORY. The user can override through the +dnl --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_NOARCH_INSTALLDIR + + +dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------- +dnl Since: 0.28 +dnl +dnl Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])dnl PKG_CHECK_VAR + +dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], +dnl [DESCRIPTION], [DEFAULT]) +dnl ------------------------------------------ +dnl +dnl Prepare a "--with-" configure option using the lowercase +dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and +dnl PKG_CHECK_MODULES in a single macro. +AC_DEFUN([PKG_WITH_MODULES], +[ +m4_pushdef([with_arg], m4_tolower([$1])) + +m4_pushdef([description], + [m4_default([$5], [build with ]with_arg[ support])]) + +m4_pushdef([def_arg], [m4_default([$6], [auto])]) +m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) +m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) + +m4_case(def_arg, + [yes],[m4_pushdef([with_without], [--without-]with_arg)], + [m4_pushdef([with_without],[--with-]with_arg)]) + +AC_ARG_WITH(with_arg, + AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, + [AS_TR_SH([with_]with_arg)=def_arg]) + +AS_CASE([$AS_TR_SH([with_]with_arg)], + [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], + [auto],[PKG_CHECK_MODULES([$1],[$2], + [m4_n([def_action_if_found]) $3], + [m4_n([def_action_if_not_found]) $4])]) + +m4_popdef([with_arg]) +m4_popdef([description]) +m4_popdef([def_arg]) + +])dnl PKG_WITH_MODULES + +dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [DESCRIPTION], [DEFAULT]) +dnl ----------------------------------------------- +dnl +dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES +dnl check._[VARIABLE-PREFIX] is exported as make variable. +AC_DEFUN([PKG_HAVE_WITH_MODULES], +[ +PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) + +AM_CONDITIONAL([HAVE_][$1], + [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) +])dnl PKG_HAVE_WITH_MODULES + +dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, +dnl [DESCRIPTION], [DEFAULT]) +dnl ------------------------------------------------------ +dnl +dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after +dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make +dnl and preprocessor variable. +AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], +[ +PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) + +AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], + [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) +])dnl PKG_HAVE_DEFINE_WITH_MODULES diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..796ebd4 --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import os + +os.chdir('bindings/python') +exec(open('setup.py').read()) diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..db78a1f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,182 @@ +AUTOMAKE_OPTIONS = subdir-objects + +lib_LTLIBRARIES = libtorrent-rasterbar.la + +if ENABLE_DHT +KADEMLIA_SOURCES = \ + kademlia/dht_state.cpp \ + kademlia/dht_storage.cpp \ + kademlia/dht_tracker.cpp \ + kademlia/find_data.cpp \ + kademlia/put_data.cpp \ + kademlia/msg.cpp \ + kademlia/node.cpp \ + kademlia/node_entry.cpp \ + kademlia/node_id.cpp \ + kademlia/refresh.cpp \ + kademlia/routing_table.cpp \ + kademlia/rpc_manager.cpp \ + kademlia/traversal_algorithm.cpp \ + kademlia/dos_blocker.cpp \ + kademlia/get_peers.cpp \ + kademlia/get_item.cpp \ + kademlia/item.cpp \ + kademlia/ed25519.cpp \ + kademlia/sample_infohashes.cpp \ + kademlia/dht_settings.cpp \ + ../ed25519/src/add_scalar.cpp \ + ../ed25519/src/fe.cpp \ + ../ed25519/src/ge.cpp \ + ../ed25519/src/key_exchange.cpp \ + ../ed25519/src/keypair.cpp \ + ../ed25519/src/sc.cpp \ + ../ed25519/src/sign.cpp \ + ../ed25519/src/verify.cpp \ + hasher512.cpp +endif + +libtorrent_rasterbar_la_SOURCES = \ + web_connection_base.cpp \ + alert.cpp \ + alert_manager.cpp \ + announce_entry.cpp \ + assert.cpp \ + bandwidth_limit.cpp \ + bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp \ + bdecode.cpp \ + bitfield.cpp \ + bloom_filter.cpp \ + broadcast_socket.cpp \ + block_cache.cpp \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_io_job.cpp \ + disk_io_thread.cpp \ + disk_io_thread_pool.cpp \ + disk_job_fence.cpp \ + disk_job_pool.cpp \ + entry.cpp \ + enum_net.cpp \ + error_code.cpp \ + escape_string.cpp \ + file.cpp \ + path.cpp \ + file_pool.cpp \ + file_storage.cpp \ + fingerprint.cpp \ + generate_peer_id.cpp \ + gzip.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_stream.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_notifier.cpp \ + ip_voter.cpp \ + lazy_bdecode.cpp \ + listen_socket_handle.cpp \ + lsd.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + natpmp.cpp \ + openssl.cpp \ + parse_url.cpp \ + part_file.cpp \ + pe_crypto.cpp \ + performance_counters.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + packet_buffer.cpp \ + proxy_base.cpp \ + peer_list.cpp \ + puff.cpp \ + random.cpp \ + receive_buffer.cpp \ + read_resume_data.cpp \ + write_resume_data.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_settings.cpp \ + proxy_settings.cpp \ + settings_pack.cpp \ + sha1_hash.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage.cpp \ + storage_piece_set.cpp \ + storage_utils.cpp \ + session_stats.cpp \ + string_util.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + time.cpp \ + timestamp_history.cpp \ + tracker_manager.cpp \ + udp_socket.cpp \ + udp_tracker_connection.cpp \ + upnp.cpp \ + ut_metadata.cpp \ + ut_pex.cpp \ + utf8.cpp \ + utp_socket_manager.cpp \ + utp_stream.cpp \ + web_peer_connection.cpp \ + xml_parse.cpp \ + version.cpp \ + file_progress.cpp \ + ffs.cpp \ + add_torrent_params.cpp \ + peer_info.cpp \ + stack_allocator.cpp \ + sha1.cpp \ + sha512.cpp \ + \ + $(KADEMLIA_SOURCES) + +AM_CPPFLAGS = -DTORRENT_BUILDING_LIBRARY @DEBUGFLAGS@ +AM_LDFLAGS = @OPENSSL_LDFLAGS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ + +libtorrent_rasterbar_la_LDFLAGS = -version-info $(INTERFACE_VERSION_INFO) +libtorrent_rasterbar_la_LIBADD = @OPENSSL_LIBS@ +libtorrent_rasterbar_la_CPPFLAGS = $(AM_CPPFLAGS) + +if HAVE_ANDROID +libtorrent_rasterbar_la_LIBADD += -ldl +endif + +if HAVE_WINDOWS +libtorrent_rasterbar_la_LIBADD += -liphlpapi -lws2_32 -lwsock32 +libtorrent_rasterbar_la_CPPFLAGS += -DWIN32_LEAN_AND_MEAN -D__USE_W32_SOCKETS -DWIN32 -D_WIN32 +endif diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..f6266c6 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,2866 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@HAVE_ANDROID_TRUE@am__append_1 = -ldl +@HAVE_WINDOWS_TRUE@am__append_2 = -liphlpapi -lws2_32 -lwsock32 +@HAVE_WINDOWS_TRUE@am__append_3 = -DWIN32_LEAN_AND_MEAN -D__USE_W32_SOCKETS -DWIN32 -D_WIN32 +subdir = src +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libtorrent_rasterbar_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am__libtorrent_rasterbar_la_SOURCES_DIST = web_connection_base.cpp \ + alert.cpp alert_manager.cpp announce_entry.cpp assert.cpp \ + bandwidth_limit.cpp bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp bdecode.cpp bitfield.cpp \ + bloom_filter.cpp broadcast_socket.cpp block_cache.cpp \ + bt_peer_connection.cpp chained_buffer.cpp choker.cpp \ + close_reason.cpp cpuid.cpp crc32c.cpp create_torrent.cpp \ + disk_buffer_holder.cpp disk_buffer_pool.cpp disk_io_job.cpp \ + disk_io_thread.cpp disk_io_thread_pool.cpp disk_job_fence.cpp \ + disk_job_pool.cpp entry.cpp enum_net.cpp error_code.cpp \ + escape_string.cpp file.cpp path.cpp file_pool.cpp \ + file_storage.cpp fingerprint.cpp generate_peer_id.cpp gzip.cpp \ + hasher.cpp hex.cpp http_connection.cpp http_parser.cpp \ + http_seed_connection.cpp http_stream.cpp \ + http_tracker_connection.cpp i2p_stream.cpp identify_client.cpp \ + instantiate_connection.cpp ip_filter.cpp ip_notifier.cpp \ + ip_voter.cpp lazy_bdecode.cpp listen_socket_handle.cpp lsd.cpp \ + magnet_uri.cpp merkle.cpp natpmp.cpp openssl.cpp parse_url.cpp \ + part_file.cpp pe_crypto.cpp performance_counters.cpp \ + peer_connection.cpp peer_connection_handle.cpp peer_class.cpp \ + peer_class_set.cpp piece_picker.cpp platform_util.cpp \ + packet_buffer.cpp proxy_base.cpp peer_list.cpp puff.cpp \ + random.cpp receive_buffer.cpp read_resume_data.cpp \ + write_resume_data.cpp request_blocks.cpp resolve_links.cpp \ + resolver.cpp session.cpp session_call.cpp session_handle.cpp \ + session_impl.cpp session_settings.cpp proxy_settings.cpp \ + settings_pack.cpp sha1_hash.cpp smart_ban.cpp socket_io.cpp \ + socket_type.cpp socks5_stream.cpp stat.cpp stat_cache.cpp \ + storage.cpp storage_piece_set.cpp storage_utils.cpp \ + session_stats.cpp string_util.cpp torrent.cpp \ + torrent_handle.cpp torrent_info.cpp torrent_peer.cpp \ + torrent_peer_allocator.cpp torrent_status.cpp time.cpp \ + timestamp_history.cpp tracker_manager.cpp udp_socket.cpp \ + udp_tracker_connection.cpp upnp.cpp ut_metadata.cpp ut_pex.cpp \ + utf8.cpp utp_socket_manager.cpp utp_stream.cpp \ + web_peer_connection.cpp xml_parse.cpp version.cpp \ + file_progress.cpp ffs.cpp add_torrent_params.cpp peer_info.cpp \ + stack_allocator.cpp sha1.cpp sha512.cpp kademlia/dht_state.cpp \ + kademlia/dht_storage.cpp kademlia/dht_tracker.cpp \ + kademlia/find_data.cpp kademlia/put_data.cpp kademlia/msg.cpp \ + kademlia/node.cpp kademlia/node_entry.cpp kademlia/node_id.cpp \ + kademlia/refresh.cpp kademlia/routing_table.cpp \ + kademlia/rpc_manager.cpp kademlia/traversal_algorithm.cpp \ + kademlia/dos_blocker.cpp kademlia/get_peers.cpp \ + kademlia/get_item.cpp kademlia/item.cpp kademlia/ed25519.cpp \ + kademlia/sample_infohashes.cpp kademlia/dht_settings.cpp \ + ../ed25519/src/add_scalar.cpp ../ed25519/src/fe.cpp \ + ../ed25519/src/ge.cpp ../ed25519/src/key_exchange.cpp \ + ../ed25519/src/keypair.cpp ../ed25519/src/sc.cpp \ + ../ed25519/src/sign.cpp ../ed25519/src/verify.cpp \ + hasher512.cpp +am__dirstamp = $(am__leading_dot)dirstamp +@ENABLE_DHT_TRUE@am__objects_1 = kademlia/libtorrent_rasterbar_la-dht_state.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-dht_storage.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-dht_tracker.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-find_data.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-put_data.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-msg.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-node.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-node_entry.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-node_id.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-refresh.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-routing_table.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-rpc_manager.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-dos_blocker.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-get_peers.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-get_item.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-item.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-ed25519.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-sample_infohashes.lo \ +@ENABLE_DHT_TRUE@ kademlia/libtorrent_rasterbar_la-dht_settings.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-fe.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-ge.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-keypair.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-sc.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-sign.lo \ +@ENABLE_DHT_TRUE@ ../ed25519/src/libtorrent_rasterbar_la-verify.lo \ +@ENABLE_DHT_TRUE@ libtorrent_rasterbar_la-hasher512.lo +am_libtorrent_rasterbar_la_OBJECTS = \ + libtorrent_rasterbar_la-web_connection_base.lo \ + libtorrent_rasterbar_la-alert.lo \ + libtorrent_rasterbar_la-alert_manager.lo \ + libtorrent_rasterbar_la-announce_entry.lo \ + libtorrent_rasterbar_la-assert.lo \ + libtorrent_rasterbar_la-bandwidth_limit.lo \ + libtorrent_rasterbar_la-bandwidth_manager.lo \ + libtorrent_rasterbar_la-bandwidth_queue_entry.lo \ + libtorrent_rasterbar_la-bdecode.lo \ + libtorrent_rasterbar_la-bitfield.lo \ + libtorrent_rasterbar_la-bloom_filter.lo \ + libtorrent_rasterbar_la-broadcast_socket.lo \ + libtorrent_rasterbar_la-block_cache.lo \ + libtorrent_rasterbar_la-bt_peer_connection.lo \ + libtorrent_rasterbar_la-chained_buffer.lo \ + libtorrent_rasterbar_la-choker.lo \ + libtorrent_rasterbar_la-close_reason.lo \ + libtorrent_rasterbar_la-cpuid.lo \ + libtorrent_rasterbar_la-crc32c.lo \ + libtorrent_rasterbar_la-create_torrent.lo \ + libtorrent_rasterbar_la-disk_buffer_holder.lo \ + libtorrent_rasterbar_la-disk_buffer_pool.lo \ + libtorrent_rasterbar_la-disk_io_job.lo \ + libtorrent_rasterbar_la-disk_io_thread.lo \ + libtorrent_rasterbar_la-disk_io_thread_pool.lo \ + libtorrent_rasterbar_la-disk_job_fence.lo \ + libtorrent_rasterbar_la-disk_job_pool.lo \ + libtorrent_rasterbar_la-entry.lo \ + libtorrent_rasterbar_la-enum_net.lo \ + libtorrent_rasterbar_la-error_code.lo \ + libtorrent_rasterbar_la-escape_string.lo \ + libtorrent_rasterbar_la-file.lo \ + libtorrent_rasterbar_la-path.lo \ + libtorrent_rasterbar_la-file_pool.lo \ + libtorrent_rasterbar_la-file_storage.lo \ + libtorrent_rasterbar_la-fingerprint.lo \ + libtorrent_rasterbar_la-generate_peer_id.lo \ + libtorrent_rasterbar_la-gzip.lo \ + libtorrent_rasterbar_la-hasher.lo \ + libtorrent_rasterbar_la-hex.lo \ + libtorrent_rasterbar_la-http_connection.lo \ + libtorrent_rasterbar_la-http_parser.lo \ + libtorrent_rasterbar_la-http_seed_connection.lo \ + libtorrent_rasterbar_la-http_stream.lo \ + libtorrent_rasterbar_la-http_tracker_connection.lo \ + libtorrent_rasterbar_la-i2p_stream.lo \ + libtorrent_rasterbar_la-identify_client.lo \ + libtorrent_rasterbar_la-instantiate_connection.lo \ + libtorrent_rasterbar_la-ip_filter.lo \ + libtorrent_rasterbar_la-ip_notifier.lo \ + libtorrent_rasterbar_la-ip_voter.lo \ + libtorrent_rasterbar_la-lazy_bdecode.lo \ + libtorrent_rasterbar_la-listen_socket_handle.lo \ + libtorrent_rasterbar_la-lsd.lo \ + libtorrent_rasterbar_la-magnet_uri.lo \ + libtorrent_rasterbar_la-merkle.lo \ + libtorrent_rasterbar_la-natpmp.lo \ + libtorrent_rasterbar_la-openssl.lo \ + libtorrent_rasterbar_la-parse_url.lo \ + libtorrent_rasterbar_la-part_file.lo \ + libtorrent_rasterbar_la-pe_crypto.lo \ + libtorrent_rasterbar_la-performance_counters.lo \ + libtorrent_rasterbar_la-peer_connection.lo \ + libtorrent_rasterbar_la-peer_connection_handle.lo \ + libtorrent_rasterbar_la-peer_class.lo \ + libtorrent_rasterbar_la-peer_class_set.lo \ + libtorrent_rasterbar_la-piece_picker.lo \ + libtorrent_rasterbar_la-platform_util.lo \ + libtorrent_rasterbar_la-packet_buffer.lo \ + libtorrent_rasterbar_la-proxy_base.lo \ + libtorrent_rasterbar_la-peer_list.lo \ + libtorrent_rasterbar_la-puff.lo \ + libtorrent_rasterbar_la-random.lo \ + libtorrent_rasterbar_la-receive_buffer.lo \ + libtorrent_rasterbar_la-read_resume_data.lo \ + libtorrent_rasterbar_la-write_resume_data.lo \ + libtorrent_rasterbar_la-request_blocks.lo \ + libtorrent_rasterbar_la-resolve_links.lo \ + libtorrent_rasterbar_la-resolver.lo \ + libtorrent_rasterbar_la-session.lo \ + libtorrent_rasterbar_la-session_call.lo \ + libtorrent_rasterbar_la-session_handle.lo \ + libtorrent_rasterbar_la-session_impl.lo \ + libtorrent_rasterbar_la-session_settings.lo \ + libtorrent_rasterbar_la-proxy_settings.lo \ + libtorrent_rasterbar_la-settings_pack.lo \ + libtorrent_rasterbar_la-sha1_hash.lo \ + libtorrent_rasterbar_la-smart_ban.lo \ + libtorrent_rasterbar_la-socket_io.lo \ + libtorrent_rasterbar_la-socket_type.lo \ + libtorrent_rasterbar_la-socks5_stream.lo \ + libtorrent_rasterbar_la-stat.lo \ + libtorrent_rasterbar_la-stat_cache.lo \ + libtorrent_rasterbar_la-storage.lo \ + libtorrent_rasterbar_la-storage_piece_set.lo \ + libtorrent_rasterbar_la-storage_utils.lo \ + libtorrent_rasterbar_la-session_stats.lo \ + libtorrent_rasterbar_la-string_util.lo \ + libtorrent_rasterbar_la-torrent.lo \ + libtorrent_rasterbar_la-torrent_handle.lo \ + libtorrent_rasterbar_la-torrent_info.lo \ + libtorrent_rasterbar_la-torrent_peer.lo \ + libtorrent_rasterbar_la-torrent_peer_allocator.lo \ + libtorrent_rasterbar_la-torrent_status.lo \ + libtorrent_rasterbar_la-time.lo \ + libtorrent_rasterbar_la-timestamp_history.lo \ + libtorrent_rasterbar_la-tracker_manager.lo \ + libtorrent_rasterbar_la-udp_socket.lo \ + libtorrent_rasterbar_la-udp_tracker_connection.lo \ + libtorrent_rasterbar_la-upnp.lo \ + libtorrent_rasterbar_la-ut_metadata.lo \ + libtorrent_rasterbar_la-ut_pex.lo \ + libtorrent_rasterbar_la-utf8.lo \ + libtorrent_rasterbar_la-utp_socket_manager.lo \ + libtorrent_rasterbar_la-utp_stream.lo \ + libtorrent_rasterbar_la-web_peer_connection.lo \ + libtorrent_rasterbar_la-xml_parse.lo \ + libtorrent_rasterbar_la-version.lo \ + libtorrent_rasterbar_la-file_progress.lo \ + libtorrent_rasterbar_la-ffs.lo \ + libtorrent_rasterbar_la-add_torrent_params.lo \ + libtorrent_rasterbar_la-peer_info.lo \ + libtorrent_rasterbar_la-stack_allocator.lo \ + libtorrent_rasterbar_la-sha1.lo \ + libtorrent_rasterbar_la-sha512.lo $(am__objects_1) +libtorrent_rasterbar_la_OBJECTS = \ + $(am_libtorrent_rasterbar_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libtorrent_rasterbar_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(libtorrent_rasterbar_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Plo \ + ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-alert.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-assert.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bdecode.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bitfield.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-block_cache.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-choker.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-close_reason.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-cpuid.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-crc32c.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-entry.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-enum_net.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-error_code.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-escape_string.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-ffs.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-file.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-file_pool.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-file_progress.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-file_storage.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-gzip.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-hasher.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-hasher512.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-hex.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-http_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-http_parser.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-http_stream.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-identify_client.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-lsd.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-merkle.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-natpmp.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-openssl.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-parse_url.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-part_file.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-path.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-peer_class.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-peer_info.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-peer_list.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-platform_util.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-puff.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-random.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-resolver.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-session.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-session_call.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-session_handle.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-session_impl.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-session_settings.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-session_stats.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-sha1.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-sha512.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-socket_io.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-socket_type.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-stat.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-storage.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-string_util.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-time.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-torrent.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-upnp.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-utf8.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-version.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Plo \ + ./$(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Plo \ + kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libtorrent_rasterbar_la_SOURCES) +DIST_SOURCES = $(am__libtorrent_rasterbar_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = subdir-objects +lib_LTLIBRARIES = libtorrent-rasterbar.la +@ENABLE_DHT_TRUE@KADEMLIA_SOURCES = \ +@ENABLE_DHT_TRUE@ kademlia/dht_state.cpp \ +@ENABLE_DHT_TRUE@ kademlia/dht_storage.cpp \ +@ENABLE_DHT_TRUE@ kademlia/dht_tracker.cpp \ +@ENABLE_DHT_TRUE@ kademlia/find_data.cpp \ +@ENABLE_DHT_TRUE@ kademlia/put_data.cpp \ +@ENABLE_DHT_TRUE@ kademlia/msg.cpp \ +@ENABLE_DHT_TRUE@ kademlia/node.cpp \ +@ENABLE_DHT_TRUE@ kademlia/node_entry.cpp \ +@ENABLE_DHT_TRUE@ kademlia/node_id.cpp \ +@ENABLE_DHT_TRUE@ kademlia/refresh.cpp \ +@ENABLE_DHT_TRUE@ kademlia/routing_table.cpp \ +@ENABLE_DHT_TRUE@ kademlia/rpc_manager.cpp \ +@ENABLE_DHT_TRUE@ kademlia/traversal_algorithm.cpp \ +@ENABLE_DHT_TRUE@ kademlia/dos_blocker.cpp \ +@ENABLE_DHT_TRUE@ kademlia/get_peers.cpp \ +@ENABLE_DHT_TRUE@ kademlia/get_item.cpp \ +@ENABLE_DHT_TRUE@ kademlia/item.cpp \ +@ENABLE_DHT_TRUE@ kademlia/ed25519.cpp \ +@ENABLE_DHT_TRUE@ kademlia/sample_infohashes.cpp \ +@ENABLE_DHT_TRUE@ kademlia/dht_settings.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/add_scalar.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/fe.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/ge.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/key_exchange.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/keypair.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sc.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/sign.cpp \ +@ENABLE_DHT_TRUE@ ../ed25519/src/verify.cpp \ +@ENABLE_DHT_TRUE@ hasher512.cpp + +libtorrent_rasterbar_la_SOURCES = \ + web_connection_base.cpp \ + alert.cpp \ + alert_manager.cpp \ + announce_entry.cpp \ + assert.cpp \ + bandwidth_limit.cpp \ + bandwidth_manager.cpp \ + bandwidth_queue_entry.cpp \ + bdecode.cpp \ + bitfield.cpp \ + bloom_filter.cpp \ + broadcast_socket.cpp \ + block_cache.cpp \ + bt_peer_connection.cpp \ + chained_buffer.cpp \ + choker.cpp \ + close_reason.cpp \ + cpuid.cpp \ + crc32c.cpp \ + create_torrent.cpp \ + disk_buffer_holder.cpp \ + disk_buffer_pool.cpp \ + disk_io_job.cpp \ + disk_io_thread.cpp \ + disk_io_thread_pool.cpp \ + disk_job_fence.cpp \ + disk_job_pool.cpp \ + entry.cpp \ + enum_net.cpp \ + error_code.cpp \ + escape_string.cpp \ + file.cpp \ + path.cpp \ + file_pool.cpp \ + file_storage.cpp \ + fingerprint.cpp \ + generate_peer_id.cpp \ + gzip.cpp \ + hasher.cpp \ + hex.cpp \ + http_connection.cpp \ + http_parser.cpp \ + http_seed_connection.cpp \ + http_stream.cpp \ + http_tracker_connection.cpp \ + i2p_stream.cpp \ + identify_client.cpp \ + instantiate_connection.cpp \ + ip_filter.cpp \ + ip_notifier.cpp \ + ip_voter.cpp \ + lazy_bdecode.cpp \ + listen_socket_handle.cpp \ + lsd.cpp \ + magnet_uri.cpp \ + merkle.cpp \ + natpmp.cpp \ + openssl.cpp \ + parse_url.cpp \ + part_file.cpp \ + pe_crypto.cpp \ + performance_counters.cpp \ + peer_connection.cpp \ + peer_connection_handle.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ + piece_picker.cpp \ + platform_util.cpp \ + packet_buffer.cpp \ + proxy_base.cpp \ + peer_list.cpp \ + puff.cpp \ + random.cpp \ + receive_buffer.cpp \ + read_resume_data.cpp \ + write_resume_data.cpp \ + request_blocks.cpp \ + resolve_links.cpp \ + resolver.cpp \ + session.cpp \ + session_call.cpp \ + session_handle.cpp \ + session_impl.cpp \ + session_settings.cpp \ + proxy_settings.cpp \ + settings_pack.cpp \ + sha1_hash.cpp \ + smart_ban.cpp \ + socket_io.cpp \ + socket_type.cpp \ + socks5_stream.cpp \ + stat.cpp \ + stat_cache.cpp \ + storage.cpp \ + storage_piece_set.cpp \ + storage_utils.cpp \ + session_stats.cpp \ + string_util.cpp \ + torrent.cpp \ + torrent_handle.cpp \ + torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ + torrent_status.cpp \ + time.cpp \ + timestamp_history.cpp \ + tracker_manager.cpp \ + udp_socket.cpp \ + udp_tracker_connection.cpp \ + upnp.cpp \ + ut_metadata.cpp \ + ut_pex.cpp \ + utf8.cpp \ + utp_socket_manager.cpp \ + utp_stream.cpp \ + web_peer_connection.cpp \ + xml_parse.cpp \ + version.cpp \ + file_progress.cpp \ + ffs.cpp \ + add_torrent_params.cpp \ + peer_info.cpp \ + stack_allocator.cpp \ + sha1.cpp \ + sha512.cpp \ + \ + $(KADEMLIA_SOURCES) + +AM_CPPFLAGS = -DTORRENT_BUILDING_LIBRARY @DEBUGFLAGS@ +AM_LDFLAGS = @OPENSSL_LDFLAGS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ +libtorrent_rasterbar_la_LDFLAGS = -version-info $(INTERFACE_VERSION_INFO) +libtorrent_rasterbar_la_LIBADD = @OPENSSL_LIBS@ $(am__append_1) \ + $(am__append_2) +libtorrent_rasterbar_la_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_3) +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +kademlia/$(am__dirstamp): + @$(MKDIR_P) kademlia + @: > kademlia/$(am__dirstamp) +kademlia/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) kademlia/$(DEPDIR) + @: > kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-dht_state.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-dht_storage.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-dht_tracker.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-find_data.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-put_data.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-msg.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-node.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-node_entry.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-node_id.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-refresh.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-routing_table.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-rpc_manager.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-dos_blocker.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-get_peers.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-get_item.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-item.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-ed25519.lo: kademlia/$(am__dirstamp) \ + kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-sample_infohashes.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +kademlia/libtorrent_rasterbar_la-dht_settings.lo: \ + kademlia/$(am__dirstamp) kademlia/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/$(am__dirstamp): + @$(MKDIR_P) ../ed25519/src + @: > ../ed25519/src/$(am__dirstamp) +../ed25519/src/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ../ed25519/src/$(DEPDIR) + @: > ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-fe.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-ge.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-keypair.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-sc.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-sign.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) +../ed25519/src/libtorrent_rasterbar_la-verify.lo: \ + ../ed25519/src/$(am__dirstamp) \ + ../ed25519/src/$(DEPDIR)/$(am__dirstamp) + +libtorrent-rasterbar.la: $(libtorrent_rasterbar_la_OBJECTS) $(libtorrent_rasterbar_la_DEPENDENCIES) $(EXTRA_libtorrent_rasterbar_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libtorrent_rasterbar_la_LINK) -rpath $(libdir) $(libtorrent_rasterbar_la_OBJECTS) $(libtorrent_rasterbar_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f ../ed25519/src/*.$(OBJEXT) + -rm -f ../ed25519/src/*.lo + -rm -f kademlia/*.$(OBJEXT) + -rm -f kademlia/*.lo + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-alert.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-assert.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bdecode.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bitfield.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-block_cache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-choker.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-close_reason.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-cpuid.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-crc32c.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-entry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-enum_net.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-error_code.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-escape_string.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-ffs.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-file_pool.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-file_progress.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-file_storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-gzip.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-hasher.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-hasher512.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-hex.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-http_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-http_parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-http_stream.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-identify_client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-lsd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-merkle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-natpmp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-openssl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-parse_url.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-part_file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-path.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-peer_class.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-peer_info.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-peer_list.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-platform_util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-puff.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-random.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-resolver.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-session.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-session_call.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-session_handle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-session_impl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-session_settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-session_stats.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-sha1.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-sha512.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-socket_io.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-socket_type.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-stat.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-string_util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-time.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-torrent.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-upnp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-utf8.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-version.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libtorrent_rasterbar_la-web_connection_base.lo: web_connection_base.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-web_connection_base.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Tpo -c -o libtorrent_rasterbar_la-web_connection_base.lo `test -f 'web_connection_base.cpp' || echo '$(srcdir)/'`web_connection_base.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Tpo $(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='web_connection_base.cpp' object='libtorrent_rasterbar_la-web_connection_base.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-web_connection_base.lo `test -f 'web_connection_base.cpp' || echo '$(srcdir)/'`web_connection_base.cpp + +libtorrent_rasterbar_la-alert.lo: alert.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-alert.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-alert.Tpo -c -o libtorrent_rasterbar_la-alert.lo `test -f 'alert.cpp' || echo '$(srcdir)/'`alert.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-alert.Tpo $(DEPDIR)/libtorrent_rasterbar_la-alert.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alert.cpp' object='libtorrent_rasterbar_la-alert.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-alert.lo `test -f 'alert.cpp' || echo '$(srcdir)/'`alert.cpp + +libtorrent_rasterbar_la-alert_manager.lo: alert_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-alert_manager.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Tpo -c -o libtorrent_rasterbar_la-alert_manager.lo `test -f 'alert_manager.cpp' || echo '$(srcdir)/'`alert_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Tpo $(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alert_manager.cpp' object='libtorrent_rasterbar_la-alert_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-alert_manager.lo `test -f 'alert_manager.cpp' || echo '$(srcdir)/'`alert_manager.cpp + +libtorrent_rasterbar_la-announce_entry.lo: announce_entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-announce_entry.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Tpo -c -o libtorrent_rasterbar_la-announce_entry.lo `test -f 'announce_entry.cpp' || echo '$(srcdir)/'`announce_entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Tpo $(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='announce_entry.cpp' object='libtorrent_rasterbar_la-announce_entry.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-announce_entry.lo `test -f 'announce_entry.cpp' || echo '$(srcdir)/'`announce_entry.cpp + +libtorrent_rasterbar_la-assert.lo: assert.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-assert.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-assert.Tpo -c -o libtorrent_rasterbar_la-assert.lo `test -f 'assert.cpp' || echo '$(srcdir)/'`assert.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-assert.Tpo $(DEPDIR)/libtorrent_rasterbar_la-assert.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='assert.cpp' object='libtorrent_rasterbar_la-assert.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-assert.lo `test -f 'assert.cpp' || echo '$(srcdir)/'`assert.cpp + +libtorrent_rasterbar_la-bandwidth_limit.lo: bandwidth_limit.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bandwidth_limit.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Tpo -c -o libtorrent_rasterbar_la-bandwidth_limit.lo `test -f 'bandwidth_limit.cpp' || echo '$(srcdir)/'`bandwidth_limit.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bandwidth_limit.cpp' object='libtorrent_rasterbar_la-bandwidth_limit.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bandwidth_limit.lo `test -f 'bandwidth_limit.cpp' || echo '$(srcdir)/'`bandwidth_limit.cpp + +libtorrent_rasterbar_la-bandwidth_manager.lo: bandwidth_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bandwidth_manager.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Tpo -c -o libtorrent_rasterbar_la-bandwidth_manager.lo `test -f 'bandwidth_manager.cpp' || echo '$(srcdir)/'`bandwidth_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bandwidth_manager.cpp' object='libtorrent_rasterbar_la-bandwidth_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bandwidth_manager.lo `test -f 'bandwidth_manager.cpp' || echo '$(srcdir)/'`bandwidth_manager.cpp + +libtorrent_rasterbar_la-bandwidth_queue_entry.lo: bandwidth_queue_entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bandwidth_queue_entry.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Tpo -c -o libtorrent_rasterbar_la-bandwidth_queue_entry.lo `test -f 'bandwidth_queue_entry.cpp' || echo '$(srcdir)/'`bandwidth_queue_entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bandwidth_queue_entry.cpp' object='libtorrent_rasterbar_la-bandwidth_queue_entry.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bandwidth_queue_entry.lo `test -f 'bandwidth_queue_entry.cpp' || echo '$(srcdir)/'`bandwidth_queue_entry.cpp + +libtorrent_rasterbar_la-bdecode.lo: bdecode.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bdecode.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bdecode.Tpo -c -o libtorrent_rasterbar_la-bdecode.lo `test -f 'bdecode.cpp' || echo '$(srcdir)/'`bdecode.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bdecode.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bdecode.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bdecode.cpp' object='libtorrent_rasterbar_la-bdecode.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bdecode.lo `test -f 'bdecode.cpp' || echo '$(srcdir)/'`bdecode.cpp + +libtorrent_rasterbar_la-bitfield.lo: bitfield.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bitfield.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bitfield.Tpo -c -o libtorrent_rasterbar_la-bitfield.lo `test -f 'bitfield.cpp' || echo '$(srcdir)/'`bitfield.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bitfield.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bitfield.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bitfield.cpp' object='libtorrent_rasterbar_la-bitfield.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bitfield.lo `test -f 'bitfield.cpp' || echo '$(srcdir)/'`bitfield.cpp + +libtorrent_rasterbar_la-bloom_filter.lo: bloom_filter.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bloom_filter.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Tpo -c -o libtorrent_rasterbar_la-bloom_filter.lo `test -f 'bloom_filter.cpp' || echo '$(srcdir)/'`bloom_filter.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bloom_filter.cpp' object='libtorrent_rasterbar_la-bloom_filter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bloom_filter.lo `test -f 'bloom_filter.cpp' || echo '$(srcdir)/'`bloom_filter.cpp + +libtorrent_rasterbar_la-broadcast_socket.lo: broadcast_socket.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-broadcast_socket.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Tpo -c -o libtorrent_rasterbar_la-broadcast_socket.lo `test -f 'broadcast_socket.cpp' || echo '$(srcdir)/'`broadcast_socket.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Tpo $(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='broadcast_socket.cpp' object='libtorrent_rasterbar_la-broadcast_socket.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-broadcast_socket.lo `test -f 'broadcast_socket.cpp' || echo '$(srcdir)/'`broadcast_socket.cpp + +libtorrent_rasterbar_la-block_cache.lo: block_cache.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-block_cache.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-block_cache.Tpo -c -o libtorrent_rasterbar_la-block_cache.lo `test -f 'block_cache.cpp' || echo '$(srcdir)/'`block_cache.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-block_cache.Tpo $(DEPDIR)/libtorrent_rasterbar_la-block_cache.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='block_cache.cpp' object='libtorrent_rasterbar_la-block_cache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-block_cache.lo `test -f 'block_cache.cpp' || echo '$(srcdir)/'`block_cache.cpp + +libtorrent_rasterbar_la-bt_peer_connection.lo: bt_peer_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-bt_peer_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Tpo -c -o libtorrent_rasterbar_la-bt_peer_connection.lo `test -f 'bt_peer_connection.cpp' || echo '$(srcdir)/'`bt_peer_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bt_peer_connection.cpp' object='libtorrent_rasterbar_la-bt_peer_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-bt_peer_connection.lo `test -f 'bt_peer_connection.cpp' || echo '$(srcdir)/'`bt_peer_connection.cpp + +libtorrent_rasterbar_la-chained_buffer.lo: chained_buffer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-chained_buffer.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Tpo -c -o libtorrent_rasterbar_la-chained_buffer.lo `test -f 'chained_buffer.cpp' || echo '$(srcdir)/'`chained_buffer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Tpo $(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='chained_buffer.cpp' object='libtorrent_rasterbar_la-chained_buffer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-chained_buffer.lo `test -f 'chained_buffer.cpp' || echo '$(srcdir)/'`chained_buffer.cpp + +libtorrent_rasterbar_la-choker.lo: choker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-choker.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-choker.Tpo -c -o libtorrent_rasterbar_la-choker.lo `test -f 'choker.cpp' || echo '$(srcdir)/'`choker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-choker.Tpo $(DEPDIR)/libtorrent_rasterbar_la-choker.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='choker.cpp' object='libtorrent_rasterbar_la-choker.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-choker.lo `test -f 'choker.cpp' || echo '$(srcdir)/'`choker.cpp + +libtorrent_rasterbar_la-close_reason.lo: close_reason.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-close_reason.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-close_reason.Tpo -c -o libtorrent_rasterbar_la-close_reason.lo `test -f 'close_reason.cpp' || echo '$(srcdir)/'`close_reason.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-close_reason.Tpo $(DEPDIR)/libtorrent_rasterbar_la-close_reason.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='close_reason.cpp' object='libtorrent_rasterbar_la-close_reason.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-close_reason.lo `test -f 'close_reason.cpp' || echo '$(srcdir)/'`close_reason.cpp + +libtorrent_rasterbar_la-cpuid.lo: cpuid.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-cpuid.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-cpuid.Tpo -c -o libtorrent_rasterbar_la-cpuid.lo `test -f 'cpuid.cpp' || echo '$(srcdir)/'`cpuid.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-cpuid.Tpo $(DEPDIR)/libtorrent_rasterbar_la-cpuid.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cpuid.cpp' object='libtorrent_rasterbar_la-cpuid.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-cpuid.lo `test -f 'cpuid.cpp' || echo '$(srcdir)/'`cpuid.cpp + +libtorrent_rasterbar_la-crc32c.lo: crc32c.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-crc32c.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-crc32c.Tpo -c -o libtorrent_rasterbar_la-crc32c.lo `test -f 'crc32c.cpp' || echo '$(srcdir)/'`crc32c.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-crc32c.Tpo $(DEPDIR)/libtorrent_rasterbar_la-crc32c.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='crc32c.cpp' object='libtorrent_rasterbar_la-crc32c.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-crc32c.lo `test -f 'crc32c.cpp' || echo '$(srcdir)/'`crc32c.cpp + +libtorrent_rasterbar_la-create_torrent.lo: create_torrent.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-create_torrent.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Tpo -c -o libtorrent_rasterbar_la-create_torrent.lo `test -f 'create_torrent.cpp' || echo '$(srcdir)/'`create_torrent.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Tpo $(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='create_torrent.cpp' object='libtorrent_rasterbar_la-create_torrent.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-create_torrent.lo `test -f 'create_torrent.cpp' || echo '$(srcdir)/'`create_torrent.cpp + +libtorrent_rasterbar_la-disk_buffer_holder.lo: disk_buffer_holder.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_buffer_holder.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Tpo -c -o libtorrent_rasterbar_la-disk_buffer_holder.lo `test -f 'disk_buffer_holder.cpp' || echo '$(srcdir)/'`disk_buffer_holder.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_buffer_holder.cpp' object='libtorrent_rasterbar_la-disk_buffer_holder.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_buffer_holder.lo `test -f 'disk_buffer_holder.cpp' || echo '$(srcdir)/'`disk_buffer_holder.cpp + +libtorrent_rasterbar_la-disk_buffer_pool.lo: disk_buffer_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_buffer_pool.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Tpo -c -o libtorrent_rasterbar_la-disk_buffer_pool.lo `test -f 'disk_buffer_pool.cpp' || echo '$(srcdir)/'`disk_buffer_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_buffer_pool.cpp' object='libtorrent_rasterbar_la-disk_buffer_pool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_buffer_pool.lo `test -f 'disk_buffer_pool.cpp' || echo '$(srcdir)/'`disk_buffer_pool.cpp + +libtorrent_rasterbar_la-disk_io_job.lo: disk_io_job.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_io_job.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Tpo -c -o libtorrent_rasterbar_la-disk_io_job.lo `test -f 'disk_io_job.cpp' || echo '$(srcdir)/'`disk_io_job.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_io_job.cpp' object='libtorrent_rasterbar_la-disk_io_job.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_io_job.lo `test -f 'disk_io_job.cpp' || echo '$(srcdir)/'`disk_io_job.cpp + +libtorrent_rasterbar_la-disk_io_thread.lo: disk_io_thread.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_io_thread.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Tpo -c -o libtorrent_rasterbar_la-disk_io_thread.lo `test -f 'disk_io_thread.cpp' || echo '$(srcdir)/'`disk_io_thread.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_io_thread.cpp' object='libtorrent_rasterbar_la-disk_io_thread.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_io_thread.lo `test -f 'disk_io_thread.cpp' || echo '$(srcdir)/'`disk_io_thread.cpp + +libtorrent_rasterbar_la-disk_io_thread_pool.lo: disk_io_thread_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_io_thread_pool.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Tpo -c -o libtorrent_rasterbar_la-disk_io_thread_pool.lo `test -f 'disk_io_thread_pool.cpp' || echo '$(srcdir)/'`disk_io_thread_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_io_thread_pool.cpp' object='libtorrent_rasterbar_la-disk_io_thread_pool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_io_thread_pool.lo `test -f 'disk_io_thread_pool.cpp' || echo '$(srcdir)/'`disk_io_thread_pool.cpp + +libtorrent_rasterbar_la-disk_job_fence.lo: disk_job_fence.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_job_fence.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Tpo -c -o libtorrent_rasterbar_la-disk_job_fence.lo `test -f 'disk_job_fence.cpp' || echo '$(srcdir)/'`disk_job_fence.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_job_fence.cpp' object='libtorrent_rasterbar_la-disk_job_fence.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_job_fence.lo `test -f 'disk_job_fence.cpp' || echo '$(srcdir)/'`disk_job_fence.cpp + +libtorrent_rasterbar_la-disk_job_pool.lo: disk_job_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-disk_job_pool.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Tpo -c -o libtorrent_rasterbar_la-disk_job_pool.lo `test -f 'disk_job_pool.cpp' || echo '$(srcdir)/'`disk_job_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Tpo $(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='disk_job_pool.cpp' object='libtorrent_rasterbar_la-disk_job_pool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-disk_job_pool.lo `test -f 'disk_job_pool.cpp' || echo '$(srcdir)/'`disk_job_pool.cpp + +libtorrent_rasterbar_la-entry.lo: entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-entry.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-entry.Tpo -c -o libtorrent_rasterbar_la-entry.lo `test -f 'entry.cpp' || echo '$(srcdir)/'`entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-entry.Tpo $(DEPDIR)/libtorrent_rasterbar_la-entry.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='entry.cpp' object='libtorrent_rasterbar_la-entry.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-entry.lo `test -f 'entry.cpp' || echo '$(srcdir)/'`entry.cpp + +libtorrent_rasterbar_la-enum_net.lo: enum_net.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-enum_net.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-enum_net.Tpo -c -o libtorrent_rasterbar_la-enum_net.lo `test -f 'enum_net.cpp' || echo '$(srcdir)/'`enum_net.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-enum_net.Tpo $(DEPDIR)/libtorrent_rasterbar_la-enum_net.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='enum_net.cpp' object='libtorrent_rasterbar_la-enum_net.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-enum_net.lo `test -f 'enum_net.cpp' || echo '$(srcdir)/'`enum_net.cpp + +libtorrent_rasterbar_la-error_code.lo: error_code.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-error_code.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-error_code.Tpo -c -o libtorrent_rasterbar_la-error_code.lo `test -f 'error_code.cpp' || echo '$(srcdir)/'`error_code.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-error_code.Tpo $(DEPDIR)/libtorrent_rasterbar_la-error_code.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='error_code.cpp' object='libtorrent_rasterbar_la-error_code.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-error_code.lo `test -f 'error_code.cpp' || echo '$(srcdir)/'`error_code.cpp + +libtorrent_rasterbar_la-escape_string.lo: escape_string.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-escape_string.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-escape_string.Tpo -c -o libtorrent_rasterbar_la-escape_string.lo `test -f 'escape_string.cpp' || echo '$(srcdir)/'`escape_string.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-escape_string.Tpo $(DEPDIR)/libtorrent_rasterbar_la-escape_string.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='escape_string.cpp' object='libtorrent_rasterbar_la-escape_string.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-escape_string.lo `test -f 'escape_string.cpp' || echo '$(srcdir)/'`escape_string.cpp + +libtorrent_rasterbar_la-file.lo: file.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-file.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-file.Tpo -c -o libtorrent_rasterbar_la-file.lo `test -f 'file.cpp' || echo '$(srcdir)/'`file.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-file.Tpo $(DEPDIR)/libtorrent_rasterbar_la-file.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file.cpp' object='libtorrent_rasterbar_la-file.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-file.lo `test -f 'file.cpp' || echo '$(srcdir)/'`file.cpp + +libtorrent_rasterbar_la-path.lo: path.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-path.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-path.Tpo -c -o libtorrent_rasterbar_la-path.lo `test -f 'path.cpp' || echo '$(srcdir)/'`path.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-path.Tpo $(DEPDIR)/libtorrent_rasterbar_la-path.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='path.cpp' object='libtorrent_rasterbar_la-path.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-path.lo `test -f 'path.cpp' || echo '$(srcdir)/'`path.cpp + +libtorrent_rasterbar_la-file_pool.lo: file_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-file_pool.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-file_pool.Tpo -c -o libtorrent_rasterbar_la-file_pool.lo `test -f 'file_pool.cpp' || echo '$(srcdir)/'`file_pool.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-file_pool.Tpo $(DEPDIR)/libtorrent_rasterbar_la-file_pool.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_pool.cpp' object='libtorrent_rasterbar_la-file_pool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-file_pool.lo `test -f 'file_pool.cpp' || echo '$(srcdir)/'`file_pool.cpp + +libtorrent_rasterbar_la-file_storage.lo: file_storage.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-file_storage.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-file_storage.Tpo -c -o libtorrent_rasterbar_la-file_storage.lo `test -f 'file_storage.cpp' || echo '$(srcdir)/'`file_storage.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-file_storage.Tpo $(DEPDIR)/libtorrent_rasterbar_la-file_storage.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_storage.cpp' object='libtorrent_rasterbar_la-file_storage.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-file_storage.lo `test -f 'file_storage.cpp' || echo '$(srcdir)/'`file_storage.cpp + +libtorrent_rasterbar_la-fingerprint.lo: fingerprint.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-fingerprint.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Tpo -c -o libtorrent_rasterbar_la-fingerprint.lo `test -f 'fingerprint.cpp' || echo '$(srcdir)/'`fingerprint.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Tpo $(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fingerprint.cpp' object='libtorrent_rasterbar_la-fingerprint.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-fingerprint.lo `test -f 'fingerprint.cpp' || echo '$(srcdir)/'`fingerprint.cpp + +libtorrent_rasterbar_la-generate_peer_id.lo: generate_peer_id.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-generate_peer_id.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Tpo -c -o libtorrent_rasterbar_la-generate_peer_id.lo `test -f 'generate_peer_id.cpp' || echo '$(srcdir)/'`generate_peer_id.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Tpo $(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generate_peer_id.cpp' object='libtorrent_rasterbar_la-generate_peer_id.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-generate_peer_id.lo `test -f 'generate_peer_id.cpp' || echo '$(srcdir)/'`generate_peer_id.cpp + +libtorrent_rasterbar_la-gzip.lo: gzip.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-gzip.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-gzip.Tpo -c -o libtorrent_rasterbar_la-gzip.lo `test -f 'gzip.cpp' || echo '$(srcdir)/'`gzip.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-gzip.Tpo $(DEPDIR)/libtorrent_rasterbar_la-gzip.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='gzip.cpp' object='libtorrent_rasterbar_la-gzip.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-gzip.lo `test -f 'gzip.cpp' || echo '$(srcdir)/'`gzip.cpp + +libtorrent_rasterbar_la-hasher.lo: hasher.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-hasher.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-hasher.Tpo -c -o libtorrent_rasterbar_la-hasher.lo `test -f 'hasher.cpp' || echo '$(srcdir)/'`hasher.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-hasher.Tpo $(DEPDIR)/libtorrent_rasterbar_la-hasher.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hasher.cpp' object='libtorrent_rasterbar_la-hasher.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-hasher.lo `test -f 'hasher.cpp' || echo '$(srcdir)/'`hasher.cpp + +libtorrent_rasterbar_la-hex.lo: hex.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-hex.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-hex.Tpo -c -o libtorrent_rasterbar_la-hex.lo `test -f 'hex.cpp' || echo '$(srcdir)/'`hex.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-hex.Tpo $(DEPDIR)/libtorrent_rasterbar_la-hex.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hex.cpp' object='libtorrent_rasterbar_la-hex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-hex.lo `test -f 'hex.cpp' || echo '$(srcdir)/'`hex.cpp + +libtorrent_rasterbar_la-http_connection.lo: http_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-http_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-http_connection.Tpo -c -o libtorrent_rasterbar_la-http_connection.lo `test -f 'http_connection.cpp' || echo '$(srcdir)/'`http_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-http_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-http_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_connection.cpp' object='libtorrent_rasterbar_la-http_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-http_connection.lo `test -f 'http_connection.cpp' || echo '$(srcdir)/'`http_connection.cpp + +libtorrent_rasterbar_la-http_parser.lo: http_parser.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-http_parser.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-http_parser.Tpo -c -o libtorrent_rasterbar_la-http_parser.lo `test -f 'http_parser.cpp' || echo '$(srcdir)/'`http_parser.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-http_parser.Tpo $(DEPDIR)/libtorrent_rasterbar_la-http_parser.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_parser.cpp' object='libtorrent_rasterbar_la-http_parser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-http_parser.lo `test -f 'http_parser.cpp' || echo '$(srcdir)/'`http_parser.cpp + +libtorrent_rasterbar_la-http_seed_connection.lo: http_seed_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-http_seed_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Tpo -c -o libtorrent_rasterbar_la-http_seed_connection.lo `test -f 'http_seed_connection.cpp' || echo '$(srcdir)/'`http_seed_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_seed_connection.cpp' object='libtorrent_rasterbar_la-http_seed_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-http_seed_connection.lo `test -f 'http_seed_connection.cpp' || echo '$(srcdir)/'`http_seed_connection.cpp + +libtorrent_rasterbar_la-http_stream.lo: http_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-http_stream.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-http_stream.Tpo -c -o libtorrent_rasterbar_la-http_stream.lo `test -f 'http_stream.cpp' || echo '$(srcdir)/'`http_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-http_stream.Tpo $(DEPDIR)/libtorrent_rasterbar_la-http_stream.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_stream.cpp' object='libtorrent_rasterbar_la-http_stream.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-http_stream.lo `test -f 'http_stream.cpp' || echo '$(srcdir)/'`http_stream.cpp + +libtorrent_rasterbar_la-http_tracker_connection.lo: http_tracker_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-http_tracker_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Tpo -c -o libtorrent_rasterbar_la-http_tracker_connection.lo `test -f 'http_tracker_connection.cpp' || echo '$(srcdir)/'`http_tracker_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_tracker_connection.cpp' object='libtorrent_rasterbar_la-http_tracker_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-http_tracker_connection.lo `test -f 'http_tracker_connection.cpp' || echo '$(srcdir)/'`http_tracker_connection.cpp + +libtorrent_rasterbar_la-i2p_stream.lo: i2p_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-i2p_stream.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Tpo -c -o libtorrent_rasterbar_la-i2p_stream.lo `test -f 'i2p_stream.cpp' || echo '$(srcdir)/'`i2p_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Tpo $(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='i2p_stream.cpp' object='libtorrent_rasterbar_la-i2p_stream.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-i2p_stream.lo `test -f 'i2p_stream.cpp' || echo '$(srcdir)/'`i2p_stream.cpp + +libtorrent_rasterbar_la-identify_client.lo: identify_client.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-identify_client.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-identify_client.Tpo -c -o libtorrent_rasterbar_la-identify_client.lo `test -f 'identify_client.cpp' || echo '$(srcdir)/'`identify_client.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-identify_client.Tpo $(DEPDIR)/libtorrent_rasterbar_la-identify_client.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='identify_client.cpp' object='libtorrent_rasterbar_la-identify_client.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-identify_client.lo `test -f 'identify_client.cpp' || echo '$(srcdir)/'`identify_client.cpp + +libtorrent_rasterbar_la-instantiate_connection.lo: instantiate_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-instantiate_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Tpo -c -o libtorrent_rasterbar_la-instantiate_connection.lo `test -f 'instantiate_connection.cpp' || echo '$(srcdir)/'`instantiate_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='instantiate_connection.cpp' object='libtorrent_rasterbar_la-instantiate_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-instantiate_connection.lo `test -f 'instantiate_connection.cpp' || echo '$(srcdir)/'`instantiate_connection.cpp + +libtorrent_rasterbar_la-ip_filter.lo: ip_filter.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-ip_filter.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Tpo -c -o libtorrent_rasterbar_la-ip_filter.lo `test -f 'ip_filter.cpp' || echo '$(srcdir)/'`ip_filter.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Tpo $(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_filter.cpp' object='libtorrent_rasterbar_la-ip_filter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-ip_filter.lo `test -f 'ip_filter.cpp' || echo '$(srcdir)/'`ip_filter.cpp + +libtorrent_rasterbar_la-ip_notifier.lo: ip_notifier.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-ip_notifier.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Tpo -c -o libtorrent_rasterbar_la-ip_notifier.lo `test -f 'ip_notifier.cpp' || echo '$(srcdir)/'`ip_notifier.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Tpo $(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_notifier.cpp' object='libtorrent_rasterbar_la-ip_notifier.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-ip_notifier.lo `test -f 'ip_notifier.cpp' || echo '$(srcdir)/'`ip_notifier.cpp + +libtorrent_rasterbar_la-ip_voter.lo: ip_voter.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-ip_voter.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Tpo -c -o libtorrent_rasterbar_la-ip_voter.lo `test -f 'ip_voter.cpp' || echo '$(srcdir)/'`ip_voter.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Tpo $(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_voter.cpp' object='libtorrent_rasterbar_la-ip_voter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-ip_voter.lo `test -f 'ip_voter.cpp' || echo '$(srcdir)/'`ip_voter.cpp + +libtorrent_rasterbar_la-lazy_bdecode.lo: lazy_bdecode.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-lazy_bdecode.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Tpo -c -o libtorrent_rasterbar_la-lazy_bdecode.lo `test -f 'lazy_bdecode.cpp' || echo '$(srcdir)/'`lazy_bdecode.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Tpo $(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lazy_bdecode.cpp' object='libtorrent_rasterbar_la-lazy_bdecode.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-lazy_bdecode.lo `test -f 'lazy_bdecode.cpp' || echo '$(srcdir)/'`lazy_bdecode.cpp + +libtorrent_rasterbar_la-listen_socket_handle.lo: listen_socket_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-listen_socket_handle.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Tpo -c -o libtorrent_rasterbar_la-listen_socket_handle.lo `test -f 'listen_socket_handle.cpp' || echo '$(srcdir)/'`listen_socket_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Tpo $(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='listen_socket_handle.cpp' object='libtorrent_rasterbar_la-listen_socket_handle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-listen_socket_handle.lo `test -f 'listen_socket_handle.cpp' || echo '$(srcdir)/'`listen_socket_handle.cpp + +libtorrent_rasterbar_la-lsd.lo: lsd.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-lsd.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-lsd.Tpo -c -o libtorrent_rasterbar_la-lsd.lo `test -f 'lsd.cpp' || echo '$(srcdir)/'`lsd.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-lsd.Tpo $(DEPDIR)/libtorrent_rasterbar_la-lsd.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lsd.cpp' object='libtorrent_rasterbar_la-lsd.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-lsd.lo `test -f 'lsd.cpp' || echo '$(srcdir)/'`lsd.cpp + +libtorrent_rasterbar_la-magnet_uri.lo: magnet_uri.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-magnet_uri.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Tpo -c -o libtorrent_rasterbar_la-magnet_uri.lo `test -f 'magnet_uri.cpp' || echo '$(srcdir)/'`magnet_uri.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Tpo $(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='magnet_uri.cpp' object='libtorrent_rasterbar_la-magnet_uri.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-magnet_uri.lo `test -f 'magnet_uri.cpp' || echo '$(srcdir)/'`magnet_uri.cpp + +libtorrent_rasterbar_la-merkle.lo: merkle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-merkle.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-merkle.Tpo -c -o libtorrent_rasterbar_la-merkle.lo `test -f 'merkle.cpp' || echo '$(srcdir)/'`merkle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-merkle.Tpo $(DEPDIR)/libtorrent_rasterbar_la-merkle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='merkle.cpp' object='libtorrent_rasterbar_la-merkle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-merkle.lo `test -f 'merkle.cpp' || echo '$(srcdir)/'`merkle.cpp + +libtorrent_rasterbar_la-natpmp.lo: natpmp.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-natpmp.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-natpmp.Tpo -c -o libtorrent_rasterbar_la-natpmp.lo `test -f 'natpmp.cpp' || echo '$(srcdir)/'`natpmp.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-natpmp.Tpo $(DEPDIR)/libtorrent_rasterbar_la-natpmp.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='natpmp.cpp' object='libtorrent_rasterbar_la-natpmp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-natpmp.lo `test -f 'natpmp.cpp' || echo '$(srcdir)/'`natpmp.cpp + +libtorrent_rasterbar_la-openssl.lo: openssl.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-openssl.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-openssl.Tpo -c -o libtorrent_rasterbar_la-openssl.lo `test -f 'openssl.cpp' || echo '$(srcdir)/'`openssl.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-openssl.Tpo $(DEPDIR)/libtorrent_rasterbar_la-openssl.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='openssl.cpp' object='libtorrent_rasterbar_la-openssl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-openssl.lo `test -f 'openssl.cpp' || echo '$(srcdir)/'`openssl.cpp + +libtorrent_rasterbar_la-parse_url.lo: parse_url.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-parse_url.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-parse_url.Tpo -c -o libtorrent_rasterbar_la-parse_url.lo `test -f 'parse_url.cpp' || echo '$(srcdir)/'`parse_url.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-parse_url.Tpo $(DEPDIR)/libtorrent_rasterbar_la-parse_url.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parse_url.cpp' object='libtorrent_rasterbar_la-parse_url.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-parse_url.lo `test -f 'parse_url.cpp' || echo '$(srcdir)/'`parse_url.cpp + +libtorrent_rasterbar_la-part_file.lo: part_file.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-part_file.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-part_file.Tpo -c -o libtorrent_rasterbar_la-part_file.lo `test -f 'part_file.cpp' || echo '$(srcdir)/'`part_file.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-part_file.Tpo $(DEPDIR)/libtorrent_rasterbar_la-part_file.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='part_file.cpp' object='libtorrent_rasterbar_la-part_file.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-part_file.lo `test -f 'part_file.cpp' || echo '$(srcdir)/'`part_file.cpp + +libtorrent_rasterbar_la-pe_crypto.lo: pe_crypto.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-pe_crypto.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Tpo -c -o libtorrent_rasterbar_la-pe_crypto.lo `test -f 'pe_crypto.cpp' || echo '$(srcdir)/'`pe_crypto.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Tpo $(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pe_crypto.cpp' object='libtorrent_rasterbar_la-pe_crypto.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-pe_crypto.lo `test -f 'pe_crypto.cpp' || echo '$(srcdir)/'`pe_crypto.cpp + +libtorrent_rasterbar_la-performance_counters.lo: performance_counters.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-performance_counters.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Tpo -c -o libtorrent_rasterbar_la-performance_counters.lo `test -f 'performance_counters.cpp' || echo '$(srcdir)/'`performance_counters.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Tpo $(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='performance_counters.cpp' object='libtorrent_rasterbar_la-performance_counters.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-performance_counters.lo `test -f 'performance_counters.cpp' || echo '$(srcdir)/'`performance_counters.cpp + +libtorrent_rasterbar_la-peer_connection.lo: peer_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-peer_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Tpo -c -o libtorrent_rasterbar_la-peer_connection.lo `test -f 'peer_connection.cpp' || echo '$(srcdir)/'`peer_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='peer_connection.cpp' object='libtorrent_rasterbar_la-peer_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-peer_connection.lo `test -f 'peer_connection.cpp' || echo '$(srcdir)/'`peer_connection.cpp + +libtorrent_rasterbar_la-peer_connection_handle.lo: peer_connection_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-peer_connection_handle.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Tpo -c -o libtorrent_rasterbar_la-peer_connection_handle.lo `test -f 'peer_connection_handle.cpp' || echo '$(srcdir)/'`peer_connection_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Tpo $(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='peer_connection_handle.cpp' object='libtorrent_rasterbar_la-peer_connection_handle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-peer_connection_handle.lo `test -f 'peer_connection_handle.cpp' || echo '$(srcdir)/'`peer_connection_handle.cpp + +libtorrent_rasterbar_la-peer_class.lo: peer_class.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-peer_class.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-peer_class.Tpo -c -o libtorrent_rasterbar_la-peer_class.lo `test -f 'peer_class.cpp' || echo '$(srcdir)/'`peer_class.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-peer_class.Tpo $(DEPDIR)/libtorrent_rasterbar_la-peer_class.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='peer_class.cpp' object='libtorrent_rasterbar_la-peer_class.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-peer_class.lo `test -f 'peer_class.cpp' || echo '$(srcdir)/'`peer_class.cpp + +libtorrent_rasterbar_la-peer_class_set.lo: peer_class_set.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-peer_class_set.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Tpo -c -o libtorrent_rasterbar_la-peer_class_set.lo `test -f 'peer_class_set.cpp' || echo '$(srcdir)/'`peer_class_set.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Tpo $(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='peer_class_set.cpp' object='libtorrent_rasterbar_la-peer_class_set.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-peer_class_set.lo `test -f 'peer_class_set.cpp' || echo '$(srcdir)/'`peer_class_set.cpp + +libtorrent_rasterbar_la-piece_picker.lo: piece_picker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-piece_picker.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Tpo -c -o libtorrent_rasterbar_la-piece_picker.lo `test -f 'piece_picker.cpp' || echo '$(srcdir)/'`piece_picker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Tpo $(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='piece_picker.cpp' object='libtorrent_rasterbar_la-piece_picker.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-piece_picker.lo `test -f 'piece_picker.cpp' || echo '$(srcdir)/'`piece_picker.cpp + +libtorrent_rasterbar_la-platform_util.lo: platform_util.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-platform_util.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-platform_util.Tpo -c -o libtorrent_rasterbar_la-platform_util.lo `test -f 'platform_util.cpp' || echo '$(srcdir)/'`platform_util.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-platform_util.Tpo $(DEPDIR)/libtorrent_rasterbar_la-platform_util.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='platform_util.cpp' object='libtorrent_rasterbar_la-platform_util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-platform_util.lo `test -f 'platform_util.cpp' || echo '$(srcdir)/'`platform_util.cpp + +libtorrent_rasterbar_la-packet_buffer.lo: packet_buffer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-packet_buffer.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Tpo -c -o libtorrent_rasterbar_la-packet_buffer.lo `test -f 'packet_buffer.cpp' || echo '$(srcdir)/'`packet_buffer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Tpo $(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_buffer.cpp' object='libtorrent_rasterbar_la-packet_buffer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-packet_buffer.lo `test -f 'packet_buffer.cpp' || echo '$(srcdir)/'`packet_buffer.cpp + +libtorrent_rasterbar_la-proxy_base.lo: proxy_base.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-proxy_base.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Tpo -c -o libtorrent_rasterbar_la-proxy_base.lo `test -f 'proxy_base.cpp' || echo '$(srcdir)/'`proxy_base.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Tpo $(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='proxy_base.cpp' object='libtorrent_rasterbar_la-proxy_base.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-proxy_base.lo `test -f 'proxy_base.cpp' || echo '$(srcdir)/'`proxy_base.cpp + +libtorrent_rasterbar_la-peer_list.lo: peer_list.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-peer_list.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-peer_list.Tpo -c -o libtorrent_rasterbar_la-peer_list.lo `test -f 'peer_list.cpp' || echo '$(srcdir)/'`peer_list.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-peer_list.Tpo $(DEPDIR)/libtorrent_rasterbar_la-peer_list.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='peer_list.cpp' object='libtorrent_rasterbar_la-peer_list.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-peer_list.lo `test -f 'peer_list.cpp' || echo '$(srcdir)/'`peer_list.cpp + +libtorrent_rasterbar_la-puff.lo: puff.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-puff.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-puff.Tpo -c -o libtorrent_rasterbar_la-puff.lo `test -f 'puff.cpp' || echo '$(srcdir)/'`puff.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-puff.Tpo $(DEPDIR)/libtorrent_rasterbar_la-puff.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='puff.cpp' object='libtorrent_rasterbar_la-puff.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-puff.lo `test -f 'puff.cpp' || echo '$(srcdir)/'`puff.cpp + +libtorrent_rasterbar_la-random.lo: random.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-random.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-random.Tpo -c -o libtorrent_rasterbar_la-random.lo `test -f 'random.cpp' || echo '$(srcdir)/'`random.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-random.Tpo $(DEPDIR)/libtorrent_rasterbar_la-random.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random.cpp' object='libtorrent_rasterbar_la-random.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-random.lo `test -f 'random.cpp' || echo '$(srcdir)/'`random.cpp + +libtorrent_rasterbar_la-receive_buffer.lo: receive_buffer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-receive_buffer.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Tpo -c -o libtorrent_rasterbar_la-receive_buffer.lo `test -f 'receive_buffer.cpp' || echo '$(srcdir)/'`receive_buffer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Tpo $(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='receive_buffer.cpp' object='libtorrent_rasterbar_la-receive_buffer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-receive_buffer.lo `test -f 'receive_buffer.cpp' || echo '$(srcdir)/'`receive_buffer.cpp + +libtorrent_rasterbar_la-read_resume_data.lo: read_resume_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-read_resume_data.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Tpo -c -o libtorrent_rasterbar_la-read_resume_data.lo `test -f 'read_resume_data.cpp' || echo '$(srcdir)/'`read_resume_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Tpo $(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='read_resume_data.cpp' object='libtorrent_rasterbar_la-read_resume_data.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-read_resume_data.lo `test -f 'read_resume_data.cpp' || echo '$(srcdir)/'`read_resume_data.cpp + +libtorrent_rasterbar_la-write_resume_data.lo: write_resume_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-write_resume_data.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Tpo -c -o libtorrent_rasterbar_la-write_resume_data.lo `test -f 'write_resume_data.cpp' || echo '$(srcdir)/'`write_resume_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Tpo $(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='write_resume_data.cpp' object='libtorrent_rasterbar_la-write_resume_data.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-write_resume_data.lo `test -f 'write_resume_data.cpp' || echo '$(srcdir)/'`write_resume_data.cpp + +libtorrent_rasterbar_la-request_blocks.lo: request_blocks.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-request_blocks.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Tpo -c -o libtorrent_rasterbar_la-request_blocks.lo `test -f 'request_blocks.cpp' || echo '$(srcdir)/'`request_blocks.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Tpo $(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_blocks.cpp' object='libtorrent_rasterbar_la-request_blocks.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-request_blocks.lo `test -f 'request_blocks.cpp' || echo '$(srcdir)/'`request_blocks.cpp + +libtorrent_rasterbar_la-resolve_links.lo: resolve_links.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-resolve_links.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Tpo -c -o libtorrent_rasterbar_la-resolve_links.lo `test -f 'resolve_links.cpp' || echo '$(srcdir)/'`resolve_links.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Tpo $(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resolve_links.cpp' object='libtorrent_rasterbar_la-resolve_links.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-resolve_links.lo `test -f 'resolve_links.cpp' || echo '$(srcdir)/'`resolve_links.cpp + +libtorrent_rasterbar_la-resolver.lo: resolver.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-resolver.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-resolver.Tpo -c -o libtorrent_rasterbar_la-resolver.lo `test -f 'resolver.cpp' || echo '$(srcdir)/'`resolver.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-resolver.Tpo $(DEPDIR)/libtorrent_rasterbar_la-resolver.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resolver.cpp' object='libtorrent_rasterbar_la-resolver.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-resolver.lo `test -f 'resolver.cpp' || echo '$(srcdir)/'`resolver.cpp + +libtorrent_rasterbar_la-session.lo: session.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-session.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-session.Tpo -c -o libtorrent_rasterbar_la-session.lo `test -f 'session.cpp' || echo '$(srcdir)/'`session.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-session.Tpo $(DEPDIR)/libtorrent_rasterbar_la-session.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='session.cpp' object='libtorrent_rasterbar_la-session.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-session.lo `test -f 'session.cpp' || echo '$(srcdir)/'`session.cpp + +libtorrent_rasterbar_la-session_call.lo: session_call.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-session_call.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-session_call.Tpo -c -o libtorrent_rasterbar_la-session_call.lo `test -f 'session_call.cpp' || echo '$(srcdir)/'`session_call.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-session_call.Tpo $(DEPDIR)/libtorrent_rasterbar_la-session_call.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='session_call.cpp' object='libtorrent_rasterbar_la-session_call.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-session_call.lo `test -f 'session_call.cpp' || echo '$(srcdir)/'`session_call.cpp + +libtorrent_rasterbar_la-session_handle.lo: session_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-session_handle.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-session_handle.Tpo -c -o libtorrent_rasterbar_la-session_handle.lo `test -f 'session_handle.cpp' || echo '$(srcdir)/'`session_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-session_handle.Tpo $(DEPDIR)/libtorrent_rasterbar_la-session_handle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='session_handle.cpp' object='libtorrent_rasterbar_la-session_handle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-session_handle.lo `test -f 'session_handle.cpp' || echo '$(srcdir)/'`session_handle.cpp + +libtorrent_rasterbar_la-session_impl.lo: session_impl.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-session_impl.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-session_impl.Tpo -c -o libtorrent_rasterbar_la-session_impl.lo `test -f 'session_impl.cpp' || echo '$(srcdir)/'`session_impl.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-session_impl.Tpo $(DEPDIR)/libtorrent_rasterbar_la-session_impl.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='session_impl.cpp' object='libtorrent_rasterbar_la-session_impl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-session_impl.lo `test -f 'session_impl.cpp' || echo '$(srcdir)/'`session_impl.cpp + +libtorrent_rasterbar_la-session_settings.lo: session_settings.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-session_settings.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-session_settings.Tpo -c -o libtorrent_rasterbar_la-session_settings.lo `test -f 'session_settings.cpp' || echo '$(srcdir)/'`session_settings.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-session_settings.Tpo $(DEPDIR)/libtorrent_rasterbar_la-session_settings.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='session_settings.cpp' object='libtorrent_rasterbar_la-session_settings.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-session_settings.lo `test -f 'session_settings.cpp' || echo '$(srcdir)/'`session_settings.cpp + +libtorrent_rasterbar_la-proxy_settings.lo: proxy_settings.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-proxy_settings.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Tpo -c -o libtorrent_rasterbar_la-proxy_settings.lo `test -f 'proxy_settings.cpp' || echo '$(srcdir)/'`proxy_settings.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Tpo $(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='proxy_settings.cpp' object='libtorrent_rasterbar_la-proxy_settings.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-proxy_settings.lo `test -f 'proxy_settings.cpp' || echo '$(srcdir)/'`proxy_settings.cpp + +libtorrent_rasterbar_la-settings_pack.lo: settings_pack.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-settings_pack.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Tpo -c -o libtorrent_rasterbar_la-settings_pack.lo `test -f 'settings_pack.cpp' || echo '$(srcdir)/'`settings_pack.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Tpo $(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='settings_pack.cpp' object='libtorrent_rasterbar_la-settings_pack.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-settings_pack.lo `test -f 'settings_pack.cpp' || echo '$(srcdir)/'`settings_pack.cpp + +libtorrent_rasterbar_la-sha1_hash.lo: sha1_hash.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-sha1_hash.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Tpo -c -o libtorrent_rasterbar_la-sha1_hash.lo `test -f 'sha1_hash.cpp' || echo '$(srcdir)/'`sha1_hash.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Tpo $(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sha1_hash.cpp' object='libtorrent_rasterbar_la-sha1_hash.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-sha1_hash.lo `test -f 'sha1_hash.cpp' || echo '$(srcdir)/'`sha1_hash.cpp + +libtorrent_rasterbar_la-smart_ban.lo: smart_ban.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-smart_ban.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Tpo -c -o libtorrent_rasterbar_la-smart_ban.lo `test -f 'smart_ban.cpp' || echo '$(srcdir)/'`smart_ban.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Tpo $(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='smart_ban.cpp' object='libtorrent_rasterbar_la-smart_ban.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-smart_ban.lo `test -f 'smart_ban.cpp' || echo '$(srcdir)/'`smart_ban.cpp + +libtorrent_rasterbar_la-socket_io.lo: socket_io.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-socket_io.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-socket_io.Tpo -c -o libtorrent_rasterbar_la-socket_io.lo `test -f 'socket_io.cpp' || echo '$(srcdir)/'`socket_io.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-socket_io.Tpo $(DEPDIR)/libtorrent_rasterbar_la-socket_io.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='socket_io.cpp' object='libtorrent_rasterbar_la-socket_io.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-socket_io.lo `test -f 'socket_io.cpp' || echo '$(srcdir)/'`socket_io.cpp + +libtorrent_rasterbar_la-socket_type.lo: socket_type.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-socket_type.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-socket_type.Tpo -c -o libtorrent_rasterbar_la-socket_type.lo `test -f 'socket_type.cpp' || echo '$(srcdir)/'`socket_type.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-socket_type.Tpo $(DEPDIR)/libtorrent_rasterbar_la-socket_type.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='socket_type.cpp' object='libtorrent_rasterbar_la-socket_type.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-socket_type.lo `test -f 'socket_type.cpp' || echo '$(srcdir)/'`socket_type.cpp + +libtorrent_rasterbar_la-socks5_stream.lo: socks5_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-socks5_stream.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Tpo -c -o libtorrent_rasterbar_la-socks5_stream.lo `test -f 'socks5_stream.cpp' || echo '$(srcdir)/'`socks5_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Tpo $(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='socks5_stream.cpp' object='libtorrent_rasterbar_la-socks5_stream.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-socks5_stream.lo `test -f 'socks5_stream.cpp' || echo '$(srcdir)/'`socks5_stream.cpp + +libtorrent_rasterbar_la-stat.lo: stat.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-stat.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-stat.Tpo -c -o libtorrent_rasterbar_la-stat.lo `test -f 'stat.cpp' || echo '$(srcdir)/'`stat.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-stat.Tpo $(DEPDIR)/libtorrent_rasterbar_la-stat.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stat.cpp' object='libtorrent_rasterbar_la-stat.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-stat.lo `test -f 'stat.cpp' || echo '$(srcdir)/'`stat.cpp + +libtorrent_rasterbar_la-stat_cache.lo: stat_cache.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-stat_cache.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Tpo -c -o libtorrent_rasterbar_la-stat_cache.lo `test -f 'stat_cache.cpp' || echo '$(srcdir)/'`stat_cache.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Tpo $(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stat_cache.cpp' object='libtorrent_rasterbar_la-stat_cache.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-stat_cache.lo `test -f 'stat_cache.cpp' || echo '$(srcdir)/'`stat_cache.cpp + +libtorrent_rasterbar_la-storage.lo: storage.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-storage.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-storage.Tpo -c -o libtorrent_rasterbar_la-storage.lo `test -f 'storage.cpp' || echo '$(srcdir)/'`storage.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-storage.Tpo $(DEPDIR)/libtorrent_rasterbar_la-storage.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='storage.cpp' object='libtorrent_rasterbar_la-storage.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-storage.lo `test -f 'storage.cpp' || echo '$(srcdir)/'`storage.cpp + +libtorrent_rasterbar_la-storage_piece_set.lo: storage_piece_set.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-storage_piece_set.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Tpo -c -o libtorrent_rasterbar_la-storage_piece_set.lo `test -f 'storage_piece_set.cpp' || echo '$(srcdir)/'`storage_piece_set.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Tpo $(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='storage_piece_set.cpp' object='libtorrent_rasterbar_la-storage_piece_set.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-storage_piece_set.lo `test -f 'storage_piece_set.cpp' || echo '$(srcdir)/'`storage_piece_set.cpp + +libtorrent_rasterbar_la-storage_utils.lo: storage_utils.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-storage_utils.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Tpo -c -o libtorrent_rasterbar_la-storage_utils.lo `test -f 'storage_utils.cpp' || echo '$(srcdir)/'`storage_utils.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Tpo $(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='storage_utils.cpp' object='libtorrent_rasterbar_la-storage_utils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-storage_utils.lo `test -f 'storage_utils.cpp' || echo '$(srcdir)/'`storage_utils.cpp + +libtorrent_rasterbar_la-session_stats.lo: session_stats.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-session_stats.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-session_stats.Tpo -c -o libtorrent_rasterbar_la-session_stats.lo `test -f 'session_stats.cpp' || echo '$(srcdir)/'`session_stats.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-session_stats.Tpo $(DEPDIR)/libtorrent_rasterbar_la-session_stats.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='session_stats.cpp' object='libtorrent_rasterbar_la-session_stats.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-session_stats.lo `test -f 'session_stats.cpp' || echo '$(srcdir)/'`session_stats.cpp + +libtorrent_rasterbar_la-string_util.lo: string_util.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-string_util.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-string_util.Tpo -c -o libtorrent_rasterbar_la-string_util.lo `test -f 'string_util.cpp' || echo '$(srcdir)/'`string_util.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-string_util.Tpo $(DEPDIR)/libtorrent_rasterbar_la-string_util.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='string_util.cpp' object='libtorrent_rasterbar_la-string_util.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-string_util.lo `test -f 'string_util.cpp' || echo '$(srcdir)/'`string_util.cpp + +libtorrent_rasterbar_la-torrent.lo: torrent.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-torrent.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-torrent.Tpo -c -o libtorrent_rasterbar_la-torrent.lo `test -f 'torrent.cpp' || echo '$(srcdir)/'`torrent.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-torrent.Tpo $(DEPDIR)/libtorrent_rasterbar_la-torrent.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='torrent.cpp' object='libtorrent_rasterbar_la-torrent.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-torrent.lo `test -f 'torrent.cpp' || echo '$(srcdir)/'`torrent.cpp + +libtorrent_rasterbar_la-torrent_handle.lo: torrent_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-torrent_handle.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Tpo -c -o libtorrent_rasterbar_la-torrent_handle.lo `test -f 'torrent_handle.cpp' || echo '$(srcdir)/'`torrent_handle.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Tpo $(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='torrent_handle.cpp' object='libtorrent_rasterbar_la-torrent_handle.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-torrent_handle.lo `test -f 'torrent_handle.cpp' || echo '$(srcdir)/'`torrent_handle.cpp + +libtorrent_rasterbar_la-torrent_info.lo: torrent_info.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-torrent_info.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Tpo -c -o libtorrent_rasterbar_la-torrent_info.lo `test -f 'torrent_info.cpp' || echo '$(srcdir)/'`torrent_info.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Tpo $(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='torrent_info.cpp' object='libtorrent_rasterbar_la-torrent_info.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-torrent_info.lo `test -f 'torrent_info.cpp' || echo '$(srcdir)/'`torrent_info.cpp + +libtorrent_rasterbar_la-torrent_peer.lo: torrent_peer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-torrent_peer.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Tpo -c -o libtorrent_rasterbar_la-torrent_peer.lo `test -f 'torrent_peer.cpp' || echo '$(srcdir)/'`torrent_peer.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Tpo $(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='torrent_peer.cpp' object='libtorrent_rasterbar_la-torrent_peer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-torrent_peer.lo `test -f 'torrent_peer.cpp' || echo '$(srcdir)/'`torrent_peer.cpp + +libtorrent_rasterbar_la-torrent_peer_allocator.lo: torrent_peer_allocator.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-torrent_peer_allocator.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Tpo -c -o libtorrent_rasterbar_la-torrent_peer_allocator.lo `test -f 'torrent_peer_allocator.cpp' || echo '$(srcdir)/'`torrent_peer_allocator.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Tpo $(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='torrent_peer_allocator.cpp' object='libtorrent_rasterbar_la-torrent_peer_allocator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-torrent_peer_allocator.lo `test -f 'torrent_peer_allocator.cpp' || echo '$(srcdir)/'`torrent_peer_allocator.cpp + +libtorrent_rasterbar_la-torrent_status.lo: torrent_status.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-torrent_status.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Tpo -c -o libtorrent_rasterbar_la-torrent_status.lo `test -f 'torrent_status.cpp' || echo '$(srcdir)/'`torrent_status.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Tpo $(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='torrent_status.cpp' object='libtorrent_rasterbar_la-torrent_status.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-torrent_status.lo `test -f 'torrent_status.cpp' || echo '$(srcdir)/'`torrent_status.cpp + +libtorrent_rasterbar_la-time.lo: time.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-time.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-time.Tpo -c -o libtorrent_rasterbar_la-time.lo `test -f 'time.cpp' || echo '$(srcdir)/'`time.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-time.Tpo $(DEPDIR)/libtorrent_rasterbar_la-time.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time.cpp' object='libtorrent_rasterbar_la-time.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-time.lo `test -f 'time.cpp' || echo '$(srcdir)/'`time.cpp + +libtorrent_rasterbar_la-timestamp_history.lo: timestamp_history.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-timestamp_history.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Tpo -c -o libtorrent_rasterbar_la-timestamp_history.lo `test -f 'timestamp_history.cpp' || echo '$(srcdir)/'`timestamp_history.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Tpo $(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='timestamp_history.cpp' object='libtorrent_rasterbar_la-timestamp_history.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-timestamp_history.lo `test -f 'timestamp_history.cpp' || echo '$(srcdir)/'`timestamp_history.cpp + +libtorrent_rasterbar_la-tracker_manager.lo: tracker_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-tracker_manager.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Tpo -c -o libtorrent_rasterbar_la-tracker_manager.lo `test -f 'tracker_manager.cpp' || echo '$(srcdir)/'`tracker_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Tpo $(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tracker_manager.cpp' object='libtorrent_rasterbar_la-tracker_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-tracker_manager.lo `test -f 'tracker_manager.cpp' || echo '$(srcdir)/'`tracker_manager.cpp + +libtorrent_rasterbar_la-udp_socket.lo: udp_socket.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-udp_socket.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Tpo -c -o libtorrent_rasterbar_la-udp_socket.lo `test -f 'udp_socket.cpp' || echo '$(srcdir)/'`udp_socket.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Tpo $(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='udp_socket.cpp' object='libtorrent_rasterbar_la-udp_socket.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-udp_socket.lo `test -f 'udp_socket.cpp' || echo '$(srcdir)/'`udp_socket.cpp + +libtorrent_rasterbar_la-udp_tracker_connection.lo: udp_tracker_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-udp_tracker_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Tpo -c -o libtorrent_rasterbar_la-udp_tracker_connection.lo `test -f 'udp_tracker_connection.cpp' || echo '$(srcdir)/'`udp_tracker_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='udp_tracker_connection.cpp' object='libtorrent_rasterbar_la-udp_tracker_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-udp_tracker_connection.lo `test -f 'udp_tracker_connection.cpp' || echo '$(srcdir)/'`udp_tracker_connection.cpp + +libtorrent_rasterbar_la-upnp.lo: upnp.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-upnp.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-upnp.Tpo -c -o libtorrent_rasterbar_la-upnp.lo `test -f 'upnp.cpp' || echo '$(srcdir)/'`upnp.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-upnp.Tpo $(DEPDIR)/libtorrent_rasterbar_la-upnp.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='upnp.cpp' object='libtorrent_rasterbar_la-upnp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-upnp.lo `test -f 'upnp.cpp' || echo '$(srcdir)/'`upnp.cpp + +libtorrent_rasterbar_la-ut_metadata.lo: ut_metadata.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-ut_metadata.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Tpo -c -o libtorrent_rasterbar_la-ut_metadata.lo `test -f 'ut_metadata.cpp' || echo '$(srcdir)/'`ut_metadata.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Tpo $(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ut_metadata.cpp' object='libtorrent_rasterbar_la-ut_metadata.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-ut_metadata.lo `test -f 'ut_metadata.cpp' || echo '$(srcdir)/'`ut_metadata.cpp + +libtorrent_rasterbar_la-ut_pex.lo: ut_pex.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-ut_pex.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Tpo -c -o libtorrent_rasterbar_la-ut_pex.lo `test -f 'ut_pex.cpp' || echo '$(srcdir)/'`ut_pex.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Tpo $(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ut_pex.cpp' object='libtorrent_rasterbar_la-ut_pex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-ut_pex.lo `test -f 'ut_pex.cpp' || echo '$(srcdir)/'`ut_pex.cpp + +libtorrent_rasterbar_la-utf8.lo: utf8.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-utf8.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-utf8.Tpo -c -o libtorrent_rasterbar_la-utf8.lo `test -f 'utf8.cpp' || echo '$(srcdir)/'`utf8.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-utf8.Tpo $(DEPDIR)/libtorrent_rasterbar_la-utf8.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utf8.cpp' object='libtorrent_rasterbar_la-utf8.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-utf8.lo `test -f 'utf8.cpp' || echo '$(srcdir)/'`utf8.cpp + +libtorrent_rasterbar_la-utp_socket_manager.lo: utp_socket_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-utp_socket_manager.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Tpo -c -o libtorrent_rasterbar_la-utp_socket_manager.lo `test -f 'utp_socket_manager.cpp' || echo '$(srcdir)/'`utp_socket_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Tpo $(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utp_socket_manager.cpp' object='libtorrent_rasterbar_la-utp_socket_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-utp_socket_manager.lo `test -f 'utp_socket_manager.cpp' || echo '$(srcdir)/'`utp_socket_manager.cpp + +libtorrent_rasterbar_la-utp_stream.lo: utp_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-utp_stream.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Tpo -c -o libtorrent_rasterbar_la-utp_stream.lo `test -f 'utp_stream.cpp' || echo '$(srcdir)/'`utp_stream.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Tpo $(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utp_stream.cpp' object='libtorrent_rasterbar_la-utp_stream.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-utp_stream.lo `test -f 'utp_stream.cpp' || echo '$(srcdir)/'`utp_stream.cpp + +libtorrent_rasterbar_la-web_peer_connection.lo: web_peer_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-web_peer_connection.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Tpo -c -o libtorrent_rasterbar_la-web_peer_connection.lo `test -f 'web_peer_connection.cpp' || echo '$(srcdir)/'`web_peer_connection.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Tpo $(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='web_peer_connection.cpp' object='libtorrent_rasterbar_la-web_peer_connection.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-web_peer_connection.lo `test -f 'web_peer_connection.cpp' || echo '$(srcdir)/'`web_peer_connection.cpp + +libtorrent_rasterbar_la-xml_parse.lo: xml_parse.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-xml_parse.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Tpo -c -o libtorrent_rasterbar_la-xml_parse.lo `test -f 'xml_parse.cpp' || echo '$(srcdir)/'`xml_parse.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Tpo $(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='xml_parse.cpp' object='libtorrent_rasterbar_la-xml_parse.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-xml_parse.lo `test -f 'xml_parse.cpp' || echo '$(srcdir)/'`xml_parse.cpp + +libtorrent_rasterbar_la-version.lo: version.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-version.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-version.Tpo -c -o libtorrent_rasterbar_la-version.lo `test -f 'version.cpp' || echo '$(srcdir)/'`version.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-version.Tpo $(DEPDIR)/libtorrent_rasterbar_la-version.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='version.cpp' object='libtorrent_rasterbar_la-version.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-version.lo `test -f 'version.cpp' || echo '$(srcdir)/'`version.cpp + +libtorrent_rasterbar_la-file_progress.lo: file_progress.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-file_progress.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-file_progress.Tpo -c -o libtorrent_rasterbar_la-file_progress.lo `test -f 'file_progress.cpp' || echo '$(srcdir)/'`file_progress.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-file_progress.Tpo $(DEPDIR)/libtorrent_rasterbar_la-file_progress.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_progress.cpp' object='libtorrent_rasterbar_la-file_progress.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-file_progress.lo `test -f 'file_progress.cpp' || echo '$(srcdir)/'`file_progress.cpp + +libtorrent_rasterbar_la-ffs.lo: ffs.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-ffs.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-ffs.Tpo -c -o libtorrent_rasterbar_la-ffs.lo `test -f 'ffs.cpp' || echo '$(srcdir)/'`ffs.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-ffs.Tpo $(DEPDIR)/libtorrent_rasterbar_la-ffs.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ffs.cpp' object='libtorrent_rasterbar_la-ffs.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-ffs.lo `test -f 'ffs.cpp' || echo '$(srcdir)/'`ffs.cpp + +libtorrent_rasterbar_la-add_torrent_params.lo: add_torrent_params.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-add_torrent_params.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Tpo -c -o libtorrent_rasterbar_la-add_torrent_params.lo `test -f 'add_torrent_params.cpp' || echo '$(srcdir)/'`add_torrent_params.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Tpo $(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='add_torrent_params.cpp' object='libtorrent_rasterbar_la-add_torrent_params.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-add_torrent_params.lo `test -f 'add_torrent_params.cpp' || echo '$(srcdir)/'`add_torrent_params.cpp + +libtorrent_rasterbar_la-peer_info.lo: peer_info.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-peer_info.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-peer_info.Tpo -c -o libtorrent_rasterbar_la-peer_info.lo `test -f 'peer_info.cpp' || echo '$(srcdir)/'`peer_info.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-peer_info.Tpo $(DEPDIR)/libtorrent_rasterbar_la-peer_info.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='peer_info.cpp' object='libtorrent_rasterbar_la-peer_info.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-peer_info.lo `test -f 'peer_info.cpp' || echo '$(srcdir)/'`peer_info.cpp + +libtorrent_rasterbar_la-stack_allocator.lo: stack_allocator.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-stack_allocator.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Tpo -c -o libtorrent_rasterbar_la-stack_allocator.lo `test -f 'stack_allocator.cpp' || echo '$(srcdir)/'`stack_allocator.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Tpo $(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stack_allocator.cpp' object='libtorrent_rasterbar_la-stack_allocator.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-stack_allocator.lo `test -f 'stack_allocator.cpp' || echo '$(srcdir)/'`stack_allocator.cpp + +libtorrent_rasterbar_la-sha1.lo: sha1.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-sha1.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-sha1.Tpo -c -o libtorrent_rasterbar_la-sha1.lo `test -f 'sha1.cpp' || echo '$(srcdir)/'`sha1.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-sha1.Tpo $(DEPDIR)/libtorrent_rasterbar_la-sha1.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sha1.cpp' object='libtorrent_rasterbar_la-sha1.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-sha1.lo `test -f 'sha1.cpp' || echo '$(srcdir)/'`sha1.cpp + +libtorrent_rasterbar_la-sha512.lo: sha512.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-sha512.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-sha512.Tpo -c -o libtorrent_rasterbar_la-sha512.lo `test -f 'sha512.cpp' || echo '$(srcdir)/'`sha512.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-sha512.Tpo $(DEPDIR)/libtorrent_rasterbar_la-sha512.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sha512.cpp' object='libtorrent_rasterbar_la-sha512.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-sha512.lo `test -f 'sha512.cpp' || echo '$(srcdir)/'`sha512.cpp + +kademlia/libtorrent_rasterbar_la-dht_state.lo: kademlia/dht_state.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-dht_state.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Tpo -c -o kademlia/libtorrent_rasterbar_la-dht_state.lo `test -f 'kademlia/dht_state.cpp' || echo '$(srcdir)/'`kademlia/dht_state.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/dht_state.cpp' object='kademlia/libtorrent_rasterbar_la-dht_state.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-dht_state.lo `test -f 'kademlia/dht_state.cpp' || echo '$(srcdir)/'`kademlia/dht_state.cpp + +kademlia/libtorrent_rasterbar_la-dht_storage.lo: kademlia/dht_storage.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-dht_storage.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Tpo -c -o kademlia/libtorrent_rasterbar_la-dht_storage.lo `test -f 'kademlia/dht_storage.cpp' || echo '$(srcdir)/'`kademlia/dht_storage.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/dht_storage.cpp' object='kademlia/libtorrent_rasterbar_la-dht_storage.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-dht_storage.lo `test -f 'kademlia/dht_storage.cpp' || echo '$(srcdir)/'`kademlia/dht_storage.cpp + +kademlia/libtorrent_rasterbar_la-dht_tracker.lo: kademlia/dht_tracker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-dht_tracker.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Tpo -c -o kademlia/libtorrent_rasterbar_la-dht_tracker.lo `test -f 'kademlia/dht_tracker.cpp' || echo '$(srcdir)/'`kademlia/dht_tracker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/dht_tracker.cpp' object='kademlia/libtorrent_rasterbar_la-dht_tracker.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-dht_tracker.lo `test -f 'kademlia/dht_tracker.cpp' || echo '$(srcdir)/'`kademlia/dht_tracker.cpp + +kademlia/libtorrent_rasterbar_la-find_data.lo: kademlia/find_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-find_data.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Tpo -c -o kademlia/libtorrent_rasterbar_la-find_data.lo `test -f 'kademlia/find_data.cpp' || echo '$(srcdir)/'`kademlia/find_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/find_data.cpp' object='kademlia/libtorrent_rasterbar_la-find_data.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-find_data.lo `test -f 'kademlia/find_data.cpp' || echo '$(srcdir)/'`kademlia/find_data.cpp + +kademlia/libtorrent_rasterbar_la-put_data.lo: kademlia/put_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-put_data.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Tpo -c -o kademlia/libtorrent_rasterbar_la-put_data.lo `test -f 'kademlia/put_data.cpp' || echo '$(srcdir)/'`kademlia/put_data.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/put_data.cpp' object='kademlia/libtorrent_rasterbar_la-put_data.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-put_data.lo `test -f 'kademlia/put_data.cpp' || echo '$(srcdir)/'`kademlia/put_data.cpp + +kademlia/libtorrent_rasterbar_la-msg.lo: kademlia/msg.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-msg.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Tpo -c -o kademlia/libtorrent_rasterbar_la-msg.lo `test -f 'kademlia/msg.cpp' || echo '$(srcdir)/'`kademlia/msg.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/msg.cpp' object='kademlia/libtorrent_rasterbar_la-msg.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-msg.lo `test -f 'kademlia/msg.cpp' || echo '$(srcdir)/'`kademlia/msg.cpp + +kademlia/libtorrent_rasterbar_la-node.lo: kademlia/node.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-node.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Tpo -c -o kademlia/libtorrent_rasterbar_la-node.lo `test -f 'kademlia/node.cpp' || echo '$(srcdir)/'`kademlia/node.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/node.cpp' object='kademlia/libtorrent_rasterbar_la-node.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-node.lo `test -f 'kademlia/node.cpp' || echo '$(srcdir)/'`kademlia/node.cpp + +kademlia/libtorrent_rasterbar_la-node_entry.lo: kademlia/node_entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-node_entry.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Tpo -c -o kademlia/libtorrent_rasterbar_la-node_entry.lo `test -f 'kademlia/node_entry.cpp' || echo '$(srcdir)/'`kademlia/node_entry.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/node_entry.cpp' object='kademlia/libtorrent_rasterbar_la-node_entry.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-node_entry.lo `test -f 'kademlia/node_entry.cpp' || echo '$(srcdir)/'`kademlia/node_entry.cpp + +kademlia/libtorrent_rasterbar_la-node_id.lo: kademlia/node_id.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-node_id.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Tpo -c -o kademlia/libtorrent_rasterbar_la-node_id.lo `test -f 'kademlia/node_id.cpp' || echo '$(srcdir)/'`kademlia/node_id.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/node_id.cpp' object='kademlia/libtorrent_rasterbar_la-node_id.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-node_id.lo `test -f 'kademlia/node_id.cpp' || echo '$(srcdir)/'`kademlia/node_id.cpp + +kademlia/libtorrent_rasterbar_la-refresh.lo: kademlia/refresh.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-refresh.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Tpo -c -o kademlia/libtorrent_rasterbar_la-refresh.lo `test -f 'kademlia/refresh.cpp' || echo '$(srcdir)/'`kademlia/refresh.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/refresh.cpp' object='kademlia/libtorrent_rasterbar_la-refresh.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-refresh.lo `test -f 'kademlia/refresh.cpp' || echo '$(srcdir)/'`kademlia/refresh.cpp + +kademlia/libtorrent_rasterbar_la-routing_table.lo: kademlia/routing_table.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-routing_table.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Tpo -c -o kademlia/libtorrent_rasterbar_la-routing_table.lo `test -f 'kademlia/routing_table.cpp' || echo '$(srcdir)/'`kademlia/routing_table.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/routing_table.cpp' object='kademlia/libtorrent_rasterbar_la-routing_table.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-routing_table.lo `test -f 'kademlia/routing_table.cpp' || echo '$(srcdir)/'`kademlia/routing_table.cpp + +kademlia/libtorrent_rasterbar_la-rpc_manager.lo: kademlia/rpc_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-rpc_manager.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Tpo -c -o kademlia/libtorrent_rasterbar_la-rpc_manager.lo `test -f 'kademlia/rpc_manager.cpp' || echo '$(srcdir)/'`kademlia/rpc_manager.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/rpc_manager.cpp' object='kademlia/libtorrent_rasterbar_la-rpc_manager.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-rpc_manager.lo `test -f 'kademlia/rpc_manager.cpp' || echo '$(srcdir)/'`kademlia/rpc_manager.cpp + +kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo: kademlia/traversal_algorithm.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Tpo -c -o kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo `test -f 'kademlia/traversal_algorithm.cpp' || echo '$(srcdir)/'`kademlia/traversal_algorithm.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/traversal_algorithm.cpp' object='kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-traversal_algorithm.lo `test -f 'kademlia/traversal_algorithm.cpp' || echo '$(srcdir)/'`kademlia/traversal_algorithm.cpp + +kademlia/libtorrent_rasterbar_la-dos_blocker.lo: kademlia/dos_blocker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-dos_blocker.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Tpo -c -o kademlia/libtorrent_rasterbar_la-dos_blocker.lo `test -f 'kademlia/dos_blocker.cpp' || echo '$(srcdir)/'`kademlia/dos_blocker.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/dos_blocker.cpp' object='kademlia/libtorrent_rasterbar_la-dos_blocker.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-dos_blocker.lo `test -f 'kademlia/dos_blocker.cpp' || echo '$(srcdir)/'`kademlia/dos_blocker.cpp + +kademlia/libtorrent_rasterbar_la-get_peers.lo: kademlia/get_peers.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-get_peers.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Tpo -c -o kademlia/libtorrent_rasterbar_la-get_peers.lo `test -f 'kademlia/get_peers.cpp' || echo '$(srcdir)/'`kademlia/get_peers.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/get_peers.cpp' object='kademlia/libtorrent_rasterbar_la-get_peers.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-get_peers.lo `test -f 'kademlia/get_peers.cpp' || echo '$(srcdir)/'`kademlia/get_peers.cpp + +kademlia/libtorrent_rasterbar_la-get_item.lo: kademlia/get_item.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-get_item.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Tpo -c -o kademlia/libtorrent_rasterbar_la-get_item.lo `test -f 'kademlia/get_item.cpp' || echo '$(srcdir)/'`kademlia/get_item.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/get_item.cpp' object='kademlia/libtorrent_rasterbar_la-get_item.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-get_item.lo `test -f 'kademlia/get_item.cpp' || echo '$(srcdir)/'`kademlia/get_item.cpp + +kademlia/libtorrent_rasterbar_la-item.lo: kademlia/item.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-item.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Tpo -c -o kademlia/libtorrent_rasterbar_la-item.lo `test -f 'kademlia/item.cpp' || echo '$(srcdir)/'`kademlia/item.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/item.cpp' object='kademlia/libtorrent_rasterbar_la-item.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-item.lo `test -f 'kademlia/item.cpp' || echo '$(srcdir)/'`kademlia/item.cpp + +kademlia/libtorrent_rasterbar_la-ed25519.lo: kademlia/ed25519.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-ed25519.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Tpo -c -o kademlia/libtorrent_rasterbar_la-ed25519.lo `test -f 'kademlia/ed25519.cpp' || echo '$(srcdir)/'`kademlia/ed25519.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/ed25519.cpp' object='kademlia/libtorrent_rasterbar_la-ed25519.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-ed25519.lo `test -f 'kademlia/ed25519.cpp' || echo '$(srcdir)/'`kademlia/ed25519.cpp + +kademlia/libtorrent_rasterbar_la-sample_infohashes.lo: kademlia/sample_infohashes.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-sample_infohashes.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Tpo -c -o kademlia/libtorrent_rasterbar_la-sample_infohashes.lo `test -f 'kademlia/sample_infohashes.cpp' || echo '$(srcdir)/'`kademlia/sample_infohashes.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/sample_infohashes.cpp' object='kademlia/libtorrent_rasterbar_la-sample_infohashes.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-sample_infohashes.lo `test -f 'kademlia/sample_infohashes.cpp' || echo '$(srcdir)/'`kademlia/sample_infohashes.cpp + +kademlia/libtorrent_rasterbar_la-dht_settings.lo: kademlia/dht_settings.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT kademlia/libtorrent_rasterbar_la-dht_settings.lo -MD -MP -MF kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Tpo -c -o kademlia/libtorrent_rasterbar_la-dht_settings.lo `test -f 'kademlia/dht_settings.cpp' || echo '$(srcdir)/'`kademlia/dht_settings.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Tpo kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kademlia/dht_settings.cpp' object='kademlia/libtorrent_rasterbar_la-dht_settings.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o kademlia/libtorrent_rasterbar_la-dht_settings.lo `test -f 'kademlia/dht_settings.cpp' || echo '$(srcdir)/'`kademlia/dht_settings.cpp + +../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo: ../ed25519/src/add_scalar.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo `test -f '../ed25519/src/add_scalar.cpp' || echo '$(srcdir)/'`../ed25519/src/add_scalar.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/add_scalar.cpp' object='../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-add_scalar.lo `test -f '../ed25519/src/add_scalar.cpp' || echo '$(srcdir)/'`../ed25519/src/add_scalar.cpp + +../ed25519/src/libtorrent_rasterbar_la-fe.lo: ../ed25519/src/fe.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-fe.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-fe.lo `test -f '../ed25519/src/fe.cpp' || echo '$(srcdir)/'`../ed25519/src/fe.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/fe.cpp' object='../ed25519/src/libtorrent_rasterbar_la-fe.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-fe.lo `test -f '../ed25519/src/fe.cpp' || echo '$(srcdir)/'`../ed25519/src/fe.cpp + +../ed25519/src/libtorrent_rasterbar_la-ge.lo: ../ed25519/src/ge.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-ge.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-ge.lo `test -f '../ed25519/src/ge.cpp' || echo '$(srcdir)/'`../ed25519/src/ge.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/ge.cpp' object='../ed25519/src/libtorrent_rasterbar_la-ge.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-ge.lo `test -f '../ed25519/src/ge.cpp' || echo '$(srcdir)/'`../ed25519/src/ge.cpp + +../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo: ../ed25519/src/key_exchange.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo `test -f '../ed25519/src/key_exchange.cpp' || echo '$(srcdir)/'`../ed25519/src/key_exchange.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/key_exchange.cpp' object='../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-key_exchange.lo `test -f '../ed25519/src/key_exchange.cpp' || echo '$(srcdir)/'`../ed25519/src/key_exchange.cpp + +../ed25519/src/libtorrent_rasterbar_la-keypair.lo: ../ed25519/src/keypair.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-keypair.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-keypair.lo `test -f '../ed25519/src/keypair.cpp' || echo '$(srcdir)/'`../ed25519/src/keypair.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/keypair.cpp' object='../ed25519/src/libtorrent_rasterbar_la-keypair.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-keypair.lo `test -f '../ed25519/src/keypair.cpp' || echo '$(srcdir)/'`../ed25519/src/keypair.cpp + +../ed25519/src/libtorrent_rasterbar_la-sc.lo: ../ed25519/src/sc.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-sc.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-sc.lo `test -f '../ed25519/src/sc.cpp' || echo '$(srcdir)/'`../ed25519/src/sc.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/sc.cpp' object='../ed25519/src/libtorrent_rasterbar_la-sc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-sc.lo `test -f '../ed25519/src/sc.cpp' || echo '$(srcdir)/'`../ed25519/src/sc.cpp + +../ed25519/src/libtorrent_rasterbar_la-sign.lo: ../ed25519/src/sign.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-sign.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-sign.lo `test -f '../ed25519/src/sign.cpp' || echo '$(srcdir)/'`../ed25519/src/sign.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/sign.cpp' object='../ed25519/src/libtorrent_rasterbar_la-sign.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-sign.lo `test -f '../ed25519/src/sign.cpp' || echo '$(srcdir)/'`../ed25519/src/sign.cpp + +../ed25519/src/libtorrent_rasterbar_la-verify.lo: ../ed25519/src/verify.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ../ed25519/src/libtorrent_rasterbar_la-verify.lo -MD -MP -MF ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Tpo -c -o ../ed25519/src/libtorrent_rasterbar_la-verify.lo `test -f '../ed25519/src/verify.cpp' || echo '$(srcdir)/'`../ed25519/src/verify.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Tpo ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='../ed25519/src/verify.cpp' object='../ed25519/src/libtorrent_rasterbar_la-verify.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ../ed25519/src/libtorrent_rasterbar_la-verify.lo `test -f '../ed25519/src/verify.cpp' || echo '$(srcdir)/'`../ed25519/src/verify.cpp + +libtorrent_rasterbar_la-hasher512.lo: hasher512.cpp +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libtorrent_rasterbar_la-hasher512.lo -MD -MP -MF $(DEPDIR)/libtorrent_rasterbar_la-hasher512.Tpo -c -o libtorrent_rasterbar_la-hasher512.lo `test -f 'hasher512.cpp' || echo '$(srcdir)/'`hasher512.cpp +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libtorrent_rasterbar_la-hasher512.Tpo $(DEPDIR)/libtorrent_rasterbar_la-hasher512.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hasher512.cpp' object='libtorrent_rasterbar_la-hasher512.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtorrent_rasterbar_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libtorrent_rasterbar_la-hasher512.lo `test -f 'hasher512.cpp' || echo '$(srcdir)/'`hasher512.cpp + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf ../ed25519/src/.libs ../ed25519/src/_libs + -rm -rf kademlia/.libs kademlia/_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: + for dir in "$(DESTDIR)$(libdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f ../ed25519/src/$(DEPDIR)/$(am__dirstamp) + -rm -f ../ed25519/src/$(am__dirstamp) + -rm -f kademlia/$(DEPDIR)/$(am__dirstamp) + -rm -f kademlia/$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-alert.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-assert.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bdecode.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bitfield.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-block_cache.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-choker.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-close_reason.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-cpuid.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-crc32c.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-entry.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-enum_net.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-error_code.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-escape_string.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ffs.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file_progress.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file_storage.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-gzip.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-hasher.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-hasher512.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-hex.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_parser.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-identify_client.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-lsd.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-merkle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-natpmp.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-openssl.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-parse_url.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-part_file.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-path.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_class.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_info.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_list.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-platform_util.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-puff.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-random.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-resolver.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_call.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_impl.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_settings.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_stats.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-sha1.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-sha512.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-socket_io.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-socket_type.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-stat.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-storage.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-string_util.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-time.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-upnp.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-utf8.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-version.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-add_scalar.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-fe.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-ge.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-key_exchange.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-keypair.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sc.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-sign.Plo + -rm -f ../ed25519/src/$(DEPDIR)/libtorrent_rasterbar_la-verify.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-add_torrent_params.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-alert.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-alert_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-announce_entry.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-assert.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_limit.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bandwidth_queue_entry.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bdecode.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bitfield.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-block_cache.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bloom_filter.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-broadcast_socket.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-bt_peer_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-chained_buffer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-choker.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-close_reason.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-cpuid.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-crc32c.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-create_torrent.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_holder.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_buffer_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_job.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_io_thread_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_fence.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-disk_job_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-entry.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-enum_net.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-error_code.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-escape_string.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ffs.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file_pool.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file_progress.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-file_storage.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-fingerprint.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-generate_peer_id.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-gzip.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-hasher.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-hasher512.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-hex.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_parser.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_seed_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-http_tracker_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-i2p_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-identify_client.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-instantiate_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ip_filter.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ip_notifier.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ip_voter.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-lazy_bdecode.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-listen_socket_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-lsd.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-magnet_uri.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-merkle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-natpmp.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-openssl.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-packet_buffer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-parse_url.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-part_file.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-path.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-pe_crypto.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_class.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_class_set.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_connection_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_info.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-peer_list.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-performance_counters.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-piece_picker.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-platform_util.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-proxy_base.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-proxy_settings.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-puff.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-random.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-read_resume_data.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-receive_buffer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-request_blocks.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-resolve_links.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-resolver.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_call.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_impl.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_settings.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-session_stats.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-settings_pack.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-sha1.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-sha1_hash.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-sha512.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-smart_ban.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-socket_io.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-socket_type.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-socks5_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-stack_allocator.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-stat.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-stat_cache.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-storage.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-storage_piece_set.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-storage_utils.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-string_util.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-time.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-timestamp_history.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_handle.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_info.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_peer_allocator.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-torrent_status.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-tracker_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-udp_socket.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-udp_tracker_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-upnp.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ut_metadata.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-ut_pex.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-utf8.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-utp_socket_manager.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-utp_stream.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-version.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-web_connection_base.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-web_peer_connection.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-write_resume_data.Plo + -rm -f ./$(DEPDIR)/libtorrent_rasterbar_la-xml_parse.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_settings.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_state.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_storage.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dht_tracker.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-dos_blocker.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-ed25519.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-find_data.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_item.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-get_peers.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-item.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-msg.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_entry.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-node_id.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-put_data.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-refresh.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-routing_table.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-rpc_manager.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-sample_infohashes.Plo + -rm -f kademlia/$(DEPDIR)/libtorrent_rasterbar_la-traversal_algorithm.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/add_torrent_params.cpp b/src/add_torrent_params.cpp new file mode 100644 index 0000000..8c3c809 --- /dev/null +++ b/src/add_torrent_params.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/add_torrent_params.hpp" + +namespace libtorrent { + + add_torrent_params::add_torrent_params(storage_constructor_type sc) + : storage(std::move(sc)) {} + add_torrent_params::add_torrent_params(add_torrent_params&&) noexcept = default; + add_torrent_params::add_torrent_params(add_torrent_params const&) = default; + add_torrent_params& add_torrent_params::operator=(add_torrent_params const&) = default; + +#if TORRENT_ABI_VERSION == 1 +#define DECL_FLAG(name) \ + constexpr torrent_flags_t add_torrent_params::flag_##name + + DECL_FLAG(seed_mode); + DECL_FLAG(upload_mode); + DECL_FLAG(share_mode); + DECL_FLAG(apply_ip_filter); + DECL_FLAG(paused); + DECL_FLAG(auto_managed); + DECL_FLAG(duplicate_is_error); + DECL_FLAG(update_subscribe); + DECL_FLAG(super_seeding); + DECL_FLAG(sequential_download); + DECL_FLAG(pinned); + DECL_FLAG(stop_when_ready); + DECL_FLAG(override_trackers); + DECL_FLAG(override_web_seeds); + DECL_FLAG(need_save_resume); + DECL_FLAG(override_resume_data); + DECL_FLAG(merge_resume_trackers); + DECL_FLAG(use_resume_save_path); + DECL_FLAG(merge_resume_http_seeds); + DECL_FLAG(default_flags); +#undef DECL_FLAG +#endif // TORRENT_ABI_VERSION + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + + // TODO: pre C++17, GCC and msvc does not make std::string nothrow move + // assignable, which means no type containing a string will be nothrow move + // assignable by default either +// static_assert(std::is_nothrow_move_assignable::value +// , "should be nothrow move assignable"); + + // TODO: it would be nice if this was nothrow default constructible +// static_assert(std::is_nothrow_default_constructible::value +// , "should be nothrow default constructible"); + +namespace aux { + + // returns whether this add_torrent_params object has "resume-data", i.e. + // information about which pieces we have. + bool contains_resume_data(add_torrent_params const& atp) + { + return !atp.have_pieces.empty() + || (atp.flags & torrent_flags::seed_mode); + } +} + +} diff --git a/src/alert.cpp b/src/alert.cpp new file mode 100644 index 0000000..58647e4 --- /dev/null +++ b/src/alert.cpp @@ -0,0 +1,2732 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include // for snprintf +#include // for PRId64 et.al. + +#include "libtorrent/config.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/piece_block.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/session_stats.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/write_resume_data.hpp" +#endif + +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native + +namespace libtorrent { + + constexpr alert_category_t alert::error_notification; + constexpr alert_category_t alert::peer_notification; + constexpr alert_category_t alert::port_mapping_notification; + constexpr alert_category_t alert::storage_notification; + constexpr alert_category_t alert::tracker_notification; + constexpr alert_category_t alert::connect_notification; + constexpr alert_category_t alert::status_notification; +#if TORRENT_ABI_VERSION == 1 + constexpr alert_category_t alert::debug_notification; + constexpr alert_category_t alert::progress_notification; +#endif + constexpr alert_category_t alert::ip_block_notification; + constexpr alert_category_t alert::performance_warning; + constexpr alert_category_t alert::dht_notification; + constexpr alert_category_t alert::stats_notification; + constexpr alert_category_t alert::session_log_notification; + constexpr alert_category_t alert::torrent_log_notification; + constexpr alert_category_t alert::peer_log_notification; + constexpr alert_category_t alert::incoming_request_notification; + constexpr alert_category_t alert::dht_log_notification; + constexpr alert_category_t alert::dht_operation_notification; + constexpr alert_category_t alert::port_mapping_log_notification; + constexpr alert_category_t alert::picker_log_notification; + constexpr alert_category_t alert::file_progress_notification; + constexpr alert_category_t alert::piece_progress_notification; + constexpr alert_category_t alert::upload_notification; + constexpr alert_category_t alert::block_progress_notification; + + constexpr alert_category_t alert::all_categories; +#if TORRENT_ABI_VERSION == 1 + constexpr alert_category_t alert::rss_notification; +#endif + + alert::alert() : m_timestamp(clock_type::now()) {} + alert::~alert() = default; + time_point alert::timestamp() const { return m_timestamp; } + + torrent_alert::torrent_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : handle(h) + , m_alloc(alloc) + { + std::shared_ptr t = h.native_handle(); + if (t) + { + std::string name_str = t->name(); + if (!name_str.empty()) + { + m_name_idx = alloc.copy_string(name_str); + } + else + { + m_name_idx = alloc.copy_string(aux::to_hex(t->info_hash())); + } + } + else + { + m_name_idx = alloc.copy_string(""); + } + +#if TORRENT_ABI_VERSION == 1 + name = m_alloc.get().ptr(m_name_idx); +#endif + } + + char const* torrent_alert::torrent_name() const + { + return m_alloc.get().ptr(m_name_idx); + } + + std::string torrent_alert::message() const + { + if (!handle.is_valid()) return " - "; + return torrent_name(); + } + + peer_alert::peer_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , tcp::endpoint const& i + , peer_id const& pi) + : torrent_alert(alloc, h) + , endpoint(i) + , pid(pi) +#if TORRENT_ABI_VERSION == 1 + , ip(i) +#endif + {} + + std::string peer_alert::message() const + { + return torrent_alert::message() + " peer [ " + print_endpoint(endpoint) + + " client: " + aux::identify_client_impl(pid) + " ]"; + } + + tracker_alert::tracker_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep, string_view u) + : torrent_alert(alloc, h) + , local_endpoint(ep) + , m_url_idx(alloc.copy_string(u)) +#if TORRENT_ABI_VERSION == 1 + , url(u) +#endif + {} + + char const* tracker_alert::tracker_url() const + { + return m_alloc.get().ptr(m_url_idx); + } + + std::string tracker_alert::message() const + { + return torrent_alert::message() + " (" + tracker_url() + ")" + + "[" + print_endpoint(local_endpoint) + "]"; + } + + read_piece_alert::read_piece_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , piece_index_t p, boost::shared_array d, int s) + : torrent_alert(alloc, h) + , buffer(std::move(d)) + , piece(p) + , size(s) + {} + + read_piece_alert::read_piece_alert(aux::stack_allocator& alloc + , torrent_handle h, piece_index_t p, error_code e) + : torrent_alert(alloc, h) + , error(e) + , piece(p) + , size(0) +#if TORRENT_ABI_VERSION == 1 + , ec(e) +#endif + {} + + std::string read_piece_alert::message() const + { + char msg[200]; + if (error) + { + std::snprintf(msg, sizeof(msg), "%s: read_piece %d failed: %s" + , torrent_alert::message().c_str() , static_cast(piece) + , convert_from_native(error.message()).c_str()); + } + else + { + std::snprintf(msg, sizeof(msg), "%s: read_piece %d successful" + , torrent_alert::message().c_str() , static_cast(piece)); + } + return msg; + } + + file_completed_alert::file_completed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , file_index_t idx) + : torrent_alert(alloc, h) + , index(idx) + {} + + std::string file_completed_alert::message() const + { + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": file %d finished downloading" + , static_cast(index)); + ret.append(msg); + return ret; + } + + file_renamed_alert::file_renamed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view n, file_index_t const idx) + : torrent_alert(alloc, h) + , index(idx) + , m_name_idx(alloc.copy_string(n)) +#if TORRENT_ABI_VERSION == 1 + , name(n) +#endif + {} + + char const* file_renamed_alert::new_name() const + { + return m_alloc.get().ptr(m_name_idx); + } + + std::string file_renamed_alert::message() const + { + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": file %d renamed to " + , static_cast(index)); + ret.append(msg); + ret.append(new_name()); + return ret; + } + + file_rename_failed_alert::file_rename_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , file_index_t const idx + , error_code ec) + : torrent_alert(alloc, h) + , index(idx) + , error(ec) + {} + + std::string file_rename_failed_alert::message() const + { + std::string ret { torrent_alert::message() }; + char msg[200]; + std::snprintf(msg, sizeof(msg), ": failed to rename file %d: " + , static_cast(index)); + ret.append(msg); + ret.append(convert_from_native(error.message())); + return ret; + } + + performance_alert::performance_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , performance_warning_t w) + : torrent_alert(alloc, h) + , warning_code(w) + {} + + char const* performance_warning_str(performance_alert::performance_warning_t i) + { + static char const* const warning_str[] = + { + "max outstanding disk writes reached", + "max outstanding piece requests reached", + "upload limit too low (download rate will suffer)", + "download limit too low (upload rate will suffer)", + "send buffer watermark too low (upload rate will suffer)", + "too many optimistic unchoke slots", + "the disk queue limit is too high compared to the cache size. The disk queue eats into the cache size", + "outstanding AIO operations limit reached", + "using bittyrant unchoker with no upload rate limit set", + "too few ports allowed for outgoing connections", + "too few file descriptors are allowed for this process. connection limit lowered" + }; + + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < std::end(warning_str) - std::begin(warning_str)); + return warning_str[i]; + } + + std::string performance_alert::message() const + { + return torrent_alert::message() + ": performance warning: " + + performance_warning_str(warning_code); + } + + state_changed_alert::state_changed_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , torrent_status::state_t st + , torrent_status::state_t prev_st) + : torrent_alert(alloc, h) + , state(st) + , prev_state(prev_st) + {} + + std::string state_changed_alert::message() const + { + static char const* const state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating" + , "checking (r)"}; + + return torrent_alert::message() + ": state changed to: " + + state_str[state]; + } + + tracker_error_alert::tracker_error_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep, int times + , string_view u, error_code const& e, string_view m) + : tracker_alert(alloc, h, ep, u) + , times_in_row(times) + , error(e) + , m_msg_idx(alloc.copy_string(m)) +#if TORRENT_ABI_VERSION == 1 + , status_code(e && e.category() == http_category() ? e.value() : -1) + , msg(m) +#endif + { + TORRENT_ASSERT(!u.empty()); + } + + char const* tracker_error_alert::error_message() const + { + return m_alloc.get().ptr(m_msg_idx); + } + + std::string tracker_error_alert::message() const + { + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s %s \"%s\" (%d)" + , tracker_alert::message().c_str() + , convert_from_native(error.message()).c_str(), error_message() + , times_in_row); + return ret; + } + + tracker_warning_alert::tracker_warning_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, string_view m) + : tracker_alert(alloc, h, ep, u) + , m_msg_idx(alloc.copy_string(m)) +#if TORRENT_ABI_VERSION == 1 + , msg(m) +#endif + { + TORRENT_ASSERT(!u.empty()); + } + + char const* tracker_warning_alert::warning_message() const + { + return m_alloc.get().ptr(m_msg_idx); + } + + std::string tracker_warning_alert::message() const + { + return tracker_alert::message() + " warning: " + warning_message(); + } + + scrape_reply_alert::scrape_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int incomp, int comp, string_view u) + : tracker_alert(alloc, h, ep, u) + , incomplete(incomp) + , complete(comp) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string scrape_reply_alert::message() const + { + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s scrape reply: %d %d" + , tracker_alert::message().c_str(), incomplete, complete); + return ret; + } + + scrape_failed_alert::scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, error_code const& e) + : tracker_alert(alloc, h, ep, u) + , error(e) + , m_msg_idx() +#if TORRENT_ABI_VERSION == 1 + , msg(convert_from_native(e.message())) +#endif + { + TORRENT_ASSERT(!u.empty()); + } + + scrape_failed_alert::scrape_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , string_view u, string_view m) + : tracker_alert(alloc, h, ep, u) + , error(errors::tracker_failure) + , m_msg_idx(alloc.copy_string(m)) +#if TORRENT_ABI_VERSION == 1 + , msg(m) +#endif + { + TORRENT_ASSERT(!u.empty()); + } + + char const* scrape_failed_alert::error_message() const + { + if (m_msg_idx == aux::allocation_slot()) return ""; + else return m_alloc.get().ptr(m_msg_idx); + } + + std::string scrape_failed_alert::message() const + { + return tracker_alert::message() + " scrape failed: " + error_message(); + } + + tracker_reply_alert::tracker_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , int np, string_view u) + : tracker_alert(alloc, h, ep, u) + , num_peers(np) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string tracker_reply_alert::message() const + { + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s received peers: %d" + , tracker_alert::message().c_str(), num_peers); + return ret; + } + + dht_reply_alert::dht_reply_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , int np) + : tracker_alert(alloc, h, {}, "") + , num_peers(np) + {} + + std::string dht_reply_alert::message() const + { + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s received DHT peers: %d" + , tracker_alert::message().c_str(), num_peers); + return ret; + } + + tracker_announce_alert::tracker_announce_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep, string_view u, int e) + : tracker_alert(alloc, h, ep, u) + , event(e) + { + TORRENT_ASSERT(!u.empty()); + } + + std::string tracker_announce_alert::message() const + { + static const char* const event_str[] = {"none", "completed", "started", "stopped", "paused"}; + TORRENT_ASSERT_VAL(event < int(sizeof(event_str) / sizeof(event_str[0])), event); + return tracker_alert::message() + " sending announce (" + event_str[event] + ")"; + } + + hash_failed_alert::hash_failed_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , piece_index_t index) + : torrent_alert(alloc, h) + , piece_index(index) + { + TORRENT_ASSERT(index >= piece_index_t(0)); + } + + std::string hash_failed_alert::message() const + { + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s hash for piece %d failed" + , torrent_alert::message().c_str(), static_cast(piece_index)); + return ret; + } + + peer_ban_alert::peer_ban_alert(aux::stack_allocator& alloc + , torrent_handle h, tcp::endpoint const& ep + , peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + {} + + std::string peer_ban_alert::message() const + { + return peer_alert::message() + " banned peer"; + } + + peer_unsnubbed_alert::peer_unsnubbed_alert(aux::stack_allocator& alloc + , torrent_handle h, tcp::endpoint const& ep + , peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + {} + + std::string peer_unsnubbed_alert::message() const + { + return peer_alert::message() + " peer unsnubbed"; + } + + peer_snubbed_alert::peer_snubbed_alert(aux::stack_allocator& alloc + , torrent_handle h, tcp::endpoint const& ep + , peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + {} + + std::string peer_snubbed_alert::message() const + { + return peer_alert::message() + " peer snubbed"; + } + + invalid_request_alert::invalid_request_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, peer_request const& r + , bool _have, bool _peer_interested, bool _withheld) + : peer_alert(alloc, h, ep, peer_id) + , request(r) + , we_have(_have) + , peer_interested(_peer_interested) + , withheld(_withheld) + {} + + std::string invalid_request_alert::message() const + { + char ret[400]; + std::snprintf(ret, sizeof(ret), "%s peer sent an invalid piece request " + "(piece: %d start: %d len: %d)%s" + , peer_alert::message().c_str() + , static_cast(request.piece) + , request.start + , request.length + , withheld ? ": super seeding withheld piece" + : !we_have ? ": we don't have piece" + : !peer_interested ? ": peer is not interested" + : ""); + return ret; + } + + torrent_finished_alert::torrent_finished_alert(aux::stack_allocator& alloc + , torrent_handle h) + : torrent_alert(alloc, h) + {} + + std::string torrent_finished_alert::message() const + { + return torrent_alert::message() + " torrent finished downloading"; + } + + piece_finished_alert::piece_finished_alert(aux::stack_allocator& alloc + , torrent_handle const& h, piece_index_t piece_num) + : torrent_alert(alloc, h) + , piece_index(piece_num) + {} + + std::string piece_finished_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s piece: %d finished downloading" + , torrent_alert::message().c_str(), static_cast(piece_index)); + return ret; + } + + request_dropped_alert::request_dropped_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= piece_index_t(0)); + } + + std::string request_dropped_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s peer dropped block ( piece: %d block: %d)" + , peer_alert::message().c_str(), static_cast(piece_index), block_index); + return ret; + } + + block_timeout_alert::block_timeout_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= piece_index_t(0)); + } + + std::string block_timeout_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s peer timed out request ( piece: %d block: %d)" + , peer_alert::message().c_str(), static_cast(piece_index), block_index); + return ret; + } + + block_finished_alert::block_finished_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= piece_index_t(0)); + } + + std::string block_finished_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s block finished downloading (piece: %d block: %d)" + , peer_alert::message().c_str(), static_cast(piece_index), block_index); + return ret; + } + + block_downloading_alert::block_downloading_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) +#if TORRENT_ABI_VERSION == 1 + , peer_speedmsg("") +#endif + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= piece_index_t(0)); + } + + std::string block_downloading_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s requested block (piece: %d block: %d)" + , peer_alert::message().c_str(), static_cast(piece_index), block_index); + return ret; + } + + unwanted_block_alert::unwanted_block_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep + , peer_id const& peer_id, int block_num, piece_index_t piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= piece_index_t(0)); + } + + std::string unwanted_block_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "%s received block not in download queue (piece: %d block: %d)" + , peer_alert::message().c_str(), static_cast(piece_index), block_index); + return ret; + } + + storage_moved_alert::storage_moved_alert(aux::stack_allocator& alloc + , torrent_handle const& h, string_view p) + : torrent_alert(alloc, h) + , m_path_idx(alloc.copy_string(p)) +#if TORRENT_ABI_VERSION == 1 + , path(p) +#endif + {} + + std::string storage_moved_alert::message() const + { + return torrent_alert::message() + " moved storage to: " + + storage_path(); + } + + char const* storage_moved_alert::storage_path() const + { + return m_alloc.get().ptr(m_path_idx); + } + + storage_moved_failed_alert::storage_moved_failed_alert( + aux::stack_allocator& alloc, torrent_handle const& h, error_code const& e + , string_view f, operation_t const op_) + : torrent_alert(alloc, h) + , error(e) + , op(op_) + , m_file_idx(alloc.copy_string(f)) +#if TORRENT_ABI_VERSION == 1 + , operation(operation_name(op_)) + , file(f) +#endif + {} + + char const* storage_moved_failed_alert::file_path() const + { + return m_alloc.get().ptr(m_file_idx); + } + + std::string storage_moved_failed_alert::message() const + { + return torrent_alert::message() + " storage move failed. " + + operation_name(op) + " (" + file_path() + "): " + + convert_from_native(error.message()); + } + + torrent_deleted_alert::torrent_deleted_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih) + : torrent_alert(alloc, h) + , info_hash(ih) + {} + + std::string torrent_deleted_alert::message() const + { + return torrent_alert::message() + " deleted"; + } + + torrent_delete_failed_alert::torrent_delete_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e, sha1_hash const& ih) + : torrent_alert(alloc, h) + , error(e) + , info_hash(ih) +#if TORRENT_ABI_VERSION == 1 + , msg(convert_from_native(error.message())) +#endif + { + } + + std::string torrent_delete_failed_alert::message() const + { + return torrent_alert::message() + " torrent deletion failed: " + + convert_from_native(error.message()); + } + + save_resume_data_alert::save_resume_data_alert(aux::stack_allocator& alloc + , add_torrent_params&& p + , torrent_handle const& h) + : torrent_alert(alloc, h) + , params(std::move(p)) +#if TORRENT_ABI_VERSION == 1 + , resume_data(std::make_shared(write_resume_data(params))) +#endif + { + } + + std::string save_resume_data_alert::message() const + { + return torrent_alert::message() + " resume data generated"; + } + + save_resume_data_failed_alert::save_resume_data_failed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, error_code const& e) + : torrent_alert(alloc, h) + , error(e) +#if TORRENT_ABI_VERSION == 1 + , msg(convert_from_native(error.message())) +#endif + { + } + + std::string save_resume_data_failed_alert::message() const + { + return torrent_alert::message() + " resume data was not generated: " + + convert_from_native(error.message()); + } + + torrent_paused_alert::torrent_paused_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_paused_alert::message() const + { + return torrent_alert::message() + " paused"; + } + + torrent_resumed_alert::torrent_resumed_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_resumed_alert::message() const + { + return torrent_alert::message() + " resumed"; + } + + torrent_checked_alert::torrent_checked_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_checked_alert::message() const + { + return torrent_alert::message() + " checked"; + } + +namespace { + + int sock_type_idx(socket_type_t type) + { + int idx = + static_cast::type>(type); + TORRENT_ASSERT(0 <= idx && idx < 6); + return idx; + } + + char const* sock_type_str(socket_type_t type) + { + static char const* const type_str[] = + { "TCP", "TCP/SSL", "UDP", "I2P", "Socks5", "uTP/SSL" }; + + return type_str[sock_type_idx(type)]; + } + + char const* const nat_type_str[] = {"NAT-PMP", "UPnP"}; + + char const* const protocol_str[] = {"none", "TCP", "UDP"}; + + char const* const socket_type_str[] = { + "null", + "TCP", + "Socks5/TCP", + "HTTP", + "uTP", + "i2p", + "SSL/TCP", + "SSL/Socks5", + "HTTPS", + "SSL/uTP" + }; + +#if TORRENT_ABI_VERSION == 1 + + int to_op_t(operation_t op) + { + using o = operation_t; + using lfo = listen_failed_alert::op_t; + + // we have to use deprecated enum values here. suppress the warnings +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4996: X: was declared deprecated +#pragma warning( disable : 4996 ) +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + switch (op) + { + case o::bittorrent: return -1; + case o::iocontrol: return -1; + case o::getpeername: return -1; + case o::getname: return lfo::get_socket_name; + case o::alloc_recvbuf: return -1; + case o::alloc_sndbuf: return -1; + case o::file_write: return -1; + case o::file_read: return -1; + case o::file: return -1; + case o::sock_write: return -1; + case o::sock_read: return -1; + case o::sock_open: return lfo::open; + case o::sock_bind: return lfo::bind; + case o::available: return -1; + case o::encryption: return -1; + case o::connect: return -1; + case o::ssl_handshake: return -1; + case o::get_interface: return -1; + case o::unknown: return -1; + case o::sock_listen: return lfo::listen; + case o::sock_bind_to_device: return lfo::bind_to_device; + case o::sock_accept: return lfo::accept; + case o::parse_address: return lfo::parse_addr; + case o::enum_if: return lfo::enum_if; + case o::file_stat: return -1; + case o::file_copy: return -1; + case o::file_fallocate: return -1; + case o::file_hard_link: return -1; + case o::file_remove: return -1; + case o::file_rename: return -1; + case o::file_open: return -1; + case o::mkdir: return -1; + case o::check_resume: return -1; + case o::exception: return -1; + case o::alloc_cache_piece: return -1; + case o::partfile_move: return -1; + case o::partfile_read: return -1; + case o::partfile_write: return -1; + case o::hostname_lookup: return -1; + case o::symlink: return -1; + case o::handshake: return -1; + case o::sock_option: return -1; + case o::enum_route: return -1; + } + return -1; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // TORRENT_ABI_VERSION + +} // anonymous namespace + + listen_failed_alert::listen_failed_alert( + aux::stack_allocator& alloc + , string_view iface + , libtorrent::address const& listen_addr + , int listen_port + , operation_t const op_ + , error_code const& ec + , libtorrent::socket_type_t t) + : error(ec) + , op(op_) + , socket_type(t) + , address(listen_addr) + , port(listen_port) + , m_alloc(alloc) + , m_interface_idx(alloc.copy_string(iface)) +#if TORRENT_ABI_VERSION == 1 + , operation(to_op_t(op_)) + , endpoint(listen_addr, std::uint16_t(listen_port)) + , sock_type(static_cast(sock_type_idx(t))) +#endif + {} + + listen_failed_alert::listen_failed_alert( + aux::stack_allocator& alloc + , string_view iface + , tcp::endpoint const& ep + , operation_t const op_ + , error_code const& ec + , libtorrent::socket_type_t t) + : listen_failed_alert(alloc + , iface + , ep.address() + , ep.port() + , op_ + , ec + , t) + {} + + listen_failed_alert::listen_failed_alert( + aux::stack_allocator& alloc + , string_view iface + , udp::endpoint const& ep + , operation_t const op_ + , error_code const& ec + , libtorrent::socket_type_t t) + : listen_failed_alert(alloc + , iface + , ep.address() + , ep.port() + , op_ + , ec + , t) + {} + + listen_failed_alert::listen_failed_alert( + aux::stack_allocator& alloc + , string_view iface + , operation_t const op_ + , error_code const& ec + , libtorrent::socket_type_t t) + : listen_failed_alert(alloc + , iface + , libtorrent::address() + , 0 + , op_ + , ec + , t) + {} + + char const* listen_failed_alert::listen_interface() const + { + return m_alloc.get().ptr(m_interface_idx); + } + + std::string listen_failed_alert::message() const + { + char ret[300]; + std::snprintf(ret, sizeof(ret), "listening on %s (device: %s) failed: [%s] [%s] %s" + , print_endpoint(address, port).c_str() + , listen_interface() + , operation_name(op) + , sock_type_str(socket_type) + , convert_from_native(error.message()).c_str()); + return ret; + } + + metadata_failed_alert::metadata_failed_alert(aux::stack_allocator& alloc + , const torrent_handle& h, error_code const& e) + : torrent_alert(alloc, h) + , error(e) + {} + + std::string metadata_failed_alert::message() const + { + return torrent_alert::message() + " invalid metadata received"; + } + + metadata_received_alert::metadata_received_alert(aux::stack_allocator& alloc + , const torrent_handle& h) + : torrent_alert(alloc, h) + {} + + std::string metadata_received_alert::message() const + { + return torrent_alert::message() + " metadata successfully received"; + } + + udp_error_alert::udp_error_alert( + aux::stack_allocator& + , udp::endpoint const& ep + , operation_t op + , error_code const& ec) + : endpoint(ep) + , operation(op) + , error(ec) + {} + + std::string udp_error_alert::message() const + { + error_code ec; + return "UDP error: " + convert_from_native(error.message()) + + " from: " + endpoint.address().to_string(ec) + + " op: " + operation_name(operation); + } + + external_ip_alert::external_ip_alert(aux::stack_allocator& + , address const& ip) + : external_address(ip) + {} + + std::string external_ip_alert::message() const + { + error_code ec; + return "external IP received: " + external_address.to_string(ec); + } + + listen_succeeded_alert::listen_succeeded_alert(aux::stack_allocator& + , libtorrent::address const& listen_addr + , int listen_port + , libtorrent::socket_type_t t) + : address(listen_addr) + , port(listen_port) + , socket_type(t) +#if TORRENT_ABI_VERSION == 1 + , endpoint(listen_addr, std::uint16_t(listen_port)) + , sock_type(static_cast(sock_type_idx(t))) +#endif + {} + + listen_succeeded_alert::listen_succeeded_alert(aux::stack_allocator& alloc + , tcp::endpoint const& ep + , libtorrent::socket_type_t t) + : listen_succeeded_alert(alloc + , ep.address() + , ep.port() + , t) + {} + + listen_succeeded_alert::listen_succeeded_alert(aux::stack_allocator& alloc + , udp::endpoint const& ep + , libtorrent::socket_type_t t) + : listen_succeeded_alert(alloc + , ep.address() + , ep.port() + , t) + {} + + std::string listen_succeeded_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "successfully listening on [%s] %s" + , sock_type_str(socket_type), print_endpoint(address, port).c_str()); + return ret; + } + + portmap_error_alert::portmap_error_alert(aux::stack_allocator& + , port_mapping_t const i, portmap_transport const t, error_code const& e) + : mapping(i) + , map_transport(t) + , error(e) +#if TORRENT_ABI_VERSION == 1 + , map_type(static_cast(t)) + , msg(convert_from_native(error.message())) +#endif + {} + + std::string portmap_error_alert::message() const + { + return std::string("could not map port using ") + + nat_type_str[static_cast(map_transport)] + + ": " + convert_from_native(error.message()); + } + + portmap_alert::portmap_alert(aux::stack_allocator&, port_mapping_t const i + , int port + , portmap_transport const t + , portmap_protocol const proto) + : mapping(i) + , external_port(port) + , map_protocol(proto) + , map_transport(t) +#if TORRENT_ABI_VERSION == 1 + , protocol(static_cast(proto)) + , map_type(static_cast(t)) +#endif + {} + + std::string portmap_alert::message() const + { + char ret[200]; + std::snprintf(ret, sizeof(ret), "successfully mapped port using %s. external port: %s/%d" + , nat_type_str[static_cast(map_transport)] + , protocol_str[static_cast(map_protocol)], external_port); + return ret; + } + + portmap_log_alert::portmap_log_alert(aux::stack_allocator& alloc + , portmap_transport const t, const char* m) + : map_transport(t) + , m_alloc(alloc) + , m_log_idx(alloc.copy_string(m)) +#if TORRENT_ABI_VERSION == 1 + , map_type(static_cast(t)) + , msg(m) +#endif + {} + + char const* portmap_log_alert::log_message() const + { + return m_alloc.get().ptr(m_log_idx); + } + + std::string portmap_log_alert::message() const + { + char ret[1024]; + std::snprintf(ret, sizeof(ret), "%s: %s" + , nat_type_str[static_cast(map_transport)] + , log_message()); + return ret; + } + + fastresume_rejected_alert::fastresume_rejected_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& ec + , string_view f + , operation_t const op_) + : torrent_alert(alloc, h) + , error(ec) + , op(op_) + , m_path_idx(alloc.copy_string(f)) +#if TORRENT_ABI_VERSION == 1 + , operation(operation_name(op_)) + , file(f) + , msg(convert_from_native(error.message())) +#endif + { + } + + std::string fastresume_rejected_alert::message() const + { + return torrent_alert::message() + " fast resume rejected. " + + operation_name(op) + "(" + file_path() + "): " + + convert_from_native(error.message()); + } + + char const* fastresume_rejected_alert::file_path() const + { + return m_alloc.get().ptr(m_path_idx); + } + + peer_blocked_alert::peer_blocked_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep, int r) + : peer_alert(alloc, h, ep, peer_id(nullptr)) + , reason(r) + {} + + std::string peer_blocked_alert::message() const + { + char ret[600]; + static char const* const reason_str[] = + { + "ip_filter", + "port_filter", + "i2p_mixed", + "privileged_ports", + "utp_disabled", + "tcp_disabled", + "invalid_local_interface" + }; + + std::snprintf(ret, sizeof(ret), "%s: blocked peer [%s]" + , peer_alert::message().c_str(), reason_str[reason]); + return ret; + } + + dht_announce_alert::dht_announce_alert(aux::stack_allocator& + , address const& i, int p + , sha1_hash const& ih) + : ip(i) + , port(p) + , info_hash(ih) + {} + + std::string dht_announce_alert::message() const + { + error_code ec; + char msg[200]; + std::snprintf(msg, sizeof(msg), "incoming dht announce: %s:%d (%s)" + , ip.to_string(ec).c_str(), port, aux::to_hex(info_hash).c_str()); + return msg; + } + + dht_get_peers_alert::dht_get_peers_alert(aux::stack_allocator& + , sha1_hash const& ih) + : info_hash(ih) + {} + + std::string dht_get_peers_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg), "incoming dht get_peers: %s", aux::to_hex(info_hash).c_str()); + return msg; + } + +namespace { + + std::array stat_to_array(stat const& s) + { + std::array arr; + + arr[stats_alert::upload_payload] = s[stat::upload_payload].counter(); + arr[stats_alert::upload_protocol] = s[stat::upload_protocol].counter(); + arr[stats_alert::download_payload] = s[stat::download_payload].counter(); + arr[stats_alert::download_protocol] = s[stat::download_protocol].counter(); + arr[stats_alert::upload_ip_protocol] = s[stat::upload_ip_protocol].counter(); + arr[stats_alert::download_ip_protocol] = s[stat::download_ip_protocol].counter(); + +#if TORRENT_ABI_VERSION == 1 + arr[stats_alert::upload_dht_protocol] = 0; + arr[stats_alert::upload_tracker_protocol] = 0; + arr[stats_alert::download_dht_protocol] = 0; + arr[stats_alert::download_tracker_protocol] = 0; +#else + arr[stats_alert::deprecated1] = 0; + arr[stats_alert::deprecated2] = 0; + arr[stats_alert::deprecated3] = 0; + arr[stats_alert::deprecated4] = 0; +#endif + return arr; + } + } + + stats_alert::stats_alert(aux::stack_allocator& alloc + , torrent_handle const& h, int in, stat const& s) + : torrent_alert(alloc, h) + , transferred(stat_to_array(s)) + , interval(in) + {} + + std::string stats_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg), "%s: [%d] %d %d %d %d %d %d" +#if TORRENT_ABI_VERSION == 1 + " %d %d %d %d" +#endif + , torrent_alert::message().c_str() + , interval + , transferred[0] + , transferred[1] + , transferred[2] + , transferred[3] + , transferred[4] + , transferred[5] +#if TORRENT_ABI_VERSION == 1 + , transferred[6] + , transferred[7] + , transferred[8] + , transferred[9] +#endif + ); + return msg; + } + + cache_flushed_alert::cache_flushed_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) {} + +#if TORRENT_ABI_VERSION == 1 + anonymous_mode_alert::anonymous_mode_alert(aux::stack_allocator& alloc + , torrent_handle const& h, int k, string_view s) + : torrent_alert(alloc, h) + , kind(k) + , str(s) + {} + + std::string anonymous_mode_alert::message() const + { + char msg[200]; + static char const* const msgs[] = { + "tracker is not anonymous, set a proxy" + }; + std::snprintf(msg, sizeof(msg), "%s: %s: %s" + , torrent_alert::message().c_str() + , msgs[kind], str.c_str()); + return msg; + } +#endif // TORRENT_ABI_VERSION + + lsd_peer_alert::lsd_peer_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& i) + : peer_alert(alloc, h, i, peer_id(nullptr)) + {} + + std::string lsd_peer_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg), "%s: received peer from local service discovery" + , peer_alert::message().c_str()); + return msg; + } + + trackerid_alert::trackerid_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , tcp::endpoint const& ep + , string_view u + , const std::string& id) + : tracker_alert(alloc, h, ep, u) + , m_tracker_idx(alloc.copy_string(id)) +#if TORRENT_ABI_VERSION == 1 + , trackerid(id) +#endif + {} + + char const* trackerid_alert::tracker_id() const + { + return m_alloc.get().ptr(m_tracker_idx); + } + + std::string trackerid_alert::message() const + { + return std::string("trackerid received: ") + tracker_id(); + } + + dht_bootstrap_alert::dht_bootstrap_alert(aux::stack_allocator&) + {} + + std::string dht_bootstrap_alert::message() const + { + return "DHT bootstrap complete"; + } + + torrent_error_alert::torrent_error_alert( + aux::stack_allocator& alloc + , torrent_handle const& h + , error_code const& e, string_view f) + : torrent_alert(alloc, h) + , error(e) + , m_file_idx(alloc.copy_string(f)) +#if TORRENT_ABI_VERSION == 1 + , error_file(f) +#endif + {} + + std::string torrent_error_alert::message() const + { + char msg[400]; + if (error) + { + std::snprintf(msg, sizeof(msg), " ERROR: (%d %s) %s" + , error.value(), convert_from_native(error.message()).c_str() + , filename()); + } + else + { + std::snprintf(msg, sizeof(msg), " ERROR: %s", filename()); + } + return torrent_alert::message() + msg; + } + + char const* torrent_error_alert::filename() const + { + return m_alloc.get().ptr(m_file_idx); + } + +#if TORRENT_ABI_VERSION == 1 + torrent_added_alert::torrent_added_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_added_alert::message() const + { + return torrent_alert::message() + " added"; + } +#endif + + torrent_removed_alert::torrent_removed_alert(aux::stack_allocator& alloc + , torrent_handle const& h, sha1_hash const& ih) + : torrent_alert(alloc, h) + , info_hash(ih) + {} + + std::string torrent_removed_alert::message() const + { + return torrent_alert::message() + " removed"; + } + + torrent_need_cert_alert::torrent_need_cert_alert(aux::stack_allocator& alloc + , torrent_handle const& h) + : torrent_alert(alloc, h) + {} + + std::string torrent_need_cert_alert::message() const + { + return torrent_alert::message() + " needs SSL certificate"; + } + + incoming_connection_alert::incoming_connection_alert(aux::stack_allocator&, int t + , tcp::endpoint const& i) + : socket_type(t) + , endpoint(i) +#if TORRENT_ABI_VERSION == 1 + , ip(i) +#endif + {} + + std::string incoming_connection_alert::message() const + { + char msg[600]; + std::snprintf(msg, sizeof(msg), "incoming connection from %s (%s)" + , print_endpoint(endpoint).c_str(), socket_type_str[socket_type]); + return msg; + } + + peer_connect_alert::peer_connect_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int type) + : peer_alert(alloc, h, ep, peer_id) + , socket_type(type) + {} + + std::string peer_connect_alert::message() const + { + char msg[600]; + std::snprintf(msg, sizeof(msg), "%s connecting to peer (%s)" + , peer_alert::message().c_str(), socket_type_str[socket_type]); + return msg; + } + + add_torrent_alert::add_torrent_alert(aux::stack_allocator& alloc, torrent_handle const& h + , add_torrent_params const& p, error_code const& ec) + : torrent_alert(alloc, h) + , params(p) + , error(ec) + {} + + std::string add_torrent_alert::message() const + { + char msg[600]; + char info_hash[41]; + char const* torrent_name = info_hash; + if (params.ti) torrent_name = params.ti->name().c_str(); + else if (!params.name.empty()) torrent_name = params.name.c_str(); +#if TORRENT_ABI_VERSION == 1 + else if (!params.url.empty()) torrent_name = params.url.c_str(); +#endif + else aux::to_hex(params.info_hash, info_hash); + + if (error) + { + std::snprintf(msg, sizeof(msg), "failed to add torrent \"%s\": [%s] %s" + , torrent_name, error.category().name() + , convert_from_native(error.message()).c_str()); + } + else + { + std::snprintf(msg, sizeof(msg), "added torrent: %s", torrent_name); + } + return msg; + } + + state_update_alert::state_update_alert(aux::stack_allocator& + , std::vector st) + : status(std::move(st)) + {} + + std::string state_update_alert::message() const + { + char msg[600]; + std::snprintf(msg, sizeof(msg), "state updates for %d torrents", int(status.size())); + return msg; + } + +#if TORRENT_ABI_VERSION == 1 + mmap_cache_alert::mmap_cache_alert(aux::stack_allocator& + , error_code const& ec): error(ec) + {} + + std::string mmap_cache_alert::message() const + { + char msg[600]; + std::snprintf(msg, sizeof(msg), "mmap cache failed: (%d) %s", error.value() + , convert_from_native(error.message()).c_str()); + return msg; + } +#endif + + char const* operation_name(operation_t const op) + { + static char const* const names[] = { + "unknown", + "bittorrent", + "iocontrol", + "getpeername", + "getname", + "alloc_recvbuf", + "alloc_sndbuf", + "file_write", + "file_read", + "file", + "sock_write", + "sock_read", + "sock_open", + "sock_bind", + "available", + "encryption", + "connect", + "ssl_handshake", + "get_interface", + "sock_listen", + "sock_bind_to_device", + "sock_accept", + "parse_address", + "enum_if", + "file_stat", + "file_copy", + "file_fallocate", + "file_hard_link", + "file_remove", + "file_rename", + "file_open", + "mkdir", + "check_resume", + "exception", + "alloc_cache_piece", + "partfile_move", + "partfile_read", + "partfile_write", + "hostname_lookup", + "symlink", + "handshake", + "sock_option", + "enum_route" + }; + + int const idx = static_cast(op); + if (idx < 0 || idx >= int(sizeof(names) / sizeof(names[0]))) + return "unknown operation"; + + return names[idx]; + } + +#if TORRENT_ABI_VERSION == 1 + char const* operation_name(int const op) + { + return operation_name(static_cast(op)); + } +#endif + + peer_error_alert::peer_error_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, operation_t const op_ + , error_code const& e) + : peer_alert(alloc, h, ep, peer_id) + , op(op_) + , error(e) +#if TORRENT_ABI_VERSION == 1 + , operation(static_cast(op_)) + , msg(convert_from_native(error.message())) +#endif + {} + + std::string peer_error_alert::message() const + { + char buf[200]; + std::snprintf(buf, sizeof(buf), "%s peer error [%s] [%s]: %s" + , peer_alert::message().c_str() + , operation_name(op), error.category().name() + , convert_from_native(error.message()).c_str()); + return buf; + } + +#if TORRENT_ABI_VERSION == 1 + torrent_update_alert::torrent_update_alert(aux::stack_allocator& alloc, torrent_handle h + , sha1_hash const& old_hash, sha1_hash const& new_hash) + : torrent_alert(alloc, h) + , old_ih(old_hash) + , new_ih(new_hash) + {} + + std::string torrent_update_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg), " torrent changed info-hash from: %s to %s" + , aux::to_hex(old_ih).c_str() + , aux::to_hex(new_ih).c_str()); + return torrent_alert::message() + msg; + } +#endif + + peer_disconnected_alert::peer_disconnected_alert(aux::stack_allocator& alloc + , torrent_handle const& h, tcp::endpoint const& ep + , peer_id const& peer_id, operation_t op_, int type, error_code const& e + , close_reason_t r) + : peer_alert(alloc, h, ep, peer_id) + , socket_type(type) + , op(op_) + , error(e) + , reason(r) +#if TORRENT_ABI_VERSION == 1 + , operation(static_cast(op)) + , msg(convert_from_native(error.message())) +#endif + {} + + std::string peer_disconnected_alert::message() const + { + char buf[600]; + std::snprintf(buf, sizeof(buf), "%s disconnecting (%s) [%s] [%s]: %s (reason: %d)" + , peer_alert::message().c_str() + , socket_type_str[socket_type] + , operation_name(op), error.category().name() + , convert_from_native(error.message()).c_str() + , int(reason)); + return buf; + } + + dht_error_alert::dht_error_alert(aux::stack_allocator& + , operation_t const op_ + , error_code const& ec) + : error(ec) + , op(op_) +#if TORRENT_ABI_VERSION == 1 + , operation(op_ == operation_t::hostname_lookup + ? op_t::hostname_lookup : op_t::unknown) +#endif + {} + + std::string dht_error_alert::message() const + { + char msg[600]; + std::snprintf(msg, sizeof(msg), "DHT error [%s] (%d) %s" + , operation_name(op) + , error.value() + , convert_from_native(error.message()).c_str()); + return msg; + } + + dht_immutable_item_alert::dht_immutable_item_alert(aux::stack_allocator& + , sha1_hash const& t, entry const& i) + : target(t), item(i) + {} + + std::string dht_immutable_item_alert::message() const + { + char msg[1050]; + std::snprintf(msg, sizeof(msg), "DHT immutable item %s [ %s ]" + , aux::to_hex(target).c_str() + , item.to_string().c_str()); + return msg; + } + + // TODO: 2 the salt here is allocated on the heap. It would be nice to + // allocate in the stack_allocator + dht_mutable_item_alert::dht_mutable_item_alert(aux::stack_allocator& + , std::array const& k + , std::array const& sig + , std::int64_t sequence + , string_view s + , entry const& i + , bool a) + : key(k), signature(sig), seq(sequence), salt(s), item(i), authoritative(a) + {} + + std::string dht_mutable_item_alert::message() const + { + char msg[1050]; + std::snprintf(msg, sizeof(msg), "DHT mutable item (key=%s salt=%s seq=%" PRId64 " %s) [ %s ]" + , aux::to_hex(key).c_str() + , salt.c_str() + , seq + , authoritative ? "auth" : "non-auth" + , item.to_string().c_str()); + return msg; + } + + dht_put_alert::dht_put_alert(aux::stack_allocator&, sha1_hash const& t, int n) + : target(t) + , public_key() + , signature() + , salt() + , seq(0) + , num_success(n) + {} + + dht_put_alert::dht_put_alert(aux::stack_allocator& + , std::array const& key + , std::array const& sig + , std::string s + , std::int64_t sequence_number + , int n) + : target(nullptr) + , public_key(key) + , signature(sig) + , salt(std::move(s)) + , seq(sequence_number) + , num_success(n) + {} + + std::string dht_put_alert::message() const + { + char msg[1050]; + if (target.is_all_zeros()) + { + std::snprintf(msg, sizeof(msg), "DHT put complete (success=%d key=%s sig=%s salt=%s seq=%" PRId64 ")" + , num_success + , aux::to_hex(public_key).c_str() + , aux::to_hex(signature).c_str() + , salt.c_str() + , seq); + return msg; + } + + std::snprintf(msg, sizeof(msg), "DHT put commplete (success=%d hash=%s)" + , num_success + , aux::to_hex(target).c_str()); + return msg; + } + + i2p_alert::i2p_alert(aux::stack_allocator&, error_code const& ec) + : error(ec) + {} + + std::string i2p_alert::message() const + { + char msg[600]; + std::snprintf(msg, sizeof(msg), "i2p_error: [%s] %s" + , error.category().name(), convert_from_native(error.message()).c_str()); + return msg; + } + + dht_outgoing_get_peers_alert::dht_outgoing_get_peers_alert(aux::stack_allocator& + , sha1_hash const& ih, sha1_hash const& obfih + , udp::endpoint ep) + : info_hash(ih) + , obfuscated_info_hash(obfih) + , endpoint(std::move(ep)) +#if TORRENT_ABI_VERSION == 1 + , ip(endpoint) +#endif + {} + + std::string dht_outgoing_get_peers_alert::message() const + { + char msg[600]; + char obf[70]; + obf[0] = '\0'; + if (obfuscated_info_hash != info_hash) + { + std::snprintf(obf, sizeof(obf), " [obfuscated: %s]" + , aux::to_hex(obfuscated_info_hash).c_str()); + } + std::snprintf(msg, sizeof(msg), "outgoing dht get_peers : %s%s -> %s" + , aux::to_hex(info_hash).c_str() + , obf + , print_endpoint(endpoint).c_str()); + return msg; + } + + log_alert::log_alert(aux::stack_allocator& alloc, char const* log) + : m_alloc(alloc) + , m_str_idx(alloc.copy_string(log)) + {} + log_alert::log_alert(aux::stack_allocator& alloc, char const* fmt, va_list v) + : m_alloc(alloc) + , m_str_idx(alloc.format_string(fmt, v)) + {} + + char const* log_alert::log_message() const + { + return m_alloc.get().ptr(m_str_idx); + } + +#if TORRENT_ABI_VERSION == 1 + char const* log_alert::msg() const + { + return log_message(); + } +#endif + + std::string log_alert::message() const + { + return log_message(); + } + + torrent_log_alert::torrent_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , char const* fmt, va_list v) + : torrent_alert(alloc, h) + , m_str_idx(alloc.format_string(fmt, v)) + {} + + char const* torrent_log_alert::log_message() const + { + return m_alloc.get().ptr(m_str_idx); + } + +#if TORRENT_ABI_VERSION == 1 + char const* torrent_log_alert::msg() const + { + return log_message(); + } +#endif + + std::string torrent_log_alert::message() const + { + return torrent_alert::message() + ": " + log_message(); + } + + peer_log_alert::peer_log_alert(aux::stack_allocator& alloc + , torrent_handle const& h + , tcp::endpoint const& i, peer_id const& pi + , peer_log_alert::direction_t dir + , char const* event, char const* fmt, va_list v) + : peer_alert(alloc, h, i, pi) + , event_type(event) + , direction(dir) + , m_str_idx(alloc.format_string(fmt, v)) + {} + + char const* peer_log_alert::log_message() const + { + return m_alloc.get().ptr(m_str_idx); + } + +#if TORRENT_ABI_VERSION == 1 + char const* peer_log_alert::msg() const + { + return log_message(); + } +#endif + + std::string peer_log_alert::message() const + { + static char const* const mode[] = + { "<==", "==>", "<<<", ">>>", "***" }; + return peer_alert::message() + " [" + print_endpoint(endpoint) + "] " + + mode[direction] + " " + event_type + " [ " + log_message() + " ]"; + } + + lsd_error_alert::lsd_error_alert(aux::stack_allocator&, error_code const& ec) + : alert() + , error(ec) + {} + + std::string lsd_error_alert::message() const + { + return "Local Service Discovery startup error: " + convert_from_native(error.message()); + } + +#if TORRENT_ABI_VERSION == 1 +namespace { + + aux::array counters_to_array(counters const& cnt) + { + aux::array arr; + + for (int i = 0; i < counters::num_counters; ++i) + arr[i] = cnt[i]; + + return arr; + } +} +#else +namespace { + template + T* align_pointer(U* ptr) + { + return reinterpret_cast((reinterpret_cast(ptr) + alignof(T) - 1) + & ~(alignof(T) - 1)); + } +} +#endif + +#if TORRENT_ABI_VERSION == 1 + session_stats_alert::session_stats_alert(aux::stack_allocator&, struct counters const& cnt) + : values(counters_to_array(cnt)) + {} +#else + session_stats_alert::session_stats_alert(aux::stack_allocator& alloc, struct counters const& cnt) + : m_alloc(alloc) + , m_counters_idx(alloc.allocate(sizeof(std::int64_t) + * counters::num_counters + sizeof(std::int64_t) - 1)) + { + std::int64_t* ptr = align_pointer(alloc.ptr(m_counters_idx)); + for (int i = 0; i < counters::num_counters; ++i, ++ptr) + *ptr = cnt[i]; + } +#endif + + std::string session_stats_alert::message() const + { + char msg[50]; + auto cnt = counters(); + std::snprintf(msg, sizeof(msg), "session stats (%d values): " , int(cnt.size())); + std::string ret = msg; + bool first = true; + for (auto v : cnt) + { + std::snprintf(msg, sizeof(msg), first ? "%" PRId64 : ", %" PRId64, v); + first = false; + ret += msg; + } + return ret; + } + + span session_stats_alert::counters() const + { +#if TORRENT_ABI_VERSION == 1 + return values; +#else + return { align_pointer(m_alloc.get().ptr(m_counters_idx)) + , counters::num_counters }; +#endif + } + + dht_stats_alert::dht_stats_alert(aux::stack_allocator& + , std::vector table + , std::vector requests) + : alert() + , active_requests(std::move(requests)) + , routing_table(std::move(table)) + {} + + std::string dht_stats_alert::message() const + { + char buf[2048]; + std::snprintf(buf, sizeof(buf), "DHT stats: reqs: %d buckets: %d" + , int(active_requests.size()) + , int(routing_table.size())); + return buf; + } + + url_seed_alert::url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, error_code const& e) + : torrent_alert(alloc, h) + , error(e) + , m_url_idx(alloc.copy_string(u)) + , m_msg_idx() +#if TORRENT_ABI_VERSION == 1 + , url(u) + , msg(convert_from_native(e.message())) +#endif + {} + + url_seed_alert::url_seed_alert(aux::stack_allocator& alloc, torrent_handle const& h + , string_view u, string_view m) + : torrent_alert(alloc, h) + , m_url_idx(alloc.copy_string(u)) + , m_msg_idx(alloc.copy_string(m)) +#if TORRENT_ABI_VERSION == 1 + , url(u) + , msg(m) +#endif + {} + + std::string url_seed_alert::message() const + { + return torrent_alert::message() + " url seed (" + + server_url() + ") failed: " + convert_from_native(error.message()); + } + + char const* url_seed_alert::server_url() const + { + return m_alloc.get().ptr(m_url_idx); + } + + char const* url_seed_alert::error_message() const + { + if (m_msg_idx == aux::allocation_slot()) return ""; + return m_alloc.get().ptr(m_msg_idx); + } + + file_error_alert::file_error_alert(aux::stack_allocator& alloc + , error_code const& ec, string_view f, operation_t const op_ + , torrent_handle const& h) + : torrent_alert(alloc, h) + , error(ec) + , op(op_) + , m_file_idx(alloc.copy_string(f)) +#if TORRENT_ABI_VERSION == 1 + , operation(operation_name(op_)) + , file(f) + , msg(convert_from_native(error.message())) +#endif + {} + + char const* file_error_alert::filename() const + { + return m_alloc.get().ptr(m_file_idx); + } + + std::string file_error_alert::message() const + { + return torrent_alert::message() + " " + + operation_name(op) + " (" + filename() + + ") error: " + convert_from_native(error.message()); + } + + incoming_request_alert::incoming_request_alert(aux::stack_allocator& alloc + , peer_request r, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id) + : peer_alert(alloc, h, ep, peer_id) + , req(r) + {} + + std::string incoming_request_alert::message() const + { + char msg[1024]; + std::snprintf(msg, sizeof(msg), "%s: incoming request [ piece: %d start: %d length: %d ]" + , peer_alert::message().c_str(), static_cast(req.piece) + , req.start, req.length); + return msg; + } + + dht_log_alert::dht_log_alert(aux::stack_allocator& alloc + , dht_log_alert::dht_module_t m, const char* fmt, va_list v) + : module(m) + , m_alloc(alloc) + , m_msg_idx(alloc.format_string(fmt, v)) + {} + + char const* dht_log_alert::log_message() const + { + return m_alloc.get().ptr(m_msg_idx); + } + + std::string dht_log_alert::message() const + { + static char const* const dht_modules[] = + { + "tracker", + "node", + "routing_table", + "rpc_manager", + "traversal" + }; + + char ret[900]; + std::snprintf(ret, sizeof(ret), "DHT %s: %s", dht_modules[module] + , log_message()); + return ret; + } + + dht_pkt_alert::dht_pkt_alert(aux::stack_allocator& alloc + , span buf, dht_pkt_alert::direction_t d + , udp::endpoint const& ep) + : direction(d) + , node(ep) + , m_alloc(alloc) + , m_msg_idx(alloc.copy_buffer(buf)) + , m_size(aux::numeric_cast(buf.size())) +#if TORRENT_ABI_VERSION == 1 + , dir(d) +#endif + {} + + span dht_pkt_alert::pkt_buf() const + { + return {m_alloc.get().ptr(m_msg_idx), m_size}; + } + + std::string dht_pkt_alert::message() const + { + bdecode_node print; + error_code ec; + + // ignore errors here. This is best-effort. It may be a broken encoding + // but at least we'll print the valid parts + span pkt = pkt_buf(); + bdecode(pkt.data(), pkt.data() + int(pkt.size()), print, ec, nullptr, 100, 100); + + std::string msg = print_entry(print, true); + + static char const* const prefix[2] = {"<==", "==>"}; + char buf[1024]; + std::snprintf(buf, sizeof(buf), "%s [%s] %s", prefix[direction] + , print_endpoint(node).c_str(), msg.c_str()); + + return buf; + } + + dht_get_peers_reply_alert::dht_get_peers_reply_alert(aux::stack_allocator& alloc + , sha1_hash const& ih + , std::vector const& peers) + : info_hash(ih) + , m_alloc(alloc) + { + for (auto const& endp : peers) + { + if (is_v4(endp)) + m_v4_num_peers++; + else + m_v6_num_peers++; + } + + m_v4_peers_idx = alloc.allocate(m_v4_num_peers * 6); + m_v6_peers_idx = alloc.allocate(m_v6_num_peers * 18); + + char* v4_ptr = alloc.ptr(m_v4_peers_idx); + char* v6_ptr = alloc.ptr(m_v6_peers_idx); + for (auto const& endp : peers) + { + if (is_v4(endp)) + detail::write_endpoint(endp, v4_ptr); + else + detail::write_endpoint(endp, v6_ptr); + } + } + + std::string dht_get_peers_reply_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg), "incoming dht get_peers reply: %s, peers %d" + , aux::to_hex(info_hash).c_str(), num_peers()); + return msg; + } + + int dht_get_peers_reply_alert::num_peers() const + { + return m_v4_num_peers + m_v6_num_peers; + } + +#if TORRENT_ABI_VERSION == 1 + void dht_get_peers_reply_alert::peers(std::vector &v) const + { + std::vector p(peers()); + v.reserve(p.size()); + std::copy(p.begin(), p.end(), std::back_inserter(v)); + } +#endif + std::vector dht_get_peers_reply_alert::peers() const + { + aux::vector peers; + peers.reserve(num_peers()); + + char const* v4_ptr = m_alloc.get().ptr(m_v4_peers_idx); + for (int i = 0; i < m_v4_num_peers; i++) + peers.push_back(detail::read_v4_endpoint(v4_ptr)); + char const* v6_ptr = m_alloc.get().ptr(m_v6_peers_idx); + for (int i = 0; i < m_v6_num_peers; i++) + peers.push_back(detail::read_v6_endpoint(v6_ptr)); + + return std::move(peers); + } + + dht_direct_response_alert::dht_direct_response_alert( + aux::stack_allocator& alloc, void* userdata_ + , udp::endpoint const& addr_, bdecode_node const& response) + : userdata(userdata_), endpoint(addr_) + , m_alloc(alloc) + , m_response_idx(alloc.copy_buffer(response.data_section())) + , m_response_size(int(response.data_section().size())) +#if TORRENT_ABI_VERSION == 1 + , addr(addr_) +#endif + {} + + dht_direct_response_alert::dht_direct_response_alert( + aux::stack_allocator& alloc + , void* userdata_ + , udp::endpoint const& addr_) + : userdata(userdata_), endpoint(addr_) + , m_alloc(alloc) + , m_response_idx() + , m_response_size(0) +#if TORRENT_ABI_VERSION == 1 + , addr(addr_) +#endif + {} + + std::string dht_direct_response_alert::message() const + { + char msg[1050]; + std::snprintf(msg, sizeof(msg), "DHT direct response (address=%s) [ %s ]" + , endpoint.address().to_string().c_str() + , m_response_size ? std::string(m_alloc.get().ptr(m_response_idx) + , aux::numeric_cast(m_response_size)).c_str() : ""); + return msg; + } + + bdecode_node dht_direct_response_alert::response() const + { + if (m_response_size == 0) return bdecode_node(); + char const* start = m_alloc.get().ptr(m_response_idx); + char const* end = start + m_response_size; + error_code ec; + bdecode_node ret; + bdecode(start, end, ret, ec); + TORRENT_ASSERT(!ec); + return ret; + } + + picker_log_alert::picker_log_alert(aux::stack_allocator& alloc, torrent_handle const& h + , tcp::endpoint const& ep, peer_id const& peer_id, picker_flags_t const flags + , span blocks) + : peer_alert(alloc, h, ep, peer_id) + , picker_flags(flags) + , m_array_idx(alloc.copy_buffer({reinterpret_cast(blocks.data()) + , blocks.size() * int(sizeof(piece_block))})) + , m_num_blocks(int(blocks.size())) + {} + + std::vector picker_log_alert::blocks() const + { + // we need to copy this array to make sure the structures are properly + // aligned, not just to have a nice API + std::size_t const num_blocks = aux::numeric_cast(m_num_blocks); + std::vector ret(num_blocks); + + char const* start = m_alloc.get().ptr(m_array_idx); + std::memcpy(ret.data(), start, num_blocks * sizeof(piece_block)); + + return ret; + } + + constexpr picker_flags_t picker_log_alert::partial_ratio; + constexpr picker_flags_t picker_log_alert::prioritize_partials; + constexpr picker_flags_t picker_log_alert::rarest_first_partials; + constexpr picker_flags_t picker_log_alert::rarest_first; + constexpr picker_flags_t picker_log_alert::reverse_rarest_first; + constexpr picker_flags_t picker_log_alert::suggested_pieces; + constexpr picker_flags_t picker_log_alert::prio_sequential_pieces; + constexpr picker_flags_t picker_log_alert::sequential_pieces; + constexpr picker_flags_t picker_log_alert::reverse_pieces; + constexpr picker_flags_t picker_log_alert::time_critical; + constexpr picker_flags_t picker_log_alert::random_pieces; + constexpr picker_flags_t picker_log_alert::prefer_contiguous; + constexpr picker_flags_t picker_log_alert::reverse_sequential; + constexpr picker_flags_t picker_log_alert::backup1; + constexpr picker_flags_t picker_log_alert::backup2; + constexpr picker_flags_t picker_log_alert::end_game; + constexpr picker_flags_t picker_log_alert::extent_affinity; + + std::string picker_log_alert::message() const + { + static char const* const flag_names[] = + { + "partial_ratio ", + "prioritize_partials ", + "rarest_first_partials ", + "rarest_first ", + "reverse_rarest_first ", + "suggested_pieces ", + "prio_sequential_pieces ", + "sequential_pieces ", + "reverse_pieces ", + "time_critical ", + "random_pieces ", + "prefer_contiguous ", + "reverse_sequential ", + "backup1 ", + "backup2 ", + "end_game ", + "extent_affinity ", + }; + + std::string ret = peer_alert::message(); + + auto flags = static_cast(picker_flags); + int idx = 0; + ret += " picker_log [ "; + for (; flags != 0; flags >>= 1, ++idx) + { + if ((flags & 1) == 0) continue; + ret += flag_names[idx]; + } + ret += "] "; + + std::vector b = blocks(); + + for (auto const& p : b) + { + char buf[50]; + std::snprintf(buf, sizeof(buf), "(%d,%d) " + , static_cast(p.piece_index), p.block_index); + ret += buf; + } + return ret; + } + + session_error_alert::session_error_alert(aux::stack_allocator& alloc + , error_code e, string_view error_str) + : error(e) + , m_alloc(alloc) + , m_msg_idx(alloc.copy_buffer(error_str)) + {} + + std::string session_error_alert::message() const + { + char buf[400]; + if (error) + { + std::snprintf(buf, sizeof(buf), "session error: (%d %s) %s" + , error.value(), convert_from_native(error.message()).c_str() + , m_alloc.get().ptr(m_msg_idx)); + } + else + { + std::snprintf(buf, sizeof(buf), "session error: %s" + , m_alloc.get().ptr(m_msg_idx)); + } + return buf; + } + +namespace { + + using nodes_slot = std::tuple; + + nodes_slot write_nodes(aux::stack_allocator& alloc + , std::vector> const& nodes) + { + int v4_num_nodes = 0; + int v6_num_nodes = 0; + + for (auto const& n : nodes) + { + if (is_v4(n.second)) + v4_num_nodes++; + else + v6_num_nodes++; + } + + aux::allocation_slot const v4_nodes_idx = alloc.allocate(v4_num_nodes * (20 + 6)); + aux::allocation_slot const v6_nodes_idx = alloc.allocate(v6_num_nodes * (20 + 18)); + + char* v4_ptr = alloc.ptr(v4_nodes_idx); + char* v6_ptr = alloc.ptr(v6_nodes_idx); + for (auto const& n : nodes) + { + udp::endpoint const& endp = n.second; + if (is_v4(endp)) + { + detail::write_string(n.first.to_string(), v4_ptr); + detail::write_endpoint(endp, v4_ptr); + } + else + { + detail::write_string(n.first.to_string(), v6_ptr); + detail::write_endpoint(endp, v6_ptr); + } + } + + return nodes_slot{v4_num_nodes, v4_nodes_idx, v6_num_nodes, v6_nodes_idx}; + } + + std::vector> read_nodes( + aux::stack_allocator const& alloc + , int const v4_num_nodes, aux::allocation_slot const v4_nodes_idx + , int const v6_num_nodes, aux::allocation_slot const v6_nodes_idx) + { + aux::vector> nodes; + nodes.reserve(v4_num_nodes + v6_num_nodes); + + char const* v4_ptr = alloc.ptr(v4_nodes_idx); + for (int i = 0; i < v4_num_nodes; i++) + { + sha1_hash ih; + std::memcpy(ih.data(), v4_ptr, 20); + v4_ptr += 20; + nodes.emplace_back(ih, detail::read_v4_endpoint(v4_ptr)); + } + char const* v6_ptr = alloc.ptr(v6_nodes_idx); + for (int i = 0; i < v6_num_nodes; i++) + { + sha1_hash ih; + std::memcpy(ih.data(), v6_ptr, 20); + v6_ptr += 20; + nodes.emplace_back(ih, detail::read_v6_endpoint(v6_ptr)); + } + + return std::move(nodes); + } + } + + dht_live_nodes_alert::dht_live_nodes_alert(aux::stack_allocator& alloc + , sha1_hash const& nid + , std::vector> const& nodes) + : node_id(nid) + , m_alloc(alloc) + { + std::tie(m_v4_num_nodes, m_v4_nodes_idx, m_v6_num_nodes, m_v6_nodes_idx) + = write_nodes(alloc, nodes); + } + + std::string dht_live_nodes_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg), "dht live nodes for id: %s, nodes %d" + , aux::to_hex(node_id).c_str(), num_nodes()); + return msg; + } + + int dht_live_nodes_alert::num_nodes() const + { + return m_v4_num_nodes + m_v6_num_nodes; + } + + std::vector> dht_live_nodes_alert::nodes() const + { + return read_nodes(m_alloc.get() + , m_v4_num_nodes, m_v4_nodes_idx + , m_v6_num_nodes, m_v6_nodes_idx); + } + + session_stats_header_alert::session_stats_header_alert(aux::stack_allocator&) + {} + + std::string session_stats_header_alert::message() const + { + std::string stats_header = "session stats header: "; + std::vector stats = session_stats_metrics(); + std::sort(stats.begin(), stats.end() + , [] (stats_metric const& lhs, stats_metric const& rhs) + { return lhs.value_index < rhs.value_index; }); + bool first = true; + for (auto const& s : stats) + { + if (!first) stats_header += ", "; + stats_header += s.name; + first = false; + } + + return stats_header; + } + + dht_sample_infohashes_alert::dht_sample_infohashes_alert(aux::stack_allocator& alloc + , udp::endpoint const& endp + , time_duration _interval + , int _num + , std::vector const& samples + , std::vector> const& nodes) + : endpoint(endp) + , interval(_interval) + , num_infohashes(_num) + , m_alloc(alloc) + , m_num_samples(aux::numeric_cast(samples.size())) + { + m_samples_idx = alloc.allocate(m_num_samples * 20); + + char *ptr = alloc.ptr(m_samples_idx); + std::memcpy(ptr, samples.data(), samples.size() * 20); + + std::tie(m_v4_num_nodes, m_v4_nodes_idx, m_v6_num_nodes, m_v6_nodes_idx) + = write_nodes(alloc, nodes); + } + + std::string dht_sample_infohashes_alert::message() const + { + char msg[200]; + std::snprintf(msg, sizeof(msg) + , "incoming dht sample_infohashes reply from: %s, samples %d" + , print_endpoint(endpoint).c_str(), m_num_samples); + return msg; + } + + int dht_sample_infohashes_alert::num_samples() const + { + return m_num_samples; + } + + std::vector dht_sample_infohashes_alert::samples() const + { + aux::vector samples; + samples.resize(m_num_samples); + + char const* ptr = m_alloc.get().ptr(m_samples_idx); + std::memcpy(samples.data(), ptr, samples.size() * 20); + + return std::move(samples); + } + + int dht_sample_infohashes_alert::num_nodes() const + { + return m_v4_num_nodes + m_v6_num_nodes; + } + + std::vector> dht_sample_infohashes_alert::nodes() const + { + return read_nodes(m_alloc.get() + , m_v4_num_nodes, m_v4_nodes_idx + , m_v6_num_nodes, m_v6_nodes_idx); + } + + block_uploaded_alert::block_uploaded_alert(aux::stack_allocator& alloc, torrent_handle h + , tcp::endpoint const& ep, peer_id const& peer_id, int block_num + , piece_index_t piece_num) + : peer_alert(alloc, h, ep, peer_id) + , block_index(block_num) + , piece_index(piece_num) + { + TORRENT_ASSERT(block_index >= 0 && piece_index >= piece_index_t{0}); + } + + std::string block_uploaded_alert::message() const + { + char ret[200]; + snprintf(ret, sizeof(ret), "%s block uploaded to a peer (piece: %d block: %d)" + , peer_alert::message().c_str(), static_cast(piece_index), block_index); + return ret; + } + + alerts_dropped_alert::alerts_dropped_alert(aux::stack_allocator& + , std::bitset const& dropped) + : dropped_alerts(dropped) + {} + + char const* alert_name(int const alert_type) + { + static std::array const names = {{ +#if TORRENT_ABI_VERSION == 1 + "torrent", "peer", "tracker", "torrent_added", +#else + "", "", "", "", +#endif + "torrent_removed", "read_piece", "file_completed", + "file_renamed", "file_rename_failed", "performance", + "state_changed", "tracker_error", "tracker_warning", + "scrape_reply", "scrape_failed", "tracker_reply", + "dht_reply", "tracker_announce", "hash_failed", + "peer_ban", "peer_unsnubbed", "peer_snubbed", + "peer_error", "peer_connect", "peer_disconnected", + "invalid_request", "torrent_finished", "piece_finished", + "request_dropped", "block_timeout", "block_finished", + "block_downloading", "unwanted_block", "storage_moved", + "storage_moved_failed", "torrent_deleted", + "torrent_delete_failed", "save_resume_data", + "save_resume_data_failed", "torrent_paused", + "torrent_resumed", "torrent_checked", "url_seed", + "file_error", "metadata_failed", "metadata_received", + "udp_error", "external_ip", "listen_failed", + "listen_succeeded", "portmap_error", "portmap", + "portmap_log", "fastresume_rejected", "peer_blocked", + "dht_announce", "dht_get_peers", "stats", + "cache_flushed", "anonymous_mode", "lsd_peer", + "trackerid", "dht_bootstrap", "", "torrent_error", + "torrent_need_cert", "incoming_connection", + "add_torrent", "state_update", +#if TORRENT_ABI_VERSION == 1 + "mmap_cache", +#else + "", +#endif + "session_stats", +#if TORRENT_ABI_VERSION == 1 + "torrent_update", +#else + "", +#endif + "", "dht_error", "dht_immutable_item", "dht_mutable_item", + "dht_put", "i2p", "dht_outgoing_get_peers", "log", + "torrent_log", "peer_log", "lsd_error", + "dht_stats", "incoming_request", "dht_log", + "dht_pkt", "dht_get_peers_reply", "dht_direct_response", + "picker_log", "session_error", "dht_live_nodes", + "session_stats_header", "dht_sample_infohashes", + "block_uploaded", "alerts_dropped", "socks5" + }}; + + TORRENT_ASSERT(alert_type >= 0); + TORRENT_ASSERT(alert_type < num_alert_types); + return names[std::size_t(alert_type)]; + } + + std::string alerts_dropped_alert::message() const + { + std::string ret = "dropped alerts: "; + + TORRENT_ASSERT(int(dropped_alerts.size()) == num_alert_types); + for (int idx = 0; idx < num_alert_types; ++idx) + { + if (!dropped_alerts.test(std::size_t(idx))) continue; + ret += alert_name(idx); + ret += ' '; + } + + return ret; + } + + socks5_alert::socks5_alert(aux::stack_allocator& + , tcp::endpoint const& ep, operation_t operation, error_code const& ec) + : error(ec) + , op(operation) + , ip(ep) + {} + + std::string socks5_alert::message() const + { + char buf[512]; + std::snprintf(buf, sizeof(buf), "SOCKS5 error. op: %s ec: %s ep: %s" + , operation_name(op), error.message().c_str(), print_endpoint(ip).c_str()); + return buf; + } + + // this will no longer be necessary in C++17 + constexpr alert_category_t torrent_removed_alert::static_category; + constexpr alert_category_t read_piece_alert::static_category; + constexpr alert_category_t file_completed_alert::static_category; + constexpr alert_category_t file_renamed_alert::static_category; + constexpr alert_category_t file_rename_failed_alert::static_category; + constexpr alert_category_t performance_alert::static_category; + constexpr alert_category_t state_changed_alert::static_category; + constexpr alert_category_t tracker_error_alert::static_category; + constexpr alert_category_t tracker_warning_alert::static_category; + constexpr alert_category_t scrape_reply_alert::static_category; + constexpr alert_category_t scrape_failed_alert::static_category; + constexpr alert_category_t tracker_reply_alert::static_category; + constexpr alert_category_t dht_reply_alert::static_category; + constexpr alert_category_t tracker_announce_alert::static_category; + constexpr alert_category_t hash_failed_alert::static_category; + constexpr alert_category_t peer_ban_alert::static_category; + constexpr alert_category_t peer_unsnubbed_alert::static_category; + constexpr alert_category_t peer_snubbed_alert::static_category; + constexpr alert_category_t peer_error_alert::static_category; + constexpr alert_category_t peer_connect_alert::static_category; + constexpr alert_category_t peer_disconnected_alert::static_category; + constexpr alert_category_t invalid_request_alert::static_category; + constexpr alert_category_t torrent_finished_alert::static_category; + constexpr alert_category_t piece_finished_alert::static_category; + constexpr alert_category_t request_dropped_alert::static_category; + constexpr alert_category_t block_timeout_alert::static_category; + constexpr alert_category_t block_finished_alert::static_category; + constexpr alert_category_t block_downloading_alert::static_category; + constexpr alert_category_t unwanted_block_alert::static_category; + constexpr alert_category_t storage_moved_alert::static_category; + constexpr alert_category_t storage_moved_failed_alert::static_category; + constexpr alert_category_t torrent_deleted_alert::static_category; + constexpr alert_category_t torrent_delete_failed_alert::static_category; + constexpr alert_category_t save_resume_data_alert::static_category; + constexpr alert_category_t save_resume_data_failed_alert::static_category; + constexpr alert_category_t torrent_paused_alert::static_category; + constexpr alert_category_t torrent_resumed_alert::static_category; + constexpr alert_category_t torrent_checked_alert::static_category; + constexpr alert_category_t url_seed_alert::static_category; + constexpr alert_category_t file_error_alert::static_category; + constexpr alert_category_t metadata_failed_alert::static_category; + constexpr alert_category_t metadata_received_alert::static_category; + constexpr alert_category_t udp_error_alert::static_category; + constexpr alert_category_t external_ip_alert::static_category; + constexpr alert_category_t listen_failed_alert::static_category; + constexpr alert_category_t listen_succeeded_alert::static_category; + constexpr alert_category_t portmap_error_alert::static_category; + constexpr alert_category_t portmap_alert::static_category; + constexpr alert_category_t portmap_log_alert::static_category; + constexpr alert_category_t fastresume_rejected_alert::static_category; + constexpr alert_category_t peer_blocked_alert::static_category; + constexpr alert_category_t dht_announce_alert::static_category; + constexpr alert_category_t dht_get_peers_alert::static_category; + constexpr alert_category_t stats_alert::static_category; + constexpr alert_category_t cache_flushed_alert::static_category; + constexpr alert_category_t lsd_peer_alert::static_category; + constexpr alert_category_t trackerid_alert::static_category; + constexpr alert_category_t dht_bootstrap_alert::static_category; + constexpr alert_category_t torrent_error_alert::static_category; + constexpr alert_category_t torrent_need_cert_alert::static_category; + constexpr alert_category_t incoming_connection_alert::static_category; + constexpr alert_category_t add_torrent_alert::static_category; + constexpr alert_category_t state_update_alert::static_category; + constexpr alert_category_t session_stats_alert::static_category; + constexpr alert_category_t dht_error_alert::static_category; + constexpr alert_category_t dht_immutable_item_alert::static_category; + constexpr alert_category_t dht_mutable_item_alert::static_category; + constexpr alert_category_t dht_put_alert::static_category; + constexpr alert_category_t i2p_alert::static_category; + constexpr alert_category_t dht_outgoing_get_peers_alert::static_category; + constexpr alert_category_t log_alert::static_category; + constexpr alert_category_t torrent_log_alert::static_category; + constexpr alert_category_t peer_log_alert::static_category; + constexpr alert_category_t lsd_error_alert::static_category; + constexpr alert_category_t dht_stats_alert::static_category; + constexpr alert_category_t incoming_request_alert::static_category; + constexpr alert_category_t dht_log_alert::static_category; + constexpr alert_category_t dht_pkt_alert::static_category; + constexpr alert_category_t dht_get_peers_reply_alert::static_category; + constexpr alert_category_t dht_direct_response_alert::static_category; + constexpr alert_category_t picker_log_alert::static_category; + constexpr alert_category_t session_error_alert::static_category; + constexpr alert_category_t dht_live_nodes_alert::static_category; + constexpr alert_category_t session_stats_header_alert::static_category; + constexpr alert_category_t dht_sample_infohashes_alert::static_category; + constexpr alert_category_t block_uploaded_alert::static_category; + constexpr alert_category_t alerts_dropped_alert::static_category; + constexpr alert_category_t socks5_alert::static_category; +#if TORRENT_ABI_VERSION == 1 + constexpr alert_category_t anonymous_mode_alert::static_category; + constexpr alert_category_t mmap_cache_alert::static_category; + constexpr alert_category_t torrent_added_alert::static_category; + constexpr alert_category_t torrent_update_alert::static_category; +#endif + +} // namespace libtorrent diff --git a/src/alert_manager.cpp b/src/alert_manager.cpp new file mode 100644 index 0000000..e4a624e --- /dev/null +++ b/src/alert_manager.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/alert_types.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS +#include "libtorrent/extensions.hpp" +#endif + +namespace libtorrent { + + alert_manager::alert_manager(int const queue_limit, alert_category_t const alert_mask) + : m_alert_mask(alert_mask) + , m_queue_size_limit(queue_limit) + {} + + alert_manager::~alert_manager() = default; + + alert* alert_manager::wait_for_alert(time_duration max_wait) + { + std::unique_lock lock(m_mutex); + + if (!m_alerts[m_generation].empty()) + return m_alerts[m_generation].front(); + + // this call can be interrupted prematurely by other signals + m_condition.wait_for(lock, max_wait); + if (!m_alerts[m_generation].empty()) + return m_alerts[m_generation].front(); + + return nullptr; + } + + void alert_manager::maybe_notify(alert* a) + { + if (m_alerts[m_generation].size() == 1) + { + // we just posted to an empty queue. If anyone is waiting for + // alerts, we need to notify them. Also (potentially) call the + // user supplied m_notify callback to let the client wake up its + // message loop to poll for alerts. + if (m_notify) m_notify(); + + // TODO: 2 keep a count of the number of threads waiting. Only if it's + // > 0 notify them + m_condition.notify_all(); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& e : m_ses_extensions) + e->on_alert(a); +#else + TORRENT_UNUSED(a); +#endif + } + + void alert_manager::set_notify_function(std::function const& fun) + { + std::unique_lock lock(m_mutex); + m_notify = fun; + if (!m_alerts[m_generation].empty()) + { + if (m_notify) m_notify(); + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void alert_manager::add_extension(std::shared_ptr ext) + { + m_ses_extensions.push_back(ext); + } +#endif + + void alert_manager::get_all(std::vector& alerts) + { + std::lock_guard lock(m_mutex); + + if (m_alerts[m_generation].empty()) + { + alerts.clear(); + return; + } + + if (m_dropped.any()) { + emplace_alert(m_dropped); + m_dropped.reset(); + } + + m_alerts[m_generation].get_pointers(alerts); + + // swap buffers + m_generation = (m_generation + 1) & 1; + // clear the one we will start writing to now + m_alerts[m_generation].clear(); + m_allocations[m_generation].reset(); + } + + bool alert_manager::pending() const + { + std::lock_guard lock(m_mutex); + return !m_alerts[m_generation].empty(); + } + + int alert_manager::set_alert_queue_size_limit(int queue_size_limit_) + { + std::lock_guard lock(m_mutex); + + std::swap(m_queue_size_limit, queue_size_limit_); + return queue_size_limit_; + } +} diff --git a/src/announce_entry.cpp b/src/announce_entry.cpp new file mode 100644 index 0000000..9345657 --- /dev/null +++ b/src/announce_entry.cpp @@ -0,0 +1,164 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/string_util.hpp" // for is_space +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" + +namespace libtorrent { + + namespace { + // wait at least 5 seconds before retrying a failed tracker + seconds32 constexpr tracker_retry_delay_min{5}; + + // never wait more than 60 minutes to retry a tracker + minutes32 constexpr tracker_retry_delay_max{60}; + } + + announce_endpoint::announce_endpoint(aux::listen_socket_handle const& s, bool const completed) + : local_endpoint(s ? s.get_local_endpoint() : tcp::endpoint()) + , socket(s) + , fails(0) + , updating(false) + , start_sent(false) + , complete_sent(completed) + , triggered_manually(false) + , enabled(true) + {} + + announce_entry::announce_entry(string_view u) + : url(u.to_string()) + , source(0) + , verified(false) +#if TORRENT_ABI_VERSION == 1 + , fails(0) + , send_stats(false) + , start_sent(false) + , complete_sent(false) + , triggered_manually(false) + , updating(false) +#endif + {} + + announce_entry::announce_entry() + : source(0) + , verified(false) +#if TORRENT_ABI_VERSION == 1 + , fails(0) + , send_stats(false) + , start_sent(false) + , complete_sent(false) + , triggered_manually(false) + , updating(false) +#endif + {} + + announce_entry::~announce_entry() = default; + announce_entry::announce_entry(announce_entry const&) = default; + announce_entry& announce_entry::operator=(announce_entry const&) = default; + + void announce_endpoint::reset() + { + start_sent = false; + next_announce = time_point32::min(); + min_announce = time_point32::min(); + } + + void announce_endpoint::failed(int const backoff_ratio, seconds32 const retry_interval) + { + // fails is only 7 bits + if (fails < (1 << 7) - 1) ++fails; + + // the exponential back-off ends up being: + // 7, 15, 27, 45, 95, 127, 165, ... seconds + // with the default tracker_backoff of 250 + int const fail_square = int(fails) * int(fails); + seconds32 const delay = std::max(retry_interval + , std::min(duration_cast(tracker_retry_delay_max) + , tracker_retry_delay_min + + fail_square * tracker_retry_delay_min * backoff_ratio / 100 + )); + if (!is_working()) next_announce = aux::time_now32() + delay; + updating = false; + } + + bool announce_endpoint::can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const + { + // if we're a seed and we haven't sent a completed + // event, we need to let this announce through + bool const need_send_complete = is_seed && !complete_sent; + + // add some slack here for rounding errors + return now + seconds(1) >= next_announce + && (now >= min_announce || need_send_complete) + && (fails < fail_limit || fail_limit == 0) + && !updating; + } + + void announce_entry::reset() + { + for (auto& aep : endpoints) + aep.reset(); + } + +#if TORRENT_ABI_VERSION == 1 + bool announce_entry::can_announce(time_point now, bool is_seed) const + { + return std::any_of(endpoints.begin(), endpoints.end() + , [&](announce_endpoint const& aep) { return aep.can_announce(now, is_seed, fail_limit); }); + } + + bool announce_entry::is_working() const + { + return std::any_of(endpoints.begin(), endpoints.end() + , [](announce_endpoint const& aep) { return aep.is_working(); }); + } +#endif + + announce_endpoint* announce_entry::find_endpoint(aux::listen_socket_handle const& s) + { + auto aep = std::find_if(endpoints.begin(), endpoints.end() + , [&](announce_endpoint const& a) { return a.socket == s; }); + if (aep != endpoints.end()) return &*aep; + else return nullptr; + } + + void announce_entry::trim() + { + while (!url.empty() && is_space(url[0])) + url.erase(url.begin()); + } + +} diff --git a/src/assert.cpp b/src/assert.cpp new file mode 100644 index 0000000..66e51ab --- /dev/null +++ b/src/assert.cpp @@ -0,0 +1,403 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_PRODUCTION_ASSERTS +#include +#endif + +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + +#ifdef __APPLE__ +#include +#endif + +#include +#include +#include +#include +#include // for snprintf +#include // for PRId64 et.al. +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +// uClibc++ doesn't have cxxabi.h +#if defined __GNUC__ && __GNUC__ >= 3 \ + && !defined __UCLIBCXX_MAJOR__ + +#include + +namespace libtorrent { +std::string demangle(char const* name) +{ +// in case this string comes + // this is needed on linux + char const* start = std::strchr(name, '('); + if (start != nullptr) + { + ++start; + } + else + { + // this is needed on macos x + start = strstr(name, "0x"); + if (start != nullptr) + { + start = std::strchr(start, ' '); + if (start != nullptr) ++start; + else start = name; + } + else start = name; + } + + char const* end = std::strchr(start, '+'); + if (end) while (*(end-1) == ' ') --end; + + std::string in; + if (end == nullptr) in.assign(start); + else in.assign(start, end); + + size_t len; + int status; + char* unmangled = ::abi::__cxa_demangle(in.c_str(), nullptr, &len, &status); + if (unmangled == nullptr) return in; + std::string ret(unmangled); + ::free(unmangled); + return ret; +} +} +#elif defined _WIN32 + +#include "windows.h" +#include "dbghelp.h" + +namespace libtorrent { +std::string demangle(char const* name) +{ + char demangled_name[256]; + if (UnDecorateSymbolName(name, demangled_name, sizeof(demangled_name), UNDNAME_NO_THROW_SIGNATURES) == 0) + demangled_name[0] = 0; + return demangled_name; +} +} + +#else +namespace libtorrent { +std::string demangle(char const* name) { return name; } +} +#endif + +#include +#include +#include +#include "libtorrent/version.hpp" + +#if TORRENT_USE_EXECINFO +#include + +namespace libtorrent { + +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth, void*) +{ + void* stack[50]; + int size = ::backtrace(stack, 50); + char** symbols = ::backtrace_symbols(stack, size); + + for (int i = 1; i < size && len > 0; ++i) + { + int ret = std::snprintf(out, std::size_t(len), "%d: %s\n", i, demangle(symbols[i]).c_str()); + out += ret; + len -= ret; + if (i - 1 == max_depth && max_depth > 0) break; + } + + ::free(symbols); +} +} + +#elif defined _WIN32 + +#include "windows.h" +#include "libtorrent/utf8.hpp" +#include + +#include "winbase.h" +#include "dbghelp.h" + +namespace libtorrent { + +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth + , void* ctx) +{ + // all calls to DbgHlp.dll are thread-unsafe. i.e. they all need to be + // synchronized and not called concurrently. This mutex serializes access + static std::mutex dbghlp_mutex; + std::lock_guard l(dbghlp_mutex); + + CONTEXT context_record; + if (ctx) + { + context_record = *static_cast(ctx); + } + else + { + // use the current thread's context + RtlCaptureContext(&context_record); + } + + int size = 0; + std::array stack; + + STACKFRAME64 stack_frame = {}; +#if defined(_WIN64) + int const machine_type = IMAGE_FILE_MACHINE_AMD64; + stack_frame.AddrPC.Offset = context_record.Rip; + stack_frame.AddrFrame.Offset = context_record.Rbp; + stack_frame.AddrStack.Offset = context_record.Rsp; +#else + int const machine_type = IMAGE_FILE_MACHINE_I386; + stack_frame.AddrPC.Offset = context_record.Eip; + stack_frame.AddrFrame.Offset = context_record.Ebp; + stack_frame.AddrStack.Offset = context_record.Esp; +#endif + stack_frame.AddrPC.Mode = AddrModeFlat; + stack_frame.AddrFrame.Mode = AddrModeFlat; + stack_frame.AddrStack.Mode = AddrModeFlat; + while (StackWalk64(machine_type, + GetCurrentProcess(), + GetCurrentThread(), + &stack_frame, + &context_record, + nullptr, + &SymFunctionTableAccess64, + &SymGetModuleBase64, + nullptr) && size < int(stack.size())) + { + stack[size++] = reinterpret_cast(stack_frame.AddrPC.Offset); + } + + struct symbol_bundle : SYMBOL_INFO + { + wchar_t name[MAX_SYM_NAME]; + }; + + HANDLE p = GetCurrentProcess(); + static bool sym_initialized = false; + if (!sym_initialized) + { + sym_initialized = true; + SymInitialize(p, nullptr, true); + } + SymRefreshModuleList(p); + for (int i = 0; i < size && len > 0; ++i) + { + DWORD_PTR frame_ptr = reinterpret_cast(stack[i]); + + DWORD64 displacement = 0; + symbol_bundle symbol; + symbol.MaxNameLen = MAX_SYM_NAME; + symbol.SizeOfStruct = sizeof(SYMBOL_INFO); + BOOL const has_symbol = SymFromAddr(p, frame_ptr, &displacement, &symbol); + + DWORD line_displacement = 0; + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + BOOL const has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame_ptr, + &line_displacement, &line); + + int ret = std::snprintf(out, len, "%2d: %p", i, stack[i]); + out += ret; len -= ret; if (len <= 0) break; + + if (has_symbol) + { + ret = std::snprintf(out, len, " %s +%-4" PRId64 + , demangle(symbol.Name).c_str(), displacement); + out += ret; len -= ret; if (len <= 0) break; + } + + if (has_line) + { + ret = std::snprintf(out, len, " %s:%d" + , line.FileName, int(line.LineNumber)); + out += ret; len -= ret; if (len <= 0) break; + } + + + ret = std::snprintf(out, len, "\n"); + out += ret; + len -= ret; + + if (i == max_depth && max_depth > 0) break; + } +} +} + +#else + +namespace libtorrent { + +TORRENT_EXPORT void print_backtrace(char* out, int len, int /*max_depth*/, void* /* ctx */) +{ + out[0] = 0; + std::strncat(out, "", std::size_t(len)); +} + +} + +#endif + +#endif + +#if (TORRENT_USE_ASSERTS || defined TORRENT_ASIO_DEBUGGING) && \ + defined TORRENT_PRODUCTION_ASSERTS +char const* libtorrent_assert_log = "asserts.log"; +namespace { +// the number of asserts we've printed to the log +std::atomic assert_counter(0); +} +#endif + +namespace libtorrent { + +#if TORRENT_USE_ASSERTS || defined TORRENT_ASIO_DEBUGGING + +TORRENT_FORMAT(1,2) +TORRENT_EXPORT void assert_print(char const* fmt, ...) +{ +#ifdef TORRENT_PRODUCTION_ASSERTS + if (assert_counter > 500) return; + + FILE* out = fopen(libtorrent_assert_log, "a+"); + if (out == nullptr) out = stderr; +#else + FILE* out = stderr; +#endif + va_list va; + va_start(va, fmt); + std::vfprintf(out, fmt, va); + va_end(va); + +#ifdef TORRENT_PRODUCTION_ASSERTS + if (out != stderr) fclose(out); +#endif +} + +// we deliberately don't want asserts to be marked as no-return, since that +// would trigger warnings in debug builds of any code coming after the assert +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#endif + +TORRENT_EXPORT void assert_fail(char const* expr, int line + , char const* file, char const* function, char const* value, int kind) +{ +#ifdef TORRENT_PRODUCTION_ASSERTS + // no need to flood the assert log with infinite number of asserts + if (assert_counter.fetch_add(1) + 1 > 500) return; +#endif + + char stack[8192]; + stack[0] = '\0'; + print_backtrace(stack, sizeof(stack), 0); + + char const* message = "assertion failed. Please file a bugreport at " + "https://github.com/arvidn/libtorrent/issues\n" + "Please include the following information:\n\n" + "version: " LIBTORRENT_VERSION "-" LIBTORRENT_REVISION "\n"; + + switch (kind) + { + case 1: + message = "A precondition of a libtorrent function has been violated.\n" + "This indicates a bug in the client application using libtorrent\n"; + } + + assert_print("%s\n" +#ifdef TORRENT_PRODUCTION_ASSERTS + "#: %d\n" +#endif + "file: '%s'\n" + "line: %d\n" + "function: %s\n" + "expression: %s\n" + "%s%s\n" + "stack:\n" + "%s\n" + , message +#ifdef TORRENT_PRODUCTION_ASSERTS + , assert_counter.load() +#endif + , file, line, function, expr + , value ? value : "", value ? "\n" : "" + , stack); + + // if production asserts are defined, don't abort, just print the error +#ifndef TORRENT_PRODUCTION_ASSERTS +#ifdef TORRENT_WINDOWS + // SIGINT doesn't trigger a break with msvc + DebugBreak(); +#else + // send SIGINT to the current process + // to break into the debugger + ::raise(SIGABRT); +#endif + ::abort(); +#endif +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#elif !TORRENT_USE_ASSERTS + +// these are just here to make it possible for a client that built with debug +// enable to be able to link against a release build (just possible, not +// necessarily supported) +TORRENT_FORMAT(1,2) +TORRENT_EXPORT void assert_print(char const*, ...) {} +TORRENT_EXPORT void assert_fail(char const*, int, char const* + , char const*, char const*, int) {} + +#endif + +} // libtorrent namespace + diff --git a/src/bandwidth_limit.cpp b/src/bandwidth_limit.cpp new file mode 100644 index 0000000..c7b905b --- /dev/null +++ b/src/bandwidth_limit.cpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bandwidth_limit.hpp" +#include + +namespace libtorrent { + + bandwidth_channel::bandwidth_channel() + : tmp(0) + , distribute_quota(0) + , m_quota_left(0) + , m_limit(0) + {} + + // 0 means infinite + void bandwidth_channel::throttle(int const limit) + { + TORRENT_ASSERT_VAL(limit >= 0, limit); + // if the throttle is more than this, we might overflow + TORRENT_ASSERT_VAL(limit < inf, limit); + m_limit = limit; + } + + int bandwidth_channel::quota_left() const + { + if (m_limit == 0) return inf; + return std::max(int(m_quota_left), 0); + } + + void bandwidth_channel::update_quota(int const dt_milliseconds) + { + TORRENT_ASSERT_VAL(m_limit >= 0, m_limit); + TORRENT_ASSERT_VAL(m_limit < inf, m_limit); + + if (m_limit == 0) return; + + // "to_add" should never have int64 overflow: "m_limit" contains < "::max" + std::int64_t const to_add = (std::int64_t(m_limit) * dt_milliseconds + 500) / 1000; + + if (to_add > inf - m_quota_left) + { + m_quota_left = inf; + } + else + { + m_quota_left += to_add; + if (m_quota_left / 3 > m_limit) m_quota_left = std::int64_t(m_limit) * 3; + // "m_quota_left" will never have int64 overflow but may exceed "::max" + m_quota_left = std::min(m_quota_left, std::int64_t(inf)); + } + + distribute_quota = int(std::max(m_quota_left, std::int64_t(0))); + } + + // this is used when connections disconnect with + // some quota left. It's returned to its bandwidth + // channels. + void bandwidth_channel::return_quota(int const amount) + { + TORRENT_ASSERT(amount >= 0); + if (m_limit == 0) return; + TORRENT_ASSERT(m_quota_left <= m_quota_left + amount); + m_quota_left += amount; + } + + void bandwidth_channel::use_quota(int const amount) + { + TORRENT_ASSERT(amount >= 0); + TORRENT_ASSERT(m_limit >= 0); + if (m_limit == 0) return; + + m_quota_left -= amount; + } + +} diff --git a/src/bandwidth_manager.cpp b/src/bandwidth_manager.cpp new file mode 100644 index 0000000..8fa6286 --- /dev/null +++ b/src/bandwidth_manager.cpp @@ -0,0 +1,221 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bandwidth_manager.hpp" + +#if TORRENT_USE_ASSERTS +#include +#endif + +namespace libtorrent { + + bandwidth_manager::bandwidth_manager(int channel) + : m_queued_bytes(0) + , m_channel(channel) + , m_abort(false) + { + } + + void bandwidth_manager::close() + { + m_abort = true; + + std::vector queue; + queue.swap(m_queue); + m_queued_bytes = 0; + + while (!queue.empty()) + { + bw_request& bwr = queue.back(); + bwr.peer->assign_bandwidth(m_channel, bwr.assigned); + queue.pop_back(); + } + } + +#if TORRENT_USE_ASSERTS + bool bandwidth_manager::is_queued(bandwidth_socket const* peer) const + { + for (auto const& r : m_queue) + { + if (r.peer.get() == peer) return true; + } + return false; + } +#endif + + int bandwidth_manager::queue_size() const + { + return int(m_queue.size()); + } + + std::int64_t bandwidth_manager::queued_bytes() const + { + return m_queued_bytes; + } + + // non prioritized means that, if there's a line for bandwidth, + // others will cut in front of the non-prioritized peers. + // this is used by web seeds + int bandwidth_manager::request_bandwidth(std::shared_ptr peer + , int const blk, int const priority, bandwidth_channel** chan, int const num_channels) + { + INVARIANT_CHECK; + if (m_abort) return 0; + + TORRENT_ASSERT(blk > 0); + TORRENT_ASSERT(priority > 0); + + // if this assert is hit, the peer is requesting more bandwidth before + // being assigned bandwidth for an already outstanding request + TORRENT_ASSERT(!is_queued(peer.get())); + + if (num_channels == 0) + { + // the connection is not rate limited by any of its + // bandwidth channels, or it doesn't belong to any + // channels. There's no point in adding it to + // the queue, just satisfy the request immediately + return blk; + } + + int k = 0; + bw_request bwr(std::move(peer), blk, priority); + for (int i = 0; i < num_channels; ++i) + { + if (chan[i]->need_queueing(blk)) + bwr.channel[k++] = chan[i]; + } + + if (k == 0) return blk; + + m_queued_bytes += blk; + m_queue.push_back(std::move(bwr)); + return 0; + } + +#if TORRENT_USE_INVARIANT_CHECKS + void bandwidth_manager::check_invariant() const + { + std::int64_t queued = 0; + for (auto const& r : m_queue) + { + queued += r.request_size - r.assigned; + } + TORRENT_ASSERT(queued == m_queued_bytes); + } +#endif + + void bandwidth_manager::update_quotas(time_duration const& dt) + { + if (m_abort) return; + if (m_queue.empty()) return; + + INVARIANT_CHECK; + + std::int64_t dt_milliseconds = total_milliseconds(dt); + if (dt_milliseconds > 3000) dt_milliseconds = 3000; + + // for each bandwidth channel, call update_quota(dt) + + std::vector channels; + + std::vector queue; + + for (auto i = m_queue.begin(); i != m_queue.end();) + { + if (i->peer->is_disconnecting()) + { + m_queued_bytes -= i->request_size - i->assigned; + + // return all assigned quota to all the + // bandwidth channels this peer belongs to + for (int j = 0; j < bw_request::max_bandwidth_channels && i->channel[j]; ++j) + { + bandwidth_channel* bwc = i->channel[j]; + bwc->return_quota(i->assigned); + } + + i->assigned = 0; + queue.push_back(std::move(*i)); + i = m_queue.erase(i); + continue; + } + for (int j = 0; j < bw_request::max_bandwidth_channels && i->channel[j]; ++j) + { + bandwidth_channel* bwc = i->channel[j]; + bwc->tmp = 0; + } + ++i; + } + + for (auto const& r : m_queue) + { + for (int j = 0; j < bw_request::max_bandwidth_channels && r.channel[j]; ++j) + { + bandwidth_channel* bwc = r.channel[j]; + if (bwc->tmp == 0) channels.push_back(bwc); + TORRENT_ASSERT(INT_MAX - bwc->tmp > r.priority); + bwc->tmp += r.priority; + } + } + + for (auto const& ch : channels) + { + ch->update_quota(int(dt_milliseconds)); + } + + for (auto i = m_queue.begin(); i != m_queue.end();) + { + int a = i->assign_bandwidth(); + if (i->assigned == i->request_size + || (i->ttl <= 0 && i->assigned > 0)) + { + a += i->request_size - i->assigned; + TORRENT_ASSERT(i->assigned <= i->request_size); + queue.push_back(std::move(*i)); + i = m_queue.erase(i); + } + else + { + ++i; + } + m_queued_bytes -= a; + } + + while (!queue.empty()) + { + bw_request& bwr = queue.back(); + bwr.peer->assign_bandwidth(m_channel, bwr.assigned); + queue.pop_back(); + } + } +} diff --git a/src/bandwidth_queue_entry.cpp b/src/bandwidth_queue_entry.cpp new file mode 100644 index 0000000..1dde602 --- /dev/null +++ b/src/bandwidth_queue_entry.cpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/bandwidth_queue_entry.hpp" + +namespace libtorrent { + + bw_request::bw_request(std::shared_ptr pe + , int blk, int prio) + : peer(std::move(pe)) + , priority(prio) + , assigned(0) + , request_size(blk) + , ttl(20) + { + TORRENT_ASSERT(priority > 0); + } + + int bw_request::assign_bandwidth() + { + TORRENT_ASSERT(assigned < request_size); + int quota = request_size - assigned; + TORRENT_ASSERT(quota >= 0); + --ttl; + if (quota == 0) return quota; + + for (int j = 0; j < 5 && channel[j]; ++j) + { + if (channel[j]->throttle() == 0) continue; + if (channel[j]->tmp == 0) continue; + quota = std::min(int(std::int64_t(channel[j]->distribute_quota) + * priority / channel[j]->tmp), quota); + } + assigned += quota; + for (int j = 0; j < 5 && channel[j]; ++j) + channel[j]->use_quota(quota); + TORRENT_ASSERT(assigned <= request_size); + return quota; + } + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + static_assert(std::is_nothrow_move_assignable::value + , "should be nothrow move assignable"); +} diff --git a/src/bdecode.cpp b/src/bdecode.cpp new file mode 100644 index 0000000..08090d5 --- /dev/null +++ b/src/bdecode.cpp @@ -0,0 +1,1143 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bdecode.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/error_code.hpp" +#include +#include // for memset +#include // for snprintf +#include // for PRId64 et.al. + +#ifndef BOOST_SYSTEM_NOEXCEPT +#define BOOST_SYSTEM_NOEXCEPT throw() +#endif + +namespace libtorrent { + + using detail::bdecode_token; + +namespace { + + bool numeric(char c) { return c >= '0' && c <= '9'; } + + // finds the end of an integer and verifies that it looks valid this does + // not detect all overflows, just the ones that are an order of magnitude + // beyond. Exact overflow checking is done when the integer value is queried + // from a bdecode_node. + char const* check_integer(char const* start, char const* end + , bdecode_errors::error_code_enum& e) + { + if (start == end) + { + e = bdecode_errors::unexpected_eof; + return start; + } + + if (*start == '-') + { + ++start; + if (start == end) + { + e = bdecode_errors::unexpected_eof; + return start; + } + } + + int digits = 0; + do + { + if (!numeric(*start)) + { + e = bdecode_errors::expected_digit; + break; + } + ++start; + ++digits; + + if (start == end) + { + e = bdecode_errors::unexpected_eof; + break; + } + } + while (*start != 'e'); + + if (digits > 20) + { + e = bdecode_errors::overflow; + } + + return start; + } + + struct stack_frame + { + stack_frame() : token(0), state(0) {} + explicit stack_frame(int const t): token(std::uint32_t(t)), state(0) {} + // this is an index into m_tokens + std::uint32_t token:31; + // this is used for dictionaries to indicate whether we're + // reading a key or a vale. 0 means key 1 is value + std::uint32_t state:1; + }; + + // diff between current and next item offset + // should only be called for non last item in array + int token_source_span(bdecode_token const& t) + { + return (&t)[1].offset - t.offset; + } + +} // anonymous namespace + +namespace detail { + void escape_string(std::string& ret, char const* str, int len) + { + for (int i = 0; i < len; ++i) + { + if (str[i] >= 32 && str[i] < 127) + { + ret += str[i]; + } + else + { + char tmp[5]; + std::snprintf(tmp, sizeof(tmp), "\\x%02x", std::uint8_t(str[i])); + ret += tmp; + } + } + } +} + + + + // reads the string between start and end, or up to the first occurrance of + // 'delimiter', whichever comes first. This string is interpreted as an + // integer which is assigned to 'val'. If there's a non-delimiter and + // non-digit in the range, a parse error is reported in 'ec'. If the value + // cannot be represented by the variable 'val' and overflow error is reported + // by 'ec'. + char const* parse_int(char const* start, char const* end, char delimiter + , std::int64_t& val, bdecode_errors::error_code_enum& ec) + { + while (start < end && *start != delimiter) + { + if (!numeric(*start)) + { + ec = bdecode_errors::expected_digit; + return start; + } + if (val > std::numeric_limits::max() / 10) + { + ec = bdecode_errors::overflow; + return start; + } + val *= 10; + int digit = *start - '0'; + if (val > std::numeric_limits::max() - digit) + { + ec = bdecode_errors::overflow; + return start; + } + val += digit; + ++start; + } + return start; + } + + + struct bdecode_error_category final : boost::system::error_category + { + const char* name() const BOOST_SYSTEM_NOEXCEPT override; + std::string message(int ev) const override; + boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT override + { return {ev, *this}; } + }; + + const char* bdecode_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "bdecode"; + } + + std::string bdecode_error_category::message(int ev) const + { + static char const* msgs[] = + { + "no error", + "expected digit in bencoded string", + "expected colon in bencoded string", + "unexpected end of file in bencoded string", + "expected value (list, dict, int or string) in bencoded string", + "bencoded nesting depth exceeded", + "bencoded item count limit exceeded", + "integer overflow", + }; + if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) + return "Unknown error"; + return msgs[ev]; + } + + boost::system::error_category& bdecode_category() + { + static bdecode_error_category bdecode_category; + return bdecode_category; + } + + namespace bdecode_errors + { + boost::system::error_code make_error_code(error_code_enum e) + { + return {e, bdecode_category()}; + } + } + + bdecode_node::bdecode_node(bdecode_node const& n) + : m_tokens(n.m_tokens) + , m_root_tokens(n.m_root_tokens) + , m_buffer(n.m_buffer) + , m_buffer_size(n.m_buffer_size) + , m_token_idx(n.m_token_idx) + , m_last_index(n.m_last_index) + , m_last_token(n.m_last_token) + , m_size(n.m_size) + { + (*this) = n; + } + + bdecode_node& bdecode_node::operator=(bdecode_node const& n) + { + if (&n == this) return *this; + m_tokens = n.m_tokens; + m_root_tokens = n.m_root_tokens; + m_buffer = n.m_buffer; + m_buffer_size = n.m_buffer_size; + m_token_idx = n.m_token_idx; + m_last_index = n.m_last_index; + m_last_token = n.m_last_token; + m_size = n.m_size; + if (!m_tokens.empty()) + { + // if this is a root, make the token pointer + // point to our storage + m_root_tokens = &m_tokens[0]; + } + return *this; + } + + bdecode_node::bdecode_node(bdecode_node&&) noexcept = default; + + bdecode_node::bdecode_node(bdecode_token const* tokens, char const* buf + , int len, int idx) + : m_root_tokens(tokens) + , m_buffer(buf) + , m_buffer_size(len) + , m_token_idx(idx) + , m_last_index(-1) + , m_last_token(-1) + , m_size(-1) + { + TORRENT_ASSERT(tokens != nullptr); + TORRENT_ASSERT(idx >= 0); + } + + bdecode_node bdecode_node::non_owning() const + { + // if we're not a root, just return a copy of ourself + if (m_tokens.empty()) return *this; + + // otherwise, return a reference to this node, but without + // being an owning root node + return bdecode_node(&m_tokens[0], m_buffer, m_buffer_size, m_token_idx); + } + + void bdecode_node::clear() + { + m_tokens.clear(); + m_root_tokens = nullptr; + m_token_idx = -1; + m_size = -1; + m_last_index = -1; + m_last_token = -1; + } + + void bdecode_node::switch_underlying_buffer(char const* buf) noexcept + { + TORRENT_ASSERT(!m_tokens.empty()); + if (m_tokens.empty()) return; + + m_buffer = buf; + } + + bool bdecode_node::has_soft_error(span error) const + { + if (type() == none_t) return false; + + bdecode_token const* tokens = m_root_tokens; + int token = m_token_idx; + + // we don't know what the original depth_limit was + // so this has to go on the heap + std::vector stack; + // make the initial allocation the default depth_limit + stack.reserve(100); + + do + { + switch (tokens[token].type) + { + case bdecode_token::integer: + if (m_buffer[tokens[token].offset + 1] == '0' + && m_buffer[tokens[token].offset + 2] != 'e') + { + std::snprintf(error.data(), std::size_t(error.size()), "leading zero in integer"); + return true; + } + break; + case bdecode_token::string: + if (m_buffer[tokens[token].offset] == '0' + && m_buffer[tokens[token].offset + 1] != ':') + { + std::snprintf(error.data(), std::size_t(error.size()), "leading zero in string length"); + return true; + } + break; + case bdecode_token::dict: + case bdecode_token::list: + stack.push_back(token); + break; + case bdecode_token::end: + auto const parent = stack.back(); + stack.pop_back(); + if (tokens[parent].type == bdecode_token::dict + && token != parent + 1) + { + // this is the end of a non-empty dict + // check the sort order of the keys + int k1 = parent + 1; + for (;;) + { + // skip to the first key's value + int const v1 = k1 + tokens[k1].next_item; + // then to the next key + int const k2 = v1 + tokens[v1].next_item; + + // check if k1 was the last key in the dict + if (k2 == token) + break; + + int const v2 = k2 + tokens[k2].next_item; + + int const k1_start = tokens[k1].offset + tokens[k1].start_offset(); + int const k1_len = tokens[v1].offset - k1_start; + int const k2_start = tokens[k2].offset + tokens[k2].start_offset(); + int const k2_len = tokens[v2].offset - k2_start; + + int const min_len = std::min(k1_len, k2_len); + + int cmp = std::memcmp(m_buffer + k1_start, m_buffer + k2_start, std::size_t(min_len)); + if (cmp > 0 || (cmp == 0 && k1_len > k2_len)) + { + std::snprintf(error.data(), std::size_t(error.size()), "unsorted dictionary key"); + return true; + } + else if (cmp == 0 && k1_len == k2_len) + { + std::snprintf(error.data(), std::size_t(error.size()), "duplicate dictionary key"); + return true; + } + + k1 = k2; + } + } + break; + } + + ++token; + } while (!stack.empty()); + return false; + } + + bdecode_node::type_t bdecode_node::type() const noexcept + { + if (m_token_idx == -1) return none_t; + return static_cast(m_root_tokens[m_token_idx].type); + } + + bdecode_node::operator bool() const noexcept + { return m_token_idx != -1; } + + span bdecode_node::data_section() const noexcept + { + if (m_token_idx == -1) return {}; + + TORRENT_ASSERT(m_token_idx != -1); + bdecode_token const& t = m_root_tokens[m_token_idx]; + bdecode_token const& next = m_root_tokens[m_token_idx + t.next_item]; + return {m_buffer + t.offset, static_cast(next.offset - t.offset)}; + } + + bdecode_node bdecode_node::list_at(int i) const + { + TORRENT_ASSERT(type() == list_t); + TORRENT_ASSERT(i >= 0); + + // make sure this is a list. + bdecode_token const* tokens = m_root_tokens; + + // this is the first item + int token = m_token_idx + 1; + int item = 0; + + // do we have a lookup cached? + if (m_last_index <= i && m_last_index != -1) + { + token = m_last_token; + item = m_last_index; + } + + while (item < i) + { + token += tokens[token].next_item; + ++item; + + // index 'i' out of range + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + } + + m_last_token = token; + m_last_index = i; + + return bdecode_node(tokens, m_buffer, m_buffer_size, token); + } + + string_view bdecode_node::list_string_value_at(int i + , string_view default_val) const + { + bdecode_node const n = list_at(i); + if (n.type() != bdecode_node::string_t) return default_val; + return n.string_value(); + } + + std::int64_t bdecode_node::list_int_value_at(int i + , std::int64_t default_val) const + { + bdecode_node const n = list_at(i); + if (n.type() != bdecode_node::int_t) return default_val; + return n.int_value(); + } + + int bdecode_node::list_size() const + { + TORRENT_ASSERT(type() == list_t); + + if (m_size != -1) return m_size; + + // make sure this is a list. + bdecode_token const* tokens = m_root_tokens; + TORRENT_ASSERT(tokens[m_token_idx].type == bdecode_token::list); + + // this is the first item + int token = m_token_idx + 1; + int ret = 0; + + // do we have a lookup cached? + if (m_last_index != -1) + { + token = m_last_token; + ret = m_last_index; + } + while (tokens[token].type != bdecode_token::end) + { + token += tokens[token].next_item; + ++ret; + } + + m_size = ret; + + return ret; + } + + std::pair bdecode_node::dict_at(int i) const + { + TORRENT_ASSERT(type() == dict_t); + TORRENT_ASSERT(m_token_idx != -1); + + bdecode_token const* tokens = m_root_tokens; + TORRENT_ASSERT(tokens[m_token_idx].type == bdecode_token::dict); + + int token = m_token_idx + 1; + int item = 0; + + // do we have a lookup cached? + if (m_last_index <= i && m_last_index != -1) + { + token = m_last_token; + item = m_last_index; + } + + while (item < i) + { + TORRENT_ASSERT(tokens[token].type == bdecode_token::string); + + // skip the key + token += tokens[token].next_item; + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + + // skip the value + token += tokens[token].next_item; + + ++item; + + // index 'i' out of range + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + } + + // there's no point in caching the first item + if (i > 0) + { + m_last_token = token; + m_last_index = i; + } + + int value_token = token + tokens[token].next_item; + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + + return std::make_pair( + bdecode_node(tokens, m_buffer, m_buffer_size, token).string_value() + , bdecode_node(tokens, m_buffer, m_buffer_size, value_token)); + } + + int bdecode_node::dict_size() const + { + TORRENT_ASSERT(type() == dict_t); + TORRENT_ASSERT(m_token_idx != -1); + + if (m_size != -1) return m_size; + + bdecode_token const* tokens = m_root_tokens; + TORRENT_ASSERT(tokens[m_token_idx].type == bdecode_token::dict); + + // this is the first item + int token = m_token_idx + 1; + int ret = 0; + + if (m_last_index != -1) + { + ret = m_last_index * 2; + token = m_last_token; + } + + while (tokens[token].type != bdecode_token::end) + { + token += tokens[token].next_item; + ++ret; + } + + // a dictionary must contain full key-value pairs. which means + // the number of entries is divisible by 2 + TORRENT_ASSERT((ret % 2) == 0); + + // each item is one key and one value, so divide by 2 + ret /= 2; + + m_size = ret; + + return ret; + } + + bdecode_node bdecode_node::dict_find(string_view key) const + { + TORRENT_ASSERT(type() == dict_t); + + bdecode_token const* const tokens = m_root_tokens; + + // this is the first item + int token = m_token_idx + 1; + + while (tokens[token].type != bdecode_token::end) + { + bdecode_token const& t = tokens[token]; + TORRENT_ASSERT(t.type == bdecode_token::string); + int const size = token_source_span(t) - t.start_offset(); + if (int(key.size()) == size + && std::equal(key.data(), key.data() + size, m_buffer + + t.offset + t.start_offset())) + { + // skip key + token += t.next_item; + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + + return bdecode_node(tokens, m_buffer, m_buffer_size, token); + } + + // skip key + token += t.next_item; + TORRENT_ASSERT(tokens[token].type != bdecode_token::end); + + // skip value + token += tokens[token].next_item; + } + + return bdecode_node(); + } + + bdecode_node bdecode_node::dict_find_list(string_view key) const + { + bdecode_node ret = dict_find(key); + if (ret.type() == bdecode_node::list_t) + return ret; + return bdecode_node(); + } + + bdecode_node bdecode_node::dict_find_dict(string_view key) const + { + bdecode_node ret = dict_find(key); + if (ret.type() == bdecode_node::dict_t) + return ret; + return bdecode_node(); + } + + bdecode_node bdecode_node::dict_find_string(string_view key) const + { + bdecode_node ret = dict_find(key); + if (ret.type() == bdecode_node::string_t) + return ret; + return bdecode_node(); + } + + bdecode_node bdecode_node::dict_find_int(string_view key) const + { + bdecode_node ret = dict_find(key); + if (ret.type() == bdecode_node::int_t) + return ret; + return bdecode_node(); + } + + string_view bdecode_node::dict_find_string_value(string_view key + , string_view default_value) const + { + bdecode_node n = dict_find(key); + if (n.type() != bdecode_node::string_t) return default_value; + return n.string_value(); + } + + std::int64_t bdecode_node::dict_find_int_value(string_view key + , std::int64_t default_val) const + { + bdecode_node n = dict_find(key); + if (n.type() != bdecode_node::int_t) return default_val; + return n.int_value(); + } + + std::int64_t bdecode_node::int_value() const + { + TORRENT_ASSERT(type() == int_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + int const size = token_source_span(t); + TORRENT_ASSERT(t.type == bdecode_token::integer); + + // +1 is to skip the 'i' + char const* ptr = m_buffer + t.offset + 1; + std::int64_t val = 0; + bool const negative = (*ptr == '-'); + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + char const* end = parse_int(ptr + int(negative) + , ptr + size, 'e', val, ec); + if (ec) return 0; + TORRENT_UNUSED(end); + TORRENT_ASSERT(end < ptr + size); + if (negative) val = -val; + return val; + } + + string_view bdecode_node::string_value() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + std::size_t const size = aux::numeric_cast(token_source_span(t) - t.start_offset()); + TORRENT_ASSERT(t.type == bdecode_token::string); + + return string_view(m_buffer + t.offset + t.start_offset(), size); + } + + char const* bdecode_node::string_ptr() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + TORRENT_ASSERT(t.type == bdecode_token::string); + return m_buffer + t.offset + t.start_offset(); + } + + int bdecode_node::string_length() const + { + TORRENT_ASSERT(type() == string_t); + bdecode_token const& t = m_root_tokens[m_token_idx]; + TORRENT_ASSERT(t.type == bdecode_token::string); + return token_source_span(t) - t.start_offset(); + } + + void bdecode_node::reserve(int tokens) + { m_tokens.reserve(aux::numeric_cast(tokens)); } + + void bdecode_node::swap(bdecode_node& n) + { +/* + bool lhs_is_root = (m_root_tokens == &m_tokens); + bool rhs_is_root = (n.m_root_tokens == &n.m_tokens); + + // swap is only defined between non-root nodes + // and between root-nodes. They may not be mixed! + // note that when swapping root nodes, all bdecode_node + // entries that exist in those subtrees are invalidated! + TORRENT_ASSERT(lhs_is_root == rhs_is_root); + + // if both are roots, m_root_tokens always point to + // its own vector, and should not get swapped (the + // underlying vectors are swapped already) + if (!lhs_is_root && !rhs_is_root) + { + // if neither is a root, we just swap the pointers + // to the token vectors, switching their roots + std::swap(m_root_tokens, n.m_root_tokens); + } +*/ + m_tokens.swap(n.m_tokens); + std::swap(m_root_tokens, n.m_root_tokens); + std::swap(m_buffer, n.m_buffer); + std::swap(m_buffer_size, n.m_buffer_size); + std::swap(m_token_idx, n.m_token_idx); + std::swap(m_last_index, n.m_last_index); + std::swap(m_last_token, n.m_last_token); + std::swap(m_size, n.m_size); + } + +#define TORRENT_FAIL_BDECODE(code) do { \ + ec = code; \ + if (error_pos) *error_pos = int(start - orig_start); \ + goto done; \ + } TORRENT_WHILE_0 + + int bdecode(char const* start, char const* end, bdecode_node& ret + , error_code& ec, int* error_pos, int const depth_limit, int token_limit) + { + ret = bdecode({start, end - start}, ec, error_pos, depth_limit, token_limit); + return ec ? -1 : 0; + } + + bdecode_node bdecode(span buffer, int depth_limit, int token_limit) + { + error_code ec; + bdecode_node ret = bdecode(buffer, ec, nullptr, depth_limit, token_limit); + if (ec) throw system_error(ec); + return ret; + } + + bdecode_node bdecode(span buffer + , error_code& ec, int* error_pos, int depth_limit, int token_limit) + { + bdecode_node ret; + ec.clear(); + + if (buffer.size() > bdecode_token::max_offset) + { + if (error_pos) *error_pos = 0; + ec = bdecode_errors::limit_exceeded; + return ret; + } + + // this is the stack of bdecode_token indices, into m_tokens. + // sp is the stack pointer, as index into the array, stack + int sp = 0; + TORRENT_ALLOCA(stack, stack_frame, depth_limit); + + // TODO: 2 attempt to simplify this implementation by embracing the span + char const* start = buffer.data(); + char const* end = start + buffer.size(); + char const* const orig_start = start; + + if (start == end) + TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + while (start <= end) + { + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + if (sp >= depth_limit) + TORRENT_FAIL_BDECODE(bdecode_errors::depth_exceeded); + + --token_limit; + if (token_limit < 0) + TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + // look for a new token + char const t = *start; + + int const current_frame = sp; + + // if we're currently parsing a dictionary, assert that + // every other node is a string. + if (current_frame > 0 + && ret.m_tokens[stack[current_frame - 1].token].type == bdecode_token::dict) + { + if (stack[current_frame - 1].state == 0) + { + // the current parent is a dict and we are parsing a key. + // only allow a digit (for a string) or 'e' to terminate + if (!numeric(t) && t != 'e') + TORRENT_FAIL_BDECODE(bdecode_errors::expected_digit); + } + } + + switch (t) + { + case 'd': + stack[sp++] = stack_frame(int(ret.m_tokens.size())); + // we push it into the stack so that we know where to fill + // in the next_node field once we pop this node off the stack. + // i.e. get to the node following the dictionary in the buffer + ret.m_tokens.push_back({start - orig_start, bdecode_token::dict}); + ++start; + break; + case 'l': + stack[sp++] = stack_frame(int(ret.m_tokens.size())); + // we push it into the stack so that we know where to fill + // in the next_node field once we pop this node off the stack. + // i.e. get to the node following the list in the buffer + ret.m_tokens.push_back({start - orig_start, bdecode_token::list}); + ++start; + break; + case 'i': + { + char const* const int_start = start; + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + // +1 here to point to the first digit, rather than 'i' + start = check_integer(start + 1, end, e); + if (e) + { + // in order to gracefully terminate the tree, + // make sure the end of the previous token is set correctly + if (error_pos) *error_pos = int(start - orig_start); + error_pos = nullptr; + start = int_start; + TORRENT_FAIL_BDECODE(e); + } + ret.m_tokens.push_back({int_start - orig_start + , 1, bdecode_token::integer, 1}); + TORRENT_ASSERT(*start == 'e'); + + // skip 'e' + ++start; + break; + } + case 'e': + { + // this is the end of a list or dict + if (sp == 0) + TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + if (sp > 0 + && ret.m_tokens[stack[sp - 1].token].type == bdecode_token::dict + && stack[sp - 1].state == 1) + { + // this means we're parsing a dictionary and about to parse a + // value associated with a key. Instead, we got a termination + TORRENT_FAIL_BDECODE(bdecode_errors::expected_value); + } + + // insert the end-of-sequence token + ret.m_tokens.push_back({start - orig_start, 1, bdecode_token::end}); + + // and back-patch the start of this sequence with the offset + // to the next token we'll insert + int const top = stack[sp - 1].token; + // subtract the token's own index, since this is a relative + // offset + if (int(ret.m_tokens.size()) - top > bdecode_token::max_next_item) + TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + ret.m_tokens[std::size_t(top)].next_item = std::uint32_t(int(ret.m_tokens.size()) - top); + + // and pop it from the stack. + TORRENT_ASSERT(sp > 0); + --sp; + ++start; + break; + } + default: + { + // this is the case for strings. The start character is any + // numeric digit + if (!numeric(t)) + TORRENT_FAIL_BDECODE(bdecode_errors::expected_value); + + std::int64_t len = t - '0'; + char const* const str_start = start; + ++start; + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + start = parse_int(start, end, ':', len, e); + if (e) + TORRENT_FAIL_BDECODE(e); + if (start == end) + TORRENT_FAIL_BDECODE(bdecode_errors::expected_colon); + + // remaining buffer size excluding ':' + ptrdiff_t const buff_size = end - start - 1; + if (len > buff_size) + TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + if (len < 0) + TORRENT_FAIL_BDECODE(bdecode_errors::overflow); + + // skip ':' + ++start; + // no need to range check start here + // the check above ensures that the buffer is long enough to hold + // the string's length which guarantees that start <= end + + // the bdecode_token only has 8 bits to keep the header size + // in. If it overflows, fail! + if (start - str_start - 2 > detail::bdecode_token::max_header) + TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + ret.m_tokens.push_back({str_start - orig_start + , 1, bdecode_token::string, std::uint8_t(start - str_start)}); + start += len; + break; + } + } + + if (current_frame > 0 + && ret.m_tokens[stack[current_frame - 1].token].type == bdecode_token::dict) + { + // the next item we parse is the opposite + // state is an unsigned 1-bit member. adding 1 will flip the bit + stack[current_frame - 1].state = (stack[current_frame - 1].state + 1) & 1; + } + + // this terminates the top level node, we're done! + if (sp == 0) break; + } + +done: + + // if parse failed, sp will be greater than 1 + // unwind the stack by inserting terminator to make whatever we have + // so far valid + while (sp > 0) { + TORRENT_ASSERT(ec); + --sp; + + // we may need to insert a dummy token to properly terminate the tree, + // in case we just parsed a key to a dict and failed in the value + if (ret.m_tokens[stack[sp].token].type == bdecode_token::dict + && stack[sp].state == 1) + { + // insert an empty dictionary as the value + ret.m_tokens.push_back({start - orig_start, 2, bdecode_token::dict}); + ret.m_tokens.push_back({start - orig_start, bdecode_token::end}); + } + + int const top = stack[sp].token; + TORRENT_ASSERT(int(ret.m_tokens.size()) - top <= bdecode_token::max_next_item); + ret.m_tokens[std::size_t(top)].next_item = std::uint32_t(int(ret.m_tokens.size()) - top); + ret.m_tokens.push_back({start - orig_start, 1, bdecode_token::end}); + } + + ret.m_tokens.push_back({start - orig_start, 0, bdecode_token::end}); + + ret.m_token_idx = 0; + ret.m_buffer = orig_start; + ret.m_buffer_size = int(start - orig_start); + ret.m_root_tokens = ret.m_tokens.data(); + + return ret; + } + + namespace { + + int line_longer_than(bdecode_node const& e, int limit) + { + int line_len = 0; + switch (e.type()) + { + case bdecode_node::list_t: + line_len += 4; + if (line_len > limit) return -1; + for (int i = 0; i < e.list_size(); ++i) + { + int const ret = line_longer_than(e.list_at(i), limit - line_len); + if (ret == -1) return -1; + line_len += ret + 2; + } + break; + case bdecode_node::dict_t: + line_len += 4; + if (line_len > limit) return -1; + for (int i = 0; i < e.dict_size(); ++i) + { + line_len += 4 + int(e.dict_at(i).first.size()); + if (line_len > limit) return -1; + int const ret = line_longer_than(e.dict_at(i).second, limit - line_len); + if (ret == -1) return -1; + line_len += ret + 1; + } + break; + case bdecode_node::string_t: + line_len += 3 + e.string_length(); + break; + case bdecode_node::int_t: + { + std::int64_t val = e.int_value(); + while (val > 0) + { + ++line_len; + val /= 10; + } + line_len += 2; + } + break; + case bdecode_node::none_t: + line_len += 4; + break; + } + + if (line_len > limit) return -1; + return line_len; + } + + void print_string(std::string& ret, string_view str, bool single_line) + { + int const len = int(str.size()); + bool printable = true; + for (int i = 0; i < len; ++i) + { + char const c = str[std::size_t(i)]; + if (c >= 32 && c < 127) continue; + printable = false; + break; + } + ret += "'"; + if (printable) + { + if (single_line && len > 30) + { + ret.append(str.data(), 14); + ret += "..."; + ret.append(str.data() + len - 14, 14); + } + else + ret.append(str.data(), std::size_t(len)); + ret += "'"; + return; + } + if (single_line && len > 20) + { + detail::escape_string(ret, str.data(), 9); + ret += "..."; + detail::escape_string(ret, str.data() + len - 9, 9); + } + else + { + detail::escape_string(ret, str.data(), len); + } + ret += "'"; + } + +} + + std::string print_entry(bdecode_node const& e + , bool single_line, int indent) + { + char indent_str[200]; + using std::memset; + memset(indent_str, ' ', 200); + indent_str[0] = ','; + indent_str[1] = '\n'; + indent_str[199] = 0; + if (indent < 197 && indent >= 0) indent_str[indent + 2] = 0; + std::string ret; + switch (e.type()) + { + case bdecode_node::none_t: return "none"; + case bdecode_node::int_t: + { + char str[100]; + std::snprintf(str, sizeof(str), "%" PRId64, e.int_value()); + return str; + } + case bdecode_node::string_t: + { + print_string(ret, e.string_value(), single_line); + return ret; + } + case bdecode_node::list_t: + { + ret += '['; + bool one_liner = line_longer_than(e, 200) != -1 || single_line; + + if (!one_liner) ret += indent_str + 1; + for (int i = 0; i < e.list_size(); ++i) + { + if (i == 0 && one_liner) ret += ' '; + ret += print_entry(e.list_at(i), single_line, indent + 2); + if (i < e.list_size() - 1) ret += (one_liner ? ", " : indent_str); + else ret += (one_liner ? " " : indent_str + 1); + } + ret += ']'; + return ret; + } + case bdecode_node::dict_t: + { + ret += '{'; + bool one_liner = line_longer_than(e, 200) != -1 || single_line; + + if (!one_liner) ret += indent_str + 1; + for (int i = 0; i < e.dict_size(); ++i) + { + if (i == 0 && one_liner) ret += ' '; + std::pair ent = e.dict_at(i); + print_string(ret, ent.first, true); + ret += ": "; + ret += print_entry(ent.second, single_line, indent + 2); + if (i < e.dict_size() - 1) ret += (one_liner ? ", " : indent_str); + else ret += (one_liner ? " " : indent_str + 1); + } + ret += '}'; + return ret; + } + } + return ret; + } +} diff --git a/src/bitfield.cpp b/src/bitfield.cpp new file mode 100644 index 0000000..c73b5c6 --- /dev/null +++ b/src/bitfield.cpp @@ -0,0 +1,230 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/aux_/cpuid.hpp" + +#ifdef _MSC_VER +#include +#endif + +namespace libtorrent { + + bool bitfield::all_set() const noexcept + { + if(size() == 0) return false; + + int const words = size() / 32; + for (int i = 1; i < words + 1; ++i) + { + if (m_buf[i] != 0xffffffff) return false; + } + int const rest = size() & 31; + if (rest > 0) + { + std::uint32_t const mask = aux::host_to_network(0xffffffff << (32 - rest)); + if ((m_buf[words + 1] & mask) != mask) return false; + } + return true; + } + + int bitfield::count() const noexcept + { + int ret = 0; + int const words = num_words(); +#if TORRENT_HAS_SSE + if (aux::mmx_support) + { + for (int i = 1; i < words + 1; ++i) + { +#ifdef __GNUC__ + std::uint32_t cnt = 0; + __asm__("popcnt %1, %0" + : "=r"(cnt) + : "r"(m_buf[i])); + ret += cnt; +#else + ret += _mm_popcnt_u32(m_buf[i]); +#endif + } + + TORRENT_ASSERT(ret <= size()); + TORRENT_ASSERT(ret >= 0); + return ret; + } +#endif // TORRENT_HAS_SSE + +#if TORRENT_HAS_ARM_NEON && defined __arm__ + if (aux::arm_neon_support) + { + for (int i = 1; i < words + 1; ++i) + { + std::uint32_t cnt; + __asm__( + "vld1.u32 d0[0], [%1] \n" + "vcnt.u8 d0, d0 \n" + "vpaddl.u8 d0, d0 \n" + "vpaddl.u16 d0, d0 \n" + "vst1.u32 d0[0], [%0]" + :: "r"(&cnt), "r"(&m_buf[i]) + : "d0", "memory"); + ret += cnt; + } + + TORRENT_ASSERT(ret <= size()); + TORRENT_ASSERT(ret >= 0); + return ret; + } +#endif // TORRENT_HAS_ARM_NEON + + for (int i = 1; i < words + 1; ++i) + { + std::uint32_t const v = m_buf[i]; + // from: + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + static const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers + static const std::uint32_t B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF}; + + std::uint32_t c = v - ((v >> 1) & B[0]); + c = ((c >> S[1]) & B[1]) + (c & B[1]); + c = ((c >> S[2]) + c) & B[2]; + c = ((c >> S[3]) + c) & B[3]; + c = ((c >> S[4]) + c) & B[4]; + ret += c; + TORRENT_ASSERT(ret <= size()); + } + + TORRENT_ASSERT(ret <= size()); + TORRENT_ASSERT(ret >= 0); + return ret; + } + + void bitfield::resize(int const bits, bool const val) + { + if (bits == size()) return; + + int const s = size(); + int const b = size() & 31; + resize(bits); + if (s >= size()) return; + int const old_size_words = (s + 31) / 32; + int const new_size_words = num_words(); + if (val) + { + if (old_size_words && b) buf()[old_size_words - 1] |= aux::host_to_network(0xffffffff >> b); + if (old_size_words < new_size_words) + std::memset(buf() + old_size_words, 0xff + , static_cast(new_size_words - old_size_words) * 4); + clear_trailing_bits(); + } + else + { + if (old_size_words < new_size_words) + std::memset(buf() + old_size_words, 0x00 + , static_cast(new_size_words - old_size_words) * 4); + } + TORRENT_ASSERT(size() == bits); + } + + void bitfield::resize(int const bits) + { + if (bits == size()) return; + + TORRENT_ASSERT(bits >= 0); + if (bits == 0) + { + m_buf.reset(); + return; + } + int const new_size_words = (bits + 31) / 32; + int const cur_size_words = num_words(); + if (cur_size_words != new_size_words) + { + aux::unique_ptr b(new std::uint32_t[std::size_t(new_size_words + 1)]); +#ifdef BOOST_NO_EXCEPTIONS + if (b == nullptr) std::terminate(); +#endif + b[0] = aux::numeric_cast(bits); + if (m_buf) std::memcpy(&b[1], buf() + , aux::numeric_cast(std::min(new_size_words, cur_size_words) * 4)); + if (new_size_words > cur_size_words) + { + std::memset(&b[1 + cur_size_words], 0 + , aux::numeric_cast((new_size_words - cur_size_words) * 4)); + } + m_buf = std::move(b); + } + else + { + m_buf[0] = aux::numeric_cast(bits); + } + + clear_trailing_bits(); + TORRENT_ASSERT(size() == bits); + } + + int bitfield::find_first_set() const noexcept + { + int const num = num_words(); + if (num == 0) return -1; + int const count = aux::count_leading_zeros({&m_buf[1], num}); + return count != num * 32 ? count : -1; + } + + int bitfield::find_last_clear() const noexcept + { + int const num = num_words(); + if (num == 0) return - 1; + int const size = this->size(); + std::uint32_t const mask = 0xffffffff << (32 - (size & 31)); + std::uint32_t const last = m_buf[num] ^ aux::host_to_network(mask); + int const ext = aux::count_trailing_ones(~last) - (31 - (size % 32)); + return last != 0 + ? (num - 1) * 32 + ext + : size - (aux::count_trailing_ones({&m_buf[1], num - 1}) + ext); + } + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + static_assert(std::is_nothrow_move_assignable::value + , "should be nothrow move assignable"); + static_assert(std::is_nothrow_default_constructible::value + , "should be nothrow default constructible"); + + static_assert(std::is_nothrow_move_constructible>::value + , "should be nothrow move constructible"); + static_assert(std::is_nothrow_move_assignable>::value + , "should be nothrow move assignable"); + static_assert(std::is_nothrow_default_constructible>::value + , "should be nothrow default constructible"); +} diff --git a/src/block_cache.cpp b/src/block_cache.cpp new file mode 100644 index 0000000..4a0b4ec --- /dev/null +++ b/src/block_cache.cpp @@ -0,0 +1,1791 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/disk_io_thread.hpp" // disk_operation_failed +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/block_cache_reference.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +/* + + The disk cache mimics ARC (adaptive replacement cache). + See paper: http://dbs.uni-leipzig.de/file/ARC.pdf + See slides: http://www-vlsi.stanford.edu/smart_memories/protected/meetings/spring2004/arc-fast.pdf + + This cache has a few modifications to make it fit the bittorrent use + case better. It has a few more lists and it defers the eviction + of pieces. + + read_lru1 + This is a plain LRU for items that have been requested once. If a piece + in this list gets accessed again, by someone other than the first + accessor, the piece is promoted into LRU2. which holds pieces that are + more frequently used, and more important to keep around as this LRU list + takes churn. + + read_lru1_ghost + This is a list of pieces that were least recently evicted from read_lru1. + These pieces don't hold any actual blocks in the cache, they are just + here to extend the reach and probability for pieces to be promoted into + read_lru2. Any piece in this list that get one more access is promoted to + read_lru2. This is technically a cache-miss, since there's no cached + blocks here, but for the purposes of promoting the piece from + infrequently used to frequently used), it's considered a cache-hit. + + read_lru2 + TODO + + read_lru2_ghost + TODO + + volatile_read_lru + TODO + + write_lru + TODO + + Cache hits + .......... + + When a piece get a cache hit, it's promoted, either to the beginning of the + lru2 or into lru2. Since this ARC implementation operates on pieces instead + of blocks, any one peer requesting blocks from one piece would essentially + always produce a "cache hit" the second block it requests. In order to make + the promotions make more sense, and be more in the spirit of the ARC + algorithm, each access contains a token, unique to each peer. If any access + has a different token than the last one, it's considered a cache hit. This + is because at least two peers requested blocks from the same piece. + + Deferred evictions + .................. + + Since pieces and blocks can be pinned in the cache, and it's not always + practical, or possible, to evict a piece at the point where a new block is + allocated (because it's not known what the block will be used for), + evictions are not done at the time of allocating blocks. Instead, whenever + an operation requires to add a new piece to the cache, it also records the + cache event leading to it, in m_last_cache_op. This is one of cache_miss + (piece did not exist in cache), lru1_ghost_hit (the piece was found in + lru1_ghost and it was promoted) or lru2_ghost_hit (the piece was found in + lru2_ghost and it was promoted). This cache operation then guides the cache + eviction algorithm to know which list to evict from. The volatile list is + always the first one to be evicted however. + + Write jobs + .......... + + When the write cache is enabled, write jobs are not issued via the normal + job queue. They are just hung on its corresponding cached piece entry, and a + flush_hashed job is issued. This job will inspect the current state of the + cached piece and determine if any of the blocks should be flushed. It also + kicks the hasher, i.e. progresses the SHA1 context, which calculates the + SHA-1 hash of the piece. This job flushed blocks that have been hashed and + also form a contiguous block run of at least the write cache line size. + + Read jobs + ......... + + The data blocks pulled in from disk by read jobs, are hung on the + corresponding cache piece (cached_piece_entry) once the operation completes. + Read operations typically pulls in an entire read cache stripe, and not just + the one block that was requested. When adjacent blocks are requested to be + read in quick succession, there is a risk that each block would pull in more + blocks (read ahead) and potentially read the same blocks several times, if + the original requests were serviced by different disk thread. This is + because all the read operation may start before any of them has completed, + hanging the resulting blocks in the cache. i.e. they would all be cache + misses, even though all but the first should be cache hits in the first's + read ahead. + + In order to solve this problem, there is only a single outstanding read job + at any given time per piece. When there is an outstanding read job on a + piece, the *outstanding_read* member is set to 1. This indicates that the + job should be hung on the piece for later processing, instead of being + issued into the main job queue. There is a tailqueue on each piece entry + called read_jobs where these jobs are added. + + At the end of every read job, this job list is inspected, any job in it is + tried against the cache to see if it's a cache hit now. If it is, complete + it right away. If it isn't, put it back in the read_jobs list except for + one, which is issued into the regular job queue. +*/ + +#define DEBUG_CACHE 0 + +#if DEBUG_CACHE +#define DLOG(...) std::fprintf(__VA_ARGS__) +#else +#define DLOG(...) do {} while (false) +#endif + +namespace libtorrent { + +#if DEBUG_CACHE +void log_refcounts(cached_piece_entry const* pe) +{ + char out[4096]; + char* ptr = out; + char* end = ptr + sizeof(out); + ptr += std::snprintf(ptr, end - ptr, "piece: %d [ ", int(pe->piece)); + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + ptr += std::snprintf(ptr, end - ptr, "%d ", int(pe->blocks[i].refcount)); + } + strncpy(ptr, "]\n", end - ptr); + DLOG(stderr, out); +} +#endif + +std::array const job_action_name = +{{ + "read", + "write", + "hash", + "move_storage", + "release_files", + "delete_files", + "check_fastresume", + "rename_file", + "stop_torrent", + "flush_piece", + "flush_hashed", + "flush_storage", + "trim_cache", + "set_file_priority", + "clear_piece", +}}; + +// make sure the job names array covers all the job IDs +static_assert(int(job_action_name.size()) == static_cast(job_action_t::num_job_ids) + , "disk-job-action and action-name-array mismatch"); + +#if TORRENT_USE_ASSERTS || !defined TORRENT_DISABLE_LOGGING + + std::array const piece_log_t::job_names = + {{ + "flushing", + "flush_expired", + "try_flush_write_blocks", + "try_flush_write_blocks2", + "flush_range", + "clear_outstanding_jobs", + "set_outstanding_jobs", + }}; + + char const* job_name(job_action_t const job) + { + int const j = static_cast(job); + if (j < 0 || j >= piece_log_t::last_job) + return "unknown"; + + if (j < piece_log_t::flushing) + return job_action_name[static_cast(j)]; + return piece_log_t::job_names[static_cast(j - piece_log_t::flushing)]; + } + +#endif // TORRENT_DISABLE_LOGGING + +#if TORRENT_USE_ASSERTS + + void print_piece_log(aux::vector const& piece_log) + { + for (int i = 0; i < int(piece_log.size()); ++i) + { + if (piece_log[i].block == -1) + { + std::printf("%d: %s\n", i, job_name(piece_log[i].job)); + } + else + { + std::printf("%d: %s %d\n", i, job_name(piece_log[i].job), piece_log[i].block); + } + } + } + + void assert_print_piece(cached_piece_entry const* pe) + { + static const char* const cache_state[] = + { + "write", "volatile-read", "read-lru", "read-lru-ghost", "read-lfu", "read-lfu-ghost" + }; + + if (pe == nullptr) + { + assert_print("piece: nullptr\n"); + } + else + { + assert_print("piece: %d\nrefcount: %d\npiece_refcount: %d\n" + "num_blocks: %d\nhashing: %d\n\nhash: %p\nhash_offset: %d\n" + "cache_state: (%d) %s\noutstanding_flush: %d\npiece: %d\n" + "num_dirty: %d\nnum_blocks: %d\nblocks_in_piece: %d\n" + "hashing_done: %d\nmarked_for_deletion: %d\nneed_readback: %d\n" + "hash_passed: %d\nread_jobs: %d\njobs: %d\n" + "piece_log:\n" + , int(pe->piece), pe->refcount, pe->piece_refcount, int(pe->num_blocks) + , int(pe->hashing), static_cast(pe->hash.get()), pe->hash ? pe->hash->offset : -1 + , int(pe->cache_state) + , pe->cache_state < cached_piece_entry::num_lrus ? cache_state[pe->cache_state] : "" + , int(pe->outstanding_flush), int(pe->piece), int(pe->num_dirty) + , int(pe->num_blocks), int(pe->blocks_in_piece), int(pe->hashing_done) + , int(pe->marked_for_eviction), int(pe->need_readback), pe->hash_passes + , pe->read_jobs.size(), pe->jobs.size()); + bool first = true; + for (auto const& log : pe->piece_log) + { + assert_print("%s %s (%d)", (first ? "" : ",") + , job_name(log.job), log.block); + first = false; + } + } + assert_print("\n"); + } + + +#define TORRENT_PIECE_ASSERT(cond, piece) \ + do { if (!(cond)) { assert_print_piece(piece); assert_fail(#cond, __LINE__, __FILE__, __func__, nullptr); } } TORRENT_WHILE_0 + +#else +#define TORRENT_PIECE_ASSERT(cond, piece) do {} TORRENT_WHILE_0 +#endif + +cached_piece_entry::cached_piece_entry() + : num_dirty(0) + , num_blocks(0) + , blocks_in_piece(0) + , hashing(0) + , hashing_done(0) + , marked_for_deletion(false) + , need_readback(false) + , cache_state(none) + , piece_refcount(0) + , outstanding_flush(0) + , outstanding_read(0) + , marked_for_eviction(false) + , pinned(0) +{} + +cached_piece_entry::~cached_piece_entry() +{ + TORRENT_ASSERT(piece_refcount == 0); + TORRENT_ASSERT(jobs.empty()); + TORRENT_ASSERT(read_jobs.empty()); +#if TORRENT_USE_ASSERTS + if (blocks) + { + for (int i = 0; i < blocks_in_piece; ++i) + { + TORRENT_ASSERT(!blocks[i].pending); + TORRENT_ASSERT(blocks[i].refcount == 0); + TORRENT_ASSERT(blocks[i].hashing_count == 0); + TORRENT_ASSERT(blocks[i].flushing_count == 0); + } + } + in_use = false; +#endif +} + +block_cache::block_cache(io_service& ios + , std::function const& trigger_trim) + : disk_buffer_pool(ios, trigger_trim) + , m_last_cache_op(cache_miss) + , m_ghost_size(8) + , m_max_volatile_blocks(100) + , m_volatile_size(0) + , m_read_cache_size(0) + , m_write_cache_size(0) + , m_send_buffer_blocks(0) + , m_pinned_blocks(0) +{ +} + +block_cache::~block_cache() +{ + std::vector bufs; + for (auto const& pe : m_pieces) + { + if (!pe.blocks) continue; + + int const num_blocks = int(pe.blocks_in_piece); + for (int i = 0; i < num_blocks; ++i) + { + if (pe.blocks[i].buf == nullptr) continue; + bufs.push_back(pe.blocks[i].buf); + } + } + free_multiple_buffers(bufs); +} + +// returns: +// -1: not in cache +// -2: no memory +int block_cache::try_read(disk_io_job* j, buffer_allocator_interface& allocator + , bool expect_no_fail) +{ + INVARIANT_CHECK; + + cached_piece_entry* p = find_piece(j); + + int ret = 0; + + // if the piece cannot be found in the cache, + // it's a cache miss + TORRENT_ASSERT(!expect_no_fail || p != nullptr); + if (p == nullptr) return -1; + +#if TORRENT_USE_ASSERTS + p->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + cache_hit(p, j->d.io.offset / default_block_size, bool(j->flags & disk_interface::volatile_read)); + + ret = copy_from_piece(p, j, allocator, expect_no_fail); + if (ret < 0) return ret; + + ret = j->d.io.buffer_size; + return ret; +} + +void block_cache::bump_lru(cached_piece_entry* p) +{ + // move to the top of the LRU list + TORRENT_PIECE_ASSERT(p->cache_state == cached_piece_entry::write_lru, p); + linked_list* lru_list = &m_lru[p->cache_state]; + + // move to the back (MRU) of the list + lru_list->erase(p); + lru_list->push_back(p); + p->expire = aux::time_now(); +} + +// this is called for pieces that we're reading from, when they +// are in the cache (including the ghost lists) +void block_cache::cache_hit(cached_piece_entry* p, int block, bool volatile_read) +{ +// this can be pretty expensive +// INVARIANT_CHECK; + + TORRENT_ASSERT(p); + TORRENT_ASSERT(p->in_use); + + // move the piece into this queue. Whenever we have a cache + // hit, we move the piece into the lru2 queue (i.e. the most + // frequently used piece). + std::uint16_t target_queue = cached_piece_entry::read_lru2; + + if (p->blocks[block].cache_hit == 0) + { + // if it's not a duplicate hit and the piece isn't in + // any of the ghost lists, ignore it + if (p->cache_state == cached_piece_entry::read_lru1 + || p->cache_state == cached_piece_entry::read_lru2 + || p->cache_state == cached_piece_entry::write_lru + || p->cache_state == cached_piece_entry::volatile_read_lru) + return; + + if (p->cache_state == cached_piece_entry::read_lru1_ghost) + target_queue = cached_piece_entry::read_lru1; + } + + if (p->cache_state == cached_piece_entry::volatile_read_lru) + { + // a volatile read hit on a volatile piece doesn't do anything + if (volatile_read) return; + + // however, if this is a proper read on a volatile piece + // we need to promote it to lru1 + target_queue = cached_piece_entry::read_lru1; + } + + // if we have this piece anywhere in L1 or L2, it's a "hit" + // and it should be bumped to the highest priority in L2 + // i.e. "frequently used" + if (p->cache_state < cached_piece_entry::read_lru1 + || p->cache_state > cached_piece_entry::read_lru2_ghost) + return; + + // if we got a cache hit in a ghost list, that indicates the proper + // list is too small. Record which ghost list we got the hit in and + // it will be used to determine which end of the cache we'll evict + // from, next time we need to reclaim blocks + if (p->cache_state == cached_piece_entry::read_lru1_ghost) + { + m_last_cache_op = ghost_hit_lru1; + } + else if (p->cache_state == cached_piece_entry::read_lru2_ghost) + { + m_last_cache_op = ghost_hit_lru2; + } + + // move into L2 (frequently used) + m_lru[p->cache_state].erase(p); + m_lru[target_queue].push_back(p); + p->cache_state = target_queue; + p->expire = aux::time_now(); +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif +} + +// this is used to move pieces primarily from the write cache +// to the read cache. Technically it can move from read to write +// cache as well, it's unclear if that ever happens though +void block_cache::update_cache_state(cached_piece_entry* p) +{ + int state = p->cache_state; + std::uint16_t desired_state = p->cache_state; + if (p->num_dirty > 0 || p->hash) + desired_state = cached_piece_entry::write_lru; + else if (p->cache_state == cached_piece_entry::write_lru) + desired_state = cached_piece_entry::read_lru1; + + if (desired_state == state) return; + + TORRENT_PIECE_ASSERT(state < cached_piece_entry::num_lrus, p); + TORRENT_PIECE_ASSERT(desired_state < cached_piece_entry::num_lrus, p); + linked_list* src = &m_lru[state]; + linked_list* dst = &m_lru[desired_state]; + + src->erase(p); + dst->push_back(p); + p->expire = aux::time_now(); + p->cache_state = desired_state; +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif +} + +void block_cache::try_evict_one_volatile() +{ + INVARIANT_CHECK; + + DLOG(stderr, "[%p] try_evict_one_volatile\n", static_cast(this)); + + if (m_volatile_size < m_max_volatile_blocks) return; + + linked_list* piece_list = &m_lru[cached_piece_entry::volatile_read_lru]; + + for (list_iterator i = piece_list->iterate(); i.get();) + { + cached_piece_entry* pe = i.get(); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + i.next(); + + if (pe->ok_to_evict() && pe->num_blocks == 0) + { +#if TORRENT_USE_INVARIANT_CHECKS + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == nullptr, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + move_to_ghost(pe); + continue; + } + + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + + // someone else is using this piece + if (pe->refcount > 0) continue; + + // some blocks are pinned in this piece, skip it + if (pe->pinned > 0) continue; + + TORRENT_ALLOCA(to_delete, char*, pe->blocks_in_piece); + int num_to_delete = 0; + + // go through the blocks and evict the ones that are not dirty and not + // referenced + for (int j = 0; j < pe->blocks_in_piece; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + TORRENT_PIECE_ASSERT(b.dirty == false, pe); + TORRENT_PIECE_ASSERT(b.pending == false, pe); + + if (b.buf == nullptr || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = nullptr; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + TORRENT_PIECE_ASSERT(m_volatile_size > 0, pe); + --m_volatile_size; + } + + if (pe->ok_to_evict() && pe->num_blocks == 0) + { +#if TORRENT_USE_INVARIANT_CHECKS + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == nullptr, pe); +#endif + move_to_ghost(pe); + } + + if (num_to_delete == 0) return; + + DLOG(stderr, "[%p] removed %d blocks\n", static_cast(this) + , num_to_delete); + + free_multiple_buffers(to_delete.first(num_to_delete)); + return; + } +} + +cached_piece_entry* block_cache::allocate_piece(disk_io_job const* j, std::uint16_t const cache_state) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(cache_state < cached_piece_entry::num_lrus); + + // we're assuming we're not allocating a ghost piece + // a bit further down + TORRENT_ASSERT(cache_state != cached_piece_entry::read_lru1_ghost + && cache_state != cached_piece_entry::read_lru2_ghost); + + cached_piece_entry* p = find_piece(j); + if (p == nullptr) + { + int const piece_size = j->storage->files().piece_size(j->piece); + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + + cached_piece_entry pe; + pe.piece = j->piece; + pe.storage = j->storage; + pe.expire = aux::time_now(); + pe.blocks_in_piece = aux::numeric_cast(blocks_in_piece); + + pe.blocks.reset(new (std::nothrow) cached_block_entry[std::size_t(blocks_in_piece)]); + if (!pe.blocks) return nullptr; + p = const_cast(&*m_pieces.insert(std::move(pe)).first); + + j->storage->add_piece(p); + p->cache_state = cache_state; + + TORRENT_PIECE_ASSERT(p->cache_state < cached_piece_entry::num_lrus, p); + linked_list* lru_list = &m_lru[p->cache_state]; + lru_list->push_back(p); + + // this piece is part of the ARC cache (as opposed to + // the write cache). Allocating a new read piece indicates + // that we just got a cache miss. Record this to determine + // which end to evict blocks from next time we need to + // evict blocks + if (cache_state == cached_piece_entry::read_lru1) + m_last_cache_op = cache_miss; + +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif + } + else + { + TORRENT_PIECE_ASSERT(p->in_use, p); + + // we want to retain the piece now + p->marked_for_eviction = false; + + // only allow changing the cache state downwards. i.e. turn a ghost + // piece into a non-ghost, or a read piece into a write piece + if (p->cache_state > cache_state) + { + // this can happen for instance if a piece fails the hash check + // first it's in the write cache, then it completes and is moved + // into the read cache, but fails and is cleared (into the ghost list) + // then we want to add new dirty blocks to it and we need to move + // it back into the write cache + m_lru[p->cache_state].erase(p); + p->cache_state = cache_state; + m_lru[p->cache_state].push_back(p); + p->expire = aux::time_now(); +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif + } + } + + return p; +} + +cached_piece_entry* block_cache::add_dirty_block(disk_io_job* j, bool const add_hasher) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(boost::get(j->argument)); + TORRENT_ASSERT(m_write_cache_size + m_read_cache_size + 1 <= in_use()); + + cached_piece_entry* pe = allocate_piece(j, cached_piece_entry::write_lru); + TORRENT_ASSERT(pe); + if (pe == nullptr) return pe; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + int block = j->d.io.offset / default_block_size; + TORRENT_ASSERT((j->d.io.offset % default_block_size) == 0); + + // we should never add a new dirty block on a piece + // that has checked the hash. Before we add it, the + // piece need to be cleared (with async_clear_piece) + TORRENT_PIECE_ASSERT(pe->hashing_done == 0, pe); + + // this only evicts read blocks + + int evict = num_to_evict(1); + if (evict > 0) try_evict_blocks(evict, pe); + + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(!pe->marked_for_eviction, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount == 0, pe); + + cached_block_entry& b = pe->blocks[block]; + + TORRENT_PIECE_ASSERT(b.buf != boost::get(j->argument).get(), pe); + + // we might have a left-over read block from + // hash checking + // we might also have a previous dirty block which + // we're still waiting for to be written + if (b.buf != nullptr && b.buf != boost::get(j->argument).get()) + { + TORRENT_PIECE_ASSERT(b.refcount == 0 && !b.pending, pe); + free_block(pe, block); + TORRENT_PIECE_ASSERT(b.dirty == 0, pe); + } + + b.buf = boost::get(j->argument).release(); + + b.dirty = true; + ++pe->num_blocks; + ++pe->num_dirty; + ++m_write_cache_size; + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(j->flags & disk_io_job::in_progress, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->jobs.push_back(j); + + if (block == 0 && !pe->hash && pe->hashing_done == false && add_hasher) + pe->hash.reset(new partial_hash); + + update_cache_state(pe); + + bump_lru(pe); + + return pe; +} + +// flushed is an array of num_flushed integers. Each integer is the block index +// that was flushed. This function marks those blocks as not pending and not +// dirty. It also adjusts its understanding of the read vs. write cache size +// (since these blocks now are part of the read cache) the refcounts of the +// blocks are also decremented by this function. They are expected to have been +// incremented by the caller. +bool block_cache::blocks_flushed(cached_piece_entry* pe, int const* flushed, int const num_flushed) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + for (int i = 0; i < num_flushed; ++i) + { + int block = flushed[i]; + TORRENT_PIECE_ASSERT(block >= 0, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].dirty, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].pending, pe); + pe->blocks[block].pending = false; + // it's important to mark it as non-dirty before decrementing the + // refcount because the buffer may be marked as discardable/volatile it + // this is the last reference to it + pe->blocks[block].dirty = false; + dec_block_refcount(pe, block, block_cache::ref_flushing); + } + + m_write_cache_size -= num_flushed; + m_read_cache_size += num_flushed; + pe->num_dirty -= num_flushed; + + update_cache_state(pe); + return maybe_free_piece(pe); +} + +std::pair block_cache::all_pieces() const +{ + return std::make_pair(m_pieces.begin(), m_pieces.end()); +} + +void block_cache::free_block(cached_piece_entry* pe, int block) +{ + TORRENT_ASSERT(pe != nullptr); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + + cached_block_entry& b = pe->blocks[block]; + + TORRENT_PIECE_ASSERT(b.refcount == 0, pe); + TORRENT_PIECE_ASSERT(!b.pending, pe); + TORRENT_PIECE_ASSERT(b.buf, pe); + + if (b.dirty) + { + --pe->num_dirty; + b.dirty = false; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + } + else + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + --m_volatile_size; + } + } + + + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + free_buffer(b.buf); + b.buf = nullptr; +} + +bool block_cache::evict_piece(cached_piece_entry* pe, tailqueue& jobs + , eviction_mode const mode) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_ALLOCA(to_delete, char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (pe->blocks[i].buf == nullptr || pe->blocks[i].refcount > 0) continue; + TORRENT_PIECE_ASSERT(!pe->blocks[i].pending, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].buf != nullptr, pe); + TORRENT_PIECE_ASSERT(num_to_delete < pe->blocks_in_piece, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = nullptr; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + if (!pe->blocks[i].dirty) + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + } + else + { + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + pe->blocks[i].dirty = false; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + } + if (pe->num_blocks == 0) break; + } + + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= num_to_delete; + } + + if (num_to_delete) free_multiple_buffers(to_delete.first(num_to_delete)); + + if (pe->ok_to_evict(true) && pe->num_blocks == 0) + { + pe->hash.reset(); + + // append will move the items from pe->jobs onto the end of jobs + jobs.append(pe->jobs); + TORRENT_ASSERT(pe->jobs.empty()); + + if (mode == allow_ghost + && (pe->cache_state == cached_piece_entry::read_lru1_ghost + || pe->cache_state == cached_piece_entry::read_lru2_ghost)) + return true; + + if (mode == disallow_ghost + || pe->cache_state == cached_piece_entry::write_lru + || pe->cache_state == cached_piece_entry::volatile_read_lru) + erase_piece(pe); + else + move_to_ghost(pe); + return true; + } + + return false; +} + +void block_cache::mark_for_eviction(cached_piece_entry* p + , eviction_mode const mode) +{ + INVARIANT_CHECK; + + DLOG(stderr, "[%p] block_cache mark-for-deletion " + "piece: %d\n", static_cast(this), int(p->piece)); + + TORRENT_PIECE_ASSERT(p->jobs.empty(), p); + tailqueue jobs; + if (!evict_piece(p, jobs, mode)) + { + p->marked_for_eviction = true; + p->marked_for_deletion = mode == disallow_ghost; + } +} + +void block_cache::erase_piece(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->ok_to_evict(), pe); + TORRENT_PIECE_ASSERT(pe->cache_state < cached_piece_entry::num_lrus, pe); + TORRENT_PIECE_ASSERT(pe->jobs.empty(), pe); + linked_list* lru_list = &m_lru[pe->cache_state]; + if (pe->hash) + { + TORRENT_PIECE_ASSERT(pe->hash->offset == 0, pe); + pe->hash.reset(); + } + pe->storage->remove_piece(pe); + lru_list->erase(pe); + m_pieces.erase(*pe); +} + +// this only evicts read blocks. For write blocks, see +// try_flush_write_blocks in disk_io_thread.cpp +int block_cache::try_evict_blocks(int num, cached_piece_entry* ignore) +{ + INVARIANT_CHECK; + + if (num <= 0) return 0; + + DLOG(stderr, "[%p] try_evict_blocks: %d\n", static_cast(this), num); + + TORRENT_ALLOCA(to_delete, char*, num); + int num_to_delete = 0; + + // There are two ends of the ARC cache we can evict from. There's L1 and L2. + // The last cache operation determines which end we'll evict from. If we go + // through the entire list from the preferred end, and still need to evict + // more blocks, we'll go to the other end and start evicting from there. The + // lru_list is an array of two lists, these are the two ends to evict from, + // ordered by preference. + + linked_list* lru_list[3]; + + // however, before we consider any of the proper LRU lists, we evict pieces + // from the volatile list. These are low priority pieces that were + // specifically marked as to not survive long in the cache. These are the + // first pieces to go when evicting + lru_list[0] = &m_lru[cached_piece_entry::volatile_read_lru]; + + if (m_last_cache_op == cache_miss) + { + // when there was a cache miss, evict from the largest list, to tend to + // keep the lists of equal size when we don't know which one is + // performing better + if (m_lru[cached_piece_entry::read_lru2].size() + > m_lru[cached_piece_entry::read_lru1].size()) + { + lru_list[1] = &m_lru[cached_piece_entry::read_lru2]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru1]; + } + else + { + lru_list[1] = &m_lru[cached_piece_entry::read_lru1]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru2]; + } + } + else if (m_last_cache_op == ghost_hit_lru1) + { + // when we insert new items or move things from L1 to L2 + // evict blocks from L2 + lru_list[1] = &m_lru[cached_piece_entry::read_lru2]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru1]; + } + else + { + // when we get cache hits in L2 evict from L1 + lru_list[1] = &m_lru[cached_piece_entry::read_lru1]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru2]; + } + + // end refers to which end of the ARC cache we're evicting + // from. The LFU or the LRU end + for (int end = 0; num > 0 && end < 3; ++end) + { + // iterate over all blocks in order of last being used (oldest first) and + // as long as we still have blocks to evict TODO: it's somewhat expensive + // to iterate over this linked list. Presumably because of the random + // access of memory. It would be nice if pieces with no evictable blocks + // weren't in this list + for (auto i = lru_list[end]->iterate(); i.get() && num > 0;) + { + cached_piece_entry* pe = i.get(); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + i.next(); + + if (pe == ignore) + continue; + + if (pe->ok_to_evict() && pe->num_blocks == 0) + { +#if TORRENT_USE_INVARIANT_CHECKS + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == nullptr, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + move_to_ghost(pe); + continue; + } + + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + + // all blocks are pinned in this piece, skip it + if (pe->num_blocks <= pe->pinned) continue; + + // go through the blocks and evict the ones that are not dirty and not + // referenced + int removed = 0; + for (int j = 0; j < pe->blocks_in_piece && num > 0; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + if (b.buf == nullptr || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = nullptr; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + ++removed; + --num; + } + + TORRENT_PIECE_ASSERT(m_read_cache_size >= removed, pe); + m_read_cache_size -= removed; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= removed; + } + + if (pe->ok_to_evict() && pe->num_blocks == 0) + { +#if TORRENT_USE_INVARIANT_CHECKS + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == nullptr, pe); +#endif + move_to_ghost(pe); + } + } + } + + // if we can't evict enough blocks from the read cache, also look at write + // cache pieces for blocks that have already been written to disk and can be + // evicted the first pass, we only evict blocks that have been hashed, the + // second pass we flush anything this is potentially a very expensive + // operation, since we're likely to have iterate every single block in the + // cache, and we might not get to evict anything. + + // TODO: this should probably only be done every n:th time + if (num > 0 && m_read_cache_size > m_pinned_blocks) + { + for (int pass = 0; pass < 2 && num > 0; ++pass) + { + for (auto i = m_lru[cached_piece_entry::write_lru].iterate(); i.get() && num > 0;) + { + cached_piece_entry* pe = i.get(); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + i.next(); + + if (pe == ignore) + continue; + + if (pe->ok_to_evict() && pe->num_blocks == 0) + { +#if TORRENT_USE_INVARIANT_CHECKS + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == nullptr, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + erase_piece(pe); + continue; + } + + // all blocks in this piece are dirty + if (pe->num_dirty == pe->num_blocks) + continue; + + int end = pe->blocks_in_piece; + + // the first pass, only evict blocks that have been + // hashed + if (pass == 0 && pe->hash) + end = pe->hash->offset / default_block_size; + + // go through the blocks and evict the ones + // that are not dirty and not referenced + int removed = 0; + for (int j = 0; j < end && num > 0; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + if (b.buf == nullptr || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = nullptr; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + ++removed; + --num; + } + + TORRENT_PIECE_ASSERT(m_read_cache_size >= removed, pe); + m_read_cache_size -= removed; + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= removed; + } + + if (pe->ok_to_evict() && pe->num_blocks == 0) + { +#if TORRENT_USE_INVARIANT_CHECKS + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == nullptr, pe); +#endif + erase_piece(pe); + } + } + } + } + + if (num_to_delete == 0) return num; + + DLOG(stderr, "[%p] removed %d blocks\n", static_cast(this) + , num_to_delete); + + free_multiple_buffers(to_delete.first(num_to_delete)); + + return num; +} + +void block_cache::clear(tailqueue& jobs) +{ + INVARIANT_CHECK; + + // this holds all the block buffers we want to free + // at the end + std::vector bufs; + + for (auto const& p : m_pieces) + { + auto& pe = const_cast(p); +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator i = pe.jobs.iterate(); i.get(); i.next()) + TORRENT_PIECE_ASSERT((static_cast(i.get()))->piece == pe.piece, &pe); + for (tailqueue_iterator i = pe.read_jobs.iterate(); i.get(); i.next()) + TORRENT_PIECE_ASSERT((static_cast(i.get()))->piece == pe.piece, &pe); +#endif + // this also removes the jobs from the piece + jobs.append(pe.jobs); + jobs.append(pe.read_jobs); + + drain_piece_bufs(pe, bufs); + } + + if (!bufs.empty()) free_multiple_buffers(bufs); + + // clear lru lists + for (auto& l : m_lru) l.get_all(); + + // it's not ok to erase pieces with a refcount > 0 + // since we're cancelling all jobs though, it shouldn't be too bad + // to let the jobs already running complete. + for (auto i = m_pieces.begin(); i != m_pieces.end();) + { + if (i->refcount == 0 && i->piece_refcount == 0) + { + i = m_pieces.erase(i); + } + else + { + ++i; + } + } +} + +void block_cache::move_to_ghost(cached_piece_entry* pe) +{ + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->piece_refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->num_blocks == 0, pe); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + erase_piece(pe); + return; + } + + TORRENT_PIECE_ASSERT(pe->cache_state == cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // if the piece is in L1 or L2, move it into the ghost list + // i.e. recently evicted + if (pe->cache_state != cached_piece_entry::read_lru1 + && pe->cache_state != cached_piece_entry::read_lru2) + return; + + // if the ghost list is growing too big, remove the oldest entry + linked_list* ghost_list = &m_lru[pe->cache_state + 1]; + while (ghost_list->size() >= m_ghost_size) + { + cached_piece_entry* p = ghost_list->front(); + TORRENT_PIECE_ASSERT(p != pe, p); + TORRENT_PIECE_ASSERT(p->num_blocks == 0, p); + TORRENT_PIECE_ASSERT(p->refcount == 0, p); + TORRENT_PIECE_ASSERT(p->piece_refcount == 0, p); + erase_piece(p); + } + + m_lru[pe->cache_state].erase(pe); + pe->cache_state += 1; + ghost_list->push_back(pe); +} + +int block_cache::pad_job(disk_io_job const* j, int const blocks_in_piece + , int const read_ahead) const +{ + int block_offset = j->d.io.offset & (default_block_size - 1); + int start = j->d.io.offset / default_block_size; + int end = block_offset > 0 && (read_ahead > default_block_size - block_offset) ? start + 2 : start + 1; + + // take the read-ahead into account + // make sure to not overflow in this case + if (read_ahead == INT_MAX) end = blocks_in_piece; + else end = std::min(blocks_in_piece, std::max(start + read_ahead, end)); + + return end - start; +} + +void block_cache::insert_blocks(cached_piece_entry* pe, int block, span iov + , disk_io_job* j, int const flags) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(pe); + TORRENT_ASSERT(pe->in_use); + TORRENT_PIECE_ASSERT(!iov.empty(), pe); + + cache_hit(pe, j->d.io.offset / default_block_size, bool(j->flags & disk_interface::volatile_read)); + + TORRENT_ASSERT(pe->in_use); + + for (auto const& buf : iov) + { + // each iovec buffer has to be the size of a block (or the size of the last block) + TORRENT_PIECE_ASSERT(int(buf.size()) == std::min(default_block_size + , pe->storage->files().piece_size(pe->piece) - block * default_block_size), pe); + + // no nullptrs allowed + TORRENT_ASSERT(buf.data() != nullptr); + +#ifdef TORRENT_DEBUG_BUFFERS + TORRENT_PIECE_ASSERT(is_disk_buffer(buf.data()), pe); +#endif + + if (pe->blocks[block].buf && (flags & blocks_inc_refcount)) + { + inc_block_refcount(pe, block, ref_reading); + } + + // either free the block or insert it. Never replace a block + if (pe->blocks[block].buf) + { + free_buffer(buf.data()); + } + else + { + pe->blocks[block].buf = buf.data(); + + TORRENT_PIECE_ASSERT(buf.data() != nullptr, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].dirty == false, pe); + ++pe->num_blocks; + ++m_read_cache_size; + if (j->flags & disk_interface::volatile_read) ++m_volatile_size; + + if (flags & blocks_inc_refcount) + { + bool ret = inc_block_refcount(pe, block, ref_reading); + TORRENT_UNUSED(ret); // suppress warning + TORRENT_ASSERT(ret); + } + } + + TORRENT_ASSERT(pe->blocks[block].buf != nullptr); + + block++; + } + + TORRENT_PIECE_ASSERT(pe->cache_state != cached_piece_entry::read_lru1_ghost, pe); + TORRENT_PIECE_ASSERT(pe->cache_state != cached_piece_entry::read_lru2_ghost, pe); +} + +// return false if the memory was purged +bool block_cache::inc_block_refcount(cached_piece_entry* pe, int const block, int const reason) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + if (pe->blocks[block].buf == nullptr) return false; + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount < cached_block_entry::max_refcount, pe); + if (pe->blocks[block].refcount == 0) + { + ++pe->pinned; + ++m_pinned_blocks; + } + ++pe->blocks[block].refcount; + ++pe->refcount; +#if TORRENT_USE_ASSERTS + switch (reason) + { + case ref_hashing: ++pe->blocks[block].hashing_count; break; + case ref_reading: ++pe->blocks[block].reading_count; break; + case ref_flushing: ++pe->blocks[block].flushing_count; break; + } + TORRENT_ASSERT(int(pe->blocks[block].refcount) >= pe->blocks[block].hashing_count + + pe->blocks[block].reading_count + pe->blocks[block].flushing_count); +#else + TORRENT_UNUSED(reason); +#endif + return true; +} + +void block_cache::dec_block_refcount(cached_piece_entry* pe, int const block, int const reason) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].buf != nullptr, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount > 0, pe); + --pe->blocks[block].refcount; + TORRENT_PIECE_ASSERT(pe->refcount > 0, pe); + --pe->refcount; + if (pe->blocks[block].refcount == 0) + { + TORRENT_PIECE_ASSERT(pe->pinned > 0, pe); + --pe->pinned; + TORRENT_PIECE_ASSERT(m_pinned_blocks > 0, pe); + --m_pinned_blocks; + } +#if TORRENT_USE_ASSERTS + switch (reason) + { + case ref_hashing: --pe->blocks[block].hashing_count; break; + case ref_reading: --pe->blocks[block].reading_count; break; + case ref_flushing: --pe->blocks[block].flushing_count; break; + } + TORRENT_PIECE_ASSERT(int(pe->blocks[block].refcount) >= pe->blocks[block].hashing_count + + pe->blocks[block].reading_count + pe->blocks[block].flushing_count, pe); +#else + TORRENT_UNUSED(reason); +#endif +} + +void block_cache::abort_dirty(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_ALLOCA(to_delete, char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (!pe->blocks[i].dirty + || pe->blocks[i].refcount > 0 + || pe->blocks[i].buf == nullptr) continue; + + TORRENT_PIECE_ASSERT(!pe->blocks[i].pending, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].dirty, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = nullptr; + pe->blocks[i].dirty = false; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + } + if (num_to_delete) free_multiple_buffers(to_delete.first(num_to_delete)); + + update_cache_state(pe); +} + +int block_cache::drain_piece_bufs(cached_piece_entry& p, std::vector& buf) +{ + int const piece_size = p.storage->files().piece_size(p.piece); + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + int ret = 0; + + TORRENT_PIECE_ASSERT(p.in_use, &p); + + int removed_clean = 0; + for (int i = 0; i < blocks_in_piece; ++i) + { + if (p.blocks[i].buf == nullptr) continue; + TORRENT_PIECE_ASSERT(p.blocks[i].refcount == 0, &p); + buf.push_back(p.blocks[i].buf); + ++ret; + p.blocks[i].buf = nullptr; + TORRENT_PIECE_ASSERT(p.num_blocks > 0, &p); + --p.num_blocks; + + if (p.blocks[i].dirty) + { + TORRENT_ASSERT(m_write_cache_size > 0); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(p.num_dirty > 0, &p); + --p.num_dirty; + } + else + { + ++removed_clean; + } + } + + TORRENT_ASSERT(m_read_cache_size >= removed_clean); + m_read_cache_size -= removed_clean; + if (p.cache_state == cached_piece_entry::volatile_read_lru) + { + m_volatile_size -= removed_clean; + } + + update_cache_state(&p); + return ret; +} + +void block_cache::update_stats_counters(counters& c) const +{ + c.set_value(counters::write_cache_blocks, m_write_cache_size); + c.set_value(counters::read_cache_blocks, m_read_cache_size); + c.set_value(counters::pinned_blocks, m_pinned_blocks); + + c.set_value(counters::arc_mru_size, m_lru[cached_piece_entry::read_lru1].size()); + c.set_value(counters::arc_mru_ghost_size, m_lru[cached_piece_entry::read_lru1_ghost].size()); + c.set_value(counters::arc_mfu_size, m_lru[cached_piece_entry::read_lru2].size()); + c.set_value(counters::arc_mfu_ghost_size, m_lru[cached_piece_entry::read_lru2_ghost].size()); + c.set_value(counters::arc_write_size, m_lru[cached_piece_entry::write_lru].size()); + c.set_value(counters::arc_volatile_size, m_lru[cached_piece_entry::volatile_read_lru].size()); +} + +#if TORRENT_ABI_VERSION == 1 +void block_cache::get_stats(cache_status* ret) const +{ + ret->write_cache_size = m_write_cache_size; + ret->read_cache_size = m_read_cache_size; + ret->pinned_blocks = m_pinned_blocks; + ret->cache_size = m_read_cache_size + m_write_cache_size; + + ret->arc_mru_size = m_lru[cached_piece_entry::read_lru1].size(); + ret->arc_mru_ghost_size = m_lru[cached_piece_entry::read_lru1_ghost].size(); + ret->arc_mfu_size = m_lru[cached_piece_entry::read_lru2].size(); + ret->arc_mfu_ghost_size = m_lru[cached_piece_entry::read_lru2_ghost].size(); + ret->arc_write_size = m_lru[cached_piece_entry::write_lru].size(); + ret->arc_volatile_size = m_lru[cached_piece_entry::volatile_read_lru].size(); +} +#endif + +void block_cache::set_settings(aux::session_settings const& sett) +{ + // the ghost size is the number of pieces to keep track of + // after they are evicted. Since cache_size is blocks, the + // assumption is that there are about 128 blocks per piece, + // and there are two ghost lists, so divide by 2. + + m_ghost_size = std::max(8, sett.get_int(settings_pack::cache_size) + / std::max(sett.get_int(settings_pack::read_cache_line_size), 4) / 2); + + m_max_volatile_blocks = sett.get_int(settings_pack::cache_size_volatile); + disk_buffer_pool::set_settings(sett); +} + +#if TORRENT_USE_INVARIANT_CHECKS +void block_cache::check_invariant() const +{ + int cached_write_blocks = 0; + int cached_read_blocks = 0; + int num_pinned = 0; + + std::set storages; + + for (int i = 0; i < cached_piece_entry::num_lrus; ++i) + { + time_point timeout = min_time(); + + for (list_iterator p = m_lru[i].iterate(); p.get(); p.next()) + { + cached_piece_entry* pe = p.get(); + TORRENT_PIECE_ASSERT(pe->cache_state == i, pe); + if (pe->num_dirty > 0) + TORRENT_PIECE_ASSERT(i == cached_piece_entry::write_lru, pe); + +// if (i == cached_piece_entry::write_lru) +// TORRENT_ASSERT(pe->num_dirty > 0); + for (tailqueue_iterator j = pe->jobs.iterate(); j.get(); j.next()) + { + disk_io_job const* job = static_cast(j.get()); + TORRENT_PIECE_ASSERT(job->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(job->in_use, pe); + TORRENT_PIECE_ASSERT(!job->callback_called, pe); + } + + if (i != cached_piece_entry::read_lru1_ghost + && i != cached_piece_entry::read_lru2_ghost) + { + TORRENT_PIECE_ASSERT(pe->expire >= timeout, pe); + timeout = pe->expire; + TORRENT_PIECE_ASSERT(pe->in_storage, pe); + } + else + { + // pieces in the ghost lists should never have any blocks + TORRENT_PIECE_ASSERT(pe->num_blocks == 0, pe); + } + // pieces in the ghost list are still in the storage's list of pieces, + // because we need to be able to evict them when stopping a torrent + + storages.insert(pe->storage.get()); + } + } + + for (auto s : storages) + { + for (auto const& pe : s->cached_pieces()) + { + TORRENT_PIECE_ASSERT(pe.storage.get() == s, &pe); + } + } + + std::unordered_set buffers; + for (auto const& p : m_pieces) + { + TORRENT_PIECE_ASSERT(p.blocks, &p); + + TORRENT_PIECE_ASSERT(p.storage, &p); + int num_blocks = 0; + int num_dirty = 0; + int num_pending = 0; + int num_refcount = 0; + + for (int k = 0; k < p.blocks_in_piece; ++k) + { + if (p.blocks[k].buf) + { + ++num_blocks; + if (p.blocks[k].dirty) + { + ++num_dirty; + ++cached_write_blocks; + } + else + { + ++cached_read_blocks; + } + if (p.blocks[k].pending) ++num_pending; + if (p.blocks[k].refcount > 0) ++num_pinned; + + TORRENT_PIECE_ASSERT(int(p.blocks[k].refcount) >= + p.blocks[k].hashing_count + + p.blocks[k].reading_count + + p.blocks[k].flushing_count, &p); + + } + else + { + TORRENT_PIECE_ASSERT(!p.blocks[k].dirty, &p); + TORRENT_PIECE_ASSERT(!p.blocks[k].pending, &p); + TORRENT_PIECE_ASSERT(p.blocks[k].refcount == 0, &p); + } + num_refcount += p.blocks[k].refcount; + } + TORRENT_PIECE_ASSERT(num_blocks == p.num_blocks, &p); + TORRENT_PIECE_ASSERT(num_pending <= p.refcount, &p); + TORRENT_PIECE_ASSERT(num_refcount == p.refcount, &p); + TORRENT_PIECE_ASSERT(num_dirty == p.num_dirty, &p); + } + TORRENT_ASSERT(m_read_cache_size == cached_read_blocks); + TORRENT_ASSERT(m_write_cache_size == cached_write_blocks); + TORRENT_ASSERT(m_pinned_blocks == num_pinned); + TORRENT_ASSERT(m_write_cache_size + m_read_cache_size <= in_use()); +} +#endif + +// TODO: 2 turn these return values into enums +// returns +// -1: block not in cache +// -2: out of memory + +int block_cache::copy_from_piece(cached_piece_entry* const pe + , disk_io_job* const j, buffer_allocator_interface& allocator + , bool const expect_no_fail) +{ + INVARIANT_CHECK; + TORRENT_UNUSED(expect_no_fail); + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + // copy from the cache and update the last use timestamp + int block = j->d.io.offset / default_block_size; + int block_offset = j->d.io.offset & (default_block_size - 1); + int buffer_offset = 0; + int size = j->d.io.buffer_size; + int const blocks_to_read = block_offset > 0 && (size > default_block_size - block_offset) ? 2 : 1; + TORRENT_PIECE_ASSERT(size <= default_block_size, pe); + int const start_block = block; + +#if TORRENT_USE_ASSERTS + int const piece_size = j->storage->files().piece_size(j->piece); + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + TORRENT_PIECE_ASSERT(start_block < blocks_in_piece, pe); +#endif + + // if there's no buffer, we don't have this block in + // the cache, and we're not currently reading it in either + // since it's not pending + + if (inc_block_refcount(pe, start_block, ref_reading) == false) + { + TORRENT_ASSERT(!expect_no_fail); + return -1; + } + + // if block_offset > 0, we need to read two blocks, and then + // copy parts of both, because it's not aligned to the block + // boundaries + if (blocks_to_read == 1 && !(j->flags & disk_interface::force_copy)) + { + // special case for block aligned request + // don't actually copy the buffer, just reference + // the existing block. Which means we don't want to decrement the + // refcount, we're handing the ownership of the reference to the calling + // thread. + cached_block_entry& bl = pe->blocks[start_block]; + bl.cache_hit = 1; + + // make sure it didn't wrap + TORRENT_PIECE_ASSERT(pe->refcount > 0, pe); + int const blocks_per_piece = (j->storage->files().piece_length() + default_block_size - 1) / default_block_size; + TORRENT_ASSERT(block_offset < 0x4000); + j->argument = disk_buffer_holder(allocator + , aux::block_cache_reference{ j->storage->storage_index() + , static_cast(pe->piece) * blocks_per_piece + start_block} + , bl.buf + block_offset, static_cast(0x4000 - block_offset)); + j->storage->inc_refcount(); + + ++m_send_buffer_blocks; + return j->d.io.buffer_size; + } + + // if we don't have the second block, it's a cache miss + if (blocks_to_read == 2 && inc_block_refcount(pe, start_block + 1, ref_reading) == false) + { + TORRENT_ASSERT(!expect_no_fail); + dec_block_refcount(pe, start_block, ref_reading); + maybe_free_piece(pe); + return -1; + } + + j->argument = disk_buffer_holder(allocator + , allocate_buffer("send buffer"), 0x4000); + if (!boost::get(j->argument)) return -2; + + while (size > 0) + { + TORRENT_PIECE_ASSERT(pe->blocks[block].buf, pe); + int const to_copy = std::min(default_block_size - block_offset, size); + std::memcpy(boost::get(j->argument).get() + + buffer_offset + , pe->blocks[block].buf + block_offset + , aux::numeric_cast(to_copy)); + pe->blocks[block].cache_hit = 1; + size -= to_copy; + block_offset = 0; + buffer_offset += to_copy; + ++block; + } + // we incremented the refcount for both of these blocks. + // now decrement it. + // TODO: create a holder for refcounts that automatically decrement + dec_block_refcount(pe, start_block, ref_reading); + if (blocks_to_read == 2) dec_block_refcount(pe, start_block + 1, ref_reading); + maybe_free_piece(pe); + return j->d.io.buffer_size; +} + +void block_cache::reclaim_block(storage_interface* st, aux::block_cache_reference const& ref) +{ + TORRENT_ASSERT(st != nullptr); + int const blocks_per_piece = (st->files().piece_length() + default_block_size - 1) / default_block_size; + piece_index_t const piece(ref.cookie / blocks_per_piece); + int const block(ref.cookie % blocks_per_piece); + + cached_piece_entry* pe = find_piece(st, piece); + TORRENT_ASSERT(pe); + if (pe == nullptr) return; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].buf, pe); + dec_block_refcount(pe, block, block_cache::ref_reading); + + TORRENT_PIECE_ASSERT(m_send_buffer_blocks > 0, pe); + --m_send_buffer_blocks; + + maybe_free_piece(pe); +} + +bool block_cache::maybe_free_piece(cached_piece_entry* pe) +{ + if (!pe->ok_to_evict() + || !pe->marked_for_eviction + || !pe->jobs.empty()) + return false; + + DLOG(stderr, "[%p] block_cache maybe_free_piece " + "piece: %d refcount: %d marked_for_eviction: %d\n" + , static_cast(this) + , int(pe->piece), int(pe->refcount), int(pe->marked_for_eviction)); + + tailqueue jobs; + bool removed = evict_piece(pe, jobs + , pe->marked_for_deletion ? disallow_ghost : allow_ghost); + TORRENT_UNUSED(removed); // suppress warning + TORRENT_PIECE_ASSERT(removed, pe); + TORRENT_PIECE_ASSERT(jobs.empty(), pe); + + return true; +} + +cached_piece_entry* block_cache::find_piece(disk_io_job const* j) +{ + return find_piece(j->storage.get(), j->piece); +} + +cached_piece_entry* block_cache::find_piece(storage_interface* st, piece_index_t const piece) +{ + cached_piece_entry model; + model.storage = st->shared_from_this(); + model.piece = piece; + auto const i = m_pieces.find(model); + TORRENT_ASSERT(i == m_pieces.end() || (i->storage.get() == st && i->piece == piece)); + if (i == m_pieces.end()) return nullptr; + TORRENT_PIECE_ASSERT(i->in_use, &*i); + +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator j = i->jobs.iterate(); j.get(); j.next()) + { + disk_io_job const* job = static_cast(j.get()); + TORRENT_PIECE_ASSERT(job->piece == piece, &*i); + } +#endif + + return const_cast(&*i); +} + +} diff --git a/src/bloom_filter.cpp b/src/bloom_filter.cpp new file mode 100644 index 0000000..021a2a8 --- /dev/null +++ b/src/bloom_filter.cpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { + + bool has_bits(std::uint8_t const* k, std::uint8_t const* bits, int const len) + { + std::uint32_t idx1 = std::uint32_t(k[0]) | (std::uint32_t(k[1]) << 8); + std::uint32_t idx2 = std::uint32_t(k[2]) | (std::uint32_t(k[3]) << 8); + idx1 %= aux::numeric_cast(len * 8); + idx2 %= aux::numeric_cast(len * 8); + return (bits[idx1 / 8] & (1 << (idx1 & 7))) != 0 + && (bits[idx2 / 8] & (1 << (idx2 & 7))) != 0; + } + + void set_bits(std::uint8_t const* k, std::uint8_t* bits, int const len) + { + std::uint32_t idx1 = std::uint32_t(k[0]) | (std::uint32_t(k[1]) << 8); + std::uint32_t idx2 = std::uint32_t(k[2]) | (std::uint32_t(k[3]) << 8); + idx1 %= aux::numeric_cast(len * 8); + idx2 %= aux::numeric_cast(len * 8); + bits[idx1 / 8] |= (1 << (idx1 & 7)); + bits[idx2 / 8] |= (1 << (idx2 & 7)); + } + + int count_zero_bits(std::uint8_t const* bits, int const len) + { + // number of bits _not_ set in a nibble + std::uint8_t bitcount[16] = + { + // 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, + // 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111 + 4, 3, 3, 2, 3, 2, 2, 1, + 3, 2, 2, 1, 2, 1, 1, 0 + }; + int ret = 0; + for (int i = 0; i < len; ++i) + { + ret += bitcount[bits[i] & 0xf]; + ret += bitcount[(bits[i] >> 4) & 0xf]; + } + return ret; + } +} diff --git a/src/broadcast_socket.cpp b/src/broadcast_socket.cpp new file mode 100644 index 0000000..42495f5 --- /dev/null +++ b/src/broadcast_socket.cpp @@ -0,0 +1,358 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/debug.hpp" + +using namespace std::placeholders; + +namespace libtorrent { + + bool is_ip_address(std::string const& host) + { + error_code ec; + make_address(host, ec); + return !ec; + } + + bool is_global(address const& a) + { + if (a.is_v6()) + { + // https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml + address_v6 const a6 = a.to_v6(); + return (a6.to_bytes()[0] & 0xe0) == 0x20; + } + else + { + address_v4 const a4 = a.to_v4(); + return !(a4.is_multicast() || a4.is_unspecified() || is_local(a)); + } + } + + bool is_link_local(address const& a) + { + if (a.is_v6()) + { + address_v6 const a6 = a.to_v6(); + return a6.is_link_local() + || a6.is_multicast_link_local(); + } + address_v4 const a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return (ip & 0xffff0000) == 0xa9fe0000; // 169.254.x.x + } + + bool is_local(address const& a) + { + if (a.is_v6()) + { + // NOTE: site local is deprecated but by + // https://www.ietf.org/rfc/rfc3879.txt: + // routers SHOULD be configured to prevent + // routing of this prefix by default. + + address_v6 const a6 = a.to_v6(); + return a6.is_loopback() + || a6.is_link_local() + || a6.is_site_local() + || a6.is_multicast_link_local() + || a6.is_multicast_site_local() + // fc00::/7, unique local address + || (a6.to_bytes()[0] & 0xfe) == 0xfc; + } + address_v4 a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 // 10.x.x.x + || (ip & 0xfff00000) == 0xac100000 // 172.16.x.x + || (ip & 0xffff0000) == 0xc0a80000 // 192.168.x.x + || (ip & 0xffff0000) == 0xa9fe0000 // 169.254.x.x + || (ip & 0xff000000) == 0x7f000000); // 127.x.x.x + } + + // TODO: this function is pointless + bool is_loopback(address const& addr) + { + return addr.is_loopback(); + } + + bool is_any(address const& addr) + { + if (addr.is_v4()) + return addr.to_v4() == address_v4::any(); + else if (addr.to_v6().is_v4_mapped()) + return (addr.to_v6().to_v4() == address_v4::any()); + else + return addr.to_v6() == address_v6::any(); + } + + bool is_teredo(address const& addr) + { + if (!addr.is_v6()) return false; + static const std::uint8_t teredo_prefix[] = {0x20, 0x01, 0, 0}; + address_v6::bytes_type b = addr.to_v6().to_bytes(); + return std::memcmp(b.data(), teredo_prefix, 4) == 0; + } + + bool supports_ipv6() + { +#if defined TORRENT_BUILD_SIMULATOR + return true; +#elif defined TORRENT_WINDOWS + TORRENT_TRY { + error_code ec; + make_address("::1", ec); + return !ec; + } TORRENT_CATCH(std::exception const&) { return false; } +#else + io_service ios; + tcp::socket test(ios); + error_code ec; + test.open(tcp::v6(), ec); + if (ec) return false; + error_code ignore; + test.bind(tcp::endpoint(make_address_v6("::1", ignore), 0), ec); + return !bool(ec); +#endif + } + + address ensure_v6(address const& a) + { + return a == address_v4() ? address_v6() : a; + } + + broadcast_socket::broadcast_socket( + udp::endpoint const& multicast_endpoint) + : m_multicast_endpoint(multicast_endpoint) + , m_outstanding_operations(0) + , m_abort(false) + { + TORRENT_ASSERT(m_multicast_endpoint.address().is_multicast()); + } + + void broadcast_socket::open(receive_handler_t handler + , io_service& ios, error_code& ec, bool loopback) + { + m_on_receive = std::move(handler); + + std::vector interfaces = enum_net_interfaces(ios, ec); + + if (is_v6(m_multicast_endpoint)) + open_multicast_socket(ios, address_v6::any(), loopback, ec); + else + open_multicast_socket(ios, address_v4::any(), loopback, ec); + + for (auto const& i : interfaces) + { + // only multicast on compatible networks + if (i.interface_address.is_v4() != is_v4(m_multicast_endpoint)) continue; + // ignore any loopback interface + if (!loopback && is_loopback(i.interface_address)) continue; + + ec = error_code(); + + open_multicast_socket(ios, i.interface_address, loopback, ec); + open_unicast_socket(ios, i.interface_address + , i.netmask.is_v4() ? i.netmask.to_v4() : address_v4()); + } + } + + void broadcast_socket::open_multicast_socket(io_service& ios + , address const& addr, bool loopback, error_code& ec) + { + using namespace boost::asio::ip::multicast; + + std::shared_ptr s = std::make_shared(ios); + s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec); + if (ec) return; + s->set_option(udp::socket::reuse_address(true), ec); + if (ec) return; + s->bind(udp::endpoint(addr, m_multicast_endpoint.port()), ec); + if (ec) return; + s->set_option(join_group(m_multicast_endpoint.address()), ec); + if (ec) return; + s->set_option(hops(255), ec); + if (ec) return; + s->set_option(enable_loopback(loopback), ec); + if (ec) return; + m_sockets.emplace_back(s); + socket_entry& se = m_sockets.back(); + ADD_OUTSTANDING_ASYNC("broadcast_socket::on_receive"); + s->async_receive_from(boost::asio::buffer(se.buffer) + , se.remote, std::bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + ++m_outstanding_operations; + } + + void broadcast_socket::open_unicast_socket(io_service& ios, address const& addr + , address_v4 const& mask) + { + error_code ec; + std::shared_ptr s = std::make_shared(ios); + s->open(addr.is_v4() ? udp::v4() : udp::v6(), ec); + if (ec) return; + + m_unicast_sockets.emplace_back(s, mask); + socket_entry& se = m_unicast_sockets.back(); + + // allow sending broadcast messages + boost::asio::socket_base::broadcast option(true); + s->set_option(option, ec); + if (!ec) se.broadcast = true; + + ADD_OUTSTANDING_ASYNC("broadcast_socket::on_receive"); + s->async_receive_from(boost::asio::buffer(se.buffer) + , se.remote, std::bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + ++m_outstanding_operations; + } + + void broadcast_socket::send_to(char const* buffer, int size + , udp::endpoint const& to, error_code& ec) + { + bool all_fail = true; + error_code e; + for (auto& s : m_sockets) + { + if (!s.socket) continue; + s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)), to, 0, e); + if (e) + { + s.socket->close(e); + s.socket.reset(); + } + else + { + all_fail = false; + } + } + if (all_fail) ec = e; + } + + void broadcast_socket::send(char const* buffer, int const size + , error_code& ec, int const flags) + { + bool all_fail = true; + error_code e; + + for (auto& s : m_unicast_sockets) + { + if (!s.socket) continue; + s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)), m_multicast_endpoint, 0, e); + + // if the user specified the broadcast flag, send one to the broadcast + // address as well + if ((flags & broadcast_socket::flag_broadcast) && s.can_broadcast()) + s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)) + , udp::endpoint(s.broadcast_address(), m_multicast_endpoint.port()), 0, e); + + if (e) + { + s.socket->close(e); + s.socket.reset(); + } + else + { + all_fail = false; + } + } + + for (auto& s : m_sockets) + { + if (!s.socket) continue; + s.socket->send_to(boost::asio::buffer(buffer, std::size_t(size)), m_multicast_endpoint, 0, e); + if (e) + { + s.socket->close(e); + s.socket.reset(); + } + else + { + all_fail = false; + } + } + + if (all_fail) ec = e; + } + + void broadcast_socket::on_receive(socket_entry* s, error_code const& ec + , std::size_t bytes_transferred) + { + COMPLETE_ASYNC("broadcast_socket::on_receive"); + TORRENT_ASSERT(m_outstanding_operations > 0); + --m_outstanding_operations; + + if (ec || bytes_transferred == 0 || !m_on_receive) + { + maybe_abort(); + return; + } + m_on_receive(s->remote, {s->buffer.data(), int(bytes_transferred)}); + + if (maybe_abort()) return; + if (!s->socket) return; + ADD_OUTSTANDING_ASYNC("broadcast_socket::on_receive"); + s->socket->async_receive_from(boost::asio::buffer(s->buffer) + , s->remote, std::bind(&broadcast_socket::on_receive, this, s, _1, _2)); + ++m_outstanding_operations; + } + + bool broadcast_socket::maybe_abort() + { + bool ret = m_abort; + if (m_abort && m_outstanding_operations == 0) + { + // it's important that m_on_receive is cleared + // before the object is destructed, since it may + // hold a reference to ourself, which would otherwise + // cause an infinite recursion destructing the objects + receive_handler_t().swap(m_on_receive); + } + return ret; + } + + void broadcast_socket::close() + { + std::for_each(m_sockets.begin(), m_sockets.end(), std::bind(&socket_entry::close, _1)); + std::for_each(m_unicast_sockets.begin(), m_unicast_sockets.end(), std::bind(&socket_entry::close, _1)); + + m_abort = true; + maybe_abort(); + } +} diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp new file mode 100644 index 0000000..ffec3f7 --- /dev/null +++ b/src/bt_peer_connection.cpp @@ -0,0 +1,3484 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +Copyright (c) 2007-2018, Arvid Norberg, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include // unique_ptr +#include +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/hex.hpp" // to_hex +#endif + +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/aux_/io.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" // for alert_manager +#include "libtorrent/string_util.hpp" // for search +#include "libtorrent/aux_/generate_peer_id.hpp" + +#if !defined TORRENT_DISABLE_ENCRYPTION +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/hasher.hpp" +#endif + +namespace libtorrent { + +#if !defined TORRENT_DISABLE_ENCRYPTION +namespace { + + constexpr std::size_t handshake_len = 68; + constexpr std::size_t dh_key_len = 96; + + // stream key (info hash of attached torrent) + // secret is the DH shared secret + // initializes m_enc_handler + std::shared_ptr init_pe_rc4_handler(key_t const& secret + , sha1_hash const& stream_key, bool const outgoing) + { + hasher h; + static const char keyA[] = {'k', 'e', 'y', 'A'}; + static const char keyB[] = {'k', 'e', 'y', 'B'}; + + // encryption rc4 longkeys + // outgoing connection : hash ('keyA',S,SKEY) + // incoming connection : hash ('keyB',S,SKEY) + + std::array const secret_buf = export_key(secret); + + if (outgoing) h.update(keyA); else h.update(keyB); + h.update(secret_buf); + h.update(stream_key); + sha1_hash const local_key = h.final(); + + h.reset(); + + // decryption rc4 longkeys + // outgoing connection : hash ('keyB',S,SKEY) + // incoming connection : hash ('keyA',S,SKEY) + + if (outgoing) h.update(keyB); else h.update(keyA); + h.update(secret_buf); + h.update(stream_key); + sha1_hash const remote_key = h.final(); + + auto ret = std::make_shared(); + + ret->set_incoming_key(remote_key); + ret->set_outgoing_key(local_key); + + return ret; + } + +} // anonymous namespace +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool ut_pex_peer_store::was_introduced_by(tcp::endpoint const &ep) + { + if (is_v4(ep)) + { + peers4_t::value_type const v(ep.address().to_v4().to_bytes(), ep.port()); + auto const i = std::lower_bound(m_peers.begin(), m_peers.end(), v); + return i != m_peers.end() && *i == v; + } + else + { + peers6_t::value_type const v(ep.address().to_v6().to_bytes(), ep.port()); + auto const i = std::lower_bound(m_peers6.begin(), m_peers6.end(), v); + return i != m_peers6.end() && *i == v; + } + } +#endif // TORRENT_DISABLE_EXTENSIONS + + bt_peer_connection::bt_peer_connection(peer_connection_args const& pack) + : peer_connection(pack) + , m_supports_extensions(false) + , m_supports_dht_port(false) + , m_supports_fast(false) + , m_sent_bitfield(false) + , m_sent_handshake(false) + , m_sent_allowed_fast(false) +#if !defined TORRENT_DISABLE_ENCRYPTION + , m_encrypted(false) + , m_rc4_encrypted(false) + , m_recv_buffer(peer_connection::m_recv_buffer) +#endif + , m_our_peer_id(pack.our_peer_id) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONSTRUCT", "bt_peer_connection"); +#endif + + m_reserved_bits.fill(0); + } + + void bt_peer_connection::start() + { + peer_connection::start(); + + // start in the state where we are trying to read the + // handshake from the other side + m_recv_buffer.reset(20); + setup_receive(); + } + + bt_peer_connection::~bt_peer_connection() = default; + +#if !defined TORRENT_DISABLE_ENCRYPTION + void bt_peer_connection::switch_send_crypto(std::shared_ptr crypto) + { + if (m_enc_handler.switch_send_crypto(std::move(crypto), send_buffer_size() - get_send_barrier())) + set_send_barrier(send_buffer_size()); + } + + void bt_peer_connection::switch_recv_crypto(std::shared_ptr crypto) + { + m_enc_handler.switch_recv_crypto(std::move(crypto), m_recv_buffer); + } +#endif + + void bt_peer_connection::on_connected() + { + if (is_disconnecting()) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + if (t->graceful_pause()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ON_CONNECTED", "graceful-paused"); +#endif + disconnect(errors::torrent_paused, operation_t::bittorrent); + return; + } + + // make sure are much as possible of the response ends up in the same + // packet, or at least back-to-back packets + cork c_(*this); + +#if !defined TORRENT_DISABLE_ENCRYPTION + + std::uint8_t out_policy = std::uint8_t(m_settings.get_int(settings_pack::out_enc_policy)); + +#ifdef TORRENT_USE_OPENSSL + // never try an encrypted connection when already using SSL + if (is_ssl(*get_socket())) + out_policy = settings_pack::pe_disabled; +#endif +#ifndef TORRENT_DISABLE_LOGGING + static char const* policy_name[] = {"forced", "enabled", "disabled"}; + TORRENT_ASSERT(out_policy < sizeof(policy_name)/sizeof(policy_name[0])); + peer_log(peer_log_alert::info, "ENCRYPTION" + , "outgoing encryption policy: %s", policy_name[out_policy]); +#endif + + if (out_policy == settings_pack::pe_forced) + { + write_pe1_2_dhkey(); + if (is_disconnecting()) return; + + m_state = state_t::read_pe_dhkey; + m_recv_buffer.reset(dh_key_len); + setup_receive(); + } + else if (out_policy == settings_pack::pe_enabled) + { + TORRENT_ASSERT(peer_info_struct()); + + torrent_peer* pi = peer_info_struct(); + if (pi->pe_support == true) + { + // toggle encryption support flag, toggled back to + // true if encrypted portion of the handshake + // completes correctly + pi->pe_support = false; + + // if this fails, we need to reconnect + // fast. + fast_reconnect(true); + + write_pe1_2_dhkey(); + if (is_disconnecting()) return; + m_state = state_t::read_pe_dhkey; + m_recv_buffer.reset(dh_key_len); + setup_receive(); + } + else // pi->pe_support == false + { + // toggled back to false if standard handshake + // completes correctly (without encryption) + pi->pe_support = true; + + write_handshake(); + m_recv_buffer.reset(20); + setup_receive(); + } + } + else if (out_policy == settings_pack::pe_disabled) +#endif + { + write_handshake(); + + // start in the state where we are trying to read the + // handshake from the other side + m_recv_buffer.reset(20); + setup_receive(); + } + } + + void bt_peer_connection::on_metadata() + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ON_METADATA"); +#endif + + disconnect_if_redundant(); + if (m_disconnecting) return; + + if (!m_sent_handshake) return; + // we're still waiting to fully handshake with this peer. At the end of + // the handshake we'll send the bitfield and dht port anyway. It's too + // early to do now + if (static_cast(m_state) + < static_cast(state_t::read_packet_size)) + { + return; + } + + // connections that are still in the handshake + // will send their bitfield when the handshake + // is done + std::shared_ptr t = associated_torrent().lock(); +#ifndef TORRENT_DISABLE_SHARE_MODE + if (!t->share_mode()) +#endif + { + bool const upload_only_enabled = t->is_upload_only() +#ifndef TORRENT_DISABLE_SUPERSEEDING + && !t->super_seeding() +#endif + ; + send_upload_only(upload_only_enabled); + } + + if (m_sent_bitfield) return; + + TORRENT_ASSERT(t); + write_bitfield(); + TORRENT_ASSERT(m_sent_bitfield); + write_dht_port(); + } + + void bt_peer_connection::write_dht_port() + { +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + { + int const port = m_ses.external_udp_port(local_endpoint().address()); + if (port >= 0) write_dht_port(port); + } +#endif + } + + void bt_peer_connection::write_dht_port(int const listen_port) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "DHT_PORT", "%d", listen_port); +#endif + char msg[] = {0,0,0,3, msg_dht_port, 0, 0}; + char* ptr = msg + 5; + detail::write_uint16(listen_port, ptr); + send_buffer(msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_dht_port); + } + + void bt_peer_connection::write_have_all() + { + INVARIANT_CHECK; + + m_sent_bitfield = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE_ALL"); +#endif + send_message(msg_have_all, counters::num_outgoing_have_all); + } + + void bt_peer_connection::write_have_none() + { + INVARIANT_CHECK; + m_sent_bitfield = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE_NONE"); +#endif + send_message(msg_have_none, counters::num_outgoing_have_none); + } + + void bt_peer_connection::write_reject_request(peer_request const& r) + { + INVARIANT_CHECK; + + stats_counters().inc_stats_counter(counters::piece_rejects); + + if (!m_supports_fast) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d | s: %d | l: %d", static_cast(r.piece) + , r.start, r.length); +#endif + + send_message(msg_reject_request, counters::num_outgoing_reject + , static_cast(r.piece), r.start, r.length); + } + + void bt_peer_connection::write_allow_fast(piece_index_t const piece) + { + INVARIANT_CHECK; + + if (!m_supports_fast) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "ALLOWED_FAST", "%d" + , static_cast(piece)); +#endif + + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + + send_message(msg_allowed_fast, counters::num_outgoing_allowed_fast + , static_cast(piece)); + } + + void bt_peer_connection::write_suggest(piece_index_t const piece) + { + INVARIANT_CHECK; + + if (!m_supports_fast) return; + +#if TORRENT_USE_ASSERTS + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { +#if !TORRENT_USE_ASSERTS + std::shared_ptr t = associated_torrent().lock(); +#endif + peer_log(peer_log_alert::outgoing_message, "SUGGEST" + , "piece: %d num_peers: %d", static_cast(piece) + , t->has_picker() ? t->picker().get_availability(piece) : -1); + } +#endif + + send_message(msg_suggest_piece, counters::num_outgoing_suggest + , static_cast(piece)); + } + + void bt_peer_connection::get_specific_peer_info(peer_info& p) const + { + TORRENT_ASSERT(!associated_torrent().expired()); + + if (is_interesting()) p.flags |= peer_info::interesting; + if (is_choked()) p.flags |= peer_info::choked; + if (is_peer_interested()) p.flags |= peer_info::remote_interested; + if (has_peer_choked()) p.flags |= peer_info::remote_choked; + if (support_extensions()) p.flags |= peer_info::supports_extensions; + if (is_outgoing()) p.flags |= peer_info::local_connection; +#if TORRENT_USE_I2P + if (is_i2p(*get_socket())) p.flags |= peer_info::i2p_socket; +#endif + if (is_utp(*get_socket())) p.flags |= peer_info::utp_socket; + if (is_ssl(*get_socket())) p.flags |= peer_info::ssl_socket; + +#if !defined TORRENT_DISABLE_ENCRYPTION + if (m_encrypted) + { + p.flags |= m_rc4_encrypted + ? peer_info::rc4_encrypted + : peer_info::plaintext_encrypted; + } +#endif + + if (!is_connecting() && in_handshake()) + p.flags |= peer_info::handshake; + if (is_connecting()) p.flags |= peer_info::connecting; + + p.client = m_client_version; + p.connection_type = peer_info::standard_bittorrent; + } + + bool bt_peer_connection::in_handshake() const + { + // this returns true until we have received a handshake + // and until we have send our handshake + return !m_sent_handshake || m_state < state_t::read_packet_size; + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + + void bt_peer_connection::write_pe1_2_dhkey() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!m_dh_key_exchange.get()); + TORRENT_ASSERT(!m_sent_handshake); + +#ifndef TORRENT_DISABLE_LOGGING + if (is_outgoing()) + peer_log(peer_log_alert::info, "ENCRYPTION", "initiating encrypted handshake"); +#endif + + m_dh_key_exchange.reset(new (std::nothrow) dh_key_exchange); + if (!m_dh_key_exchange || !m_dh_key_exchange->good()) + { + disconnect(errors::no_memory, operation_t::encryption); + return; + } + + int const pad_size = int(random(512)); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "pad size: %d", pad_size); +#endif + + char msg[dh_key_len + 512]; + char* ptr = msg; + int const buf_size = int(dh_key_len) + pad_size; + + std::array const local_key = export_key(m_dh_key_exchange->get_local_key()); + std::memcpy(ptr, local_key.data(), dh_key_len); + ptr += dh_key_len; + + aux::random_bytes({ptr, pad_size}); + send_buffer({msg, buf_size}); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "sent DH key"); +#endif + } + + void bt_peer_connection::write_pe3_sync() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(is_outgoing()); + TORRENT_ASSERT(!m_sent_handshake); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + hasher h; + sha1_hash const& info_hash = t->torrent_file().info_hash(); + key_t const secret_key = m_dh_key_exchange->get_secret(); + std::array const secret = export_key(secret_key); + + int const pad_size = int(random(512)); + + // synchash,skeyhash,vc,crypto_provide,len(pad),pad,len(ia) + char msg[20 + 20 + 8 + 4 + 2 + 512 + 2]; + char* ptr = msg; + + static char const req1[4] = {'r', 'e', 'q', '1'}; + // sync hash (hash('req1',S)) + h.reset(); + h.update(req1); + h.update(secret); + sha1_hash const sync_hash = h.final(); + + std::memcpy(ptr, sync_hash.data(), 20); + ptr += 20; +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ENCRYPTION" + , "writing synchash %s secret: %s" + , aux::to_hex(sync_hash).c_str() + , aux::to_hex(secret).c_str()); + } +#endif + + static char const req2[4] = {'r', 'e', 'q', '2'}; + // stream key obfuscated hash [ hash('req2',SKEY) xor hash('req3',S) ] + h.reset(); + h.update(req2); + h.update(info_hash); + sha1_hash const streamkey_hash = h.final(); + + static char const req3[4] = {'r', 'e', 'q', '3'}; + h.reset(); + h.update(req3); + h.update(secret); + sha1_hash const obfsc_hash = h.final() ^ streamkey_hash; + + std::memcpy(ptr, obfsc_hash.data(), 20); + ptr += 20; + + // Discard DH key exchange data, setup RC4 keys + m_rc4 = init_pe_rc4_handler(secret_key, info_hash, is_outgoing()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "computed RC4 keys"); +#endif + m_dh_key_exchange.reset(); // secret should be invalid at this point + + // write the verification constant and crypto field + int const encrypt_size = int(sizeof(msg)) - 512 + pad_size - 40; + + // this is an invalid setting, but let's just make the best of the situation + int const enc_level = m_settings.get_int(settings_pack::allowed_enc_level); + std::uint8_t const crypto_provide = ((enc_level & settings_pack::pe_both) == 0) + ? std::uint8_t(settings_pack::pe_both) + : std::uint8_t(enc_level); + +#ifndef TORRENT_DISABLE_LOGGING + static char const* level[] = {"plaintext", "rc4", "plaintext rc4"}; + peer_log(peer_log_alert::info, "ENCRYPTION" + , "%s", level[crypto_provide - 1]); +#endif + + write_pe_vc_cryptofield({ptr, encrypt_size}, crypto_provide, pad_size); + span vec(ptr, encrypt_size); + m_rc4->encrypt(vec); + send_buffer({msg, int(sizeof(msg)) - 512 + pad_size}); + } + + void bt_peer_connection::write_pe4_sync(int const crypto_select) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(crypto_select == 0x02 || crypto_select == 0x01); + TORRENT_ASSERT(!m_sent_handshake); + + int const pad_size = int(random(512)); + + int const buf_size = 8 + 4 + 2 + pad_size; + char msg[512 + 8 + 4 + 2]; + write_pe_vc_cryptofield(msg, crypto_select, pad_size); + + span vec(msg, buf_size); + m_rc4->encrypt(vec); + send_buffer(vec); + + // encryption method has been negotiated + if (crypto_select == 0x02) + m_rc4_encrypted = true; + else // 0x01 + m_rc4_encrypted = false; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", " crypto select: %s" + , (crypto_select == 0x01) ? "plaintext" : "rc4"); +#endif + } + + void bt_peer_connection::write_pe_vc_cryptofield( + span write_buf + , int const crypto_field + , int const pad_size) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(crypto_field <= 0x03 && crypto_field > 0); + // vc,crypto_field,len(pad),pad, (len(ia)) + TORRENT_ASSERT((write_buf.size() >= 8+4+2+pad_size+2 + && is_outgoing()) + || (write_buf.size() >= 8+4+2+pad_size && !is_outgoing())); + TORRENT_ASSERT(!m_sent_handshake); + + // encrypt(vc, crypto_provide/select, len(Pad), len(IA)) + // len(pad) is zero for now, len(IA) only for outgoing connections + + // vc + std::memset(write_buf.data(), 0, 8); + write_buf = write_buf.subspan(8); + + aux::write_uint32(crypto_field, write_buf); + aux::write_uint16(pad_size, write_buf); // len (pad) + + aux::random_bytes(write_buf.first(pad_size)); + write_buf = write_buf.subspan(pad_size); + + // append len(ia) if we are initiating + if (is_outgoing()) + aux::write_uint16(handshake_len, write_buf); // len(IA) + } + + void bt_peer_connection::rc4_decrypt(span buf) + { + m_rc4->decrypt(buf); + } + +#endif // #if !defined TORRENT_DISABLE_ENCRYPTION + + void bt_peer_connection::write_handshake() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_sent_handshake); + m_sent_handshake = true; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // add handshake to the send buffer + static const char version_string[] = "BitTorrent protocol"; + const int string_len = sizeof(version_string) - 1; + + char handshake[1 + string_len + 8 + 20 + 20]; + char* ptr = handshake; + // length of version string + detail::write_uint8(string_len, ptr); + // protocol identifier + std::memcpy(ptr, version_string, string_len); + ptr += string_len; + // 8 zeroes + std::memset(ptr, 0, 8); + +#ifndef TORRENT_DISABLE_DHT + // indicate that we support the DHT messages + *(ptr + 7) |= 0x01; +#endif + + // we support extensions + *(ptr + 5) |= 0x10; + + if (m_settings.get_bool(settings_pack::support_merkle_torrents)) + { + // we support merkle torrents + *(ptr + 5) |= 0x08; + } + + // we support FAST extension + *(ptr + 7) |= 0x04; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + std::string bitmask; + for (int k = 0; k < 8; ++k) + { + for (int j = 0; j < 8; ++j) + { + if (ptr[k] & (0x80 >> j)) bitmask += '1'; + else bitmask += '0'; + } + } + peer_log(peer_log_alert::outgoing_message, "EXTENSIONS" + , "%s", bitmask.c_str()); + } +#endif + ptr += 8; + + // info hash + sha1_hash const& ih = t->torrent_file().info_hash(); + std::memcpy(ptr, ih.data(), ih.size()); + ptr += 20; + + std::memcpy(ptr, m_our_peer_id.data(), 20); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "HANDSHAKE" + , "sent peer_id: %s client: %s" + , aux::to_hex(m_our_peer_id).c_str(), identify_client(m_our_peer_id).c_str()); + } + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "HANDSHAKE" + , "ih: %s", aux::to_hex(ih).c_str()); + } +#endif + send_buffer(handshake); + } + + piece_block_progress bt_peer_connection::downloading_piece_progress() const + { + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + span recv_buffer = m_recv_buffer.get(); + // are we currently receiving a 'piece' message? + if (m_state != state_t::read_packet + || int(recv_buffer.size()) <= 9 + || recv_buffer[0] != msg_piece) + return piece_block_progress(); + + const char* ptr = recv_buffer.data() + 1; + peer_request r; + r.piece = piece_index_t(detail::read_int32(ptr)); + r.start = detail::read_int32(ptr); + r.length = m_recv_buffer.packet_size() - 9; + + // is any of the piece message header data invalid? + if (!verify_piece(r)) + return piece_block_progress(); + + piece_block_progress p; + + p.piece_index = r.piece; + p.block_index = r.start / t->block_size(); + p.bytes_downloaded = int(recv_buffer.size()) - 9; + p.full_block_bytes = r.length; + + return p; + } + + + // message handlers + + // ----------------------------- + // ----------- CHOKE ----------- + // ----------------------------- + + void bt_peer_connection::on_choke(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 1) + { + disconnect(errors::invalid_choke, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + incoming_choke(); + if (is_disconnecting()) return; + if (!m_supports_fast) + { + // we just got choked, and the peer that choked use + // doesn't support fast extensions, so we have to + // assume that the choke message implies that all + // of our requests are rejected. Go through them and + // pretend that we received reject request messages + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + auto const dlq = download_queue(); + for (pending_block const& pb : dlq) + { + peer_request r; + r.piece = pb.block.piece_index; + r.start = pb.block.block_index * t->block_size(); + r.length = t->block_size(); + // if it's the last piece, make sure to + // set the length of the request to not + // exceed the end of the torrent. This is + // necessary in order to maintain a correct + // m_outstanding_bytes + if (r.piece == t->torrent_file().last_piece()) + { + r.length = std::min(t->torrent_file().piece_size( + r.piece) - r.start, r.length); + } + incoming_reject_request(r); + } + } + } + + // ----------------------------- + // ---------- UNCHOKE ---------- + // ----------------------------- + + void bt_peer_connection::on_unchoke(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 1) + { + disconnect(errors::invalid_unchoke, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + incoming_unchoke(); + } + + // ----------------------------- + // -------- INTERESTED --------- + // ----------------------------- + + void bt_peer_connection::on_interested(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 1) + { + disconnect(errors::invalid_interested, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + // we defer sending the allowed set until the peer says it's interested in + // us. This saves some bandwidth and allows us to omit messages for pieces + // that the peer already has + if (!m_sent_allowed_fast && m_supports_fast) + { + m_sent_allowed_fast = true; + send_allowed_set(); + } + + incoming_interested(); + } + + // ----------------------------- + // ------ NOT INTERESTED ------- + // ----------------------------- + + void bt_peer_connection::on_not_interested(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 1) + { + disconnect(errors::invalid_not_interested, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + incoming_not_interested(); + } + + // ----------------------------- + // ----------- HAVE ------------ + // ----------------------------- + + void bt_peer_connection::on_have(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 5) + { + disconnect(errors::invalid_have, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + const char* ptr = recv_buffer.data() + 1; + piece_index_t const index(detail::read_int32(ptr)); + + incoming_have(index); + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void bt_peer_connection::on_bitfield(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + received_bytes(0, received); + // if we don't have the metadata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && m_recv_buffer.packet_size() - 1 != (t->torrent_file().num_pieces() + CHAR_BIT - 1) / CHAR_BIT) + { + disconnect(errors::invalid_bitfield_size, operation_t::bittorrent, peer_error); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + typed_bitfield bits; + bits.assign(recv_buffer.data() + 1 + , t->valid_metadata()?get_bitfield().size():(m_recv_buffer.packet_size()-1)*CHAR_BIT); + + incoming_bitfield(bits); + } + + // ----------------------------- + // ---------- REQUEST ---------- + // ----------------------------- + + void bt_peer_connection::on_request(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 13) + { + disconnect(errors::invalid_request, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + peer_request r; + const char* ptr = recv_buffer.data() + 1; + r.piece = piece_index_t(detail::read_int32(ptr)); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_request(r); + } + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void bt_peer_connection::on_piece(int const received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + + span recv_buffer = m_recv_buffer.get(); + int const recv_pos = m_recv_buffer.pos(); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + bool const merkle = static_cast(recv_buffer.front()) == 250; + if (merkle) + { + if (recv_pos == 1) + { + received_bytes(0, received); + return; + } + if (recv_pos < 13) + { + received_bytes(0, received); + return; + } + char const* ptr = recv_buffer.data() + 9; + int const list_size = detail::read_int32(ptr); + + if (list_size > m_recv_buffer.packet_size() - 13 || list_size < 0) + { + received_bytes(0, received); + disconnect(errors::invalid_hash_list, operation_t::bittorrent, peer_error); + return; + } + + if (m_recv_buffer.packet_size() - 13 - list_size > t->block_size()) + { + received_bytes(0, received); + disconnect(errors::packet_too_large, operation_t::bittorrent, peer_error); + return; + } + } + else + { + if (recv_pos == 1) + { + if (m_recv_buffer.packet_size() - 9 > t->block_size()) + { + received_bytes(0, received); + disconnect(errors::packet_too_large, operation_t::bittorrent, peer_error); + return; + } + } + } + // classify the received data as protocol chatter + // or data payload for the statistics + int piece_bytes = 0; + + int header_size = merkle?13:9; + + peer_request p; + int list_size = 0; + + if (recv_pos >= header_size) + { + const char* ptr = recv_buffer.data() + 1; + p.piece = piece_index_t(detail::read_int32(ptr)); + p.start = detail::read_int32(ptr); + + if (merkle) + { + list_size = detail::read_int32(ptr); + if (list_size < 0) + { + received_bytes(0, received); + disconnect(errors::invalid_hash_list, operation_t::bittorrent, peer_error); + return; + } + p.length = m_recv_buffer.packet_size() - list_size - header_size; + header_size += list_size; + } + else + { + p.length = m_recv_buffer.packet_size() - header_size; + } + } + else + { + p.piece = piece_index_t(0); + p.start = 0; + p.length = 0; + } + + if (recv_pos <= header_size) + { + // only received protocol data + received_bytes(0, received); + } + else if (recv_pos - received >= header_size) + { + // only received payload data + received_bytes(received, 0); + piece_bytes = received; + } + else + { + // received a bit of both + TORRENT_ASSERT(recv_pos - received < header_size); + TORRENT_ASSERT(recv_pos > header_size); + TORRENT_ASSERT(header_size - (recv_pos - received) <= header_size); + received_bytes( + recv_pos - header_size + , header_size - (recv_pos - received)); + piece_bytes = recv_pos - header_size; + } + + if (recv_pos < header_size) return; + +#ifndef TORRENT_DISABLE_LOGGING +// peer_log(peer_log_alert::incoming_message, "PIECE_FRAGMENT", "p: %d start: %d length: %d" +// , p.piece, p.start, p.length); +#endif + + if (recv_pos - received < header_size && recv_pos >= header_size) + { + // call this once, the first time the entire header + // has been received + start_receive_piece(p); + if (is_disconnecting()) return; + } + + incoming_piece_fragment(piece_bytes); + if (!m_recv_buffer.packet_finished()) return; + + if (merkle && list_size > 0) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HASHPIECE" + , "piece: %d list: %d", static_cast(p.piece), list_size); +#endif + error_code ec; + bdecode_node const hash_list = bdecode(recv_buffer.subspan(13).first(list_size) + , ec); + if (ec) + { + disconnect(errors::invalid_hash_piece, operation_t::bittorrent, peer_error); + return; + } + + // the list has this format: + // [ [node-index, hash], [node-index, hash], ... ] + if (hash_list.type() != bdecode_node::list_t) + { + disconnect(errors::invalid_hash_list, operation_t::bittorrent, peer_error); + return; + } + + std::map nodes; + for (int i = 0; i < hash_list.list_size(); ++i) + { + bdecode_node const e = hash_list.list_at(i); + if (e.type() != bdecode_node::list_t + || e.list_size() != 2 + || e.list_at(0).type() != bdecode_node::int_t + || e.list_at(1).type() != bdecode_node::string_t + || e.list_at(1).string_length() != 20) continue; + + nodes.emplace(int(e.list_int_value_at(0)) + , sha1_hash(e.list_at(1).string_ptr())); + } + if (!nodes.empty() && !t->add_merkle_nodes(nodes, p.piece)) + { + disconnect(errors::invalid_hash_piece, operation_t::bittorrent, peer_error); + return; + } + } + + incoming_piece(p, recv_buffer.data() + header_size); + } + + // ----------------------------- + // ---------- CANCEL ----------- + // ----------------------------- + + void bt_peer_connection::on_cancel(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 13) + { + disconnect(errors::invalid_cancel, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + peer_request r; + const char* ptr = recv_buffer.data() + 1; + r.piece = piece_index_t(detail::read_int32(ptr)); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_cancel(r); + } + + // ----------------------------- + // --------- DHT PORT ---------- + // ----------------------------- + + void bt_peer_connection::on_dht_port(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() != 3) + { + disconnect(errors::invalid_dht_port, operation_t::bittorrent, peer_error); + return; + } + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + const char* ptr = recv_buffer.data() + 1; + int const listen_port = detail::read_uint16(ptr); + + incoming_dht_port(listen_port); + + if (!m_supports_dht_port) + { + m_supports_dht_port = true; + // if we're done with the handshake, respond right away, otherwise + // we'll send the DHT port later + if (m_sent_bitfield) + write_dht_port(); + } + } + + void bt_peer_connection::on_suggest_piece(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast || m_recv_buffer.packet_size() != 5) + { + disconnect(errors::invalid_suggest, operation_t::bittorrent, peer_error); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + const char* ptr = recv_buffer.data() + 1; + piece_index_t const piece(detail::read_int32(ptr)); + incoming_suggest(piece); + } + + void bt_peer_connection::on_have_all(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast || m_recv_buffer.packet_size() != 1) + { + disconnect(errors::invalid_have_all, operation_t::bittorrent, peer_error); + return; + } + incoming_have_all(); + } + + void bt_peer_connection::on_have_none(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast || m_recv_buffer.packet_size() != 1) + { + disconnect(errors::invalid_have_none, operation_t::bittorrent, peer_error); + return; + } + incoming_have_none(); + } + + void bt_peer_connection::on_reject_request(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast || m_recv_buffer.packet_size() != 13) + { + disconnect(errors::invalid_reject, operation_t::bittorrent, peer_error); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + + span recv_buffer = m_recv_buffer.get(); + + peer_request r; + const char* ptr = recv_buffer.data() + 1; + r.piece = piece_index_t(detail::read_int32(ptr)); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + + incoming_reject_request(r); + } + + void bt_peer_connection::on_allowed_fast(int received) + { + INVARIANT_CHECK; + + received_bytes(0, received); + if (!m_supports_fast || m_recv_buffer.packet_size() != 5) + { + disconnect(errors::invalid_allow_fast, operation_t::bittorrent, peer_error); + return; + } + + if (!m_recv_buffer.packet_finished()) return; + span recv_buffer = m_recv_buffer.get(); + const char* ptr = recv_buffer.data() + 1; + piece_index_t const index(detail::read_int32(ptr)); + + incoming_allowed_fast(index); + } + + // ----------------------------- + // -------- RENDEZVOUS --------- + // ----------------------------- + + void bt_peer_connection::on_holepunch() + { + INVARIANT_CHECK; + + if (!m_recv_buffer.packet_finished()) return; + + // we can't accept holepunch messages from peers + // that don't support the holepunch extension + // because we wouldn't be able to respond + if (m_holepunch_id == 0) return; + + span recv_buffer = m_recv_buffer.get(); + TORRENT_ASSERT(recv_buffer.front() == msg_extended); + recv_buffer = recv_buffer.subspan(1); + TORRENT_ASSERT(recv_buffer.front() == holepunch_msg); + recv_buffer = recv_buffer.subspan(1); + + char const* ptr = recv_buffer.data(); + char const* const end = recv_buffer.data() + recv_buffer.size(); + + // ignore invalid messages + if (int(recv_buffer.size()) < 2) return; + + auto const msg_type = static_cast(detail::read_uint8(ptr)); + int const addr_type = detail::read_uint8(ptr); + + tcp::endpoint ep; + + if (addr_type == 0) + { + if (int(recv_buffer.size()) < 2 + 4 + 2) return; + // IPv4 address + ep = detail::read_v4_endpoint(ptr); + } + else if (addr_type == 1) + { + // IPv6 address + if (int(recv_buffer.size()) < 2 + 16 + 2) return; + ep = detail::read_v6_endpoint(ptr); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + static const char* hp_msg_name[] = {"rendezvous", "connect", "failed"}; + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg: %s from %s to: unknown address type" + , (static_cast(msg_type) < 3 + ? hp_msg_name[static_cast(msg_type)] + : "unknown message type") + , print_address(remote().address()).c_str()); + } +#endif + + return; // unknown address type + } + +#ifndef TORRENT_DISABLE_LOGGING + if (msg_type > hp_message::failed) + { + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg: unknown message type (%d) to: %s" + , static_cast(msg_type) + , print_address(ep.address()).c_str()); + } + return; + } +#endif + + std::shared_ptr t = associated_torrent().lock(); + if (!t) return; + + switch (msg_type) + { + case hp_message::rendezvous: // rendezvous + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg: rendezvous to: %s", print_address(ep.address()).c_str()); + } +#endif + // this peer is asking us to introduce it to + // the peer at 'ep'. We need to find which of + // our connections points to that endpoint + bt_peer_connection* p = t->find_peer(ep); + if (p == nullptr) + { + // we're not connected to this peer + write_holepunch_msg(hp_message::failed, ep, hp_error::not_connected); + break; + } + if (!p->supports_holepunch()) + { + write_holepunch_msg(hp_message::failed, ep, hp_error::no_support); + break; + } + if (p == this) + { + write_holepunch_msg(hp_message::failed, ep, hp_error::no_self); + break; + } + + write_holepunch_msg(hp_message::connect, ep); + p->write_holepunch_msg(hp_message::connect, remote()); + } break; + case hp_message::connect: + { + // add or find the peer with this endpoint + torrent_peer* p = t->add_peer(ep, peer_info::pex); + if (p == nullptr || p->connection) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg:connect to: %s ERROR: failed to add peer" + , print_address(ep.address()).c_str()); + } +#endif + // we either couldn't add this peer, or it's + // already connected. Just ignore the connect message + break; + } + if (p->banned) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg:connect to: %s ERROR: peer banned", print_address(ep.address()).c_str()); + } +#endif + // this peer is banned, don't connect to it + break; + } + // to make sure we use the uTP protocol + p->supports_utp = true; + // #error make sure we make this a connection candidate + // in case it has too many failures for instance + t->connect_to_peer(p, true); + // mark this connection to be in holepunch mode + // so that it will retry faster and stick to uTP while it's + // retrying + t->update_want_peers(); + if (p->connection) + p->connection->set_holepunch_mode(); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg:connect to: %s" + , print_address(ep.address()).c_str()); + } +#endif + } break; + case hp_message::failed: + { + if (end - ptr < 4) return; + std::uint32_t const error = detail::read_uint32(ptr); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + static char const* err_msg[] = {"no such peer", "not connected", "no support", "no self"}; + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH" + , "msg:failed ERROR: %d msg: %s", error + , ((error > 0 && error < 5)?err_msg[error-1]:"unknown message id")); + } +#endif + // #error deal with holepunch errors + (void)error; + } break; + } + } + + void bt_peer_connection::write_holepunch_msg(hp_message const type + , tcp::endpoint const& ep, hp_error const error) + { + char buf[35]; + char* ptr = buf + 6; + detail::write_uint8(type, ptr); + if (is_v4(ep)) detail::write_uint8(0, ptr); + else detail::write_uint8(1, ptr); + detail::write_endpoint(ep, ptr); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + static const char* hp_msg_name[] = {"rendezvous", "connect", "failed"}; + static const char* hp_error_string[] = {"", "no such peer", "not connected", "no support", "no self"}; + peer_log(peer_log_alert::outgoing_message, "HOLEPUNCH" + , "msg: %s to: %s ERROR: %s" + , (static_cast(type) < 3 + ? hp_msg_name[static_cast(type)] + : "unknown message type") + , print_address(ep.address()).c_str() + , hp_error_string[static_cast(error)]); + } +#endif + if (type == hp_message::failed) + { + detail::write_uint32(static_cast(error), ptr); + } + + // write the packet length and type + char* hdr = buf; + detail::write_uint32(ptr - buf - 4, hdr); + detail::write_uint8(msg_extended, hdr); + detail::write_uint8(m_holepunch_id, hdr); + + TORRENT_ASSERT(ptr <= buf + sizeof(buf)); + + send_buffer({buf, ptr - buf}); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); + } + + // ----------------------------- + // --------- EXTENDED ---------- + // ----------------------------- + + void bt_peer_connection::on_extended(int received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); + if (m_recv_buffer.packet_size() < 2) + { + disconnect(errors::invalid_extended, operation_t::bittorrent, peer_error); + return; + } + + if (associated_torrent().expired()) + { + disconnect(errors::invalid_extended, operation_t::bittorrent, peer_error); + return; + } + + span recv_buffer = m_recv_buffer.get(); + if (int(recv_buffer.size()) < 2) return; + + TORRENT_ASSERT(recv_buffer.front() == msg_extended); + recv_buffer = recv_buffer.subspan(1); + + int const extended_id = aux::read_uint8(recv_buffer); + + if (extended_id == 0) + { + on_extended_handshake(); + disconnect_if_redundant(); + return; + } + + if (extended_id == upload_only_msg) + { + if (!m_recv_buffer.packet_finished()) return; + if (m_recv_buffer.packet_size() != 3) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "UPLOAD_ONLY" + , "ERROR: unexpected packet size: %d", m_recv_buffer.packet_size()); +#endif + return; + } + bool const ul = aux::read_uint8(recv_buffer) != 0; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "UPLOAD_ONLY" + , "%s", (ul?"true":"false")); +#endif + set_upload_only(ul); + return; + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (extended_id == share_mode_msg) + { + if (!m_recv_buffer.packet_finished()) return; + if (m_recv_buffer.packet_size() != 3) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "SHARE_MODE" + , "ERROR: unexpected packet size: %d", m_recv_buffer.packet_size()); +#endif + return; + } + bool sm = aux::read_uint8(recv_buffer) != 0; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "SHARE_MODE" + , "%s", (sm?"true":"false")); +#endif + set_share_mode(sm); + return; + } +#endif // TORRENT_DISABLE_SHARE_MODE + + if (extended_id == holepunch_msg) + { + if (!m_recv_buffer.packet_finished()) return; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HOLEPUNCH"); +#endif + on_holepunch(); + return; + } + + if (extended_id == dont_have_msg) + { + if (!m_recv_buffer.packet_finished()) return; + if (m_recv_buffer.packet_size() != 6) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "DONT_HAVE" + , "ERROR: unexpected packet size: %d", m_recv_buffer.packet_size()); +#endif + return; + } + piece_index_t const piece(aux::read_int32(recv_buffer)); + incoming_dont_have(piece); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (m_recv_buffer.packet_finished()) + peer_log(peer_log_alert::incoming_message, "EXTENSION_MESSAGE" + , "msg: %d size: %d", extended_id, m_recv_buffer.packet_size()); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_extended(m_recv_buffer.packet_size() - 2, extended_id + , recv_buffer)) + return; + } +#endif + + disconnect(errors::invalid_message, operation_t::bittorrent, peer_error); + } + + void bt_peer_connection::on_extended_handshake() + { + if (!m_recv_buffer.packet_finished()) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + span recv_buffer = m_recv_buffer.get(); + + error_code ec; + int pos; + bdecode_node root = bdecode(recv_buffer.subspan(2), ec, &pos); + if (ec || root.type() != bdecode_node::dict_t) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "EXTENSION_MESSAGE" + , "invalid extended handshake. pos: %d %s" + , pos, print_error(ec).c_str()); + } +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "EXTENDED_HANDSHAKE" + , "%s", print_entry(root, true).c_str()); + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto i = m_extensions.begin(); + !m_extensions.empty() && i != m_extensions.end();) + { + // a false return value means that the extension + // isn't supported by the other end. So, it is removed. + if (!(*i)->on_extension_handshake(root)) + i = m_extensions.erase(i); + else + ++i; + } + if (is_disconnecting()) return; +#endif + + // upload_only + if (bdecode_node const m = root.dict_find_dict("m")) + { + m_upload_only_id = std::uint8_t(m.dict_find_int_value("upload_only", 0)); + m_holepunch_id = std::uint8_t(m.dict_find_int_value("ut_holepunch", 0)); + m_dont_have_id = std::uint8_t(m.dict_find_int_value("lt_donthave", 0)); + } + + // there is supposed to be a remote listen port + int const listen_port = int(root.dict_find_int_value("p")); + if (listen_port > 0 && peer_info_struct() != nullptr) + { + t->update_peer_port(listen_port, peer_info_struct(), peer_info::incoming); + received_listen_port(); + if (is_disconnecting()) return; + } + + // there should be a version too + // but where do we put that info? + + int const last_seen_complete = int(root.dict_find_int_value("complete_ago", -1)); + if (last_seen_complete >= 0) set_last_seen_complete(last_seen_complete); + + auto const client_info = root.dict_find_string_value("v"); + if (!client_info.empty()) + { + m_client_version = client_info.to_string(); + // the client name is supposed to be UTF-8 + verify_encoding(m_client_version); + } + + int const reqq = int(root.dict_find_int_value("reqq")); + if (reqq > 0) max_out_request_queue(reqq); + + if (root.dict_find_int_value("upload_only", 0)) + set_upload_only(true); + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_settings.get_bool(settings_pack::support_share_mode) + && root.dict_find_int_value("share_mode", 0)) + set_share_mode(true); +#endif + + auto const myip = root.dict_find_string_value("yourip"); + if (!myip.empty()) + { + if (myip.size() == std::tuple_size::value) + { + address_v4::bytes_type bytes; + std::copy(myip.begin(), myip.end(), bytes.begin()); + m_ses.set_external_address(local_endpoint() + , address_v4(bytes) + , aux::session_interface::source_peer, remote().address()); + } + else if (myip.size() == std::tuple_size::value) + { + address_v6::bytes_type bytes; + std::copy(myip.begin(), myip.end(), bytes.begin()); + address_v6 ipv6_address(bytes); + if (ipv6_address.is_v4_mapped()) + m_ses.set_external_address(local_endpoint() + , ipv6_address.to_v4() + , aux::session_interface::source_peer, remote().address()); + else + m_ses.set_external_address(local_endpoint() + , ipv6_address + , aux::session_interface::source_peer, remote().address()); + } + } + + // if we're finished and this peer is uploading only + // disconnect it + if (t->is_finished() && upload_only() + && m_settings.get_bool(settings_pack::close_redundant_connections) +#ifndef TORRENT_DISABLE_SHARE_MODE + && !t->share_mode() +#endif + ) + disconnect(errors::upload_upload_connection, operation_t::bittorrent); + + stats_counters().inc_stats_counter(counters::num_incoming_ext_handshake); + } + + bool bt_peer_connection::dispatch_message(int const received) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(received >= 0); + + // this means the connection has been closed already + if (associated_torrent().expired()) + { + received_bytes(0, received); + return false; + } + + span recv_buffer = m_recv_buffer.get(); + + TORRENT_ASSERT(int(recv_buffer.size()) >= 1); + int packet_type = static_cast(recv_buffer[0]); + + if (m_settings.get_bool(settings_pack::support_merkle_torrents) + && packet_type == 250) packet_type = msg_piece; + +#if TORRENT_USE_ASSERTS + std::int64_t const cur_payload_dl = statistics().last_payload_downloaded(); + std::int64_t const cur_protocol_dl = statistics().last_protocol_downloaded(); +#endif + + // call the handler for this packet type + switch (packet_type) + { + // original BitTorrent message + case msg_choke: on_choke(received); break; + case msg_unchoke: on_unchoke(received); break; + case msg_interested: on_interested(received); break; + case msg_not_interested: on_not_interested(received); break; + case msg_have: on_have(received); break; + case msg_bitfield: on_bitfield(received); break; + case msg_request: on_request(received); break; + case msg_piece: on_piece(received); break; + case msg_cancel: on_cancel(received); break; + + // DHT extension + case msg_dht_port: on_dht_port(received); break; + + // FAST extension messages + case msg_suggest_piece: on_suggest_piece(received); break; + case msg_have_all: on_have_all(received); break; + case msg_have_none: on_have_none(received); break; + case msg_reject_request: on_reject_request(received); break; + case msg_allowed_fast: on_allowed_fast(received); break; + case msg_extended: on_extended(received); break; + default: + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_unknown_message(m_recv_buffer.packet_size(), packet_type + , recv_buffer.subspan(1))) + return m_recv_buffer.packet_finished(); + } +#endif + received_bytes(0, received); + disconnect(errors::invalid_message, operation_t::bittorrent); + return m_recv_buffer.packet_finished(); + } + } + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(statistics().last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(statistics().last_protocol_downloaded() - cur_protocol_dl >= 0); + std::int64_t const stats_diff = statistics().last_payload_downloaded() + - cur_payload_dl + statistics().last_protocol_downloaded() + - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == received); +#endif + + bool const finished = m_recv_buffer.packet_finished(); + + if (finished) + { + // count this packet in the session stats counters + int const counter = (packet_type <= msg_dht_port) + ? counters::num_incoming_choke + packet_type + : (packet_type <= msg_allowed_fast) + ? counters::num_incoming_suggest + packet_type + : counters::num_incoming_extended; + + stats_counters().inc_stats_counter(counter); + } + + return finished; + } + + void bt_peer_connection::write_upload_only(bool const enabled) + { + INVARIANT_CHECK; + +#if TORRENT_USE_ASSERTS && !defined TORRENT_DISABLE_SHARE_MODE + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(!t->share_mode()); +#endif + + if (m_upload_only_id == 0) return; + + // if we send upload-only, the other end is very likely to disconnect + // us, at least if it's a seed. If we don't want to close redundant + // connections, don't sent upload-only + if (!m_settings.get_bool(settings_pack::close_redundant_connections)) return; + + char msg[7] = {0, 0, 0, 3, msg_extended}; + char* ptr = msg + 5; + detail::write_uint8(m_upload_only_id, ptr); + detail::write_uint8(enabled, ptr); + send_buffer(msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void bt_peer_connection::write_share_mode() + { + INVARIANT_CHECK; + + std::shared_ptr t = associated_torrent().lock(); + if (m_share_mode_id == 0) return; + + char msg[7] = {0, 0, 0, 3, msg_extended}; + char* ptr = msg + 5; + detail::write_uint8(m_share_mode_id, ptr); + detail::write_uint8(t->share_mode(), ptr); + send_buffer(msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); + } +#endif + + void bt_peer_connection::write_keepalive() + { + INVARIANT_CHECK; + + // Don't require the bitfield to have been sent at this point + // the case where m_sent_bitfield may not be true is if the + // torrent doesn't have any metadata, and a peer is timing out. + // then the keep-alive message will be sent before the bitfield + // this is a violation to the original protocol, but necessary + // for the metadata extension. + TORRENT_ASSERT(m_sent_handshake); + + static const char msg[] = {0,0,0,0}; + send_buffer(msg); + } + + void bt_peer_connection::write_cancel(peer_request const& r) + { + INVARIANT_CHECK; + + send_message(msg_cancel, counters::num_outgoing_cancel + , static_cast(r.piece), r.start, r.length); + + if (!m_supports_fast) incoming_reject_request(r); + } + + void bt_peer_connection::write_request(peer_request const& r) + { + INVARIANT_CHECK; + + send_message(msg_request, counters::num_outgoing_request + , static_cast(r.piece), r.start, r.length); + } + + void bt_peer_connection::write_bitfield() + { + INVARIANT_CHECK; + + // if we have not received the other peer's extension bits yet, how do we + // know whether to send a have-all or have-none? + TORRENT_ASSERT(m_state >= state_t::read_peer_id); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(t->valid_metadata()); + +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (t->super_seeding()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "BITFIELD", "not sending bitfield, super seeding"); +#endif + if (m_supports_fast) write_have_none(); + + // if we are super seeding, pretend to not have any piece + // and don't send a bitfield + m_sent_bitfield = true; + + // bootstrap super-seeding by sending two have message + piece_index_t piece = t->get_piece_to_super_seed(get_bitfield()); + if (piece >= piece_index_t(0)) superseed_piece(piece_index_t(-1), piece); + piece = t->get_piece_to_super_seed(get_bitfield()); + if (piece >= piece_index_t(0)) superseed_piece(piece_index_t(-1), piece); + return; + } + else +#endif + if (m_supports_fast && t->is_seed()) + { + write_have_all(); + return; + } + else if (m_supports_fast && t->num_have() == 0) + { + write_have_none(); + return; + } + else if (t->num_have() == 0) + { + // don't send a bitfield if we don't have any pieces +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "BITFIELD", "not sending bitfield, have none"); +#endif + m_sent_bitfield = true; + return; + } + + const int num_pieces = t->torrent_file().num_pieces(); + TORRENT_ASSERT(num_pieces > 0); + + constexpr std::uint8_t char_bit_mask = CHAR_BIT - 1; + constexpr std::uint8_t char_top_bit = 1 << (CHAR_BIT - 1); + + const int packet_size = (num_pieces + char_bit_mask) / CHAR_BIT + 5; + + TORRENT_ALLOCA(msg, char, packet_size); + if (msg.data() == nullptr) return; // out of memory + auto ptr = msg.begin(); + + detail::write_int32(packet_size - 4, ptr); + detail::write_uint8(msg_bitfield, ptr); + + if (t->is_seed()) + { + std::fill_n(ptr, packet_size - 5, std::uint8_t{0xff}); + + // Clear trailing bits + msg.back() = static_cast((0xff << ((CHAR_BIT - (num_pieces & char_bit_mask)) & char_bit_mask)) & 0xff); + } + else + { + std::memset(ptr, 0, aux::numeric_cast(packet_size - 5)); + piece_picker const& p = t->picker(); + int mask = char_top_bit; + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + if (p.have_piece(i)) *ptr |= mask; + mask >>= 1; + if (mask == 0) + { + mask = char_top_bit; + ++ptr; + } + } + } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // add predictive pieces to the bitfield as well, since we won't + // announce them again + for (piece_index_t const p : t->predictive_pieces()) + msg[5 + static_cast(p) / CHAR_BIT] |= (char_top_bit >> (static_cast(p) & char_bit_mask)); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + std::string bitfield_string; + std::size_t const n_pieces = aux::numeric_cast(num_pieces); + bitfield_string.resize(n_pieces); + for (std::size_t k = 0; k < n_pieces; ++k) + { + if (msg[5 + int(k) / CHAR_BIT] & (char_top_bit >> (k % CHAR_BIT))) bitfield_string[k] = '1'; + else bitfield_string[k] = '0'; + } + peer_log(peer_log_alert::outgoing_message, "BITFIELD" + , "%s", bitfield_string.c_str()); + } +#endif + m_sent_bitfield = true; + + send_buffer(msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_bitfield); + } + + void bt_peer_connection::write_extensions() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_supports_extensions); + TORRENT_ASSERT(m_sent_handshake); + + entry handshake; + entry::dictionary_type& m = handshake["m"].dict(); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // if we're using a proxy, our listen port won't be useful + // anyway. + if (is_outgoing()) + { + auto const port = m_ses.listen_port( + t->is_ssl_torrent() ? aux::transport::ssl : aux::transport::plaintext + , local_endpoint().address()); + if (port != 0) handshake["p"] = port; + } + + // only send the port in case we bade the connection + // on incoming connections the other end already knows + // our listen port + if (!m_settings.get_bool(settings_pack::anonymous_mode)) + { + handshake["v"] = m_settings.get_str(settings_pack::handshake_client_version).empty() + ? m_settings.get_str(settings_pack::user_agent) + : m_settings.get_str(settings_pack::handshake_client_version); + } + + std::string remote_address; + std::back_insert_iterator out(remote_address); + detail::write_address(remote().address(), out); +#if TORRENT_USE_I2P + if (!is_i2p(*get_socket())) +#endif + handshake["yourip"] = remote_address; + handshake["reqq"] = m_settings.get_int(settings_pack::max_allowed_in_request_queue); + + m["upload_only"] = upload_only_msg; + m["ut_holepunch"] = holepunch_msg; +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_settings.get_bool(settings_pack::support_share_mode)) + m["share_mode"] = share_mode_msg; +#endif + m["lt_donthave"] = dont_have_msg; + + int complete_ago = -1; + if (t->last_seen_complete() > 0) complete_ago = t->time_since_complete(); + handshake["complete_ago"] = complete_ago; + + // if we're super seeding, don't say we're upload only, since it might + // make peers disconnect. don't tell anyone we're upload only when in + // share mode, we want to stay connected to seeds. if we're super seeding, + // we don't want to make peers think that we only have a single piece and + // is upload only, since they might disconnect immediately when they have + // downloaded a single piece, although we'll make another piece available. + // If we don't have metadata, we also need to suppress saying we're + // upload-only. If we do, we may be disconnected before we receive the + // metadata. + if (t->is_upload_only() +#ifndef TORRENT_DISABLE_SHARE_MODE + && !t->share_mode() +#endif + && t->valid_metadata() +#ifndef TORRENT_DISABLE_SUPERSEEDING + && !t->super_seeding() +#endif + ) + { + handshake["upload_only"] = 1; + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_settings.get_bool(settings_pack::support_share_mode) + && t->share_mode()) + handshake["share_mode"] = 1; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + // loop backwards, to make the first extension be the last + // to fill in the handshake (i.e. give the first extensions priority) + for (auto const& e : m_extensions) + { + e->add_handshake(handshake); + } +#endif + +#ifndef NDEBUG + // make sure there are not conflicting extensions + std::set ext; + for (entry::dictionary_type::const_iterator i = m.begin() + , end(m.end()); i != end; ++i) + { + if (i->second.type() != entry::int_t) continue; + int val = int(i->second.integer()); + TORRENT_ASSERT(ext.find(val) == ext.end()); + ext.insert(val); + } +#endif + + std::vector dict_msg; + bencode(std::back_inserter(dict_msg), handshake); + + char msg[6]; + char* ptr = msg; + + // write the length of the message + detail::write_int32(int(dict_msg.size()) + 2, ptr); + detail::write_uint8(msg_extended, ptr); + // signal handshake message + detail::write_uint8(0, ptr); + send_buffer(msg); + send_buffer(dict_msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_ext_handshake); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "EXTENDED_HANDSHAKE" + , "%s", handshake.to_string(true).c_str()); + } +#endif + } + + void bt_peer_connection::write_choke() + { + INVARIANT_CHECK; + + if (is_choked()) return; + send_message(msg_choke, counters::num_outgoing_choke); + } + + void bt_peer_connection::write_unchoke() + { + INVARIANT_CHECK; + + send_message(msg_unchoke, counters::num_outgoing_unchoke); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + e->sent_unchoke(); + } +#endif + } + + void bt_peer_connection::write_interested() + { + INVARIANT_CHECK; + + send_message(msg_interested, counters::num_outgoing_interested); + } + + void bt_peer_connection::write_not_interested() + { + INVARIANT_CHECK; + + send_message(msg_not_interested, counters::num_outgoing_not_interested); + } + + void bt_peer_connection::write_have(piece_index_t const index) + { + INVARIANT_CHECK; + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < associated_torrent().lock()->torrent_file().end_piece()); + + // if we haven't sent the bitfield yet, this piece should be included in + // there instead + if (!m_sent_bitfield) return; + + send_message(msg_have, counters::num_outgoing_have + , static_cast(index)); + } + + void bt_peer_connection::write_dont_have(piece_index_t const index) + { + INVARIANT_CHECK; + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < associated_torrent().lock()->torrent_file().end_piece()); + + if (in_handshake()) return; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + if (!m_supports_extensions || m_dont_have_id == 0) return; + + char msg[] = {0,0,0,6,msg_extended,char(m_dont_have_id),0,0,0,0}; + char* ptr = msg + 6; + detail::write_int32(static_cast(index), ptr); + send_buffer(msg); + + stats_counters().inc_stats_counter(counters::num_outgoing_extended); + } + + void bt_peer_connection::write_piece(peer_request const& r, disk_buffer_holder buffer) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_sent_handshake); + TORRENT_ASSERT(m_sent_bitfield); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + bool merkle = t->torrent_file().is_merkle_torrent() && r.start == 0; + // the hash piece looks like this: + // uint8_t msg + // uint32_t piece index + // uint32_t start + // uint32_t list len + // var bencoded list + // var piece data + char msg[4 + 1 + 4 + 4 + 4]; + char* ptr = msg; + TORRENT_ASSERT(r.length <= 16 * 1024); + detail::write_int32(r.length + 1 + 4 + 4, ptr); + if (m_settings.get_bool(settings_pack::support_merkle_torrents) && merkle) + detail::write_uint8(250, ptr); + else + detail::write_uint8(msg_piece, ptr); + detail::write_int32(static_cast(r.piece), ptr); + detail::write_int32(r.start, ptr); + + // if this is a merkle torrent and the start offset + // is 0, we need to include the merkle node hashes + if (merkle) + { + std::vector piece_list_buf; + entry piece_list; + entry::list_type& l = piece_list.list(); + std::map merkle_node_list = t->torrent_file().build_merkle_list(r.piece); + l.reserve(merkle_node_list.size()); + for (auto const& i : merkle_node_list) + { + l.emplace_back(entry::list_t); + l.back().list().emplace_back(i.first); + l.back().list().emplace_back(i.second.to_string()); + } + bencode(std::back_inserter(piece_list_buf), piece_list); + detail::write_int32(int(piece_list_buf.size()), ptr); + + // back-patch the length field + char* ptr2 = msg; + detail::write_int32(r.length + 1 + 4 + 4 + 4 + int(piece_list_buf.size()) + , ptr2); + + send_buffer({msg, 17}); + send_buffer(piece_list_buf); + } + else + { + send_buffer({msg, 13}); + } + + if (buffer.is_mutable()) + { + append_send_buffer(std::move(buffer), r.length); + } + else + { + append_const_send_buffer(std::move(buffer), r.length); + } + + m_payloads.emplace_back(send_buffer_size() - r.length, r.length); + setup_send(); + + stats_counters().inc_stats_counter(counters::num_outgoing_piece); + + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle(), + remote(), pid(), r.start / t->block_size() , r.piece); + } + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + void bt_peer_connection::on_receive(error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) + { + received_bytes(0, int(bytes_transferred)); + return; + } + + // make sure are much as possible of the response ends up in the same + // packet, or at least back-to-back packets + cork c_(*this); + +#if !defined TORRENT_DISABLE_ENCRYPTION + if (!m_enc_handler.is_recv_plaintext()) + { + int const consumed = m_enc_handler.decrypt(m_recv_buffer, bytes_transferred); +#ifndef TORRENT_DISABLE_LOGGING + if (consumed + int(bytes_transferred) > 0) + peer_log(peer_log_alert::incoming_message, "ENCRYPTION" + , "decrypted block s = %d", consumed + int(bytes_transferred)); +#endif + if (bytes_transferred == SIZE_MAX) + { + disconnect(errors::parse_failed, operation_t::encryption); + return; + } + received_bytes(0, consumed); + + // don't accept packets larger than 1 MB with a 1KB allowance for headers + if (!m_recv_buffer.crypto_packet_finished() + && m_recv_buffer.crypto_packet_size() > 1025 * 1024) + { + disconnect(errors::packet_too_large, operation_t::encryption, peer_error); + return; + } + + int sub_transferred = 0; + while (bytes_transferred > 0 && + ((sub_transferred = m_recv_buffer.advance_pos(int(bytes_transferred))) > 0)) + { +#if TORRENT_USE_ASSERTS + std::int64_t const cur_payload_dl = m_statistics.last_payload_downloaded(); + std::int64_t const cur_protocol_dl = m_statistics.last_protocol_downloaded(); +#endif + TORRENT_ASSERT(sub_transferred > 0); + on_receive_impl(std::size_t(sub_transferred)); + bytes_transferred -= std::size_t(sub_transferred); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_statistics.last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(m_statistics.last_protocol_downloaded() - cur_protocol_dl >= 0); + std::int64_t const stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + + m_statistics.last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == sub_transferred); +#endif + + if (m_disconnecting) return; + } + } + else +#endif + on_receive_impl(bytes_transferred); + } + + void bt_peer_connection::on_receive_impl(std::size_t bytes_transferred) + { + std::shared_ptr t = associated_torrent().lock(); + + span recv_buffer = m_recv_buffer.get(); + +#if !defined TORRENT_DISABLE_ENCRYPTION + // m_state is set to read_pe_dhkey in initial state + // (read_protocol_identifier) for incoming, or in constructor + // for outgoing + if (m_state == state_t::read_pe_dhkey) + { + received_bytes(0, int(bytes_transferred)); + + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(m_recv_buffer.packet_size() == dh_key_len); + TORRENT_ASSERT(recv_buffer.data() == m_recv_buffer.get().data()); + TORRENT_ASSERT(recv_buffer.size() == m_recv_buffer.get().size()); + + if (!m_recv_buffer.packet_finished()) return; + + // write our dh public key. m_dh_key_exchange is + // initialized in write_pe1_2_dhkey() + if (!is_outgoing()) write_pe1_2_dhkey(); + if (is_disconnecting()) return; + + // read dh key, generate shared secret + m_dh_key_exchange->compute_secret( + reinterpret_cast(recv_buffer.data())); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "received DH key"); +#endif + + // PadA/B can be a max of 512 bytes, and 20 bytes more for + // the sync hash (if incoming), or 8 bytes more for the + // encrypted verification constant (if outgoing). Instead + // of requesting the maximum possible, request the maximum + // possible to ensure we do not overshoot the standard + // handshake. + + if (is_outgoing()) + { + m_state = state_t::read_pe_syncvc; + write_pe3_sync(); + + // initial payload is the standard handshake, this is + // always rc4 if sent here. m_rc4_encrypted is flagged + // again according to peer selection. + switch_send_crypto(m_rc4); + write_handshake(); + switch_send_crypto(std::shared_ptr()); + + // vc,crypto_select,len(pad),pad, encrypt(handshake) + // 8+4+2+0+handshake_len + m_recv_buffer.reset(8+4+2+0+handshake_len); + } + else + { + // already written dh key + m_state = state_t::read_pe_synchash; + // synchash,skeyhash,vc,crypto_provide,len(pad),pad,encrypt(handshake) + m_recv_buffer.reset(20+20+8+4+2+0+handshake_len); + } + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + return; + } + + // cannot fall through into + if (m_state == state_t::read_pe_synchash) + { + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(recv_buffer.data() == m_recv_buffer.get().data()); + TORRENT_ASSERT(recv_buffer.size() == m_recv_buffer.get().size()); + + if (int(recv_buffer.size()) < 20) + { + received_bytes(0, int(bytes_transferred)); + + if (m_recv_buffer.packet_finished()) + disconnect(errors::sync_hash_not_found, operation_t::bittorrent, failure); + return; + } + + if (!m_sync_hash) + { + TORRENT_ASSERT(m_sync_bytes_read == 0); + + static char const req1[4] = {'r', 'e', 'q', '1'}; + // compute synchash (hash('req1',S)) + std::array const buffer = export_key(m_dh_key_exchange->get_secret()); + hasher h(req1); + h.update(buffer); + m_sync_hash.reset(new sha1_hash(h.final())); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ENCRYPTION" + , "looking for synchash %s secret: %s" + , aux::to_hex(*m_sync_hash).c_str() + , aux::to_hex(buffer).c_str()); + } +#endif + } + + int const syncoffset = search(*m_sync_hash, recv_buffer); + + // No sync + if (syncoffset == -1) + { + received_bytes(0, int(bytes_transferred)); + + int const bytes_processed = int(recv_buffer.size()) - 20; + m_sync_bytes_read += bytes_processed; + if (m_sync_bytes_read >= 512) + { + disconnect(errors::sync_hash_not_found, operation_t::encryption, failure); + return; + } + + m_recv_buffer.cut(bytes_processed, std::min(m_recv_buffer.packet_size() + , (512 + 20) - m_sync_bytes_read)); + + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + return; + } + // found complete sync + else + { + int const bytes_processed = syncoffset + 20; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "sync point (hash) found at offset %d" + , m_sync_bytes_read + bytes_processed - 20); +#endif + m_state = state_t::read_pe_skey_vc; + // skey,vc - 28 bytes + m_sync_hash.reset(); + int const transferred_used = bytes_processed + - aux::numeric_cast(recv_buffer.size()) + + aux::numeric_cast(bytes_transferred); + TORRENT_ASSERT(transferred_used >= 0); + TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); + received_bytes(0, transferred_used); + bytes_transferred -= std::size_t(transferred_used); + m_recv_buffer.cut(bytes_processed, 28); + } + } + + if (m_state == state_t::read_pe_skey_vc) + { + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(m_recv_buffer.packet_size() == 28); + + if (!m_recv_buffer.packet_finished()) return; + if (is_disconnecting()) return; + TORRENT_ASSERT(!is_disconnecting()); + + recv_buffer = m_recv_buffer.get(); + + TORRENT_ASSERT(!is_disconnecting()); + + sha1_hash ih(recv_buffer.data()); + torrent const* ti = m_ses.find_encrypted_torrent(ih, m_dh_key_exchange->get_hash_xor_mask()); + + if (ti) + { + if (!t) + { + attach_to_torrent(ti->info_hash()); + if (is_disconnecting()) return; + TORRENT_ASSERT(!is_disconnecting()); + + t = associated_torrent().lock(); + TORRENT_ASSERT(t); + } + + m_rc4 = init_pe_rc4_handler(m_dh_key_exchange->get_secret() + , ti->info_hash(), is_outgoing()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "computed RC4 keys"); + peer_log(peer_log_alert::info, "ENCRYPTION", "stream key found, torrent located"); +#endif + } + + if (!m_rc4) + { + disconnect(errors::invalid_info_hash, operation_t::bittorrent, failure); + return; + } + + // verify constant + rc4_decrypt(m_recv_buffer.mutable_buffer().subspan(20, 8)); + + static const char sh_vc[] = {0,0,0,0, 0,0,0,0}; + if (!std::equal(sh_vc, sh_vc + 8, recv_buffer.begin() + 20)) + { + disconnect(errors::invalid_encryption_constant, operation_t::encryption, peer_error); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "verification constant found"); +#endif + m_state = state_t::read_pe_cryptofield; + m_recv_buffer.reset(4 + 2); + } + + // cannot fall through into + if (m_state == state_t::read_pe_syncvc) + { + TORRENT_ASSERT(is_outgoing()); + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(recv_buffer.data() == m_recv_buffer.get().data()); + TORRENT_ASSERT(recv_buffer.size() == m_recv_buffer.get().size()); + + if (int(recv_buffer.size()) < 8) + { + received_bytes(0, int(bytes_transferred)); + if (m_recv_buffer.packet_finished()) + disconnect(errors::invalid_encryption_constant, operation_t::encryption, peer_error); + return; + } + + // generate the verification constant + if (!m_sync_vc) + { + TORRENT_ASSERT(m_sync_bytes_read == 0); + + m_sync_vc.reset(new (std::nothrow) char[8]); + if (!m_sync_vc) + { + disconnect(errors::no_memory, operation_t::encryption); + return; + } + std::fill(m_sync_vc.get(), m_sync_vc.get() + 8, char{0}); + rc4_decrypt({m_sync_vc.get(), 8}); + } + + TORRENT_ASSERT(m_sync_vc); + int const syncoffset = search({m_sync_vc.get(), 8}, recv_buffer); + + // No sync + if (syncoffset == -1) + { + int const bytes_processed = int(recv_buffer.size()) - 8; + m_sync_bytes_read += bytes_processed; + received_bytes(0, int(bytes_transferred)); + + if (m_sync_bytes_read >= 512) + { + disconnect(errors::invalid_encryption_constant, operation_t::encryption, peer_error); + return; + } + + m_recv_buffer.cut(bytes_processed, std::min(m_recv_buffer.packet_size() + , (512 + 8) - m_sync_bytes_read)); + + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + } + // found complete sync + else + { + int const bytes_processed = syncoffset + 8; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "sync point (verification constant) found at offset %d" + , m_sync_bytes_read + bytes_processed - 8); +#endif + int const transferred_used = bytes_processed + - aux::numeric_cast(recv_buffer.size()) + + aux::numeric_cast(bytes_transferred); + TORRENT_ASSERT(transferred_used >= 0); + TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); + received_bytes(0, transferred_used); + bytes_transferred -= std::size_t(transferred_used); + + m_recv_buffer.cut(bytes_processed, 4 + 2); + + // delete verification constant + m_sync_vc.reset(); + m_state = state_t::read_pe_cryptofield; + // fall through + } + } + + if (m_state == state_t::read_pe_cryptofield) // local/remote + { + TORRENT_ASSERT(!m_encrypted); + TORRENT_ASSERT(!m_rc4_encrypted); + TORRENT_ASSERT(m_recv_buffer.packet_size() == 4+2); + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + + if (!m_recv_buffer.packet_finished()) return; + + rc4_decrypt(m_recv_buffer.mutable_buffer().first( + m_recv_buffer.packet_size())); + + recv_buffer = m_recv_buffer.get(); + + std::uint32_t crypto_field = aux::read_uint32(recv_buffer); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "crypto %s : [%s%s ]" + , is_outgoing() ? "select" : "provide" + , (crypto_field & 1) ? " plaintext" : "" + , (crypto_field & 2) ? " rc4" : ""); +#endif + + if (!is_outgoing()) + { + // select a crypto method + int allowed_encryption = m_settings.get_int(settings_pack::allowed_enc_level); + std::uint32_t crypto_select = crypto_field & std::uint32_t(allowed_encryption); + + // when prefer_rc4 is set, keep the most significant bit + // otherwise keep the least significant one + if (m_settings.get_bool(settings_pack::prefer_rc4)) + { + std::uint32_t mask = std::numeric_limits::max(); + while (crypto_select & (mask << 1)) + { + mask <<= 1; + crypto_select = crypto_select & mask; + } + } + else + { + std::uint32_t mask = std::numeric_limits::max(); + while (crypto_select & (mask >> 1)) + { + mask >>= 1; + crypto_select = crypto_select & mask; + } + } + + if (crypto_select == 0) + { + disconnect(errors::unsupported_encryption_mode, operation_t::encryption, failure); + return; + } + + // write the pe4 step + write_pe4_sync(aux::numeric_cast(crypto_select)); + } + else // is_outgoing() + { + // check if crypto select is valid + int allowed_encryption = m_settings.get_int(settings_pack::allowed_enc_level); + + crypto_field &= std::uint32_t(allowed_encryption); + if (crypto_field == 0) + { + // we don't allow any of the offered encryption levels + disconnect(errors::unsupported_encryption_mode_selected, operation_t::encryption, peer_error); + return; + } + + if (crypto_field == settings_pack::pe_plaintext) + m_rc4_encrypted = false; + else if (crypto_field == settings_pack::pe_rc4) + m_rc4_encrypted = true; + } + + int const len_pad = aux::read_int16(recv_buffer); + if (len_pad < 0 || len_pad > 512) + { + disconnect(errors::invalid_pad_size, operation_t::encryption, peer_error); + return; + } + + m_state = state_t::read_pe_pad; + if (!is_outgoing()) + m_recv_buffer.reset(len_pad + 2); // len(IA) at the end of pad + else + { + if (len_pad == 0) + { + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_state = state_t::init_bt_handshake; + } + else + m_recv_buffer.reset(len_pad); + } + } + + if (m_state == state_t::read_pe_pad) + { + TORRENT_ASSERT(!m_encrypted); + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + if (!m_recv_buffer.packet_finished()) return; + + int const pad_size = is_outgoing() ? m_recv_buffer.packet_size() : m_recv_buffer.packet_size() - 2; + + rc4_decrypt(m_recv_buffer.mutable_buffer().first(m_recv_buffer.packet_size())); + + recv_buffer = m_recv_buffer.get(); + + if (!is_outgoing()) + { + recv_buffer = recv_buffer.subspan(pad_size); + int const len_ia = aux::read_int16(recv_buffer); + + if (len_ia < 0) + { + disconnect(errors::invalid_encrypt_handshake, operation_t::encryption, peer_error); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "len(IA) : %d", len_ia); +#endif + if (len_ia == 0) + { + // everything after this is Encrypt2 + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_state = state_t::init_bt_handshake; + } + else + { + m_state = state_t::read_pe_ia; + m_recv_buffer.reset(len_ia); + } + } + else // is_outgoing() + { + // everything that arrives after this is Encrypt2 + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_state = state_t::init_bt_handshake; + } + } + + if (m_state == state_t::read_pe_ia) + { + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + TORRENT_ASSERT(!is_outgoing()); + TORRENT_ASSERT(!m_encrypted); + + if (!m_recv_buffer.packet_finished()) return; + + // ia is always rc4, so decrypt it + rc4_decrypt(m_recv_buffer.mutable_buffer().first(m_recv_buffer.packet_size())); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "decrypted ia : %d bytes", m_recv_buffer.packet_size()); +#endif + + // everything that arrives after this is encrypted + m_encrypted = true; + if (m_rc4_encrypted) + { + switch_send_crypto(m_rc4); + switch_recv_crypto(m_rc4); + } + m_rc4.reset(); + + m_state = state_t::read_protocol_identifier; + m_recv_buffer.cut(0, 20); + } + + if (m_state == state_t::init_bt_handshake) + { + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + TORRENT_ASSERT(m_encrypted); + + // decrypt remaining received bytes + if (m_rc4_encrypted) + { + span const remaining = m_recv_buffer.mutable_buffer() + .subspan(m_recv_buffer.packet_size()); + rc4_decrypt(remaining); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "decrypted remaining %d bytes", int(remaining.size())); +#endif + } + m_rc4.reset(); + + // payload stream, start with 20 handshake bytes + m_state = state_t::read_protocol_identifier; + m_recv_buffer.reset(20); + + // encrypted portion of handshake completed, toggle + // peer_info pe_support flag back to true + if (is_outgoing() && + m_settings.get_int(settings_pack::out_enc_policy) + == settings_pack::pe_enabled) + { + torrent_peer* pi = peer_info_struct(); + TORRENT_ASSERT(pi); + + pi->pe_support = true; + } + } + +#endif // #if !defined TORRENT_DISABLE_ENCRYPTION + + if (m_state == state_t::read_protocol_identifier) + { + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + TORRENT_ASSERT(m_recv_buffer.packet_size() == 20); + + if (!m_recv_buffer.packet_finished()) return; + recv_buffer = m_recv_buffer.get(); + + int const packet_size = recv_buffer[0]; + static const char protocol_string[] = "\x13" "BitTorrent protocol"; + + if (packet_size != 19 || + recv_buffer.first(20) != span{protocol_string, 20}) + { +#if !defined TORRENT_DISABLE_ENCRYPTION +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "unrecognized protocol header"); +#endif + +#ifdef TORRENT_USE_OPENSSL + if (is_ssl(*get_socket())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION" + , "SSL peers are not allowed to use any other encryption"); +#endif + disconnect(errors::invalid_info_hash, operation_t::bittorrent, failure); + return; + } +#endif // TORRENT_USE_OPENSSL + + if (!is_outgoing() + && m_settings.get_int(settings_pack::in_enc_policy) + == settings_pack::pe_disabled) + { + disconnect(errors::no_incoming_encrypted, operation_t::bittorrent); + return; + } + + // Don't attempt to perform an encrypted handshake + // within an encrypted connection. For local connections, + // we're expected to already have passed the encrypted + // handshake by this point + if (m_encrypted || is_outgoing()) + { + disconnect(errors::invalid_info_hash, operation_t::bittorrent, failure); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ENCRYPTION", "attempting encrypted connection"); +#endif + m_state = state_t::read_pe_dhkey; + m_recv_buffer.cut(0, dh_key_len); + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + return; +#else + disconnect(errors::invalid_info_hash, operation_t::bittorrent, failure); + return; +#endif // TORRENT_DISABLE_ENCRYPTION + } + else + { +#if !defined TORRENT_DISABLE_ENCRYPTION + TORRENT_ASSERT(m_state != state_t::read_pe_dhkey); + + if (!is_outgoing() + && m_settings.get_int(settings_pack::in_enc_policy) + == settings_pack::pe_forced + && !m_encrypted + && !is_ssl(*get_socket())) + { + disconnect(errors::no_incoming_regular, operation_t::bittorrent); + return; + } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HANDSHAKE", "BitTorrent protocol"); +#endif + } + + m_state = state_t::read_info_hash; + m_recv_buffer.reset(28); + } + + // fall through + if (m_state == state_t::read_info_hash) + { + received_bytes(0, int(bytes_transferred)); + bytes_transferred = 0; + TORRENT_ASSERT(m_recv_buffer.packet_size() == 28); + + if (!m_recv_buffer.packet_finished()) return; + recv_buffer = m_recv_buffer.get(); + +#ifndef TORRENT_DISABLE_LOGGING + std::string extensions; + extensions.reserve(8 * 8); + for (int i = 0; i < 8; ++i) + for (int j = 0; j < 8; ++j) + extensions += (recv_buffer[i] & (0x80 >> j)) ? '1' : '0'; + + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "EXTENSIONS", "%s ext: %s%s%s" + , extensions.c_str() + , (recv_buffer[7] & 0x01) ? "DHT " : "" + , (recv_buffer[7] & 0x04) ? "FAST " : "" + , (recv_buffer[5] & 0x10) ? "extension " : ""); + } +#endif + + std::memcpy(m_reserved_bits.data(), recv_buffer.data(), 8); + if (recv_buffer[5] & 0x10) + m_supports_extensions = true; + + if (recv_buffer[7] & 0x01) + m_supports_dht_port = true; + + if (recv_buffer[7] & 0x04) + m_supports_fast = true; + + t = associated_torrent().lock(); + + // ok, now we have got enough of the handshake. Is this connection + // attached to a torrent? + if (!t) + { + // now, we have to see if there's a torrent with the + // info_hash we got from the peer + sha1_hash info_hash; + std::copy(recv_buffer.begin() + 8, recv_buffer.begin() + 28 + , info_hash.data()); + + attach_to_torrent(info_hash); + if (is_disconnecting()) return; + } + else + { + // verify info hash + if (!std::equal(recv_buffer.begin() + 8, recv_buffer.begin() + 28 + , t->torrent_file().info_hash().data())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR", "received invalid info_hash"); +#endif + disconnect(errors::invalid_info_hash, operation_t::bittorrent, failure); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "HANDSHAKE", "info_hash received"); +#endif + } + + t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // if this is a local connection, we have already + // sent the handshake + if (!is_outgoing()) write_handshake(); + TORRENT_ASSERT(m_sent_handshake); + + if (is_disconnecting()) return; + + m_state = state_t::read_peer_id; + m_recv_buffer.reset(20); + } + + // fall through + if (m_state == state_t::read_peer_id) + { + TORRENT_ASSERT(m_sent_handshake); + received_bytes(0, int(bytes_transferred)); + + t = associated_torrent().lock(); + if (!t) + { + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); // TODO + return; + } + TORRENT_ASSERT(m_recv_buffer.packet_size() == 20); + + if (!m_recv_buffer.packet_finished()) return; + recv_buffer = m_recv_buffer.get(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming)) + { + char hex_pid[41]; + aux::to_hex({recv_buffer.data(), 20}, hex_pid); + hex_pid[40] = 0; + char ascii_pid[21]; + ascii_pid[20] = 0; + for (int i = 0; i != 20; ++i) + ascii_pid[i] = (is_print(recv_buffer[i])) ? recv_buffer[i] : '.'; + + peer_log(peer_log_alert::incoming, "HANDSHAKE", "received peer_id: %s client: %s ascii: \"%s\"" + , hex_pid, identify_client(peer_id(recv_buffer.data())).c_str(), ascii_pid); + } +#endif + peer_id pid; + std::copy(recv_buffer.begin(), recv_buffer.begin() + 20, pid.data()); + + // now, let's see if this connection should be closed + peer_connection* p = t->find_peer(pid); + if (p) + { + TORRENT_ASSERT(p->pid() == pid); + // we found another connection with the same peer-id + // which connection should be closed in order to be + // sure that the other end closes the same connection? + // the peer with greatest peer-id is the one allowed to + // initiate connections. So, if our peer-id is greater than + // the others, we should close the incoming connection, + // if not, we should close the outgoing one. + if ((pid < m_our_peer_id) == is_outgoing()) + { + p->disconnect(errors::duplicate_peer_id, operation_t::bittorrent); + } + else + { + disconnect(errors::duplicate_peer_id, operation_t::bittorrent); + return; + } + } + + set_pid(pid); + m_client_version = identify_client(pid); + if (pid[0] == '-' && pid[1] == 'B' && pid[2] == 'C' && pid[7] == '-') + { + // if this is a bitcomet client, lower the request queue size limit + if (max_out_request_queue() > 50) max_out_request_queue(50); + } + + if (t->is_self_connection(pid)) + { + disconnect(errors::self_connection, operation_t::bittorrent); + return; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto i = m_extensions.begin() + , end(m_extensions.end()); i != end;) + { + if (!(*i)->on_handshake(m_reserved_bits)) + { + i = m_extensions.erase(i); + } + else + { + ++i; + } + } + if (is_disconnecting()) return; +#endif + + if (m_supports_extensions) write_extensions(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HANDSHAKE", "connection ready"); +#endif + // consider this a successful connection, reset the failcount + if (peer_info_struct()) + t->clear_failcount(peer_info_struct()); + +#if !defined TORRENT_DISABLE_ENCRYPTION + // Toggle pe_support back to false if this is a + // standard successful connection + if (is_outgoing() && !m_encrypted && + m_settings.get_int(settings_pack::out_enc_policy) + == settings_pack::pe_enabled) + { + torrent_peer* pi = peer_info_struct(); + TORRENT_ASSERT(pi); + + pi->pe_support = false; + } +#endif + + // complete the handshake + // we don't know how many pieces there are until we + // have the metadata + if (t->ready_for_connections()) + { + write_bitfield(); + write_dht_port(); + + // if we don't have any pieces, don't do any preemptive + // unchoking at all. + if (t->num_have() > 0) + { + // if the peer is ignoring unchoke slots, or if we have enough + // unused slots, unchoke this peer right away, to save a round-trip + // in case it's interested. + maybe_unchoke_this_peer(); + } + } + + m_state = state_t::read_packet_size; + m_recv_buffer.reset(5); + + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + return; + } + + // cannot fall through into + if (m_state == state_t::read_packet_size) + { + // Make sure this is not fallen though into + TORRENT_ASSERT(recv_buffer.data() == m_recv_buffer.get().data()); + TORRENT_ASSERT(recv_buffer.size() == m_recv_buffer.get().size()); + TORRENT_ASSERT(m_recv_buffer.packet_size() == 5); + + if (!t) return; + + // the 5th byte (if one) should not count as protocol + // byte here, instead it's counted in the message + // handler itself, for the specific message + TORRENT_ASSERT(bytes_transferred <= 5); + int used_bytes = int(recv_buffer.size()) > 4 ? int(bytes_transferred) - 1: int(bytes_transferred); + received_bytes(0, used_bytes); + bytes_transferred -= aux::numeric_cast(used_bytes); + if (int(recv_buffer.size()) < 4) return; + + TORRENT_ASSERT(bytes_transferred <= 1); + + const char* ptr = recv_buffer.data(); + int const packet_size = detail::read_int32(ptr); + + // don't accept packets larger than 1 MB + if (packet_size > 1024 * 1024 || packet_size < 0) + { + // packet too large + received_bytes(0, int(bytes_transferred)); + disconnect(errors::packet_too_large, operation_t::bittorrent, peer_error); + return; + } + + if (packet_size == 0) + { + TORRENT_ASSERT(bytes_transferred <= 1); + received_bytes(0, int(bytes_transferred)); + incoming_keepalive(); + if (is_disconnecting()) return; + // keepalive message + m_state = state_t::read_packet_size; + m_recv_buffer.cut(4, 5); + return; + } + if (int(recv_buffer.size()) < 5) return; + + m_state = state_t::read_packet; + m_recv_buffer.cut(4, packet_size); + recv_buffer = m_recv_buffer.get(); + TORRENT_ASSERT(int(recv_buffer.size()) == 1); + TORRENT_ASSERT(bytes_transferred == 1); + } + + if (m_state == state_t::read_packet) + { + TORRENT_ASSERT(recv_buffer.data() == m_recv_buffer.get().data()); + TORRENT_ASSERT(recv_buffer.size() == m_recv_buffer.get().size()); + if (!t) + { + received_bytes(0, int(bytes_transferred)); + disconnect(errors::torrent_removed, operation_t::bittorrent, failure); + return; + } +#if TORRENT_USE_ASSERTS + std::int64_t const cur_payload_dl = statistics().last_payload_downloaded(); + std::int64_t const cur_protocol_dl = statistics().last_protocol_downloaded(); +#endif + if (dispatch_message(int(bytes_transferred))) + { + m_state = state_t::read_packet_size; + m_recv_buffer.reset(5); + } + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(statistics().last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(statistics().last_protocol_downloaded() - cur_protocol_dl >= 0); + std::int64_t const stats_diff = statistics().last_payload_downloaded() - cur_payload_dl + + statistics().last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == std::int64_t(bytes_transferred)); + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); +#endif + return; + } + + TORRENT_ASSERT(!m_recv_buffer.packet_finished()); + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + std::tuple>> + bt_peer_connection::hit_send_barrier( + span> iovec) + { + int next_barrier; + span> out_iovec; + std::tie(next_barrier, out_iovec) = m_enc_handler.encrypt(iovec); +#ifndef TORRENT_DISABLE_LOGGING + if (next_barrier != 0) + peer_log(peer_log_alert::outgoing, "SEND_BARRIER" + , "encrypted block s = %d", next_barrier); +#endif + return std::make_tuple(next_barrier, out_iovec); + } +#endif + + // -------------------------- + // SEND DATA + // -------------------------- + + void bt_peer_connection::on_sent(error_code const& error + , std::size_t const bytes_transferred) + { + INVARIANT_CHECK; + + if (error) + { + sent_bytes(0, int(bytes_transferred)); + return; + } + + // manage the payload markers + int amount_payload = 0; + if (!m_payloads.empty()) + { + // this points to the first entry to not erase. i.e. + // [begin, first_to_keep) will be erased because + // the payload ranges they represent have been sent + auto first_to_keep = m_payloads.begin(); + + for (auto i = m_payloads.begin(); i != m_payloads.end(); ++i) + { + i->start -= int(bytes_transferred); + if (i->start < 0) + { + if (i->start + i->length <= 0) + { + amount_payload += i->length; + TORRENT_ASSERT(first_to_keep == i); + ++first_to_keep; + } + else + { + amount_payload += -i->start; + i->length -= -i->start; + i->start = 0; + } + } + } + + // remove all payload ranges that have been sent + m_payloads.erase(m_payloads.begin(), first_to_keep); + } + + TORRENT_ASSERT(amount_payload <= int(bytes_transferred)); + sent_bytes(amount_payload, int(bytes_transferred) - amount_payload); + + if (amount_payload > 0) + { + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + if (t) t->update_last_upload(); + } + } + +#if TORRENT_USE_INVARIANT_CHECKS + void bt_peer_connection::check_invariant() const + { + std::shared_ptr t = associated_torrent().lock(); + +#if !defined TORRENT_DISABLE_ENCRYPTION + TORRENT_ASSERT( (bool(m_state != state_t::read_pe_dhkey) || m_dh_key_exchange.get()) + || !is_outgoing()); + + TORRENT_ASSERT(!m_rc4_encrypted || (!m_encrypted && m_rc4) + || (m_encrypted && !m_enc_handler.is_send_plaintext())); +#endif + if (!in_handshake()) + { + TORRENT_ASSERT(m_sent_handshake); + } + + if (!m_payloads.empty()) + { + for (std::vector::const_iterator i = m_payloads.begin(); + i != m_payloads.end() - 1; ++i) + { + TORRENT_ASSERT(i->start + i->length <= (i+1)->start); + } + } + } +#endif + +} diff --git a/src/chained_buffer.cpp b/src/chained_buffer.cpp new file mode 100644 index 0000000..a8fe153 --- /dev/null +++ b/src/chained_buffer.cpp @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/chained_buffer.hpp" +#include "libtorrent/assert.hpp" + +#include // for copy + +namespace libtorrent { + + void chained_buffer::pop_front(int bytes_to_pop) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_destructed); + TORRENT_ASSERT(bytes_to_pop <= m_bytes); + while (bytes_to_pop > 0 && !m_vec.empty()) + { + buffer_t& b = m_vec.front(); + if (b.used_size > bytes_to_pop) + { + b.buf += bytes_to_pop; + b.used_size -= bytes_to_pop; + b.size -= bytes_to_pop; + m_capacity -= bytes_to_pop; + m_bytes -= bytes_to_pop; + TORRENT_ASSERT(m_bytes <= m_capacity); + TORRENT_ASSERT(m_bytes >= 0); + TORRENT_ASSERT(m_capacity >= 0); + break; + } + + b.destruct_holder(static_cast(&b.holder)); + m_bytes -= b.used_size; + m_capacity -= b.size; + bytes_to_pop -= b.used_size; + TORRENT_ASSERT(m_bytes >= 0); + TORRENT_ASSERT(m_capacity >= 0); + TORRENT_ASSERT(m_bytes <= m_capacity); + m_vec.pop_front(); + } + } + + // returns the number of bytes available at the + // end of the last chained buffer. + int chained_buffer::space_in_last_buffer() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_destructed); + if (m_vec.empty()) return 0; + buffer_t& b = m_vec.back(); + TORRENT_ASSERT(b.buf != nullptr); + return b.size - b.used_size; + } + + // tries to copy the given buffer to the end of the + // last chained buffer. If there's not enough room + // it returns nullptr + char* chained_buffer::append(span buf) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_destructed); + char* const insert = allocate_appendix(static_cast(buf.size())); + if (insert == nullptr) return nullptr; + std::copy(buf.begin(), buf.end(), insert); + return insert; + } + + // tries to allocate memory from the end + // of the last buffer. If there isn't + // enough room, returns 0 + char* chained_buffer::allocate_appendix(int const s) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_destructed); + if (m_vec.empty()) return nullptr; + buffer_t& b = m_vec.back(); + TORRENT_ASSERT(b.buf != nullptr); + char* const insert = b.buf + b.used_size; + if (insert + s > b.buf + b.size) return nullptr; + b.used_size += s; + m_bytes += s; + TORRENT_ASSERT(m_bytes <= m_capacity); + return insert; + } + + span chained_buffer::build_iovec(int const to_send) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_destructed); + m_tmp_vec.clear(); + build_vec(to_send, m_tmp_vec); + return m_tmp_vec; + } + + void chained_buffer::build_mutable_iovec(int bytes, std::vector> &vec) + { + TORRENT_ASSERT(!m_destructed); + build_vec(bytes, vec); + } + + template + void chained_buffer::build_vec(int bytes, std::vector& vec) + { + TORRENT_ASSERT(!m_destructed); + for (auto i = m_vec.begin(), end(m_vec.end()); bytes > 0 && i != end; ++i) + { + TORRENT_ASSERT(i->buf != nullptr); + if (i->used_size > bytes) + { + TORRENT_ASSERT(bytes > 0); + vec.emplace_back(i->buf, std::size_t(bytes)); + break; + } + TORRENT_ASSERT(i->used_size > 0); + vec.emplace_back(i->buf, std::size_t(i->used_size)); + bytes -= i->used_size; + } + } + + void chained_buffer::clear() + { + TORRENT_ASSERT(!m_destructed); + for (auto& b : m_vec) + b.destruct_holder(static_cast(&b.holder)); + m_bytes = 0; + m_capacity = 0; + m_vec.clear(); + } + + chained_buffer::~chained_buffer() + { + TORRENT_ASSERT(!m_destructed); + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_bytes >= 0); + TORRENT_ASSERT(m_capacity >= 0); + clear(); +#if TORRENT_USE_ASSERTS + m_destructed = true; +#endif + } + +} diff --git a/src/choker.cpp b/src/choker.cpp new file mode 100644 index 0000000..d54e5ae --- /dev/null +++ b/src/choker.cpp @@ -0,0 +1,387 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/choker.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/torrent.hpp" + +#include + +using namespace std::placeholders; + +namespace libtorrent { + +namespace { + + int compare_peers(peer_connection const* lhs, peer_connection const* rhs) + { + int const prio1 = lhs->get_priority(peer_connection::upload_channel); + int const prio2 = rhs->get_priority(peer_connection::upload_channel); + + if (prio1 != prio2) return prio1 > prio2 ? 1 : -1; + + // compare how many bytes they've sent us + std::int64_t const c1 = lhs->downloaded_in_last_round(); + std::int64_t const c2 = rhs->downloaded_in_last_round(); + + if (c1 != c2) return c1 > c2 ? 1 : -1; + return 0; + } + + // return true if 'lhs' peer should be preferred to be unchoke over 'rhs' + bool unchoke_compare_rr(peer_connection const* lhs + , peer_connection const* rhs, int pieces) + { + int const cmp = compare_peers(lhs, rhs); + if (cmp != 0) return cmp > 0; + + // when seeding, rotate which peer is unchoked in a round-robin fasion + + // the amount uploaded since unchoked (not just in the last round) + std::int64_t const u1 = lhs->uploaded_since_unchoked(); + std::int64_t const u2 = rhs->uploaded_since_unchoked(); + + // the way the round-robin unchoker works is that it, + // by default, prioritizes any peer that is already unchoked. + // this maintain the status quo across unchoke rounds. However, + // peers that are unchoked, but have sent more than one quota + // since they were unchoked, they get de-prioritized. + + std::shared_ptr const t1 = lhs->associated_torrent().lock(); + std::shared_ptr const t2 = rhs->associated_torrent().lock(); + TORRENT_ASSERT(t1); + TORRENT_ASSERT(t2); + + // if a peer is already unchoked, the number of bytes sent since it was unchoked + // is greater than the send quanta, and it has been unchoked for at least one minute + // then it's done with its upload slot, and we can de-prioritize it + bool const c1_quota_complete = !lhs->is_choked() + && u1 > std::int64_t(t1->torrent_file().piece_length()) * pieces + && aux::time_now() - lhs->time_of_last_unchoke() > minutes(1); + bool const c2_quota_complete = !rhs->is_choked() + && u2 > std::int64_t(t2->torrent_file().piece_length()) * pieces + && aux::time_now() - rhs->time_of_last_unchoke() > minutes(1); + + // if c2 has completed a quanta, it should be de-prioritized + // and vice versa + if (c1_quota_complete != c2_quota_complete) + return int(c1_quota_complete) < int(c2_quota_complete); + + // when seeding, prefer the peer we're uploading the fastest to + + // force the upload rate to zero for choked peers because + // if the peers just got choked the previous round + // there may have been a residual transfer which was already + // in-flight at the time and we don't want that to cause the peer + // to be ranked at the top of the choked peers + std::int64_t const c1 = lhs->is_choked() ? 0 : lhs->uploaded_in_last_round(); + std::int64_t const c2 = rhs->is_choked() ? 0 : rhs->uploaded_in_last_round(); + + if (c1 != c2) return c1 > c2; + + // if the peers are still identical (say, they're both waiting to be unchoked) + // prioritize the one that has waited the longest to be unchoked + // the round-robin unchoker relies on this logic. Don't change it + // without moving this into that unchoker logic + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } + + // return true if 'lhs' peer should be preferred to be unchoke over 'rhs' + bool unchoke_compare_fastest_upload(peer_connection const* lhs + , peer_connection const* rhs) + { + int const cmp = compare_peers(lhs, rhs); + if (cmp != 0) return cmp > 0; + + // when seeding, prefer the peer we're uploading the fastest to + std::int64_t const c1 = lhs->uploaded_in_last_round(); + std::int64_t const c2 = rhs->uploaded_in_last_round(); + + if (c1 != c2) return c1 > c2; + + // prioritize the one that has waited the longest to be unchoked + // the round-robin unchoker relies on this logic. Don't change it + // without moving this into that unchoker logic + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } + + int anti_leech_score(peer_connection const* peer) + { + // the anti-leech seeding algorithm is based on the paper "Improving + // BitTorrent: A Simple Approach" from Chow et. al. and ranks peers based + // on how many pieces they have, preferring to unchoke peers that just + // started and peers that are close to completing. Like this: + // ^ + // | \ / | + // | \ / | + // | \ / | + // s | \ / | + // c | \ / | + // o | \ / | + // r | \ / | + // e | \ / | + // | \ / | + // | \ / | + // | \ / | + // | \ / | + // | V | + // +---------------------------+ + // 0% num have pieces 100% + std::shared_ptr const t = peer->associated_torrent().lock(); + TORRENT_ASSERT(t); + + std::int64_t const total_size = t->torrent_file().total_size(); + if (total_size == 0) return 0; + std::int64_t const have_size = std::max(peer->statistics().total_payload_upload() + , std::int64_t(t->torrent_file().piece_length()) * peer->num_have_pieces()); + return int(std::abs((have_size - total_size / 2) * 2000 / total_size)); + } + + // return true if 'lhs' peer should be preferred to be unchoke over 'rhs' + bool unchoke_compare_anti_leech(peer_connection const* lhs + , peer_connection const* rhs) + { + int const cmp = compare_peers(lhs, rhs); + if (cmp != 0) return cmp > 0; + + int const score1 = anti_leech_score(lhs); + int const score2 = anti_leech_score(rhs); + if (score1 != score2) return score1 > score2; + + // prioritize the one that has waited the longest to be unchoked + // the round-robin unchoker relies on this logic. Don't change it + // without moving this into that unchoker logic + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } + + bool upload_rate_compare(peer_connection const* lhs + , peer_connection const* rhs) + { + // take torrent priority into account + std::int64_t const c1 = lhs->uploaded_in_last_round() + * lhs->get_priority(peer_connection::upload_channel); + std::int64_t const c2 = rhs->uploaded_in_last_round() + * rhs->get_priority(peer_connection::upload_channel); + + return c1 > c2; + } + +#if TORRENT_ABI_VERSION == 1 + bool bittyrant_unchoke_compare(peer_connection const* lhs + , peer_connection const* rhs) + { + // first compare how many bytes they've sent us + std::int64_t d1 = lhs->downloaded_in_last_round(); + std::int64_t d2 = rhs->downloaded_in_last_round(); + // divided by the number of bytes we've sent them + std::int64_t const u1 = lhs->uploaded_in_last_round(); + std::int64_t const u2 = rhs->uploaded_in_last_round(); + + // take torrent priority into account + d1 *= lhs->get_priority(peer_connection::upload_channel); + d2 *= rhs->get_priority(peer_connection::upload_channel); + + d1 = d1 * 1000 / std::max(std::int64_t(1), u1); + d2 = d2 * 1000 / std::max(std::int64_t(1), u2); + if (d1 != d2) return d1 > d2; + + // if both peers are still in their send quota or not in their send quota + // prioritize the one that has waited the longest to be unchoked + return lhs->time_of_last_unchoke() < rhs->time_of_last_unchoke(); + } +#endif + + } // anonymous namespace + + int unchoke_sort(std::vector& peers + , int const max_upload_rate + , time_duration const unchoke_interval + , aux::session_settings const& sett) + { +#if TORRENT_USE_ASSERTS + for (auto p : peers) + { + TORRENT_ASSERT(p->self()); + TORRENT_ASSERT(p->associated_torrent().lock()); + } +#endif + +#if TORRENT_ABI_VERSION == 1 + // ==== BitTyrant ==== + // + // if we're using the bittyrant unchoker, go through all peers that + // we have unchoked already, and adjust our estimated reciprocation + // rate. If the peer has reciprocated, lower the estimate, if it hasn't, + // increase the estimate (this attempts to optimize "ROI" of upload + // capacity, by sending just enough to be reciprocated). + // For more information, see: http://bittyrant.cs.washington.edu/ + if (sett.get_int(settings_pack::choking_algorithm) + == settings_pack::bittyrant_choker) + { + for (auto const p : peers) + { + if (p->is_choked() || !p->is_interesting()) continue; + + if (!p->has_peer_choked()) + { + // we're unchoked, we may want to lower our estimated + // reciprocation rate + p->decrease_est_reciprocation_rate(); + } + else + { + // we've unchoked this peer, and it hasn't reciprocated + // we may want to increase our estimated reciprocation rate + p->increase_est_reciprocation_rate(); + } + } + + // if we're using the bittyrant choker, sort peers by their return + // on investment. i.e. download rate / upload rate + // TODO: use an incremental partial_sort() here + std::sort(peers.begin(), peers.end() + , std::bind(&bittyrant_unchoke_compare, _1, _2)); + + int upload_capacity_left = max_upload_rate; + + // now, figure out how many peers should be unchoked. We deduct the + // estimated reciprocation rate from our upload_capacity estimate + // until there none left + int upload_slots = 0; + + for (auto const p : peers) + { + TORRENT_ASSERT(p != nullptr); + + if (p->est_reciprocation_rate() > upload_capacity_left) break; + + ++upload_slots; + upload_capacity_left -= p->est_reciprocation_rate(); + } + + return upload_slots; + } +#else + TORRENT_UNUSED(max_upload_rate); +#endif + + int upload_slots = sett.get_int(settings_pack::unchoke_slots_limit); + if (upload_slots < 0) + upload_slots = std::numeric_limits::max(); + + // ==== rate-based ==== + // + // The rate based unchoker looks at our upload rate to peers, and find + // a balance between number of upload slots and the rate we achieve. The + // intention is to not spread upload bandwidth too thin, but also to not + // unchoke few enough peers to not be able to saturate the up-link. + // this is done by traversing the peers sorted by our upload rate to + // them in decreasing rates. For each peer we increase the threshold by + // 2 kiB/s. The first peer we get to whom we upload slower than + // the threshold, we stop and that's the number of unchoke slots we have. + if (sett.get_int(settings_pack::choking_algorithm) + == settings_pack::rate_based_choker) + { + // first reset the number of unchoke slots, because we'll calculate + // it purely based on the current state of our peers. + upload_slots = 0; + + int rate_threshold = sett.get_int(settings_pack::rate_choker_initial_threshold); + + std::sort(peers.begin(), peers.end() + , std::bind(&upload_rate_compare, _1, _2)); + + for (auto const* p : peers) + { + int const rate = int(p->uploaded_in_last_round() + * 1000 / total_milliseconds(unchoke_interval)); + + // always have at least 1 unchoke slot + if (rate < rate_threshold) break; + + ++upload_slots; + + // TODO: make configurable + rate_threshold += 2048; + } + ++upload_slots; + } + + // sorts the peers that are eligible for unchoke by download rate and + // secondary by total upload. The reason for this is, if all torrents are + // being seeded, the download rate will be 0, and the peers we have sent + // the least to should be unchoked + + // we use partial sort here, because we only care about the top + // upload_slots peers. + + int const slots = std::min(upload_slots, int(peers.size())); + + if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::round_robin) + { + int const pieces = sett.get_int(settings_pack::seeding_piece_quota); + + std::nth_element(peers.begin(), peers.begin() + + slots, peers.end() + , std::bind(&unchoke_compare_rr, _1, _2, pieces)); + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::fastest_upload) + { + std::nth_element(peers.begin(), peers.begin() + + slots, peers.end() + , std::bind(&unchoke_compare_fastest_upload, _1, _2)); + } + else if (sett.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::anti_leech) + { + std::nth_element(peers.begin(), peers.begin() + + slots, peers.end() + , std::bind(&unchoke_compare_anti_leech, _1, _2)); + } + else + { + int const pieces = sett.get_int(settings_pack::seeding_piece_quota); + std::nth_element(peers.begin(), peers.begin() + + slots, peers.end() + , std::bind(&unchoke_compare_rr, _1, _2, pieces)); + + TORRENT_ASSERT_FAIL(); + } + + return upload_slots; + } + +} diff --git a/src/close_reason.cpp b/src/close_reason.cpp new file mode 100644 index 0000000..c64768d --- /dev/null +++ b/src/close_reason.cpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/close_reason.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent { + + close_reason_t error_to_close_reason(error_code const& ec) + { + if (ec.category() == libtorrent_category()) + { +#define TORRENT_MAP(error, close_reason) \ + case errors:: error : \ + return close_reason; + + switch (ec.value()) + { + TORRENT_MAP(invalid_swarm_metadata, close_reason_t::invalid_metadata) + TORRENT_MAP(session_is_closing, close_reason_t::torrent_removed) + TORRENT_MAP(peer_sent_empty_piece, close_reason_t::invalid_piece_message) + TORRENT_MAP(mismatching_info_hash, close_reason_t::invalid_info_hash) + TORRENT_MAP(port_blocked, close_reason_t::port_blocked) + TORRENT_MAP(destructing_torrent, close_reason_t::torrent_removed) + TORRENT_MAP(timed_out, close_reason_t::timeout) + TORRENT_MAP(upload_upload_connection, close_reason_t::upload_to_upload) + TORRENT_MAP(uninteresting_upload_peer, close_reason_t::not_interested_upload_only) + TORRENT_MAP(invalid_info_hash, close_reason_t::invalid_info_hash) + TORRENT_MAP(torrent_paused, close_reason_t::torrent_removed) + TORRENT_MAP(invalid_have, close_reason_t::invalid_have_message) + TORRENT_MAP(invalid_bitfield_size, close_reason_t::invalid_bitfield_message) + TORRENT_MAP(too_many_requests_when_choked, close_reason_t::request_when_choked) + TORRENT_MAP(invalid_piece, close_reason_t::invalid_piece_message) + TORRENT_MAP(invalid_piece_size, close_reason_t::invalid_piece_message) + TORRENT_MAP(no_memory, close_reason_t::no_memory) + TORRENT_MAP(torrent_aborted, close_reason_t::torrent_removed) + TORRENT_MAP(self_connection, close_reason_t::self_connection) + TORRENT_MAP(timed_out_no_interest, close_reason_t::timed_out_interest) + TORRENT_MAP(timed_out_inactivity, close_reason_t::timed_out_activity) + TORRENT_MAP(timed_out_no_handshake, close_reason_t::timed_out_handshake) + TORRENT_MAP(timed_out_no_request, close_reason_t::timed_out_request) + TORRENT_MAP(invalid_choke, close_reason_t::invalid_choke_message) + TORRENT_MAP(invalid_unchoke, close_reason_t::invalid_unchoke_message) + TORRENT_MAP(invalid_interested, close_reason_t::invalid_interested_message) + TORRENT_MAP(invalid_not_interested, close_reason_t::invalid_not_interested_message) + TORRENT_MAP(invalid_request, close_reason_t::invalid_request_message) + TORRENT_MAP(invalid_hash_list, close_reason_t::invalid_message) + TORRENT_MAP(invalid_hash_piece, close_reason_t::invalid_message) + TORRENT_MAP(invalid_cancel, close_reason_t::invalid_cancel_message) + TORRENT_MAP(invalid_dht_port, close_reason_t::invalid_dht_port_message) + TORRENT_MAP(invalid_suggest, close_reason_t::invalid_suggest_message) + TORRENT_MAP(invalid_have_all, close_reason_t::invalid_have_all_message) + TORRENT_MAP(invalid_have_none, close_reason_t::invalid_have_none_message) + TORRENT_MAP(invalid_reject, close_reason_t::invalid_reject_message) + TORRENT_MAP(invalid_allow_fast, close_reason_t::invalid_allow_fast_message) + TORRENT_MAP(invalid_extended, close_reason_t::invalid_extended_message) + TORRENT_MAP(invalid_message, close_reason_t::invalid_message_id) + TORRENT_MAP(sync_hash_not_found, close_reason_t::encryption_error) + TORRENT_MAP(invalid_encryption_constant, close_reason_t::encryption_error) + TORRENT_MAP(no_plaintext_mode, close_reason_t::protocol_blocked) + TORRENT_MAP(no_rc4_mode, close_reason_t::protocol_blocked) + TORRENT_MAP(unsupported_encryption_mode_selected, close_reason_t::protocol_blocked) + TORRENT_MAP(invalid_pad_size, close_reason_t::encryption_error) + TORRENT_MAP(invalid_encrypt_handshake, close_reason_t::encryption_error) + TORRENT_MAP(no_incoming_encrypted, close_reason_t::protocol_blocked) + TORRENT_MAP(no_incoming_regular, close_reason_t::protocol_blocked) + TORRENT_MAP(duplicate_peer_id, close_reason_t::duplicate_peer_id) + TORRENT_MAP(torrent_removed, close_reason_t::torrent_removed) + TORRENT_MAP(packet_too_large, close_reason_t::message_too_big) + TORRENT_MAP(torrent_not_ready, close_reason_t::torrent_removed) + TORRENT_MAP(session_closing, close_reason_t::torrent_removed) + TORRENT_MAP(optimistic_disconnect, close_reason_t::peer_churn) + TORRENT_MAP(torrent_finished, close_reason_t::upload_to_upload) + TORRENT_MAP(too_many_corrupt_pieces, close_reason_t::corrupt_pieces) + TORRENT_MAP(too_many_connections, close_reason_t::too_many_connections) + TORRENT_MAP(peer_banned, close_reason_t::blocked) + TORRENT_MAP(stopping_torrent, close_reason_t::torrent_removed) + TORRENT_MAP(metadata_too_large, close_reason_t::metadata_too_big) + TORRENT_MAP(invalid_metadata_size, close_reason_t::metadata_too_big) + TORRENT_MAP(invalid_metadata_request, close_reason_t::invalid_metadata_request_message) + TORRENT_MAP(invalid_metadata_offset, close_reason_t::invalid_metadata_offset) + TORRENT_MAP(invalid_metadata_message, close_reason_t::invalid_metadata_message) + TORRENT_MAP(pex_message_too_large, close_reason_t::pex_message_too_big) + TORRENT_MAP(invalid_pex_message, close_reason_t::invalid_pex_message) + TORRENT_MAP(invalid_lt_tracker_message, close_reason_t::invalid_message) + TORRENT_MAP(too_frequent_pex, close_reason_t::pex_too_frequent) + TORRENT_MAP(invalid_dont_have, close_reason_t::invalid_dont_have_message) + TORRENT_MAP(requires_ssl_connection, close_reason_t::protocol_blocked) + TORRENT_MAP(invalid_ssl_cert, close_reason_t::blocked) + TORRENT_MAP(not_an_ssl_torrent, close_reason_t::blocked) + TORRENT_MAP(banned_by_port_filter, close_reason_t::port_blocked) + +#ifdef TORRENT_USE_ASSERTS + case errors::redirecting: + return close_reason_t::none; +#endif + + default: + return close_reason_t::none; + } + } + else if (ec.category() == boost::asio::error::get_misc_category()) + { + switch (ec.value()) + { + case boost::asio::error::eof: + return close_reason_t::none; + } + } + else if (ec.category() == generic_category()) + { + switch (ec.value()) + { +#ifdef TORRENT_USE_ASSERTS + case boost::system::errc::connection_reset: + case boost::system::errc::broken_pipe: + return close_reason_t::none; +#endif + case boost::system::errc::timed_out: + return close_reason_t::timeout; + case boost::system::errc::too_many_files_open: + case boost::system::errc::too_many_files_open_in_system: + return close_reason_t::too_many_files; + case boost::system::errc::not_enough_memory: + case boost::system::errc::no_buffer_space: + return close_reason_t::no_memory; + } + } + else if (ec.category() == http_category()) + { + return close_reason_t::no_memory; + } + + return close_reason_t::none; + } +} diff --git a/src/cpuid.cpp b/src/cpuid.cpp new file mode 100644 index 0000000..493cef2 --- /dev/null +++ b/src/cpuid.cpp @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/cpuid.hpp" + +#include + +#if defined _MSC_VER && TORRENT_HAS_SSE +#include +#include +#endif + +#if TORRENT_HAS_SSE && defined __GNUC__ +#include +#else +#include // for std::memset +#endif + +#if defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16)) +#define TORRENT_HAS_AUXV 1 +#elif defined TORRENT_ANDROID +#define TORRENT_HAS_AUXV 1 +#else +#define TORRENT_HAS_AUXV 0 +#endif + +#if TORRENT_HAS_ARM && TORRENT_HAS_AUXV +#if defined TORRENT_ANDROID +#include +namespace { +unsigned long int helper_getauxval(unsigned long int type) +{ + using getauxval_t = unsigned long int(*)(unsigned long int); + getauxval_t pf_getauxval = reinterpret_cast(dlsym(RTLD_DEFAULT, "getauxval")); + if (pf_getauxval == nullptr) + return 0; + return pf_getauxval(type); +} +} +#else // TORRENT_ANDROID +#include +#define helper_getauxval getauxval +#endif +#endif // TORRENT_HAS_ARM && TORRENT_HAS_AUXV + +namespace libtorrent { namespace aux { + + namespace { + +#if TORRENT_HAS_SSE + // internal + void cpuid(std::uint32_t* info, int type) + { +#if defined _MSC_VER + __cpuid((int*)info, type); + +#elif defined __GNUC__ + __get_cpuid(std::uint32_t(type), &info[0], &info[1], &info[2], &info[3]); +#else + TORRENT_UNUSED(type); + // for non-x86 and non-amd64, just return zeroes + std::memset(&info[0], 0, sizeof(std::uint32_t) * 4); +#endif + } +#endif + + bool supports_sse42() + { +#if TORRENT_HAS_SSE + std::uint32_t cpui[4] = {0}; + cpuid(cpui, 1); + return (cpui[2] & (1 << 20)) != 0; +#else + return false; +#endif + } + + bool supports_mmx() + { +#if TORRENT_HAS_SSE + std::uint32_t cpui[4] = {0}; + cpuid(cpui, 1); + return (cpui[2] & (1 << 23)) != 0; +#else + return false; +#endif + } + + bool supports_arm_neon() + { +#if TORRENT_HAS_ARM_NEON && TORRENT_HAS_AUXV +#if defined __arm__ + //return (getauxval(AT_HWCAP) & HWCAP_NEON); + return (helper_getauxval(16) & (1 << 12)); +#elif defined __aarch64__ + //return (getauxval(AT_HWCAP) & HWCAP_ASIMD); + //return (getauxval(16) & (1 << 1)); + // TODO: enable when aarch64 is really tested + return false; +#endif +#else + return false; +#endif + } + + bool supports_arm_crc32c() + { +#if TORRENT_HAS_ARM_CRC32 && TORRENT_HAS_AUXV +#if defined TORRENT_FORCE_ARM_CRC32 + return true; +#elif defined __arm__ + //return (getauxval(AT_HWCAP2) & HWCAP2_CRC32); + return (helper_getauxval(26) & (1 << 4)); +#elif defined __aarch64__ + //return (getauxval(AT_HWCAP) & HWCAP_CRC32); + return (helper_getauxval(16) & (1 << 7)); +#endif +#else + return false; +#endif + } + + } // anonymous namespace + + bool const sse42_support = supports_sse42(); + bool const mmx_support = supports_mmx(); + bool const arm_neon_support = supports_arm_neon(); + bool const arm_crc32c_support = supports_arm_crc32c(); +} } diff --git a/src/crc32c.cpp b/src/crc32c.cpp new file mode 100644 index 0000000..1562859 --- /dev/null +++ b/src/crc32c.cpp @@ -0,0 +1,149 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/aux_/cpuid.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#if (defined _MSC_VER && _MSC_VER >= 1600 && (defined _M_IX86 || defined _M_X64)) +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if TORRENT_HAS_ARM_CRC32 +#include +#endif + +namespace libtorrent { + + std::uint32_t crc32c_32(std::uint32_t v) + { +#if TORRENT_HAS_SSE + if (aux::sse42_support) + { + std::uint32_t ret = 0xffffffff; +#ifdef __GNUC__ + // we can't use these because then we'd have to tell + // -msse4.2 to gcc on the command line +// return __builtin_ia32_crc32si(ret, v) ^ 0xffffffff; + asm ("crc32l\t" "(%1), %0" + : "=r"(ret) + : "r"(&v), "0"(ret)); + return ret ^ 0xffffffff; +#else + return _mm_crc32_u32(ret, v) ^ 0xffffffff; +#endif + } +#endif + +#if TORRENT_HAS_ARM_CRC32 + if (aux::arm_crc32c_support) + { + std::uint32_t ret = 0xffffffff; + return __crc32cw(ret, v) ^ 0xffffffff; + } +#endif + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_bytes(&v, 4); + return crc.checksum(); + } + + std::uint32_t crc32c(std::uint64_t const* buf, int num_words) + { +#if TORRENT_HAS_SSE + if (aux::sse42_support) + { +#if defined _M_AMD64 || defined __x86_64__ \ + || defined __x86_64 || defined _M_X64 || defined __amd64__ + std::uint64_t ret = 0xffffffff; + for (int i = 0; i < num_words; ++i) + { +#ifdef __GNUC__ + // we can't use these because then we'd have to tell + // -msse4.2 to gcc on the command line +// ret = __builtin_ia32_crc32di(ret, buf[i]); + __asm__("crc32q\t" "(%1), %0" + : "=r"(ret) + : "r"(buf+i), "0"(ret)); +#else + ret = _mm_crc32_u64(ret, buf[i]); +#endif + } + return std::uint32_t(ret) ^ 0xffffffff; +#else + std::uint32_t ret = 0xffffffff; + std::uint32_t const* buf0 = reinterpret_cast(buf); + for (int i = 0; i < num_words; ++i) + { +#ifdef __GNUC__ + // we can't use these because then we'd have to tell + // -msse4.2 to gcc on the command line +// ret = __builtin_ia32_crc32si(ret, buf0[i*2]); +// ret = __builtin_ia32_crc32si(ret, buf0[i*2+1]); + asm ("crc32l\t" "(%1), %0" + : "=r"(ret) + : "r"(buf0+i*2), "0"(ret)); + asm ("crc32l\t" "(%1), %0" + : "=r"(ret) + : "r"(buf0+i*2+1), "0"(ret)); +#else + ret = _mm_crc32_u32(ret, buf0[i*2]); + ret = _mm_crc32_u32(ret, buf0[i*2+1]); +#endif + } + return ret ^ 0xffffffff; +#endif // amd64 or x86 + } +#endif // x86 or amd64 and gcc or msvc + +#if TORRENT_HAS_ARM_CRC32 + if (aux::arm_crc32c_support) + { + std::uint32_t ret = 0xffffffff; + for (int i = 0; i < num_words; ++i) + { + ret = __crc32cd(ret, buf[i]); + } + return ret ^ 0xffffffff; + } +#endif + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_bytes(buf, std::size_t(num_words) * 8); + return crc.checksum(); + } +} diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp new file mode 100644 index 0000000..ce39031 --- /dev/null +++ b/src/create_torrent.cpp @@ -0,0 +1,774 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/aux_/merkle.hpp" // for merkle_*() +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/aux_/path.hpp" + +#include +#include + +#include +#include + +#if TORRENT_ABI_VERSION == 1 && defined TORRENT_WINDOWS +#include "libtorrent/aux_/escape_string.hpp" +#endif + +using namespace std::placeholders; + +namespace libtorrent { + + constexpr create_flags_t create_torrent::optimize_alignment; +#if TORRENT_ABI_VERSION == 1 + constexpr create_flags_t create_torrent::optimize; +#endif + constexpr create_flags_t create_torrent::merkle; + constexpr create_flags_t create_torrent::modification_time; + constexpr create_flags_t create_torrent::symlinks; + constexpr create_flags_t create_torrent::mutable_torrent_support; + +namespace { + + bool default_pred(std::string const&) { return true; } + + bool ignore_subdir(std::string const& leaf) + { return leaf == ".." || leaf == "."; } + +#ifndef TORRENT_WINDOWS + std::string get_symlink_path_impl(char const* path) + { + constexpr int MAX_SYMLINK_PATH = 200; + + char buf[MAX_SYMLINK_PATH]; + std::string f = convert_to_native_path_string(path); + int char_read = int(readlink(f.c_str(), buf, MAX_SYMLINK_PATH)); + if (char_read < 0) return ""; + if (char_read < MAX_SYMLINK_PATH) buf[char_read] = 0; + else buf[0] = 0; + return convert_from_native_path(buf); + } +#endif + + void add_files_impl(file_storage& fs, std::string const& p + , std::string const& l, std::function const& pred + , create_flags_t const flags) + { + std::string const f = combine_path(p, l); + if (!pred(f)) return; + error_code ec; + file_status s; + stat_file(f, &s, ec, (flags & create_torrent::symlinks) ? dont_follow_links : 0); + if (ec) return; + + // recurse into directories + bool recurse = (s.mode & file_status::directory) != 0; + + // if the file is not a link or we're following links, and it's a directory + // only then should we recurse +#ifndef TORRENT_WINDOWS + if ((s.mode & file_status::link) && (flags & create_torrent::symlinks)) + recurse = false; +#endif + + if (recurse) + { + for (directory i(f, ec); !i.done(); i.next(ec)) + { + std::string const leaf = i.file(); + if (ignore_subdir(leaf)) continue; + add_files_impl(fs, p, combine_path(l, leaf), pred, flags); + } + } + else + { + // #error use the fields from s + file_flags_t const file_flags = aux::get_file_attributes(f); + + // mask all bits to check if the file is a symlink + if ((file_flags & file_storage::flag_symlink) + && (flags & create_torrent::symlinks)) + { + std::string const sym_path = aux::get_symlink_path(f); + fs.add_file(l, 0, file_flags, std::time_t(s.mtime), sym_path); + } + else + { + fs.add_file(l, s.file_size, file_flags, std::time_t(s.mtime)); + } + } + } + + struct hash_state + { + create_torrent& ct; + storage_holder storage; + disk_io_thread& iothread; + piece_index_t piece_counter; + piece_index_t completed_piece; + std::function const& f; + error_code& ec; + }; + + void on_hash(piece_index_t const piece, sha1_hash const& piece_hash + , storage_error const& error, hash_state* st) + { + if (error) + { + // on error + st->ec = error.ec; + st->iothread.abort(true); + return; + } + st->ct.set_hash(piece, piece_hash); + st->f(st->completed_piece); + ++st->completed_piece; + if (st->piece_counter < st->ct.files().end_piece()) + { + st->iothread.async_hash(st->storage, st->piece_counter + , disk_interface::sequential_access + , std::bind(&on_hash, _1, _2, _3, st)); + ++st->piece_counter; + } + else + { + st->iothread.abort(true); + } + st->iothread.submit_jobs(); + } + +} // anonymous namespace + +namespace aux { + + file_flags_t get_file_attributes(std::string const& p) + { + auto const path = convert_to_native_path_string(p); + +#ifdef TORRENT_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; + GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &attr); + if (attr.dwFileAttributes == INVALID_FILE_ATTRIBUTES) return {}; + if (attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) return file_storage::flag_hidden; + return {}; +#else + struct ::stat s{}; + if (::lstat(path.c_str(), &s) < 0) return {}; + file_flags_t file_attr = {}; + if (s.st_mode & S_IXUSR) + file_attr |= file_storage::flag_executable; + if (S_ISLNK(s.st_mode)) + file_attr |= file_storage::flag_symlink; + return file_attr; +#endif + } + + std::string get_symlink_path(std::string const& p) + { +#if defined TORRENT_WINDOWS + TORRENT_UNUSED(p); + return ""; +#else + return get_symlink_path_impl(p.c_str()); +#endif + } + +} // anonymous aux + +#if TORRENT_ABI_VERSION == 1 + +#if defined TORRENT_WINDOWS + void add_files(file_storage& fs, std::wstring const& wfile + , std::function p, create_flags_t const flags) + { + std::string utf8 = convert_from_wstring(wfile); + add_files_impl(fs, parent_path(complete(utf8)) + , filename(utf8), p, flags); + } + + void add_files(file_storage& fs + , std::wstring const& wfile, create_flags_t const flags) + { + std::string utf8 = convert_from_wstring(wfile); + add_files_impl(fs, parent_path(complete(utf8)) + , filename(utf8), default_pred, flags); + } + + void set_piece_hashes(create_torrent& t, std::wstring const& p + , std::function f, error_code& ec) + { + std::string utf8 = convert_from_wstring(p); + set_piece_hashes(t, utf8, f, ec); + } + + void set_piece_hashes_deprecated(create_torrent& t, std::wstring const& p + , std::function f, error_code& ec) + { + std::string utf8 = convert_from_wstring(p); + set_piece_hashes(t, utf8, f, ec); + } +#endif // TORRENT_WINDOWS +#endif // TORRENT_ABI_VERSION + + void add_files(file_storage& fs, std::string const& file + , std::function p, create_flags_t const flags) + { + add_files_impl(fs, parent_path(complete(file)), filename(file), p, flags); + } + + void add_files(file_storage& fs, std::string const& file, create_flags_t const flags) + { + add_files_impl(fs, parent_path(complete(file)), filename(file) + , default_pred, flags); + } + +namespace { + struct disk_aborter + { + explicit disk_aborter(disk_io_thread& dio) : m_dio(dio) {} + ~disk_aborter() { m_dio.abort(true); } + disk_aborter(disk_aborter const&) = delete; + disk_aborter& operator=(disk_aborter const&) = delete; + private: + disk_io_thread& m_dio; + }; +} + + void set_piece_hashes(create_torrent& t, std::string const& p + , std::function const& f, error_code& ec) + { + // optimized path +#ifdef TORRENT_BUILD_SIMULATOR + sim::default_config conf; + sim::simulation sim{conf}; + io_service ios{sim}; +#else + io_service ios; +#endif + +#if TORRENT_USE_UNC_PATHS + std::string const path = canonicalize_path(p); +#else + std::string const& path = p; +#endif + + if (t.files().num_files() == 0) + { + ec = errors::no_files_in_torrent; + return; + } + + if (t.files().total_size() == 0) + { + ec = errors::torrent_invalid_length; + return; + } + + counters cnt; + aux::session_settings sett; + + sett.set_int(settings_pack::cache_size, 0); + int const num_threads = disk_io_thread::hasher_thread_divisor - 1; + int const jobs_per_thread = 4; + sett.set_int(settings_pack::aio_threads, num_threads); + + disk_io_thread disk_thread(ios, sett, cnt); + disk_aborter da(disk_thread); + + aux::vector priorities; + sha1_hash info_hash; + storage_params params{ + t.files(), + nullptr, + path, + storage_mode_t::storage_mode_sparse, + priorities, + info_hash + }; + + storage_holder storage = disk_thread.new_torrent(default_storage_constructor + , params, std::shared_ptr()); + + int const piece_read_ahead = std::max(num_threads * jobs_per_thread + , default_block_size / t.piece_length()); + + hash_state st = { t, std::move(storage), disk_thread, piece_index_t(0), piece_index_t(0), f, ec }; + for (piece_index_t i(0); i < piece_index_t(piece_read_ahead); ++i) + { + disk_thread.async_hash(st.storage, i, disk_interface::sequential_access + , std::bind(&on_hash, _1, _2, _3, &st)); + ++st.piece_counter; + if (st.piece_counter >= t.files().end_piece()) break; + } + disk_thread.submit_jobs(); + +#ifdef TORRENT_BUILD_SIMULATOR + sim.run(); +#else + ios.run(ec); +#endif + } + + create_torrent::~create_torrent() = default; + + create_torrent::create_torrent(file_storage& fs, int piece_size + , int pad_file_limit, create_flags_t const flags, int alignment) + : m_files(fs) + , m_creation_date(::time(nullptr)) + , m_multifile(fs.num_files() > 1) + , m_private(false) + , m_merkle_torrent(bool(flags & create_torrent::merkle)) + , m_include_mtime(bool(flags & create_torrent::modification_time)) + , m_include_symlinks(bool(flags & create_torrent::symlinks)) + { + // return instead of crash in release mode + if (fs.num_files() == 0 || fs.total_size() == 0) return; + + if (!m_multifile && has_parent_path(m_files.file_path(file_index_t(0)))) + m_multifile = true; + + // a piece_size of 0 means automatic + if (piece_size == 0 && !m_merkle_torrent) + { + // size_table is computed from the following: + // target_list_size = sqrt(total_size) * 2; + // target_piece_size = total_size / (target_list_size / hash_size); + // Given hash_size = 20 bytes, target_piece_size = (16*1024 * pow(2, i)) + // we can determine size_table = (total_size = pow(2 * target_piece_size / hash_size, 2)) + std::array const size_table{{ + 2684355LL // -> 16kiB + , 10737418LL // -> 32 kiB + , 42949673LL // -> 64 kiB + , 171798692LL // -> 128 kiB + , 687194767LL // -> 256 kiB + , 2748779069LL // -> 512 kiB + , 10995116278LL // -> 1 MiB + , 43980465111LL // -> 2 MiB + , 175921860444LL // -> 4 MiB + , 703687441777LL}}; // -> 8 MiB + + int i = 0; + for (auto const s : size_table) + { + if (s >= fs.total_size()) break; + ++i; + } + piece_size = default_block_size << i; + } + else if (piece_size == 0 && m_merkle_torrent) + { + piece_size = 64*1024; + } + + // to support mutable torrents, alignment always has to be the piece size, + // because piece hashes are compared to determine whether files are + // identical + if (flags & mutable_torrent_support) + alignment = piece_size; + + // make sure the size is an even power of 2 + // i.e. only a single bit is set + TORRENT_ASSERT((piece_size & (piece_size - 1)) == 0); + + m_files.set_piece_length(piece_size); + if (flags & (optimize_alignment | mutable_torrent_support)) + m_files.optimize(pad_file_limit, alignment, bool(flags & mutable_torrent_support)); + + m_files.set_num_pieces(static_cast( + (m_files.total_size() + m_files.piece_length() - 1) / m_files.piece_length())); + m_piece_hash.resize(m_files.num_pieces()); + } + + create_torrent::create_torrent(torrent_info const& ti) + : m_files(const_cast(ti.files())) + , m_creation_date(::time(nullptr)) + , m_multifile(ti.num_files() > 1) + , m_private(ti.priv()) + , m_merkle_torrent(ti.is_merkle_torrent()) + , m_include_mtime(false) + , m_include_symlinks(false) + { + TORRENT_ASSERT(ti.is_valid()); + TORRENT_ASSERT(ti.num_pieces() > 0); + TORRENT_ASSERT(ti.num_files() > 0); + TORRENT_ASSERT(ti.total_size() > 0); + + if (!ti.is_valid()) return; + if (ti.creation_date() > 0) m_creation_date = ti.creation_date(); + + if (!ti.creator().empty()) set_creator(ti.creator().c_str()); + if (!ti.comment().empty()) set_comment(ti.comment().c_str()); + + for (auto const& n : ti.nodes()) + add_node(n); + + for (auto const& t : ti.trackers()) + add_tracker(t.url, t.tier); + + for (auto const& s : ti.web_seeds()) + { + if (s.type == web_seed_entry::url_seed) + add_url_seed(s.url); + else if (s.type == web_seed_entry::http_seed) + add_http_seed(s.url); + } + + m_piece_hash.resize(m_files.num_pieces()); + for (auto const i : m_files.piece_range()) + set_hash(i, ti.hash_for_piece(i)); + + boost::shared_array const info = ti.metadata(); + int const size = ti.metadata_size(); + m_info_dict.preformatted().assign(&info[0], &info[0] + size); + } + + entry create_torrent::generate() const + { + entry dict; + + if (m_files.num_files() == 0 || m_files.total_size() == 0) + return dict; + + TORRENT_ASSERT(m_files.piece_length() > 0); + + if (!m_urls.empty()) dict["announce"] = m_urls.front().first; + + if (!m_nodes.empty()) + { + entry& nodes = dict["nodes"]; + entry::list_type& nodes_list = nodes.list(); + for (auto const& n : m_nodes) + { + entry::list_type node; + node.emplace_back(n.first); + node.emplace_back(n.second); + nodes_list.emplace_back(node); + } + } + + if (m_urls.size() > 1) + { + entry trackers(entry::list_t); + entry tier(entry::list_t); + int current_tier = m_urls.front().second; + for (auto const& url : m_urls) + { + if (url.second != current_tier) + { + current_tier = url.second; + trackers.list().push_back(tier); + tier.list().clear(); + } + tier.list().emplace_back(url.first); + } + trackers.list().push_back(tier); + dict["announce-list"] = trackers; + } + + if (!m_comment.empty()) + dict["comment"] = m_comment; + + dict["creation date"] = m_creation_date; + + if (!m_created_by.empty()) + dict["created by"] = m_created_by; + + if (!m_url_seeds.empty()) + { + if (m_url_seeds.size() == 1) + { + dict["url-list"] = m_url_seeds.front(); + } + else + { + entry& list = dict["url-list"]; + for (auto const& url : m_url_seeds) + { + list.list().emplace_back(url); + } + } + } + + if (!m_http_seeds.empty()) + { + if (m_http_seeds.size() == 1) + { + dict["httpseeds"] = m_http_seeds.front(); + } + else + { + entry& list = dict["httpseeds"]; + for (auto const& url : m_http_seeds) + { + list.list().emplace_back(url); + } + } + } + + entry& info = dict["info"]; + if (m_info_dict.type() == entry::dictionary_t + || m_info_dict.type() == entry::preformatted_t) + { + info = m_info_dict; + return dict; + } + + if (!m_collections.empty()) + { + entry& list = info["collections"]; + for (auto const& c : m_collections) + { + list.list().emplace_back(c); + } + } + + if (!m_similar.empty()) + { + entry& list = info["similar"]; + for (auto const& ih : m_similar) + { + list.list().emplace_back(ih.to_string()); + } + } + + info["name"] = m_files.name(); + + if (!m_root_cert.empty()) + info["ssl-cert"] = m_root_cert; + + if (m_private) info["private"] = 1; + + if (!m_multifile) + { + file_index_t const first(0); + if (m_include_mtime) info["mtime"] = m_files.mtime(first); + info["length"] = m_files.file_size(first); + file_flags_t const flags = m_files.file_flags(first); + if (flags & (file_storage::flag_pad_file + | file_storage::flag_hidden + | file_storage::flag_executable + | file_storage::flag_symlink)) + { + std::string& attr = info["attr"].string(); + if (flags & file_storage::flag_pad_file) attr += 'p'; + if (flags & file_storage::flag_hidden) attr += 'h'; + if (flags & file_storage::flag_executable) attr += 'x'; + if (m_include_symlinks && (flags & file_storage::flag_symlink)) attr += 'l'; + } + if (m_include_symlinks + && (flags & file_storage::flag_symlink)) + { + entry& sympath_e = info["symlink path"]; + + for (auto elems = lsplit_path(m_files.symlink(first)); !elems.first.empty(); + elems = lsplit_path(elems.second)) + sympath_e.list().emplace_back(elems.first); + } + if (!m_filehashes.empty()) + { + info["sha1"] = m_filehashes[first].to_string(); + } + } + else + { + if (!info.find_key("files")) + { + entry& files = info["files"]; + + for (auto const i : m_files.file_range()) + { + files.list().emplace_back(); + entry& file_e = files.list().back(); + if (m_include_mtime && m_files.mtime(i)) file_e["mtime"] = m_files.mtime(i); + file_e["length"] = m_files.file_size(i); + + TORRENT_ASSERT(has_parent_path(m_files.file_path(i))); + + { + entry& path_e = file_e["path"]; + + std::string const p = m_files.file_path(i); + // deliberately skip the first path element, since that's the + // "name" of the torrent already + string_view path = lsplit_path(p).second; + for (auto elems = lsplit_path(path); !elems.first.empty(); elems = lsplit_path(elems.second)) + path_e.list().emplace_back(elems.first); + } + + file_flags_t const flags = m_files.file_flags(i); + if (flags) + { + std::string& attr = file_e["attr"].string(); + if (flags & file_storage::flag_pad_file) attr += 'p'; + if (flags & file_storage::flag_hidden) attr += 'h'; + if (flags & file_storage::flag_executable) attr += 'x'; + if (m_include_symlinks && (flags & file_storage::flag_symlink)) attr += 'l'; + } + + if (m_include_symlinks + && (flags & file_storage::flag_symlink)) + { + entry& sympath_e = file_e["symlink path"]; + + for (auto elems = lsplit_path(m_files.symlink(i)); !elems.first.empty(); + elems = lsplit_path(elems.second)) + sympath_e.list().emplace_back(elems.first); + } + if (!m_filehashes.empty() && m_filehashes[i] != sha1_hash()) + { + file_e["sha1"] = m_filehashes[i].to_string(); + } + } + } + } + + info["piece length"] = m_files.piece_length(); + if (m_merkle_torrent) + { + int const num_leafs = merkle_num_leafs(m_files.num_pieces()); + int const num_nodes = merkle_num_nodes(num_leafs); + int const first_leaf = num_nodes - num_leafs; + m_merkle_tree.resize(num_nodes); + auto const num_pieces = int(m_piece_hash.size()); + for (int i = 0; i < num_pieces; ++i) + m_merkle_tree[first_leaf + i] = m_piece_hash[piece_index_t(i)]; + for (int i = num_pieces; i < num_leafs; ++i) + m_merkle_tree[first_leaf + i].clear(); + + // now that we have initialized all leaves, build + // each level bottom-up + int level_start = first_leaf; + int level_size = num_leafs; + while (level_start > 0) + { + int parent = merkle_get_parent(level_start); + for (int i = level_start; i < level_start + level_size; i += 2, ++parent) + { + hasher h; + h.update(m_merkle_tree[i]); + h.update(m_merkle_tree[i + 1]); + m_merkle_tree[parent] = h.final(); + } + level_start = merkle_get_parent(level_start); + level_size /= 2; + } + TORRENT_ASSERT(level_size == 1); + info["root hash"] = m_merkle_tree[0]; + } + else + { + std::string& p = info["pieces"].string(); + + for (sha1_hash const& h : m_piece_hash) + p.append(h.data(), h.size()); + } + + return dict; + } + + void create_torrent::add_tracker(string_view url, int const tier) + { + if (url.empty()) return; + using announce_entry = std::pair; + auto const i = std::find_if(m_urls.begin(), m_urls.end() + , [&url](announce_entry const& ae) { return ae.first == url; }); + if (i != m_urls.end()) return; + m_urls.emplace_back(url.to_string(), tier); + + std::sort(m_urls.begin(), m_urls.end() + , [](announce_entry const& lhs, announce_entry const& rhs) + { return lhs.second < rhs.second; }); + } + + void create_torrent::set_root_cert(string_view cert) + { + m_root_cert.assign(cert.data(), cert.size()); + } + + void create_torrent::add_similar_torrent(sha1_hash ih) + { + m_similar.emplace_back(ih); + } + + void create_torrent::add_collection(string_view c) + { + m_collections.emplace_back(c); + } + + void create_torrent::set_hash(piece_index_t index, sha1_hash const& h) + { + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_piece_hash.end_index()); + m_piece_hash[index] = h; + } + + void create_torrent::set_file_hash(file_index_t index, sha1_hash const& h) + { + TORRENT_ASSERT(index >= file_index_t(0)); + TORRENT_ASSERT(index < m_files.end_file()); + if (m_filehashes.empty()) m_filehashes.resize(m_files.num_files()); + m_filehashes[index] = h; + } + + void create_torrent::add_node(std::pair node) + { + m_nodes.emplace_back(std::move(node)); + } + + void create_torrent::add_url_seed(string_view url) + { + m_url_seeds.emplace_back(url); + } + + void create_torrent::add_http_seed(string_view url) + { + m_http_seeds.emplace_back(url); + } + + void create_torrent::set_comment(char const* str) + { + if (str == nullptr) m_comment.clear(); + else m_comment = str; + } + + void create_torrent::set_creator(char const* str) + { + if (str == nullptr) m_created_by.clear(); + else m_created_by = str; + } +} diff --git a/src/disk_buffer_holder.cpp b/src/disk_buffer_holder.cpp new file mode 100644 index 0000000..e2ad688 --- /dev/null +++ b/src/disk_buffer_holder.cpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/disk_buffer_holder.hpp" + +namespace libtorrent { + + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc + , char* buf, std::size_t sz) noexcept + : m_allocator(&alloc), m_buf(buf), m_size(sz), m_ref() + {} + + disk_buffer_holder& disk_buffer_holder::operator=(disk_buffer_holder&& h) & noexcept + { + if (&h == this) return *this; + disk_buffer_holder(std::move(h)).swap(*this); + return *this; + } + + disk_buffer_holder::disk_buffer_holder(disk_buffer_holder&& h) noexcept + : m_allocator(h.m_allocator), m_buf(h.m_buf), m_size(h.m_size), m_ref(h.m_ref) + { + // we own this buffer now + h.m_buf = nullptr; + h.m_ref = aux::block_cache_reference(); + } + + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc + , aux::block_cache_reference const& ref, char* buf + , std::size_t sz) noexcept + : m_allocator(&alloc), m_buf(buf), m_size(sz), m_ref(ref) + {} + + void disk_buffer_holder::reset(aux::block_cache_reference const& ref, char* buf, std::size_t const sz) + { + if (m_ref.cookie != aux::block_cache_reference::none) m_allocator->reclaim_blocks(m_ref); + else if (m_buf) m_allocator->free_disk_buffer(m_buf); + m_buf = buf; + m_size = sz; + m_ref = ref; + } + + void disk_buffer_holder::reset(char* const buf, std::size_t const sz) + { + if (m_ref.cookie != aux::block_cache_reference::none) m_allocator->reclaim_blocks(m_ref); + else if (m_buf) m_allocator->free_disk_buffer(m_buf); + m_buf = buf; + m_size = sz; + m_ref = aux::block_cache_reference(); + } + + char* disk_buffer_holder::release() noexcept + { + TORRENT_ASSERT(m_ref.cookie == aux::block_cache_reference::none); + char* ret = m_buf; + m_buf = nullptr; + m_size = 0; + m_ref = aux::block_cache_reference(); + return ret; + } + + disk_buffer_holder::~disk_buffer_holder() { reset(); } +} diff --git a/src/disk_buffer_pool.cpp b/src/disk_buffer_pool.cpp new file mode 100644 index 0000000..3d1b824 --- /dev/null +++ b/src/disk_buffer_pool.cpp @@ -0,0 +1,380 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/platform_util.hpp" // for total_physical_ram +#include "libtorrent/disk_interface.hpp" // for default_block_size + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef TORRENT_BSD +#include +#endif + +#ifdef TORRENT_LINUX +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + namespace { + + // this is posted to the network thread + void watermark_callback(std::vector> const& cbs) + { + for (auto const& i : cbs) + { + std::shared_ptr o = i.lock(); + if (o) o->on_disk(); + } + } + + } // anonymous namespace + + disk_buffer_pool::disk_buffer_pool(io_service& ios + , std::function const& trigger_trim) + : m_in_use(0) + , m_max_use(64) + , m_low_watermark(std::max(m_max_use - 32, 0)) + , m_trigger_cache_trim(trigger_trim) + , m_exceeded_max_size(false) + , m_ios(ios) + { +#if TORRENT_USE_ASSERTS + m_magic = 0x1337; + m_settings_set = false; +#endif + } + + disk_buffer_pool::~disk_buffer_pool() + { + TORRENT_ASSERT(m_magic == 0x1337); +#if TORRENT_USE_ASSERTS + m_magic = 0; +#endif + + } + + int disk_buffer_pool::num_to_evict(int const num_needed) + { + int ret = 0; + + std::unique_lock l(m_pool_mutex); + + if (m_exceeded_max_size) + ret = m_in_use - std::min(m_low_watermark, m_max_use - int(m_observers.size()) * 2); + + if (m_in_use + num_needed > m_max_use) + ret = std::max(ret, m_in_use + num_needed - m_max_use); + + if (ret < 0) ret = 0; + else if (ret > m_in_use) ret = m_in_use; + + return ret; + } + + // checks to see if we're no longer exceeding the high watermark, + // and if we're in fact below the low watermark. If so, we need to + // post the notification messages to the peers that are waiting for + // more buffers to received data into + void disk_buffer_pool::check_buffer_level(std::unique_lock& l) + { + TORRENT_ASSERT(l.owns_lock()); + if (!m_exceeded_max_size || m_in_use > m_low_watermark) return; + + m_exceeded_max_size = false; + + std::vector> cbs; + m_observers.swap(cbs); + l.unlock(); + m_ios.post(std::bind(&watermark_callback, std::move(cbs))); + } + +#if TORRENT_USE_ASSERTS + bool disk_buffer_pool::is_disk_buffer(char* buffer + , std::unique_lock& l) const + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(l.owns_lock()); + TORRENT_UNUSED(l); + +#if TORRENT_USE_INVARIANT_CHECKS + return m_buffers_in_use.count(buffer) == 1; +#else + TORRENT_UNUSED(buffer); + return true; +#endif + } + + bool disk_buffer_pool::is_disk_buffer(char* buffer) const + { + std::unique_lock l(m_pool_mutex); + return is_disk_buffer(buffer, l); + } +#endif + + char* disk_buffer_pool::allocate_buffer(char const* category) + { + std::unique_lock l(m_pool_mutex); + return allocate_buffer_impl(l, category); + } + + // we allow allocating more blocks even after we exceed the max size, + // but communicate back to the allocator (typically the peer_connection) + // that we have exceeded the limit via the out-parameter "exceeded". The + // caller is expected to honor this by not allocating any more buffers + // until the disk_observer object (passed in as "o") is invoked, indicating + // that there's more room in the pool now. This caps the amount of over- + // allocation to one block per peer connection. + char* disk_buffer_pool::allocate_buffer(bool& exceeded + , std::shared_ptr o, char const* category) + { + std::unique_lock l(m_pool_mutex); + char* ret = allocate_buffer_impl(l, category); + if (m_exceeded_max_size) + { + exceeded = true; + if (o) m_observers.push_back(o); + } + return ret; + } + +// this function allocates buffers and +// fills in the iovec array with the buffers + int disk_buffer_pool::allocate_iovec(span iov) + { + std::unique_lock l(m_pool_mutex); + for (auto& i : iov) + { + i = { allocate_buffer_impl(l, "pending read"), std::size_t(default_block_size)}; + if (i.data() == nullptr) + { + // uh oh. We failed to allocate the buffer! + // we need to roll back and free all the buffers + // we've already allocated + for (auto j : iov) + { + if (j.data() == nullptr) break; + char* buf = j.data(); + TORRENT_ASSERT(is_disk_buffer(buf, l)); + remove_buffer_in_use(buf); + free_buffer_impl(buf, l); + } + return -1; + } + } + return 0; + } + + void disk_buffer_pool::free_iovec(span iov) + { + // TODO: perhaps we should sort the buffers here? + std::unique_lock l(m_pool_mutex); + for (auto i : iov) + { + char* buf = i.data(); + TORRENT_ASSERT(is_disk_buffer(buf, l)); + remove_buffer_in_use(buf); + free_buffer_impl(buf, l); + } + check_buffer_level(l); + } + + char* disk_buffer_pool::allocate_buffer_impl(std::unique_lock& l + , char const*) + { + TORRENT_ASSERT(m_settings_set); + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(l.owns_lock()); + TORRENT_UNUSED(l); + + char* ret = static_cast(std::malloc(default_block_size)); + + if (ret == nullptr) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + return nullptr; + } + + ++m_in_use; + +#if TORRENT_USE_INVARIANT_CHECKS + try + { + TORRENT_ASSERT(m_buffers_in_use.count(ret) == 0); + m_buffers_in_use.insert(ret); + } + catch (...) + { + free_buffer_impl(ret, l); + return nullptr; + } +#endif + + if (m_in_use >= m_low_watermark + (m_max_use - m_low_watermark) + / 2 && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + + TORRENT_ASSERT(is_disk_buffer(ret, l)); + return ret; + } + + void disk_buffer_pool::free_multiple_buffers(span bufvec) + { + // sort the pointers in order to maximize cache hits + std::sort(bufvec.begin(), bufvec.end()); + + std::unique_lock l(m_pool_mutex); + for (char* buf : bufvec) + { + TORRENT_ASSERT(is_disk_buffer(buf, l)); + remove_buffer_in_use(buf); + free_buffer_impl(buf, l); + } + + check_buffer_level(l); + } + + void disk_buffer_pool::free_buffer(char* buf) + { + std::unique_lock l(m_pool_mutex); + TORRENT_ASSERT(is_disk_buffer(buf, l)); + remove_buffer_in_use(buf); + free_buffer_impl(buf, l); + check_buffer_level(l); + } + + void disk_buffer_pool::set_settings(aux::session_settings const& sett) + { + std::unique_lock l(m_pool_mutex); + + int const cache_size = sett.get_int(settings_pack::cache_size); + if (cache_size < 0) + { + std::int64_t phys_ram = total_physical_ram(); + if (phys_ram == 0) m_max_use = default_int_value(settings_pack::cache_size); + else + { + // this is the logic to calculate the automatic disk cache size + // based on the amount of physical RAM. + // The more physical RAM, the smaller portion of it is allocated + // for the cache. + + // we take a 40th of everything exceeding 4 GiB + // a 30th of everything exceeding 1 GiB + // and a 10th of everything below a GiB + + constexpr std::int64_t gb = 1024 * 1024 * 1024; + + std::int64_t result = 0; + if (phys_ram > 4 * gb) + { + result += (phys_ram - 4 * gb) / 40; + phys_ram = 4 * gb; + } + if (phys_ram > 1 * gb) + { + result += (phys_ram - 1 * gb) / 30; + phys_ram = 1 * gb; + } + result += phys_ram / 20; + m_max_use = int(result / default_block_size); + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127 ) /* warning C4127: conditional expression is constant */ +#endif // _MSC_VER + if (sizeof(void*) == 4) +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + { + // 32 bit builds should capped below 2 GB of memory, even + // when more actual ram is available, because we're still + // constrained by the 32 bit virtual address space. + m_max_use = std::min(2 * 1024 * 1024 * 3 / 4 * 1024 + / default_block_size, m_max_use); + } + } + else + { + m_max_use = cache_size; + } + m_low_watermark = m_max_use - std::max(16, sett.get_int(settings_pack::max_queued_disk_bytes) / 0x4000); + if (m_low_watermark < 0) m_low_watermark = 0; + if (m_in_use >= m_max_use && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + +#if TORRENT_USE_ASSERTS + m_settings_set = true; +#endif + } + + void disk_buffer_pool::remove_buffer_in_use(char* buf) + { + TORRENT_UNUSED(buf); +#if TORRENT_USE_INVARIANT_CHECKS + std::set::iterator i = m_buffers_in_use.find(buf); + TORRENT_ASSERT(i != m_buffers_in_use.end()); + m_buffers_in_use.erase(i); +#endif + } + + void disk_buffer_pool::free_buffer_impl(char* buf, std::unique_lock& l) + { + TORRENT_ASSERT(buf); + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(m_settings_set); + TORRENT_ASSERT(l.owns_lock()); + TORRENT_UNUSED(l); + + std::free(buf); + + --m_in_use; + } + +} diff --git a/src/disk_io_job.cpp b/src/disk_io_job.cpp new file mode 100644 index 0000000..b45f6ff --- /dev/null +++ b/src/disk_io_job.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/block_cache.hpp" // for cached_piece_entry +#include "libtorrent/disk_buffer_holder.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + namespace { + struct caller_visitor : boost::static_visitor<> + { + explicit caller_visitor(disk_io_job& j) + : m_job(j) {} + + void operator()(disk_io_job::read_handler& h) const + { + if (!h) return; + h(std::move(boost::get(m_job.argument)) + , m_job.flags, m_job.error); + } + + void operator()(disk_io_job::write_handler& h) const + { + if (!h) return; + h(m_job.error); + } + + void operator()(disk_io_job::hash_handler& h) const + { + if (!h) return; + h(m_job.piece, m_job.d.piece_hash, m_job.error); + } + + void operator()(disk_io_job::move_handler& h) const + { + if (!h) return; + h(m_job.ret, std::move(boost::get(m_job.argument)) + , m_job.error); + } + + void operator()(disk_io_job::release_handler& h) const + { + if (!h) return; + h(); + } + + void operator()(disk_io_job::check_handler& h) const + { + if (!h) return; + h(m_job.ret, m_job.error); + } + + void operator()(disk_io_job::rename_handler& h) const + { + if (!h) return; + h(std::move(boost::get(m_job.argument)) + , m_job.file_index, m_job.error); + } + + void operator()(disk_io_job::clear_piece_handler& h) const + { + if (!h) return; + h(m_job.piece); + } + + void operator()(disk_io_job::set_file_prio_handler& h) const + { + if (!h) return; + h(m_job.error, std::move(boost::get>(m_job.argument))); + } + + private: + disk_io_job& m_job; + }; + } + + constexpr disk_job_flags_t disk_io_job::fence; + constexpr disk_job_flags_t disk_io_job::in_progress; + constexpr disk_job_flags_t disk_io_job::aborted; + + disk_io_job::disk_io_job() + : argument(remove_flags_t{}) + , piece(0) + { + d.io.offset = 0; + d.io.buffer_size = 0; + } + + void disk_io_job::call_callback() + { + boost::apply_visitor(caller_visitor(*this), callback); + } + + bool disk_io_job::completed(cached_piece_entry const* pe) + { + if (action != job_action_t::write) return false; + + int const block_offset = d.io.offset & (default_block_size - 1); + int const size = d.io.buffer_size; + int const start = d.io.offset / default_block_size; + int const end = block_offset > 0 && (size > default_block_size - block_offset) ? start + 2 : start + 1; + + for (int i = start; i < end; ++i) + { + cached_block_entry const& b = pe->blocks[i]; + if (b.dirty || b.pending) return false; + } + + // if all our blocks are not pending and not dirty, it means they + // were successfully written to disk. This job is complete + return true; + } +} diff --git a/src/disk_io_thread.cpp b/src/disk_io_thread.cpp new file mode 100644 index 0000000..91e436f --- /dev/null +++ b/src/disk_io_thread.cpp @@ -0,0 +1,3521 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/scope_end.hpp" + +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#define DEBUG_DISK_THREAD 0 + +namespace libtorrent { +char const* job_name(job_action_t const job); +} + +#if DEBUG_DISK_THREAD +#include // for va_list +#include +#include // for vsnprintf + +#define DLOG(...) debug_log(__VA_ARGS__) +#else +#define DLOG(...) do {} while(false) +#endif + +namespace libtorrent { + +#if TORRENT_USE_ASSERTS + +#define TORRENT_PIECE_ASSERT(cond, piece) \ + do { if (!(cond)) { assert_print_piece(piece); assert_fail(#cond, __LINE__, __FILE__, __func__, nullptr); } } TORRENT_WHILE_0 + +#define TORRENT_PIECE_ASSERT_FAIL(piece) \ + do { assert_print_piece(piece); assert_fail("", __LINE__, __FILE__, __func__, nullptr); } TORRENT_WHILE_0 + +#else +#define TORRENT_PIECE_ASSERT(cond, piece) do {} TORRENT_WHILE_0 +#define TORRENT_PIECE_ASSERT_FAIL(piece) do {} TORRENT_WHILE_0 +#endif // TORRENT_USE_ASSERTS + + + namespace { + +#if DEBUG_DISK_THREAD + + void debug_log(char const* fmt, ...) + { + static std::mutex log_mutex; + static const time_point start = clock_type::now(); + // map thread IDs to low numbers + static std::unordered_map thread_ids; + + std::thread::id const self = std::this_thread::get_id(); + + std::unique_lock l(log_mutex); + auto it = thread_ids.insert({self, int(thread_ids.size())}).first; + + va_list v; + va_start(v, fmt); + + char usr[2048]; + int len = std::vsnprintf(usr, sizeof(usr), fmt, v); + + static bool prepend_time = true; + if (!prepend_time) + { + prepend_time = (usr[len-1] == '\n'); + fputs(usr, stderr); + return; + } + va_end(v); + char buf[2300]; + int const t = int(total_milliseconds(clock_type::now() - start)); + std::snprintf(buf, sizeof(buf), "\x1b[3%dm%05d: [%d] %s\x1b[0m" + , (it->second % 7) + 1, t, it->second, usr); + prepend_time = (usr[len-1] == '\n'); + fputs(buf, stderr); + } + +#endif // DEBUG_DISK_THREAD + + open_mode_t file_flags_for_job(disk_io_job* j + , bool const coalesce_buffers) + { + open_mode_t ret = open_mode_t{}; + if (!(j->flags & disk_interface::sequential_access)) ret |= open_mode::random_access; + if (coalesce_buffers) ret |= open_mode::coalesce_buffers; + return ret; + } + + // the do_* functions can return this to indicate the disk + // job did not complete immediately, and shouldn't be posted yet + constexpr status_t defer_handler = static_cast(200); + + // the job cannot be completed right now, put it back in the + // queue and try again later + constexpr status_t retry_job = static_cast(201); + + + struct piece_refcount_holder + { + explicit piece_refcount_holder(cached_piece_entry* p) : m_pe(p) + { ++m_pe->piece_refcount; } + ~piece_refcount_holder() + { + if (!m_executed) + { + TORRENT_PIECE_ASSERT(m_pe->piece_refcount > 0, m_pe); + --m_pe->piece_refcount; + } + } + piece_refcount_holder(piece_refcount_holder const&) = delete; + piece_refcount_holder& operator=(piece_refcount_holder const&) = delete; + void release() + { + TORRENT_ASSERT(!m_executed); + m_executed = true; + TORRENT_PIECE_ASSERT(m_pe->piece_refcount > 0, m_pe); + --m_pe->piece_refcount; + } + private: + cached_piece_entry* m_pe; + bool m_executed = false; + }; + + template + struct scoped_unlocker_impl + { + explicit scoped_unlocker_impl(Lock& l) : m_lock(&l) { m_lock->unlock(); } + ~scoped_unlocker_impl() { if (m_lock) m_lock->lock(); } + scoped_unlocker_impl(scoped_unlocker_impl&& rhs) noexcept : m_lock(rhs.m_lock) + { rhs.m_lock = nullptr; } + scoped_unlocker_impl& operator=(scoped_unlocker_impl&& rhs) noexcept + { + if (&rhs == this) return *this; + if (m_lock) m_lock->lock(); + m_lock = rhs.m_lock; + rhs.m_lock = nullptr; + return *this; + } + private: + Lock* m_lock; + }; + + template + scoped_unlocker_impl scoped_unlock(Lock& l) + { return scoped_unlocker_impl(l); } + + } // anonymous namespace + +constexpr disk_job_flags_t disk_interface::force_copy; +constexpr disk_job_flags_t disk_interface::sequential_access; +constexpr disk_job_flags_t disk_interface::volatile_read; +constexpr disk_job_flags_t disk_interface::cache_hit; + +// ------- disk_io_thread ------ + + disk_io_thread::disk_io_thread(io_service& ios, aux::session_settings const& sett, counters& cnt) + : m_generic_io_jobs(*this) + , m_generic_threads(m_generic_io_jobs, ios) + , m_hash_io_jobs(*this) + , m_hash_threads(m_hash_io_jobs, ios) + , m_settings(sett) + , m_disk_cache(ios, std::bind(&disk_io_thread::trigger_cache_trim, this)) + , m_stats_counters(cnt) + , m_ios(ios) + { + settings_updated(); + } + + storage_interface* disk_io_thread::get_torrent(storage_index_t const storage) + { + TORRENT_ASSERT(m_magic == 0x1337); + return m_torrents[storage].get(); + } + + std::vector disk_io_thread::get_status(storage_index_t const st) const + { + return m_file_pool.get_status(st); + } + + storage_holder disk_io_thread::new_torrent(storage_constructor_type sc + , storage_params p, std::shared_ptr const& owner) + { + std::unique_ptr storage(sc(p, m_file_pool)); + storage->set_owner(owner); + + TORRENT_ASSERT(storage); + if (m_free_slots.empty()) + { + // make sure there's always space in here to add another free slot. + // stopping a torrent should never fail because it needs to allocate memory + m_free_slots.reserve(m_torrents.size() + 1); + storage_index_t const idx = m_torrents.end_index(); + m_torrents.emplace_back(std::move(storage)); + m_torrents.back()->set_storage_index(idx); + return storage_holder(idx, *this); + } + else + { + storage_index_t const idx = m_free_slots.back(); + m_free_slots.pop_back(); + (m_torrents[idx] = std::move(storage))->set_storage_index(idx); + return storage_holder(idx, *this); + } + } + + void disk_io_thread::remove_torrent(storage_index_t const idx) + { + auto& pos = m_torrents[idx]; + if (pos->dec_refcount() == 0) + { + pos.reset(); + m_free_slots.push_back(idx); + } + } + +#if TORRENT_USE_ASSERTS + disk_io_thread::~disk_io_thread() + { + DLOG("destructing disk_io_thread\n"); + + TORRENT_ASSERT(m_magic == 0x1337); + m_magic = 0xdead; + TORRENT_ASSERT(m_generic_io_jobs.m_queued_jobs.empty()); + TORRENT_ASSERT(m_hash_io_jobs.m_queued_jobs.empty()); + } +#endif + + void disk_io_thread::abort(bool const wait) + { + DLOG("disk_io_thread::abort: (wait: %d)\n", int(wait)); + + // first make sure queued jobs have been submitted + // otherwise the queue may not get processed + submit_jobs(); + + // abuse the job mutex to make setting m_abort and checking the thread count atomic + // see also the comment in thread_fun + std::unique_lock l(m_job_mutex); + if (m_abort.exchange(true)) return; + bool const no_threads = m_generic_threads.num_threads() == 0 + && m_hash_threads.num_threads() == 0; + // abort outstanding jobs belonging to this torrent + + DLOG("aborting hash jobs\n"); + for (auto i = m_hash_io_jobs.m_queued_jobs.iterate(); i.get(); i.next()) + i.get()->flags |= disk_io_job::aborted; + l.unlock(); + + // if there are no disk threads, we can't wait for the jobs here, because + // we'd stall indefinitely + if (no_threads) + { + abort_jobs(); + } + + DLOG("aborting thread pools\n"); + // even if there are no threads it doesn't hurt to abort the pools + // it prevents threads from being started after an abort which is a good + // defensive programming measure + m_generic_threads.abort(wait); + m_hash_threads.abort(wait); + } + + void disk_io_thread::reclaim_blocks(span refs) + { + TORRENT_ASSERT(m_magic == 0x1337); + + std::unique_lock l(m_cache_mutex); + for (auto ref : refs) + { + auto& pos = m_torrents[ref.storage]; + storage_interface* st = pos.get(); + TORRENT_ASSERT(st != nullptr); + m_disk_cache.reclaim_block(st, ref); + if (st->dec_refcount() == 0) + { + pos.reset(); + m_free_slots.push_back(ref.storage); + } + } + } + + void disk_io_thread::settings_updated() + { + TORRENT_ASSERT(m_magic == 0x1337); + std::unique_lock l(m_cache_mutex); + m_disk_cache.set_settings(m_settings); + m_file_pool.resize(m_settings.get_int(settings_pack::file_pool_size)); + + int const num_threads = m_settings.get_int(settings_pack::aio_threads); + // add one hasher thread for every three generic threads + int const num_hash_threads = num_threads / hasher_thread_divisor; + + DLOG("set_max_threads(%d, %d)\n", num_threads - num_hash_threads + , num_hash_threads); + m_generic_threads.set_max_threads(num_threads - num_hash_threads); + m_hash_threads.set_max_threads(num_hash_threads); + } + + // flush all blocks that are below p->hash.offset, since we've + // already hashed those blocks, they won't cause any read-back + int disk_io_thread::try_flush_hashed(cached_piece_entry* p, int const cont_block + , jobqueue_t& completed_jobs, std::unique_lock& l) + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(l.owns_lock()); + TORRENT_ASSERT(cont_block > 0); + if (p->hash == nullptr && !p->hashing_done) + { + DLOG("try_flush_hashed: (%d) no hash\n", int(p->piece)); + return 0; + } + + if (p->num_dirty == 0) + { + DLOG("try_flush_hashed: no dirty blocks\n"); + return 0; + } + + // end is one past the end + // round offset up to include the last block, which might + // have an odd size + int end = p->hashing_done ? int(p->blocks_in_piece) : (p->hash->offset + default_block_size - 1) / default_block_size; + + // nothing has been hashed yet, don't flush anything + if (end == 0 && !p->need_readback) return 0; + + // the number of contiguous blocks we need to be allowed to flush + int block_limit = std::min(cont_block, int(p->blocks_in_piece)); + + // if everything has been hashed, we might as well flush everything + // regardless of the contiguous block restriction + if (end == int(p->blocks_in_piece)) block_limit = 1; + + if (p->need_readback) + { + // if this piece needs a read-back already, don't + // try to keep it from being flushed, since we'll + // need to read it back regardless. Flushing will + // save blocks that can be used to "save" other + // pieces from being flushed prematurely + end = int(p->blocks_in_piece); + } + + TORRENT_ASSERT(end <= p->blocks_in_piece); + + // count number of blocks that would be flushed + int num_blocks = 0; + for (int i = end - 1; i >= 0; --i) + num_blocks += (p->blocks[i].dirty && !p->blocks[i].pending); + + // we did not satisfy the block_limit requirement + // i.e. too few blocks would be flushed at this point, put it off + if (block_limit > num_blocks) return 0; + + // if the cache line size is larger than a whole piece, hold + // off flushing this piece until enough adjacent pieces are + // full as well. + int cont_pieces = int(cont_block / p->blocks_in_piece); + + // at this point, we may enforce flushing full cache stripes even when + // they span multiple pieces. This won't necessarily work in the general + // case, because it assumes that the piece picker will have an affinity + // to download whole stripes at a time. This is why this setting is turned + // off by default, flushing only one piece at a time + + if (cont_pieces <= 1 || m_settings.get_bool(settings_pack::allow_partial_disk_writes)) + { + DLOG("try_flush_hashed: (%d) blocks_in_piece: %d end: %d\n" + , int(p->piece), int(p->blocks_in_piece), end); + + return flush_range(p, 0, end, completed_jobs, l); + } + + // piece range + piece_index_t const range_start((static_cast(p->piece) / cont_pieces) * cont_pieces); + piece_index_t const range_end(std::min(static_cast(range_start) + + cont_pieces, p->storage->files().num_pieces())); + + // look through all the pieces in this range to see if + // they are ready to be flushed. If so, flush them all, + // otherwise, hold off + bool range_full = true; + + cached_piece_entry* first_piece = nullptr; + DLOG("try_flush_hashed: multi-piece: "); + for (piece_index_t i = range_start; i != range_end; ++i) + { + if (i == p->piece) + { + if (i == range_start) first_piece = p; + DLOG("[%d self] ", static_cast(i)); + continue; + } + cached_piece_entry* pe = m_disk_cache.find_piece(p->storage.get(), i); + if (pe == nullptr) + { + DLOG("[%d nullptr] ", static_cast(i)); + range_full = false; + break; + } + if (i == range_start) first_piece = pe; + + // if this is a read-cache piece, it has already been flushed + if (pe->cache_state != cached_piece_entry::write_lru) + { + DLOG("[%d read-cache] ", static_cast(i)); + continue; + } + int hash_cursor = pe->hash ? pe->hash->offset / default_block_size : 0; + + // if the piece has all blocks, and they're all dirty, and they've + // all been hashed, then this piece is eligible for flushing + if (pe->num_dirty == pe->blocks_in_piece + && (pe->hashing_done + || hash_cursor == pe->blocks_in_piece + || m_settings.get_bool(settings_pack::disable_hash_checks))) + { + DLOG("[%d hash-done] ", static_cast(i)); + continue; + } + +#if DEBUG_DISK_THREAD + if (pe->num_dirty < pe->blocks_in_piece) + { + DLOG("[%d dirty:%d] ", static_cast(i), int(pe->num_dirty)); + } + else if (pe->hashing_done == 0 && hash_cursor < pe->blocks_in_piece) + { + DLOG("[%d cursor:%d] ", static_cast(i), hash_cursor); + } + else + { + DLOG("[%d xx] ", static_cast(i)); + } +#endif + + // TODO: in this case, the piece should probably not be flushed yet. are there + // any more cases where it should? + + range_full = false; + break; + } + + if (!range_full) + { + DLOG("not flushing\n"); + return 0; + } + DLOG("\n"); + + // now, build a iovec for all pieces that we want to flush, so that they + // can be flushed in a single atomic operation. This is especially important + // when there are more than 1 disk thread, to make sure they don't + // interleave in undesired places. + // in order to remember where each piece boundary ended up in the iovec, + // we keep the indices in the iovec_offset array + + cont_pieces = static_cast(range_end) - static_cast(range_start); + int const blocks_to_flush = int(p->blocks_in_piece * cont_pieces); + TORRENT_ALLOCA(iov, iovec_t, blocks_to_flush); + TORRENT_ALLOCA(flushing, int, blocks_to_flush); + // this is the offset into iov and flushing for each piece + TORRENT_ALLOCA(iovec_offset, int, cont_pieces + 1); + int iov_len = 0; + // this is the block index each piece starts at + int block_start = 0; + // keep track of the pieces that have had their refcount incremented + // so we know to decrement them later + TORRENT_ALLOCA(refcount_pieces, int, cont_pieces); + piece_index_t piece = range_start; + for (int i = 0; i < cont_pieces; ++i, ++piece) + { + cached_piece_entry* pe; + if (piece == p->piece) pe = p; + else pe = m_disk_cache.find_piece(p->storage.get(), piece); + if (pe == nullptr + || pe->cache_state != cached_piece_entry::write_lru) + { + refcount_pieces[i] = 0; + iovec_offset[i] = iov_len; + block_start += int(p->blocks_in_piece); + continue; + } + + iovec_offset[i] = iov_len; + refcount_pieces[i] = 1; + TORRENT_ASSERT_VAL(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::flushing, -1)); +#endif + ++pe->piece_refcount; + + iov_len += build_iovec(pe, 0, p->blocks_in_piece + , iov.subspan(iov_len), flushing.subspan(iov_len), block_start); + + block_start += int(p->blocks_in_piece); + } + iovec_offset[cont_pieces] = iov_len; + + // ok, now we have one (or more, but hopefully one) contiguous + // iovec array. Now, flush it to disk + + TORRENT_ASSERT(first_piece != nullptr); + + if (iov_len == 0) + { + // we may not exit here if we incremented any piece refcounters + TORRENT_ASSERT(cont_pieces == 0); + DLOG(" iov_len: 0 cont_pieces: %d range_start: %d range_end: %d\n" + , cont_pieces, static_cast(range_start), static_cast(range_end)); + return 0; + } + + storage_error error; + { + // unlock while we're performing the actual disk I/O + // then lock again + auto unlock = scoped_unlock(l); + flush_iovec(first_piece, iov, flushing, iov_len, error); + } + + block_start = 0; + + piece = range_start; + for (int i = 0; i < cont_pieces; ++i, ++piece) + { + cached_piece_entry* pe; + if (piece == p->piece) pe = p; + else pe = m_disk_cache.find_piece(p->storage.get(), piece); + if (pe == nullptr) + { + DLOG("iovec_flushed: piece %d gone!\n", static_cast(piece)); + TORRENT_PIECE_ASSERT(refcount_pieces[i] == 0, pe); + block_start += int(p->blocks_in_piece); + continue; + } + if (refcount_pieces[i]) + { + TORRENT_PIECE_ASSERT(pe->piece_refcount > 0, pe); + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + } + const int block_diff = iovec_offset[i + 1] - iovec_offset[i]; + iovec_flushed(pe, flushing.subspan(iovec_offset[i]).data(), block_diff + , block_start, error, completed_jobs); + block_start += int(p->blocks_in_piece); + } + + // if the cache is under high pressure, we need to evict + // the blocks we just flushed to make room for more write pieces + int const evict = m_disk_cache.num_to_evict(0); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + return iov_len; + } + + // iov and flushing are expected to be arrays to at least pe->blocks_in_piece + // items in them. Returns the number of iovecs written to the iov array. + // The same number of block indices are written to the flushing array. These + // are block indices that the respective iovec structure refers to, since + // we might not be able to flush everything as a single contiguous block, + // the block indices indicates where the block run is broken + // the cache needs to be locked when calling this function + // block_base_index is the offset added to every block index written to + // the flushing array. This can be used when building iovecs spanning + // multiple pieces, the subsequent pieces after the first one, must have + // their block indices start where the previous one left off + int disk_io_thread::build_iovec(cached_piece_entry* pe, int const start, int end + , span iov, span flushing, int const block_base_index) + { + DLOG("build_iovec: piece=%d [%d, %d)\n" + , int(pe->piece), start, end); + TORRENT_PIECE_ASSERT(start >= 0, pe); + TORRENT_PIECE_ASSERT(start < end, pe); + end = std::min(end, int(pe->blocks_in_piece)); + + int const piece_size = pe->storage->files().piece_size(pe->piece); + TORRENT_PIECE_ASSERT(piece_size > 0, pe); + + int iov_len = 0; + // the blocks we're flushing + int num_flushing = 0; + +#if DEBUG_DISK_THREAD + DLOG("build_iov: piece: %d [", int(pe->piece)); + for (int i = 0; i < start; ++i) DLOG("."); +#endif + + int size_left = piece_size; + for (int i = start; i < end; ++i, size_left -= default_block_size) + { + TORRENT_PIECE_ASSERT(size_left > 0, pe); + // don't flush blocks that are empty (buf == 0), not dirty + // (read cache blocks), or pending (already being written) + if (pe->blocks[i].buf == nullptr + || pe->blocks[i].pending + || !pe->blocks[i].dirty) + { + DLOG("-"); + continue; + } + + // if we fail to lock the block, it' no longer in the cache + bool const locked = m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_flushing); + + // it should always succeed, since it's a dirty block, and + // should never have been marked as volatile + TORRENT_ASSERT(locked); + TORRENT_ASSERT(pe->cache_state != cached_piece_entry::volatile_read_lru); + TORRENT_UNUSED(locked); + + flushing[num_flushing++] = i + block_base_index; + iov[iov_len] = { pe->blocks[i].buf, std::min(default_block_size, size_left) }; + ++iov_len; + pe->blocks[i].pending = true; + + DLOG("x"); + } + DLOG("]\n"); + + TORRENT_PIECE_ASSERT(iov_len == num_flushing, pe); + return aux::numeric_cast(iov_len); + } + + // does the actual writing to disk + // the cached_piece_entry is supposed to point to the + // first piece, if the iovec spans multiple pieces + void disk_io_thread::flush_iovec(cached_piece_entry* pe + , span iov, span flushing + , int const num_blocks, storage_error& error) + { + TORRENT_PIECE_ASSERT(!error, pe); + TORRENT_PIECE_ASSERT(num_blocks > 0, pe); + m_stats_counters.inc_stats_counter(counters::num_writing_threads, 1); + + time_point const start_time = clock_type::now(); + +#if DEBUG_DISK_THREAD + DLOG("flush_iovec: piece: %d [ ", int(pe->piece)); + for (int i = 0; i < num_blocks; ++i) + DLOG("%d ", flushing[i]); + DLOG("]\n"); +#endif + + open_mode_t const file_flags = m_settings.get_bool(settings_pack::coalesce_writes) + ? open_mode::coalesce_buffers : open_mode_t{}; + + // issue the actual write operation + auto iov_start = iov; + int flushing_start = 0; + piece_index_t const piece = pe->piece; + int const blocks_in_piece = int(pe->blocks_in_piece); + bool failed = false; + for (int i = 1; i <= num_blocks; ++i) + { + if (i < num_blocks && flushing[i] == flushing[i - 1] + 1) continue; + int const ret = pe->storage->writev( + iov_start.first(i - flushing_start) + , piece_index_t(static_cast(piece) + flushing[flushing_start] / blocks_in_piece) + , (flushing[flushing_start] % blocks_in_piece) * default_block_size + , file_flags, error); + if (ret < 0 || error) failed = true; + iov_start = iov.subspan(i); + flushing_start = i; + } + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, -1); + + { + std::lock_guard l(m_need_tick_mutex); + if (!pe->storage->set_need_tick()) + m_need_tick.emplace_back(aux::time_now() + minutes(2), pe->storage); + } + + if (!failed) + { + TORRENT_PIECE_ASSERT(!error, pe); + std::int64_t const write_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_written, num_blocks); + m_stats_counters.inc_stats_counter(counters::num_write_ops); + m_stats_counters.inc_stats_counter(counters::disk_write_time, write_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, write_time); +#if DEBUG_DISK_THREAD + DLOG("flush_iovec: %d\n", num_blocks); +#endif + } +#if DEBUG_DISK_THREAD + else + { + DLOG("flush_iovec: error: (%d) %s\n" + , error.ec.value(), error.ec.message().c_str()); + } +#endif + } + + // It is necessary to call this function with the blocks produced by + // build_iovec, to reset their state to not being flushed anymore + // the cache needs to be locked when calling this function + bool disk_io_thread::iovec_flushed(cached_piece_entry* pe + , int* flushing, int const num_blocks, int const block_offset + , storage_error const& error + , jobqueue_t& completed_jobs) + { + for (int i = 0; i < num_blocks; ++i) + flushing[i] -= block_offset; + +#if DEBUG_DISK_THREAD + DLOG("iovec_flushed: piece: %d block_offset: %d [ " + , static_cast(pe->piece), block_offset); + for (int i = 0; i < num_blocks; ++i) + DLOG("%d ", flushing[i]); + DLOG("]\n"); +#endif + if (m_disk_cache.blocks_flushed(pe, flushing, num_blocks)) + return true; + + if (error) + { + fail_jobs_impl(error, pe->jobs, completed_jobs); + } + else + { + disk_io_job* j = pe->jobs.get_all(); + while (j) + { + disk_io_job* next = j->next; + j->next = nullptr; + TORRENT_PIECE_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + if (j->completed(pe)) + { + j->ret = status_t::no_error; + j->error = error; + completed_jobs.push_back(j); + } + else + { + pe->jobs.push_back(j); + } + j = next; + } + } + + return false; + } + + // issues write operations for blocks in the given + // range on the given piece. + int disk_io_thread::flush_range(cached_piece_entry* pe, int const start, int const end + , jobqueue_t& completed_jobs, std::unique_lock& l) + { + TORRENT_ASSERT(l.owns_lock()); + + DLOG("flush_range: piece=%d [%d, %d)\n" + , static_cast(pe->piece), start, end); + TORRENT_PIECE_ASSERT(start >= 0, pe); + TORRENT_PIECE_ASSERT(start < end, pe); + + TORRENT_ALLOCA(iov, iovec_t, pe->blocks_in_piece); + TORRENT_ALLOCA(flushing, int, pe->blocks_in_piece); + int const iov_len = build_iovec(pe, start, end, iov, flushing, 0); + if (iov_len == 0) return 0; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::flush_range, -1)); +#endif + + storage_error error; + { + piece_refcount_holder refcount_holder(pe); + auto unlocker = scoped_unlock(l); + + flush_iovec(pe, iov, flushing, iov_len, error); + } + + if (!iovec_flushed(pe, flushing.data(), iov_len, 0, error, completed_jobs)) + m_disk_cache.maybe_free_piece(pe); + + // if the cache is under high pressure, we need to evict + // the blocks we just flushed to make room for more write pieces + int const evict = m_disk_cache.num_to_evict(0); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + return iov_len; + } + + void disk_io_thread::fail_jobs(storage_error const& e, jobqueue_t& jobs_) + { + jobqueue_t jobs; + fail_jobs_impl(e, jobs_, jobs); + if (!jobs.empty()) add_completed_jobs(jobs); + } + + void disk_io_thread::fail_jobs_impl(storage_error const& e, jobqueue_t& src, jobqueue_t& dst) + { + while (!src.empty()) + { + disk_io_job* j = src.pop_front(); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + j->ret = status_t::fatal_disk_error; + j->error = e; + dst.push_back(j); + } + } + + void disk_io_thread::flush_piece(cached_piece_entry* pe, std::uint32_t const flags + , jobqueue_t& completed_jobs, std::unique_lock& l) + { + TORRENT_ASSERT(l.owns_lock()); + if (flags & flush_delete_cache) + { + // delete dirty blocks and post handlers with + // operation_aborted error code + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , pe->jobs, completed_jobs); + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , pe->read_jobs, completed_jobs); + m_disk_cache.abort_dirty(pe); + } + else if ((flags & flush_write_cache) && pe->num_dirty > 0) + { + // issue write commands + flush_range(pe, 0, INT_MAX, completed_jobs, l); + + // if we're also flushing the read cache, this piece + // should be removed as soon as all write jobs finishes + // otherwise it will turn into a read piece + } + + // mark_for_eviction may erase the piece from the cache, that's + // why we don't have the 'i' iterator referencing it at this point + if (flags & (flush_read_cache | flush_delete_cache)) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), pe->jobs, completed_jobs); + // we're removing the torrent, don't keep any entries around in the + // ghost list + m_disk_cache.mark_for_eviction(pe, block_cache::disallow_ghost); + } + } + + void disk_io_thread::flush_cache(storage_interface* storage, std::uint32_t const flags + , jobqueue_t& completed_jobs, std::unique_lock& l) + { + if (storage != nullptr) + { + auto const& pieces = storage->cached_pieces(); + std::vector piece_index; + piece_index.reserve(pieces.size()); + for (auto const& p : pieces) + { + TORRENT_ASSERT(p.get_storage() == storage); + if (p.get_storage() != storage) continue; + piece_index.push_back(p.piece); + } + + for (auto idx : piece_index) + { + cached_piece_entry* pe = m_disk_cache.find_piece(storage, idx); + if (pe == nullptr) continue; + TORRENT_PIECE_ASSERT(pe->storage.get() == storage, pe); + flush_piece(pe, flags, completed_jobs, l); + } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(l.owns_lock()); + // if the user asked to delete the cache for this storage + // we really should not have any pieces left. This is only called + // from disk_io_thread::do_delete, which is a fence job and should + // have any other jobs active, i.e. there should not be any references + // keeping pieces or blocks alive + if ((flags & flush_delete_cache) && (flags & flush_expect_clear)) + { + auto const& storage_pieces = storage->cached_pieces(); + for (auto const& p : storage_pieces) + { + cached_piece_entry* pe = m_disk_cache.find_piece(storage, p.piece); + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + } + } +#endif + } + else + { + auto range = m_disk_cache.all_pieces(); + while (range.first != range.second) + { + // TODO: it would be nice to optimize this by having the cache + // pieces also ordered by + if ((flags & (flush_read_cache | flush_delete_cache)) == 0) + { + // if we're not flushing the read cache, and not deleting the + // cache, skip pieces with no dirty blocks, i.e. read cache + // pieces + while (range.first->num_dirty == 0) + { + ++range.first; + if (range.first == range.second) return; + } + } + cached_piece_entry* pe = const_cast(&*range.first); + flush_piece(pe, flags, completed_jobs, l); + range = m_disk_cache.all_pieces(); + } + } + } + + // this is called if we're exceeding (or about to exceed) the cache + // size limit. This means we should not restrict ourselves to contiguous + // blocks of write cache line size, but try to flush all old blocks + // this is why we pass in 1 as cont_block to the flushing functions + void disk_io_thread::try_flush_write_blocks(int num, jobqueue_t& completed_jobs + , std::unique_lock& l) + { + DLOG("try_flush_write_blocks: %d\n", num); + + auto const range = m_disk_cache.write_lru_pieces(); + aux::vector, piece_index_t>> pieces; + pieces.reserve(m_disk_cache.num_write_lru_pieces()); + + for (auto p = range; p.get() && num > 0; p.next()) + { + cached_piece_entry* e = p.get(); + if (e->num_dirty == 0) continue; + pieces.emplace_back(e->storage, e->piece); + } + + for (auto const& p : pieces) + { + // TODO: instead of doing a lookup each time through the loop, save + // cached_piece_entry pointers with piece_refcount incremented to pin them + cached_piece_entry* pe = m_disk_cache.find_piece(p.first.get(), p.second); + if (pe == nullptr) continue; + + // another thread may flush this piece while we're looping and + // evict it into a read piece and then also evict it to ghost + if (pe->cache_state != cached_piece_entry::write_lru) continue; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::try_flush_write_blocks, -1)); +#endif + ++pe->piece_refcount; + kick_hasher(pe, l); + num -= try_flush_hashed(pe, 1, completed_jobs, l); + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + } + + // when the write cache is under high pressure, it is likely + // counter productive to actually do this, since a piece may + // not have had its flush_hashed job run on it + // so only do it if no other thread is currently flushing + + if (num == 0 || m_stats_counters[counters::num_writing_threads] > 0) return; + + // if we still need to flush blocks, start over and flush + // everything in LRU order (degrade to lru cache eviction) + for (auto const& p : pieces) + { + cached_piece_entry* pe = m_disk_cache.find_piece(p.first.get(), p.second); + if (pe == nullptr) continue; + if (pe->num_dirty == 0) continue; + + // another thread may flush this piece while we're looping and + // evict it into a read piece and then also evict it to ghost + if (pe->cache_state != cached_piece_entry::write_lru) continue; + + // don't flush blocks that are being hashed by another thread + if (pe->num_dirty == 0 || pe->hashing) continue; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::try_flush_write_blocks2, -1)); +#endif + ++pe->piece_refcount; + + num -= flush_range(pe, 0, INT_MAX, completed_jobs, l); + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + } + } + + void disk_io_thread::flush_expired_write_blocks(jobqueue_t& completed_jobs + , std::unique_lock& l) + { + DLOG("flush_expired_write_blocks\n"); + + time_point const now = aux::time_now(); + time_duration const expiration_limit = seconds(m_settings.get_int(settings_pack::cache_expiry)); + +#if TORRENT_USE_ASSERTS + time_point timeout = min_time(); +#endif + + TORRENT_ALLOCA(to_flush, cached_piece_entry*, 200); + int num_flush = 0; + + for (list_iterator p = m_disk_cache.write_lru_pieces(); p.get(); p.next()) + { + cached_piece_entry* e = p.get(); +#if TORRENT_USE_ASSERTS + TORRENT_PIECE_ASSERT(e->expire >= timeout, e); + timeout = e->expire; +#endif + + // since we're iterating in order of last use, if this piece + // shouldn't be evicted, none of the following ones will either + if (now - e->expire < expiration_limit) break; + if (e->num_dirty == 0) continue; + + TORRENT_PIECE_ASSERT(e->cache_state <= cached_piece_entry::read_lru1 || e->cache_state == cached_piece_entry::read_lru2, e); +#if TORRENT_USE_ASSERTS + e->piece_log.push_back(piece_log_t(piece_log_t::flush_expired, -1)); +#endif + ++e->piece_refcount; + // We can rely on the piece entry not being removed by + // incrementing the piece_refcount + to_flush[num_flush++] = e; + if (num_flush == 200) break; + } + + for (int i = 0; i < num_flush; ++i) + { + flush_range(to_flush[i], 0, INT_MAX, completed_jobs, l); + TORRENT_ASSERT(to_flush[i]->piece_refcount > 0); + --to_flush[i]->piece_refcount; + m_disk_cache.maybe_free_piece(to_flush[i]); + } + } + + namespace { + + using disk_io_fun_t = status_t (disk_io_thread::*)(disk_io_job*, jobqueue_t&); + + // this is a jump-table for disk I/O jobs + std::array const job_functions = + {{ + &disk_io_thread::do_read, + &disk_io_thread::do_write, + &disk_io_thread::do_hash, + &disk_io_thread::do_move_storage, + &disk_io_thread::do_release_files, + &disk_io_thread::do_delete_files, + &disk_io_thread::do_check_fastresume, + &disk_io_thread::do_rename_file, + &disk_io_thread::do_stop_torrent, + &disk_io_thread::do_flush_piece, + &disk_io_thread::do_flush_hashed, + &disk_io_thread::do_flush_storage, + &disk_io_thread::do_trim_cache, + &disk_io_thread::do_file_priority, + &disk_io_thread::do_clear_piece + }}; + + } // anonymous namespace + + // evict and/or flush blocks if we're exceeding the cache size + // or used to exceed it and haven't dropped below the low watermark yet + // the low watermark is dynamic, based on the number of peers waiting + // on buffers to free up. The more waiters, the lower the low watermark + // is. Because of this, the target for flushing jobs may have dropped + // below the number of blocks we flushed by the time we're done flushing + // that's why we need to call this fairly often. Both before and after + // a disk job is executed + void disk_io_thread::check_cache_level(std::unique_lock& l, jobqueue_t& completed_jobs) + { + // when the read cache is disabled, always try to evict all read cache + // blocks + if (!m_settings.get_bool(settings_pack::use_read_cache)) + { + int const evict = m_disk_cache.read_cache_size(); + m_disk_cache.try_evict_blocks(evict); + } + + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) + { + evict = m_disk_cache.try_evict_blocks(evict); + // don't evict write jobs if at least one other thread + // is flushing right now. Doing so could result in + // unnecessary flushing of the wrong pieces + if (evict > 0 && m_stats_counters[counters::num_writing_threads] == 0) + { + try_flush_write_blocks(evict, completed_jobs, l); + } + } + } + + void disk_io_thread::perform_job(disk_io_job* j, jobqueue_t& completed_jobs) + { + TORRENT_ASSERT(j->next == nullptr); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + +#if DEBUG_DISK_THREAD + { + std::unique_lock l(m_cache_mutex); + + DLOG("perform_job job: %s ( %s) piece: %d offset: %d outstanding: %d\n" + , job_name(j->action) + , (j->flags & disk_io_job::fence) ? "fence ": "" + , static_cast(j->piece), j->d.io.offset + , j->storage ? j->storage->num_outstanding_jobs() : -1); + } +#endif + + std::shared_ptr storage = j->storage; + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + if (j->storage) + { + std::unique_lock l(m_cache_mutex); + auto const& pieces = j->storage->cached_pieces(); + for (auto const& p : pieces) + TORRENT_ASSERT(p.storage == j->storage); + } +#endif + // TODO: 4 instead of doing this. pass in the settings to each storage_interface + // call. Each disk thread could hold its most recent understanding of the settings + // in a shared_ptr, and update it every time it wakes up from a job. That way + // each access to the settings won't require a std::mutex to be held. + if (storage && storage->m_settings == nullptr) + storage->m_settings = &m_settings; + + TORRENT_ASSERT(static_cast(j->action) < int(job_functions.size())); + + m_stats_counters.inc_stats_counter(counters::num_running_disk_jobs, 1); + + // call disk function + // TODO: in the future, propagate exceptions back to the handlers + status_t ret = status_t::no_error; + try + { + int const idx = static_cast(j->action); + ret = (this->*(job_functions[static_cast(idx)]))(j, completed_jobs); + } + catch (boost::system::system_error const& err) + { + ret = status_t::fatal_disk_error; + j->error.ec = err.code(); + j->error.operation = operation_t::exception; + } + catch (std::bad_alloc const&) + { + ret = status_t::fatal_disk_error; + j->error.ec = errors::no_memory; + j->error.operation = operation_t::exception; + } + catch (std::exception const&) + { + ret = status_t::fatal_disk_error; + j->error.ec = boost::asio::error::fault; + j->error.operation = operation_t::exception; + } + + // note that -2 errors are OK + TORRENT_ASSERT(ret != status_t::fatal_disk_error + || (j->error.ec && j->error.operation != operation_t::unknown)); + + m_stats_counters.inc_stats_counter(counters::num_running_disk_jobs, -1); + + std::unique_lock l(m_cache_mutex); + if (m_cache_check_state == cache_check_idle) + { + m_cache_check_state = cache_check_active; + while (m_cache_check_state != cache_check_idle) + { + check_cache_level(l, completed_jobs); + TORRENT_ASSERT(l.owns_lock()); + --m_cache_check_state; + } + } + else + { + m_cache_check_state = cache_check_reinvoke; + } + l.unlock(); + + if (ret == retry_job) + { + job_queue& q = queue_for_job(j); + + std::unique_lock l2(m_job_mutex); + // to avoid busy looping here, give up + // our quanta in case there aren't any other + // jobs to run in between + + // TODO: a potentially more efficient solution would be to have a special + // queue for retry jobs, that's only ever run when a job completes, in + // any thread. It would only work if counters::num_running_disk_jobs > 0 + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + bool const need_sleep = q.m_queued_jobs.empty(); + q.m_queued_jobs.push_back(j); + l2.unlock(); + if (need_sleep) std::this_thread::yield(); + return; + } + + if (ret == defer_handler) return; + + j->ret = ret; + + completed_jobs.push_back(j); + } + + status_t disk_io_thread::do_uncached_read(disk_io_job* j) + { + j->argument = disk_buffer_holder(*this, m_disk_cache.allocate_buffer("send buffer"), 0x4000); + auto& buffer = boost::get(j->argument); + if (buffer.get() == nullptr) + { + j->error.ec = error::no_memory; + j->error.operation = operation_t::alloc_cache_piece; + return status_t::fatal_disk_error; + } + + time_point const start_time = clock_type::now(); + + open_mode_t const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + iovec_t b = {buffer.get(), j->d.io.buffer_size}; + + int const ret = j->storage->readv(b + , j->piece, j->d.io.offset, file_flags, j->error); + + TORRENT_ASSERT(ret >= 0 || (j->error.ec && j->error.operation != operation_t::unknown)); + TORRENT_UNUSED(ret); + + if (!j->error.ec) + { + std::int64_t const read_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_read_back); + m_stats_counters.inc_stats_counter(counters::num_blocks_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + return status_t::no_error; + } + + status_t disk_io_thread::do_read(disk_io_job* j, jobqueue_t& completed_jobs) + { + int const piece_size = j->storage->files().piece_size(j->piece); + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + int const iov_len = m_disk_cache.pad_job(j, blocks_in_piece + , m_settings.get_int(settings_pack::read_cache_line_size)); + + TORRENT_ALLOCA(iov, iovec_t, iov_len); + + std::unique_lock l(m_cache_mutex); + + int const evict = m_disk_cache.num_to_evict(iov_len); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == nullptr) + { + l.unlock(); + return do_uncached_read(j); + } + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + l.unlock(); + + // then we'll actually allocate the buffers + int ret = m_disk_cache.allocate_iovec(iov); + + if (ret < 0) + { + status_t const s = do_uncached_read(j); + + std::unique_lock l2(m_cache_mutex); + pe = m_disk_cache.find_piece(j); + if (pe != nullptr) maybe_issue_queued_read_jobs(pe, completed_jobs); + return s; + } + + // free buffers at the end of the scope + auto iov_dealloc = aux::scope_end([&]{ m_disk_cache.free_iovec(iov); }); + + // this is the offset that's aligned to block boundaries + int const adjusted_offset = aux::numeric_cast(j->d.io.offset & ~(default_block_size - 1)); + + // if this is the last piece, adjust the size of the + // last buffer to match up + iov[iov_len - 1] = iov[iov_len - 1].first( + std::min(piece_size - adjusted_offset - (iov_len - 1) + * default_block_size, default_block_size)); + TORRENT_ASSERT(iov[iov_len - 1].size() > 0); + + // at this point, all the buffers are allocated and iov is initialized + // and the blocks have their refcounters incremented, so no other thread + // can remove them. We can now release the cache std::mutex and dive into the + // disk operations. + + open_mode_t const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + time_point const start_time = clock_type::now(); + + ret = j->storage->readv(iov + , j->piece, int(adjusted_offset), file_flags, j->error); + + TORRENT_ASSERT(ret >= 0 || (j->error.ec && j->error.operation != operation_t::unknown)); + + if (!j->error.ec) + { + std::int64_t const read_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_read, iov_len); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + l.lock(); + + if (ret < 0) + { + pe = m_disk_cache.find_piece(j); + if (pe == nullptr) + { + // the piece is supposed to be allocated when the + // disk job is allocated + TORRENT_ASSERT_FAIL(); + return status_t::fatal_disk_error; + } + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + if (!pe->read_jobs.empty()) + fail_jobs_impl(j->error, pe->read_jobs, completed_jobs); + TORRENT_PIECE_ASSERT(pe->read_jobs.empty(), pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.emplace_back(piece_log_t::clear_outstanding_jobs); +#endif + m_disk_cache.maybe_free_piece(pe); + return status_t::fatal_disk_error; + } + + int block = j->d.io.offset / default_block_size; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, block)); +#endif + + // we want to hold on to the iov now + iov_dealloc.disarm(); + + // as soon we insert the blocks they may be evicted + // (if using purgeable memory). In order to prevent that + // until we can read from them, increment the refcounts + m_disk_cache.insert_blocks(pe, block, iov, j, block_cache::blocks_inc_refcount); + + TORRENT_ASSERT(pe->blocks[block].buf); + + int const tmp = m_disk_cache.try_read(j, *this, true); + + // This should always succeed because we just checked to see there is a + // buffer for this block + TORRENT_ASSERT(tmp >= 0); + TORRENT_UNUSED(tmp); + + maybe_issue_queued_read_jobs(pe, completed_jobs); + + for (int i = 0; i < iov_len; ++i, ++block) + m_disk_cache.dec_block_refcount(pe, block, block_cache::ref_reading); + + return status_t::no_error; + } + + void disk_io_thread::maybe_issue_queued_read_jobs(cached_piece_entry* pe + , jobqueue_t& completed_jobs) + { + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + // if we're shutting down, just cancel the jobs + if (m_abort) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , pe->read_jobs, completed_jobs); + TORRENT_PIECE_ASSERT(pe->read_jobs.empty(), pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.emplace_back(piece_log_t::clear_outstanding_jobs); +#endif + m_disk_cache.maybe_free_piece(pe); + return; + } + + // while we were reading, there may have been a few jobs + // that got queued up also wanting to read from this piece. + // Any job that is a cache hit now, complete it immediately. + // Then, issue the first non-cache-hit job. Once it complete + // it will keep working off this list + jobqueue_t stalled_jobs; + pe->read_jobs.swap(stalled_jobs); + + // the next job to issue (i.e. this is a cache-miss) + disk_io_job* next_job = nullptr; + + while (!stalled_jobs.empty()) + { + disk_io_job* j = stalled_jobs.pop_front(); + TORRENT_ASSERT(j->flags & disk_io_job::in_progress); + + int ret = m_disk_cache.try_read(j, *this); + if (ret >= 0) + { + // cache-hit + m_stats_counters.inc_stats_counter(counters::num_blocks_cache_hits); + DLOG("do_read: cache hit\n"); + j->flags |= disk_interface::cache_hit; + j->ret = status_t::no_error; + completed_jobs.push_back(j); + } + else if (ret == -2) + { + // error + j->ret = status_t::fatal_disk_error; + completed_jobs.push_back(j); + } + else + { + // cache-miss, issue the first one + // put back the rest + if (next_job == nullptr) + { + next_job = j; + } + else + { + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->read_jobs.push_back(j); + } + } + } + + if (next_job) + { + add_job(next_job, false); + } + else + { + TORRENT_PIECE_ASSERT(pe->read_jobs.empty(), pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.emplace_back(piece_log_t::clear_outstanding_jobs); +#endif + m_disk_cache.maybe_free_piece(pe); + } + } + + status_t disk_io_thread::do_uncached_write(disk_io_job* j) + { + time_point const start_time = clock_type::now(); + auto buffer = std::move(boost::get(j->argument)); + + iovec_t const b = { buffer.get(), j->d.io.buffer_size}; + open_mode_t const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_writes)); + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, 1); + + // the actual write operation + int const ret = j->storage->writev(b + , j->piece, j->d.io.offset, file_flags, j->error); + + TORRENT_ASSERT(ret >= 0 || (j->error.ec && j->error.operation != operation_t::unknown)); + + m_stats_counters.inc_stats_counter(counters::num_writing_threads, -1); + + if (!j->error.ec) + { + std::int64_t const write_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_written); + m_stats_counters.inc_stats_counter(counters::num_write_ops); + m_stats_counters.inc_stats_counter(counters::disk_write_time, write_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, write_time); + } + + { + std::lock_guard l(m_need_tick_mutex); + if (!j->storage->set_need_tick()) + m_need_tick.emplace_back(aux::time_now() + minutes(2), j->storage); + } + + return ret != j->d.io.buffer_size + ? status_t::fatal_disk_error : status_t::no_error; + } + + status_t disk_io_thread::do_write(disk_io_job* j, jobqueue_t& completed_jobs) + { + TORRENT_ASSERT(j->d.io.buffer_size <= default_block_size); + + std::unique_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe != nullptr && pe->hashing_done) + { +#if TORRENT_USE_ASSERTS + print_piece_log(pe->piece_log); +#endif + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf + != boost::get(j->argument).get()); + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != nullptr); + j->error.ec = error::operation_aborted; + j->error.operation = operation_t::file_write; + return status_t::fatal_disk_error; + } + + pe = m_disk_cache.add_dirty_block(j + , !m_settings.get_bool(settings_pack::disable_hash_checks)); + + if (pe) + { +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + + if (!pe->hashing_done + && pe->hash == nullptr + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash.reset(new partial_hash); + m_disk_cache.update_cache_state(pe); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + // see if we can progress the hash cursor with this new block + kick_hasher(pe, l); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // flushes the piece to disk in case + // it satisfies the condition for a write + // piece to be flushed + try_flush_hashed(pe, m_settings.get_int( + settings_pack::write_cache_line_size), completed_jobs, l); + + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + + return defer_handler; + } + + // ok, we should just perform this job right now. + return do_uncached_write(j); + } + + void disk_io_thread::async_read(storage_index_t storage, peer_request const& r + , std::function handler, disk_job_flags_t const flags) + { + TORRENT_ASSERT(r.length <= default_block_size); + + DLOG("async_read piece: %d block: %d\n", static_cast(r.piece) + , r.start / default_block_size); + + disk_io_job* j = allocate_job(job_action_t::read); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = r.start; + j->d.io.buffer_size = std::uint16_t(r.length); + j->argument = disk_buffer_holder(*this, nullptr, 0); + j->flags = flags; + j->callback = std::move(handler); + + TORRENT_ASSERT(static_cast(r.piece) * static_cast(j->storage->files().piece_length()) + + r.start + r.length <= j->storage->files().total_size()); + + std::unique_lock l(m_cache_mutex); + int const ret = prep_read_job_impl(j); + l.unlock(); + + switch (ret) + { + case 0: + j->call_callback(); + free_job(j); + break; + case 1: + add_job(j); + break; + } + } + + // this function checks to see if a read job is a cache hit, + // and if it doesn't have a piece allocated, it allocates + // one and it sets outstanding_read flag and possibly queues + // up the job in the piece read job list + // the cache std::mutex must be held when calling this + // + // returns 0 if the job succeeded immediately + // 1 if it needs to be added to the job queue + // 2 if it was deferred and will be performed later (no need to + // add it to the queue) + int disk_io_thread::prep_read_job_impl(disk_io_job* j, bool const check_fence) + { + TORRENT_ASSERT(j->action == job_action_t::read); + + int const ret = m_disk_cache.try_read(j, *this); + if (ret >= 0) + { + m_stats_counters.inc_stats_counter(counters::num_blocks_cache_hits); + DLOG("do_read: cache hit\n"); + j->flags |= disk_interface::cache_hit; + j->ret = status_t::no_error; + return 0; + } + else if (ret == -2) + { + j->error.ec = error::no_memory; + j->error.operation = operation_t::alloc_cache_piece; + j->ret = status_t::fatal_disk_error; + return 0; + } + + if (check_fence && j->storage->is_blocked(j)) + { + // this means the job was queued up inside storage + m_stats_counters.inc_stats_counter(counters::blocked_disk_jobs); + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_name(j->action), j->storage ? j->storage->num_blocked() : 0 + , int(m_stats_counters[counters::blocked_disk_jobs])); + return 2; + } + + if (!m_settings.get_bool(settings_pack::use_read_cache) + || m_settings.get_int(settings_pack::cache_size) == 0) + { + // if the read cache is disabled then we can skip going through the cache + // but only if there is no existing piece entry. Otherwise there may be a + // partial hit on one-or-more dirty buffers so we must use the cache + // to avoid reading bogus data from storage + if (m_disk_cache.find_piece(j) == nullptr) + return 1; + } + + cached_piece_entry* pe = m_disk_cache.allocate_piece(j, cached_piece_entry::read_lru1); + + if (pe == nullptr) + { + j->ret = status_t::fatal_disk_error; + j->error.ec = error::no_memory; + j->error.operation = operation_t::file_read; + return 0; + } + if (pe->outstanding_read) + { + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->read_jobs.push_back(j); + return 2; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::set_outstanding_jobs)); +#endif + pe->outstanding_read = 1; + + return 1; + } + + bool disk_io_thread::async_write(storage_index_t const storage, peer_request const& r + , char const* buf, std::shared_ptr o + , std::function handler + , disk_job_flags_t const flags) + { + TORRENT_ASSERT(r.length <= default_block_size); + TORRENT_ASSERT(r.length <= 16 * 1024); + TORRENT_ASSERT(buf != nullptr); + + bool exceeded = false; + disk_buffer_holder buffer(*this, m_disk_cache.allocate_buffer(exceeded, o, "receive buffer"), 0x4000); + if (!buffer) aux::throw_ex(); + std::memcpy(buffer.get(), buf, aux::numeric_cast(r.length)); + + disk_io_job* j = allocate_job(job_action_t::write); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = r.start; + j->d.io.buffer_size = std::uint16_t(r.length); + j->argument = std::move(buffer); + j->callback = std::move(handler); + j->flags = flags; + +#if TORRENT_USE_ASSERTS + std::unique_lock l3_(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { + // we should never add a new dirty block to a piece + // whose hash we have calculated. The piece needs + // to be cleared first, (async_clear_piece). + TORRENT_ASSERT(pe->hashing_done == 0); + + TORRENT_ASSERT(pe->blocks[r.start / 0x4000].refcount == 0 || pe->blocks[r.start / 0x4000].buf == nullptr); + } + l3_.unlock(); +#endif + +#if TORRENT_USE_ASSERTS && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + std::unique_lock l2_(m_cache_mutex); + auto range = m_disk_cache.all_pieces(); + for (auto i = range.first; i != range.second; ++i) + { + cached_piece_entry const& p = *i; + int const piece_size = p.storage->files().piece_size(p.piece); + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + for (int k = 0; k < blocks_in_piece; ++k) + TORRENT_PIECE_ASSERT(p.blocks[k].buf != boost::get(j->argument).get(), &p); + } + l2_.unlock(); +#endif + + TORRENT_ASSERT((r.start % default_block_size) == 0); + + if (j->storage->is_blocked(j)) + { + // this means the job was queued up inside storage + m_stats_counters.inc_stats_counter(counters::blocked_disk_jobs); + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_name(j->action), j->storage ? j->storage->num_blocked() : 0 + , int(m_stats_counters[counters::blocked_disk_jobs])); + return exceeded; + } + + std::unique_lock l(m_cache_mutex); + // if we succeed in adding the block to the cache, the job will + // be added along with it. we may not free j if so + cached_piece_entry* dpe = m_disk_cache.add_dirty_block(j + , !m_settings.get_bool(settings_pack::disable_hash_checks)); + + if (dpe != nullptr) + { + if (dpe->outstanding_flush == 0) + { + dpe->outstanding_flush = 1; + l.unlock(); + + // the block and write job were successfully inserted + // into the cache. Now, see if we should trigger a flush + j = allocate_job(job_action_t::flush_hashed); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = r.piece; + j->flags = flags; + add_job(j); + } + + // if we added the block (regardless of whether we also + // issued a flush job or not), we're done. + return exceeded; + } + l.unlock(); + + add_job(j); + return exceeded; + } + + void disk_io_thread::async_hash(storage_index_t const storage + , piece_index_t const piece, disk_job_flags_t const flags + , std::function handler) + { + disk_io_job* j = allocate_job(job_action_t::hash); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = piece; + j->callback = std::move(handler); + j->flags = flags; + + int const piece_size = j->storage->files().piece_size(piece); + + // first check to see if the hashing is already done + std::unique_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe != nullptr && !pe->hashing && pe->hash && pe->hash->offset == piece_size) + { + j->d.piece_hash = pe->hash->h.final(); + + pe->hash.reset(); + + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; + +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + + l.unlock(); + j->call_callback(); + free_job(j); + return; + } + l.unlock(); + add_job(j); + } + + void disk_io_thread::async_move_storage(storage_index_t const storage + , std::string p, move_flags_t const flags + , std::function handler) + { + disk_io_job* j = allocate_job(job_action_t::move_storage); + j->storage = m_torrents[storage]->shared_from_this(); + j->argument = std::move(p); + j->callback = std::move(handler); + j->move_flags = flags; + + add_fence_job(j); + } + + void disk_io_thread::async_release_files(storage_index_t const storage + , std::function handler) + { + disk_io_job* j = allocate_job(job_action_t::release_files); + j->storage = m_torrents[storage]->shared_from_this(); + j->callback = std::move(handler); + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + { + std::unique_lock l(m_cache_mutex); + auto const& pieces = j->storage->cached_pieces(); + for (auto const& p : pieces) + TORRENT_ASSERT(p.storage == j->storage); + } +#endif + add_fence_job(j); + } + + void disk_io_thread::abort_hash_jobs(storage_index_t const storage) + { + // abort outstanding hash jobs belonging to this torrent + std::unique_lock l(m_job_mutex); + + std::shared_ptr st + = m_torrents[storage]->shared_from_this(); + // hash jobs + for (auto i = m_hash_io_jobs.m_queued_jobs.iterate(); i.get(); i.next()) + { + disk_io_job *j = i.get(); + if (j->storage != st) continue; + // only cancel volatile-read jobs. This means only full checking + // jobs. These jobs are likely to have a pretty deep queue and + // really gain from being cancelled. They can also be restarted + // easily. + if (!(j->flags & disk_interface::volatile_read)) continue; + j->flags |= disk_io_job::aborted; + } + } + + void disk_io_thread::async_delete_files(storage_index_t const storage + , remove_flags_t const options + , std::function handler) + { + abort_hash_jobs(storage); + disk_io_job* j = allocate_job(job_action_t::delete_files); + j->storage = m_torrents[storage]->shared_from_this(); + j->callback = std::move(handler); + j->argument = options; + add_fence_job(j); + } + + void disk_io_thread::async_check_files(storage_index_t const storage + , add_torrent_params const* resume_data + , aux::vector& links + , std::function handler) + { + auto links_vector = new aux::vector(); + links_vector->swap(links); + + disk_io_job* j = allocate_job(job_action_t::check_fastresume); + j->storage = m_torrents[storage]->shared_from_this(); + j->argument = resume_data; + j->d.links = links_vector; + j->callback = std::move(handler); + + add_fence_job(j); + } + + void disk_io_thread::async_rename_file(storage_index_t const storage + , file_index_t const index, std::string name + , std::function handler) + { + disk_io_job* j = allocate_job(job_action_t::rename_file); + j->storage = m_torrents[storage]->shared_from_this(); + j->file_index = index; + j->argument = std::move(name); + j->callback = std::move(handler); + add_fence_job(j); + } + + void disk_io_thread::async_stop_torrent(storage_index_t const storage + , std::function handler) + { + abort_hash_jobs(storage); + disk_io_job* j = allocate_job(job_action_t::stop_torrent); + j->storage = m_torrents[storage]->shared_from_this(); + j->callback = std::move(handler); + add_fence_job(j); + } + + void disk_io_thread::async_flush_piece(storage_index_t const storage + , piece_index_t const piece + , std::function handler) + { + disk_io_job* j = allocate_job(job_action_t::flush_piece); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = piece; + j->callback = std::move(handler); + + if (m_abort) + { + j->error.ec = boost::asio::error::operation_aborted; + j->call_callback(); + free_job(j); + return; + } + + add_job(j); + } + + void disk_io_thread::async_set_file_priority(storage_index_t const storage + , aux::vector prios + , std::function)> handler) + { + disk_io_job* j = allocate_job(job_action_t::file_priority); + j->storage = m_torrents[storage]->shared_from_this(); + j->argument = std::move(prios); + j->callback = std::move(handler); + + add_fence_job(j); + } + + void disk_io_thread::async_clear_piece(storage_index_t const storage + , piece_index_t const index, std::function handler) + { + disk_io_job* j = allocate_job(job_action_t::clear_piece); + j->storage = m_torrents[storage]->shared_from_this(); + j->piece = index; + j->callback = std::move(handler); + + // regular jobs are not guaranteed to be executed in-order + // since clear piece must guarantee that all write jobs that + // have been issued finish before the clear piece job completes + + // TODO: this is potentially very expensive. One way to solve + // it would be to have a fence for just this one piece. + add_fence_job(j); + } + + void disk_io_thread::clear_piece(storage_index_t const storage + , piece_index_t const index) + { + + storage_interface* st = m_torrents[storage].get(); + std::unique_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(st, index); + if (pe == nullptr) return; + TORRENT_PIECE_ASSERT(pe->hashing == false, pe); + pe->hashing_done = 0; + pe->hash.reset(); + + // evict_piece returns true if the piece was in fact + // evicted. A piece may fail to be evicted if there + // are still outstanding operations on it, which should + // never be the case when this function is used + // in fact, no jobs should really be hung on this piece + // at this point + jobqueue_t jobs; + bool const ok = m_disk_cache.evict_piece(pe, jobs, block_cache::allow_ghost); + TORRENT_PIECE_ASSERT(ok, pe); + TORRENT_UNUSED(ok); + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + } + + void disk_io_thread::kick_hasher(cached_piece_entry* pe, std::unique_lock& l) + { + if (!pe->hash) return; + if (pe->hashing) return; + + int const piece_size = pe->storage->files().piece_size(pe->piece); + partial_hash* ph = pe->hash.get(); + + // are we already done? + if (ph->offset >= piece_size) return; + + int const cursor = ph->offset / default_block_size; + int end = cursor; + TORRENT_PIECE_ASSERT(ph->offset % default_block_size == 0, pe); + + for (int i = cursor; i < pe->blocks_in_piece; ++i) + { + cached_block_entry& bl = pe->blocks[i]; + if (bl.buf == nullptr) break; + + // if we fail to lock the block, it' no longer in the cache + if (m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_hashing) == false) + break; + + ++end; + } + + // no blocks to hash? + if (end == cursor) return; + + pe->hashing = 1; + + DLOG("kick_hasher: %d - %d (piece: %d offset: %d)\n" + , cursor, end, int(pe->piece), ph->offset); + + // save a local copy of offset to avoid concurrent access + int offset = ph->offset; +#if TORRENT_USE_ASSERTS + int old_offset = offset; +#endif + + l.unlock(); + + time_point const start_time = clock_type::now(); + + for (int i = cursor; i < end; ++i) + { + cached_block_entry& bl = pe->blocks[i]; + int const size = std::min(default_block_size, piece_size - offset); + ph->h.update(bl.buf, size); + offset += size; + } + + std::int64_t const hash_time = total_microseconds(clock_type::now() - start_time); + + l.lock(); + + TORRENT_ASSERT(old_offset == ph->offset); + ph->offset = offset; + + TORRENT_PIECE_ASSERT(pe->hashing, pe); + TORRENT_PIECE_ASSERT(pe->hash, pe); + + m_stats_counters.inc_stats_counter(counters::num_blocks_hashed, end - cursor); + m_stats_counters.inc_stats_counter(counters::disk_hash_time, hash_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, hash_time); + + pe->hashing = 0; + + // decrement the block refcounters + for (int i = cursor; i < end; ++i) + m_disk_cache.dec_block_refcount(pe, i, block_cache::ref_hashing); + + // did we complete the hash? + if (pe->hash->offset != piece_size) return; + + // if there are any hash-jobs hanging off of this piece + // we should post them now + disk_io_job* j = pe->jobs.get_all(); + jobqueue_t hash_jobs; + while (j) + { + TORRENT_PIECE_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage, pe); + disk_io_job* next = j->next; + j->next = nullptr; + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + if (j->action == job_action_t::hash) hash_jobs.push_back(j); + else pe->jobs.push_back(j); + j = next; + } + if (!hash_jobs.empty()) + { + sha1_hash const result = pe->hash->h.final(); + + for (auto i = hash_jobs.iterate(); i.get(); i.next()) + { + disk_io_job* hj = i.get(); + hj->d.piece_hash = result; + hj->ret = status_t::no_error; + } + + pe->hash.reset(); + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + add_completed_jobs(hash_jobs); + } + } + + status_t disk_io_thread::do_uncached_hash(disk_io_job* j) + { + // we're not using a cache. This is the simple path + // just read straight from the file + TORRENT_ASSERT(m_magic == 0x1337); + + int const piece_size = j->storage->files().piece_size(j->piece); + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + open_mode_t const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + + iovec_t iov = { m_disk_cache.allocate_buffer("hashing") + , static_cast(default_block_size) }; + + // free at the end of the scope + auto iov_dealloc = aux::scope_end([&]{ m_disk_cache.free_buffer(iov.data()); }); + + hasher h; + int ret = 0; + int offset = 0; + for (int i = 0; i < blocks_in_piece; ++i) + { + DLOG("do_hash: (uncached) reading (piece: %d block: %d)\n" + , int(j->piece), i); + + time_point const start_time = clock_type::now(); + + iov = iov.first(std::min(default_block_size, piece_size - offset)); + ret = j->storage->readv(iov, j->piece, offset, file_flags, j->error); + if (ret <= 0) break; + iov = iov.first(ret); + + if (!j->error.ec) + { + std::int64_t const read_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + offset += default_block_size; + h.update(iov); + } + + j->d.piece_hash = h.final(); + return ret >= 0 ? status_t::no_error : status_t::fatal_disk_error; + } + + status_t disk_io_thread::do_hash(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + if (m_settings.get_bool(settings_pack::disable_hash_checks)) + return status_t::no_error; + + int const piece_size = j->storage->files().piece_size(j->piece); + open_mode_t const file_flags = file_flags_for_job(j + , m_settings.get_bool(settings_pack::coalesce_reads)); + + std::unique_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe != nullptr) + { + TORRENT_ASSERT(pe->in_use); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + m_disk_cache.cache_hit(pe, j->d.io.offset / default_block_size + , bool(j->flags & disk_interface::volatile_read)); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + { + piece_refcount_holder h(pe); + kick_hasher(pe, l); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // are we already done hashing? + if (pe->hash && !pe->hashing && pe->hash->offset == piece_size) + { + DLOG("do_hash: (%d) (already done)\n", int(pe->piece)); + j->d.piece_hash = pe->hash->h.final(); + pe->hash.reset(); + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + m_disk_cache.update_cache_state(pe); + m_disk_cache.maybe_free_piece(pe); + return status_t::no_error; + } + } + else if (m_settings.get_bool(settings_pack::use_read_cache) == false) + { + return do_uncached_hash(j); + } + + if (pe == nullptr) + { + std::uint16_t const cache_state = std::uint16_t((j->flags & disk_interface::volatile_read) + ? cached_piece_entry::volatile_read_lru + : cached_piece_entry::read_lru1); + pe = m_disk_cache.allocate_piece(j, cache_state); + } + if (pe == nullptr) + { + j->error.ec = error::no_memory; + j->error.operation = operation_t::alloc_cache_piece; + return status_t::fatal_disk_error; + } + + if (pe->hashing) + { + TORRENT_PIECE_ASSERT(pe->hash, pe); + // another thread is hashing this piece right now + // try again in a little bit + DLOG("do_hash: retry\n"); + // TODO: we should probably just hang the job on the piece and make sure the hasher gets kicked + return retry_job; + } + + pe->hashing = 1; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + + piece_refcount_holder refcount_holder(pe); + + if (!pe->hash) + { + pe->hashing_done = 0; + pe->hash.reset(new partial_hash); + } + partial_hash* ph = pe->hash.get(); + + int const blocks_in_piece = (piece_size + default_block_size - 1) / default_block_size; + + // we don't care about anything to the left of ph->offset + // since those blocks have already been hashed. + // we just care about [firs_block, first_block + blocks_left] + int const first_block = ph->offset / default_block_size; + int const blocks_left = blocks_in_piece - first_block; + + // ph->offset + // | first_block + // | | + // v v + // +---+---+---+---+---+---+ + // | | | | | | | + // +---+---+---+---+---+---+ + // + // \-----------/ + // blocks_left + // + // \-----------------------/ + // blocks_in_piece + + // keep track of which blocks we have locked by incrementing + // their refcounts. This is used to decrement only these blocks + // later. + TORRENT_ALLOCA(locked_blocks, int, blocks_in_piece); + std::fill(locked_blocks.begin(), locked_blocks.end(), 0); + int num_locked_blocks = 0; + + // increment the refcounts of all + // blocks up front, and then hash them without holding the lock + TORRENT_PIECE_ASSERT(ph->offset % default_block_size == 0, pe); + for (int i = 0; i < blocks_left; ++i) + { + // is the block not in the cache? + if (pe->blocks[first_block + i].buf == nullptr) continue; + + // if we fail to lock the block, it's no longer in the cache + if (m_disk_cache.inc_block_refcount(pe, first_block + i, block_cache::ref_hashing) == false) + continue; + + locked_blocks[num_locked_blocks++] = i; + } + + // to keep the cache footprint low, try to evict a volatile piece + m_disk_cache.try_evict_one_volatile(); + + // save a local copy of offset to avoid concurrent access + int offset = ph->offset; +#if TORRENT_USE_ASSERTS + int old_offset = offset; +#endif + + l.unlock(); + + bool slow_path = true; + + if (num_locked_blocks == 0) + { + // this is the fast path where we don't have any blocks in the cache. + // We'll need to read all (remaining blocks) from disk + TORRENT_ALLOCA(iov, iovec_t, blocks_left); + if (m_disk_cache.allocate_iovec(iov) >= 0) + { + // free buffers at the end of the scope + auto iov_dealloc = aux::scope_end([&]{ m_disk_cache.free_iovec(iov); }); + + // if this is the last piece, adjust the size of the + // last buffer to match up + iov[blocks_left - 1] = iov[blocks_left - 1].first( + piece_size - (blocks_in_piece - 1) * default_block_size); + TORRENT_ASSERT(iov[blocks_left - 1].size() > 0); + TORRENT_ASSERT(iov[blocks_left - 1].size() <= default_block_size); + + time_point const start_time = clock_type::now(); + int const read_ret = j->storage->readv(iov + , j->piece, offset, file_flags, j->error); + + if (read_ret == piece_size - offset) + { + std::int64_t const read_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_blocks_hashed, blocks_left); + m_stats_counters.inc_stats_counter(counters::num_read_back, blocks_left); + m_stats_counters.inc_stats_counter(counters::num_blocks_read, blocks_left); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + + for (auto const& v : iov) + { + offset += int(v.size()); + ph->h.update(v); + } + + slow_path = false; + + TORRENT_ASSERT(offset == piece_size); + + // we want to hold on to the buffers now, to insert them in the + // cache + iov_dealloc.disarm(); + l.lock(); + m_disk_cache.insert_blocks(pe, first_block, iov, j); + l.unlock(); + } + } + } + + status_t ret = status_t::no_error; + if (slow_path) + { + int next_locked_block = 0; + for (int i = 0; i < blocks_left; ++i) + { + if (next_locked_block < num_locked_blocks + && locked_blocks[next_locked_block] == i) + { + int const len = std::min(default_block_size, piece_size - offset); + ++next_locked_block; + TORRENT_PIECE_ASSERT(pe->blocks[first_block + i].buf, pe); + TORRENT_PIECE_ASSERT(offset == (first_block + i) * default_block_size, pe); + offset += len; + ph->h.update({pe->blocks[first_block + i].buf, len}); + } + else + { + iovec_t const iov = { m_disk_cache.allocate_buffer("hashing") + , std::min(default_block_size, piece_size - offset)}; + + if (iov.data() == nullptr) + { + l.lock(); + // decrement the refcounts of the blocks we just hashed + for (int k = 0; k < num_locked_blocks; ++k) + m_disk_cache.dec_block_refcount(pe, first_block + locked_blocks[k], block_cache::ref_hashing); + + refcount_holder.release(); + pe->hashing = false; + pe->hash.reset(); + m_disk_cache.maybe_free_piece(pe); + + j->error.ec = errors::no_memory; + j->error.operation = operation_t::alloc_cache_piece; + return status_t::fatal_disk_error; + } + + // free buffers at the end of the scope + auto iov_dealloc = aux::scope_end([&]{ m_disk_cache.free_buffer(iov.data()); }); + + DLOG("do_hash: reading (piece: %d block: %d)\n" + , static_cast(pe->piece), first_block + i); + + time_point const start_time = clock_type::now(); + TORRENT_PIECE_ASSERT(offset == (first_block + i) * default_block_size, pe); + int const read_ret = j->storage->readv(iov, j->piece + , offset, file_flags, j->error); + + if (read_ret < 0) + { + ret = status_t::fatal_disk_error; + TORRENT_ASSERT(j->error.ec && j->error.operation != operation_t::unknown); + break; + } + + // treat a short read as an error. The hash will be invalid, the + // block cannot be cached and the main thread should skip the rest + // of this file + if (read_ret != int(iov.size())) + { + ret = status_t::fatal_disk_error; + j->error.ec = boost::asio::error::eof; + j->error.operation = operation_t::file_read; + break; + } + + if (!j->error.ec) + { + std::int64_t const read_time = total_microseconds(clock_type::now() - start_time); + + m_stats_counters.inc_stats_counter(counters::num_read_back); + m_stats_counters.inc_stats_counter(counters::num_blocks_read); + m_stats_counters.inc_stats_counter(counters::num_read_ops); + m_stats_counters.inc_stats_counter(counters::disk_read_time, read_time); + m_stats_counters.inc_stats_counter(counters::disk_job_time, read_time); + } + + TORRENT_PIECE_ASSERT(offset == (first_block + i) * default_block_size, pe); + offset += int(iov.size()); + ph->h.update(iov); + + iov_dealloc.disarm(); + l.lock(); + m_disk_cache.insert_blocks(pe, first_block + i, iov, j); + l.unlock(); + } + } + } + + l.lock(); + + TORRENT_ASSERT(old_offset == ph->offset); + ph->offset = offset; + + // decrement the refcounts of the blocks we just hashed + for (int i = 0; i < num_locked_blocks; ++i) + m_disk_cache.dec_block_refcount(pe, first_block + locked_blocks[i], block_cache::ref_hashing); + + refcount_holder.release(); + + pe->hashing = 0; + + if (ret == status_t::no_error) + { + j->d.piece_hash = ph->h.final(); + + pe->hash.reset(); + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + m_disk_cache.update_cache_state(pe); + } + + m_disk_cache.maybe_free_piece(pe); + + TORRENT_ASSERT(ret == status_t::no_error || (j->error.ec && j->error.operation != operation_t::unknown)); + + return ret; + } + + status_t disk_io_thread::do_move_storage(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // if files have to be closed, that's the storage's responsibility + return j->storage->move_storage(boost::get(j->argument) + , j->move_flags, j->error); + } + + status_t disk_io_thread::do_release_files(disk_io_job* j, jobqueue_t& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + std::unique_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + l.unlock(); + + j->storage->release_files(j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + status_t disk_io_thread::do_delete_files(disk_io_job* j, jobqueue_t& completed_jobs) + { + TORRENT_ASSERT(boost::get(j->argument)); + + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + std::unique_lock l(m_cache_mutex); + + flush_cache(j->storage.get() + , flush_read_cache | flush_delete_cache | flush_expect_clear + , completed_jobs, l); + l.unlock(); + + j->storage->delete_files(boost::get(j->argument), j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + status_t disk_io_thread::do_check_fastresume(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + add_torrent_params const* rd = boost::get(j->argument); + add_torrent_params tmp; + if (rd == nullptr) rd = &tmp; + + std::unique_ptr> links(j->d.links); + // check if the fastresume data is up to date + // if it is, use it and return true. If it + // isn't return false and the full check + // will be run. If the links pointer is non-empty, it has the same number + // of elements as there are files. Each element is either empty or contains + // the absolute path to a file identical to the corresponding file in this + // torrent. The storage must create hard links (or copy) those files. If + // any file does not exist or is inaccessible, the disk job must fail. + + TORRENT_ASSERT(j->storage->files().piece_length() > 0); + + // always initialize the storage + j->storage->initialize(j->error); + if (j->error) return status_t::fatal_disk_error; + + bool const verify_success = j->storage->verify_resume_data(*rd + , links ? *links : aux::vector(), j->error); + + // j->error may have been set at this point, by verify_resume_data() + // it's important to not have it cleared out subsequent calls, as long + // as they succeed. + + if (m_settings.get_bool(settings_pack::no_recheck_incomplete_resume)) + return status_t::no_error; + + if (!aux::contains_resume_data(*rd)) + { + // if we don't have any resume data, we still may need to trigger a + // full re-check, if there are *any* files. + storage_error ignore; + return (j->storage->has_any_file(ignore)) + ? status_t::need_full_check + : status_t::no_error; + } + + return verify_success + ? status_t::no_error + : status_t::need_full_check; + } + + status_t disk_io_thread::do_rename_file(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // if files need to be closed, that's the storage's responsibility + j->storage->rename_file(j->file_index, boost::get(j->argument) + , j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + status_t disk_io_thread::do_stop_torrent(disk_io_job* j, jobqueue_t& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // issue write commands for all dirty blocks + // and clear all read jobs + std::unique_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_read_cache | flush_write_cache + , completed_jobs, l); + l.unlock(); + + j->storage->release_files(j->error); + return j->error ? status_t::fatal_disk_error : status_t::no_error; + } + + namespace { + + void get_cache_info_impl(cached_piece_info& info, cached_piece_entry const* i) + { + info.piece = i->piece; + info.storage = i->storage.get(); + info.last_use = i->expire; + info.need_readback = i->need_readback; + info.next_to_hash = i->hash == nullptr ? -1 : (i->hash->offset + default_block_size - 1) / default_block_size; + info.kind = i->cache_state == cached_piece_entry::write_lru + ? cached_piece_info::write_cache + : i->cache_state == cached_piece_entry::volatile_read_lru + ? cached_piece_info::volatile_read_cache + : cached_piece_info::read_cache; + int const blocks_in_piece = i->blocks_in_piece; + info.blocks.resize(aux::numeric_cast(blocks_in_piece)); + for (int b = 0; b < blocks_in_piece; ++b) + info.blocks[std::size_t(b)] = i->blocks[b].buf != nullptr; + } + + } // anonymous namespace + + void disk_io_thread::update_stats_counters(counters& c) const + { + // These are atomic_counts, so it's safe to access them from + // a different thread + std::unique_lock jl(m_job_mutex); + + c.set_value(counters::num_read_jobs, read_jobs_in_use()); + c.set_value(counters::num_write_jobs, write_jobs_in_use()); + c.set_value(counters::num_jobs, jobs_in_use()); + c.set_value(counters::queued_disk_jobs, m_generic_io_jobs.m_queued_jobs.size() + + m_hash_io_jobs.m_queued_jobs.size()); + + jl.unlock(); + + std::unique_lock l(m_cache_mutex); + + // gauges + c.set_value(counters::disk_blocks_in_use, m_disk_cache.in_use()); + + m_disk_cache.update_stats_counters(c); + } + + void disk_io_thread::get_cache_info(cache_status* ret, storage_index_t const st + , bool const no_pieces, bool const session) const + { + std::unique_lock l(m_cache_mutex); + +#if TORRENT_ABI_VERSION == 1 + ret->total_used_buffers = m_disk_cache.in_use(); + + ret->blocks_read_hit = int(m_stats_counters[counters::num_blocks_cache_hits]); + ret->blocks_read = int(m_stats_counters[counters::num_blocks_read]); + ret->blocks_written = int(m_stats_counters[counters::num_blocks_written]); + ret->writes = int(m_stats_counters[counters::num_write_ops]); + ret->reads = int(m_stats_counters[counters::num_read_ops]); + + int num_read_jobs = int(std::max(std::int64_t(1) + , m_stats_counters[counters::num_read_ops])); + int num_write_jobs = int(std::max(std::int64_t(1) + , m_stats_counters[counters::num_write_ops])); + int num_hash_jobs = int(std::max(std::int64_t(1) + , m_stats_counters[counters::num_blocks_hashed])); + + ret->average_read_time = int(m_stats_counters[counters::disk_read_time] / num_read_jobs); + ret->average_write_time = int(m_stats_counters[counters::disk_write_time] / num_write_jobs); + ret->average_hash_time = int(m_stats_counters[counters::disk_hash_time] / num_hash_jobs); + ret->average_job_time = int(m_stats_counters[counters::disk_job_time] + / (num_read_jobs + num_write_jobs + num_hash_jobs)); + ret->cumulative_job_time = int(m_stats_counters[counters::disk_job_time]); + ret->cumulative_read_time = int(m_stats_counters[counters::disk_read_time]); + ret->cumulative_write_time = int(m_stats_counters[counters::disk_write_time]); + ret->cumulative_hash_time = int(m_stats_counters[counters::disk_hash_time]); + ret->total_read_back = int(m_stats_counters[counters::num_read_back]); + + ret->blocked_jobs = int(m_stats_counters[counters::blocked_disk_jobs]); + + ret->num_jobs = jobs_in_use(); + ret->num_read_jobs = read_jobs_in_use(); + ret->read_queue_size = read_jobs_in_use(); + ret->num_write_jobs = write_jobs_in_use(); + ret->pending_jobs = int(m_stats_counters[counters::num_running_disk_jobs]); + ret->num_writing_threads = int(m_stats_counters[counters::num_writing_threads]); + + for (int i = 0; i < static_cast(job_action_t::num_job_ids); ++i) + ret->num_fence_jobs[i] = int(m_stats_counters[counters::num_fenced_read + i]); + + m_disk_cache.get_stats(ret); + +#endif + + ret->pieces.clear(); + + if (no_pieces == false) + { + if (!session) + { + std::shared_ptr storage = m_torrents[st]; + TORRENT_ASSERT(storage); + ret->pieces.reserve(aux::numeric_cast(storage->num_pieces())); + + for (auto const& pe : storage->cached_pieces()) + { + TORRENT_ASSERT(pe.storage.get() == storage.get()); + + if (pe.cache_state == cached_piece_entry::read_lru2_ghost + || pe.cache_state == cached_piece_entry::read_lru1_ghost) + continue; + ret->pieces.emplace_back(); + get_cache_info_impl(ret->pieces.back(), &pe); + } + } + else + { + ret->pieces.reserve(aux::numeric_cast(m_disk_cache.num_pieces())); + + auto range = m_disk_cache.all_pieces(); + for (auto i = range.first; i != range.second; ++i) + { + if (i->cache_state == cached_piece_entry::read_lru2_ghost + || i->cache_state == cached_piece_entry::read_lru1_ghost) + continue; + ret->pieces.emplace_back(); + get_cache_info_impl(ret->pieces.back(), &*i); + } + } + } + + l.unlock(); + +#if TORRENT_ABI_VERSION == 1 + std::unique_lock jl(m_job_mutex); + ret->queued_jobs = m_generic_io_jobs.m_queued_jobs.size() + m_hash_io_jobs.m_queued_jobs.size(); + jl.unlock(); +#endif + } + + status_t disk_io_thread::do_flush_piece(disk_io_job* j, jobqueue_t& completed_jobs) + { + std::unique_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == nullptr) return status_t::no_error; + +#if TORRENT_USE_ASSERTS + pe->piece_log.emplace_back(j->action); +#endif + try_flush_hashed(pe, m_settings.get_int( + settings_pack::write_cache_line_size), completed_jobs, l); + + return status_t::no_error; + } + + // this is triggered every time we insert a new dirty block in a piece + // by the time this gets executed, the block may already have been flushed + // triggered by another mechanism. + status_t disk_io_thread::do_flush_hashed(disk_io_job* j, jobqueue_t& completed_jobs) + { + std::unique_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + + if (pe == nullptr) return status_t::no_error; + + pe->outstanding_flush = 0; + + if (pe->num_dirty == 0) return status_t::no_error; + + // if multiple threads are flushing this piece, this assert may fire + // this happens if the cache is running full and pieces are started to + // get flushed +// TORRENT_PIECE_ASSERT(pe->outstanding_flush == 1, pe); + +#if TORRENT_USE_ASSERTS + pe->piece_log.emplace_back(j->action); +#endif + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + + piece_refcount_holder refcount_holder(pe); + + if (!pe->hashing_done) + { + if (pe->hash == nullptr && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash.reset(new partial_hash); + m_disk_cache.update_cache_state(pe); + } + + // see if we can progress the hash cursor with this new block + kick_hasher(pe, l); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + } + + // flushes the piece to disk in case + // it satisfies the condition for a write + // piece to be flushed + // #error if hash checks are disabled, always just flush + try_flush_hashed(pe, m_settings.get_int( + settings_pack::write_cache_line_size), completed_jobs, l); + + TORRENT_ASSERT(l.owns_lock()); + + refcount_holder.release(); + + m_disk_cache.maybe_free_piece(pe); + + return status_t::no_error; + } + + status_t disk_io_thread::do_flush_storage(disk_io_job* j, jobqueue_t& completed_jobs) + { + std::unique_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + return status_t::no_error; + } + + status_t disk_io_thread::do_trim_cache(disk_io_job*, jobqueue_t& /* completed_jobs */) + { +//#error implement + return status_t::no_error; + } + + status_t disk_io_thread::do_file_priority(disk_io_job* j, jobqueue_t& /* completed_jobs */ ) + { + j->storage->set_file_priority( + boost::get>(j->argument) + , j->error); + return status_t::no_error; + } + + // this job won't return until all outstanding jobs on this + // piece are completed or cancelled and the buffers for it + // have been evicted + status_t disk_io_thread::do_clear_piece(disk_io_job* j, jobqueue_t& completed_jobs) + { + std::unique_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == nullptr) return status_t::no_error; + TORRENT_PIECE_ASSERT(pe->hashing == false, pe); + pe->hashing_done = 0; + pe->hash.reset(); + pe->hashing_done = false; + +#if TORRENT_USE_ASSERTS + pe->piece_log.emplace_back(j->action); +#endif + + // evict_piece returns true if the piece was in fact + // evicted. A piece may fail to be evicted if there + // are still outstanding operations on it, in which case + // try again later + jobqueue_t jobs; + if (m_disk_cache.evict_piece(pe, jobs, block_cache::allow_ghost)) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted) + , jobs, completed_jobs); + return status_t::no_error; + } + + m_disk_cache.mark_for_eviction(pe, block_cache::allow_ghost); + if (pe->num_blocks == 0) return status_t::no_error; + + // we should always be able to evict the piece, since + // this is a fence job + TORRENT_PIECE_ASSERT_FAIL(pe); + return retry_job; + } + + void disk_io_thread::add_fence_job(disk_io_job* j, bool const user_add) + { + // if this happens, it means we started to shut down + // the disk threads too early. We have to post all jobs + // before the disk threads are shut down + TORRENT_ASSERT(!m_abort); + + DLOG("add_fence:job: %s (outstanding: %d)\n" + , job_name(j->action) + , j->storage->num_outstanding_jobs()); + + m_stats_counters.inc_stats_counter(counters::num_fenced_read + static_cast(j->action)); + + disk_io_job* fj = allocate_job(job_action_t::flush_storage); + fj->storage = j->storage; + TORRENT_ASSERT(fj->flags == disk_job_flags_t{}); + + int ret = j->storage->raise_fence(j, fj, m_stats_counters); + if (ret == aux::disk_job_fence::fence_post_fence) + { + std::unique_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + m_generic_io_jobs.m_queued_jobs.push_back(j); + l.unlock(); + + // discard the flush job + free_job(fj); + + if (num_threads() == 0 && user_add) + immediate_execute(); + + return; + } + + if (ret == aux::disk_job_fence::fence_post_flush) + { + // now, we have to make sure that all outstanding jobs on this + // storage actually get flushed, in order for the fence job to + // be executed + std::unique_lock l(m_job_mutex); + TORRENT_ASSERT((fj->flags & disk_io_job::in_progress) || !fj->storage); + + m_generic_io_jobs.m_queued_jobs.push_front(fj); + } + else + { + TORRENT_ASSERT(!(fj->flags & disk_io_job::in_progress)); + TORRENT_ASSERT(fj->blocked); + } + + if (num_threads() == 0 && user_add) + immediate_execute(); + } + + void disk_io_thread::add_job(disk_io_job* j, bool const user_add) + { + TORRENT_ASSERT(m_magic == 0x1337); + + TORRENT_ASSERT(!j->storage || j->storage->files().is_valid()); + TORRENT_ASSERT(j->next == nullptr); + // if this happens, it means we started to shut down + // the disk threads too early. We have to post all jobs + // before the disk threads are shut down + TORRENT_ASSERT(!m_abort + || j->action == job_action_t::flush_piece + || j->action == job_action_t::trim_cache); + + // this happens for read jobs that get hung on pieces in the + // block cache, and then get issued + if (j->flags & disk_io_job::in_progress) + { + std::unique_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + m_generic_io_jobs.m_queued_jobs.push_back(j); + + // if we literally have 0 disk threads, we have to execute the jobs + // immediately. If add job is called internally by the disk_io_thread, + // we need to defer executing it. We only want the top level to loop + // over the job queue (as is done below) + if (num_threads() == 0 && user_add) + { + l.unlock(); + immediate_execute(); + } + return; + } + + DLOG("add_job: %s (outstanding: %d)\n" + , job_name(j->action) + , j->storage ? j->storage->num_outstanding_jobs() : 0); + + // is the fence up for this storage? + // jobs that are instantaneous are not affected by the fence, is_blocked() + // will take ownership of the job and queue it up, in case the fence is up + // if the fence flag is set, this job just raised the fence on the storage + // and should be scheduled + if (j->storage && j->storage->is_blocked(j)) + { + m_stats_counters.inc_stats_counter(counters::blocked_disk_jobs); + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_name(j->action), j->storage ? j->storage->num_blocked() : 0 + , int(m_stats_counters[counters::blocked_disk_jobs])); + return; + } + + std::unique_lock l(m_job_mutex); + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + job_queue& q = queue_for_job(j); + q.m_queued_jobs.push_back(j); + // if we literally have 0 disk threads, we have to execute the jobs + // immediately. If add job is called internally by the disk_io_thread, + // we need to defer executing it. We only want the top level to loop + // over the job queue (as is done below) + if (pool_for_job(j).max_threads() == 0 && user_add) + { + l.unlock(); + immediate_execute(); + } + } + + void disk_io_thread::immediate_execute() + { + while (!m_generic_io_jobs.m_queued_jobs.empty()) + { + disk_io_job* j = m_generic_io_jobs.m_queued_jobs.pop_front(); + maybe_flush_write_blocks(); + execute_job(j); + } + } + + void disk_io_thread::submit_jobs() + { + std::unique_lock l(m_job_mutex); + if (!m_generic_io_jobs.m_queued_jobs.empty()) + { + m_generic_io_jobs.m_job_cond.notify_all(); + m_generic_threads.job_queued(m_generic_io_jobs.m_queued_jobs.size()); + } + if (!m_hash_io_jobs.m_queued_jobs.empty()) + { + m_hash_io_jobs.m_job_cond.notify_all(); + m_hash_threads.job_queued(m_hash_io_jobs.m_queued_jobs.size()); + } + } + + void disk_io_thread::maybe_flush_write_blocks() + { + time_point const now = clock_type::now(); + if (now <= m_last_cache_expiry + seconds(5)) return; + + std::unique_lock l(m_cache_mutex); + DLOG("blocked_jobs: %d queued_jobs: %d num_threads %d\n" + , int(m_stats_counters[counters::blocked_disk_jobs]) + , m_generic_io_jobs.m_queued_jobs.size(), num_threads()); + m_last_cache_expiry = now; + jobqueue_t completed_jobs; + flush_expired_write_blocks(completed_jobs, l); + l.unlock(); + if (!completed_jobs.empty()) + add_completed_jobs(completed_jobs); + } + + void disk_io_thread::execute_job(disk_io_job* j) + { + jobqueue_t completed_jobs; + if (j->flags & disk_io_job::aborted) + { + j->ret = status_t::fatal_disk_error; + j->error = storage_error(boost::asio::error::operation_aborted); + completed_jobs.push_back(j); + add_completed_jobs(completed_jobs); + return; + } + + perform_job(j, completed_jobs); + if (!completed_jobs.empty()) + add_completed_jobs(completed_jobs); + } + + bool disk_io_thread::wait_for_job(job_queue& jobq, disk_io_thread_pool& threads + , std::unique_lock& l) + { + TORRENT_ASSERT(l.owns_lock()); + + // the thread should only go active if it is exiting or there is work to do + // if the thread goes active on every wakeup it causes the minimum idle thread + // count to be lower than it should be + // for performance reasons we also want to avoid going idle and active again + // if there is already work to do + if (jobq.m_queued_jobs.empty()) + { + threads.thread_idle(); + + do + { + // if the number of wanted threads is decreased, + // we may stop this thread + // when we're terminating the last thread, make sure + // we finish up all queued jobs first + if (threads.should_exit() + && (jobq.m_queued_jobs.empty() + || threads.num_threads() > 1) + // try_thread_exit must be the last condition + && threads.try_thread_exit(std::this_thread::get_id())) + { + // time to exit this thread. + threads.thread_active(); + return true; + } + + jobq.m_job_cond.wait(l); + } while (jobq.m_queued_jobs.empty()); + + threads.thread_active(); + } + + return false; + } + + void disk_io_thread::thread_fun(job_queue& queue + , disk_io_thread_pool& pool) + { + std::thread::id const thread_id = std::this_thread::get_id(); + + DLOG("started disk thread\n"); + + std::unique_lock l(m_job_mutex); + + ++m_num_running_threads; + m_stats_counters.inc_stats_counter(counters::num_running_threads, 1); + + for (;;) + { + disk_io_job* j = nullptr; + bool const should_exit = wait_for_job(queue, pool, l); + if (should_exit) break; + j = queue.m_queued_jobs.pop_front(); + l.unlock(); + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + if (&pool == &m_generic_threads && thread_id == pool.first_thread_id()) + { + // there's no need for all threads to be doing this + maybe_flush_write_blocks(); + + time_point const now = aux::time_now(); + { + std::unique_lock l2(m_need_tick_mutex); + while (!m_need_tick.empty() && m_need_tick.front().first < now) + { + std::shared_ptr st = m_need_tick.front().second.lock(); + m_need_tick.erase(m_need_tick.begin()); + if (st) + { + l2.unlock(); + st->tick(); + l2.lock(); + } + } + } + + if (now > m_next_close_oldest_file) + { + seconds const interval(m_settings.get_int(settings_pack::close_file_interval)); + if (interval <= seconds(0)) + { + m_next_close_oldest_file = max_time(); + } + else + { + m_next_close_oldest_file = now + interval; + m_file_pool.close_oldest(); + } + } + } + + execute_job(j); + + l.lock(); + } + + // do cleanup in the last running thread + // if we're not aborting, that means we just configured the thread pool to + // not have any threads (i.e. perform all disk operations in the network + // thread). In this case, the cleanup will happen in abort(). + int const threads_left = --m_num_running_threads; + if (threads_left > 0 || !m_abort) + { + DLOG("exiting disk thread. num_threads: %d aborting: %d\n" + , threads_left, int(m_abort)); + TORRENT_ASSERT(m_magic == 0x1337); + m_stats_counters.inc_stats_counter(counters::num_running_threads, -1); + return; + } + + // it is important to hold the job mutex while calling try_thread_exit() + // and continue to hold it until checking m_abort above so that abort() + // doesn't inadvertently trigger the code below when it thinks there are no + // more disk I/O threads running + l.unlock(); + + DLOG("last thread alive. (left: %d) cleaning up. (generic-jobs: %d hash-jobs: %d)\n" + , threads_left + , m_generic_io_jobs.m_queued_jobs.size() + , m_hash_io_jobs.m_queued_jobs.size()); + + // at this point, there are no queued jobs left. However, main + // thread is still running and may still have peer_connections + // that haven't fully destructed yet, reclaiming their references + // to read blocks in the disk cache. We need to wait until all + // references are removed from other threads before we can go + // ahead with the cleanup. + // This is not supposed to happen because the disk thread is now scheduled + // for shut down after all peers have shut down (see + // session_impl::abort_stage2()). + std::unique_lock l2(m_cache_mutex); + TORRENT_ASSERT_VAL(m_disk_cache.pinned_blocks() == 0 + , m_disk_cache.pinned_blocks()); + while (m_disk_cache.pinned_blocks() > 0) + { + l2.unlock(); + std::this_thread::sleep_for(milliseconds(100)); + l2.lock(); + } + l2.unlock(); + + DLOG("the last disk thread alive. cleaning up\n"); + + abort_jobs(); + + TORRENT_ASSERT(m_magic == 0x1337); + m_stats_counters.inc_stats_counter(counters::num_running_threads, -1); + } + + void disk_io_thread::abort_jobs() + { + DLOG("disk_io_thread::abort_jobs\n"); + + TORRENT_ASSERT(m_magic == 0x1337); + if (m_jobs_aborted.test_and_set()) return; + + jobqueue_t jobs; + m_disk_cache.clear(jobs); + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + + // close all files. This may take a long + // time on certain OSes (i.e. Mac OS) + // that's why it's important to do this in + // the disk thread in parallel with stopping + // trackers. + m_file_pool.release(); + +#if TORRENT_USE_ASSERTS + // by now, all pieces should have been evicted + auto pieces = m_disk_cache.all_pieces(); + TORRENT_ASSERT(pieces.first == pieces.second); +#endif + + TORRENT_ASSERT(m_magic == 0x1337); + } + + int disk_io_thread::num_threads() const + { + return m_generic_threads.max_threads() + m_hash_threads.max_threads(); + } + + disk_io_thread::job_queue& disk_io_thread::queue_for_job(disk_io_job* j) + { + if (m_hash_threads.max_threads() > 0 && j->action == job_action_t::hash) + return m_hash_io_jobs; + else + return m_generic_io_jobs; + } + + disk_io_thread_pool& disk_io_thread::pool_for_job(disk_io_job* j) + { + if (m_hash_threads.max_threads() > 0 && j->action == job_action_t::hash) + return m_hash_threads; + else + return m_generic_threads; + } + + // this is a callback called by the block_cache when + // it's exceeding the disk cache size. + void disk_io_thread::trigger_cache_trim() + { + // we just exceeded the cache size limit. Trigger a trim job + disk_io_job* j = allocate_job(job_action_t::trim_cache); + add_job(j, false); + submit_jobs(); + } + + void disk_io_thread::add_completed_jobs(jobqueue_t& jobs) + { + jobqueue_t new_completed_jobs; + do + { + // when a job completes, it's possible for it to cause + // a fence to be lowered, issuing the jobs queued up + // behind the fence. It's also possible for some of these + // jobs to be cache-hits, completing immediately. Those + // jobs are added to the new_completed_jobs queue and + // we need to re-issue those + add_completed_jobs_impl(jobs, new_completed_jobs); + TORRENT_ASSERT(jobs.empty()); + jobs.swap(new_completed_jobs); + } while (!jobs.empty()); + } + + void disk_io_thread::add_completed_jobs_impl(jobqueue_t& jobs + , jobqueue_t& completed_jobs) + { + jobqueue_t new_jobs; + int ret = 0; + for (auto i = jobs.iterate(); i.get(); i.next()) + { + disk_io_job* j = i.get(); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + +// DLOG("job_complete %s outstanding: %d\n" +// , job_name(j->action), j->storage ? j->storage->num_outstanding_jobs() : 0); + + if (j->storage) + { + if (j->flags & disk_io_job::fence) + { + m_stats_counters.inc_stats_counter( + counters::num_fenced_read + static_cast(j->action), -1); + } + + ret += j->storage->job_complete(j, new_jobs); + } + TORRENT_ASSERT(ret == new_jobs.size()); + TORRENT_ASSERT(!(j->flags & disk_io_job::in_progress)); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->job_posted == false); + j->job_posted = true; +#endif + } + + if (ret) + { + DLOG("unblocked %d jobs (%d left)\n", ret + , int(m_stats_counters[counters::blocked_disk_jobs]) - ret); + } + + m_stats_counters.inc_stats_counter(counters::blocked_disk_jobs, -ret); + TORRENT_ASSERT(int(m_stats_counters[counters::blocked_disk_jobs]) >= 0); + + if (m_abort.load()) + { + // cancel all jobs + fail_jobs(storage_error(boost::asio::error::operation_aborted), new_jobs); + } + + if (!new_jobs.empty()) + { +#if TORRENT_USE_ASSERTS + for (auto i = new_jobs.iterate(); i.get(); i.next()) + { + disk_io_job const* j = static_cast(i.get()); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + if (j->action != job_action_t::write) continue; + + std::unique_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (!pe) continue; + + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf + != boost::get(j->argument).get()); + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf == nullptr); + TORRENT_ASSERT(!pe->hashing_done); + } +#endif + jobqueue_t other_jobs; + jobqueue_t flush_jobs; + std::unique_lock l_(m_cache_mutex); + while (!new_jobs.empty()) + { + disk_io_job* j = new_jobs.pop_front(); + + if (j->action == job_action_t::read) + { + int const state = prep_read_job_impl(j, false); + switch (state) + { + case 0: + completed_jobs.push_back(j); + break; + case 1: + other_jobs.push_back(j); + break; + } + continue; + } + + // write jobs should be put straight into the cache + if (j->action != job_action_t::write) + { + other_jobs.push_back(j); + continue; + } + + cached_piece_entry* pe = m_disk_cache.add_dirty_block(j + , !m_settings.get_bool(settings_pack::disable_hash_checks)); + + if (pe == nullptr) + { + // this isn't correct, since jobs in the jobs + // queue aren't ordered + other_jobs.push_back(j); + continue; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + + if (!pe->hashing_done + && pe->hash == nullptr + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash.reset(new partial_hash); + m_disk_cache.update_cache_state(pe); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + if (pe->outstanding_flush == 0) + { + pe->outstanding_flush = 1; + + // the block and write job were successfully inserted + // into the cache. Now, see if we should trigger a flush + disk_io_job* fj = allocate_job(job_action_t::flush_hashed); + fj->storage = j->storage; + fj->piece = j->piece; + flush_jobs.push_back(fj); + } + } + l_.unlock(); + + { + std::lock_guard l(m_job_mutex); + m_generic_io_jobs.m_queued_jobs.append(other_jobs); + } + + while (!flush_jobs.empty()) + { + disk_io_job* j = flush_jobs.pop_front(); + add_job(j, false); + } + + { + std::lock_guard l(m_job_mutex); + m_generic_io_jobs.m_job_cond.notify_all(); + m_generic_threads.job_queued(m_generic_io_jobs.m_queued_jobs.size()); + } + } + + std::lock_guard l(m_completed_jobs_mutex); + m_completed_jobs.append(jobs); + + if (!m_job_completions_in_flight) + { + // we take this lock just to make the logging prettier (non-interleaved) + DLOG("posting job handlers (%d)\n", m_completed_jobs.size()); + + m_ios.post(std::bind(&disk_io_thread::call_job_handlers, this)); + m_job_completions_in_flight = true; + } + } + + // This is run in the network thread + void disk_io_thread::call_job_handlers() + { + m_stats_counters.inc_stats_counter(counters::on_disk_counter); + std::unique_lock l(m_completed_jobs_mutex); + + DLOG("call_job_handlers (%d)\n", m_completed_jobs.size()); + + TORRENT_ASSERT(m_job_completions_in_flight); + m_job_completions_in_flight = false; + + disk_io_job* j = m_completed_jobs.get_all(); + l.unlock(); + + aux::array to_delete; + int cnt = 0; + + while (j) + { + TORRENT_ASSERT(j->job_posted == true); + TORRENT_ASSERT(j->callback_called == false); +// DLOG(" callback: %s\n", job_name(j->action)); + disk_io_job* next = j->next; + +#if TORRENT_USE_ASSERTS + j->callback_called = true; +#endif + j->call_callback(); + to_delete[cnt++] = j; + j = next; + if (cnt == int(to_delete.size())) + { + cnt = 0; + free_jobs(to_delete.data(), int(to_delete.size())); + } + } + + if (cnt > 0) free_jobs(to_delete.data(), cnt); + } +} diff --git a/src/disk_io_thread_pool.cpp b/src/disk_io_thread_pool.cpp new file mode 100644 index 0000000..123159a --- /dev/null +++ b/src/disk_io_thread_pool.cpp @@ -0,0 +1,204 @@ +/* + +Copyright (c) 2005-2016, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/disk_io_thread_pool.hpp" +#include "libtorrent/assert.hpp" + +#include + +namespace { + + constexpr std::chrono::seconds reap_idle_threads_interval(60); +} + +namespace libtorrent { + + disk_io_thread_pool::disk_io_thread_pool(pool_thread_interface& thread_iface + , io_service& ios) + : m_thread_iface(thread_iface) + , m_max_threads(0) + , m_threads_to_exit(0) + , m_abort(false) + , m_num_idle_threads(0) + , m_min_idle_threads(0) + , m_idle_timer(ios) + {} + + disk_io_thread_pool::~disk_io_thread_pool() + { + abort(true); + } + + void disk_io_thread_pool::set_max_threads(int const i) + { + std::lock_guard l(m_mutex); + if (i == m_max_threads) return; + m_max_threads = i; + if (int(m_threads.size()) < i) return; + stop_threads(int(m_threads.size()) - i); + } + + void disk_io_thread_pool::abort(bool wait) + { + std::unique_lock l(m_mutex); + if (m_abort) return; + m_abort = true; + m_idle_timer.cancel(); + stop_threads(int(m_threads.size())); + for (auto& t : m_threads) + { + if (wait) + { + // must release m_mutex to avoid a deadlock if the thread + // tries to acquire it + l.unlock(); + t.join(); + l.lock(); + } + else + t.detach(); + } + m_threads.clear(); + } + + void disk_io_thread_pool::thread_active() + { + int const num_idle_threads = --m_num_idle_threads; + TORRENT_ASSERT(num_idle_threads >= 0); + + int current_min = m_min_idle_threads; + while (num_idle_threads < current_min + && !m_min_idle_threads.compare_exchange_weak(current_min, num_idle_threads)); + } + + bool disk_io_thread_pool::try_thread_exit(std::thread::id id) + { + int to_exit = m_threads_to_exit; + while (to_exit > 0 && + !m_threads_to_exit.compare_exchange_weak(to_exit, to_exit - 1)); + if (to_exit > 0) + { + std::unique_lock l(m_mutex); + if (!m_abort) + { + auto new_end = std::remove_if(m_threads.begin(), m_threads.end() + , [id](std::thread& t) + { + if (t.get_id() == id) + { + t.detach(); + return true; + } + return false; + }); + TORRENT_ASSERT(new_end != m_threads.end()); + m_threads.erase(new_end, m_threads.end()); + if (m_threads.empty()) m_idle_timer.cancel(); + } + } + return to_exit > 0; + } + + std::thread::id disk_io_thread_pool::first_thread_id() + { + std::lock_guard l(m_mutex); + if (m_threads.empty()) return {}; + return m_threads.front().get_id(); + } + + void disk_io_thread_pool::job_queued(int const queue_size) + { + // this check is not strictly necessary + // but do it to avoid acquiring the mutex in the trivial case + if (m_num_idle_threads >= queue_size) return; + std::lock_guard l(m_mutex); + if (m_abort) return; + + // reduce the number of threads requested to stop if we're going to need + // them for these new jobs + int to_exit = m_threads_to_exit; + while (to_exit > std::max(0, m_num_idle_threads - queue_size) && + !m_threads_to_exit.compare_exchange_weak(to_exit + , std::max(0, m_num_idle_threads - queue_size))); + + // now start threads until we either have enough to service + // all queued jobs without blocking or hit the max + for (int i = m_num_idle_threads + ; i < queue_size && int(m_threads.size()) < m_max_threads + ; ++i) + { + // if this is the first thread started, start the reaper timer + if (m_threads.empty()) + { + m_idle_timer.expires_from_now(reap_idle_threads_interval); + m_idle_timer.async_wait([this](error_code const& ec) { reap_idle_threads(ec); }); + } + + // work keeps the io_service::run() call blocked from returning. + // When shutting down, it's possible that the event queue is drained + // before the disk_io_thread has posted its last callback. When this + // happens, the io_service will have a pending callback from the + // disk_io_thread, but the event loop is not running. this means + // that the event is destructed after the disk_io_thread. If the + // event refers to a disk buffer it will try to free it, but the + // buffer pool won't exist anymore, and crash. This prevents that. + m_threads.emplace_back(&pool_thread_interface::thread_fun + , &m_thread_iface, std::ref(*this) + , io_service::work(get_io_service(m_idle_timer))); + } + } + + void disk_io_thread_pool::reap_idle_threads(error_code const& ec) + { + // take the minimum number of idle threads during the last + // sample period and request that many threads to exit + if (ec) return; + std::lock_guard l(m_mutex); + if (m_abort) return; + if (m_threads.empty()) return; + m_idle_timer.expires_from_now(reap_idle_threads_interval); + m_idle_timer.async_wait([this](error_code const& e) { reap_idle_threads(e); }); + int const min_idle = m_min_idle_threads.exchange(m_num_idle_threads); + if (min_idle <= 0) return; + // stop either the minimum number of idle threads or the number of threads + // which must be stopped to get below the max, whichever is larger + int const to_stop = std::max(min_idle, int(m_threads.size()) - m_max_threads); + stop_threads(to_stop); + } + + void disk_io_thread_pool::stop_threads(int num_to_stop) + { + m_threads_to_exit = num_to_stop; + m_thread_iface.notify_all(); + } + +} // namespace libtorrent diff --git a/src/disk_job_fence.cpp b/src/disk_job_fence.cpp new file mode 100644 index 0000000..779ccd7 --- /dev/null +++ b/src/disk_job_fence.cpp @@ -0,0 +1,236 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#include "libtorrent/aux_/disk_job_fence.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/performance_counters.hpp" + +#define DEBUG_STORAGE 0 + +#if DEBUG_STORAGE +#define DLOG(...) std::fprintf(__VA_ARGS__) +#else +#define DLOG(...) do {} while (false) +#endif + +namespace libtorrent { namespace aux { + + int disk_job_fence::job_complete(disk_io_job* j, tailqueue& jobs) + { + std::lock_guard l(m_mutex); + + TORRENT_ASSERT(j->flags & disk_io_job::in_progress); + j->flags &= ~disk_io_job::in_progress; + + TORRENT_ASSERT(m_outstanding_jobs > 0); + --m_outstanding_jobs; + if (j->flags & disk_io_job::fence) + { + // a fence job just completed. Make sure the fence logic + // works by asserting m_outstanding_jobs is in fact 0 now + TORRENT_ASSERT(m_outstanding_jobs == 0); + + // the fence can now be lowered + --m_has_fence; + + // now we need to post all jobs that have been queued up + // while this fence was up. However, if there's another fence + // in the queue, stop there and raise the fence again + int ret = 0; + while (!m_blocked_jobs.empty()) + { + disk_io_job *bj = m_blocked_jobs.pop_front(); + if (bj->flags & disk_io_job::fence) + { + // we encountered another fence. We cannot post anymore + // jobs from the blocked jobs queue. We have to go back + // into a raised fence mode and wait for all current jobs + // to complete. The exception is that if there are no jobs + // executing currently, we should add the fence job. + if (m_outstanding_jobs == 0 && jobs.empty()) + { + TORRENT_ASSERT(!(bj->flags & disk_io_job::in_progress)); + bj->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + ++ret; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + jobs.push_back(bj); + } + else + { + // put the fence job back in the blocked queue + m_blocked_jobs.push_front(bj); + } + return ret; + } + TORRENT_ASSERT(!(bj->flags & disk_io_job::in_progress)); + bj->flags |= disk_io_job::in_progress; + + ++m_outstanding_jobs; + ++ret; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + jobs.push_back(bj); + } + return ret; + } + + // there are still outstanding jobs, even if we have a + // fence, it's not time to lower it yet + // also, if we don't have a fence, we're done + if (m_outstanding_jobs > 0 || m_has_fence == 0) return 0; + + // there's a fence raised, and no outstanding operations. + // it means we can execute the fence job right now. + TORRENT_ASSERT(m_blocked_jobs.size() > 0); + + // this is the fence job + disk_io_job *bj = m_blocked_jobs.pop_front(); + TORRENT_ASSERT(bj->flags & disk_io_job::fence); + + TORRENT_ASSERT(!(bj->flags & disk_io_job::in_progress)); + bj->flags |= disk_io_job::in_progress; + + ++m_outstanding_jobs; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + // prioritize fence jobs since they're blocking other jobs + jobs.push_front(bj); + return 1; + } + + bool disk_job_fence::is_blocked(disk_io_job* j) + { + std::lock_guard l(m_mutex); + DLOG(stderr, "[%p] is_blocked: fence: %d num_outstanding: %d\n" + , static_cast(this), m_has_fence, int(m_outstanding_jobs)); + + // if this is the job that raised the fence, don't block it + // ignore fence can only ignore one fence. If there are several, + // this job still needs to get queued up + if (m_has_fence == 0) + { + TORRENT_ASSERT(!(j->flags & disk_io_job::in_progress)); + j->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + return false; + } + + m_blocked_jobs.push_back(j); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->blocked == false); + j->blocked = true; +#endif + + return true; + } + + bool disk_job_fence::has_fence() const + { + std::lock_guard l(m_mutex); + return m_has_fence != 0; + } + + int disk_job_fence::num_blocked() const + { + std::lock_guard l(m_mutex); + return m_blocked_jobs.size(); + } + + // j is the fence job. It must have exclusive access to the storage + // fj is the flush job. If the job j is queued, we need to issue + // this job + int disk_job_fence::raise_fence(disk_io_job* j, disk_io_job* fj + , counters& cnt) + { + TORRENT_ASSERT(!(j->flags & disk_io_job::in_progress)); + TORRENT_ASSERT(!(j->flags & disk_io_job::fence)); + j->flags |= disk_io_job::fence; + + std::lock_guard l(m_mutex); + + DLOG(stderr, "[%p] raise_fence: fence: %d num_outstanding: %d\n" + , static_cast(this), m_has_fence, int(m_outstanding_jobs)); + + if (m_has_fence == 0 && m_outstanding_jobs == 0) + { + ++m_has_fence; + DLOG(stderr, "[%p] raise_fence: need posting\n" + , static_cast(this)); + + // the job j is expected to be put on the job queue + // after this, without being passed through is_blocked() + // that's why we're accounting for it here + + // fj is expected to be discarded by the caller + j->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + return fence_post_fence; + } + + ++m_has_fence; + if (m_has_fence > 1) + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(fj->blocked == false); + fj->blocked = true; +#endif + m_blocked_jobs.push_back(fj); + cnt.inc_stats_counter(counters::blocked_disk_jobs); + TORRENT_ASSERT(!(j->flags & disk_io_job::in_progress)); + } + else + { + // in this case, fj is expected to be put on the job queue + fj->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->blocked == false); + j->blocked = true; +#endif + m_blocked_jobs.push_back(j); + cnt.inc_stats_counter(counters::blocked_disk_jobs); + + return m_has_fence > 1 ? fence_post_none : fence_post_flush; + } + +}} diff --git a/src/disk_job_pool.cpp b/src/disk_job_pool.cpp new file mode 100644 index 0000000..4a9cb4d --- /dev/null +++ b/src/disk_job_pool.cpp @@ -0,0 +1,109 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/disk_job_pool.hpp" +#include "libtorrent/disk_io_job.hpp" + +namespace libtorrent { + + disk_job_pool::disk_job_pool() + : m_jobs_in_use(0) + , m_read_jobs(0) + , m_write_jobs(0) + , m_job_pool(sizeof(disk_io_job)) + {} + + disk_job_pool::~disk_job_pool() + { +// #error this should be fixed! +// TORRENT_ASSERT(m_jobs_in_use == 0); + } + + disk_io_job* disk_job_pool::allocate_job(job_action_t const type) + { + std::unique_lock l(m_job_mutex); + disk_io_job* ptr = static_cast(m_job_pool.malloc()); + m_job_pool.set_next_size(100); + if (ptr == nullptr) return nullptr; + ++m_jobs_in_use; + if (type == job_action_t::read) ++m_read_jobs; + else if (type == job_action_t::write) ++m_write_jobs; + l.unlock(); + TORRENT_ASSERT(ptr); + + new (ptr) disk_io_job; + ptr->action = type; +#if TORRENT_USE_ASSERTS + ptr->in_use = true; +#endif + return ptr; + } + + void disk_job_pool::free_job(disk_io_job* j) + { + TORRENT_ASSERT(j); + if (j == nullptr) return; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->in_use); + j->in_use = false; +#endif + job_action_t const type = j->action; + j->~disk_io_job(); + std::lock_guard l(m_job_mutex); + if (type == job_action_t::read) --m_read_jobs; + else if (type == job_action_t::write) --m_write_jobs; + --m_jobs_in_use; + m_job_pool.free(j); + } + + void disk_job_pool::free_jobs(disk_io_job** j, int const num) + { + if (num == 0) return; + + int read_jobs = 0; + int write_jobs = 0; + for (int i = 0; i < num; ++i) + { + job_action_t const type = j[i]->action; + j[i]->~disk_io_job(); + if (type == job_action_t::read) ++read_jobs; + else if (type == job_action_t::write) ++write_jobs; + } + + std::lock_guard l(m_job_mutex); + m_read_jobs -= read_jobs; + m_write_jobs -= write_jobs; + m_jobs_in_use -= num; + for (int i = 0; i < num; ++i) + m_job_pool.free(j[i]); + } +} diff --git a/src/entry.cpp b/src/entry.cpp new file mode 100644 index 0000000..005a9a2 --- /dev/null +++ b/src/entry.cpp @@ -0,0 +1,754 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/lazy_entry.hpp" +#endif +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/throw.hpp" + +namespace libtorrent { + +namespace detail { + + string_view integer_to_str(span buf + , entry::integer_type val) + { + int sign = 0; + if (val < 0) + { + sign = 1; + val = -val; + } + char* ptr = &buf.back(); + *ptr-- = '\0'; + if (val == 0) *ptr-- = '0'; + while (ptr > buf.data() + sign && val != 0) + { + *ptr-- = '0' + char(val % 10); + val /= 10; + } + if (sign) *ptr-- = '-'; + ++ptr; + return {ptr, static_cast(&buf.back() - ptr)}; + } +} // detail + +namespace { + + [[noreturn]] inline void throw_error() + { aux::throw_ex(errors::invalid_entry_type); } + + template + void call_destructor(T* o) + { + TORRENT_ASSERT(o); + o->~T(); + } +} // anonymous + + entry& entry::operator[](string_view key) + { + auto const i = dict().find(key); + if (i != dict().end()) return i->second; + auto const ret = dict().emplace( + std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple()).first; + return ret->second; + } + + const entry& entry::operator[](string_view key) const + { + auto const i = dict().find(key); + if (i == dict().end()) throw_error(); + return i->second; + } + + entry* entry::find_key(string_view key) + { + auto const i = dict().find(key); + if (i == dict().end()) return nullptr; + return &i->second; + } + + entry const* entry::find_key(string_view key) const + { + auto const i = dict().find(key); + if (i == dict().end()) return nullptr; + return &i->second; + } + + entry::data_type entry::type() const + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return entry::data_type(m_type); + } + + entry::~entry() { destruct(); } + + entry& entry::operator=(const entry& e) & + { + if (&e == this) return *this; + destruct(); + copy(e); + return *this; + } + + entry& entry::operator=(entry&& e) & noexcept + { + if (&e == this) return *this; + destruct(); + const auto t = e.type(); + switch (t) + { + case int_t: + new (&data) integer_type(std::move(e.integer())); + break; + case string_t: + new (&data) string_type(std::move(e.string())); + break; + case list_t: + new (&data) list_type(std::move(e.list())); + break; + case dictionary_t: + new (&data) dictionary_type(std::move(e.dict())); + break; + case undefined_t: + break; + case preformatted_t: + new (&data) preformatted_type(std::move(e.preformatted())); + break; + } + m_type = t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + entry::integer_type& entry::integer() + { + if (m_type == undefined_t) construct(int_t); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + if (m_type != int_t) throw_error(); + TORRENT_ASSERT(m_type == int_t); + return *reinterpret_cast(&data); + } + + entry::integer_type const& entry::integer() const + { + if (m_type != int_t) throw_error(); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == int_t); + return *reinterpret_cast(&data); + } + + entry::string_type& entry::string() + { + if (m_type == undefined_t) construct(string_t); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + if (m_type != string_t) throw_error(); + TORRENT_ASSERT(m_type == string_t); + return *reinterpret_cast(&data); + } + + entry::string_type const& entry::string() const + { + if (m_type != string_t) throw_error(); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == string_t); + return *reinterpret_cast(&data); + } + + entry::list_type& entry::list() + { + if (m_type == undefined_t) construct(list_t); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + if (m_type != list_t) throw_error(); + TORRENT_ASSERT(m_type == list_t); + return *reinterpret_cast(&data); + } + + entry::list_type const& entry::list() const + { + if (m_type != list_t) throw_error(); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == list_t); + return *reinterpret_cast(&data); + } + + entry::dictionary_type& entry::dict() + { + if (m_type == undefined_t) construct(dictionary_t); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + if (m_type != dictionary_t) throw_error(); + TORRENT_ASSERT(m_type == dictionary_t); + return *reinterpret_cast(&data); + } + + entry::dictionary_type const& entry::dict() const + { + if (m_type != dictionary_t) throw_error(); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == dictionary_t); + return *reinterpret_cast(&data); + } + + entry::preformatted_type& entry::preformatted() + { + if (m_type == undefined_t) construct(preformatted_t); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + if (m_type != preformatted_t) throw_error(); + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(&data); + } + + entry::preformatted_type const& entry::preformatted() const + { + if (m_type != preformatted_t) throw_error(); +#ifdef BOOST_NO_EXCEPTIONS + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(&data); + } + + entry::entry() + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + } + + entry::entry(data_type t) + : m_type(undefined_t) + { + construct(t); +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + } + + entry::entry(const entry& e) + : m_type(undefined_t) + { + copy(e); +#if TORRENT_USE_ASSERTS + m_type_queried = e.m_type_queried; +#endif + } + + entry::entry(entry&& e) noexcept + : m_type(undefined_t) + { + this->operator=(std::move(e)); + } + + entry::entry(bdecode_node const& n) + : m_type(undefined_t) + { + this->operator=(n); + } + + entry::entry(dictionary_type v) + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) dictionary_type(std::move(v)); + m_type = dictionary_t; + } + + entry::entry(span v) + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) string_type(v.data(), std::size_t(v.size())); + m_type = string_t; + } + + entry::entry(list_type v) + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) list_type(std::move(v)); + m_type = list_t; + } + + entry::entry(integer_type v) + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) integer_type(std::move(v)); + m_type = int_t; + } + + entry::entry(preformatted_type v) + : m_type(undefined_t) + { +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + new(&data) preformatted_type(std::move(v)); + m_type = preformatted_t; + } + + // convert a bdecode_node into an old school entry + entry& entry::operator=(bdecode_node const& e) & + { + destruct(); + switch (e.type()) + { + case bdecode_node::string_t: + this->string() = e.string_value().to_string(); + break; + case bdecode_node::int_t: + this->integer() = e.int_value(); + break; + case bdecode_node::dict_t: + { + dictionary_type& d = this->dict(); + for (int i = 0; i < e.dict_size(); ++i) + { + std::pair elem = e.dict_at(i); + d[elem.first.to_string()] = elem.second; + } + break; + } + case bdecode_node::list_t: + { + list_type& l = this->list(); + for (int i = 0; i < e.list_size(); ++i) + { + l.emplace_back(); + l.back() = e.list_at(i); + } + break; + } + case bdecode_node::none_t: + break; + } + return *this; + } + +#if TORRENT_ABI_VERSION == 1 + // convert a lazy_entry into an old school entry + entry& entry::operator=(lazy_entry const& e) & + { + destruct(); + switch (e.type()) + { + case lazy_entry::string_t: + this->string() = e.string_value(); + break; + case lazy_entry::int_t: + this->integer() = e.int_value(); + break; + case lazy_entry::dict_t: + { + dictionary_type& d = this->dict(); + for (int i = 0; i < e.dict_size(); ++i) + { + std::pair elem = e.dict_at(i); + d[elem.first] = *elem.second; + } + break; + } + case lazy_entry::list_t: + { + list_type& l = this->list(); + for (int i = 0; i < e.list_size(); ++i) + { + l.emplace_back(); + l.back() = *e.list_at(i); + } + break; + } + case lazy_entry::none_t: + break; + } + return *this; + } +#endif + + entry& entry::operator=(preformatted_type v) & + { + destruct(); + new(&data) preformatted_type(std::move(v)); + m_type = preformatted_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + entry& entry::operator=(dictionary_type v) & + { + destruct(); + new(&data) dictionary_type(std::move(v)); + m_type = dictionary_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + entry& entry::operator=(span v) & + { + destruct(); + new(&data) string_type(v.data(), std::size_t(v.size())); + m_type = string_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + entry& entry::operator=(list_type v) & + { + destruct(); + new(&data) list_type(std::move(v)); + m_type = list_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + entry& entry::operator=(integer_type v) & + { + destruct(); + new(&data) integer_type(std::move(v)); + m_type = int_t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + return *this; + } + + bool operator==(entry const& lhs, entry const& rhs) + { + if (lhs.type() != rhs.type()) return false; + + switch (lhs.type()) + { + case entry::int_t: + return lhs.integer() == rhs.integer(); + case entry::string_t: + return lhs.string() == rhs.string(); + case entry::list_t: + return lhs.list() == rhs.list(); + case entry::dictionary_t: + return lhs.dict() == rhs.dict(); + case entry::preformatted_t: + return lhs.preformatted() == rhs.preformatted(); + case entry::undefined_t: + return true; + } + return false; + } + + void entry::construct(data_type t) + { + switch (t) + { + case int_t: + new (&data) integer_type(0); + break; + case string_t: + new (&data) string_type; + break; + case list_t: + new (&data) list_type; + break; + case dictionary_t: + new (&data) dictionary_type; + break; + case undefined_t: + break; + case preformatted_t: + new (&data) preformatted_type; + break; + } + m_type = t; +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + } + + void entry::copy(entry const& e) + { + switch (e.type()) + { + case int_t: + new (&data) integer_type(e.integer()); + break; + case string_t: + new (&data) string_type(e.string()); + break; + case list_t: + new (&data) list_type(e.list()); + break; + case dictionary_t: + new (&data) dictionary_type(e.dict()); + break; + case undefined_t: + TORRENT_ASSERT(e.type() == undefined_t); + break; + case preformatted_t: + new (&data) preformatted_type(e.preformatted()); + break; + } + m_type = e.type(); +#if TORRENT_USE_ASSERTS + m_type_queried = true; +#endif + } + + void entry::destruct() + { + switch(m_type) + { + case int_t: + call_destructor(reinterpret_cast(&data)); + break; + case string_t: + call_destructor(reinterpret_cast(&data)); + break; + case list_t: + call_destructor(reinterpret_cast(&data)); + break; + case dictionary_t: + call_destructor(reinterpret_cast(&data)); + break; + case preformatted_t: + call_destructor(reinterpret_cast(&data)); + break; + default: + TORRENT_ASSERT(m_type == undefined_t); + break; + } + m_type = undefined_t; +#if TORRENT_USE_ASSERTS + m_type_queried = false; +#endif + } + + void entry::swap(entry& e) + { + bool clear_this = false; + bool clear_that = false; + + if (m_type == undefined_t && e.m_type == undefined_t) + return; + + if (m_type == undefined_t) + { + construct(data_type(e.m_type)); + clear_that = true; + } + + if (e.m_type == undefined_t) + { + e.construct(data_type(m_type)); + clear_this = true; + } + + if (m_type == e.m_type) + { + switch (m_type) + { + case int_t: + std::swap(*reinterpret_cast(&data) + , *reinterpret_cast(&e.data)); + break; + case string_t: + std::swap(*reinterpret_cast(&data) + , *reinterpret_cast(&e.data)); + break; + case list_t: + std::swap(*reinterpret_cast(&data) + , *reinterpret_cast(&e.data)); + break; + case dictionary_t: + std::swap(*reinterpret_cast(&data) + , *reinterpret_cast(&e.data)); + break; + case preformatted_t: + std::swap(*reinterpret_cast(&data) + , *reinterpret_cast(&e.data)); + break; + default: + break; + } + + if (clear_this) + destruct(); + + if (clear_that) + e.destruct(); + } + else + { + // currently, only swapping entries of the same type or where one + // of the entries is uninitialized is supported. + TORRENT_ASSERT_FAIL(); + } + } + +namespace { + bool is_binary(std::string const& str) + { + return std::any_of(str.begin(), str.end() + , [](char const c) { return !is_print(c); }); + } + + std::string print_string(std::string const& str) + { + if (is_binary(str)) return aux::to_hex(str); + else return str; + } + + void add_indent(std::string& out, int const indent) + { + out.resize(out.size() + size_t(indent), ' '); + } + + void print_list(std::string&, entry const&, int, bool); + void print_dict(std::string&, entry const&, int, bool); + + void to_string_impl(std::string& out, entry const& e, int const indent + , bool const single_line) + { + TORRENT_ASSERT(indent >= 0); + switch (e.type()) + { + case entry::int_t: + out += libtorrent::to_string(e.integer()).data(); + break; + case entry::string_t: + out += "'"; + out += print_string(e.string()); + out += "'"; + break; + case entry::list_t: + print_list(out, e, indent + 1, single_line); + break; + case entry::dictionary_t: + print_dict(out, e, indent + 1, single_line); + break; + case entry::preformatted_t: + out += ""; + break; + case entry::undefined_t: + out += ""; + break; + } + } + + void print_list(std::string& out, entry const& e + , int const indent, bool const single_line) + { + out += single_line ? "[ " : "[\n"; + bool first = true; + for (auto const& item : e.list()) + { + if (!first) out += single_line ? ", " : ",\n"; + first = false; + if (!single_line) add_indent(out, indent); + to_string_impl(out, item, indent, single_line); + } + out += " ]"; + } + + void print_dict(std::string& out, entry const& e + , int const indent, bool const single_line) + { + out += single_line ? "{ " : "{\n"; + bool first = true; + for (auto const& item : e.dict()) + { + if (!first) out += single_line ? ", " : ",\n"; + first = false; + if (!single_line) add_indent(out, indent); + out += "'"; + out += print_string(item.first); + out += "': "; + + to_string_impl(out, item.second, indent+1, single_line); + } + out += " }"; + } +} + + std::string entry::to_string(bool const single_line) const + { + std::string ret; + to_string_impl(ret, *this, 0, single_line); + return ret; + } + +} diff --git a/src/enum_net.cpp b/src/enum_net.cpp new file mode 100644 index 0000000..ea01821 --- /dev/null +++ b/src/enum_net.cpp @@ -0,0 +1,1417 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/span.hpp" +#ifdef TORRENT_WINDOWS +#include "libtorrent/aux_/win_util.hpp" +#endif + +#include +#include // for wcstombscstombs + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +#if TORRENT_USE_IFCONF +#include +#include +#include +#include +#include +#endif + +#if TORRENT_USE_SYSCTL +#include +#ifdef __APPLE__ +#include "TargetConditionals.h" +#endif + +#if defined TARGET_IPHONE_SIMULATOR || defined TARGET_OS_IPHONE +// net/route.h is not included in the iphone sdk. +#include "libtorrent/aux_/route.h" +#else +#include +#endif +#endif // TORRENT_USE_SYSCTL + +#if TORRENT_USE_GETIPFORWARDTABLE || TORRENT_USE_GETADAPTERSADDRESSES +#include "libtorrent/aux_/windows.hpp" +#include +#include // for IF_OPER_STATUS +#endif + +#if TORRENT_USE_NETLINK + +// We really should be including here, for the IF_OPER_* flags. +// Howerver, including this header creates conflicting definitions of +// on some platforms. So, instead, we just pull those flags out and define them +// here. +//#include // for IF_OPER* flags + +// RFC 2863 operational status +// these match the ones in linux/if.h, but with different names to not cause any +// conflicts +namespace if_oper { +enum : int { + unknown, + notpresent, + down, + lowerlayerdown, + testing, + dormant, + up, +}; +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined TORRENT_ANDROID && !defined IFA_F_DADFAILED +#define IFA_F_DADFAILED 8 +#endif + +#endif + +#if TORRENT_USE_IFADDRS +#include +#include +#include +#endif + +#if TORRENT_USE_IFADDRS || TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +// capture this here where warnings are disabled (the macro generates warnings) +const unsigned long siocgifmtu = SIOCGIFMTU; +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if defined(TORRENT_OS2) && !defined(IF_NAMESIZE) +#define IF_NAMESIZE IFNAMSIZ +#endif + +namespace libtorrent { + +namespace { + +#ifndef TORRENT_WINDOWS + struct socket_closer + { + socket_closer(int s) : m_socket(s) {} + socket_closer(socket_closer const&) = delete; + socket_closer(socket_closer &&) = delete; + socket_closer& operator=(socket_closer const&) = delete; + socket_closer& operator=(socket_closer &&) = delete; + ~socket_closer() { ::close(m_socket); } + private: + int m_socket; + }; +#endif + +#if !defined TORRENT_BUILD_SIMULATOR + address_v4 inaddr_to_address(void const* ina, int const len = 4) + { + boost::asio::ip::address_v4::bytes_type b = {}; + if (len > 0) std::memcpy(b.data(), ina, std::min(std::size_t(len), b.size())); + return address_v4(b); + } + + address_v6 inaddr6_to_address(void const* ina6, int const len = 16) + { + boost::asio::ip::address_v6::bytes_type b = {}; + if (len > 0) std::memcpy(b.data(), ina6, std::min(std::size_t(len), b.size())); + return address_v6(b); + } + +#if !TORRENT_USE_NETLINK + int sockaddr_len(sockaddr const* sin) + { +#if TORRENT_HAS_SALEN + return sin->sa_len; +#else + return sin->sa_family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); +#endif + } + + address sockaddr_to_address(sockaddr const* sin, int assume_family = -1) + { + if (sin->sa_family == AF_INET || assume_family == AF_INET) + return inaddr_to_address(&reinterpret_cast(sin)->sin_addr + , sockaddr_len(sin) - int(offsetof(sockaddr, sa_data))); + else if (sin->sa_family == AF_INET6 || assume_family == AF_INET6) + { + auto saddr = reinterpret_cast(sin); + auto ret = inaddr6_to_address(&saddr->sin6_addr + , sockaddr_len(sin) - int(offsetof(sockaddr, sa_data))); + ret.scope_id(saddr->sin6_scope_id); + return ret; + } + return address(); + } +#endif + + bool valid_addr_family(int family) + { + return (family == AF_INET + || family == AF_INET6 + ); + } + +#if TORRENT_USE_NETLINK || TORRENT_USE_IFADDRS || TORRENT_USE_IFCONF + interface_flags convert_if_flags(unsigned int const f) + { + return ((f & IFF_UP) ? if_flags::up : interface_flags{}) + | ((f & IFF_BROADCAST) ? if_flags::broadcast : interface_flags{}) + | ((f & IFF_LOOPBACK) ? if_flags::loopback : interface_flags{}) + | ((f & IFF_POINTOPOINT) ? if_flags::pointopoint : interface_flags{}) + | ((f & IFF_RUNNING) ? if_flags::running : interface_flags{}) + | ((f & IFF_NOARP) ? if_flags::noarp : interface_flags{}) + | ((f & IFF_PROMISC) ? if_flags::promisc : interface_flags{}) + | ((f & IFF_ALLMULTI) ? if_flags::allmulti : interface_flags{}) +#ifdef IFF_MASTER + | ((f & IFF_MASTER) ? if_flags::master : interface_flags{}) +#endif +#ifdef IFF_SLAVE + | ((f & IFF_SLAVE) ? if_flags::slave : interface_flags{}) +#endif + | ((f & IFF_MULTICAST) ? if_flags::multicast : interface_flags{}) +#ifdef IFF_DYNAMIC + | ((f & IFF_DYNAMIC) ? if_flags::dynamic : interface_flags{}) +#endif + ; + } +#endif + +#if TORRENT_USE_NETLINK + + int read_nl_sock(int sock, std::uint32_t const seq, std::uint32_t const pid + , std::function on_msg) + { + std::array buf; + for (;;) + { + int const read_len = int(recv(sock, buf.data(), buf.size(), 0)); + if (read_len < 0) return -1; + + nlmsghdr const* nl_hdr = reinterpret_cast(buf.data()); + int len = read_len; + + for (; len > 0 && NLMSG_OK(nl_hdr, len); nl_hdr = NLMSG_NEXT(nl_hdr, len)) + { +#ifdef __clang__ +#pragma clang diagnostic push +// NLMSG_OK uses signed/unsigned compare in the same expression +#pragma clang diagnostic ignored "-Wsign-compare" +#endif + // TODO: if we get here, the caller still assumes the error code + // is reported via errno + if ((NLMSG_OK(nl_hdr, read_len) == 0) || (nl_hdr->nlmsg_type == NLMSG_ERROR)) + return -1; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // this function doesn't handle multiple requests at the same time + // so report an error if the message does not have the expected seq and pid + // TODO: if we get here, the caller still assumes the error code + // is reported via errno + if (nl_hdr->nlmsg_seq != seq || nl_hdr->nlmsg_pid != pid) + return -1; + + if (nl_hdr->nlmsg_type == NLMSG_DONE) return 0; + + on_msg(nl_hdr); + + if ((nl_hdr->nlmsg_flags & NLM_F_MULTI) == 0) return 0; + } + } + return 0; + } + + int nl_dump_request(int const sock, std::uint32_t const seq + , nlmsghdr* const request_msg, std::function on_msg) + { + request_msg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; + request_msg->nlmsg_seq = seq; + // in theory nlmsg_pid should be set to the netlink port ID (NOT the process ID) + // of the sender, but the kernel ignores this field so it is typically set to + // zero + request_msg->nlmsg_pid = 0; + + if (::send(sock, request_msg, request_msg->nlmsg_len, 0) < 0) + return -1; + + // get the socket's port ID so that we can verify it in the repsonse + sockaddr_nl sock_addr; + socklen_t sock_addr_len = sizeof(sock_addr); + if (::getsockname(sock, reinterpret_cast(&sock_addr), &sock_addr_len) < 0) + return -1; + + return read_nl_sock(sock, seq, sock_addr.nl_pid, std::move(on_msg)); + } + + address to_address(int const address_family, void const* in) + { + if (address_family == AF_INET6) return inaddr6_to_address(in); + else return inaddr_to_address(in); + } + + struct link_info + { + int mtu; + std::uint32_t if_idx; + int type; + int oper_state; + char name[64]; + interface_flags flags; + }; + + link_info parse_nl_link(nlmsghdr const* nl_hdr) + { + auto const* if_msg = reinterpret_cast(NLMSG_DATA(nl_hdr)); + auto const* rta_ptr = reinterpret_cast(IFLA_RTA(if_msg)); + int attr_len = IFLA_PAYLOAD(nl_hdr); + + link_info ret{}; + ret.flags = convert_if_flags(if_msg->ifi_flags); + ret.if_idx = if_msg->ifi_index; + + for (; RTA_OK(rta_ptr, attr_len); rta_ptr = RTA_NEXT(rta_ptr, attr_len)) + { + auto* const ptr = RTA_DATA(rta_ptr); + switch (rta_ptr->rta_type) + { + case IFLA_IFNAME: + std::strncpy(ret.name, static_cast(ptr), sizeof(ret.name) - 1); + ret.name[sizeof(ret.name)-1] = '\0'; + break; + case IFLA_MTU: memcpy(&ret.mtu, ptr, sizeof(int)); break; + case IFLA_LINK: memcpy(&ret.type, ptr, sizeof(int)); break; + case IFLA_OPERSTATE: memcpy(&ret.oper_state, ptr, sizeof(int)); break; + + // ignore these attributes + case IFLA_CARRIER: + case IFLA_ADDRESS: + case IFLA_BROADCAST: + case IFLA_QDISC: + case IFLA_COST: + case IFLA_PRIORITY: + case IFLA_MASTER: + case IFLA_WIRELESS: + case IFLA_WEIGHT: + case IFLA_LINKMODE: + case IFLA_LINKINFO: + case IFLA_STATS64: + case IFLA_STATS: + case IFLA_PROMISCUITY: + default: + break; + }; + } + return ret; + } + + bool parse_route(int s, nlmsghdr const* nl_hdr, ip_route* rt_info) + { + // sanity check + if (nl_hdr->nlmsg_type != RTM_NEWROUTE) return false; + + rtmsg* rt_msg = reinterpret_cast(NLMSG_DATA(nl_hdr)); + + if (!valid_addr_family(rt_msg->rtm_family)) + return false; + + // make sure the defaults have the right address family + // in case the attributes are not present + if (rt_msg->rtm_family == AF_INET6) + { + rt_info->gateway = address_v6(); + rt_info->destination = address_v6(); + } + + int if_index = 0; + auto rt_len = RTM_PAYLOAD(nl_hdr); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-align" +#endif + for (rtattr* rt_attr = reinterpret_cast(RTM_RTA(rt_msg)); + RTA_OK(rt_attr, rt_len); rt_attr = RTA_NEXT(rt_attr, rt_len)) + { + switch(rt_attr->rta_type) + { + case RTA_OIF: + if_index = *reinterpret_cast(RTA_DATA(rt_attr)); + break; + case RTA_GATEWAY: + rt_info->gateway = to_address(rt_msg->rtm_family, RTA_DATA(rt_attr)); + break; + case RTA_DST: + rt_info->destination = to_address(rt_msg->rtm_family, RTA_DATA(rt_attr)); + break; + case RTA_PREFSRC: + rt_info->source_hint = to_address(rt_msg->rtm_family, RTA_DATA(rt_attr)); + break; + } + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + if (rt_info->gateway.is_v6() && rt_info->gateway.to_v6().is_link_local()) + { + address_v6 gateway6 = rt_info->gateway.to_v6(); + gateway6.scope_id(std::uint32_t(if_index)); + rt_info->gateway = gateway6; + } + + ifreq req = {}; + ::if_indextoname(std::uint32_t(if_index), req.ifr_name); + static_assert(sizeof(rt_info->name) >= sizeof(req.ifr_name), "ip_route::name is too small"); + std::memcpy(rt_info->name, req.ifr_name, sizeof(req.ifr_name)); + ::ioctl(s, ::siocgifmtu, &req); + rt_info->mtu = req.ifr_mtu; + rt_info->netmask = build_netmask(rt_msg->rtm_dst_len, rt_msg->rtm_family); + return true; + } + + bool parse_nl_address(nlmsghdr const* nl_hdr, span nics + , ip_interface* ip_info) + { + // sanity check + if (nl_hdr->nlmsg_type != RTM_NEWADDR) return false; + + ifaddrmsg* addr_msg = reinterpret_cast(NLMSG_DATA(nl_hdr)); + + if (!valid_addr_family(addr_msg->ifa_family)) + return false; + + auto interface = std::find_if(nics.begin(), nics.end() + , [addr_msg](link_info const& li) { return li.if_idx == addr_msg->ifa_index; }); + TORRENT_ASSERT(interface != nics.end()); + if (interface == nics.end()) return false; + + ip_info->preferred = (addr_msg->ifa_flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED | IFA_F_TENTATIVE)) == 0; + ip_info->netmask = build_netmask(addr_msg->ifa_prefixlen, addr_msg->ifa_family); + + ip_info->interface_address = address(); + auto rt_len = IFA_PAYLOAD(nl_hdr); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-align" +#endif + for (rtattr* rt_attr = reinterpret_cast(IFA_RTA(addr_msg)); + RTA_OK(rt_attr, rt_len); rt_attr = RTA_NEXT(rt_attr, rt_len)) + { + switch(rt_attr->rta_type) + { + case IFA_ADDRESS: + // if this is a point-to-point link then IFA_LOCAL holds + // the local address while IFA_ADDRESS is the destination + // don't overwrite the former with the latter + if (!ip_info->interface_address.is_unspecified()) + break; + BOOST_FALLTHROUGH; + case IFA_LOCAL: + if (addr_msg->ifa_family == AF_INET6) + { + address_v6 addr = inaddr6_to_address(RTA_DATA(rt_attr)); + if (addr_msg->ifa_scope == RT_SCOPE_LINK) + addr.scope_id(addr_msg->ifa_index); + ip_info->interface_address = addr; + } + else + { + ip_info->interface_address = inaddr_to_address(RTA_DATA(rt_attr)); + } + break; + } + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + static_assert(sizeof(ip_info->name) == sizeof(interface->name), "interface name field sizes differ"); + memcpy(ip_info->name, interface->name, sizeof(ip_info->name)); + ip_info->flags = interface->flags; + + ip_info->state + = interface->oper_state == if_oper::up ? if_state::up + : interface->oper_state == if_oper::dormant ? if_state::dormant + : interface->oper_state == if_oper::lowerlayerdown ? if_state::lowerlayerdown + : interface->oper_state == if_oper::down ? if_state::down + : interface->oper_state == if_oper::notpresent ? if_state::notpresent + : interface->oper_state == if_oper::testing ? if_state::testing + : interface->oper_state == if_oper::unknown ? if_state::unknown + : if_state::unknown; + + return true; + } +#endif // TORRENT_USE_NETLINK +#endif // !BUILD_SIMULATOR + +#if TORRENT_USE_SYSCTL && !defined TORRENT_BUILD_SIMULATOR +#ifdef TORRENT_OS2 +int _System __libsocket_sysctl(int* mib, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen); +#endif + + bool parse_route(int, rt_msghdr* rtm, ip_route* rt_info) + { + sockaddr* rti_info[RTAX_MAX]; + auto* sa = reinterpret_cast(rtm + 1); + for (int i = 0; i < RTAX_MAX; ++i) + { + if ((rtm->rtm_addrs & (1 << i)) == 0) + { + rti_info[i] = nullptr; + continue; + } + rti_info[i] = sa; + +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) + + sa = reinterpret_cast(reinterpret_cast(sa) + ROUNDUP(sa->sa_len)); + +#undef ROUNDUP + } + + sa = rti_info[RTAX_GATEWAY]; + if (sa == nullptr + || rti_info[RTAX_DST] == nullptr + || rti_info[RTAX_NETMASK] == nullptr + || !valid_addr_family(sa->sa_family)) + return false; + + rt_info->gateway = sockaddr_to_address(rti_info[RTAX_GATEWAY]); + rt_info->destination = sockaddr_to_address(rti_info[RTAX_DST]); + rt_info->netmask = sockaddr_to_address(rti_info[RTAX_NETMASK] + , rt_info->destination.is_v4() ? AF_INET : AF_INET6); + if_indextoname(rtm->rtm_index, rt_info->name); + if (rti_info[RTAX_IFA]) rt_info->source_hint = sockaddr_to_address(rti_info[RTAX_IFA]); + return true; + } +#endif + +#if TORRENT_USE_IFADDRS && !defined TORRENT_BUILD_SIMULATOR + bool iface_from_ifaddrs(ifaddrs *ifa, ip_interface &rv) + { + // determine address + rv.interface_address = sockaddr_to_address(ifa->ifa_addr); + if (rv.interface_address.is_unspecified()) return false; + + std::strncpy(rv.name, ifa->ifa_name, sizeof(rv.name) - 1); + rv.name[sizeof(rv.name) - 1] = '\0'; + + // determine netmask + if (ifa->ifa_netmask != nullptr) + rv.netmask = sockaddr_to_address(ifa->ifa_netmask); + + rv.flags = convert_if_flags(ifa->ifa_flags); + return true; + } +#endif + + void build_netmask_impl(span mask, int prefix_bits) + { + TORRENT_ASSERT(prefix_bits <= mask.size() * 8); + TORRENT_ASSERT(prefix_bits >= 0); + int i = 0; + while (prefix_bits >= 8) + { + mask[i] = 0xff; + prefix_bits -= 8; + ++i; + } + if (i < mask.size()) + { + mask[i] = (0xff << (8 - prefix_bits)) & 0xff; + ++i; + while (i < mask.size()) + { + mask[i] = 0; + ++i; + } + } + } + +} // + + int family(address const& a) { return a.is_v4() ? AF_INET : AF_INET6; } + + address build_netmask(int prefix_bits, int const family) + { + if (family == AF_INET) + { + address_v4::bytes_type b; + build_netmask_impl(b, prefix_bits); + return address_v4(b); + } + else if (family == AF_INET6) + { + address_v6::bytes_type b; + build_netmask_impl(b, prefix_bits); + return address_v6(b); + } + return {}; + } + + // return (a1 & mask) == (a2 & mask) + bool match_addr_mask(address const& a1, address const& a2, address const& mask) + { + // all 3 addresses needs to belong to the same family + if (a1.is_v4() != a2.is_v4()) return false; + if (a1.is_v4() != mask.is_v4()) return false; + + if (a1.is_v6()) + { + if (a1.to_v6().scope_id() != a2.to_v6().scope_id()) return false; + + address_v6::bytes_type b1 = a1.to_v6().to_bytes(); + address_v6::bytes_type b2 = a2.to_v6().to_bytes(); + address_v6::bytes_type m = mask.to_v6().to_bytes(); + for (std::size_t i = 0; i < b1.size(); ++i) + { + b1[i] &= m[i]; + b2[i] &= m[i]; + } + return b1 == b2; + } + return (a1.to_v4().to_ulong() & mask.to_v4().to_ulong()) + == (a2.to_v4().to_ulong() & mask.to_v4().to_ulong()); + } + + std::vector enum_net_interfaces(io_service& ios, error_code& ec) + { + TORRENT_UNUSED(ios); // this may be unused depending on configuration + std::vector ret; + ec.clear(); +#if defined TORRENT_BUILD_SIMULATOR + + std::vector
    ips = ios.get_ips(); + + for (auto const& ip : ips) + { + ip_interface wan; + wan.interface_address = ip; + if (ip.is_v4()) + wan.netmask = address_v4::from_string("255.0.0.0"); + else + wan.netmask = address_v6::from_string("ffff::"); + std::strcpy(wan.name, "eth0"); + std::strcpy(wan.friendly_name, "Ethernet"); + std::strcpy(wan.description, "Simulator Ethernet Adapter"); + ret.push_back(wan); + } +#elif TORRENT_USE_NETLINK + int const sock = ::socket(PF_ROUTE, SOCK_DGRAM, NETLINK_ROUTE); + if (sock < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + socket_closer c1(sock); + + // netlink socket documentation: + // https://people.redhat.com/nhorman/papers/netlink.pdf + int seq = 0; + + struct + { + struct nlmsghdr hdr; + struct ifinfomsg msg; + } link_req{}; + + link_req.hdr.nlmsg_len = std::uint32_t(NLMSG_LENGTH(sizeof(link_req.msg))); + link_req.hdr.nlmsg_type = RTM_GETLINK; + link_req.msg.ifi_family = AF_PACKET; + link_req.msg.ifi_change = 0xFFFFFFFF; + + std::vector nics; + if (nl_dump_request(sock, seq++, &link_req.hdr, [&](nlmsghdr const* msg) { + + // sanity check + if (msg->nlmsg_type != RTM_NEWLINK) return; + + nics.push_back(parse_nl_link(msg)); + }) != 0) + { + ec = error_code(errno, system_category()); + return ret; + } + + struct + { + struct nlmsghdr hdr; + struct ifaddrmsg msg; + } request{}; + + request.hdr.nlmsg_len = std::uint32_t(NLMSG_LENGTH(sizeof(request.msg))); + request.hdr.nlmsg_type = RTM_GETADDR; + request.msg.ifa_family = AF_PACKET; + + if (nl_dump_request(sock, seq++, &request.hdr, [&](nlmsghdr const* msg) { + ip_interface iface; + if (parse_nl_address(msg, nics, &iface)) ret.push_back(iface); + }) != 0) + { + ec = error_code(errno, system_category()); + return ret; + } +#elif TORRENT_USE_IFADDRS + int const s = ::socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + socket_closer c1(s); + + ifaddrs *ifaddr; + if (getifaddrs(&ifaddr) == -1) + { + ec = error_code(errno, system_category()); + return ret; + } + + for (ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + ip_interface iface; + if (iface_from_ifaddrs(ifa, iface)) + ret.push_back(iface); + } + freeifaddrs(ifaddr); +// MacOS X, BSD and solaris +#elif TORRENT_USE_IFCONF + int const s = ::socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + socket_closer c1(s); + ifconf ifc; + // make sure the buffer is aligned to hold ifreq structs + ifreq buf[40]; + ifc.ifc_len = sizeof(buf); + ifc.ifc_req = buf; + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) + { + ec = error_code(errno, system_category()); + return ret; + } + + char *ifr = ifc.ifc_buf; + + int current_size = 0; + for (int remaining = ifc.ifc_len; + remaining > 0; + ifr += current_size, remaining -= current_size) + { + ifreq const& item = *reinterpret_cast(ifr); + +#ifdef _SIZEOF_ADDR_IFREQ + current_size = _SIZEOF_ADDR_IFREQ(item); +#elif defined TORRENT_BSD + current_size = item.ifr_addr.sa_len + IFNAMSIZ; +#else + current_size = sizeof(ifreq); +#endif + + if (remaining < current_size) break; + + if (!valid_addr_family(item.ifr_addr.sa_family)) + continue; + + ip_interface iface; + iface.interface_address = sockaddr_to_address(&item.ifr_addr); + std::strncpy(iface.name, item.ifr_name, sizeof(iface.name) - 1); + iface.name[sizeof(iface.name) - 1] = '\0'; + + ifreq req = {}; + std::strncpy(req.ifr_name, item.ifr_name, IF_NAMESIZE - 1); + if (ioctl(s, SIOCGIFFLAGS, &req) < 0) + { + ec = error_code(errno, system_category()); + return {}; + } + iface.flags = convert_if_flags(req.ifr_flags); + + if (ioctl(s, SIOCGIFNETMASK, &req) < 0) + { + if (iface.interface_address.is_v6()) + { + // this is expected to fail (at least on MacOS X) + iface.netmask = address_v6::any(); + } + else + { + ec = error_code(errno, system_category()); + return ret; + } + } + else + { + iface.netmask = sockaddr_to_address(&req.ifr_addr, item.ifr_addr.sa_family); + } + ret.push_back(iface); + } + +#elif TORRENT_USE_GETADAPTERSADDRESSES + +#if _WIN32_WINNT >= 0x0501 + using GetAdaptersAddresses_t = ULONG (WINAPI *)(ULONG,ULONG,PVOID,PIP_ADAPTER_ADDRESSES,PULONG); + // Get GetAdaptersAddresses() pointer + auto GetAdaptersAddresses = + aux::get_library_procedure("GetAdaptersAddresses"); + + if (GetAdaptersAddresses != nullptr) + { + ULONG buf_size = 10000; + std::vector buffer(buf_size); + PIP_ADAPTER_ADDRESSES adapter_addresses + = reinterpret_cast(&buffer[0]); + + DWORD res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER + | GAA_FLAG_SKIP_ANYCAST, nullptr, adapter_addresses, &buf_size); + if (res == ERROR_BUFFER_OVERFLOW) + { + buffer.resize(buf_size); + adapter_addresses = reinterpret_cast(&buffer[0]); + res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER + | GAA_FLAG_SKIP_ANYCAST, nullptr, adapter_addresses, &buf_size); + } + if (res != NO_ERROR) + { + ec = error_code(WSAGetLastError(), system_category()); + return std::vector(); + } + + for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; + adapter != 0; adapter = adapter->Next) + { + ip_interface r; + std::strncpy(r.name, adapter->AdapterName, sizeof(r.name) - 1); + r.name[sizeof(r.name) - 1] = '\0'; + wcstombs(r.friendly_name, adapter->FriendlyName, sizeof(r.friendly_name)); + r.friendly_name[sizeof(r.friendly_name) - 1] = '\0'; + wcstombs(r.description, adapter->Description, sizeof(r.description)); + r.description[sizeof(r.description) - 1] = '\0'; + r.state + = (adapter->OperStatus == IfOperStatusUp) ? if_state::up + : (adapter->OperStatus == IfOperStatusDown) ? if_state::down + : (adapter->OperStatus == IfOperStatusTesting) ? if_state::testing + : (adapter->OperStatus == IfOperStatusUnknown) ? if_state::unknown + : (adapter->OperStatus == IfOperStatusDormant) ? if_state::dormant + : (adapter->OperStatus == IfOperStatusNotPresent) ? if_state::notpresent + : (adapter->OperStatus == IfOperStatusLowerLayerDown) ? if_state::lowerlayerdown + : if_state::unknown; + + r.flags = r.state != if_state::down ? if_flags::up : interface_flags{}; + if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) + r.flags |= if_flags::loopback; + if (adapter->IfType == IF_TYPE_PPP) + r.flags |= if_flags::pointopoint; + if (!(adapter->Flags & IP_ADAPTER_NO_MULTICAST)) + r.flags |= if_flags::multicast; + + for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; + unicast; unicast = unicast->Next) + { + auto const family = unicast->Address.lpSockaddr->sa_family; + + if (!valid_addr_family(family)) + continue; + + if (family == AF_INET && !(adapter->Flags & IP_ADAPTER_IPV4_ENABLED)) + r.flags &= ~if_flags::up; + else if (family == AF_INET6 && !(adapter->Flags & IP_ADAPTER_IPV6_ENABLED)) + r.flags &= ~if_flags::up; + else + r.flags |= if_flags::up; + + r.preferred = unicast->DadState == IpDadStatePreferred; + r.interface_address = sockaddr_to_address(unicast->Address.lpSockaddr); + int const max_prefix_len = family == AF_INET ? 32 : 128; + + if (unicast->Length <= offsetof(IP_ADAPTER_UNICAST_ADDRESS, OnLinkPrefixLength)) + { + // OnLinkPrefixLength is only present on Vista and newer. If + // we're running on XP, we don't have the netmask. + r.netmask = (family == AF_INET) + ? address(address_v4()) + : address(address_v6()); + ret.push_back(r); + continue; + } + + if (family == AF_INET6 + && unicast->OnLinkPrefixLength == 128 + && (unicast->PrefixOrigin == IpPrefixOriginDhcp + || unicast->SuffixOrigin == IpSuffixOriginRandom)) + { + // DHCPv6 does not specify a subnet mask (it should be taken from the RA) + // but apparently MS didn't get the memo and incorrectly reports a + // prefix length of 128 for DHCPv6 assigned addresses + // 128 is also reported for privacy addresses despite claiming to + // have gotten the prefix length from the RA *shrug* + // use a 64 bit prefix in these cases since that is likely to be + // the correct value, or at least less wrong than 128 + r.netmask = build_netmask(64, family); + } + else if (unicast->OnLinkPrefixLength <= max_prefix_len) + { + r.netmask = build_netmask(unicast->OnLinkPrefixLength, family); + } + else + { + // we don't know what the netmask is + r.netmask = (family == AF_INET) + ? address(address_v4()) + : address(address_v6()); + } + + ret.push_back(r); + } + } + + return ret; + } +#endif + + SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0); + if (int(s) == SOCKET_ERROR) + { + ec = error_code(WSAGetLastError(), system_category()); + return ret; + } + + INTERFACE_INFO buffer[30]; + DWORD size; + + if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, + sizeof(buffer), &size, 0, 0) != 0) + { + ec = error_code(WSAGetLastError(), system_category()); + closesocket(s); + return ret; + } + closesocket(s); + + int n = size / sizeof(INTERFACE_INFO); + + for (int i = 0; i < n; ++i) + { + ip_interface iface; + iface.interface_address = sockaddr_to_address(&buffer[i].iiAddress.Address); + if (iface.interface_address == address_v4::any()) continue; + iface.netmask = sockaddr_to_address(&buffer[i].iiNetmask.Address + , iface.interface_address.is_v4() ? AF_INET : AF_INET6); + ret.push_back(iface); + } + +#else + +#error "Don't know how to enumerate network interfaces on this platform" + +#endif + return ret; + } + + boost::optional
    get_gateway(ip_interface const& iface, span routes) + { + bool const v4 = iface.interface_address.is_v4(); + + // local IPv6 addresses can never be used to reach the internet + if (!v4 && is_local(iface.interface_address)) return {}; + + auto const it = std::find_if(routes.begin(), routes.end() + , [&](ip_route const& r) -> bool + { + return r.destination.is_unspecified() + && r.destination.is_v4() == iface.interface_address.is_v4() + && !r.gateway.is_unspecified() + // in case there are multiple networks on the same networking + // device, the source hint may be the only thing telling them + // apart + && (r.source_hint.is_unspecified() || r.source_hint == iface.interface_address) + && std::strcmp(r.name, iface.name) == 0; + }); + if (it != routes.end()) return it->gateway; + return {}; + } + + bool has_internet_route(string_view device, int const fam, span routes) + { + return std::find_if(routes.begin(), routes.end() + , [&](ip_route const& r) -> bool + { + // if *any* global IP can be routed to this interface, it's + // considered able to reach the internet + return family(r.destination) == fam + && r.name == device + && (r.destination.is_unspecified() + || is_global(r.destination) + ); + }) != routes.end(); + } + + std::vector enum_routes(io_service& ios, error_code& ec) + { + std::vector ret; + TORRENT_UNUSED(ios); + ec.clear(); + +#ifdef TORRENT_BUILD_SIMULATOR + + TORRENT_UNUSED(ec); + + std::vector
    ips = ios.get_ips(); + + for (auto const& ip : ips) + { + ip_route r; + if (ip.is_v4()) + { + r.destination = address_v4(); + r.netmask = address_v4::from_string("255.0.0.0"); + address_v4::bytes_type b = ip.to_v4().to_bytes(); + b[3] = 1; + r.gateway = address_v4(b); + } + else + { + r.destination = address_v6(); + r.netmask = address_v6::from_string("ffff:ffff:ffff:ffff::0"); + address_v6::bytes_type b = ip.to_v6().to_bytes(); + b[14] = 1; + r.gateway = address_v6(b); + } + std::strcpy(r.name, "eth0"); + r.mtu = ios.sim().config().path_mtu(ip, ip); + ret.push_back(r); + } + +#elif TORRENT_USE_SYSCTL +/* + struct rt_msg + { + rt_msghdr m_rtm; + char buf[512]; + }; + + rt_msg m; + int len = sizeof(rt_msg); + bzero(&m, len); + m.m_rtm.rtm_type = RTM_GET; + m.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY; + m.m_rtm.rtm_version = RTM_VERSION; + m.m_rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; + m.m_rtm.rtm_seq = 0; + m.m_rtm.rtm_msglen = len; + + int s = ::socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); + if (s == -1) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + + int n = write(s, &m, len); + if (n == -1) + { + ec = error_code(errno, system_category()); + ::close(s); + return std::vector(); + } + else if (n != len) + { + ec = boost::asio::error::operation_not_supported; + ::close(s); + return std::vector(); + } + bzero(&m, len); + + n = read(s, &m, len); + if (n == -1) + { + ec = error_code(errno, system_category()); + ::close(s); + return std::vector(); + } + + for (rt_msghdr* ptr = &m.m_rtm; (char*)ptr < ((char*)&m.m_rtm) + n; ptr = (rt_msghdr*)(((char*)ptr) + ptr->rtm_msglen)) + { + std::cout << " rtm_msglen: " << ptr->rtm_msglen << std::endl; + std::cout << " rtm_type: " << ptr->rtm_type << std::endl; + if (ptr->rtm_errno) + { + ec = error_code(ptr->rtm_errno, system_category()); + return std::vector(); + } + if (m.m_rtm.rtm_flags & RTF_UP == 0 + || m.m_rtm.rtm_flags & RTF_GATEWAY == 0) + { + ec = boost::asio::error::operation_not_supported; + return address_v4::any(); + } + if (ptr->rtm_addrs & RTA_DST == 0 + || ptr->rtm_addrs & RTA_GATEWAY == 0 + || ptr->rtm_addrs & RTA_NETMASK == 0) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + if (ptr->rtm_msglen > len - ((char*)ptr - ((char*)&m.m_rtm))) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + int min_len = sizeof(rt_msghdr) + 2 * sizeof(sockaddr_in); + if (m.m_rtm.rtm_msglen < min_len) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + ip_route r; + // destination + char* p = m.buf; + sockaddr_in* sin = (sockaddr_in*)p; + r.destination = sockaddr_to_address((sockaddr*)p); + + // gateway + p += sin->sin_len; + sin = (sockaddr_in*)p; + r.gateway = sockaddr_to_address((sockaddr*)p); + + // netmask + p += sin->sin_len; + sin = (sockaddr_in*)p; + r.netmask = sockaddr_to_address((sockaddr*)p); + ret.push_back(r); + } + ::close(s); +*/ + int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0}; + + std::size_t needed = 0; +#ifdef TORRENT_OS2 + if (__libsocket_sysctl(mib, 6, 0, &needed, 0, 0) < 0) +#else + if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) +#endif + { + ec = error_code(errno, system_category()); + return std::vector(); + } + + if (needed == 0) + { + return std::vector(); + } + + std::unique_ptr buf(new (std::nothrow) char[needed]); + if (buf == nullptr) + { + ec = boost::asio::error::no_memory; + return std::vector(); + } + +#ifdef TORRENT_OS2 + if (__libsocket_sysctl(mib, 6, buf.get(), &needed, 0, 0) < 0) +#else + if (sysctl(mib, 6, buf.get(), &needed, nullptr, 0) < 0) +#endif + { + ec = error_code(errno, system_category()); + return std::vector(); + } + + char* end = buf.get() + needed; + + int const s = ::socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + socket_closer c1(s); + rt_msghdr* rtm; + for (char* next = buf.get(); next < end; next += rtm->rtm_msglen) + { + rtm = reinterpret_cast(next); + if (rtm->rtm_version != RTM_VERSION + || (rtm->rtm_type != RTM_ADD + && rtm->rtm_type != RTM_GET)) + { + continue; + } + + ip_route r; + if (parse_route(s, rtm, &r)) ret.push_back(r); + } +#elif TORRENT_USE_GETIPFORWARDTABLE +/* + move this to enum_net_interfaces + // Get GetAdaptersInfo() pointer + using GetAdaptersInfo_t = DWORD (WINAPI*)(PIP_ADAPTER_INFO, PULONG); + GetAdaptersInfo_t GetAdaptersInfo = get_library_procedure("GetAdaptersInfo"); + if (GetAdaptersInfo == nullptr) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + PIP_ADAPTER_INFO adapter_info = 0; + ULONG out_buf_size = 0; + if (GetAdaptersInfo(adapter_info, &out_buf_size) != ERROR_BUFFER_OVERFLOW) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + adapter_info = (IP_ADAPTER_INFO*)malloc(out_buf_size); + if (!adapter_info) + { + ec = boost::asio::error::no_memory; + return std::vector(); + } + + if (GetAdaptersInfo(adapter_info, &out_buf_size) == NO_ERROR) + { + for (PIP_ADAPTER_INFO adapter = adapter_info; + adapter != 0; adapter = adapter->Next) + { + + ip_route r; + r.destination = address::from_string(adapter->IpAddressList.IpAddress.String, ec); + r.gateway = address::from_string(adapter->GatewayList.IpAddress.String, ec); + r.netmask = address::from_string(adapter->IpAddressList.IpMask.String, ec); + strncpy(r.name, adapter->AdapterName, sizeof(r.name) - 1); + r.name[sizeof(r.name) - 1] = '\0'; + + if (ec) + { + ec = error_code(); + continue; + } + ret.push_back(r); + } + } + + // Free memory + free(adapter_info); +*/ + + using GetIfEntry_t = DWORD (WINAPI *)(PMIB_IFROW pIfRow); + auto GetIfEntry = aux::get_library_procedure( + "GetIfEntry"); + + if (GetIfEntry == nullptr) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + +#if _WIN32_WINNT >= 0x0600 + using GetIpForwardTable2_t = DWORD (WINAPI *)( + ADDRESS_FAMILY, PMIB_IPFORWARD_TABLE2*); + using FreeMibTable_t = void (WINAPI *)(PVOID Memory); + + auto GetIpForwardTable2 = aux::get_library_procedure("GetIpForwardTable2"); + auto FreeMibTable = aux::get_library_procedure("FreeMibTable"); + if (GetIpForwardTable2 != nullptr && FreeMibTable != nullptr) + { + MIB_IPFORWARD_TABLE2* routes = nullptr; + int res = GetIpForwardTable2(AF_UNSPEC, &routes); + if (res == NO_ERROR) + { + for (int i = 0; i < int(routes->NumEntries); ++i) + { + ip_route r; + r.gateway = sockaddr_to_address((const sockaddr*)&routes->Table[i].NextHop); + // The scope_id in NextHop is always zero because that would make + // things too easy apparently + if (r.gateway.is_v6() && r.gateway.to_v6().is_link_local()) + { + address_v6 gateway6 = r.gateway.to_v6(); + gateway6.scope_id(routes->Table[i].InterfaceIndex); + r.gateway = gateway6; + } + r.destination = sockaddr_to_address( + (const sockaddr*)&routes->Table[i].DestinationPrefix.Prefix); + r.netmask = build_netmask(routes->Table[i].DestinationPrefix.PrefixLength + , routes->Table[i].DestinationPrefix.Prefix.si_family); + MIB_IFROW ifentry; + ifentry.dwIndex = routes->Table[i].InterfaceIndex; + if (GetIfEntry(&ifentry) == NO_ERROR) + { + WCHAR* name = ifentry.wszName; + // strip UNC prefix to match the names returned by enum_net_interfaces + if (wcsncmp(name, L"\\DEVICE\\TCPIP_", wcslen(L"\\DEVICE\\TCPIP_")) == 0) + { + name += wcslen(L"\\DEVICE\\TCPIP_"); + } + wcstombs(r.name, name, sizeof(r.name) - 1); + r.name[sizeof(r.name) - 1] = '\0'; + r.mtu = ifentry.dwMtu; + ret.push_back(r); + } + } + } + if (routes) FreeMibTable(routes); + return ret; + } +#endif + + // Get GetIpForwardTable() pointer + using GetIpForwardTable_t = DWORD (WINAPI*)(PMIB_IPFORWARDTABLE pIpForwardTable,PULONG pdwSize,BOOL bOrder); + + auto GetIpForwardTable = aux::get_library_procedure("GetIpForwardTable"); + if (GetIpForwardTable == nullptr) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + MIB_IPFORWARDTABLE* routes = nullptr; + ULONG out_buf_size = 0; + if (GetIpForwardTable(routes, &out_buf_size, FALSE) != ERROR_INSUFFICIENT_BUFFER) + { + ec = boost::asio::error::operation_not_supported; + return std::vector(); + } + + routes = (MIB_IPFORWARDTABLE*)malloc(out_buf_size); + if (!routes) + { + ec = boost::asio::error::no_memory; + return std::vector(); + } + + if (GetIpForwardTable(routes, &out_buf_size, FALSE) == NO_ERROR) + { + for (int i = 0; i < int(routes->dwNumEntries); ++i) + { + ip_route r; + r.destination = inaddr_to_address(&routes->table[i].dwForwardDest); + r.netmask = inaddr_to_address(&routes->table[i].dwForwardMask); + r.gateway = inaddr_to_address(&routes->table[i].dwForwardNextHop); + MIB_IFROW ifentry; + ifentry.dwIndex = routes->table[i].dwForwardIfIndex; + if (GetIfEntry(&ifentry) == NO_ERROR) + { + wcstombs(r.name, ifentry.wszName, sizeof(r.name) - 1); + r.name[sizeof(r.name) - 1] = '\0'; + r.mtu = ifentry.dwMtu; + ret.push_back(r); + } + } + } + + // Free memory + free(routes); +#elif TORRENT_USE_NETLINK + int const sock = ::socket(PF_ROUTE, SOCK_DGRAM, NETLINK_ROUTE); + if (sock < 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + socket_closer c1(sock); + + int dgram_sock = ::socket(AF_INET, SOCK_DGRAM, 0); + if (dgram_sock < 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + socket_closer c2(dgram_sock); + + struct + { + struct nlmsghdr hdr; + struct rtmsg msg; + } request{}; + request.hdr.nlmsg_len = std::uint32_t(NLMSG_LENGTH(sizeof(request.msg))); + request.hdr.nlmsg_type = RTM_GETROUTE; + request.msg.rtm_family = AF_UNSPEC; + + std::uint32_t seq = 0; + if (nl_dump_request(sock, seq++, &request.hdr, [&](nlmsghdr const* msg) { + ip_route r; + if (parse_route(dgram_sock, msg, &r)) ret.push_back(r); + }) != 0) + { + ec = error_code(errno, system_category()); + return std::vector(); + } + +#else +#error "don't know how to enumerate network routes on this platform" +#endif + return ret; + } + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + std::string device_for_address(address addr, io_service& ios, error_code& ec) + { + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return {}; + + auto const iter = std::find_if(ifs.begin(), ifs.end() + , [&addr](ip_interface const& iface) + { return iface.interface_address == addr; }); + return (iter == ifs.end()) ? std::string() : iter->name; + } +} diff --git a/src/error_code.cpp b/src/error_code.cpp new file mode 100644 index 0000000..11a1b53 --- /dev/null +++ b/src/error_code.cpp @@ -0,0 +1,348 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_util.hpp" // for to_string() + +#include + +namespace libtorrent { + + struct libtorrent_error_category final : boost::system::error_category + { + const char* name() const BOOST_SYSTEM_NOEXCEPT override; + std::string message(int ev) const override; + boost::system::error_condition default_error_condition(int ev) const BOOST_SYSTEM_NOEXCEPT override + { return boost::system::error_condition(ev, *this); } + }; + + const char* libtorrent_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "libtorrent"; + } + + std::string libtorrent_error_category::message(int ev) const + { + static char const* msgs[] = + { + "no error", + "torrent file collides with file from another torrent", + "hash check failed", + "torrent file is not a dictionary", + "missing or invalid 'info' section in torrent file", + "'info' entry is not a dictionary", + "invalid or missing 'piece length' entry in torrent file", + "missing name in torrent file", + "invalid 'name' of torrent (possible exploit attempt)", + "invalid length of torrent", + "failed to parse files from torrent file", + "invalid or missing 'pieces' entry in torrent file", + "incorrect number of piece hashes in torrent file", + "too many pieces in torrent", + "invalid metadata received from swarm", + "invalid bencoding", + "no files in torrent", + "invalid escaped string", + "session is closing", + "torrent already exists in session", + "invalid torrent handle used", + "invalid type requested from entry", + "missing info-hash from URI", + "file too short", + "unsupported URL protocol", + "failed to parse URL", + "peer sent 0 length piece", + "parse failed", + "invalid file format tag", + "missing info-hash", + "mismatching info-hash", + "invalid hostname", + "invalid port", + "port blocked by port-filter", + "expected closing ] for address", + "destructing torrent", + "timed out", + "upload to upload connection", + "uninteresting upload-only peer", + "invalid info-hash", + "torrent paused", + "'have'-message with higher index than the number of pieces", + "bitfield of invalid size", + "too many piece requests while choked", + "invalid piece packet", + "out of memory", + "torrent aborted", + "connected to ourselves", + "invalid piece size", + "timed out: no interest", + "timed out: inactivity", + "timed out: no handshake", + "timed out: no request", + "invalid choke message", + "invalid unchoke message", + "invalid interested message", + "invalid not-interested message", + "invalid request message", + "invalid hash list", + "invalid hash piece message", + "invalid cancel message", + "invalid dht-port message", + "invalid suggest piece message", + "invalid have-all message", + "invalid have-none message", + "invalid reject message", + "invalid allow-fast message", + "invalid extended message", + "invalid message", + "sync hash not found", + "unable to verify encryption constant", + "plaintext mode not provided", + "rc4 mode not provided", + "unsupported encryption mode", + "peer selected unsupported encryption mode", + "invalid encryption pad size", + "invalid encryption handshake", + "incoming encrypted connections disabled", + "incoming regular connections disabled", + "duplicate peer-id", + "torrent removed", + "packet too large", + "", + "HTTP error", + "missing location header", + "invalid redirection", + "redirecting", + "invalid HTTP range", + "missing content-length", + "banned by IP filter", + "too many connections", + "peer banned", + "stopping torrent", + "too many corrupt pieces", + "torrent is not ready to accept peers", + "peer is not properly constructed", + "session is closing", + "optimistic disconnect", + "torrent finished", + "no router found", + "metadata too large", + "invalid metadata request", + "invalid metadata size", + "invalid metadata offset", + "invalid metadata message", + "pex message too large", + "invalid pex message", + "invalid lt_tracker message", + "pex messages sent too frequent (possible attack)", + "torrent has no metadata", + "invalid dont-have message", + "SSL connection required", + "invalid SSL certificate", + "not an SSL torrent", + "banned by port filter", + "invalid session handle used", + "listen socket has been closed", + "", + "", + "", + +// natpmp errors + "unsupported protocol version", + "not authorized to create port map (enable NAT-PMP on your router)", + "network failure", + "out of resources", + "unsupported opcode", + "", + "", + "", + "", + "", + +// fastresume errors + "missing or invalid 'file sizes' entry", + "no files in resume data", + "missing 'slots' and 'pieces' entry", + "mismatching number of files", + "mismatching file size", + "mismatching file timestamp", + "not a dictionary", + "invalid 'blocks per piece' entry", + "missing slots list", + "file has more slots than torrent", + "invalid entry type in slot list", + "invalid piece index in slot list", + "pieces needs to be reordered", + "fastresume not modified since last save", + "", + "", + "", + "", + "", + "", + +// HTTP errors + "Invalid HTTP header", + "missing Location header in HTTP redirect", + "failed to decompress HTTP response", + "", + "", + "", + "", + "", + "", + "", + +// i2p errors + "no i2p router is set up", + "", + "", + "", + "", + "", + "", + "", + "", + "", + +// tracker errors + "scrape not available on tracker", + "invalid tracker response", + "invalid peer dictionary entry", + "tracker sent a failure message", + "missing or invalid 'files' entry", + "missing or invalid 'hash' entry", + "missing or invalid 'peers' and 'peers6' entry", + "udp tracker response packet has invalid size", + "invalid transaction id in udp tracker response", + "invalid action field in udp tracker response", +#if TORRENT_ABI_VERSION == 1 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + +// bdecode errors + "expected string in bencoded string", + "expected colon in bencoded string", + "unexpected end of file in bencoded string", + "expected value (list, dict, int or string) in bencoded string", + "bencoded nesting depth exceeded", + "bencoded item count limit exceeded", + "integer overflow", + "", + "", + "", +#else + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", +#endif + "random number generator failed", + }; + if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) + return "Unknown error"; + return msgs[ev]; + } + + boost::system::error_category& libtorrent_category() + { + static libtorrent_error_category libtorrent_category; + return libtorrent_category; + } + + struct http_error_category final : boost::system::error_category + { + const char* name() const BOOST_SYSTEM_NOEXCEPT override + { return "http"; } + std::string message(int ev) const override + { + std::string ret; + ret += to_string(ev).data(); + ret += ' '; + switch (ev) + { + case errors::cont: ret += "Continue"; break; + case errors::ok: ret += "OK"; break; + case errors::created: ret += "Created"; break; + case errors::accepted: ret += "Accepted"; break; + case errors::no_content: ret += "No Content"; break; + case errors::multiple_choices: ret += "Multiple Choices"; break; + case errors::moved_permanently: ret += "Moved Permanently"; break; + case errors::moved_temporarily: ret += "Moved Temporarily"; break; + case errors::not_modified: ret += "Not Modified"; break; + case errors::bad_request: ret += "Bad Request"; break; + case errors::unauthorized: ret += "Unauthorized"; break; + case errors::forbidden: ret += "Forbidden"; break; + case errors::not_found: ret += "Not Found"; break; + case errors::internal_server_error: ret += "Internal Server Error"; break; + case errors::not_implemented: ret += "Not Implemented"; break; + case errors::bad_gateway: ret += "Bad Gateway"; break; + case errors::service_unavailable: ret += "Service Unavailable"; break; + default: ret += "(unknown HTTP error)"; break; + } + return ret; + } + boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT override + { return boost::system::error_condition(ev, *this); } + }; + + boost::system::error_category& http_category() + { + static http_error_category http_category; + return http_category; + } + + namespace errors + { + // hidden + boost::system::error_code make_error_code(error_code_enum e) + { + return boost::system::error_code(e, libtorrent_category()); + } + } + + std::string print_error(error_code const& ec) + { + if (!ec) return {}; + std::stringstream ret; + ret << "ERROR: (" << ec.category().name() << ":" << ec.value() << ") " + << ec.message(); + return ret.str(); + } + +} diff --git a/src/escape_string.cpp b/src/escape_string.cpp new file mode 100644 index 0000000..bd89654 --- /dev/null +++ b/src/escape_string.cpp @@ -0,0 +1,645 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include +#include + +#ifdef TORRENT_WINDOWS +#include "libtorrent/aux_/windows.hpp" +#endif + +#if TORRENT_USE_ICONV +#include +#include +#endif + +#if TORRENT_USE_LOCALE +#include +#endif + +#include "libtorrent/assert.hpp" +#include "libtorrent/parse_url.hpp" + +#include "libtorrent/utf8.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/string_util.hpp" // for to_string +#include "libtorrent/aux_/array.hpp" + +namespace libtorrent { + + // defined in hex.cpp + namespace aux { + + extern const char hex_chars[]; + } + + std::string unescape_string(string_view s, error_code& ec) + { + std::string ret; + for (auto i = s.begin(); i != s.end(); ++i) + { + if (*i == '+') + { + ret += ' '; + } + else if (*i != '%') + { + ret += *i; + } + else + { + ++i; + if (i == s.end()) + { + ec = errors::invalid_escaped_string; + return ret; + } + + int high; + if (*i >= '0' && *i <= '9') high = *i - '0'; + else if (*i >= 'A' && *i <= 'F') high = *i + 10 - 'A'; + else if (*i >= 'a' && *i <= 'f') high = *i + 10 - 'a'; + else + { + ec = errors::invalid_escaped_string; + return ret; + } + + ++i; + if (i == s.end()) + { + ec = errors::invalid_escaped_string; + return ret; + } + + int low; + if(*i >= '0' && *i <= '9') low = *i - '0'; + else if(*i >= 'A' && *i <= 'F') low = *i + 10 - 'A'; + else if(*i >= 'a' && *i <= 'f') low = *i + 10 - 'a'; + else + { + ec = errors::invalid_escaped_string; + return ret; + } + + ret += char(high * 16 + low); + } + } + return ret; + } + + // http://www.ietf.org/rfc/rfc2396.txt + // section 2.3 + static char const unreserved_chars[] = + // when determining if a url needs encoding + // % should be ok + "%+" + // reserved + ";?:@=&,$/" + // unreserved (special characters) ' excluded, + // since some buggy trackers fail with those + "-_!.~*()" + // unreserved (alphanumerics) + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789"; + + namespace { + + // the offset is used to ignore the first characters in the unreserved_chars table. + std::string escape_string_impl(const char* str, int const len, int const offset) + { + TORRENT_ASSERT(str != nullptr); + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset < int(sizeof(unreserved_chars)) - 1); + + std::string ret; + for (int i = 0; i < len; ++i) + { + if (std::strchr(unreserved_chars + offset, *str) && *str != 0) + { + ret += *str; + } + else + { + ret += '%'; + ret += aux::hex_chars[std::uint8_t(*str) >> 4]; + ret += aux::hex_chars[std::uint8_t(*str) & 15]; + } + ++str; + } + return ret; + } + + } // anonymous namespace + + std::string escape_string(string_view str) + { + return escape_string_impl(str.data(), int(str.size()), 11); + } + + std::string escape_path(string_view str) + { + return escape_string_impl(str.data(), int(str.size()), 10); + } + + bool need_encoding(char const* str, int const len) + { + for (int i = 0; i < len; ++i) + { + if (std::strchr(unreserved_chars, *str) == nullptr || *str == 0) + return true; + ++str; + } + return false; + } + + void convert_path_to_posix(std::string& path) + { + std::replace(path.begin(), path.end(), '\\', '/'); + } + +#ifdef TORRENT_WINDOWS + void convert_path_to_windows(std::string& path) + { + std::replace(path.begin(), path.end(), '/', '\\'); + } +#endif + + // TODO: 2 this should probably be moved into string_util.cpp + std::string read_until(char const*& str, char const delim, char const* end) + { + TORRENT_ASSERT(str <= end); + + std::string ret; + while (str != end && *str != delim) + { + ret += *str; + ++str; + } + // skip the delimiter as well + while (str != end && *str == delim) ++str; + return ret; + } + + std::string maybe_url_encode(std::string const& url) + { + std::string protocol, host, auth, path; + int port; + error_code ec; + std::tie(protocol, auth, host, port, path) = parse_url_components(url, ec); + if (ec) return url; + + // first figure out if this url contains unencoded characters + if (!need_encoding(path.c_str(), int(path.size()))) + return url; + + std::string msg; + std::string escaped_path { escape_path(path) }; + // reserve enough space so further append will + // only copy values to existing location + msg.reserve(protocol.size() + 3 + // protocol part + auth.size() + 1 + // auth part + host.size() + // host part + 1 + 5 + // port part + escaped_path.size()); + msg.append(protocol); + msg.append("://"); + if (!auth.empty()) + { + msg.append(auth); + msg.append("@"); + } + msg.append(host); + if (port != -1) + { + msg.append(":"); + msg.append(to_string(port).data()); + } + msg.append(escaped_path); + + return msg; + } + +#if TORRENT_ABI_VERSION == 1 + std::string resolve_file_url(std::string const& url) + { + TORRENT_ASSERT(url.substr(0, 7) == "file://"); + // first, strip the file:// part. + // On windows, we have + // to strip the first / as well + std::size_t num_to_strip = 7; +#ifdef TORRENT_WINDOWS + if (url[7] == '/' || url[7] == '\\') ++num_to_strip; +#endif + std::string ret = url.substr(num_to_strip); + + // we also need to URL-decode it + error_code ec; + std::string unescaped = unescape_string(ret, ec); + if (ec) unescaped = ret; + + // on windows, we need to convert forward slashes + // to backslashes +#ifdef TORRENT_WINDOWS + convert_path_to_windows(unescaped); +#endif + + return unescaped; + } +#endif + + std::string base64encode(const std::string& s) + { + static char const base64_table[] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + aux::array inbuf; + aux::array outbuf; + + std::string ret; + for (auto i = s.cbegin(); i != s.cend();) + { + // available input is 1,2 or 3 bytes + // since we read 3 bytes at a time at most + int available_input = std::min(int(inbuf.size()), int(s.end() - i)); + + // clear input buffer + inbuf.fill(0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf.begin()); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xfc) >> 2; + outbuf[1] = (((inbuf[0] & 0x03) << 4) | ((inbuf [1] & 0xf0) >> 4)) & 0xff; + outbuf[2] = (((inbuf[1] & 0x0f) << 2) | ((inbuf [2] & 0xc0) >> 6)) & 0xff; + outbuf[3] = inbuf[2] & 0x3f; + + // write output + for (int j = 0; j < available_input + 1; ++j) + { + ret += base64_table[outbuf[j]]; + } + + // write pad + for (int j = 0; j < int(inbuf.size()) - available_input; ++j) + { + ret += '='; + } + } + return ret; + } + +#if TORRENT_USE_I2P + std::string base32encode(string_view s, encode_string_flags_t const flags) + { + static char const base32_table_canonical[] = + { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '2', '3', '4', '5', '6', '7' + }; + static char const base32_table_lowercase[] = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '2', '3', '4', '5', '6', '7' + }; + char const *base32_table = (flags & string::lowercase) ? base32_table_lowercase : base32_table_canonical; + + static aux::array const input_output_mapping{{{0, 2, 4, 5, 7, 8}}}; + + aux::array inbuf; + aux::array outbuf; + + std::string ret; + for (auto i = s.begin(); i != s.end();) + { + int available_input = std::min(int(inbuf.size()), int(s.end() - i)); + + // clear input buffer + inbuf.fill(0); + + // read a chunk of input into inbuf + std::copy(i, i + available_input, inbuf.begin()); + i += available_input; + + // encode inbuf to outbuf + outbuf[0] = (inbuf[0] & 0xf8) >> 3; + outbuf[1] = (((inbuf[0] & 0x07) << 2) | ((inbuf[1] & 0xc0) >> 6)) & 0xff; + outbuf[2] = ((inbuf[1] & 0x3e) >> 1); + outbuf[3] = (((inbuf[1] & 0x01) << 4) | ((inbuf[2] & 0xf0) >> 4)) & 0xff; + outbuf[4] = (((inbuf[2] & 0x0f) << 1) | ((inbuf[3] & 0x80) >> 7)) & 0xff; + outbuf[5] = ((inbuf[3] & 0x7c) >> 2); + outbuf[6] = (((inbuf[3] & 0x03) << 3) | ((inbuf[4] & 0xe0) >> 5)) & 0xff; + outbuf[7] = inbuf[4] & 0x1f; + + // write output + int const num_out = input_output_mapping[available_input]; + for (int j = 0; j < num_out; ++j) + { + ret += base32_table[outbuf[j]]; + } + + if (!(flags & string::no_padding)) + { + // write pad + for (int j = 0; j < int(outbuf.size()) - num_out; ++j) + { + ret += '='; + } + } + } + return ret; + } +#endif // TORRENT_USE_I2P + + std::string base32decode(string_view s) + { + aux::array inbuf; + aux::array outbuf; + + std::string ret; + for (auto i = s.begin(); i != s.end();) + { + int available_input = std::min(int(inbuf.size()), int(s.end() - i)); + + int pad_start = 0; + if (available_input < 8) pad_start = available_input; + + // clear input buffer + inbuf.fill(0); + for (int j = 0; j < available_input; ++j) + { + char const in = char(std::toupper(*i++)); + if (in >= 'A' && in <= 'Z') + inbuf[j] = (in - 'A') & 0xff; + else if (in >= '2' && in <= '7') + inbuf[j] = (in - '2' + ('Z' - 'A') + 1) & 0xff; + else if (in == '=') + { + inbuf[j] = 0; + if (pad_start == 0) pad_start = j; + } + else if (in == '1') + inbuf[j] = 'I' - 'A'; + else + return std::string(); + TORRENT_ASSERT(inbuf[j] == (inbuf[j] & 0x1f)); + } + + // decode inbuf to outbuf + outbuf[0] = (inbuf[0] << 3) & 0xff; + outbuf[0] |= inbuf[1] >> 2; + outbuf[1] = ((inbuf[1] & 0x3) << 6) & 0xff; + outbuf[1] |= inbuf[2] << 1; + outbuf[1] |= (inbuf[3] & 0x10) >> 4; + outbuf[2] = ((inbuf[3] & 0x0f) << 4) & 0xff; + outbuf[2] |= (inbuf[4] & 0x1e) >> 1; + outbuf[3] = ((inbuf[4] & 0x01) << 7) & 0xff; + outbuf[3] |= (inbuf[5] & 0x1f) << 2; + outbuf[3] |= (inbuf[6] & 0x18) >> 3; + outbuf[4] = ((inbuf[6] & 0x07) << 5) & 0xff; + outbuf[4] |= inbuf[7]; + + static int const input_output_mapping[] = {5, 1, 1, 2, 2, 3, 4, 4, 5}; + int num_out = input_output_mapping[pad_start]; + + // write output + std::copy(outbuf.begin(), outbuf.begin() + num_out, std::back_inserter(ret)); + } + return ret; + } + + string_view trim(string_view str) + { + auto const first = str.find_first_not_of(" \t\n\r"); + auto const last = str.find_last_not_of(" \t\n\r"); + return str.substr(first == string_view::npos ? str.size() : first, last - first + 1); + } + + string_view::size_type find(string_view haystack, string_view needle, string_view::size_type pos) + { + auto const p = haystack.substr(pos).find(needle); + if (p == string_view::npos) return p; + return pos + p; + } + +#if defined TORRENT_WINDOWS + std::wstring convert_to_wstring(std::string const& s) + { + std::wstring ws; + ws.resize(s.size() + 1); + int wsize = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, &ws[0], int(ws.size())); + if (wsize < 0) return {}; + if (wsize > 0 && ws[wsize - 1] == '\0') --wsize; + ws.resize(wsize); + return ws; + } + + std::string convert_from_wstring(std::wstring const& s) + { + std::string ret; + ret.resize(s.size() * 4 + 1); + int size = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1 + , &ret[0], int(ret.size()), nullptr, nullptr); + if (size < 0) return {}; + if (size > 0 && ret[size - 1] == '\0') --size; + ret.resize(size); + return ret; + } +#endif + +#if TORRENT_USE_ICONV +namespace { + + // this is a helper function to deduce the type of the second argument to + // the iconv() function. + + template + size_t call_iconv(size_t (&fun)(iconv_t, Input**, size_t*, char**, size_t*) + , iconv_t cd, char const** in, size_t* insize, char** out, size_t* outsize) + { + return fun(cd, const_cast(in), insize, out, outsize); + } + + std::string iconv_convert_impl(std::string const& s, iconv_t h) + { + std::string ret; + size_t insize = s.size(); + size_t outsize = insize * 4; + ret.resize(outsize); + char const* in = s.c_str(); + char* out = &ret[0]; + // posix has a weird iconv() signature. implementations + // differ on the type of the second parameter. We use a helper template + // to deduce what we need to cast to. + std::size_t const retval = call_iconv(::iconv, h, &in, &insize, &out, &outsize); + if (retval == size_t(-1)) return s; + // if this string has an invalid utf-8 sequence in it, don't touch it + if (insize != 0) return s; + // not sure why this would happen, but it seems to be possible + if (outsize > s.size() * 4) return s; + // outsize is the number of bytes unused of the out-buffer + TORRENT_ASSERT(ret.size() >= outsize); + ret.resize(ret.size() - outsize); + return ret; + } +} // anonymous namespace + + std::string convert_to_native(std::string const& s) + { + static std::mutex iconv_mutex; + // only one thread can use this handle at a time + std::lock_guard l(iconv_mutex); + + // the empty string represents the local dependent encoding + static iconv_t iconv_handle = ::iconv_open("", "UTF-8"); + if (iconv_handle == iconv_t(-1)) return s; + return iconv_convert_impl(s, iconv_handle); + } + + std::string convert_from_native(std::string const& s) + { + static std::mutex iconv_mutex; + // only one thread can use this handle at a time + std::lock_guard l(iconv_mutex); + + // the empty string represents the local dependent encoding + static iconv_t iconv_handle = ::iconv_open("UTF-8", ""); + if (iconv_handle == iconv_t(-1)) return s; + return iconv_convert_impl(s, iconv_handle); + } + +#elif defined TORRENT_WINDOWS + +namespace { + + std::string convert_impl(std::string const& s, UINT from, UINT to) + { + std::wstring ws; + ws.resize(s.size() + 1); + int wsize = MultiByteToWideChar(from, 0, s.c_str(), -1, &ws[0], int(ws.size())); + if (wsize > 0 && ws[wsize - 1] == '\0') --wsize; + ws.resize(wsize); + + std::string ret; + ret.resize(ws.size() * 4 + 1); + int size = WideCharToMultiByte(to, 0, ws.c_str(), -1, &ret[0], int(ret.size()), nullptr, nullptr); + if (size > 0 && ret[size - 1] == '\0') --size; + ret.resize(size); + return ret; + } +} // anonymous namespace + + std::string convert_to_native(std::string const& s) + { + return convert_impl(s, CP_UTF8, CP_ACP); + } + + std::string convert_from_native(std::string const& s) + { + return convert_impl(s, CP_ACP, CP_UTF8); + } + +#elif TORRENT_USE_LOCALE + + std::string convert_to_native(std::string const& s) + { + std::mbstate_t state{}; + std::string ret; + string_view ptr = s; + while (!ptr.empty()) + { + std::int32_t codepoint; + int len; + + // decode a single utf-8 character + std::tie(codepoint, len) = parse_utf8_codepoint(ptr); + + if (codepoint == -1) + codepoint = '.'; + + ptr = ptr.substr(std::size_t(len)); + + char out[10]; + int const size = std::wcrtomb(out, static_cast(codepoint), &state); + if (size < 0) + ret += '.'; + else + for (int i = 0; i < size; ++i) + ret += out[i]; + } + return ret; + } + + std::string convert_from_native(std::string const& s) + { + std::mbstate_t state{}; + std::string ret; + string_view ptr = s; + while (!ptr.empty()) + { + wchar_t codepoint; + int const size = std::mbrtowc(&codepoint, ptr.data(), ptr.size(), &state); + if (size < 0) + ret.push_back('.'); + else + append_utf8_codepoint(ret, static_cast(codepoint)); + + ptr = ptr.substr(std::size_t(size < 1 ? 1 : size)); + } + + return ret; + } + +#endif + +} diff --git a/src/ffs.cpp b/src/ffs.cpp new file mode 100644 index 0000000..f00a8c7 --- /dev/null +++ b/src/ffs.cpp @@ -0,0 +1,183 @@ +/* + +Copyright (c) 2014-2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/ffs.hpp" +#include "libtorrent/aux_/byteswap.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if (defined _MSC_VER && _MSC_VER >= 1600 && (defined _M_IX86 || defined _M_X64)) +#include +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { +namespace aux { + + // returns the index of the first set bit. + // Use std::log2p1 in C++20 + int log2p1(std::uint32_t v) + { +// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + static const int MultiplyDeBruijnBitPosition[32] = + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return MultiplyDeBruijnBitPosition[std::uint32_t(v * 0x07C4ACDDU) >> 27]; + } + + int count_leading_zeros_sw(span buf) + { + auto const num = int(buf.size()); + std::uint32_t const* ptr = buf.data(); + + TORRENT_ASSERT(num >= 0); + TORRENT_ASSERT(ptr != nullptr); + + for (int i = 0; i < num; i++) + { + if (ptr[i] == 0) continue; + return i * 32 + 31 - log2p1(aux::network_to_host(ptr[i])); + } + + return num * 32; + } + + int count_leading_zeros_hw(span buf) + { + auto const num = int(buf.size()); + std::uint32_t const* ptr = buf.data(); + + TORRENT_ASSERT(num >= 0); + TORRENT_ASSERT(ptr != nullptr); + + for (int i = 0; i < num; i++) + { + if (ptr[i] == 0) continue; + +#if TORRENT_HAS_BUILTIN_CLZ + std::uint32_t const v = aux::network_to_host(ptr[i]); + return i * 32 + __builtin_clz(v); +#elif defined _MSC_VER + std::uint32_t const v = aux::network_to_host(ptr[i]); + DWORD pos; + _BitScanReverse(&pos, v); + return i * 32 + 31 - pos; +#else + TORRENT_ASSERT_FAIL(); + return -1; +#endif + } + + return num * 32; + } + + int count_leading_zeros(span buf) + { +#if TORRENT_HAS_BUILTIN_CLZ || defined _MSC_VER + return aux::count_leading_zeros_hw(buf); +#else + return aux::count_leading_zeros_sw(buf); +#endif + } + + int count_trailing_ones_sw(span buf) + { + auto const num = int(buf.size()); + std::uint32_t const* ptr = buf.data(); + + TORRENT_ASSERT(num >= 0); + TORRENT_ASSERT(ptr != nullptr); + + for (int i = num - 1; i >= 0; i--) + { + if (ptr[i] == 0xffffffff) continue; + std::uint32_t v = ~aux::network_to_host(ptr[i]); + + for (int k = 0; k < 32; ++k, v >>= 1) + { + if ((v & 1) == 0) continue; + return (num - i - 1) * 32 + k; + } + } + + return num * 32; + } + + int count_trailing_ones_hw(span buf) + { + auto const num = int(buf.size()); + std::uint32_t const* ptr = buf.data(); + + TORRENT_ASSERT(num >= 0); + TORRENT_ASSERT(ptr != nullptr); + + for (int i = num - 1; i >= 0; i--) + { + if (ptr[i] == 0xffffffff) continue; + +#if TORRENT_HAS_BUILTIN_CTZ + std::uint32_t const v = ~aux::network_to_host(ptr[i]); + return (num - i - 1) * 32 + __builtin_ctz(v); +#elif defined _MSC_VER + std::uint32_t const v = ~aux::network_to_host(ptr[i]); + DWORD pos; + _BitScanForward(&pos, v); + return (num - i - 1) * 32 + pos; +#else + TORRENT_ASSERT_FAIL(); + return -1; +#endif + } + + return num * 32; + } + + int count_trailing_ones(span buf) + { +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + return aux::count_trailing_ones_hw(buf); +#else + return aux::count_trailing_ones_sw(buf); +#endif + } +}} diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 0000000..791d1c6 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,1264 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include "libtorrent/span.hpp" +#include // for call_once + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-macros" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wunused-macros" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif + +// these defines are just in case the system we're on needs them for 64 bit file +// support +#define _FILE_OFFSET_BITS 64 +#define _LARGE_FILES 1 + +#ifndef TORRENT_WINDOWS +#include // for iovec +#else +#include +namespace { +struct iovec +{ + void* iov_base; + std::size_t iov_len; +}; +} // anonymous namespace +#endif + +// on mingw this is necessary to enable 64-bit time_t, specifically used for +// the stat struct. Without this, modification times returned by stat may be +// incorrect and consistently fail resume data +#ifndef __MINGW_USE_VC2005_COMPAT +# define __MINGW_USE_VC2005_COMPAT +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/aux_/path.hpp" // for convert_to_native_path_string +#include "libtorrent/string_util.hpp" +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/throw.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include // for IOV_MAX + +#ifdef TORRENT_WINDOWS +// windows part + +#ifndef PtrToPtr64 +#define PtrToPtr64(x) (x) +#endif + +#include "libtorrent/utf8.hpp" +#include "libtorrent/aux_/win_util.hpp" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#else +// posix part + +#include +#include +#include +#include + +#ifdef TORRENT_LINUX +// linux specifics + +#include +#ifdef TORRENT_ANDROID +#include +#define lseek lseek64 +#define pread pread64 +#define pwrite pwrite64 +#define ftruncate ftruncate64 +#endif + +#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +// mac specifics + +#include + +#endif + +// make sure the _FILE_OFFSET_BITS define worked +// on this platform. It's supposed to make file +// related functions support 64-bit offsets. +// this test makes sure lseek() returns a type +// at least 64 bits wide +static_assert(sizeof(lseek(0, 0, 0)) >= 8, "64 bit file operations are required"); + +#endif // posix part + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#if TORRENT_USE_PREADV +# if defined TORRENT_WINDOWS +namespace { + + // wrap the windows function in something that looks + // like preadv() and pwritev() + + // windows only lets us wait for 64 handles at a time, so this function makes + // sure we wait for all of them, partially in sequence + DWORD wait_for_multiple_objects(int num_handles, HANDLE* h) + { + int batch_size = std::min(num_handles, MAXIMUM_WAIT_OBJECTS); + while (WaitForMultipleObjects(batch_size, h, TRUE, INFINITE) != WAIT_FAILED) + { + h += batch_size; + num_handles -= batch_size; + batch_size = std::min(num_handles, MAXIMUM_WAIT_OBJECTS); + if (batch_size <= 0) return WAIT_OBJECT_0; + } + return WAIT_FAILED; + } + + int allocate_overlapped(::iovec const* bufs, lt::span ol + , lt::span h, std::int64_t file_offset) + { + std::memset(ol.data(), 0, sizeof(OVERLAPPED) * ol.size()); + for (std::ptrdiff_t i = 0; i < ol.size(); ++i) + { + ol[i].OffsetHigh = file_offset >> 32; + ol[i].Offset = file_offset & 0xffffffff; + ol[i].hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + h[i] = ol[i].hEvent; + if (h[i] == nullptr) + { + // we failed to create the event, roll-back and return an error + for (int j = 0; j < i; ++j) CloseHandle(h[i]); + return -1; + } + file_offset += bufs[i].iov_len; + } + return 0; + } + + int preadv(HANDLE fd, ::iovec const* bufs, int num_bufs, std::int64_t const file_offset) + { + TORRENT_ALLOCA(ol, OVERLAPPED, num_bufs); + TORRENT_ALLOCA(h, HANDLE, num_bufs); + + if (allocate_overlapped(bufs, ol, h, file_offset) < 0) return -1; + + BOOST_SCOPE_EXIT_ALL(&h) { + for (auto hnd : h) + CloseHandle(hnd); + }; + + int num_waits = num_bufs; + for (int i = 0; i < num_bufs; ++i) + { + DWORD num_read; + if (ReadFile(fd, bufs[i].iov_base, DWORD(bufs[i].iov_len), &num_read, &ol[i]) == FALSE) + { + DWORD const last_error = GetLastError(); + if (last_error == ERROR_HANDLE_EOF) + { + num_waits = i; + break; + } + else if (last_error != ERROR_IO_PENDING +#ifdef ERROR_CANT_WAIT + && last_error != ERROR_CANT_WAIT +#endif + ) + { + return -1; + } + } + } + + if (num_waits == 0) return 0; + + if (wait_for_multiple_objects(num_waits, h.data()) == WAIT_FAILED) + return -1; + + int ret = 0; + for (auto& o : ol.first(num_waits)) + { + if (WaitForSingleObject(o.hEvent, INFINITE) == WAIT_FAILED) + return -1; + + DWORD num_read; + if (GetOverlappedResult(fd, &o, &num_read, FALSE) == FALSE) + { + DWORD const last_error = GetLastError(); + if (last_error != ERROR_HANDLE_EOF) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(last_error != ERROR_CANT_WAIT); +#endif + return -1; + } + } + ret += num_read; + } + + return ret; + } + + int pwritev(HANDLE fd, ::iovec const* bufs, int num_bufs, std::int64_t const file_offset) + { + TORRENT_ALLOCA(ol, OVERLAPPED, num_bufs); + TORRENT_ALLOCA(h, HANDLE, num_bufs); + + if (allocate_overlapped(bufs, ol, h, file_offset) < 0) return -1; + + BOOST_SCOPE_EXIT_ALL(&h) { + for (auto hnd : h) + CloseHandle(hnd); + }; + + for (int i = 0; i < num_bufs; ++i) + { + DWORD num_written; + if (WriteFile(fd, bufs[i].iov_base, DWORD(bufs[i].iov_len), &num_written, &ol[i]) == FALSE + && GetLastError() != ERROR_IO_PENDING +#ifdef ERROR_CANT_WAIT + && GetLastError() != ERROR_CANT_WAIT +#endif + ) + { + return -1; + } + } + + if (wait_for_multiple_objects(int(h.size()), h.data()) == WAIT_FAILED) + return -1; + + int ret = 0; + for (auto& o : ol) + { + if (WaitForSingleObject(o.hEvent, INFINITE) == WAIT_FAILED) + return -1; + + DWORD num_written; + if (GetOverlappedResult(fd, &o, &num_written, FALSE) == FALSE) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(GetLastError() != ERROR_CANT_WAIT); +#endif + return -1; + } + ret += num_written; + } + + return ret; + } +} // namespace +# else + +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wreserved-id-macro" +# pragma clang diagnostic ignored "-Wunused-macros" +# endif + +# undef _BSD_SOURCE +# define _BSD_SOURCE // deprecated since glibc 2.20 +# undef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +# include + +# ifdef __clang__ +# pragma clang diagnostic pop +# endif + +# endif +#endif + +namespace libtorrent { + +static_assert(!(open_mode::rw_mask & open_mode::sparse), "internal flags error"); +static_assert(!(open_mode::rw_mask & open_mode::attribute_mask), "internal flags error"); +static_assert(!(open_mode::sparse & open_mode::attribute_mask), "internal flags error"); + + directory::directory(std::string const& path, error_code& ec) + : m_done(false) + { + ec.clear(); + std::string p{ path }; + +#ifdef TORRENT_WINDOWS + // the path passed to FindFirstFile() must be + // a pattern + p.append((!p.empty() && p.back() != '\\') ? "\\*" : "*"); +#else + // the path passed to opendir() may not + // end with a / + if (!p.empty() && p.back() == '/') + p.pop_back(); +#endif + + native_path_string f = convert_to_native_path_string(p); + +#ifdef TORRENT_WINDOWS + m_handle = FindFirstFileW(f.c_str(), &m_fd); + if (m_handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + m_done = true; + return; + } +#else + m_handle = ::opendir(f.c_str()); + if (m_handle == nullptr) + { + ec.assign(errno, system_category()); + m_done = true; + return; + } + // read the first entry + next(ec); +#endif // TORRENT_WINDOWS + } + + directory::~directory() + { +#ifdef TORRENT_WINDOWS + if (m_handle != INVALID_HANDLE_VALUE) + FindClose(m_handle); +#else + if (m_handle) ::closedir(m_handle); +#endif + } + + std::string directory::file() const + { +#ifdef TORRENT_WINDOWS + return convert_from_native_path(m_fd.cFileName); +#else + return convert_from_native_path(m_name.c_str()); +#endif + } + + void directory::next(error_code& ec) + { + ec.clear(); +#ifdef TORRENT_WINDOWS + if (FindNextFileW(m_handle, &m_fd) == 0) + { + m_done = true; + int err = GetLastError(); + if (err != ERROR_NO_MORE_FILES) + ec.assign(err, system_category()); + } +#else + struct dirent* de; + errno = 0; + if ((de = ::readdir(m_handle)) != nullptr) + { + m_name = de->d_name; + } + else + { + if (errno) ec.assign(errno, system_category()); + m_done = true; + } +#endif + } + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE (-1) +#endif + +#ifdef TORRENT_WINDOWS + struct overlapped_t + { + overlapped_t() + { + std::memset(&ol, 0, sizeof(ol)); + ol.hEvent = CreateEvent(0, true, false, 0); + } + ~overlapped_t() + { + if (ol.hEvent != INVALID_HANDLE_VALUE) + CloseHandle(ol.hEvent); + } + int wait(HANDLE file, error_code& ec) + { + if (ol.hEvent != INVALID_HANDLE_VALUE + && WaitForSingleObject(ol.hEvent, INFINITE) == WAIT_FAILED) + { + ec.assign(GetLastError(), system_category()); + return -1; + } + + DWORD ret; + if (GetOverlappedResult(file, &ol, &ret, false) == 0) + { + DWORD last_error = GetLastError(); + if (last_error != ERROR_HANDLE_EOF) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(last_error != ERROR_CANT_WAIT); +#endif + ec.assign(last_error, system_category()); + return -1; + } + } + return ret; + } + + OVERLAPPED ol; + }; +#endif // TORRENT_WINDOWS + + +#ifdef TORRENT_WINDOWS + void acquire_manage_volume_privs(); +#endif + + file::file() : m_file_handle(INVALID_HANDLE_VALUE) + {} + + file::file(file&& f) noexcept + : m_file_handle(f.m_file_handle) + , m_open_mode(f.m_open_mode) + { + f.m_file_handle = INVALID_HANDLE_VALUE; + } + + file& file::operator=(file&& f) + { + file tmp(std::move(*this)); // close at end of scope + m_file_handle = f.m_file_handle; + m_open_mode = f.m_open_mode; + f.m_file_handle = INVALID_HANDLE_VALUE; + return *this; + } + + file::file(std::string const& path, open_mode_t const mode, error_code& ec) + : m_file_handle(INVALID_HANDLE_VALUE) + { + // the return value is not important, since the + // error code contains the same information + open(path, mode, ec); + } + + file::~file() + { + close(); + } + + bool file::open(std::string const& path, open_mode_t mode, error_code& ec) + { + close(); + native_path_string file_path = convert_to_native_path_string(path); + +#ifdef TORRENT_WINDOWS + + struct win_open_mode_t + { + DWORD rw_mode; + DWORD create_mode; + }; + + static std::array const mode_array{ + { + // read_only + {GENERIC_READ, OPEN_EXISTING}, + // write_only + {GENERIC_WRITE, OPEN_ALWAYS}, + // read_write + {GENERIC_WRITE | GENERIC_READ, OPEN_ALWAYS}, + }}; + + static std::array const attrib_array{ + { + FILE_ATTRIBUTE_NORMAL, // no attrib + FILE_ATTRIBUTE_HIDDEN, // hidden + FILE_ATTRIBUTE_NORMAL, // executable + FILE_ATTRIBUTE_HIDDEN, // hidden + executable + }}; + + TORRENT_ASSERT(static_cast(mode & open_mode::rw_mask) < mode_array.size()); + win_open_mode_t const& m = mode_array[static_cast(mode & open_mode::rw_mask)]; + DWORD a = attrib_array[static_cast(mode & open_mode::attribute_mask) >> 7]; + + // one might think it's a good idea to pass in FILE_FLAG_RANDOM_ACCESS. It + // turns out that it isn't. That flag will break your operating system: + // http://support.microsoft.com/kb/2549369 + + DWORD const flags = ((mode & open_mode::random_access) ? 0 : FILE_FLAG_SEQUENTIAL_SCAN) + | a + | FILE_FLAG_OVERLAPPED + | ((mode & open_mode::no_cache) ? FILE_FLAG_WRITE_THROUGH : 0); + + if (!(mode & open_mode::sparse)) + { + // Enable privilege required by SetFileValidData() + // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilevaliddata + static std::once_flag flag; + std::call_once(flag, acquire_manage_volume_privs); + } + + handle_type handle = CreateFileW(file_path.c_str(), m.rw_mode + , FILE_SHARE_READ | FILE_SHARE_WRITE + , 0, m.create_mode, flags, 0); + + if (handle == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return false; + } + + m_file_handle = handle; + + // try to make the file sparse if supported + // only set this flag if the file is opened for writing + if ((mode & open_mode::sparse) + && (mode & open_mode::rw_mask) != open_mode::read_only) + { + DWORD temp; + overlapped_t ol; + BOOL ret = ::DeviceIoControl(native_handle(), FSCTL_SET_SPARSE, 0, 0 + , 0, 0, &temp, &ol.ol); + error_code error; + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + ol.wait(native_handle(), error); + } +#else // TORRENT_WINDOWS + + // rely on default umask to filter x and w permissions + // for group and others + int permissions = S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH; + + if ((mode & open_mode::attribute_executable)) + permissions |= S_IXGRP | S_IXOTH | S_IXUSR; +#ifdef O_BINARY + static const int mode_array[] = {O_RDONLY | O_BINARY, O_WRONLY | O_CREAT | O_BINARY, O_RDWR | O_CREAT | O_BINARY}; +#else + static const int mode_array[] = {O_RDONLY, O_WRONLY | O_CREAT, O_RDWR | O_CREAT}; +#endif + + int open_mode = 0 +#ifdef O_NOATIME + | ((mode & open_mode::no_atime) ? O_NOATIME : 0) +#endif +#ifdef O_SYNC + | ((mode & open_mode::no_cache) ? O_SYNC : 0) +#endif + ; + + handle_type handle = ::open(file_path.c_str() + , mode_array[static_cast(mode & open_mode::rw_mask)] | open_mode + , permissions); + +#ifdef O_NOATIME + // O_NOATIME is not allowed for files we don't own + // so, if we get EPERM when we try to open with it + // try again without O_NOATIME + if (handle == -1 && (mode & open_mode::no_atime) && errno == EPERM) + { + mode &= ~open_mode::no_atime; + open_mode &= ~O_NOATIME; + handle = ::open(file_path.c_str() + , mode_array[static_cast(mode & open_mode::rw_mask)] | open_mode + , permissions); + } +#endif + if (handle == -1) + { + ec.assign(errno, system_category()); + TORRENT_ASSERT(ec); + return false; + } + + m_file_handle = handle; + +#ifdef DIRECTIO_ON + // for solaris + if ((mode & open_mode::no_cache)) + { + int yes = 1; + directio(native_handle(), DIRECTIO_ON); + } +#endif + +#ifdef F_NOCACHE + // for BSD/Mac + if ((mode & open_mode::no_cache)) + { + int yes = 1; + ::fcntl(native_handle(), F_NOCACHE, &yes); + +#ifdef F_NODIRECT + // it's OK to temporarily cache written pages + ::fcntl(native_handle(), F_NODIRECT, &yes); +#endif + } +#endif + +#ifdef POSIX_FADV_RANDOM + if ((mode & open_mode::random_access)) + { + // disable read-ahead + // NOTE: in android this function was introduced in API 21, + // but the constant POSIX_FADV_RANDOM is there for lower + // API levels, just don't add :: to allow a macro workaround + posix_fadvise(native_handle(), 0, 0, POSIX_FADV_RANDOM); + } +#endif + +#endif + m_open_mode = mode; + + TORRENT_ASSERT(is_open()); + return true; + } + + bool file::is_open() const + { + return m_file_handle != INVALID_HANDLE_VALUE; + } + +#ifdef TORRENT_WINDOWS + // returns true if the given file has any regions that are + // sparse, i.e. not allocated. + bool is_sparse(HANDLE file) + { + LARGE_INTEGER file_size; + if (!GetFileSizeEx(file, &file_size)) + return false; + + overlapped_t ol; + if (ol.ol.hEvent == nullptr) return false; + +#ifndef FSCTL_QUERY_ALLOCATED_RANGES +typedef struct _FILE_ALLOCATED_RANGE_BUFFER { + LARGE_INTEGER FileOffset; + LARGE_INTEGER Length; +} FILE_ALLOCATED_RANGE_BUFFER; +#define FSCTL_QUERY_ALLOCATED_RANGES ((0x9 << 16) | (1 << 14) | (51 << 2) | 3) +#endif + FILE_ALLOCATED_RANGE_BUFFER in; + in.FileOffset.QuadPart = 0; + in.Length.QuadPart = file_size.QuadPart; + + FILE_ALLOCATED_RANGE_BUFFER out[2]; + + DWORD returned_bytes = 0; + BOOL ret = DeviceIoControl(file, FSCTL_QUERY_ALLOCATED_RANGES, (void*)&in, sizeof(in) + , out, sizeof(out), &returned_bytes, &ol.ol); + + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + { + error_code ec; + returned_bytes = ol.wait(file, ec); + if (ec) return true; + } + else if (ret == FALSE) + { + return true; + } + + // if we have more than one range in the file, we're sparse + if (returned_bytes != sizeof(FILE_ALLOCATED_RANGE_BUFFER)) { + return true; + } + + return (in.Length.QuadPart != out[0].Length.QuadPart); + } +#endif + + void file::close() + { + if (!is_open()) return; + +#ifdef TORRENT_WINDOWS + + // if this file is open for writing, has the sparse + // flag set, but there are no sparse regions, unset + // the flag + open_mode_t const rw_mode = m_open_mode & open_mode::rw_mask; + if ((rw_mode != open_mode::read_only) + && (m_open_mode & open_mode::sparse) + && !is_sparse(native_handle())) + { + overlapped_t ol; + // according to MSDN, clearing the sparse flag of a file only + // works on windows vista and later +#ifdef TORRENT_MINGW + typedef struct _FILE_SET_SPARSE_BUFFER { + BOOLEAN SetSparse; + } FILE_SET_SPARSE_BUFFER; +#endif + DWORD temp; + FILE_SET_SPARSE_BUFFER b; + b.SetSparse = FALSE; + BOOL ret = ::DeviceIoControl(native_handle(), FSCTL_SET_SPARSE, &b, sizeof(b) + , 0, 0, &temp, &ol.ol); + error_code ec; + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + { + ol.wait(native_handle(), ec); + } + } + + CloseHandle(native_handle()); +#else + if (m_file_handle != INVALID_HANDLE_VALUE) + ::close(m_file_handle); +#endif + + m_file_handle = INVALID_HANDLE_VALUE; + + m_open_mode = open_mode_t{}; + } + + namespace { + + void gather_copy(span bufs, char* dst) + { + std::ptrdiff_t offset = 0; + for (auto buf : bufs) + { + std::copy(buf.begin(), buf.end(), dst + offset); + offset += buf.size(); + } + } + + void scatter_copy(span bufs, char const* src) + { + std::ptrdiff_t offset = 0; + for (auto buf : bufs) + { + std::copy(src + offset, src + offset + buf.size(), buf.data()); + offset += buf.size(); + } + } + + bool coalesce_read_buffers(span& bufs + , iovec_t& tmp) + { + auto const buf_size = bufs_size(bufs); + auto buf = new char[std::size_t(buf_size)]; + tmp = { buf, buf_size }; + bufs = span(tmp); + return true; + } + + void coalesce_read_buffers_end(span bufs + , char* const buf, bool const copy) + { + if (copy) scatter_copy(bufs, buf); + delete[] buf; + } + + bool coalesce_write_buffers(span& bufs + , iovec_t& tmp) + { + auto const buf_size = bufs_size(bufs); + auto buf = new char[std::size_t(buf_size)]; + gather_copy(bufs, buf); + tmp = { buf, buf_size }; + bufs = span(tmp); + return true; + } + +#if TORRENT_USE_PREADV +namespace { + int bufs_size(span<::iovec> bufs) + { + std::size_t size = 0; + for (auto buf : bufs) size += buf.iov_len; + return int(size); + } +} +#endif // TORRENT_USE_PREADV + + template + std::int64_t iov(Fun f, handle_type fd, std::int64_t file_offset + , span bufs, error_code& ec) + { +#if TORRENT_USE_PREADV + + TORRENT_ALLOCA(vec, ::iovec, bufs.size()); + auto it = vec.begin(); + for (auto const& b : bufs) + { + it->iov_base = b.data(); + it->iov_len = std::size_t(b.size()); + ++it; + } + + std::int64_t ret = 0; + while (!vec.empty()) + { +#ifdef IOV_MAX + auto const nbufs = vec.first(std::min(int(vec.size()), IOV_MAX)); +#else + auto const nbufs = vec; +#endif + + std::int64_t tmp_ret = 0; + tmp_ret = f(fd, nbufs.data(), int(nbufs.size()), file_offset); + if (tmp_ret < 0) + { +#ifdef TORRENT_WINDOWS + ec.assign(GetLastError(), system_category()); +#else + ec.assign(errno, system_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + + // we got a short read/write. It's either 0, and we're at EOF, or we + // just need to issue the read/write operation again. In either case, + // punt that to the upper layer, as reissuing the operations is + // complicated here + int const expected_len = bufs_size(nbufs); + if (tmp_ret < expected_len) break; + + vec = vec.subspan(nbufs.size()); + } + return ret; + +#elif TORRENT_USE_PREAD + + std::int64_t ret = 0; + for (auto i : bufs) + { + std::int64_t const tmp_ret = f(fd, i.data() + , static_cast(i.size()), file_offset); + if (tmp_ret < 0) + { +#ifdef TORRENT_WINDOWS + ec.assign(GetLastError(), system_category()); +#else + ec.assign(errno, system_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + if (tmp_ret < int(i.size())) break; + } + + return ret; + +#else // not PREADV nor PREAD + + int ret = 0; + +#ifdef TORRENT_WINDOWS + if (SetFilePointerEx(fd, offs, &offs, FILE_BEGIN) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return -1; + } +#else + if (lseek(fd, file_offset, SEEK_SET) < 0) + { + ec.assign(errno, system_category()); + return -1; + } +#endif + + for (auto i : bufs) + { + int tmp_ret = f(fd, i.data(), static_cast(i.size())); + if (tmp_ret < 0) + { +#ifdef TORRENT_WINDOWS + ec.assign(GetLastError(), system_category()); +#else + ec.assign(errno, system_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + if (tmp_ret < int(i.size())) break; + } + + return ret; + +#endif // USE_PREADV + } + + } // anonymous namespace + + // this has to be thread safe and atomic. i.e. on posix systems it has to be + // turned into a series of pread() calls + std::int64_t file::readv(std::int64_t file_offset, span bufs + , error_code& ec, open_mode_t flags) + { + if (m_file_handle == INVALID_HANDLE_VALUE) + { +#ifdef TORRENT_WINDOWS + ec = error_code(ERROR_INVALID_HANDLE, system_category()); +#else + ec = error_code(boost::system::errc::bad_file_descriptor, generic_category()); +#endif + return -1; + } + TORRENT_ASSERT((m_open_mode & open_mode::rw_mask) == open_mode::read_only + || (m_open_mode & open_mode::rw_mask) == open_mode::read_write); + TORRENT_ASSERT(!bufs.empty()); + TORRENT_ASSERT(is_open()); + + // there's no point in coalescing single buffer writes + if (bufs.size() == 1) + { + flags &= ~open_mode::coalesce_buffers; + } + + iovec_t tmp; + span tmp_bufs = bufs; + if (flags & open_mode::coalesce_buffers) + { + if (!coalesce_read_buffers(tmp_bufs, tmp)) + // ok, that failed, don't coalesce this read + flags &= ~open_mode::coalesce_buffers; + } + +#if TORRENT_USE_PREADV + std::int64_t ret = iov(&::preadv, native_handle(), file_offset, tmp_bufs, ec); +#elif TORRENT_USE_PREAD + std::int64_t ret = iov(&::pread, native_handle(), file_offset, tmp_bufs, ec); +#else + std::int64_t ret = iov(&::read, native_handle(), file_offset, tmp_bufs, ec); +#endif + + if (flags & open_mode::coalesce_buffers) + coalesce_read_buffers_end(bufs + , tmp.data(), !ec); + + return ret; + } + + // This has to be thread safe, i.e. atomic. + // that means, on posix this has to be turned into a series of + // pwrite() calls + std::int64_t file::writev(std::int64_t file_offset, span bufs + , error_code& ec, open_mode_t flags) + { + if (m_file_handle == INVALID_HANDLE_VALUE) + { +#ifdef TORRENT_WINDOWS + ec = error_code(ERROR_INVALID_HANDLE, system_category()); +#else + ec = error_code(boost::system::errc::bad_file_descriptor, generic_category()); +#endif + return -1; + } + TORRENT_ASSERT((m_open_mode & open_mode::rw_mask) == open_mode::write_only + || (m_open_mode & open_mode::rw_mask) == open_mode::read_write); + TORRENT_ASSERT(!bufs.empty()); + TORRENT_ASSERT(is_open()); + + ec.clear(); + + // there's no point in coalescing single buffer writes + if (bufs.size() == 1) + { + flags &= ~open_mode::coalesce_buffers; + } + + iovec_t tmp; + if (flags & open_mode::coalesce_buffers) + { + if (!coalesce_write_buffers(bufs, tmp)) + // ok, that failed, don't coalesce writes + flags &= ~open_mode::coalesce_buffers; + } + +#if TORRENT_USE_PREADV + std::int64_t ret = iov(&::pwritev, native_handle(), file_offset, bufs, ec); +#elif TORRENT_USE_PREAD + std::int64_t ret = iov(&::pwrite, native_handle(), file_offset, bufs, ec); +#else + std::int64_t ret = iov(&::write, native_handle(), file_offset, bufs, ec); +#endif + + if (flags & open_mode::coalesce_buffers) + delete[] tmp.data(); + +#if TORRENT_USE_FDATASYNC \ + && !defined F_NOCACHE && \ + !defined DIRECTIO_ON + if (m_open_mode & open_mode::no_cache) + { + if (::fdatasync(native_handle()) != 0 + && errno != EINVAL + && errno != ENOSYS) + { + ec.assign(errno, system_category()); + } + } +#endif + return ret; + } + +#ifdef TORRENT_WINDOWS + void acquire_manage_volume_privs() + { + using OpenProcessToken_t = BOOL (WINAPI*)(HANDLE, DWORD, PHANDLE); + + using LookupPrivilegeValue_t = BOOL (WINAPI*)(LPCSTR, LPCSTR, PLUID); + + using AdjustTokenPrivileges_t = BOOL (WINAPI*)( + HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); + + auto OpenProcessToken = + aux::get_library_procedure("OpenProcessToken"); + auto LookupPrivilegeValue = + aux::get_library_procedure("LookupPrivilegeValueA"); + auto AdjustTokenPrivileges = + aux::get_library_procedure("AdjustTokenPrivileges"); + + if (OpenProcessToken == nullptr + || LookupPrivilegeValue == nullptr + || AdjustTokenPrivileges == nullptr) + { + return; + } + + + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess() + , TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + return; + + BOOST_SCOPE_EXIT_ALL(&token) { + CloseHandle(token); + }; + + TOKEN_PRIVILEGES privs{}; + if (!LookupPrivilegeValue(nullptr, "SeManageVolumePrivilege" + , &privs.Privileges[0].Luid)) + { + return; + } + + privs.PrivilegeCount = 1; + privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + AdjustTokenPrivileges(token, FALSE, &privs, 0, nullptr, nullptr); + } + + void set_file_valid_data(HANDLE f, std::int64_t size) + { + using SetFileValidData_t = BOOL (WINAPI*)(HANDLE, LONGLONG); + auto SetFileValidData = + aux::get_library_procedure("SetFileValidData"); + + if (SetFileValidData) + { + // we don't necessarily expect to have enough + // privilege to do this, so ignore errors. + SetFileValidData(f, size); + } + } +#endif + + bool file::set_size(std::int64_t s, error_code& ec) + { + TORRENT_ASSERT(is_open()); + TORRENT_ASSERT(s >= 0); + +#ifdef TORRENT_WINDOWS + + LARGE_INTEGER offs; + LARGE_INTEGER cur_size; + if (GetFileSizeEx(native_handle(), &cur_size) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return false; + } + offs.QuadPart = s; + // only set the file size if it's not already at + // the right size. We don't want to update the + // modification time if we don't have to + if (cur_size.QuadPart != s) + { + if (SetFilePointerEx(native_handle(), offs, &offs, FILE_BEGIN) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return false; + } + if (::SetEndOfFile(native_handle()) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return false; + } + if (!(m_open_mode & open_mode::sparse)) + { + // if the user has permissions, avoid filling + // the file with zeroes, but just fill it with + // garbage instead + set_file_valid_data(m_file_handle, s); + } + } +#else // NON-WINDOWS + struct stat st{}; + if (::fstat(native_handle(), &st) != 0) + { + ec.assign(errno, system_category()); + return false; + } + + // only truncate the file if it doesn't already + // have the right size. We don't want to update + if (st.st_size != s && ::ftruncate(native_handle(), s) < 0) + { + ec.assign(errno, system_category()); + return false; + } + + // if we're not in sparse mode, allocate the storage + // but only if the number of allocated blocks for the file + // is less than the file size. Otherwise we would just + // update the modification time of the file for no good + // reason. + if (!(m_open_mode & open_mode::sparse) + && std::int64_t(st.st_blocks) < (s + st.st_blksize - 1) / st.st_blksize) + { + // How do we know that the file is already allocated? + // if we always try to allocate the space, we'll update + // the modification time without actually changing the file + // but if we don't do anything if the file size is +#ifdef F_PREALLOCATE + fstore_t f = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, s, 0}; + if (fcntl(native_handle(), F_PREALLOCATE, &f) < 0) + { + // MacOS returns EINVAL if the file already has the space + // pre-allocated. In which case we can just move on. + if (errno != EINVAL) + { + if (errno != ENOSPC) + { + ec.assign(errno, system_category()); + return false; + } + // ok, let's try to allocate non contiguous space then + f.fst_flags = F_ALLOCATEALL; + if (fcntl(native_handle(), F_PREALLOCATE, &f) < 0) + { + ec.assign(errno, system_category()); + return false; + } + } + } +#endif // F_PREALLOCATE + +#ifdef F_ALLOCSP64 + flock64 fl64; + fl64.l_whence = SEEK_SET; + fl64.l_start = 0; + fl64.l_len = s; + if (fcntl(native_handle(), F_ALLOCSP64, &fl64) < 0) + { + ec.assign(errno, system_category()); + return false; + } + +#endif // F_ALLOCSP64 + +#if TORRENT_HAS_FALLOCATE + // if fallocate failed, we have to use posix_fallocate + // which can be painfully slow + // if you get a compile error here, you might want to + // define TORRENT_HAS_FALLOCATE to 0. + int const ret = posix_fallocate(native_handle(), 0, s); + // posix_allocate fails with EINVAL in case the underlying + // filesystem does not support this operation + if (ret != 0 && ret != EINVAL && ret != ENOTSUP) + { + ec.assign(ret, system_category()); + return false; + } +#endif // TORRENT_HAS_FALLOCATE + } +#endif // TORRENT_WINDOWS + return true; + } + + std::int64_t file::get_size(error_code& ec) const + { +#ifdef TORRENT_WINDOWS + LARGE_INTEGER file_size; + if (!GetFileSizeEx(native_handle(), &file_size)) + { + ec.assign(GetLastError(), system_category()); + return -1; + } + return file_size.QuadPart; +#else + struct stat fs = {}; + if (::fstat(native_handle(), &fs) != 0) + { + ec.assign(errno, system_category()); + return -1; + } + return fs.st_size; +#endif + } +} diff --git a/src/file_pool.cpp b/src/file_pool.cpp new file mode 100644 index 0000000..a66568a --- /dev/null +++ b/src/file_pool.cpp @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include "libtorrent/assert.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/aux_/path.hpp" +#ifdef TORRENT_WINDOWS +#include "libtorrent/aux_/win_util.hpp" +#endif + +#include + +namespace libtorrent { + + file_pool::file_pool(int size) : m_size(size) {} + file_pool::~file_pool() = default; + +#ifdef TORRENT_WINDOWS + void set_low_priority(file_handle const& f) + { + // file prio is only supported on vista and up + // so load the functions dynamically + enum FILE_INFO_BY_HANDLE_CLASS_LOCAL { + FileBasicInfo, + FileStandardInfo, + FileNameInfo, + FileRenameInfo, + FileDispositionInfo, + FileAllocationInfo, + FileEndOfFileInfo, + FileStreamInfo, + FileCompressionInfo, + FileAttributeTagInfo, + FileIdBothDirectoryInfo, + FileIdBothDirectoryRestartInfo, + FileIoPriorityHintInfo, + FileRemoteProtocolInfo, + MaximumFileInfoByHandleClass + }; + + enum PRIORITY_HINT_LOCAL { + IoPriorityHintVeryLow = 0, + IoPriorityHintLow, + IoPriorityHintNormal, + MaximumIoPriorityHintType + }; + + struct FILE_IO_PRIORITY_HINT_INFO_LOCAL { + PRIORITY_HINT_LOCAL PriorityHint; + }; + + using SetFileInformationByHandle_t = BOOL (WINAPI *)(HANDLE + , FILE_INFO_BY_HANDLE_CLASS_LOCAL, LPVOID, DWORD); + auto SetFileInformationByHandle = + aux::get_library_procedure( + "SetFileInformationByHandle"); + + if (SetFileInformationByHandle == nullptr) return; + + FILE_IO_PRIORITY_HINT_INFO_LOCAL io_hint; + io_hint.PriorityHint = IoPriorityHintLow; + SetFileInformationByHandle(f->native_handle(), + FileIoPriorityHintInfo, &io_hint, sizeof(io_hint)); + } +#endif // TORRENT_WINDOWS + + file_handle file_pool::open_file(storage_index_t st, std::string const& p + , file_index_t const file_index, file_storage const& fs + , open_mode_t const m, error_code& ec) + { + // potentially used to hold a reference to a file object that's + // about to be destructed. If we have such object we assign it to + // this member to be destructed after we release the std::mutex. On some + // operating systems (such as OSX) closing a file may take a long + // time. We don't want to hold the std::mutex for that. + file_handle defer_destruction; + + std::unique_lock l(m_mutex); + + TORRENT_ASSERT(is_complete(p)); + TORRENT_ASSERT((m & open_mode::rw_mask) == open_mode::read_only + || (m & open_mode::rw_mask) == open_mode::read_write); + auto const i = m_files.find(std::make_pair(st, file_index)); + if (i != m_files.end()) + { + lru_file_entry& e = i->second; + e.last_use = aux::time_now(); + + // if we asked for a file in write mode, + // and the cached file is is not opened in + // write mode, re-open it + if ((((e.mode & open_mode::rw_mask) != open_mode::read_write) + && ((m & open_mode::rw_mask) == open_mode::read_write)) + || (e.mode & open_mode::random_access) != (m & open_mode::random_access)) + { + file_handle new_file = std::make_shared(); + + std::string full_path = fs.file_path(file_index, p); + if (!new_file->open(full_path, m, ec)) + return file_handle(); +#ifdef TORRENT_WINDOWS + if (m_low_prio_io) + set_low_priority(new_file); +#endif + + TORRENT_ASSERT(new_file->is_open()); + defer_destruction = std::move(e.file_ptr); + e.file_ptr = std::move(new_file); + e.mode = m; + } + return e.file_ptr; + } + + lru_file_entry e; + e.file_ptr = std::make_shared(); + if (!e.file_ptr) + { + ec = error_code(boost::system::errc::not_enough_memory, generic_category()); + return file_handle(); + } + std::string full_path = fs.file_path(file_index, p); + if (!e.file_ptr->open(full_path, m, ec)) + return file_handle(); +#ifdef TORRENT_WINDOWS + if (m_low_prio_io) + set_low_priority(e.file_ptr); +#endif + e.mode = m; + file_handle file_ptr = e.file_ptr; + m_files.insert(std::make_pair(std::make_pair(st, file_index), e)); + TORRENT_ASSERT(file_ptr->is_open()); + + if (int(m_files.size()) >= m_size) + { + // the file cache is at its maximum size, close + // the least recently used (lru) file from it + defer_destruction = remove_oldest(l); + } + return file_ptr; + } + + namespace { + + file_open_mode_t to_file_open_mode(open_mode_t const mode) + { + file_open_mode_t ret; + open_mode_t const rw_mode = mode & open_mode::rw_mask; + + ret = (rw_mode == open_mode::read_only) + ? file_open_mode::read_only + : (rw_mode == open_mode::write_only) + ? file_open_mode::write_only + : (rw_mode == open_mode::read_write) + ? file_open_mode::read_write + : file_open_mode_t{}; + + if (mode & open_mode::sparse) ret |= file_open_mode::sparse; + if (mode & open_mode::no_atime) ret |= file_open_mode::no_atime; + if (mode & open_mode::random_access) ret |= file_open_mode::random_access; + return ret; + } + + } + + std::vector file_pool::get_status(storage_index_t const st) const + { + std::vector ret; + { + std::unique_lock l(m_mutex); + + auto const start = m_files.lower_bound(std::make_pair(st, file_index_t(0))); + auto const end = m_files.upper_bound(std::make_pair(st + , std::numeric_limits::max())); + + for (auto i = start; i != end; ++i) + { + ret.push_back({i->first.second, to_file_open_mode(i->second.mode) + , i->second.last_use}); + } + } + return ret; + } + + file_handle file_pool::remove_oldest(std::unique_lock&) + { + using value_type = decltype(m_files)::value_type; + auto const i = std::min_element(m_files.begin(), m_files.end() + , [] (value_type const& lhs, value_type const& rhs) + { return lhs.second.last_use < rhs.second.last_use; }); + if (i == m_files.end()) return file_handle(); + + file_handle file_ptr = i->second.file_ptr; + m_files.erase(i); + + // closing a file may be long running operation (mac os x) + // let the calling function destruct it after releasing the mutex + return file_ptr; + } + + void file_pool::release(storage_index_t const st, file_index_t file_index) + { + std::unique_lock l(m_mutex); + + auto const i = m_files.find(std::make_pair(st, file_index)); + if (i == m_files.end()) return; + + file_handle file_ptr = i->second.file_ptr; + m_files.erase(i); + + // closing a file may take a long time (mac os x), so make sure + // we're not holding the mutex + l.unlock(); + file_ptr.reset(); + } + + // closes files belonging to the specified + // storage, or all if none is specified. + void file_pool::release() + { + std::unique_lock l(m_mutex); + m_files.clear(); + l.unlock(); + } + + void file_pool::release(storage_index_t const st) + { + std::unique_lock l(m_mutex); + + auto const begin = m_files.lower_bound(std::make_pair(st, file_index_t(0))); + auto const end = m_files.upper_bound(std::make_pair(st + , std::numeric_limits::max())); + + std::vector to_close; + for (auto it = begin; it != end; ++it) + to_close.push_back(std::move(it->second.file_ptr)); + if (!to_close.empty()) m_files.erase(begin, end); + l.unlock(); + // the files are closed here while the lock is not held + } + + void file_pool::resize(int size) + { + // these are destructed _after_ the mutex is released + std::vector defer_destruction; + + std::unique_lock l(m_mutex); + + TORRENT_ASSERT(size > 0); + + if (size == m_size) return; + m_size = size; + if (int(m_files.size()) <= m_size) return; + + // close the least recently used files + while (int(m_files.size()) > m_size) + defer_destruction.push_back(remove_oldest(l)); + } + + void file_pool::close_oldest() + { + std::unique_lock l(m_mutex); + + using value_type = decltype(m_files)::value_type; + auto const i = std::min_element(m_files.begin(), m_files.end() + , [] (value_type const& lhs, value_type const& rhs) + { return lhs.second.opened < rhs.second.opened; }); + if (i == m_files.end()) return; + + file_handle file_ptr = i->second.file_ptr; + m_files.erase(i); + + // closing a file may be long running operation (mac os x) + l.unlock(); + file_ptr.reset(); + l.lock(); + } +} diff --git a/src/file_progress.cpp b/src/file_progress.cpp new file mode 100644 index 0000000..ef879e2 --- /dev/null +++ b/src/file_progress.cpp @@ -0,0 +1,184 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/invariant_check.hpp" + +namespace libtorrent { namespace aux { + + void file_progress::init(piece_picker const& picker, file_storage const& fs) + { + INVARIANT_CHECK; + + if (!m_file_progress.empty()) return; + + int const num_files = fs.num_files(); + +#if TORRENT_USE_INVARIANT_CHECKS + int const num_pieces = fs.num_pieces(); + m_have_pieces.clear(); + m_have_pieces.resize(num_pieces, false); + m_file_sizes.clear(); + m_file_sizes.reserve(num_files); + for (file_index_t i(0); i < fs.end_file(); ++i) + m_file_sizes.push_back(fs.file_size(i)); +#endif + + m_file_progress.resize(num_files, 0); + std::fill(m_file_progress.begin(), m_file_progress.end(), 0); + + // initialize the progress of each file + + int const piece_size = fs.piece_length(); + std::int64_t off = 0; + std::int64_t const total_size = fs.total_size(); + file_index_t file_index(0); + for (piece_index_t piece(0); piece < fs.end_piece(); ++piece, off += piece_size) + { + TORRENT_ASSERT(file_index < fs.end_file()); + std::int64_t file_offset = off - fs.file_offset(file_index); + TORRENT_ASSERT(file_offset >= 0); + while (file_offset >= fs.file_size(file_index)) + { + ++file_index; + TORRENT_ASSERT(file_index < fs.end_file()); + file_offset = off - fs.file_offset(file_index); + TORRENT_ASSERT(file_offset >= 0); + } + TORRENT_ASSERT(file_offset <= fs.file_size(file_index)); + + if (!picker.have_piece(piece)) continue; + +#if TORRENT_USE_INVARIANT_CHECKS + m_have_pieces.set_bit(piece); +#endif + + TORRENT_ASSERT(total_size >= off); + std::int64_t size = std::min(std::int64_t(piece_size), total_size - off); + TORRENT_ASSERT(size >= 0); + + while (size) + { + std::int64_t const add = std::min(size, fs.file_size(file_index) - file_offset); + TORRENT_ASSERT(add >= 0); + m_file_progress[file_index] += add; + + TORRENT_ASSERT(m_file_progress[file_index] + <= fs.file_size(file_index)); + + size -= add; + TORRENT_ASSERT(size >= 0); + if (size > 0) + { + ++file_index; + TORRENT_ASSERT(file_index < fs.end_file()); + file_offset = 0; + } + } + } + } + + void file_progress::export_progress(vector& fp) + { + INVARIANT_CHECK; + fp.resize(m_file_progress.size(), 0); + std::copy(m_file_progress.begin(), m_file_progress.end(), fp.begin()); + } + + void file_progress::clear() + { + INVARIANT_CHECK; + m_file_progress.clear(); + m_file_progress.shrink_to_fit(); +#if TORRENT_USE_INVARIANT_CHECKS + m_have_pieces.clear(); +#endif + } + + // update the file progress now that we just completed downloading piece + // 'index' + void file_progress::update(file_storage const& fs, piece_index_t const index + , std::function const& completed_cb) + { + INVARIANT_CHECK; + if (m_file_progress.empty()) return; + +#if TORRENT_USE_INVARIANT_CHECKS + // if this assert fires, we've told the file_progress object that we have + // a piece twice. That violates its precondition and will cause incorrect + // accounting + TORRENT_ASSERT(m_have_pieces.get_bit(index) == false); + m_have_pieces.set_bit(index); +#endif + + int const piece_size = fs.piece_length(); + std::int64_t off = std::int64_t(static_cast(index)) * piece_size; + file_index_t file_index = fs.file_index_at_offset(off); + std::int64_t size = fs.piece_size(index); + for (; size > 0; ++file_index) + { + std::int64_t const file_offset = off - fs.file_offset(file_index); + TORRENT_ASSERT(file_index != fs.end_file()); + TORRENT_ASSERT(file_offset <= fs.file_size(file_index)); + std::int64_t const add = std::min(fs.file_size(file_index) + - file_offset, size); + m_file_progress[file_index] += add; + + TORRENT_ASSERT(m_file_progress[file_index] + <= fs.file_size(file_index)); + + if (m_file_progress[file_index] >= fs.file_size(file_index) && completed_cb) + { + if (!fs.pad_file_at(file_index)) + completed_cb(file_index); + } + size -= add; + off += add; + TORRENT_ASSERT(size >= 0); + } + } + +#if TORRENT_USE_INVARIANT_CHECKS + void file_progress::check_invariant() const + { + if (m_file_progress.empty()) return; + + file_index_t index(0); + for (std::int64_t progress : m_file_progress) + { + TORRENT_ASSERT(progress <= m_file_sizes[index++]); + } + } +#endif +} } diff --git a/src/file_storage.cpp b/src/file_storage.cpp new file mode 100644 index 0000000..7afb60c --- /dev/null +++ b/src/file_storage.cpp @@ -0,0 +1,1357 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file_storage.hpp" +#include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/utf8.hpp" +#include "libtorrent/index_range.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include +#include + +#if TORRENT_ABI_VERSION == 1 && defined TORRENT_WINDOWS +#include "libtorrent/aux_/escape_string.hpp" +#endif + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR '\\' +#else +#define TORRENT_SEPARATOR '/' +#endif + +using namespace std::placeholders; + +namespace libtorrent { + + constexpr file_flags_t file_storage::flag_pad_file; + constexpr file_flags_t file_storage::flag_hidden; + constexpr file_flags_t file_storage::flag_executable; + constexpr file_flags_t file_storage::flag_symlink; + +#if TORRENT_ABI_VERSION == 1 + constexpr file_flags_t file_storage::pad_file; + constexpr file_flags_t file_storage::attribute_hidden; + constexpr file_flags_t file_storage::attribute_executable; + constexpr file_flags_t file_storage::attribute_symlink; +#endif + + file_storage::file_storage() + : m_piece_length(0) + , m_num_pieces(0) + , m_total_size(0) + {} + + file_storage::~file_storage() = default; + + // even though this copy constructor and the copy assignment + // operator are identical to what the compiler would have + // generated, they are put here to explicitly make them part + // of libtorrent and properly exported by the .dll. + file_storage::file_storage(file_storage const&) = default; + file_storage& file_storage::operator=(file_storage const&) = default; + file_storage::file_storage(file_storage&&) noexcept = default; + + void file_storage::reserve(int num_files) + { + m_files.reserve(num_files); + } + + int file_storage::piece_size(piece_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= piece_index_t(0) && index < end_piece()); + if (index == last_piece()) + { + std::int64_t const size_except_last + = (num_pieces() - 1) * std::int64_t(piece_length()); + std::int64_t const size = total_size() - size_except_last; + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(size <= piece_length()); + return int(size); + } + else + return piece_length(); + } + +namespace { + + bool compare_file_offset(internal_file_entry const& lhs + , internal_file_entry const& rhs) + { + return lhs.offset < rhs.offset; + } + +} + + // path is supposed to include the name of the torrent itself. + // or an absolute path, to move a file outside of the download directory + void file_storage::update_path_index(internal_file_entry& e + , std::string const& path, bool const set_name) + { + if (is_complete(path)) + { + TORRENT_ASSERT(set_name); + e.set_name(path); + e.path_index = -2; + return; + } + + TORRENT_ASSERT(path[0] != '/'); + + // split the string into the leaf filename + // and the branch path + string_view leaf; + string_view branch_path; + std::tie(branch_path, leaf) = rsplit_path(path); + + if (branch_path.empty()) + { + if (set_name) e.set_name(leaf); + e.path_index = -1; + return; + } + + // if the path *does* contain the name of the torrent (as we expect) + // strip it before adding it to m_paths + if (lsplit_path(branch_path).first == m_name) + { + branch_path = lsplit_path(branch_path).second; + // strip duplicate separators + while (!branch_path.empty() && (branch_path.front() == TORRENT_SEPARATOR +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || branch_path.front() == '/' +#endif + )) + branch_path.remove_prefix(1); + e.no_root_dir = false; + } + else + { + e.no_root_dir = true; + } + + e.path_index = get_or_add_path(branch_path); + if (set_name) e.set_name(leaf); + } + + int file_storage::get_or_add_path(string_view const path) + { + // do we already have this path in the path list? + auto const p = std::find(m_paths.rbegin(), m_paths.rend(), path); + + if (p == m_paths.rend()) + { + // no, we don't. add it + int const ret = int(m_paths.size()); + TORRENT_ASSERT(path.size() == 0 || path[0] != '/'); + m_paths.emplace_back(path.data(), path.size()); + return ret; + } + else + { + // yes we do. use it + return int(p.base() - m_paths.begin() - 1); + } + } + +#if TORRENT_ABI_VERSION == 1 + file_entry::file_entry(): offset(0), size(0) + , mtime(0), pad_file(false), hidden_attribute(false) + , executable_attribute(false) + , symlink_attribute(false) + {} + + file_entry::~file_entry() = default; +#endif // TORRENT_ABI_VERSION + + internal_file_entry::internal_file_entry() + : offset(0) + , symlink_index(not_a_symlink) + , no_root_dir(false) + , size(0) + , name_len(name_is_owned) + , pad_file(false) + , hidden_attribute(false) + , executable_attribute(false) + , symlink_attribute(false) + , name(nullptr) + , path_index(-1) + {} + + internal_file_entry::~internal_file_entry() + { + if (name_len == name_is_owned) delete[] name; + } + + internal_file_entry::internal_file_entry(internal_file_entry const& fe) + : offset(fe.offset) + , symlink_index(fe.symlink_index) + , no_root_dir(fe.no_root_dir) + , size(fe.size) + , name_len(fe.name_len) + , pad_file(fe.pad_file) + , hidden_attribute(fe.hidden_attribute) + , executable_attribute(fe.executable_attribute) + , symlink_attribute(fe.symlink_attribute) + , name(nullptr) + , path_index(fe.path_index) + { + bool const borrow = fe.name_len != name_is_owned; + set_name(fe.filename(), borrow); + } + + internal_file_entry& internal_file_entry::operator=(internal_file_entry const& fe) & + { + if (&fe == this) return *this; + offset = fe.offset; + size = fe.size; + path_index = fe.path_index; + symlink_index = fe.symlink_index; + pad_file = fe.pad_file; + hidden_attribute = fe.hidden_attribute; + executable_attribute = fe.executable_attribute; + symlink_attribute = fe.symlink_attribute; + no_root_dir = fe.no_root_dir; + // if the name is not owned, don't allocate memory, we can point into the + // same metadata buffer + bool const borrow = fe.name_len != name_is_owned; + set_name(fe.filename(), borrow); + return *this; + } + + internal_file_entry::internal_file_entry(internal_file_entry&& fe) noexcept + : offset(fe.offset) + , symlink_index(fe.symlink_index) + , no_root_dir(fe.no_root_dir) + , size(fe.size) + , name_len(fe.name_len) + , pad_file(fe.pad_file) + , hidden_attribute(fe.hidden_attribute) + , executable_attribute(fe.executable_attribute) + , symlink_attribute(fe.symlink_attribute) + , name(fe.name) + , path_index(fe.path_index) + { + fe.name_len = 0; + fe.name = nullptr; + } + + internal_file_entry& internal_file_entry::operator=(internal_file_entry&& fe) & noexcept + { + if (&fe == this) return *this; + offset = fe.offset; + size = fe.size; + path_index = fe.path_index; + symlink_index = fe.symlink_index; + pad_file = fe.pad_file; + hidden_attribute = fe.hidden_attribute; + executable_attribute = fe.executable_attribute; + symlink_attribute = fe.symlink_attribute; + no_root_dir = fe.no_root_dir; + name = fe.name; + name_len = fe.name_len; + + fe.name_len = 0; + fe.name = nullptr; + return *this; + } + + // if borrow_string is true, don't take ownership over n, just + // point to it. + // if borrow_string is false, n will be copied and owned by the + // internal_file_entry. + void internal_file_entry::set_name(string_view n, bool const borrow_string) + { + // free the current string, before assigning the new one + if (name_len == name_is_owned) delete[] name; + if (n.empty()) + { + TORRENT_ASSERT(borrow_string == false); + name = nullptr; + } + else if (borrow_string) + { + // we have limited space in the length field. truncate string + // if it's too long + if (n.size() >= name_is_owned) n = n.substr(name_is_owned - 1); + + name = n.data(); + name_len = aux::numeric_cast(n.size()); + } + else + { + name = allocate_string_copy(n); + name_len = name_is_owned; + } + } + + string_view internal_file_entry::filename() const + { + if (name_len != name_is_owned) return {name, std::size_t(name_len)}; + return name ? string_view(name) : string_view(); + } + + void file_storage::apply_pointer_offset(std::ptrdiff_t const off) + { + for (auto& f : m_files) + { + if (f.name_len == internal_file_entry::name_is_owned) continue; + f.name += off; + } + + for (auto& h : m_file_hashes) + { + if (h == nullptr) continue; + h += off; + } + } + +#if TORRENT_ABI_VERSION == 1 + + void file_storage::add_file_borrow(char const* filename, int filename_len + , std::string const& path, std::int64_t file_size, file_flags_t file_flags + , char const* filehash, std::int64_t mtime, string_view symlink_path) + { + TORRENT_ASSERT(filename_len >= 0); + add_file_borrow({filename, std::size_t(filename_len)}, path, file_size + , file_flags, filehash, mtime, symlink_path); + } + + void file_storage::add_file(file_entry const& fe, char const* filehash) + { + file_flags_t flags = {}; + if (fe.pad_file) flags |= file_storage::flag_pad_file; + if (fe.hidden_attribute) flags |= file_storage::flag_hidden; + if (fe.executable_attribute) flags |= file_storage::flag_executable; + if (fe.symlink_attribute) flags |= file_storage::flag_symlink; + + add_file_borrow({}, fe.path, fe.size, flags, filehash, fe.mtime + , fe.symlink_path); + } + +#if defined TORRENT_WINDOWS + void file_storage::set_name(std::wstring const& n) + { + m_name = convert_from_wstring(n); + } + + void file_storage::rename_file_deprecated(file_index_t index, std::wstring const& new_filename) + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + update_path_index(m_files[index], convert_from_wstring(new_filename)); + } + + void file_storage::add_file(std::wstring const& file, std::int64_t file_size + , file_flags_t const file_flags, std::time_t mtime, string_view symlink_path) + { + add_file(convert_from_wstring(file), file_size, file_flags, mtime, symlink_path); + } + + void file_storage::rename_file(file_index_t index, std::wstring const& new_filename) + { + rename_file_deprecated(index, new_filename); + } +#endif // TORRENT_WINDOWS +#endif // TORRENT_ABI_VERSION + + void file_storage::rename_file(file_index_t const index + , std::string const& new_filename) + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + update_path_index(m_files[index], new_filename); + } + +#if TORRENT_ABI_VERSION == 1 + file_storage::iterator file_storage::file_at_offset_deprecated(std::int64_t offset) const + { + // find the file iterator and file offset + internal_file_entry target; + target.offset = aux::numeric_cast(offset); + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + auto file_iter = std::upper_bound( + begin_deprecated(), end_deprecated(), target, compare_file_offset); + + TORRENT_ASSERT(file_iter != begin_deprecated()); + --file_iter; + return file_iter; + } + + file_storage::iterator file_storage::file_at_offset(std::int64_t offset) const + { + return file_at_offset_deprecated(offset); + } +#endif + + file_index_t file_storage::file_index_at_offset(std::int64_t const offset) const + { + TORRENT_ASSERT_PRECOND(offset >= 0); + TORRENT_ASSERT_PRECOND(offset < m_total_size); + // find the file iterator and file offset + internal_file_entry target; + target.offset = aux::numeric_cast(offset); + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + auto file_iter = std::upper_bound( + m_files.begin(), m_files.end(), target, compare_file_offset); + + TORRENT_ASSERT(file_iter != m_files.begin()); + --file_iter; + return file_index_t(int(file_iter - m_files.begin())); + } + + char const* file_storage::file_name_ptr(file_index_t const index) const + { + return m_files[index].name; + } + + int file_storage::file_name_len(file_index_t const index) const + { + if (m_files[index].name_len == internal_file_entry::name_is_owned) + return -1; + return m_files[index].name_len; + } + + std::vector file_storage::map_block(piece_index_t const piece + , std::int64_t const offset, int size) const + { + TORRENT_ASSERT_PRECOND(piece >= piece_index_t{0}); + TORRENT_ASSERT_PRECOND(piece < end_piece()); + TORRENT_ASSERT_PRECOND(num_files() > 0); + std::vector ret; + + if (m_files.empty()) return ret; + + // find the file iterator and file offset + internal_file_entry target; + target.offset = aux::numeric_cast(static_cast(piece) * std::int64_t(m_piece_length) + offset); + TORRENT_ASSERT_PRECOND(std::int64_t(target.offset) + size <= m_total_size); + TORRENT_ASSERT(!compare_file_offset(target, m_files.front())); + + // in case the size is past the end, fix it up + if (std::int64_t(target.offset) + size > m_total_size) + size = aux::numeric_cast(m_total_size - std::int64_t(target.offset)); + + auto file_iter = std::upper_bound( + m_files.begin(), m_files.end(), target, compare_file_offset); + + TORRENT_ASSERT(file_iter != m_files.begin()); + --file_iter; + + std::int64_t file_offset = target.offset - file_iter->offset; + for (; size > 0; file_offset -= file_iter->size, ++file_iter) + { + TORRENT_ASSERT(file_iter != m_files.end()); + if (file_offset < std::int64_t(file_iter->size)) + { + file_slice f{}; + f.file_index = file_index_t(int(file_iter - m_files.begin())); + f.offset = file_offset; + f.size = std::min(std::int64_t(file_iter->size) - file_offset, std::int64_t(size)); + TORRENT_ASSERT(f.size <= size); + size -= int(f.size); + file_offset += f.size; + ret.push_back(f); + } + + TORRENT_ASSERT(size >= 0); + } + return ret; + } + +#if TORRENT_ABI_VERSION == 1 + file_entry file_storage::at(int index) const + { + return at_deprecated(index); + } + + internal_file_entry const& file_storage::internal_at(int const index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_files.size())); + return m_files[file_index_t(index)]; + } + + file_entry file_storage::at_deprecated(int index) const + { + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + file_entry ret; + internal_file_entry const& ife = m_files[index]; + ret.path = file_path(index); + ret.offset = ife.offset; + ret.size = ife.size; + ret.mtime = mtime(index); + ret.pad_file = ife.pad_file; + ret.hidden_attribute = ife.hidden_attribute; + ret.executable_attribute = ife.executable_attribute; + ret.symlink_attribute = ife.symlink_attribute; + if (ife.symlink_index != internal_file_entry::not_a_symlink) + ret.symlink_path = symlink(index); + ret.filehash = hash(index); + return ret; + } +#endif // TORRENT_ABI_VERSION + + int file_storage::num_files() const noexcept + { return int(m_files.size()); } + + // returns the index of the one-past-end file in the file storage + file_index_t file_storage::end_file() const noexcept + { return m_files.end_index(); } + + file_index_t file_storage::last_file() const noexcept + { return --m_files.end_index(); } + + index_range file_storage::file_range() const noexcept + { return m_files.range(); } + + index_range file_storage::piece_range() const noexcept + { return {piece_index_t{0}, end_piece()}; } + + peer_request file_storage::map_file(file_index_t const file_index + , std::int64_t const file_offset, int const size) const + { + TORRENT_ASSERT_PRECOND(file_index < end_file()); + TORRENT_ASSERT(m_num_pieces >= 0); + + peer_request ret{}; + if (file_index >= end_file()) + { + ret.piece = end_piece(); + ret.start = 0; + ret.length = 0; + return ret; + } + + std::int64_t const offset = file_offset + this->file_offset(file_index); + + if (offset >= total_size()) + { + ret.piece = end_piece(); + ret.start = 0; + ret.length = 0; + } + else + { + ret.piece = piece_index_t(int(offset / piece_length())); + ret.start = int(offset % piece_length()); + ret.length = size; + if (offset + size > total_size()) + ret.length = int(total_size() - offset); + } + return ret; + } + + void file_storage::add_file(std::string const& path, std::int64_t file_size + , file_flags_t const file_flags, std::time_t mtime, string_view symlink_path) + { + add_file_borrow({}, path, file_size, file_flags, nullptr, mtime + , symlink_path); + } + + void file_storage::add_file_borrow(string_view filename + , std::string const& path, std::int64_t const file_size + , file_flags_t const file_flags, char const* filehash + , std::int64_t const mtime, string_view symlink_path) + { + TORRENT_ASSERT_PRECOND(file_size >= 0); + TORRENT_ASSERT_PRECOND(!is_complete(filename)); + if (!has_parent_path(path)) + { + // you have already added at least one file with a + // path to the file (branch_path), which means that + // all the other files need to be in the same top + // directory as the first file. + TORRENT_ASSERT_PRECOND(m_files.empty()); + m_name = path; + } + else + { + if (m_files.empty()) + m_name = lsplit_path(path).first.to_string(); + } + + // this is poor-man's emplace_back() + m_files.resize(m_files.size() + 1); + internal_file_entry& e = m_files.back(); + + // the last argument specified whether the function should also set + // the filename. If it does, it will copy the leaf filename from path. + // if filename is empty, we should copy it. If it isn't, we're borrowing + // it and we can save the copy by setting it after this call to + // update_path_index(). + update_path_index(e, path, filename.empty()); + + // filename is allowed to be empty, in which case we just use path + if (!filename.empty()) + e.set_name(filename, true); + + e.size = aux::numeric_cast(file_size); + e.offset = aux::numeric_cast(m_total_size); + e.pad_file = bool(file_flags & file_storage::flag_pad_file); + e.hidden_attribute = bool(file_flags & file_storage::flag_hidden); + e.executable_attribute = bool(file_flags & file_storage::flag_executable); + e.symlink_attribute = bool(file_flags & file_storage::flag_symlink); + + if (filehash) + { + if (m_file_hashes.size() < m_files.size()) m_file_hashes.resize(m_files.size()); + m_file_hashes[last_file()] = filehash; + } + if (!symlink_path.empty() + && m_symlinks.size() < internal_file_entry::not_a_symlink - 1) + { + e.symlink_index = m_symlinks.size(); + m_symlinks.emplace_back(symlink_path.to_string()); + } + else + { + e.symlink_attribute = false; + } + if (mtime) + { + if (m_mtime.size() < m_files.size()) m_mtime.resize(m_files.size()); + m_mtime[last_file()] = std::time_t(mtime); + } + + m_total_size += e.size; + } + + sha1_hash file_storage::hash(file_index_t const index) const + { + if (index >= m_file_hashes.end_index()) return sha1_hash(); + return sha1_hash(m_file_hashes[index]); + } + + std::string const& file_storage::symlink(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size())); + + auto const& link = m_symlinks[fe.symlink_index]; + + // TODO: 3 this is a hack to retain ABI compatibility with 1.2.1 + // in next major release, make this return by value + static std::string storage[4]; + static std::atomic counter{0}; + std::string& ret = storage[(counter++) % 4]; + ret.reserve(m_name.size() + link.size() + 1); + ret.assign(m_name); + append_path(ret, link); + return ret; + } + + std::time_t file_storage::mtime(file_index_t const index) const + { + if (index >= m_mtime.end_index()) return 0; + return m_mtime[index]; + } + +namespace { + + template + void process_string_lowercase(CRC& crc, string_view str) + { + for (char const c : str) + crc.process_byte(to_lower(c) & 0xff); + } + + template + void process_path_lowercase( + std::unordered_set& table + , CRC crc, string_view str) + { + if (str.empty()) return; + for (char const c : str) + { + if (c == TORRENT_SEPARATOR) + table.insert(crc.checksum()); + crc.process_byte(to_lower(c) & 0xff); + } + table.insert(crc.checksum()); + } + } + + void file_storage::all_path_hashes( + std::unordered_set& table) const + { + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + + if (!m_name.empty()) + { + process_string_lowercase(crc, m_name); + TORRENT_ASSERT(m_name[m_name.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + + for (auto const& p : m_paths) + process_path_lowercase(table, crc, p); + } + + std::uint32_t file_storage::file_path_hash(file_index_t const index + , std::string const& save_path) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + + if (fe.path_index == -2) + { + // -2 means this is an absolute path filename + process_string_lowercase(crc, fe.filename()); + } + else if (fe.path_index == -1) + { + // -1 means no path + if (!save_path.empty()) + { + process_string_lowercase(crc, save_path); + TORRENT_ASSERT(save_path[save_path.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, fe.filename()); + } + else if (fe.no_root_dir) + { + if (!save_path.empty()) + { + process_string_lowercase(crc, save_path); + TORRENT_ASSERT(save_path[save_path.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + std::string const& p = m_paths[fe.path_index]; + if (!p.empty()) + { + process_string_lowercase(crc, p); + TORRENT_ASSERT(p[p.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, fe.filename()); + } + else + { + if (!save_path.empty()) + { + process_string_lowercase(crc, save_path); + TORRENT_ASSERT(save_path[save_path.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, m_name); + TORRENT_ASSERT(m_name.size() > 0); + TORRENT_ASSERT(m_name[m_name.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + + std::string const& p = m_paths[fe.path_index]; + if (!p.empty()) + { + process_string_lowercase(crc, p); + TORRENT_ASSERT(p.size() > 0); + TORRENT_ASSERT(p[p.size() - 1] != TORRENT_SEPARATOR); + crc.process_byte(TORRENT_SEPARATOR); + } + process_string_lowercase(crc, fe.filename()); + } + + return crc.checksum(); + } + + std::string file_storage::file_path(file_index_t const index, std::string const& save_path) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + + std::string ret; + + // -2 means this is an absolute path filename + if (fe.path_index == -2) + { + ret = fe.filename().to_string(); + } + else if (fe.path_index == -1) + { + // -1 means no path + ret.reserve(save_path.size() + fe.filename().size() + 1); + ret.assign(save_path); + append_path(ret, fe.filename()); + } + else if (fe.no_root_dir) + { + std::string const& p = m_paths[fe.path_index]; + + ret.reserve(save_path.size() + p.size() + fe.filename().size() + 2); + ret.assign(save_path); + append_path(ret, p); + append_path(ret, fe.filename()); + } + else + { + std::string const& p = m_paths[fe.path_index]; + + ret.reserve(save_path.size() + m_name.size() + p.size() + fe.filename().size() + 3); + ret.assign(save_path); + append_path(ret, m_name); + append_path(ret, p); + append_path(ret, fe.filename()); + } + + // a single return statement, just to make NRVO more likely to kick in + return ret; + } + + std::string file_storage::internal_file_path(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + + if (fe.path_index >= 0) + { + std::string ret; + std::string const& p = m_paths[fe.path_index]; + ret.reserve(p.size() + fe.filename().size() + 2); + append_path(ret, p); + append_path(ret, fe.filename()); + return ret; + } + else + { + return fe.filename().to_string(); + } + } + + string_view file_storage::file_name(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + return fe.filename(); + } + + std::int64_t file_storage::file_size(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + return m_files[index].size; + } + + bool file_storage::pad_file_at(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + return m_files[index].pad_file; + } + + std::int64_t file_storage::file_offset(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + return m_files[index].offset; + } + + file_flags_t file_storage::file_flags(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + return (fe.pad_file ? file_storage::flag_pad_file : file_flags_t{}) + | (fe.hidden_attribute ? file_storage::flag_hidden : file_flags_t{}) + | (fe.executable_attribute ? file_storage::flag_executable : file_flags_t{}) + | (fe.symlink_attribute ? file_storage::flag_symlink : file_flags_t{}); + } + + bool file_storage::file_absolute_path(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0) && index < end_file()); + internal_file_entry const& fe = m_files[index]; + return fe.path_index == -2; + } + +#if TORRENT_ABI_VERSION == 1 + sha1_hash file_storage::hash(internal_file_entry const& fe) const + { + int index = int(&fe - &m_files[0]); + if (index >= int(m_file_hashes.size())) return sha1_hash(nullptr); + return sha1_hash(m_file_hashes[index]); + } + + std::string const& file_storage::symlink(internal_file_entry const& fe) const + { + TORRENT_ASSERT_PRECOND(fe.symlink_index < int(m_symlinks.size())); + return m_symlinks[fe.symlink_index]; + } + + std::time_t file_storage::mtime(internal_file_entry const& fe) const + { + int index = int(&fe - &m_files[0]); + if (index >= int(m_mtime.size())) return 0; + return m_mtime[index]; + } + + int file_storage::file_index(internal_file_entry const& fe) const + { + int index = int(&fe - &m_files[0]); + TORRENT_ASSERT_PRECOND(index >= 0 && index < int(m_files.size())); + return index; + } + + std::string file_storage::file_path(internal_file_entry const& fe + , std::string const& save_path) const + { + int const index = int(&fe - &m_files[0]); + return file_path(index, save_path); + } + + std::string file_storage::file_name(internal_file_entry const& fe) const + { + return fe.filename().to_string(); + } + + std::int64_t file_storage::file_size(internal_file_entry const& fe) const + { + return fe.size; + } + + bool file_storage::pad_file_at(internal_file_entry const& fe) const + { + return fe.pad_file; + } + + std::int64_t file_storage::file_offset(internal_file_entry const& fe) const + { + return fe.offset; + } + + file_entry file_storage::at(file_storage::iterator i) const + { return at_deprecated(int(i - m_files.begin())); } +#endif // TORRENT_ABI_VERSION + + void file_storage::reorder_file(int const index, int const dst) + { + TORRENT_ASSERT(index < int(m_files.size())); + TORRENT_ASSERT(dst < int(m_files.size())); + TORRENT_ASSERT(dst < index); + + std::iter_swap(m_files.begin() + index, m_files.begin() + dst); + if (!m_mtime.empty()) + { + TORRENT_ASSERT(m_mtime.size() == m_files.size()); + if (int(m_mtime.size()) < index) m_mtime.resize(index + 1, 0); + std::iter_swap(m_mtime.begin() + dst, m_mtime.begin() + index); + } + if (!m_file_hashes.empty()) + { + TORRENT_ASSERT(m_file_hashes.size() == m_files.size()); + if (int(m_file_hashes.size()) < index) m_file_hashes.resize(index + 1, nullptr); + std::iter_swap(m_file_hashes.begin() + dst, m_file_hashes.begin() + index); + } + } + + void file_storage::swap(file_storage& ti) noexcept + { + using std::swap; + swap(ti.m_files, m_files); + swap(ti.m_file_hashes, m_file_hashes); + swap(ti.m_symlinks, m_symlinks); + swap(ti.m_mtime, m_mtime); + swap(ti.m_paths, m_paths); + swap(ti.m_name, m_name); + swap(ti.m_total_size, m_total_size); + swap(ti.m_num_pieces, m_num_pieces); + swap(ti.m_piece_length, m_piece_length); + } + + void file_storage::optimize(int const pad_file_limit, int alignment + , bool const tail_padding) + { + if (alignment == -1) + alignment = m_piece_length; + + // TODO: padfiles should be removed + + std::int64_t off = 0; + int padding_file = 0; + for (auto i = m_files.begin(); i != m_files.end(); ++i) + { + if ((off % alignment) == 0) + { + // this file position is aligned, pick the largest + // available file to put here. If we encounter a file whose size is + // divisible by `alignment`, we pick that immediately, since that + // will not affect whether we're at an aligned position and will + // improve packing of files + auto best_match = i; + for (auto k = i; k != m_files.end(); ++k) + { + // a file whose size fits the alignment always takes priority, + // since it will let us keep placing aligned files + if ((k->size % aux::numeric_cast(alignment)) == 0) + { + best_match = k; + break; + } + // otherwise, pick the largest file, to have as many bytes be + // aligned. + if (best_match->size < k->size) best_match = k; + } + + if (best_match != i) + { + int const index = int(best_match - m_files.begin()); + int const cur_index = int(i - m_files.begin()); + reorder_file(index, cur_index); + i = m_files.begin() + cur_index; + } + } + else if (pad_file_limit >= 0 + && i->size > std::uint32_t(pad_file_limit) + && i->pad_file == false) + { + // if we have pad files enabled, and this file is + // not piece-aligned and the file size exceeds the + // limit, and it's not a padding file itself. + // so add a padding file in front of it + int const pad_size = alignment - (off % alignment); + + // find the largest file that fits in pad_size + auto best_match = m_files.end(); + + // if pad_file_limit is 0, it means all files are padded, there's + // no point in trying to find smaller files to use as filling + if (pad_file_limit > 0) + { + for (auto j = i + 1; j < m_files.end(); ++j) + { + if (j->size > std::uint32_t(pad_size)) continue; + if (best_match == m_files.end() || j->size > best_match->size) + best_match = j; + } + + if (best_match != m_files.end()) + { + // we found one + // We cannot have found i, because i->size > pad_file_limit + // which is forced to be no less than alignment. We only + // look for files <= pad_size, which never is greater than + // alignment + TORRENT_ASSERT(best_match != i); + int index = int(best_match - m_files.begin()); + int cur_index = int(i - m_files.begin()); + reorder_file(index, cur_index); + i = m_files.begin() + cur_index; + i->offset = aux::numeric_cast(off); + off += i->size; + continue; + } + } + + // we could not find a file that fits in pad_size + // add a padding file + // note that i will be set to point to the + // new pad file. Once we're done adding it, we need + // to increment i to point to the current file again + // first add the pad file to the end of the file list + // then swap it in place. This minimizes the amount + // of copying of internal_file_entry, which is somewhat + // expensive (until we have move semantics) + add_pad_file(pad_size, i, off, padding_file); + + TORRENT_ASSERT((off % alignment) == 0); + continue; + } + i->offset = aux::numeric_cast(off); + off += i->size; + + if (tail_padding + && i->size > std::uint32_t(pad_file_limit) + && (off % alignment) != 0) + { + // skip the file we just put in place, so we put the pad + // file after it + ++i; + + // tail-padding is enabled, and the offset after this file is not + // aligned. The last file must be padded too, in order to match an + // equivalent tail-padded file. + add_pad_file(alignment - (off % alignment), i, off, padding_file); + + TORRENT_ASSERT((off % alignment) == 0); + + if (i == m_files.end()) break; + } + } + m_total_size = off; + } + + void file_storage::add_pad_file(int const size + , std::vector::iterator& i + , std::int64_t& offset + , int& pad_file_counter) + { + int const cur_index = int(i - m_files.begin()); + int const index = int(m_files.size()); + m_files.push_back(internal_file_entry()); + internal_file_entry& e = m_files.back(); + // i may have been invalidated, refresh it + i = m_files.begin() + cur_index; + e.size = aux::numeric_cast(size); + e.offset = aux::numeric_cast(offset); + e.path_index = get_or_add_path(".pad"); + char name[15]; + std::snprintf(name, sizeof(name), "%d", pad_file_counter); + e.set_name(name); + e.pad_file = true; + offset += size; + ++pad_file_counter; + + if (!m_mtime.empty()) m_mtime.resize(index + 1, 0); + if (!m_file_hashes.empty()) m_file_hashes.resize(index + 1, nullptr); + + if (index != cur_index) reorder_file(index, cur_index); + } + + void file_storage::sanitize_symlinks() + { + // symlinks are unusual, this function is optimized assuming there are no + // symbolic links in the torrent. If we find one symbolic link, we'll + // build the hash table of files it's allowed to refer to, but don't pay + // that price up-front. + std::unordered_map file_map; + bool file_map_initialized = false; + + // lazily instantiated set of all valid directories a symlink may point to + // TODO: in C++17 this could be string_view + std::unordered_set dir_map; + bool dir_map_initialized = false; + + // symbolic links that points to directories + std::unordered_map dir_links; + + // we validate symlinks in (potentially) 2 passes over the files. + // remaining symlinks to validate after the first pass + std::vector symlinks_to_validate; + + for (auto const i : file_range()) + { + if (!(file_flags(i) & file_storage::flag_symlink)) continue; + + if (!file_map_initialized) + { + for (auto const j : file_range()) + file_map.insert({internal_file_path(j), j}); + file_map_initialized = true; + } + + internal_file_entry const& fe = m_files[i]; + TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size())); + + // symlink targets are only allowed to point to files or directories in + // this torrent. + { + std::string target = m_symlinks[fe.symlink_index]; + + if (is_complete(target)) + { + // a symlink target is not allowed to be an absolute path, ever + // this symlink is invalid, make it point to itself + m_symlinks[fe.symlink_index] = internal_file_path(i); + continue; + } + + auto const iter = file_map.find(target); + if (iter != file_map.end()) + { + m_symlinks[fe.symlink_index] = target; + if (file_flags(iter->second) & file_storage::flag_symlink) + { + // we don't know whether this symlink is a file or a + // directory, so make the conservative assumption that it's a + // directory + dir_links[internal_file_path(i)] = target; + } + continue; + } + + // it may point to a directory that doesn't have any files (but only + // other directories), in which case it won't show up in m_paths + if (!dir_map_initialized) + { + for (auto const& p : m_paths) + for (string_view pv = p; !pv.empty(); pv = rsplit_path(pv).first) + dir_map.insert(pv.to_string()); + dir_map_initialized = true; + } + + if (dir_map.count(target)) + { + // it points to a sub directory within the torrent, that's OK + m_symlinks[fe.symlink_index] = target; + dir_links[internal_file_path(i)] = target; + continue; + } + + } + + // for backwards compatibility, allow paths relative to the link as + // well + if (fe.path_index >= 0) + { + std::string target = m_paths[fe.path_index]; + append_path(target, m_symlinks[fe.symlink_index]); + // if it points to a directory, that's OK + auto const it = std::find(m_paths.begin(), m_paths.end(), target); + if (it != m_paths.end()) + { + m_symlinks[fe.symlink_index] = *it; + dir_links[internal_file_path(i)] = *it; + continue; + } + + if (dir_map.count(target)) + { + // it points to a sub directory within the torrent, that's OK + m_symlinks[fe.symlink_index] = target; + dir_links[internal_file_path(i)] = target; + continue; + } + + auto const iter = file_map.find(target); + if (iter != file_map.end()) + { + m_symlinks[fe.symlink_index] = target; + if (file_flags(iter->second) & file_storage::flag_symlink) + { + // we don't know whether this symlink is a file or a + // directory, so make the conservative assumption that it's a + // directory + dir_links[internal_file_path(i)] = target; + } + continue; + } + } + + // we don't know whether this symlink is a file or a + // directory, so make the conservative assumption that it's a + // directory + dir_links[internal_file_path(i)] = m_symlinks[fe.symlink_index]; + symlinks_to_validate.push_back(i); + } + + // in case there were some "complex" symlinks, we nee a second pass to + // validate those. For example, symlinks whose target rely on other + // symlinks + for (auto const i : symlinks_to_validate) + { + internal_file_entry const& fe = m_files[i]; + TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size())); + + std::string target = m_symlinks[fe.symlink_index]; + + // to avoid getting stuck in an infinite loop, we only allow traversing + // a symlink once + std::set traversed; + + // this is where we check every path element for existence. If it's not + // among the concrete paths, it may be a symlink, which is also OK + // note that we won't iterate through this for the last step, where the + // filename is included. The filename is validated after the loop + for (string_view branch = lsplit_path(target).first; + branch.size() < target.size(); + branch = lsplit_path(target, branch.size() + 1).first) + { + // this is a concrete directory + if (dir_map.count(branch.to_string())) continue; + + auto const iter = dir_links.find(branch.to_string()); + if (iter == dir_links.end()) goto failed; + if (traversed.count(branch.to_string())) goto failed; + traversed.insert(branch.to_string()); + + // this path element is a symlink. substitute the branch so far by + // the link target + target = combine_path(iter->second, target.substr(branch.size() + 1)); + + // start over with the new (concrete) path + branch = {}; + } + + // the final (resolved) target must be a valid file + // or directory + if (file_map.count(target) == 0 + && dir_map.count(target) == 0) goto failed; + + // this is OK + continue; + +failed: + + // this symlink is invalid, make it point to itself + m_symlinks[fe.symlink_index] = internal_file_path(i); + } + } + + +namespace aux { + + std::tuple + file_piece_range_exclusive(file_storage const& fs, file_index_t const file) + { + peer_request const range = fs.map_file(file, 0, 1); + std::int64_t const file_size = fs.file_size(file); + std::int64_t const piece_size = fs.piece_length(); + piece_index_t const begin_piece = range.start == 0 ? range.piece : piece_index_t(static_cast(range.piece) + 1); + // the last piece is potentially smaller than the other pieces, so the + // generic logic doesn't really work. If this file is the last file, the + // last piece doesn't overlap with any other file and it's entirely + // contained within the last file. + piece_index_t const end_piece = (file == file_index_t(fs.num_files() - 1)) + ? piece_index_t(fs.num_pieces()) + : piece_index_t(int((static_cast(range.piece) * piece_size + range.start + file_size + 1) / piece_size)); + return std::make_tuple(begin_piece, end_piece); + } + + std::tuple + file_piece_range_inclusive(file_storage const& fs, file_index_t const file) + { + peer_request const range = fs.map_file(file, 0, 1); + std::int64_t const file_size = fs.file_size(file); + std::int64_t const piece_size = fs.piece_length(); + piece_index_t const end_piece = piece_index_t(int((static_cast(range.piece) + * piece_size + range.start + file_size - 1) / piece_size + 1)); + return std::make_tuple(range.piece, end_piece); + } + + } // namespace aux +} diff --git a/src/fingerprint.cpp b/src/fingerprint.cpp new file mode 100644 index 0000000..fa9459b --- /dev/null +++ b/src/fingerprint.cpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2016-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/assert.hpp" +#include // for strlen + +namespace libtorrent { + + namespace { + + char version_to_char(int const v) + { + if (v >= 0 && v < 10) return char('0' + v); + else if (v >= 10) return char('A' + (v - 10)); + TORRENT_ASSERT_FAIL(); + return '0'; + } + + } // anonymous namespace + + std::string generate_fingerprint(std::string name, int const major + , int const minor + , int const revision + , int const tag) + { + TORRENT_ASSERT_PRECOND(major >= 0); + TORRENT_ASSERT_PRECOND(minor >= 0); + TORRENT_ASSERT_PRECOND(revision >= 0); + TORRENT_ASSERT_PRECOND(tag >= 0); + TORRENT_ASSERT_PRECOND(name.size() == 2); + if (name.size() < 2) name = "--"; + + std::string ret; + ret.resize(8); + ret[0] = '-'; + ret[1] = name[0]; + ret[2] = name[1]; + ret[3] = version_to_char(major); + ret[4] = version_to_char(minor); + ret[5] = version_to_char(revision); + ret[6] = version_to_char(tag); + ret[7] = '-'; + return ret; + } + + fingerprint::fingerprint(const char* id_string, int major, int minor + , int revision, int tag) + : major_version(major) + , minor_version(minor) + , revision_version(revision) + , tag_version(tag) + { + TORRENT_ASSERT(id_string); + TORRENT_ASSERT(major >= 0); + TORRENT_ASSERT(minor >= 0); + TORRENT_ASSERT(revision >= 0); + TORRENT_ASSERT(tag >= 0); + TORRENT_ASSERT(std::strlen(id_string) == 2); + name[0] = id_string[0]; + name[1] = id_string[1]; + } + +#if TORRENT_ABI_VERSION == 1 + std::string fingerprint::to_string() const + { + return generate_fingerprint(std::string(name, 2), major_version, minor_version + , revision_version, tag_version); + } +#endif // TORRENT_ABI_VERSION + +} + diff --git a/src/generate_peer_id.cpp b/src/generate_peer_id.cpp new file mode 100644 index 0000000..8f09e0f --- /dev/null +++ b/src/generate_peer_id.cpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/generate_peer_id.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/string_util.hpp" // for url_random + +#include + +namespace libtorrent { namespace aux { + +peer_id generate_peer_id(session_settings const& sett) +{ + peer_id ret; + std::string print = sett.get_str(settings_pack::peer_fingerprint); + if (std::ptrdiff_t(print.size()) > ret.size()) + print.resize(std::size_t(ret.size())); + + // the client's fingerprint + std::copy(print.begin(), print.end(), ret.begin()); + if (std::ptrdiff_t(print.size()) < ret.size()) + url_random(span(ret).subspan(std::ptrdiff_t(print.length()))); + return ret; +} + +}} diff --git a/src/gzip.cpp b/src/gzip.cpp new file mode 100644 index 0000000..0150795 --- /dev/null +++ b/src/gzip.cpp @@ -0,0 +1,264 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/assert.hpp" +#include "libtorrent/puff.hpp" +#include "libtorrent/gzip.hpp" + +#include + +namespace { + + enum + { + FTEXT = 0x01, + FHCRC = 0x02, + FEXTRA = 0x04, + FNAME = 0x08, + FCOMMENT = 0x10, + FRESERVED = 0xe0, + + GZIP_MAGIC0 = 0x1f, + GZIP_MAGIC1 = 0x8b + }; + +} + +namespace libtorrent { + + struct gzip_error_category final : boost::system::error_category + { + const char* name() const BOOST_SYSTEM_NOEXCEPT override; + std::string message(int ev) const override; + boost::system::error_condition default_error_condition(int ev) const BOOST_SYSTEM_NOEXCEPT override + { return {ev, *this}; } + }; + + const char* gzip_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "gzip error"; + } + + std::string gzip_error_category::message(int ev) const + { + static char const* msgs[] = + { + "no error", + "invalid gzip header", + "inflated data too large", + "available inflate data did not terminate", + "output space exhausted before completing inflate", + "invalid block type (type == 3)", + "stored block length did not match one's complement", + "dynamic block code description: too many length or distance codes", + "dynamic block code description: code lengths codes incomplete", + "dynamic block code description: repeat lengths with no first length", + "dynamic block code description: repeat more than specified lengths", + "dynamic block code description: invalid literal/length code lengths", + "dynamic block code description: invalid distance code lengths", + "invalid literal/length or distance code in fixed or dynamic block", + "distance is too far back in fixed or dynamic block", + "unknown gzip error", + }; + if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) + return "Unknown error"; + return msgs[ev]; + } + + boost::system::error_category& gzip_category() + { + static gzip_error_category category; + return category; + } + + namespace gzip_errors + { + boost::system::error_code make_error_code(error_code_enum e) + { + return {e, gzip_category()}; + } + } + +namespace { + + // returns -1 if gzip header is invalid or the header size in bytes + int gzip_header(span const in) + { + // The zip header cannot be shorter than 10 bytes + if (in.size() < 10) return -1; + + span buffer( + reinterpret_cast(in.data()), in.size()); + + // gzip is defined in https://tools.ietf.org/html/rfc1952 + + // check the magic header of gzip + if ((buffer[0] != GZIP_MAGIC0) || (buffer[1] != GZIP_MAGIC1)) return -1; + + int const method = buffer[2]; + int const flags = buffer[3]; + + // check for reserved flag and make sure it's compressed with the correct metod + // we only support deflate + if (method != 8 || (flags & FRESERVED) != 0) return -1; + + // skip time, xflags, OS code. The first 10 bytes of the header: + // +---+---+---+---+---+---+---+---+---+---+ + // |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->) + // +---+---+---+---+---+---+---+---+---+---+ + + buffer = buffer.subspan(10); + + if (flags & FEXTRA) + { + if (buffer.size() < 2) return -1; + + auto const extra_len = (buffer[1] << 8) | buffer[0]; + if (buffer.size() < extra_len + 2) return -1; + buffer = buffer.subspan(extra_len + 2); + } + + if (flags & FNAME) + { + if (buffer.empty()) return -1; + while (buffer[0] != 0) + { + buffer = buffer.subspan(1); + if (buffer.empty()) return -1; + } + buffer = buffer.subspan(1); + } + + if (flags & FCOMMENT) + { + if (buffer.empty()) return -1; + while (buffer[0] != 0) + { + buffer = buffer.subspan(1); + if (buffer.empty()) return -1; + } + buffer = buffer.subspan(1); + } + + if (flags & FHCRC) + { + if (buffer.size() < 2) return -1; + buffer = buffer.subspan(2); + } + + return static_cast(in.size() - buffer.size()); + } + } // anonymous namespace + + void inflate_gzip(span in + , std::vector& buffer + , int maximum_size + , error_code& ec) + { + ec.clear(); + TORRENT_ASSERT(maximum_size > 0); + + int const header_len = gzip_header(in); + if (header_len < 0) + { + ec = gzip_errors::invalid_gzip_header; + return; + } + + // start off with 4 kilobytes and grow + // if needed + unsigned long destlen = 4096; + int ret = 0; + in = in.subspan(header_len); + unsigned long srclen = std::uint32_t(in.size()); + + do + { + TORRENT_TRY { + buffer.resize(destlen); + } TORRENT_CATCH (std::exception const&) { + ec = errors::no_memory; + return; + } + + ret = puff(reinterpret_cast(buffer.data()) + , &destlen + , reinterpret_cast(in.data()) + , &srclen); + + // if the destination buffer wasn't large enough, double its + // size and try again. Unless it's already at its max, in which + // case we fail + if (ret == 1) // 1: output space exhausted before completing inflate + { + if (destlen == std::uint32_t(maximum_size)) + { + ec = gzip_errors::inflated_data_too_large; + return; + } + + destlen *= 2; + if (destlen > std::uint32_t(maximum_size)) + destlen = std::uint32_t(maximum_size); + } + } while (ret == 1); + + if (ret != 0) + { + switch (ret) + { + case 2: ec = gzip_errors::data_did_not_terminate; return; + case 1: ec = gzip_errors::space_exhausted; return; + case -1: ec = gzip_errors::invalid_block_type; return; + case -2: ec = gzip_errors::invalid_stored_block_length; return; + case -3: ec = gzip_errors::too_many_length_or_distance_codes; return; + case -4: ec = gzip_errors::code_lengths_codes_incomplete; return; + case -5: ec = gzip_errors::repeat_lengths_with_no_first_length; return; + case -6: ec = gzip_errors::repeat_more_than_specified_lengths; return; + case -7: ec = gzip_errors::invalid_literal_length_code_lengths; return; + case -8: ec = gzip_errors::invalid_distance_code_lengths; return; + case -9: ec = gzip_errors::invalid_literal_code_in_block; return; + case -10: ec = gzip_errors::distance_too_far_back_in_block; return; + default: ec = gzip_errors::unknown_gzip_error; return; + } + } + + if (destlen > buffer.size()) + { + ec = gzip_errors::unknown_gzip_error; + return; + } + + buffer.resize(destlen); + } + +} diff --git a/src/hasher.cpp b/src/hasher.cpp new file mode 100644 index 0000000..ed36937 --- /dev/null +++ b/src/hasher.cpp @@ -0,0 +1,157 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hasher.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/openssl.hpp" + +namespace libtorrent { + +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + + hasher::hasher() + { +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_open(&m_context, GCRY_MD_SHA1, 0); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Init(&m_context); +#elif TORRENT_USE_CRYPTOAPI +#elif defined TORRENT_USE_LIBCRYPTO + SHA1_Init(&m_context); +#else + SHA1_init(&m_context); +#endif + } + + hasher::hasher(span data) + : hasher() + { + update(data); + } + + hasher::hasher(char const* data, int len) + : hasher() + { + TORRENT_ASSERT(len > 0); + update({data, len}); + } + +#ifdef TORRENT_USE_LIBGCRYPT + hasher::hasher(hasher const& h) + { + gcry_md_copy(&m_context, h.m_context); + } + + hasher& hasher::operator=(hasher const& h) & + { + if (this == &h) return *this; + gcry_md_close(m_context); + gcry_md_copy(&m_context, h.m_context); + return *this; + } +#else + hasher::hasher(hasher const&) = default; + hasher& hasher::operator=(hasher const&) & = default; +#endif + + hasher& hasher::update(char const* data, int len) + { + return update({data, len}); + } + + hasher& hasher::update(span data) + { + TORRENT_ASSERT(data.size() > 0); +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_write(m_context, data.data(), static_cast(data.size())); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Update(&m_context, reinterpret_cast(data.data()), CC_LONG(data.size())); +#elif TORRENT_USE_CRYPTOAPI + m_context.update(data); +#elif defined TORRENT_USE_LIBCRYPTO + SHA1_Update(&m_context, reinterpret_cast(data.data()) + , static_cast(data.size())); +#else + SHA1_update(&m_context, reinterpret_cast(data.data()) + , static_cast(data.size())); +#endif + return *this; + } + + sha1_hash hasher::final() + { + sha1_hash digest; +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_final(m_context); + digest.assign(reinterpret_cast(gcry_md_read(m_context, 0))); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Final(reinterpret_cast(digest.data()), &m_context); +#elif TORRENT_USE_CRYPTOAPI + m_context.get_hash(digest.data(), digest.size()); +#elif defined TORRENT_USE_LIBCRYPTO + SHA1_Final(reinterpret_cast(digest.data()), &m_context); +#else + SHA1_final(reinterpret_cast(digest.data()), &m_context); +#endif + return digest; + } + + void hasher::reset() + { +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_reset(m_context); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA1_Init(&m_context); +#elif TORRENT_USE_CRYPTOAPI + m_context.reset(); +#elif defined TORRENT_USE_LIBCRYPTO + SHA1_Init(&m_context); +#else + SHA1_init(&m_context); +#endif + } + + hasher::~hasher() + { +#if defined TORRENT_USE_LIBGCRYPT + gcry_md_close(m_context); +#endif + } + +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif +} diff --git a/src/hasher512.cpp b/src/hasher512.cpp new file mode 100644 index 0000000..07799d1 --- /dev/null +++ b/src/hasher512.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hasher512.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/openssl.hpp" + +namespace libtorrent { + +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + + hasher512::hasher512() + { +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_open(&m_context, GCRY_MD_SHA512, 0); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA512_Init(&m_context); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 +#elif defined TORRENT_USE_LIBCRYPTO + SHA512_Init(&m_context); +#else + SHA512_init(&m_context); +#endif + } + + hasher512::hasher512(span data) + : hasher512() + { + update(data); + } + +#ifdef TORRENT_USE_LIBGCRYPT + hasher512::hasher512(hasher512 const& h) + { + gcry_md_copy(&m_context, h.m_context); + } + + hasher512& hasher512::operator=(hasher512 const& h) & + { + if (this == &h) return *this; + gcry_md_close(m_context); + gcry_md_copy(&m_context, h.m_context); + return *this; + } +#else + hasher512::hasher512(hasher512 const&) = default; + hasher512& hasher512::operator=(hasher512 const&) & = default; +#endif + + hasher512& hasher512::update(span data) + { + TORRENT_ASSERT(data.size() > 0); +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_write(m_context, data.data(), static_cast(data.size())); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA512_Update(&m_context, reinterpret_cast(data.data()), CC_LONG(data.size())); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + m_context.update(data); +#elif defined TORRENT_USE_LIBCRYPTO + SHA512_Update(&m_context, reinterpret_cast(data.data()) + , static_cast(data.size())); +#else + SHA512_update(&m_context, reinterpret_cast(data.data()) + , static_cast(data.size())); +#endif + return *this; + } + + sha512_hash hasher512::final() + { + sha512_hash digest; +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_final(m_context); + digest.assign(reinterpret_cast(gcry_md_read(m_context, 0))); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA512_Final(reinterpret_cast(digest.data()), &m_context); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + m_context.get_hash(digest.data(), digest.size()); +#elif defined TORRENT_USE_LIBCRYPTO + SHA512_Final(reinterpret_cast(digest.data()), &m_context); +#else + SHA512_final(reinterpret_cast(digest.data()), &m_context); +#endif + return digest; + } + + void hasher512::reset() + { +#ifdef TORRENT_USE_LIBGCRYPT + gcry_md_reset(m_context); +#elif TORRENT_USE_COMMONCRYPTO + CC_SHA512_Init(&m_context); +#elif TORRENT_USE_CRYPTOAPI_SHA_512 + m_context.reset(); +#elif defined TORRENT_USE_LIBCRYPTO + SHA512_Init(&m_context); +#else + SHA512_init(&m_context); +#endif + } + +#if defined TORRENT_USE_LIBGCRYPT + hasher512::~hasher512() + { + gcry_md_close(m_context); + } +#else + hasher512::~hasher512() = default; +#endif + +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif +} diff --git a/src/hex.cpp b/src/hex.cpp new file mode 100644 index 0000000..5b2651a --- /dev/null +++ b/src/hex.cpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hex.hpp" + +namespace libtorrent { + + namespace aux { + + int hex_to_int(char in) + { + if (in >= '0' && in <= '9') return int(in) - '0'; + if (in >= 'A' && in <= 'F') return int(in) - 'A' + 10; + if (in >= 'a' && in <= 'f') return int(in) - 'a' + 10; + return -1; + } + + bool is_hex(span in) + { + for (char const c : in) + { + int const t = hex_to_int(c); + if (t == -1) return false; + } + return true; + } + + bool from_hex(span in, char* out) + { + for (auto i = in.begin(), end = in.end(); i != end; ++i, ++out) + { + int const t1 = aux::hex_to_int(*i); + if (t1 == -1) return false; + *out = char(t1 << 4); + ++i; + int const t2 = aux::hex_to_int(*i); + if (t2 == -1) return false; + *out |= t2 & 15; + } + return true; + } + + extern char const hex_chars[]; + + char const hex_chars[] = "0123456789abcdef"; + void to_hex(char const* in, int const len, char* out) + { + int idx = 0; + for (int i = 0; i < len; ++i) + { + out[idx++] = hex_chars[std::uint8_t(in[i]) >> 4]; + out[idx++] = hex_chars[std::uint8_t(in[i]) & 0xf]; + } + } + + std::string to_hex(span in) + { + std::string ret; + if (!in.empty()) + { + ret.resize(std::size_t(in.size() * 2)); + to_hex(in.data(), int(in.size()), &ret[0]); + } + return ret; + } + + void to_hex(span in, char* out) + { + to_hex(in.data(), int(in.size()), out); + out[in.size() * 2] = '\0'; + } + + } // aux namespace + +} diff --git a/src/http_connection.cpp b/src/http_connection.cpp new file mode 100644 index 0000000..d372ab6 --- /dev/null +++ b/src/http_connection.cpp @@ -0,0 +1,889 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/http_connection.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/aux_/instantiate_connection.hpp" +#include "libtorrent/gzip.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/socket_type.hpp" // for async_shutdown +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/time.hpp" + +#include +#include +#include +#include + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +using namespace std::placeholders; + +namespace libtorrent { + +http_connection::http_connection(io_service& ios + , resolver_interface& resolver + , http_handler const& handler + , bool bottled + , int max_bottled_buffer_size + , http_connect_handler const& ch + , http_filter_handler const& fh +#ifdef TORRENT_USE_OPENSSL + , ssl::context* ssl_ctx +#endif + ) + : m_next_ep(0) + , m_sock(ios) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx(ssl_ctx) +#endif +#if TORRENT_USE_I2P + , m_i2p_conn(nullptr) +#endif + , m_resolver(resolver) + , m_handler(handler) + , m_connect_handler(ch) + , m_filter_handler(fh) + , m_timer(ios) + , m_completion_timeout(seconds(5)) + , m_limiter_timer(ios) + , m_last_receive(aux::time_now()) + , m_start_time(aux::time_now()) + , m_read_pos(0) + , m_redirects(5) + , m_max_bottled_buffer_size(max_bottled_buffer_size) + , m_rate_limit(0) + , m_download_quota(0) + , m_priority(0) + , m_resolve_flags{} + , m_port(0) + , m_bottled(bottled) +{ + TORRENT_ASSERT(m_handler); +} + +http_connection::~http_connection() = default; + +void http_connection::get(std::string const& url, time_duration timeout, int prio + , aux::proxy_settings const* ps, int handle_redirects, std::string const& user_agent + , boost::optional
    const& bind_addr, resolver_flags const resolve_flags, std::string const& auth_ +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn +#endif + ) +{ + m_user_agent = user_agent; + m_resolve_flags = resolve_flags; + + std::string protocol; + std::string auth; + std::string hostname; + std::string path; + error_code ec; + int port; + + std::tie(protocol, auth, hostname, port, path) + = parse_url_components(url, ec); + + if (auth.empty()) auth = auth_; + + m_auth = auth; + + int default_port = protocol == "https" ? 443 : 80; + if (port == -1) port = default_port; + + // keep ourselves alive even if the callback function + // deletes this object + std::shared_ptr me(shared_from_this()); + + if (ec) + { + lt::get_io_service(m_timer).post(std::bind(&http_connection::callback + , me, ec, span{})); + return; + } + + if (protocol != "http" +#ifdef TORRENT_USE_OPENSSL + && protocol != "https" +#endif + ) + { + error_code err(errors::unsupported_url_protocol); + lt::get_io_service(m_timer).post(std::bind(&http_connection::callback + , me, err, span{})); + return; + } + + TORRENT_ASSERT(prio >= 0 && prio < 3); + + bool const ssl = (protocol == "https"); + + std::stringstream request; + + // exclude ssl here, because SSL assumes CONNECT support in the + // proxy and is handled at the lower layer + if (ps && (ps->type == settings_pack::http + || ps->type == settings_pack::http_pw) + && !ssl) + { + // if we're using an http proxy and not an ssl + // connection, just do a regular http proxy request + request << "GET " << url << " HTTP/1.1\r\n"; + if (ps->type == settings_pack::http_pw) + request << "Proxy-Authorization: Basic " << base64encode( + ps->username + ":" + ps->password) << "\r\n"; + + request << "Host: " << hostname; + if (port != default_port) request << ":" << port << "\r\n"; + else request << "\r\n"; + + hostname = ps->hostname; + port = ps->port; + } + else + { + request << "GET " << path << " HTTP/1.1\r\nHost: " << hostname; + if (port != default_port) request << ":" << port << "\r\n"; + else request << "\r\n"; + } + +// request << "Accept: */*\r\n"; + + if (!m_user_agent.empty()) + request << "User-Agent: " << m_user_agent << "\r\n"; + + if (m_bottled) + request << "Accept-Encoding: gzip\r\n"; + + if (!auth.empty()) + request << "Authorization: Basic " << base64encode(auth) << "\r\n"; + + request << "Connection: close\r\n\r\n"; + + m_sendbuffer.assign(request.str()); + m_url = url; + start(hostname, port, timeout, prio + , ps, ssl, handle_redirects, bind_addr, m_resolve_flags +#if TORRENT_USE_I2P + , i2p_conn +#endif + ); +} + +void http_connection::start(std::string const& hostname, int port + , time_duration timeout, int prio, aux::proxy_settings const* ps, bool ssl + , int handle_redirects + , boost::optional
    const& bind_addr + , resolver_flags const resolve_flags +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn +#endif + ) +{ + TORRENT_ASSERT(prio >= 0 && prio < 3); + + m_redirects = handle_redirects; + m_resolve_flags = resolve_flags; + if (ps) m_proxy = *ps; + + // keep ourselves alive even if the callback function + // deletes this object + std::shared_ptr me(shared_from_this()); + + m_completion_timeout = timeout; + error_code ec; + m_timer.expires_from_now(m_completion_timeout, ec); + ADD_OUTSTANDING_ASYNC("http_connection::on_timeout"); + m_timer.async_wait(std::bind(&http_connection::on_timeout + , std::weak_ptr(me), _1)); + m_called = false; + m_parser.reset(); + m_recvbuffer.clear(); + m_read_pos = 0; + m_priority = prio; + +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASSERT(!ssl || m_ssl_ctx != nullptr); +#endif + + if (ec) + { + lt::get_io_service(m_timer).post(std::bind(&http_connection::callback + , me, ec, span{})); + return; + } + + if (m_sock.is_open() && m_hostname == hostname && m_port == port + && m_ssl == ssl && m_bind_addr == bind_addr) + { + ADD_OUTSTANDING_ASYNC("http_connection::on_write"); + async_write(m_sock, boost::asio::buffer(m_sendbuffer) + , std::bind(&http_connection::on_write, me, _1)); + } + else + { + m_ssl = ssl; + m_bind_addr = bind_addr; + error_code err; + if (m_sock.is_open()) m_sock.close(err); + + aux::proxy_settings const* proxy = ps; + +#if TORRENT_USE_I2P + bool is_i2p = false; + char const* top_domain = strrchr(hostname.c_str(), '.'); + aux::proxy_settings i2p_proxy; + if (top_domain && top_domain == ".i2p"_sv && i2p_conn) + { + // this is an i2p name, we need to use the sam connection + // to do the name lookup + is_i2p = true; + m_i2p_conn = i2p_conn; + // quadruple the timeout for i2p destinations + // because i2p is sloooooow + m_completion_timeout *= 4; + +#if TORRENT_USE_I2P + if (i2p_conn->proxy().type != settings_pack::i2p_proxy) + { + lt::get_io_service(m_timer).post(std::bind(&http_connection::callback + , me, error_code(errors::no_i2p_router), span{})); + return; + } +#endif + + i2p_proxy = i2p_conn->proxy(); + proxy = &i2p_proxy; + } +#endif + + // in this case, the upper layer is assumed to have taken + // care of the proxying already. Don't instantiate the socket + // with this proxy + if (proxy && (proxy->type == settings_pack::http + || proxy->type == settings_pack::http_pw) + && !ssl) + { + proxy = nullptr; + } + aux::proxy_settings null_proxy; + + void* userdata = nullptr; +#ifdef TORRENT_USE_OPENSSL + if (m_ssl) + { + TORRENT_ASSERT(m_ssl_ctx != nullptr); + userdata = m_ssl_ctx; + } +#endif + // assume this is not a tracker connection. Tracker connections that + // shouldn't be subject to the proxy should pass in nullptr as the proxy + // pointer. + instantiate_connection(lt::get_io_service(m_timer) + , proxy ? *proxy : null_proxy, m_sock, userdata, nullptr, false, false); + + if (m_bind_addr) + { + m_sock.open(m_bind_addr->is_v4() ? tcp::v4() : tcp::v6(), ec); + m_sock.bind(tcp::endpoint(*m_bind_addr, 0), ec); + if (ec) + { + lt::get_io_service(m_timer).post(std::bind(&http_connection::callback + , me, ec, span{})); + return; + } + } + + setup_ssl_hostname(m_sock, hostname, ec); + if (ec) + { + lt::get_io_service(m_timer).post(std::bind(&http_connection::callback + , me, ec, span{})); + return; + } + + m_endpoints.clear(); + m_next_ep = 0; + +#if TORRENT_USE_I2P + if (is_i2p) + { + if (hostname.length() < 516) // Base64 encoded destination with optional .i2p + { + ADD_OUTSTANDING_ASYNC("http_connection::on_i2p_resolve"); + i2p_conn->async_name_lookup(hostname.c_str(), std::bind(&http_connection::on_i2p_resolve + , me, _1, _2)); + } + else + connect_i2p_tracker(hostname.c_str()); + } + else +#endif + m_hostname = hostname; + if (ps && ps->proxy_hostnames + && (ps->type == settings_pack::socks5 + || ps->type == settings_pack::socks5_pw)) + { + m_port = std::uint16_t(port); + m_endpoints.emplace_back(address(), m_port); + connect(); + } + else + { + m_resolving_host = true; + ADD_OUTSTANDING_ASYNC("http_connection::on_resolve"); + m_resolver.async_resolve(hostname, m_resolve_flags + , std::bind(&http_connection::on_resolve + , me, _1, _2)); + } + m_port = std::uint16_t(port); + } +} + +void http_connection::on_timeout(std::weak_ptr p + , error_code const& e) +{ + COMPLETE_ASYNC("http_connection::on_timeout"); + std::shared_ptr c = p.lock(); + if (!c) return; + + if (e == boost::asio::error::operation_aborted) return; + + if (c->m_abort) return; + + time_point const now = clock_type::now(); + + // be forgiving of timeout while we're still resolving the hostname + // it may be delayed because we're queued up behind another slow lookup + if (c->m_start_time + (c->m_completion_timeout * (int(c->m_resolving_host) + 1)) <= now) + { + // the connection timed out. If we have more endpoints to try, just + // close this connection. The on_connect handler will try the next + // endpoint in the list. + if (c->m_next_ep < int(c->m_endpoints.size())) + { + error_code ec; + c->m_sock.close(ec); + if (!c->m_connecting) c->connect(); + c->m_last_receive = now; + c->m_start_time = c->m_last_receive; + } + else + { + // the socket may have an outstanding operation, that keeps the + // http_connection object alive. We want to cancel all that. + error_code ec; + c->m_sock.close(ec); + c->callback(lt::errors::timed_out); + return; + } + } + + ADD_OUTSTANDING_ASYNC("http_connection::on_timeout"); + error_code ec; + c->m_timer.expires_at(c->m_start_time + c->m_completion_timeout, ec); + c->m_timer.async_wait(std::bind(&http_connection::on_timeout, p, _1)); +} + +void http_connection::close(bool force) +{ + if (m_abort) return; + + error_code ec; + if (force) + { + m_sock.close(ec); + m_timer.cancel(ec); + } + else + { + async_shutdown(m_sock, shared_from_this()); + m_timer.cancel(ec); + } + + m_limiter_timer.cancel(ec); + + m_hostname.clear(); + m_port = 0; + m_handler = nullptr; + m_abort = true; +} + +#if TORRENT_USE_I2P +void http_connection::connect_i2p_tracker(char const* destination) +{ + TORRENT_ASSERT(m_sock.get()); +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASSERT(m_ssl == false); +#endif + m_sock.get()->set_destination(destination); + m_sock.get()->set_command(i2p_stream::cmd_connect); + m_sock.get()->set_session_id(m_i2p_conn->session_id()); + ADD_OUTSTANDING_ASYNC("http_connection::on_connect"); + TORRENT_ASSERT(!m_connecting); + m_connecting = true; + m_sock.async_connect(tcp::endpoint(), std::bind(&http_connection::on_connect + , shared_from_this(), _1)); +} + +void http_connection::on_i2p_resolve(error_code const& e, char const* destination) +{ + COMPLETE_ASYNC("http_connection::on_i2p_resolve"); + if (e) + { + callback(e); + return; + } + connect_i2p_tracker(destination); +} +#endif + +void http_connection::on_resolve(error_code const& e + , std::vector
    const& addresses) +{ + COMPLETE_ASYNC("http_connection::on_resolve"); + m_resolving_host = false; + if (e) + { + callback(e); + return; + } + TORRENT_ASSERT(!addresses.empty()); + + // reset timeout + m_start_time = clock_type::now(); + + for (auto const& addr : addresses) + m_endpoints.emplace_back(addr, m_port); + + if (m_filter_handler) m_filter_handler(*this, m_endpoints); + if (m_endpoints.empty()) + { + close(); + return; + } + + aux::random_shuffle(m_endpoints); + + // if we have been told to bind to a particular address + // only connect to addresses of the same family + if (m_bind_addr) + { + auto const new_end = std::remove_if(m_endpoints.begin(), m_endpoints.end() + , [&](tcp::endpoint const& ep) { return is_v4(ep) != m_bind_addr->is_v4(); }); + + m_endpoints.erase(new_end, m_endpoints.end()); + if (m_endpoints.empty()) + { + callback(error_code(boost::system::errc::address_family_not_supported, generic_category())); + close(); + return; + } + } + + connect(); +} + +void http_connection::connect() +{ + TORRENT_ASSERT(m_next_ep < int(m_endpoints.size())); + + std::shared_ptr me(shared_from_this()); + + if (m_proxy.proxy_hostnames + && (m_proxy.type == settings_pack::socks5 + || m_proxy.type == settings_pack::socks5_pw)) + { + // test to see if m_hostname really just is an IP (and not a hostname). If it + // is, ec will be represent "success". If so, don't set it as the socks5 + // hostname, just connect to the IP + error_code ec; + address adr = make_address(m_hostname, ec); + + if (ec) + { + // we're using a socks proxy and we're resolving + // hostnames through it +#ifdef TORRENT_USE_OPENSSL + if (m_ssl) + { + TORRENT_ASSERT(m_sock.get>()); + m_sock.get>()->next_layer().set_dst_name(m_hostname); + } + else +#endif + { + TORRENT_ASSERT(m_sock.get()); + m_sock.get()->set_dst_name(m_hostname); + } + } + else + { + m_endpoints[0].address(adr); + } + } + + TORRENT_ASSERT(m_next_ep < int(m_endpoints.size())); + if (m_next_ep >= int(m_endpoints.size())) return; + + tcp::endpoint target_address = m_endpoints[m_next_ep]; + ++m_next_ep; + + ADD_OUTSTANDING_ASYNC("http_connection::on_connect"); + TORRENT_ASSERT(!m_connecting); + m_connecting = true; + m_sock.async_connect(target_address, std::bind(&http_connection::on_connect + , me, _1)); +} + +void http_connection::on_connect(error_code const& e) +{ + COMPLETE_ASYNC("http_connection::on_connect"); + TORRENT_ASSERT(m_connecting); + m_connecting = false; + + m_last_receive = clock_type::now(); + m_start_time = m_last_receive; + if (!e) + { + if (m_connect_handler) m_connect_handler(*this); + ADD_OUTSTANDING_ASYNC("http_connection::on_write"); + async_write(m_sock, boost::asio::buffer(m_sendbuffer) + , std::bind(&http_connection::on_write, shared_from_this(), _1)); + } + else if (m_next_ep < int(m_endpoints.size()) && !m_abort) + { + // The connection failed. Try the next endpoint in the list. + error_code ec; + m_sock.close(ec); + connect(); + } + else + { + error_code ec; + m_sock.close(ec); + callback(e); + } +} + +void http_connection::callback(error_code e, span data) +{ + if (m_bottled && m_called) return; + + std::vector buf; + if (!data.empty() && m_bottled && m_parser.header_finished()) + { + data = m_parser.collapse_chunk_headers(data); + + std::string const& encoding = m_parser.header("content-encoding"); + if (encoding == "gzip" || encoding == "x-gzip") + { + error_code ec; + inflate_gzip(data, buf, m_max_bottled_buffer_size, ec); + + if (ec) + { + if (m_handler) m_handler(ec, m_parser, data, *this); + return; + } + data = buf; + } + + // if we completed the whole response, no need + // to tell the user that the connection was closed by + // the server or by us. Just clear any error + if (m_parser.finished()) e.clear(); + } + m_called = true; + error_code ec; + m_timer.cancel(ec); + if (m_handler) m_handler(e, m_parser, data, *this); +} + +void http_connection::on_write(error_code const& e) +{ + COMPLETE_ASYNC("http_connection::on_write"); + + if (e == boost::asio::error::operation_aborted) return; + + if (e) + { + callback(e); + return; + } + + if (m_abort) return; + + std::string().swap(m_sendbuffer); + m_recvbuffer.resize(4096); + + int amount_to_read = int(m_recvbuffer.size()) - m_read_pos; + if (m_rate_limit > 0 && amount_to_read > m_download_quota) + { + amount_to_read = m_download_quota; + if (m_download_quota == 0) + { + if (!m_limiter_timer_active) + { + ADD_OUTSTANDING_ASYNC("http_connection::on_assign_bandwidth"); + on_assign_bandwidth(error_code()); + } + return; + } + } + ADD_OUTSTANDING_ASYNC("http_connection::on_read"); + m_sock.async_read_some(boost::asio::buffer(m_recvbuffer.data() + m_read_pos + , std::size_t(amount_to_read)) + , std::bind(&http_connection::on_read + , shared_from_this(), _1, _2)); +} + +void http_connection::on_read(error_code const& e + , std::size_t bytes_transferred) +{ + COMPLETE_ASYNC("http_connection::on_read"); + + if (m_rate_limit) + { + m_download_quota -= int(bytes_transferred); + TORRENT_ASSERT(m_download_quota >= 0); + } + + if (e == boost::asio::error::operation_aborted) return; + + if (m_abort) return; + + // keep ourselves alive even if the callback function + // deletes this object + std::shared_ptr me(shared_from_this()); + + // when using the asio SSL wrapper, it seems like + // we get the shut_down error instead of EOF + if (e == boost::asio::error::eof || e == boost::asio::error::shut_down) + { + error_code ec = boost::asio::error::eof; + TORRENT_ASSERT(bytes_transferred == 0); + span body; + if (m_bottled && m_parser.header_finished()) + { + body = span(m_recvbuffer.data() + m_parser.body_start() + , m_parser.get_body().size()); + } + callback(ec, body); + return; + } + + if (e) + { + TORRENT_ASSERT(bytes_transferred == 0); + callback(e); + return; + } + + m_read_pos += int(bytes_transferred); + TORRENT_ASSERT(m_read_pos <= int(m_recvbuffer.size())); + + if (m_bottled || !m_parser.header_finished()) + { + span rcv_buf(m_recvbuffer); + bool error = false; + m_parser.incoming(rcv_buf.first(m_read_pos), error); + if (error) + { + // HTTP parse error + error_code ec = errors::http_parse_error; + callback(ec); + return; + } + + // having a nonempty path means we should handle redirects + if (m_redirects && m_parser.header_finished()) + { + int code = m_parser.status_code(); + + if (is_redirect(code)) + { + // attempt a redirect + std::string const& location = m_parser.header("location"); + if (location.empty()) + { + // missing location header + callback(error_code(errors::http_missing_location)); + return; + } + + error_code ec; + // it would be nice to gracefully shut down SSL here + // but then we'd have to do all the reconnect logic + // in its handler. For now, just kill the connection. +// async_shutdown(m_sock, me); + m_sock.close(ec); + + std::string url = resolve_redirect_location(m_url, location); + get(url, m_completion_timeout, m_priority, &m_proxy, m_redirects - 1 + , m_user_agent, m_bind_addr, m_resolve_flags, m_auth +#if TORRENT_USE_I2P + , m_i2p_conn +#endif + ); + return; + } + + m_redirects = 0; + } + + if (!m_bottled && m_parser.header_finished()) + { + if (m_read_pos > m_parser.body_start()) + { + callback(e, span(m_recvbuffer) + .first(m_read_pos) + .subspan(m_parser.body_start())); + } + m_read_pos = 0; + m_last_receive = clock_type::now(); + } + else if (m_bottled && m_parser.finished()) + { + error_code ec; + m_timer.cancel(ec); + callback(e, span(m_recvbuffer) + .first(m_read_pos) + .subspan(m_parser.body_start())); + } + } + else + { + TORRENT_ASSERT(!m_bottled); + callback(e, span(m_recvbuffer).first(m_read_pos)); + m_read_pos = 0; + m_last_receive = clock_type::now(); + } + + // if we've hit the limit, double the buffer size + if (int(m_recvbuffer.size()) == m_read_pos) + m_recvbuffer.resize(std::min(m_read_pos * 2, m_max_bottled_buffer_size)); + + if (m_read_pos == m_max_bottled_buffer_size) + { + // if we've reached the size limit, terminate the connection and + // report the error + callback(error_code(boost::system::errc::file_too_large, generic_category())); + return; + } + int amount_to_read = int(m_recvbuffer.size()) - m_read_pos; + if (m_rate_limit > 0 && amount_to_read > m_download_quota) + { + amount_to_read = m_download_quota; + if (m_download_quota == 0) + { + if (!m_limiter_timer_active) + { + ADD_OUTSTANDING_ASYNC("http_connection::on_assign_bandwidth"); + on_assign_bandwidth(error_code()); + } + return; + } + } + ADD_OUTSTANDING_ASYNC("http_connection::on_read"); + m_sock.async_read_some(boost::asio::buffer(m_recvbuffer.data() + m_read_pos + , std::size_t(amount_to_read)) + , std::bind(&http_connection::on_read + , me, _1, _2)); +} + +void http_connection::on_assign_bandwidth(error_code const& e) +{ + COMPLETE_ASYNC("http_connection::on_assign_bandwidth"); + if ((e == boost::asio::error::operation_aborted + && m_limiter_timer_active) + || !m_sock.is_open()) + { + callback(boost::asio::error::eof); + return; + } + m_limiter_timer_active = false; + if (e) return; + + if (m_abort) return; + + if (m_download_quota > 0) return; + + m_download_quota = m_rate_limit / 4; + + int amount_to_read = int(m_recvbuffer.size()) - m_read_pos; + if (amount_to_read > m_download_quota) + amount_to_read = m_download_quota; + + if (!m_sock.is_open()) return; + + ADD_OUTSTANDING_ASYNC("http_connection::on_read"); + m_sock.async_read_some(boost::asio::buffer(m_recvbuffer.data() + m_read_pos + , std::size_t(amount_to_read)) + , std::bind(&http_connection::on_read + , shared_from_this(), _1, _2)); + + error_code ec; + m_limiter_timer_active = true; + m_limiter_timer.expires_from_now(milliseconds(250), ec); + ADD_OUTSTANDING_ASYNC("http_connection::on_assign_bandwidth"); + m_limiter_timer.async_wait(std::bind(&http_connection::on_assign_bandwidth + , shared_from_this(), _1)); +} + +void http_connection::rate_limit(int limit) +{ + if (!m_sock.is_open()) return; + + if (!m_limiter_timer_active) + { + error_code ec; + m_limiter_timer_active = true; + m_limiter_timer.expires_from_now(milliseconds(250), ec); + ADD_OUTSTANDING_ASYNC("http_connection::on_assign_bandwidth"); + m_limiter_timer.async_wait(std::bind(&http_connection::on_assign_bandwidth + , shared_from_this(), _1)); + } + m_rate_limit = limit; +} + +} diff --git a/src/http_parser.cpp b/src/http_parser.cpp new file mode 100644 index 0000000..8f348b0 --- /dev/null +++ b/src/http_parser.cpp @@ -0,0 +1,632 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/hex.hpp" // for hex_to_int +#include "libtorrent/assert.hpp" +#include "libtorrent/parse_url.hpp" // for parse_url_components +#include "libtorrent/string_util.hpp" // for ensure_trailing_slash, to_lower +#include "libtorrent/aux_/escape_string.hpp" // for read_until +#include "libtorrent/time.hpp" // for seconds32 +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { + + bool is_ok_status(int http_status) + { + return http_status == 206 // partial content + || http_status == 200 // OK + || (http_status >= 300 // redirect + && http_status < 400); + } + + bool is_redirect(int http_status) + { + return http_status >= 300 + && http_status < 400; + } + + std::string resolve_redirect_location(std::string referrer + , std::string location) + { + if (location.empty()) return referrer; + + error_code ec; + using std::ignore; + std::tie(ignore, ignore, ignore, ignore, ignore) + = parse_url_components(location, ec); + + // if location is a full URL, just return it + if (!ec) return location; + + // otherwise it's likely to be just the path, or a relative path + std::string url = referrer; + + if (location[0] == '/') + { + // it's an absolute path. replace the path component of + // referrer with location. + + // first skip the url scheme of the referer + std::size_t i = url.find("://"); + + // if the referrer doesn't appear to have a proper URL scheme + // just return the location verbatim (and probably fail) + if (i == std::string::npos) + return location; + + // then skip the hostname and port, it's fine for this to fail, in + // case the referrer doesn't have a path component, it's just the + // url-scheme and hostname, in which case we just append the location + i = url.find_first_of('/', i + 3); + if (i != std::string::npos) + url.resize(i); + + url += location; + } + else + { + // some web servers send out relative paths + // in the location header. + + // remove the leaf filename + // first skip the url scheme of the referer + std::size_t start = url.find("://"); + + // the referrer is not a valid full URL + if (start == std::string::npos) + return location; + + std::size_t end = url.find_last_of('/'); + // if the / we find is part of the scheme, there is no / in the path + // component or hostname. + if (end <= start + 2) end = std::string::npos; + + // if this fails, the referrer is just url-scheme and hostname. We can + // just append the location to it. + if (end != std::string::npos) + url.resize(end); + + // however, we may still need to insert a '/' in case neither side + // has one. We know the location doesn't start with a / already. + // so, if the referrer doesn't end with one, add it. + ensure_trailing_slash(url); + url += location; + } + return url; + } + + std::string const& http_parser::header(string_view const key) const + { + static std::string const empty; + // TODO: remove to_string() if we're in C++14 + auto const i = m_header.find(key.to_string()); + if (i == m_header.end()) return empty; + return i->second; + } + + boost::optional http_parser::header_duration(string_view const key) const + { + // TODO: remove to_string() if we're in C++14 + auto const i = m_header.find(key.to_string()); + if (i == m_header.end()) return boost::none; + auto const val = std::atol(i->second.c_str()); + if (val <= 0) return boost::none; + return seconds32(val); + } + + http_parser::~http_parser() = default; + + http_parser::http_parser(int const flags) : m_flags(flags) {} + + std::tuple http_parser::incoming( + span recv_buffer, bool& error) + { + TORRENT_ASSERT(recv_buffer.size() >= m_recv_buffer.size()); + std::tuple ret(0, 0); + std::ptrdiff_t start_pos = m_recv_buffer.size(); + + // early exit if there's nothing new in the receive buffer + if (start_pos == recv_buffer.size()) return ret; + m_recv_buffer = recv_buffer; + + if (m_state == error_state) + { + error = true; + return ret; + } + + char const* pos = recv_buffer.data() + m_recv_pos; + +restart_response: + + if (m_state == read_status) + { + TORRENT_ASSERT(!m_finished); + TORRENT_ASSERT(pos <= recv_buffer.end()); + char const* newline = std::find(pos, recv_buffer.end(), '\n'); + // if we don't have a full line yet, wait. + if (newline == recv_buffer.end()) + { + std::get<1>(ret) += int(m_recv_buffer.size() - start_pos); + return ret; + } + + if (newline == pos) + { + m_state = error_state; + error = true; + return ret; + } + + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + + char const* line = pos; + ++newline; + TORRENT_ASSERT(newline >= pos); + int incoming = int(newline - pos); + m_recv_pos += incoming; + std::get<1>(ret) += int(newline - (m_recv_buffer.data() + start_pos)); + pos = newline; + + m_protocol = read_until(line, ' ', line_end); + if (m_protocol.substr(0, 5) == "HTTP/") + { + m_status_code = atoi(read_until(line, ' ', line_end).c_str()); + m_server_message = read_until(line, '\r', line_end); + + // HTTP 1.0 always closes the connection after + // each request + if (m_protocol == "HTTP/1.0") m_connection_close = true; + } + else + { + m_method = m_protocol; + std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower); + // the content length is assumed to be 0 for requests + m_content_length = 0; + m_protocol.clear(); + m_path = read_until(line, ' ', line_end); + m_protocol = read_until(line, ' ', line_end); + m_status_code = 0; + } + m_state = read_header; + start_pos = pos - recv_buffer.data(); + } + + if (m_state == read_header) + { + TORRENT_ASSERT(!m_finished); + TORRENT_ASSERT(pos <= recv_buffer.end()); + char const* newline = std::find(pos, recv_buffer.end(), '\n'); + std::string line; + + while (newline != recv_buffer.end() && m_state == read_header) + { + // if the LF character is preceded by a CR + // character, don't copy it into the line string. + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + line.assign(pos, line_end); + ++newline; + m_recv_pos += newline - pos; + pos = newline; + + std::string::size_type separator = line.find(':'); + if (separator == std::string::npos) + { + if (m_status_code == 100) + { + // for 100 Continue, we need to read another response header + // before reading the body + m_state = read_status; + goto restart_response; + } + // this means we got a blank line, + // the header is finished and the body + // starts. + m_state = read_body; + // if this is a request (not a response) + // we're done once we reach the end of the headers +// if (!m_method.empty()) m_finished = true; + // the HTTP header should always be < 2 GB + TORRENT_ASSERT(m_recv_pos < std::numeric_limits::max()); + m_body_start_pos = int(m_recv_pos); + break; + } + + std::string name = line.substr(0, separator); + std::transform(name.begin(), name.end(), name.begin(), &to_lower); + ++separator; + // skip whitespace + while (separator < line.size() + && (line[separator] == ' ' || line[separator] == '\t')) + ++separator; + std::string value = line.substr(separator, std::string::npos); + m_header.insert(std::make_pair(name, value)); + + if (name == "content-length") + { + m_content_length = std::strtoll(value.c_str(), nullptr, 10); + if (m_content_length < 0 + || m_content_length == std::numeric_limits::max()) + { + m_state = error_state; + error = true; + return ret; + } + } + else if (name == "connection") + { + m_connection_close = string_begins_no_case("close", value.c_str()); + } + else if (name == "content-range") + { + bool success = true; + char const* ptr = value.c_str(); + + // apparently some web servers do not send the "bytes" + // in their content-range. Don't treat it as an error + // if we can't find it, just assume the byte counters + // start immediately + if (string_begins_no_case("bytes ", ptr)) ptr += 6; + char* end; + m_range_start = std::strtoll(ptr, &end, 10); + if (m_range_start < 0 + || m_range_start == std::numeric_limits::max()) + { + m_state = error_state; + error = true; + return ret; + } + if (end == ptr) success = false; + else if (*end != '-') success = false; + else + { + ptr = end + 1; + m_range_end = std::strtoll(ptr, &end, 10); + if (m_range_end < 0 + || m_range_end == std::numeric_limits::max()) + { + m_state = error_state; + error = true; + return ret; + } + if (end == ptr) success = false; + } + + if (!success || m_range_end < m_range_start) + { + m_state = error_state; + error = true; + return ret; + } + // the http range is inclusive + m_content_length = m_range_end - m_range_start + 1; + } + else if (name == "transfer-encoding") + { + m_chunked_encoding = string_begins_no_case("chunked", value.c_str()); + } + + TORRENT_ASSERT(m_recv_pos <= int(recv_buffer.size())); + TORRENT_ASSERT(pos <= recv_buffer.end()); + newline = std::find(pos, recv_buffer.end(), '\n'); + } + std::get<1>(ret) += int(newline - (m_recv_buffer.data() + start_pos)); + } + + if (m_state == read_body) + { + int incoming = int(recv_buffer.end() - pos); + + if (m_chunked_encoding && (m_flags & dont_parse_chunks) == 0) + { + if (m_cur_chunk_end == -1) + m_cur_chunk_end = m_body_start_pos; + + while (m_cur_chunk_end <= m_recv_pos + incoming && !m_finished && incoming > 0) + { + std::int64_t payload = m_cur_chunk_end - m_recv_pos; + if (payload > 0) + { + TORRENT_ASSERT(payload < std::numeric_limits::max()); + m_recv_pos += payload; + std::get<0>(ret) += int(payload); + incoming -= int(payload); + } + auto const buf = span(recv_buffer) + .subspan(aux::numeric_cast(m_cur_chunk_end)); + std::int64_t chunk_size; + int header_size; + if (parse_chunk_header(buf, &chunk_size, &header_size)) + { + if (chunk_size < 0 + || chunk_size > std::numeric_limits::max() - m_cur_chunk_end - header_size) + { + m_state = error_state; + error = true; + return ret; + } + if (chunk_size > 0) + { + std::pair chunk_range(m_cur_chunk_end + header_size + , m_cur_chunk_end + header_size + chunk_size); + m_chunked_ranges.push_back(chunk_range); + } + m_cur_chunk_end += header_size + chunk_size; + if (chunk_size == 0) + { + m_finished = true; + } + header_size -= m_partial_chunk_header; + m_partial_chunk_header = 0; +// std::fprintf(stderr, "parse_chunk_header(%d, -> %" PRId64 ", -> %d) -> %d\n" +// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %" PRId64 "\n" +// " content-length = %d\n" +// , int(buf.size()), chunk_size, header_size, 1, incoming, int(m_recv_pos) +// , m_cur_chunk_end, int(m_content_length)); + } + else + { + m_partial_chunk_header += incoming; + header_size = incoming; + +// std::fprintf(stderr, "parse_chunk_header(%d, -> %" PRId64 ", -> %d) -> %d\n" +// " incoming = %d\n m_recv_pos = %d\n m_cur_chunk_end = %" PRId64 "\n" +// " content-length = %d\n" +// , int(buf.size()), chunk_size, header_size, 0, incoming, int(m_recv_pos) +// , m_cur_chunk_end, int(m_content_length)); + } + m_chunk_header_size += header_size; + m_recv_pos += header_size; + std::get<1>(ret) += header_size; + incoming -= header_size; + } + if (incoming > 0) + { + m_recv_pos += incoming; + std::get<0>(ret) += incoming; +// incoming = 0; + } + } + else + { + std::int64_t payload_received = m_recv_pos - m_body_start_pos + incoming; + if (payload_received > m_content_length + && m_content_length >= 0) + { + TORRENT_ASSERT(m_content_length - m_recv_pos + m_body_start_pos + < std::numeric_limits::max()); + incoming = int(m_content_length - m_recv_pos + m_body_start_pos); + } + + TORRENT_ASSERT(incoming >= 0); + m_recv_pos += incoming; + std::get<0>(ret) += incoming; + } + + if (m_content_length >= 0 + && !m_chunked_encoding + && m_recv_pos - m_body_start_pos >= m_content_length) + { + m_finished = true; + } + } + return ret; + } + + // this function signals error by assigning a negative value to "chunk_size" + // the return value indicates whether enough data is available in "buf" to + // completely parse the chunk header. Returning false means we need more data + bool http_parser::parse_chunk_header(span buf + , std::int64_t* chunk_size, int* header_size) + { + char const* pos = buf.data(); + + // ignore one optional new-line. This is since each chunk + // is terminated by a newline. we're likely to see one + // before the actual header. + + if (pos < buf.end() && pos[0] == '\r') ++pos; + if (pos < buf.end() && pos[0] == '\n') ++pos; + if (pos == buf.end()) return false; + + TORRENT_ASSERT(pos <= buf.end()); + char const* newline = std::find(pos, buf.end(), '\n'); + if (newline == buf.end()) return false; + ++newline; + + // the chunk header is a single line, a hex length of the + // chunk followed by an optional semi-colon with a comment + // in case the length is 0, the stream is terminated and + // there are extra tail headers, which is terminated by an + // empty line + + *header_size = int(newline - buf.data()); + + // first, read the chunk length + std::int64_t size = 0; + for (char const* i = pos; i != newline; ++i) + { + if (*i == '\r') continue; + if (*i == '\n') continue; + if (*i == ';') break; + int const digit = aux::hex_to_int(*i); + if (digit < 0) + { + *chunk_size = -1; + return true; + } + if (size >= std::numeric_limits::max() / 16) + { + *chunk_size = -1; + return true; + } + size *= 16; + size += digit; + } + *chunk_size = size; + + if (*chunk_size != 0) + { + // the newline is at least 1 byte, and the length-prefix is at least 1 + // byte + TORRENT_ASSERT(newline - buf.data() >= 2); + return true; + } + + // this is the terminator of the stream. Also read headers + std::map tail_headers; + pos = newline; + newline = std::find(pos, buf.end(), '\n'); + + std::string line; + while (newline != buf.end()) + { + // if the LF character is preceded by a CR + // character, don't copy it into the line string. + char const* line_end = newline; + if (pos != line_end && *(line_end - 1) == '\r') --line_end; + line.assign(pos, line_end); + ++newline; + pos = newline; + + std::string::size_type separator = line.find(':'); + if (separator == std::string::npos) + { + // this means we got a blank line, + // the header is finished and the body + // starts. + *header_size = int(newline - buf.data()); + + // the newline alone is two bytes + TORRENT_ASSERT(newline - buf.data() > 2); + + // we were successful in parsing the headers. + // add them to the headers in the parser + for (auto const& p : tail_headers) + m_header.insert(p); + + return true; + } + + std::string name = line.substr(0, separator); + std::transform(name.begin(), name.end(), name.begin(), &to_lower); + ++separator; + // skip whitespace + while (separator < line.size() + && (line[separator] == ' ' || line[separator] == '\t')) + ++separator; + std::string value = line.substr(separator, std::string::npos); + tail_headers.insert(std::make_pair(name, value)); +// std::fprintf(stderr, "tail_header: %s: %s\n", name.c_str(), value.c_str()); + + newline = std::find(pos, buf.end(), '\n'); + } + return false; + } + + span http_parser::get_body() const + { + TORRENT_ASSERT(m_state == read_body); + std::int64_t const received = m_recv_pos - m_body_start_pos; + + std::int64_t const body_length = m_chunked_encoding && !m_chunked_ranges.empty() + ? std::min(m_chunked_ranges.back().second - m_body_start_pos, received) + : m_content_length < 0 ? received : std::min(m_content_length, received); + + return m_recv_buffer.subspan(m_body_start_pos, aux::numeric_cast(body_length)); + } + + void http_parser::reset() + { + m_method.clear(); + m_recv_pos = 0; + m_body_start_pos = 0; + m_status_code = -1; + m_content_length = -1; + m_range_start = -1; + m_range_end = -1; + m_finished = false; + m_state = read_status; + m_recv_buffer = span(); + m_header.clear(); + m_chunked_encoding = false; + m_chunked_ranges.clear(); + m_cur_chunk_end = -1; + m_chunk_header_size = 0; + m_partial_chunk_header = 0; + } + + span http_parser::collapse_chunk_headers(span buffer) const + { + if (!chunked_encoding()) return buffer; + + // go through all chunks and compact them + // since we're bottled, and the buffer is our after all + // it's OK to mutate it + char* write_ptr = buffer.data(); + // the offsets in the array are from the start of the + // buffer, not start of the body, so subtract the size + // of the HTTP header from them + int const offset = body_start(); + for (auto const& i : chunks()) + { + auto const chunk_start = i.first; + auto const chunk_end = i.second; + TORRENT_ASSERT(i.second - i.first < std::numeric_limits::max()); + TORRENT_ASSERT(chunk_end - offset <= buffer.size()); + span chunk = buffer.subspan( + aux::numeric_cast(chunk_start - offset) + , aux::numeric_cast(chunk_end - chunk_start)); +#if defined __GNUC__ && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif + std::memmove(write_ptr, chunk.data(), std::size_t(chunk.size())); +#if defined __GNUC__ && __GNUC__ >= 7 +#pragma GCC diagnostic pop +#endif + write_ptr += chunk.size(); + } + return buffer.first(write_ptr - buffer.data()); + } +} diff --git a/src/http_seed_connection.cpp b/src/http_seed_connection.cpp new file mode 100644 index 0000000..3aad000 --- /dev/null +++ b/src/http_seed_connection.cpp @@ -0,0 +1,468 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // for PRId64 et.al. + +#include "libtorrent/config.hpp" +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/hex.hpp" // for is_hex +#include "libtorrent/optional.hpp" + +namespace libtorrent { + + http_seed_connection::http_seed_connection(peer_connection_args const& pack + , web_seed_t& web) + : web_connection_base(pack, web) + , m_url(web.url) + , m_web(&web) + , m_response_left(0) + , m_chunk_pos(0) + , m_partial_chunk_header(0) + { + INVARIANT_CHECK; + + if (!m_settings.get_bool(settings_pack::report_web_seed_downloads)) + ignore_stats(true); + + std::shared_ptr tor = pack.tor.lock(); + TORRENT_ASSERT(tor); + int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size(); + + // multiply with the blocks per piece since that many requests are + // merged into one http request + max_out_request_queue(m_settings.get_int(settings_pack::urlseed_pipeline_size) + * blocks_per_piece); + + prefer_contiguous_blocks(blocks_per_piece); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONNECT", "http_seed_connection"); +#endif + } + + void http_seed_connection::on_connected() + { + // this is always a seed + incoming_have_all(); + web_connection_base::on_connected(); + } + + void http_seed_connection::disconnect(error_code const& ec + , operation_t const op, disconnect_severity_t const error) + { + if (is_disconnecting()) return; + + if (op == operation_t::connect && m_web && !m_web->endpoints.empty()) + { + // we failed to connect to this IP. remove it so that the next attempt + // uses the next IP in the list. + m_web->endpoints.erase(m_web->endpoints.begin()); + } + + std::shared_ptr t = associated_torrent().lock(); + peer_connection::disconnect(ec, op, error); + if (t) t->disconnect_web_seed(this); + } + + piece_block_progress http_seed_connection::downloading_piece_progress() const + { + if (m_requests.empty()) return {}; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + piece_block_progress ret; + + peer_request const& pr = m_requests.front(); + ret.piece_index = pr.piece; + if (!m_parser.header_finished()) + { + ret.bytes_downloaded = 0; + } + else + { + int const receive_buffer_size = int(m_recv_buffer.get().size()) - m_parser.body_start(); + // this is an approximation. in chunked encoding mode the chunk headers + // should really be subtracted from the receive_buffer_size + ret.bytes_downloaded = std::max(0, t->block_size() - receive_buffer_size); + } + // this is used to make sure that the block_index stays within + // bounds. If the entire piece is downloaded, the block_index + // would otherwise point to one past the end + int const correction = ret.bytes_downloaded ? -1 : 0; + ret.block_index = (pr.start + ret.bytes_downloaded + correction) / t->block_size(); + ret.full_block_bytes = t->block_size(); + piece_index_t const last_piece = t->torrent_file().last_piece(); + if (ret.piece_index == last_piece && ret.block_index + == t->torrent_file().piece_size(last_piece) / t->block_size()) + ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size(); + return ret; + } + + void http_seed_connection::write_request(peer_request const& r) + { + INVARIANT_CHECK; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + TORRENT_ASSERT(t->valid_metadata()); + // http_seeds don't support requesting more than one piece + // at a time + TORRENT_ASSERT(r.length <= t->torrent_file().piece_size(r.piece)); + + std::string request; + request.reserve(400); + + int size = r.length; + const int bs = t->block_size(); + const int piece_size = t->torrent_file().piece_length(); + peer_request pr; + while (size > 0) + { + int request_offset = r.start + r.length - size; + pr.start = request_offset % piece_size; + pr.length = std::min(bs, size); + pr.piece = piece_index_t(static_cast(r.piece) + request_offset / piece_size); + m_requests.push_back(pr); + size -= pr.length; + } + + int proxy_type = m_settings.get_int(settings_pack::proxy_type); + bool using_proxy = (proxy_type == settings_pack::http + || proxy_type == settings_pack::http_pw) && !m_ssl; + + request += "GET "; + request += using_proxy ? m_url : m_path; + request += "?info_hash="; + request += escape_string({t->torrent_file().info_hash().data(), 20}); + request += "&piece="; + request += to_string(r.piece); + + // if we're requesting less than an entire piece we need to + // add ranges + if (r.start > 0 || r.length != t->torrent_file().piece_size(r.piece)) + { + request += "&ranges="; + request += to_string(r.start).data(); + request += "-"; + // ranges are inclusive, just like HTTP + request += to_string(r.start + r.length - 1).data(); + } + + request += " HTTP/1.1\r\n"; + add_headers(request, m_settings, using_proxy); + request += "\r\n\r\n"; + m_first_request = false; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REQUEST", "%s", request.c_str()); +#endif + + send_buffer(request); + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + void http_seed_connection::on_receive(error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) + { + received_bytes(0, int(bytes_transferred)); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ERROR" + , "http_seed_connection error: %s", error.message().c_str()); + } +#endif + return; + } + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + for (;;) + { + span recv_buffer = m_recv_buffer.get(); + + if (bytes_transferred == 0) break; + TORRENT_ASSERT(int(recv_buffer.size()) > 0); + + TORRENT_ASSERT(!m_requests.empty()); + if (m_requests.empty()) + { + received_bytes(0, int(bytes_transferred)); + disconnect(errors::http_error, operation_t::bittorrent, peer_error); + return; + } + + peer_request front_request = m_requests.front(); + + bool header_finished = m_parser.header_finished(); + if (!header_finished) + { + bool parse_error = false; + int protocol = 0; + int payload = 0; + std::tie(payload, protocol) = m_parser.incoming( + recv_buffer, parse_error); + received_bytes(0, protocol); + TORRENT_ASSERT(bytes_transferred >= aux::numeric_cast(protocol)); + bytes_transferred -= aux::numeric_cast(protocol); +#if TORRENT_USE_ASSERTS + if (payload > front_request.length) payload = front_request.length; +#endif + + if (parse_error) + { + received_bytes(0, int(bytes_transferred)); + disconnect(errors::http_parse_error, operation_t::bittorrent, peer_error); + return; + } + + TORRENT_ASSERT(int(recv_buffer.size()) == 0 || recv_buffer.front() == 'H'); + + TORRENT_ASSERT(int(recv_buffer.size()) <= m_recv_buffer.packet_size()); + + // this means the entire status line hasn't been received yet + if (m_parser.status_code() == -1) + { + TORRENT_ASSERT(payload == 0); + TORRENT_ASSERT(bytes_transferred == 0); + break; + } + + // if the status code is not one of the accepted ones, abort + if (!is_ok_status(m_parser.status_code())) + { + auto const retry_time = value_or(m_parser.header_duration("retry-after") + , seconds32(m_settings.get_int(settings_pack::urlseed_wait_retry))); + + // temporarily unavailable, retry later + t->retry_web_seed(this, retry_time); + + if (t->alerts().should_post()) + { + std::string const error_msg = to_string(m_parser.status_code()).data() + + (" " + m_parser.message()); + t->alerts().emplace_alert(t->get_handle(), url() + , error_msg); + } + received_bytes(0, int(bytes_transferred)); + disconnect(error_code(m_parser.status_code(), http_category()), operation_t::bittorrent, failure); + return; + } + if (!m_parser.header_finished()) + { + TORRENT_ASSERT(payload == 0); + TORRENT_ASSERT(bytes_transferred == 0); + break; + } + } + + // we just completed reading the header + if (!header_finished) + { + if (is_redirect(m_parser.status_code())) + { + // this means we got a redirection request + // look for the location header + std::string location = m_parser.header("location"); + received_bytes(0, int(bytes_transferred)); + + if (location.empty()) + { + // we should not try this server again. + t->remove_web_seed_conn(this, errors::missing_location, operation_t::bittorrent, peer_error); + return; + } + + // add the redirected url and remove the current one + t->add_web_seed(location, web_seed_entry::http_seed); + t->remove_web_seed_conn(this, errors::redirecting, operation_t::bittorrent, peer_error); + return; + } + + std::string const& server_version = m_parser.header("server"); + if (!server_version.empty()) + { + m_server_string = "URL seed @ "; + m_server_string += m_host; + m_server_string += " ("; + m_server_string += server_version; + m_server_string += ")"; + } + + m_response_left = atol(m_parser.header("content-length").c_str()); + if (m_response_left == -1) + { + received_bytes(0, int(bytes_transferred)); + // we should not try this server again. + t->remove_web_seed_conn(this, errors::no_content_length, operation_t::bittorrent, peer_error); + return; + } + if (m_response_left != front_request.length) + { + received_bytes(0, int(bytes_transferred)); + // we should not try this server again. + t->remove_web_seed_conn(this, errors::invalid_range, operation_t::bittorrent, peer_error); + return; + } + m_body_start = m_parser.body_start(); + } + + recv_buffer = recv_buffer.subspan(m_body_start); + + // ========================= + // === CHUNKED ENCODING === + // ========================= + while (m_parser.chunked_encoding() + && m_chunk_pos >= 0 + && m_chunk_pos < recv_buffer.size()) + { + int header_size = 0; + std::int64_t chunk_size = 0; + span chunk_start = recv_buffer.subspan(aux::numeric_cast(m_chunk_pos)); + TORRENT_ASSERT(chunk_start[0] == '\r' + || aux::is_hex(chunk_start[0])); + bool ret = m_parser.parse_chunk_header(chunk_start, &chunk_size, &header_size); + if (!ret) + { + TORRENT_ASSERT(bytes_transferred >= aux::numeric_cast(chunk_start.size() - m_partial_chunk_header)); + bytes_transferred -= aux::numeric_cast(chunk_start.size() - m_partial_chunk_header); + received_bytes(0, aux::numeric_cast(chunk_start.size() - m_partial_chunk_header)); + m_partial_chunk_header = aux::numeric_cast(chunk_start.size()); + if (bytes_transferred == 0) return; + break; + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CHUNKED_ENCODING" + , "parsed chunk: %" PRId64 " header_size: %d" + , chunk_size, header_size); +#endif + TORRENT_ASSERT(bytes_transferred >= aux::numeric_cast(header_size - m_partial_chunk_header)); + bytes_transferred -= aux::numeric_cast(header_size - m_partial_chunk_header); + + received_bytes(0, header_size - m_partial_chunk_header); + m_partial_chunk_header = 0; + TORRENT_ASSERT(chunk_size != 0 || chunk_start.size() <= header_size || chunk_start[header_size] == 'H'); + // cut out the chunk header from the receive buffer + TORRENT_ASSERT(m_chunk_pos + m_body_start < INT_MAX); + m_recv_buffer.cut(header_size, t->block_size() + 1024, aux::numeric_cast(m_chunk_pos + m_body_start)); + recv_buffer = m_recv_buffer.get(); + recv_buffer = recv_buffer.subspan(m_body_start); + m_chunk_pos += chunk_size; + if (chunk_size == 0) + { + TORRENT_ASSERT(m_recv_buffer.get().size() < m_chunk_pos + m_body_start + 1 + || m_recv_buffer.get()[static_cast(m_chunk_pos + m_body_start)] == 'H' + || (m_parser.chunked_encoding() + && m_recv_buffer.get()[static_cast(m_chunk_pos + m_body_start)] == '\r')); + m_chunk_pos = -1; + } + } + } + + int payload = int(bytes_transferred); + if (payload > m_response_left) payload = int(m_response_left); + if (payload > front_request.length) payload = front_request.length; + received_bytes(payload, 0); + incoming_piece_fragment(payload); + m_response_left -= payload; + + if (m_parser.status_code() == 503) + { + if (!m_parser.finished()) return; + + int retry_time = std::atoi(std::string(recv_buffer.begin(), recv_buffer.end()).c_str()); + if (retry_time <= 0) retry_time = 60; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONNECT", "retrying in %d seconds", retry_time); +#endif + + received_bytes(0, int(bytes_transferred)); + // temporarily unavailable, retry later + t->retry_web_seed(this, seconds32(retry_time)); + disconnect(error_code(m_parser.status_code(), http_category()), operation_t::bittorrent, failure); + return; + } + + + // we only received the header, no data + if (recv_buffer.empty()) break; + + if (recv_buffer.size() < front_request.length) break; + + // if the response is chunked, we need to receive the last + // terminating chunk and the tail headers before we can proceed + if (m_parser.chunked_encoding() && m_chunk_pos >= 0) break; + + m_requests.pop_front(); + incoming_piece(front_request, recv_buffer.begin()); + if (associated_torrent().expired()) return; + + int const size_to_cut = m_body_start + front_request.length; + TORRENT_ASSERT(m_recv_buffer.get().size() < size_to_cut + 1 + || m_recv_buffer.get()[size_to_cut] == 'H' + || (m_parser.chunked_encoding() && m_recv_buffer.get()[size_to_cut] == '\r')); + + m_recv_buffer.cut(size_to_cut, t->block_size() + 1024); + if (m_response_left == 0) m_chunk_pos = 0; + else m_chunk_pos -= front_request.length; + TORRENT_ASSERT(bytes_transferred >= aux::numeric_cast(payload)); + bytes_transferred -= aux::numeric_cast(payload); + m_body_start = 0; + if (m_response_left > 0) continue; + TORRENT_ASSERT(m_response_left == 0); + m_parser.reset(); + } + } + + void http_seed_connection::get_specific_peer_info(peer_info& p) const + { + web_connection_base::get_specific_peer_info(p); + p.flags |= peer_info::local_connection; + p.connection_type = peer_info::http_seed; + } + +} diff --git a/src/http_stream.cpp b/src/http_stream.cpp new file mode 100644 index 0000000..00fa617 --- /dev/null +++ b/src/http_stream.cpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/http_stream.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for base64encode +#include "libtorrent/socket_io.hpp" + +#include + +using namespace std::placeholders; + +namespace libtorrent { + + void http_stream::name_lookup(error_code const& e, tcp::resolver::iterator i + , handler_type& h) + { + if (handle_error(e, h)) return; + + m_sock.async_connect(i->endpoint(), std::bind( + &http_stream::connected, this, _1, std::move(h))); + } + + void http_stream::connected(error_code const& e, handler_type& h) + { + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + if (m_no_connect) + { + std::vector().swap(m_buffer); + h(e); + return; + } + + // send CONNECT + std::back_insert_iterator> p(m_buffer); + std::string const endpoint = print_endpoint(m_remote_endpoint); + write_string("CONNECT " + endpoint + " HTTP/1.0\r\n", p); + if (!m_user.empty()) + { + write_string("Proxy-Authorization: Basic " + base64encode( + m_user + ":" + m_password) + "\r\n", p); + } + write_string("\r\n", p); + async_write(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&http_stream::handshake1, this, _1, std::move(h))); + } + + void http_stream::handshake1(error_code const& e, handler_type& h) + { + if (handle_error(e, h)) return; + + // read one byte from the socket + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&http_stream::handshake2, this, _1, std::move(h))); + } + + void http_stream::handshake2(error_code const& e, handler_type& h) + { + if (handle_error(e, h)) return; + + std::size_t const read_pos = m_buffer.size(); + // look for \n\n and \r\n\r\n + // both of which means end of http response header + bool found_end = false; + if (read_pos > 2 && m_buffer[read_pos - 1] == '\n') + { + if (m_buffer[read_pos - 2] == '\n') + { + found_end = true; + } + else if (read_pos > 4 + && m_buffer[read_pos - 2] == '\r' + && m_buffer[read_pos - 3] == '\n' + && m_buffer[read_pos - 4] == '\r') + { + found_end = true; + } + } + + if (found_end) + { + m_buffer.push_back(0); + char const* status = std::strchr(m_buffer.data(), ' '); + if (status == nullptr) + { + h(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + status++; + int const code = std::atoi(status); + if (code != 200) + { + h(boost::asio::error::operation_not_supported); + error_code ec; + close(ec); + return; + } + + h(e); + std::vector().swap(m_buffer); + return; + } + + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(m_buffer.data() + read_pos, 1) + , std::bind(&http_stream::handshake2, this, _1, std::move(h))); + } + +} diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp new file mode 100644 index 0000000..e966243 --- /dev/null +++ b/src/http_tracker_connection.cpp @@ -0,0 +1,601 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/socket_io.hpp" + +#include +#include +#include +#include +#include +#include +#include // for snprintf +#include // for PRId64 et.al. + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/string_util.hpp" // for is_i2p_url +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/ip_filter.hpp" + +namespace libtorrent { + + http_tracker_connection::http_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , std::weak_ptr c) + : tracker_connection(man, req, ios, std::move(c)) + {} + + void http_tracker_connection::start() + { + std::string url = tracker_req().url; + + if (0 != (tracker_req().kind & tracker_request::scrape_request)) + { + // find and replace "announce" with "scrape" + // in request + + std::size_t pos = url.find("announce"); + if (pos == std::string::npos) + { + tracker_connection::fail(errors::scrape_not_available); + return; + } + url.replace(pos, 8, "scrape"); + } + +#if TORRENT_USE_I2P + bool const i2p = is_i2p_url(url); +#else + static const bool i2p = false; +#endif + + aux::session_settings const& settings = m_man.settings(); + + // if request-string already contains + // some parameters, append an ampersand instead + // of a question mark + std::size_t arguments_start = url.find('?'); + if (arguments_start != std::string::npos) + url += "&"; + else + url += "?"; + + url += "info_hash="; + url += escape_string({tracker_req().info_hash.data(), 20}); + + if (0 == (tracker_req().kind & tracker_request::scrape_request)) + { + static const char* event_string[] = {"completed", "started", "stopped", "paused"}; + + char str[1024]; + std::snprintf(str, sizeof(str) + , "&peer_id=%s" + "&port=%d" + "&uploaded=%" PRId64 + "&downloaded=%" PRId64 + "&left=%" PRId64 + "&corrupt=%" PRId64 + "&key=%08X" + "%s%s" // event + "&numwant=%d" + "&compact=1" + "&no_peer_id=1" + , escape_string({tracker_req().pid.data(), 20}).c_str() + // the i2p tracker seems to verify that the port is not 0, + // even though it ignores it otherwise + , tracker_req().listen_port + , tracker_req().uploaded + , tracker_req().downloaded + , tracker_req().left + , tracker_req().corrupt + , tracker_req().key + , (tracker_req().event != tracker_request::none) ? "&event=" : "" + , (tracker_req().event != tracker_request::none) ? event_string[tracker_req().event - 1] : "" + , tracker_req().num_want); + url += str; +#if !defined TORRENT_DISABLE_ENCRYPTION + if (settings.get_int(settings_pack::in_enc_policy) != settings_pack::pe_disabled + && settings.get_bool(settings_pack::announce_crypto_support)) + url += "&supportcrypto=1"; +#endif + if (settings.get_bool(settings_pack::report_redundant_bytes)) + { + url += "&redundant="; + url += to_string(tracker_req().redundant).data(); + } + if (!tracker_req().trackerid.empty()) + { + url += "&trackerid="; + url += escape_string(tracker_req().trackerid); + } + +#if TORRENT_USE_I2P + if (i2p && tracker_req().i2pconn) + { + if (tracker_req().i2pconn->local_endpoint().empty()) + { + fail(errors::no_i2p_endpoint, "Waiting for i2p acceptor from SAM bridge", seconds32(5)); + return; + } + else + { + url += "&ip=" + tracker_req().i2pconn->local_endpoint () + ".i2p"; + } + } + else +#endif + if (!settings.get_bool(settings_pack::anonymous_mode)) + { + std::string const& announce_ip = settings.get_str(settings_pack::announce_ip); + if (!announce_ip.empty()) + { + url += "&ip=" + escape_string(announce_ip); + } + } + } + + if (!tracker_req().ipv4.empty() && !i2p) + { + for (auto const& v4 : tracker_req().ipv4) + { + error_code err; + std::string const ip = v4.to_string(err); + if (err) continue; + url += "&ipv4="; + url += escape_string(ip); + } + } + if (!tracker_req().ipv6.empty() && !i2p) + { + for (auto const& v6 : tracker_req().ipv6) + { + error_code err; + std::string const ip = v6.to_string(err); + if (err) continue; + url += "&ipv6="; + url += escape_string(ip); + } + } + + if (!tracker_req().outgoing_socket) + { + fail(errors::invalid_listen_socket, "outgoing socket was closed"); + return; + } + + using namespace std::placeholders; + m_tracker_connection = std::make_shared(get_io_service(), m_man.host_resolver() + , std::bind(&http_tracker_connection::on_response, shared_from_this(), _1, _2, _3) + , true, settings.get_int(settings_pack::max_http_recv_buffer_size) + , std::bind(&http_tracker_connection::on_connect, shared_from_this(), _1) + , std::bind(&http_tracker_connection::on_filter, shared_from_this(), _1, _2) +#ifdef TORRENT_USE_OPENSSL + , tracker_req().ssl_ctx +#endif + ); + + int const timeout = tracker_req().event == tracker_request::stopped + ? settings.get_int(settings_pack::stop_tracker_timeout) + : settings.get_int(settings_pack::tracker_completion_timeout); + + // in anonymous mode we omit the user agent to mitigate fingerprinting of + // the client. Private torrents is an exception because some private + // trackers may require the user agent + std::string const user_agent = settings.get_bool(settings_pack::anonymous_mode) + && !tracker_req().private_torrent ? "" : settings.get_str(settings_pack::user_agent); + + // when sending stopped requests, prefer the cached DNS entry + // to avoid being blocked for slow or failing responses. Chances + // are that we're shutting down, and this should be a best-effort + // attempt. It's not worth stalling shutdown. + aux::proxy_settings ps(settings); + m_tracker_connection->get(url, seconds(timeout) + , tracker_req().event == tracker_request::stopped ? 2 : 1 + , ps.proxy_tracker_connections ? &ps : nullptr + , 5, user_agent, bind_interface() + , (tracker_req().event == tracker_request::stopped + ? resolver_interface::cache_only : resolver_flags{}) + | resolver_interface::abort_on_shutdown +#if TORRENT_ABI_VERSION == 1 + , tracker_req().auth +#else + , "" +#endif +#if TORRENT_USE_I2P + , tracker_req().i2pconn +#endif + ); + + // the url + 100 estimated header size + sent_bytes(int(url.size()) + 100); + +#ifndef TORRENT_DISABLE_LOGGING + + std::shared_ptr cb = requester(); + if (cb) + { + cb->debug_log("==> TRACKER_REQUEST [ url: %s ]", url.c_str()); + } +#endif + } + + void http_tracker_connection::close() + { + if (m_tracker_connection) + { + m_tracker_connection->close(); + m_tracker_connection.reset(); + } + cancel(); + m_man.remove_request(this); + } + + // endpoints is an in-out parameter + void http_tracker_connection::on_filter(http_connection& c + , std::vector& endpoints) + { + // filter all endpoints we cannot reach from this listen socket, which may + // be all of them, in which case we should not announce this listen socket + // to this tracker + auto const ls = bind_socket(); + endpoints.erase(std::remove_if(endpoints.begin(), endpoints.end() + , [&](tcp::endpoint const& ep) { return !ls.can_route(ep.address()); }) + , endpoints.end()); + + if (endpoints.empty()) + { + fail(error_code(boost::system::errc::host_unreachable, system_category())); + return; + } + + TORRENT_UNUSED(c); + if (!tracker_req().filter) return; + + // remove endpoints that are filtered by the IP filter + for (auto i = endpoints.begin(); i != endpoints.end();) + { + if (tracker_req().filter->access(i->address()) == ip_filter::blocked) + i = endpoints.erase(i); + else + ++i; + } + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); + if (cb) + { + cb->debug_log("*** TRACKER_FILTER"); + } +#endif + if (endpoints.empty()) + fail(errors::banned_by_ip_filter); + } + + void http_tracker_connection::on_connect(http_connection& c) + { + error_code ec; + tcp::endpoint ep = c.socket().remote_endpoint(ec); + m_tracker_ip = ep.address(); + } + + void http_tracker_connection::on_response(error_code const& ec + , http_parser const& parser, span data) + { + // keep this alive + std::shared_ptr me(shared_from_this()); + + if (ec && ec != boost::asio::error::eof) + { + fail(ec); + return; + } + + if (!parser.header_finished()) + { + fail(boost::asio::error::eof); + return; + } + + if (parser.status_code() != 200) + { + fail(error_code(parser.status_code(), http_category()) + , parser.message().c_str()); + return; + } + + received_bytes(static_cast(data.size()) + parser.body_start()); + + // handle tracker response + error_code ecode; + + std::shared_ptr cb = requester(); + if (!cb) + { + close(); + return; + } + + tracker_response resp = parse_tracker_response(data, ecode + , tracker_req().kind, tracker_req().info_hash); + + if (!resp.warning_message.empty()) + cb->tracker_warning(tracker_req(), resp.warning_message); + + if (ecode) + { + fail(ecode, resp.failure_reason.c_str() + , resp.interval, resp.min_interval); + close(); + return; + } + + // do slightly different things for scrape requests + if (0 != (tracker_req().kind & tracker_request::scrape_request)) + { + cb->tracker_scrape_response(tracker_req(), resp.complete + , resp.incomplete, resp.downloaded, resp.downloaders); + } + else + { + std::list
    ip_list; + if (m_tracker_connection) + { + for (auto const& endp : m_tracker_connection->endpoints()) + { + ip_list.push_back(endp.address()); + } + } + + cb->tracker_response(tracker_req(), m_tracker_ip, ip_list, resp); + } + close(); + } + + // TODO: 2 returning a bool here is redundant. Instead this function should + // return the peer_entry + bool extract_peer_info(bdecode_node const& info, peer_entry& ret, error_code& ec) + { + // extract peer id (if any) + if (info.type() != bdecode_node::dict_t) + { + ec = errors::invalid_peer_dict; + return false; + } + bdecode_node i = info.dict_find_string("peer id"); + if (i && i.string_length() == 20) + { + std::copy(i.string_ptr(), i.string_ptr() + 20, ret.pid.begin()); + } + else + { + // if there's no peer_id, just initialize it to a bunch of zeroes + ret.pid.clear(); + } + + // extract ip + i = info.dict_find_string("ip"); + if (!i) + { + ec = errors::invalid_tracker_response; + return false; + } + ret.hostname = i.string_value().to_string(); + + // extract port + i = info.dict_find_int("port"); + if (!i) + { + ec = errors::invalid_tracker_response; + return false; + } + ret.port = std::uint16_t(i.int_value()); + + return true; + } + + tracker_response parse_tracker_response(span const data, error_code& ec + , int const flags, sha1_hash const& scrape_ih) + { + tracker_response resp; + + bdecode_node e; + int const res = bdecode(data.begin(), data.end(), e, ec); + + if (ec) return resp; + + if (res != 0 || e.type() != bdecode_node::dict_t) + { + ec = errors::invalid_tracker_response; + return resp; + } + + // if no interval is specified, default to 30 minutes + resp.interval = seconds32{e.dict_find_int_value("interval", 1800)}; + resp.min_interval = seconds32{e.dict_find_int_value("min interval", 30)}; + + bdecode_node const tracker_id = e.dict_find_string("tracker id"); + if (tracker_id) + resp.trackerid = tracker_id.string_value().to_string(); + + // parse the response + bdecode_node const failure = e.dict_find_string("failure reason"); + if (failure) + { + resp.failure_reason = failure.string_value().to_string(); + ec = errors::tracker_failure; + return resp; + } + + bdecode_node const warning = e.dict_find_string("warning message"); + if (warning) + resp.warning_message = warning.string_value().to_string(); + + if (0 != (flags & tracker_request::scrape_request)) + { + bdecode_node const files = e.dict_find_dict("files"); + if (!files) + { + ec = errors::invalid_files_entry; + return resp; + } + + bdecode_node const scrape_data = files.dict_find_dict( + scrape_ih.to_string()); + + if (!scrape_data) + { + ec = errors::invalid_hash_entry; + return resp; + } + + resp.complete = int(scrape_data.dict_find_int_value("complete", -1)); + resp.incomplete = int(scrape_data.dict_find_int_value("incomplete", -1)); + resp.downloaded = int(scrape_data.dict_find_int_value("downloaded", -1)); + resp.downloaders = int(scrape_data.dict_find_int_value("downloaders", -1)); + + return resp; + } + + // look for optional scrape info + resp.complete = int(e.dict_find_int_value("complete", -1)); + resp.incomplete = int(e.dict_find_int_value("incomplete", -1)); + resp.downloaded = int(e.dict_find_int_value("downloaded", -1)); + + bdecode_node peers_ent = e.dict_find("peers"); + if (peers_ent && peers_ent.type() == bdecode_node::string_t) + { + char const* peers = peers_ent.string_ptr(); + int const len = peers_ent.string_length(); +#if TORRENT_USE_I2P + if (0 != (flags & tracker_request::i2p)) + { + for (int i = 0; i < len; i += 32) + { + if (len - i < 32) break; + peer_entry p; + p.hostname = base32encode(std::string(peers + i, 32), string::i2p); + p.hostname += ".b32.i2p"; + p.port = 6881; + resp.peers.push_back(p); + } + } + else +#endif + { + resp.peers4.reserve(std::size_t(len / 6)); + for (int i = 0; i < len; i += 6) + { + if (len - i < 6) break; + + ipv4_peer_entry p; + p.ip = detail::read_v4_address(peers).to_v4().to_bytes(); + p.port = detail::read_uint16(peers); + resp.peers4.push_back(p); + } + } + } + else if (peers_ent && peers_ent.type() == bdecode_node::list_t) + { + int const len = peers_ent.list_size(); + resp.peers.reserve(std::size_t(len)); + error_code parse_error; + for (int i = 0; i < len; ++i) + { + peer_entry p; + if (!extract_peer_info(peers_ent.list_at(i), p, parse_error)) + continue; + resp.peers.push_back(p); + } + + // only report an error if all peer entries are invalid + if (resp.peers.empty() && parse_error) + { + ec = parse_error; + return resp; + } + } + else + { + peers_ent.clear(); + } + + bdecode_node ipv6_peers = e.dict_find_string("peers6"); + if (ipv6_peers) + { + char const* peers = ipv6_peers.string_ptr(); + int const len = ipv6_peers.string_length(); + resp.peers6.reserve(std::size_t(len / 18)); + for (int i = 0; i < len; i += 18) + { + if (len - i < 18) break; + + ipv6_peer_entry p; + p.ip = detail::read_v6_address(peers).to_v6().to_bytes(); + p.port = detail::read_uint16(peers); + resp.peers6.push_back(p); + } + } + else + { + ipv6_peers.clear(); + } +/* + // if we didn't receive any peers. We don't care if we're stopping anyway + if (peers_ent == 0 && ipv6_peers == 0 + && tracker_req().event != tracker_request::stopped) + { + ec = errors::invalid_peers_entry; + return resp; + } +*/ + bdecode_node const ip_ent = e.dict_find_string("external ip"); + if (ip_ent) + { + char const* p = ip_ent.string_ptr(); + if (ip_ent.string_length() == std::tuple_size::value) + resp.external_ip = detail::read_v4_address(p); + else if (ip_ent.string_length() == std::tuple_size::value) + resp.external_ip = detail::read_v6_address(p); + } + + return resp; + } +} diff --git a/src/i2p_stream.cpp b/src/i2p_stream.cpp new file mode 100644 index 0000000..0bbe7a2 --- /dev/null +++ b/src/i2p_stream.cpp @@ -0,0 +1,499 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_I2P + +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/hex.hpp" // for to_hex +#include "libtorrent/debug.hpp" + +#include +#include + +using namespace std::placeholders; + +namespace libtorrent { + + struct i2p_error_category final : boost::system::error_category + { + const char* name() const BOOST_SYSTEM_NOEXCEPT override + { return "i2p error"; } + std::string message(int ev) const override + { + static char const* messages[] = + { + "no error", + "parse failed", + "cannot reach peer", + "i2p error", + "invalid key", + "invalid id", + "timeout", + "key not found", + "duplicated id" + }; + + if (ev < 0 || ev >= i2p_error::num_errors) return "unknown error"; + return messages[ev]; + } + boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT override + { return {ev, *this}; } + }; + + + boost::system::error_category& i2p_category() + { + static i2p_error_category i2p_category; + return i2p_category; + } + + namespace i2p_error + { + boost::system::error_code make_error_code(i2p_error_code e) + { + return {e, i2p_category()}; + } + } + + i2p_connection::i2p_connection(io_service& ios) + : m_port(0) + , m_state(sam_idle) + , m_io_service(ios) + {} + + i2p_connection::~i2p_connection() = default; + + void i2p_connection::close(error_code& e) + { + if (m_sam_socket) m_sam_socket->close(e); + } + + aux::proxy_settings i2p_connection::proxy() const + { + aux::proxy_settings ret; + ret.hostname = m_hostname; + ret.port = std::uint16_t(m_port); + ret.type = settings_pack::i2p_proxy; + return ret; + } + + void i2p_connection::open(std::string const& s, int port + , i2p_stream::handler_type handler) + { + // we already seem to have a session to this SAM router + if (m_hostname == s + && m_port == port + && m_sam_socket + && (is_open() || m_state == sam_connecting)) return; + + m_hostname = s; + m_port = port; + + if (m_hostname.empty()) return; + + m_state = sam_connecting; + + char tmp[20]; + aux::random_bytes(tmp); + m_session_id.resize(sizeof(tmp)*2); + aux::to_hex(tmp, &m_session_id[0]); + + m_sam_socket.reset(new i2p_stream(m_io_service)); + m_sam_socket->set_proxy(m_hostname, m_port); + m_sam_socket->set_command(i2p_stream::cmd_create_session); + m_sam_socket->set_session_id(m_session_id.c_str()); + + ADD_OUTSTANDING_ASYNC("i2p_stream::on_sam_connect"); + m_sam_socket->async_connect(tcp::endpoint() + , std::bind(&i2p_connection::on_sam_connect, this, _1 + , std::move(handler), m_sam_socket)); + } + + void i2p_connection::on_sam_connect(error_code const& ec + , i2p_stream::handler_type& h, std::shared_ptr) + { + COMPLETE_ASYNC("i2p_stream::on_sam_connect"); + m_state = sam_idle; + + if (ec) + { + h(ec); + return; + } + + do_name_lookup("ME", std::bind(&i2p_connection::set_local_endpoint + , this, _1, _2, std::move(h))); + } + + void i2p_connection::set_local_endpoint(error_code const& ec, char const* dest + , i2p_stream::handler_type& h) + { + if (!ec && dest != nullptr) + m_i2p_local_endpoint = dest; + else + m_i2p_local_endpoint.clear(); + + h(ec); + } + + void i2p_connection::async_name_lookup(char const* name + , i2p_connection::name_lookup_handler handler) + { + if (m_state == sam_idle && m_name_lookup.empty() && is_open()) + do_name_lookup(name, std::move(handler)); + else + m_name_lookup.emplace_back(std::string(name) + , std::move(handler)); + } + + void i2p_connection::do_name_lookup(std::string const& name + , name_lookup_handler handler) + { + TORRENT_ASSERT(m_state == sam_idle); + m_state = sam_name_lookup; + m_sam_socket->set_name_lookup(name.c_str()); + m_sam_socket->send_name_lookup(std::bind(&i2p_connection::on_name_lookup + , this, _1, std::move(handler), m_sam_socket)); + } + + void i2p_connection::on_name_lookup(error_code const& ec + , name_lookup_handler& handler, std::shared_ptr) + { + m_state = sam_idle; + + std::string name = m_sam_socket->name_lookup(); + if (!m_name_lookup.empty()) + { + std::pair& nl = m_name_lookup.front(); + do_name_lookup(nl.first, std::move(nl.second)); + m_name_lookup.pop_front(); + } + + if (ec) + { + handler(ec, nullptr); + return; + } + + handler(ec, name.c_str()); + } + + i2p_stream::i2p_stream(io_service& io_service) + : proxy_base(io_service) + , m_id(nullptr) + , m_command(cmd_create_session) + , m_state(read_hello_response) + { +#if TORRENT_USE_ASSERTS + m_magic = 0x1337; +#endif + } + +#if TORRENT_USE_ASSERTS + i2p_stream::~i2p_stream() + { + TORRENT_ASSERT(m_magic == 0x1337); + m_magic = 0; + } +#endif + + void i2p_stream::do_connect(error_code const& e, tcp::resolver::iterator i + , handler_type h) + { + TORRENT_ASSERT(m_magic == 0x1337); + if (e || i == tcp::resolver::iterator()) + { + h(e); + error_code ec; + close(ec); + return; + } + + ADD_OUTSTANDING_ASYNC("i2p_stream::connected"); + m_sock.async_connect(i->endpoint(), std::bind( + &i2p_stream::connected, this, _1, std::move(h))); + } + + void i2p_stream::connected(error_code const& e, handler_type& h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::connected"); + if (handle_error(e, h)) return; + + // send hello command + m_state = read_hello_response; + static const char cmd[] = "HELLO VERSION MIN=3.0 MAX=3.0\n"; + + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, sizeof(cmd) - 1) + , std::bind(&i2p_stream::start_read_line, this, _1, std::move(h))); + } + + void i2p_stream::start_read_line(error_code const& e, handler_type& h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::start_read_line"); + if (handle_error(e, h)) return; + + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + m_buffer.resize(1); + async_read(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&i2p_stream::read_line, this, _1, std::move(h))); + } + + void i2p_stream::read_line(error_code const& e, handler_type& h) + { + TORRENT_ASSERT(m_magic == 0x1337); + COMPLETE_ASYNC("i2p_stream::read_line"); + if (handle_error(e, h)) return; + + auto const read_pos = int(m_buffer.size()); + + // look for \n which means end of the response + if (m_buffer[read_pos - 1] != '\n') + { + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, boost::asio::buffer(&m_buffer[read_pos], 1) + , std::bind(&i2p_stream::read_line, this, _1, std::move(h))); + return; + } + m_buffer[read_pos - 1] = 0; + + if (m_command == cmd_incoming) + { + // this is the line containing the destination + // of the incoming connection in an accept call + m_dest = &m_buffer[0]; + h(e); + std::vector().swap(m_buffer); + return; + } + + error_code invalid_response(i2p_error::parse_failed + , i2p_category()); + + string_view expect1; + string_view expect2; + + switch (m_state) + { + case read_hello_response: + expect1 = "HELLO"_sv; + expect2 = "REPLY"_sv; + break; + case read_connect_response: + case read_accept_response: + expect1 = "STREAM"_sv; + expect2 = "STATUS"_sv; + break; + case read_session_create_response: + expect1 = "SESSION"_sv; + expect2 = "STATUS"_sv; + break; + case read_name_lookup_response: + expect1 = "NAMING"_sv; + expect2 = "REPLY"_sv; + break; + } + + string_view remaining(m_buffer.data(), m_buffer.size()); + string_view token; + + std::tie(token, remaining) = split_string(remaining, ' '); + if (expect1.empty() || expect1 != token) + { handle_error(invalid_response, h); return; } + + std::tie(token, remaining) = split_string(remaining, ' '); + if (expect2.empty() || expect2 != token) + { handle_error(invalid_response, h); return; } + + int result = 0; + + for(;;) + { + string_view name; + std::tie(name, remaining) = split_string(remaining, '='); + if (name.empty()) break; + string_view value; + std::tie(value, remaining) = split_string(remaining, ' '); + if (value.empty()) { handle_error(invalid_response, h); return; } + + if ("RESULT"_sv == name) + { + if ("OK"_sv == value) + result = i2p_error::no_error; + else if ("CANT_REACH_PEER"_sv == value) + result = i2p_error::cant_reach_peer; + else if ("I2P_ERROR"_sv == value) + result = i2p_error::i2p_error; + else if ("INVALID_KEY"_sv == value) + result = i2p_error::invalid_key; + else if ("INVALID_ID"_sv == value) + result = i2p_error::invalid_id; + else if ("TIMEOUT"_sv == value) + result = i2p_error::timeout; + else if ("KEY_NOT_FOUND"_sv == value) + result = i2p_error::key_not_found; + else if ("DUPLICATED_ID"_sv == value) + result = i2p_error::duplicated_id; + else + result = i2p_error::num_errors; // unknown error + } + /*else if ("MESSAGE" == name) + { + } + else if ("VERSION"_sv == name) + { + }*/ + else if ("VALUE"_sv == name) + { + m_name_lookup = value.to_string(); + } + else if ("DESTINATION"_sv == name) + { + m_dest = value.to_string(); + } + } + + error_code ec(result, i2p_category()); + switch (result) + { + case i2p_error::no_error: + case i2p_error::invalid_key: + break; + default: + { + handle_error (ec, h); + return; + } + } + + switch (m_state) + { + case read_hello_response: + switch (m_command) + { + case cmd_create_session: + send_session_create(std::move(h)); + break; + case cmd_accept: + send_accept(std::move(h)); + break; + case cmd_connect: + send_connect(std::move(h)); + break; + case cmd_none: + case cmd_name_lookup: + case cmd_incoming: + h(e); + std::vector().swap(m_buffer); + } + break; + case read_connect_response: + case read_session_create_response: + case read_name_lookup_response: + h(ec); + std::vector().swap(m_buffer); + break; + case read_accept_response: + // the SAM bridge is waiting for an incoming + // connection. + // wait for one more line containing + // the destination of the remote peer + m_command = cmd_incoming; + m_buffer.resize(1); + ADD_OUTSTANDING_ASYNC("i2p_stream::read_line"); + async_read(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&i2p_stream::read_line, this, _1, h)); + break; + } + } + + void i2p_stream::send_connect(handler_type h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_connect_response; + char cmd[1024]; + int size = std::snprintf(cmd, sizeof(cmd), "STREAM CONNECT ID=%s DESTINATION=%s\n" + , m_id, m_dest.c_str()); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)) + , std::bind(&i2p_stream::start_read_line, this, _1, std::move(h))); + } + + void i2p_stream::send_accept(handler_type h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_accept_response; + char cmd[400]; + int size = std::snprintf(cmd, sizeof(cmd), "STREAM ACCEPT ID=%s\n", m_id); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)) + , std::bind(&i2p_stream::start_read_line, this, _1, std::move(h))); + } + + void i2p_stream::send_session_create(handler_type h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_session_create_response; + char cmd[400]; + int size = std::snprintf(cmd, sizeof(cmd), "SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT\n" + , m_id); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)) + , std::bind(&i2p_stream::start_read_line, this, _1, std::move(h))); + } + + void i2p_stream::send_name_lookup(handler_type h) + { + TORRENT_ASSERT(m_magic == 0x1337); + m_state = read_name_lookup_response; + char cmd[1024]; + int size = std::snprintf(cmd, sizeof(cmd), "NAMING LOOKUP NAME=%s\n", m_name_lookup.c_str()); + ADD_OUTSTANDING_ASYNC("i2p_stream::start_read_line"); + async_write(m_sock, boost::asio::buffer(cmd, std::size_t(size)) + , std::bind(&i2p_stream::start_read_line, this, _1, std::move(h))); + } +} + +#endif diff --git a/src/identify_client.cpp b/src/identify_client.cpp new file mode 100644 index 0000000..f946584 --- /dev/null +++ b/src/identify_client.cpp @@ -0,0 +1,430 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/identify_client.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace { + + using namespace libtorrent; + + int decode_digit(std::uint8_t c) + { + if (is_digit(char(c))) return c - '0'; + return c - 'A' + 10; + } + + // takes a peer id and returns a valid boost::optional + // object if the peer id matched the azureus style encoding + // the returned fingerprint contains information about the + // client's id + boost::optional parse_az_style(const peer_id& id) + { + fingerprint ret("..", 0, 0, 0, 0); + + if (id[0] != '-' || !is_print(char(id[1])) || (id[2] < '0') + || (id[3] < '0') || (id[4] < '0') + || (id[5] < '0') || (id[6] < '0') + || id[7] != '-') + return boost::optional(); + + ret.name[0] = char(id[1]); + ret.name[1] = char(id[2]); + ret.major_version = decode_digit(id[3]); + ret.minor_version = decode_digit(id[4]); + ret.revision_version = decode_digit(id[5]); + ret.tag_version = decode_digit(id[6]); + + return boost::optional(ret); + } + + // checks if a peer id can possibly contain a shadow-style + // identification + boost::optional parse_shadow_style(const peer_id& id) + { + fingerprint ret("..", 0, 0, 0, 0); + + if (!is_alpha(char(id[0])) && !is_digit(char(id[0]))) + return boost::optional(); + + if (std::equal(id.begin() + 4, id.begin() + 6, "--")) + { + if ((id[1] < '0') || (id[2] < '0') + || (id[3] < '0')) + return boost::optional(); + ret.major_version = decode_digit(id[1]); + ret.minor_version = decode_digit(id[2]); + ret.revision_version = decode_digit(id[3]); + } + else + { + if (id[8] != 0 || id[1] > 127 || id[2] > 127 || id[3] > 127) + return boost::optional(); + ret.major_version = id[1]; + ret.minor_version = id[2]; + ret.revision_version = id[3]; + } + + ret.name[0] = char(id[0]); + ret.name[1] = 0; + + ret.tag_version = 0; + return boost::optional(ret); + } + + // checks if a peer id can possibly contain a mainline-style + // identification + boost::optional parse_mainline_style(const peer_id& id) + { + char ids[21]; + std::copy(id.begin(), id.end(), ids); + ids[20] = 0; + fingerprint ret("..", 0, 0, 0, 0); + ret.name[1] = 0; + ret.tag_version = 0; + if (sscanf(ids, "%1c%3d-%3d-%3d--", &ret.name[0], &ret.major_version, &ret.minor_version + , &ret.revision_version) != 4 + || !is_print(ret.name[0])) + return boost::optional(); + + return boost::optional(ret); + } + + struct map_entry + { + char const* id; + char const* name; + }; + + // only support BitTorrentSpecification + // must be ordered alphabetically + const map_entry name_map[] = + { + {"7T", "aTorrent for android"} + , {"A", "ABC"} + , {"AB", "AnyEvent BitTorrent"} + , {"AG", "Ares"} + , {"AR", "Arctic Torrent"} + , {"AT", "Artemis"} + , {"AV", "Avicora"} + , {"AX", "BitPump"} + , {"AZ", "Azureus"} + , {"A~", "Ares"} + , {"BB", "BitBuddy"} + , {"BC", "BitComet"} + , {"BE", "baretorrent"} + , {"BF", "Bitflu"} + , {"BG", "BTG"} + , {"BL", "BitBlinder"} + , {"BP", "BitTorrent Pro"} + , {"BR", "BitRocket"} + , {"BS", "BTSlave"} + , {"BT", "BitTorrent"} + , {"BU", "BigUp"} + , {"BW", "BitWombat"} + , {"BX", "BittorrentX"} + , {"CD", "Enhanced CTorrent"} + , {"CT", "CTorrent"} + , {"DE", "Deluge"} + , {"DP", "Propagate Data Client"} + , {"EB", "EBit"} + , {"ES", "electric sheep"} + , {"FC", "FileCroc"} + , {"FT", "FoxTorrent"} + , {"FW", "FrostWire"} + , {"FX", "Freebox BitTorrent"} + , {"GS", "GSTorrent"} + , {"HK", "Hekate"} + , {"HL", "Halite"} + , {"HN", "Hydranode"} + , {"IL", "iLivid"} + , {"KC", "Koinonein"} + , {"KG", "KGet"} + , {"KT", "KTorrent"} + , {"LC", "LeechCraft"} + , {"LH", "LH-ABC"} + , {"LK", "Linkage"} + , {"LP", "lphant"} + , {"LT", "libtorrent"} + , {"LW", "Limewire"} + , {"M", "Mainline"} + , {"ML", "MLDonkey"} + , {"MO", "Mono Torrent"} + , {"MP", "MooPolice"} + , {"MR", "Miro"} + , {"MT", "Moonlight Torrent"} + , {"NX", "Net Transport"} + , {"O", "Osprey Permaseed"} + , {"OS", "OneSwarm"} + , {"OT", "OmegaTorrent"} + , {"PD", "Pando"} + , {"Q", "BTQueue"} + , {"QD", "QQDownload"} + , {"QT", "Qt 4"} + , {"R", "Tribler"} + , {"RT", "Retriever"} + , {"RZ", "RezTorrent"} + , {"S", "Shadow"} + , {"SB", "Swiftbit"} + , {"SD", "Xunlei"} + , {"SK", "spark"} + , {"SN", "ShareNet"} + , {"SS", "SwarmScope"} + , {"ST", "SymTorrent"} + , {"SZ", "Shareaza"} + , {"S~", "Shareaza (beta)"} + , {"T", "BitTornado"} + , {"TB", "Torch"} + , {"TL", "Tribler"} + , {"TN", "Torrent.NET"} + , {"TR", "Transmission"} + , {"TS", "TorrentStorm"} + , {"TT", "TuoTu"} + , {"U", "UPnP"} + , {"UL", "uLeecher"} + , {"UM", "uTorrent Mac"} + , {"UT", "uTorrent"} + , {"VG", "Vagaa"} + , {"WT", "BitLet"} + , {"WY", "FireTorrent"} + , {"XF", "Xfplay"} + , {"XL", "Xunlei"} + , {"XS", "XSwifter"} + , {"XT", "XanTorrent"} + , {"XX", "Xtorrent"} + , {"ZT", "ZipTorrent"} + , {"lt", "rTorrent"} + , {"pX", "pHoeniX"} + , {"qB", "qBittorrent"} + , {"st", "SharkTorrent"} + }; + + struct generic_map_entry + { + int offset; + char const* id; + char const* name; + }; + // non-standard names + const generic_map_entry generic_mappings[] = + { + {0, "Deadman Walking-", "Deadman"} + , {5, "Azureus", "Azureus 2.0.3.2"} + , {0, "DansClient", "XanTorrent"} + , {4, "btfans", "SimpleBT"} + , {0, "PRC.P---", "Bittorrent Plus! II"} + , {0, "P87.P---", "Bittorrent Plus!"} + , {0, "S587Plus", "Bittorrent Plus!"} + , {0, "martini", "Martini Man"} + , {0, "Plus---", "Bittorrent Plus"} + , {0, "turbobt", "TurboBT"} + , {0, "a00---0", "Swarmy"} + , {0, "a02---0", "Swarmy"} + , {0, "T00---0", "Teeweety"} + , {0, "BTDWV-", "Deadman Walking"} + , {2, "BS", "BitSpirit"} + , {0, "Pando-", "Pando"} + , {0, "LIME", "LimeWire"} + , {0, "btuga", "BTugaXP"} + , {0, "oernu", "BTugaXP"} + , {0, "Mbrst", "Burst!"} + , {0, "PEERAPP", "PeerApp"} + , {0, "Plus", "Plus!"} + , {0, "-Qt-", "Qt"} + , {0, "exbc", "BitComet"} + , {0, "DNA", "BitTorrent DNA"} + , {0, "-G3", "G3 Torrent"} + , {0, "-FG", "FlashGet"} + , {0, "-ML", "MLdonkey"} + , {0, "-MG", "Media Get"} + , {0, "XBT", "XBT"} + , {0, "OP", "Opera"} + , {2, "RS", "Rufus"} + , {0, "AZ2500BT", "BitTyrant"} + , {0, "btpd/", "BitTorrent Protocol Daemon"} + , {0, "TIX", "Tixati"} + , {0, "QVOD", "Qvod"} + }; + + bool compare_id(map_entry const& lhs, map_entry const& rhs) + { + return lhs.id[0] < rhs.id[0] + || ((lhs.id[0] == rhs.id[0]) && (lhs.id[1] < rhs.id[1])); + } + + std::string lookup(fingerprint const& f) + { + char identity[200]; + + const int size = sizeof(name_map)/sizeof(name_map[0]); + const map_entry tmp = {f.name, ""}; + const map_entry* i = + std::lower_bound(name_map, name_map + size + , tmp, &compare_id); + +#ifndef NDEBUG + for (int j = 1; j < size; ++j) + { + TORRENT_ASSERT(compare_id(name_map[j-1] + , name_map[j])); + } +#endif + + char temp[3]; + char const* name = nullptr; + if (i < name_map + size && std::equal(f.name, f.name + 2, i->id)) + { + name = i->name; + } + else + { + // if we don't have this client in the list + // just use the one or two letter code + std::memcpy(temp, f.name, 2); + temp[2] = 0; + name = temp; + } + + int num_chars = std::snprintf(identity, sizeof(identity), "%s %d.%d.%d", name + , f.major_version, f.minor_version, f.revision_version); + + if (f.tag_version != 0) + { + std::snprintf(identity + num_chars, sizeof(identity) - aux::numeric_cast(num_chars) + , ".%d", f.tag_version); + } + + return identity; + } + + bool find_string(char const* id, char const* search) + { + return std::equal(search, search + std::strlen(search), id); + } + +} // anonymous namespace + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + + boost::optional client_fingerprint(peer_id const& p) + { + // look for azureus style id + boost::optional f; + f = parse_az_style(p); + if (f) return f; + + // look for shadow style id + f = parse_shadow_style(p); + if (f) return f; + + // look for mainline style id + return parse_mainline_style(p); + } + +#endif + + std::string identify_client(peer_id const& p) + { + return aux::identify_client_impl(p); + } + +namespace aux { + + std::string identify_client_impl(peer_id const& p) + { + char const* PID = p.data(); + + if (p.is_all_zeros()) return "Unknown"; + + // ---------------------- + // non standard encodings + // ---------------------- + + for (auto const& e : generic_mappings) + { + if (find_string(PID + e.offset, e.id)) return e.name; + } + + if (find_string(PID, "-BOW") && PID[7] == '-') + return "Bits on Wheels " + std::string(PID + 4, PID + 7); + + if (find_string(PID, "eX")) + { + std::string user(PID + 2, 12); + return std::string("eXeem ('") + user + "')"; + } + bool const is_equ_zero = std::equal(PID, PID + 12, "\0\0\0\0\0\0\0\0\0\0\0\0"); + + if (is_equ_zero && PID[12] == '\x97') + return "Experimental 3.2.1b2"; + + if (is_equ_zero && PID[12] == '\0') + return "Experimental 3.1"; + + // look for azureus style id + boost::optional f = parse_az_style(p); + if (f) return lookup(*f); + + // look for shadow style id + f = parse_shadow_style(p); + if (f) return lookup(*f); + + // look for mainline style id + f = parse_mainline_style(p); + if (f) return lookup(*f); + + + if (is_equ_zero) + return "Generic"; + + std::string unknown("Unknown ["); + for (unsigned char const c : p) + unknown += is_print(char(c)) ? char(c) : '.'; + unknown += "]"; + return unknown; + } + +} // aux +} // libtorrent diff --git a/src/instantiate_connection.cpp b/src/instantiate_connection.cpp new file mode 100644 index 0000000..0b43490 --- /dev/null +++ b/src/instantiate_connection.cpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/aux_/instantiate_connection.hpp" + +namespace libtorrent { namespace aux { + + // TODO: 2 peer_connection and tracker_connection should probably be flags + bool instantiate_connection(io_service& ios + , aux::proxy_settings const& ps, aux::socket_type& s + , void* ssl_context + , utp_socket_manager* sm + , bool peer_connection + , bool tracker_connection) + { +#ifndef TORRENT_USE_OPENSSL + TORRENT_UNUSED(ssl_context); +#endif + + if (sm) + { + utp_stream* str; +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate>(ios, ssl_context); + str = &s.get>()->next_layer(); + } + else +#endif + { + s.instantiate(ios); + str = s.get(); + } + str->set_impl(sm->new_utp_socket(str)); + } +#if TORRENT_USE_I2P + else if (ps.type == settings_pack::i2p_proxy) + { + // it doesn't make any sense to try ssl over i2p + TORRENT_ASSERT(ssl_context == nullptr); + s.instantiate(ios); + s.get()->set_proxy(ps.hostname, ps.port); + } +#endif + else if (ps.type == settings_pack::none + || (peer_connection && !ps.proxy_peer_connections) + || (tracker_connection && !ps.proxy_tracker_connections)) + { +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate>(ios, ssl_context); + } + else +#endif + { + s.instantiate(ios); + } + } + else if (ps.type == settings_pack::http + || ps.type == settings_pack::http_pw) + { + http_stream* str; +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate>(ios, ssl_context); + str = &s.get>()->next_layer(); + } + else +#endif + { + s.instantiate(ios); + str = s.get(); + } + + str->set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::http_pw) + str->set_username(ps.username, ps.password); + } + else if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw + || ps.type == settings_pack::socks4) + { + socks5_stream* str; +#ifdef TORRENT_USE_OPENSSL + if (ssl_context) + { + s.instantiate>(ios, ssl_context); + str = &s.get>()->next_layer(); + } + else +#endif + { + s.instantiate(ios); + str = s.get(); + } + str->set_proxy(ps.hostname, ps.port); + if (ps.type == settings_pack::socks5_pw) + str->set_username(ps.username, ps.password); + if (ps.type == settings_pack::socks4) + str->set_version(4); + } + else + { + TORRENT_ASSERT_FAIL_VAL(ps.type); + return false; + } + return true; + } + +}} diff --git a/src/ip_filter.cpp b/src/ip_filter.cpp new file mode 100644 index 0000000..d28cb72 --- /dev/null +++ b/src/ip_filter.cpp @@ -0,0 +1,76 @@ +/* + +Copyright (c) 2005-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/ip_filter.hpp" + +namespace libtorrent { + + void ip_filter::add_rule(address const& first, address const& last, std::uint32_t flags) + { + if (first.is_v4()) + { + TORRENT_ASSERT(last.is_v4()); + m_filter4.add_rule(first.to_v4().to_bytes(), last.to_v4().to_bytes(), flags); + } + else if (first.is_v6()) + { + TORRENT_ASSERT(last.is_v6()); + m_filter6.add_rule(first.to_v6().to_bytes(), last.to_v6().to_bytes(), flags); + } + else + TORRENT_ASSERT_FAIL(); + } + + std::uint32_t ip_filter::access(address const& addr) const + { + if (addr.is_v4()) + return m_filter4.access(addr.to_v4().to_bytes()); + TORRENT_ASSERT(addr.is_v6()); + return m_filter6.access(addr.to_v6().to_bytes()); + } + + ip_filter::filter_tuple_t ip_filter::export_filter() const + { + return std::make_tuple(m_filter4.export_filter() + , m_filter6.export_filter()); + } + + void port_filter::add_rule(std::uint16_t first, std::uint16_t last, std::uint32_t flags) + { + m_filter.add_rule(first, last, flags); + } + + std::uint32_t port_filter::access(std::uint16_t port) const + { + return m_filter.access(port); + } +} diff --git a/src/ip_notifier.cpp b/src/ip_notifier.cpp new file mode 100644 index 0000000..11f7b4f --- /dev/null +++ b/src/ip_notifier.cpp @@ -0,0 +1,443 @@ +/* + +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/ip_notifier.hpp" +#include "libtorrent/assert.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +// TODO: simulator support +#elif TORRENT_USE_NETLINK +#include "libtorrent/netlink.hpp" +#include "libtorrent/socket.hpp" +#include +#elif TORRENT_USE_SYSTEMCONFIGURATION +#include +#elif defined TORRENT_WINDOWS +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +namespace libtorrent { namespace aux { + +namespace { + +#if defined TORRENT_BUILD_SIMULATOR +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_service& ios) + : m_ios(ios) {} + + void async_wait(std::function cb) override + { + m_ios.post([cb]() + { cb(make_error_code(boost::system::errc::not_supported)); }); + } + + void cancel() override {} + +private: + io_service& m_ios; +}; +#elif TORRENT_USE_NETLINK +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_service& ios) + : m_socket(ios + , netlink::endpoint(netlink(NETLINK_ROUTE), RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR)) + { + // Linux can generate ENOBUFS if the socket's buffers are full + // don't treat it as an error + error_code ec; + m_socket.set_option(libtorrent::no_enobufs(true), ec); + } + + // non-copyable + ip_change_notifier_impl(ip_change_notifier_impl const&) = delete; + ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete; + + void async_wait(std::function cb) override + { + using namespace std::placeholders; + m_socket.async_receive(boost::asio::buffer(m_buf) + , std::bind(&ip_change_notifier_impl::on_notify, _1, _2, std::move(cb))); + } + + void cancel() override + { m_socket.cancel();} + +private: + netlink::socket m_socket; + std::array m_buf; + + static void on_notify(error_code const& ec, std::size_t bytes_transferred + , std::function const& cb) + { + TORRENT_UNUSED(bytes_transferred); + + // on linux we could parse the message to get information about the + // change but Windows requires the application to enumerate the + // interfaces after a notification so do that for Linux as well to + // minimize the difference between platforms + + cb(ec); + } +}; +#elif TORRENT_USE_SYSTEMCONFIGURATION + +template void CFRefRetain(T h) { CFRetain(h); } +template void CFRefRelease(T h) { CFRelease(h); } + +template , void (*Release)(T) = CFRefRelease> +struct CFRef +{ + CFRef() = default; + explicit CFRef(T h) : m_h(h) {} // take ownership + ~CFRef() { release(); } + + CFRef(CFRef&& rhs) : m_h(rhs.m_h) { rhs.m_h = nullptr; } + CFRef& operator=(CFRef&& rhs) + { + if (m_h == rhs.m_h) return *this; + release(); + m_h = rhs.m_h; + rhs.m_h = nullptr; + return *this; + } + + CFRef(CFRef const& rhs) : m_h(rhs.m_h) { retain(); } + CFRef& operator=(CFRef const& rhs) + { + if (m_h == rhs.m_h) return *this; + release(); + m_h = rhs.m_h; + retain(); + return *this; + } + + CFRef& operator=(T h) { m_h = h; return *this;} + CFRef& operator=(std::nullptr_t) { release(); return *this;} + + T get() const { return m_h; } + explicit operator bool() const { return m_h != nullptr; } + +private: + T m_h = nullptr; // handle + + void retain() { if (m_h != nullptr) Retain(m_h); } + void release() { if (m_h != nullptr) Release(m_h); m_h = nullptr; } +}; + +void CFDispatchRetain(dispatch_queue_t q) { dispatch_retain(q); } +void CFDispatchRelease(dispatch_queue_t q) { dispatch_release(q); } +using CFDispatchRef = CFRef; + +#if TORRENT_USE_SC_NETWORK_REACHABILITY +CFRef create_reachability(SCNetworkReachabilityCallBack callback + , void* context_info) +{ + TORRENT_ASSERT(callback != nullptr); + + sockaddr_in addr = {}; + addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + + CFRef reach{SCNetworkReachabilityCreateWithAddress(nullptr + , reinterpret_cast(&addr))}; + if (!reach) + return CFRef(); + + SCNetworkReachabilityContext context = {0, nullptr, nullptr, nullptr, nullptr}; + context.info = context_info; + + return SCNetworkReachabilitySetCallback(reach.get(), callback, &context) + ? reach : CFRef(); +} + +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_service& ios) + : m_ios(ios) + { + m_queue = dispatch_queue_create("libtorrent.IPChangeNotifierQueue", nullptr); + m_reach = create_reachability( + [](SCNetworkReachabilityRef /*target*/, SCNetworkReachabilityFlags /*flags*/, void *info) + { + auto obj = static_cast(info); + obj->m_ios.post([obj]() + { + if (!obj->m_cb) return; + auto cb = std::move(obj->m_cb); + obj->m_cb = nullptr; + cb(error_code()); + }); + }, this); + + if (!m_queue || !m_reach + || !SCNetworkReachabilitySetDispatchQueue(m_reach.get(), m_queue.get())) + cancel(); + } + + // non-copyable + ip_change_notifier_impl(ip_change_notifier_impl const&) = delete; + ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete; + + ~ip_change_notifier_impl() override + { cancel(); } + + void async_wait(std::function cb) override + { + if (m_queue) + m_cb = std::move(cb); + else + m_ios.post([cb]() + { cb(make_error_code(boost::system::errc::not_supported)); }); + } + + void cancel() override + { + if (m_reach) + SCNetworkReachabilitySetDispatchQueue(m_reach.get(), nullptr); + + m_cb = nullptr; + m_reach = nullptr; + m_queue = nullptr; + } + +private: + io_service& m_ios; + CFDispatchRef m_queue; + CFRef m_reach; + std::function m_cb = nullptr; +}; +#else +// see https://developer.apple.com/library/content/technotes/tn1145/_index.html +CFRef create_keys_array() +{ + CFRef keys{CFArrayCreateMutable(nullptr + , 0, &kCFTypeArrayCallBacks)}; + + // "State:/Network/Interface/[^/]+/IPv4" + CFRef key{SCDynamicStoreKeyCreateNetworkInterfaceEntity(nullptr + , kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4)}; + CFArrayAppendValue(keys.get(), key.get()); + + // NOTE: for IPv6, you can replicate the above setup with kSCEntNetIPv6 + // but due to the current state of most common configurations, where + // IPv4 is used alongside with IPv6, you will end up with twice the + // notifications for the same change + + return keys; +} + +CFRef create_dynamic_store(SCDynamicStoreCallBack callback, void* context_info) +{ + TORRENT_ASSERT(callback != nullptr); + + SCDynamicStoreContext context = {0, nullptr, nullptr, nullptr, nullptr}; + context.info = context_info; + +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + CFRef store{SCDynamicStoreCreate(nullptr + , CFSTR("libtorrent.IPChangeNotifierStore"), callback, &context)}; +#if defined __clang__ +#pragma clang diagnostic pop +#endif + if (!store) + return CFRef(); + + CFRef keys = create_keys_array(); + return SCDynamicStoreSetNotificationKeys(store.get(), nullptr, keys.get()) + ? store : CFRef(); +} + +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_service& ios) + : m_ios(ios) + { + m_queue = dispatch_queue_create("libtorrent.IPChangeNotifierQueue", nullptr); + m_store = create_dynamic_store( + [](SCDynamicStoreRef /*store*/, CFArrayRef /*changedKeys*/, void *info) + { + auto obj = static_cast(info); + obj->m_ios.post([obj]() + { + if (!obj->m_cb) return; + auto cb = std::move(obj->m_cb); + obj->m_cb = nullptr; + cb(error_code()); + }); + }, this); + + if (!m_queue || !m_store + || !SCDynamicStoreSetDispatchQueue(m_store.get(), m_queue.get())) + cancel(); + } + + // non-copyable + ip_change_notifier_impl(ip_change_notifier_impl const&) = delete; + ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete; + + ~ip_change_notifier_impl() override + { cancel(); } + + void async_wait(std::function cb) override + { + if (m_queue) + m_cb = std::move(cb); + else + m_ios.post([cb]() + { cb(make_error_code(boost::system::errc::not_supported)); }); + } + + void cancel() override + { + if (m_store) + SCDynamicStoreSetDispatchQueue(m_store.get(), nullptr); + + m_cb = nullptr; + m_store = nullptr; + m_queue = nullptr; + } + +private: + io_service& m_ios; + CFDispatchRef m_queue; + CFRef m_store; + std::function m_cb = nullptr; +}; +#endif // TORRENT_USE_SC_NETWORK_REACHABILITY + +#elif defined TORRENT_WINDOWS +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_service& ios) + : m_ios(ios) + { + NotifyUnicastIpAddressChange(AF_UNSPEC, address_change_cb, this, false, &m_hnd); + } + + // non-copyable + ip_change_notifier_impl(ip_change_notifier_impl const&) = delete; + ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete; + + // non-moveable + ip_change_notifier_impl(ip_change_notifier_impl&&) = delete; + ip_change_notifier_impl& operator=(ip_change_notifier_impl&&) = delete; + + ~ip_change_notifier_impl() override + { + if (m_hnd != nullptr) + { + CancelMibChangeNotify2(m_hnd); + m_hnd = nullptr; + } + } + + void async_wait(std::function cb) override + { + if (m_hnd == nullptr) + { + cb(make_error_code(boost::system::errc::not_supported)); + return; + } + + std::lock_guard l(m_cb_mutex); + m_cb.emplace_back(std::move(cb)); + } + + void cancel() override + { + std::vector> cbs; + { + std::lock_guard l(m_cb_mutex); + cbs = std::move(m_cb); + } + for (auto& cb : cbs) cb(make_error_code(boost::asio::error::operation_aborted)); + } + +private: + static void WINAPI address_change_cb(void* ctx, MIB_UNICASTIPADDRESS_ROW*, MIB_NOTIFICATION_TYPE) + { + ip_change_notifier_impl* impl = static_cast(ctx); + std::vector> cbs; + { + std::lock_guard l(impl->m_cb_mutex); + cbs = std::move(impl->m_cb); + } + // TODO move cbs into the lambda with C++14 + impl->m_ios.post([cbs]() + { + for (auto& cb : cbs) cb(error_code()); + }); + } + + io_service& m_ios; + HANDLE m_hnd = nullptr; + // address_change_cb gets invoked from a separate worker thread so the callbacks + // vector must be protected by a mutex + std::mutex m_cb_mutex; + std::vector> m_cb; +}; +#else +struct ip_change_notifier_impl final : ip_change_notifier +{ + explicit ip_change_notifier_impl(io_service& ios) + : m_ios(ios) {} + + void async_wait(std::function cb) override + { + m_ios.post([cb]() + { cb(make_error_code(boost::system::errc::not_supported)); }); + } + + void cancel() override {} + +private: + io_service& m_ios; +}; +#endif + +} // anonymous namespace + + std::unique_ptr create_ip_notifier(io_service& ios) + { + return std::unique_ptr(new ip_change_notifier_impl(ios)); + } +}} diff --git a/src/ip_voter.cpp b/src/ip_voter.cpp new file mode 100644 index 0000000..23f6ad8 --- /dev/null +++ b/src/ip_voter.cpp @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_any() etc. +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/random.hpp" // for random() +#include "libtorrent/aux_/time.hpp" // for aux::time_now() + +namespace libtorrent { + + ip_voter::ip_voter() + : m_total_votes(0) + , m_valid_external(false) + , m_last_rotate(aux::time_now()) + { + } + + // returns true if our external IP changed + bool ip_voter::maybe_rotate() + { + time_point now = aux::time_now(); + + // if we have more than or equal to 50 votes, + // we rotate. Also, if it's been more than 5 minutes + // and we have at least one vote, we also rotate. + // this is the inverse condition, since this is the case + // were we exit, without rotating + if (m_total_votes < 50 + && (now - m_last_rotate < minutes(5) || m_total_votes == 0) + && m_valid_external) + return false; + + // this shouldn't really happen if we have at least one + // vote. + if (m_external_addresses.empty()) return false; + + // if there's just one vote, go with that + if (m_external_addresses.size() == 1) + { + // avoid flapping. We need more votes to change our mind on the + // external IP + if (m_external_addresses[0].num_votes < 2) return false; + } + else + { + // find the top two votes. + std::partial_sort(m_external_addresses.begin() + , m_external_addresses.begin() + 2, m_external_addresses.end()); + + // if we don't have enough of a majority voting for the winning + // IP, don't rotate. This avoids flapping + if (m_external_addresses[0].num_votes * 2 / 3 <= m_external_addresses[1].num_votes) + return false; + } + + auto const i = m_external_addresses.begin(); + + bool ret = m_external_address != i->addr; + m_external_address = i->addr; + + m_external_address_voters.clear(); + m_total_votes = 0; + m_external_addresses.clear(); + m_last_rotate = now; + m_valid_external = true; + return ret; + } + + bool ip_voter::cast_vote(address const& ip + , aux::ip_source_t const source_type, address const& source) + { + if (is_any(ip)) return false; + if (is_local(ip)) return false; + if (is_loopback(ip)) return false; + + // don't trust source that aren't connected to us + // on a different address family than the external + // IP they claim we have + if (ip.is_v4() != source.is_v4()) return false; + + // this is the key to use for the bloom filters + // it represents the identity of the voter + sha1_hash const k = hash_address(source); + + // do we already have an entry for this external IP? + auto i = std::find_if(m_external_addresses.begin() + , m_external_addresses.end(), [&ip] (external_ip_t const& e) { return e.addr == ip; }); + + if (i == m_external_addresses.end()) + { + // each IP only gets to add a new IP once + if (m_external_address_voters.find(k)) return maybe_rotate(); + + if (m_external_addresses.size() > 40) + { + if (random(1)) return maybe_rotate(); + + // use stable sort here to maintain the fifo-order + // of the entries with the same number of votes + // this will sort in ascending order, i.e. the lowest + // votes first. Also, the oldest are first, so this + // is a sort of weighted LRU. + std::stable_sort(m_external_addresses.begin(), m_external_addresses.end()); + + // erase the last element, since it is one of the + // ones with the fewest votes + m_external_addresses.erase(m_external_addresses.end() - 1); + } + m_external_addresses.emplace_back(); + i = m_external_addresses.end() - 1; + i->addr = ip; + } + // add one more vote to this external IP + if (!i->add_vote(k, source_type)) return maybe_rotate(); + ++m_total_votes; + + if (m_valid_external) return maybe_rotate(); + + i = std::min_element(m_external_addresses.begin(), m_external_addresses.end()); + TORRENT_ASSERT(i != m_external_addresses.end()); + + if (i->addr == m_external_address) return maybe_rotate(); + + if (m_external_address != address()) + { + // we have a temporary external address. As soon as we have + // more than 25 votes, consider deciding which one to settle for + return (m_total_votes >= 25) ? maybe_rotate() : false; + } + + m_external_address = i->addr; + + return true; + } + + bool ip_voter::external_ip_t::add_vote(sha1_hash const& k + , aux::ip_source_t const type) + { + sources |= type; + if (voters.find(k)) return false; + voters.set(k); + ++num_votes; + return true; + } + + external_ip::external_ip(address const& local4, address const& global4 + , address const& local6, address const& global6) + : m_addresses{{global4, ensure_v6(global6)}, {local4, ensure_v6(local6)}} + { + TORRENT_ASSERT(m_addresses[0][1].is_v6()); + TORRENT_ASSERT(m_addresses[1][1].is_v6()); + TORRENT_ASSERT(m_addresses[0][0].is_v4()); + TORRENT_ASSERT(m_addresses[1][0].is_v4()); + } + + address external_ip::external_address(address const& ip) const + { + address ext = m_addresses[is_local(ip)][ip.is_v6()]; + if (ip.is_v6() && ext == address_v4()) return address_v6(); + return ext; + } +} diff --git a/src/kademlia/dht_settings.cpp b/src/kademlia/dht_settings.cpp new file mode 100644 index 0000000..df457ec --- /dev/null +++ b/src/kademlia/dht_settings.cpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/bdecode.hpp" + +namespace libtorrent { +namespace dht { + + dht_settings read_dht_settings(bdecode_node const& e) + { + dht_settings sett; + + if (e.type() != bdecode_node::dict_t) return sett; + + bdecode_node val; + val = e.dict_find_int("max_peers_reply"); + if (val) sett.max_peers_reply = int(val.int_value()); + val = e.dict_find_int("search_branching"); + if (val) sett.search_branching = int(val.int_value()); + val = e.dict_find_int("max_fail_count"); + if (val) sett.max_fail_count = int(val.int_value()); + val = e.dict_find_int("max_torrents"); + if (val) sett.max_torrents = int(val.int_value()); + val = e.dict_find_int("max_dht_items"); + if (val) sett.max_dht_items = int(val.int_value()); + val = e.dict_find_int("max_peers"); + if (val) sett.max_peers = int(val.int_value()); + val = e.dict_find_int("max_torrent_search_reply"); + if (val) sett.max_torrent_search_reply = int(val.int_value()); + val = e.dict_find_int("restrict_routing_ips"); + if (val) sett.restrict_routing_ips = (val.int_value() != 0); + val = e.dict_find_int("restrict_search_ips"); + if (val) sett.restrict_search_ips = (val.int_value() != 0); + val = e.dict_find_int("extended_routing_table"); + if (val) sett.extended_routing_table = (val.int_value() != 0); + val = e.dict_find_int("aggressive_lookups"); + if (val) sett.aggressive_lookups = (val.int_value() != 0); + val = e.dict_find_int("privacy_lookups"); + if (val) sett.privacy_lookups = (val.int_value() != 0); + val = e.dict_find_int("enforce_node_id"); + if (val) sett.enforce_node_id = (val.int_value() != 0); + val = e.dict_find_int("ignore_dark_internet"); + if (val) sett.ignore_dark_internet = (val.int_value() != 0); + val = e.dict_find_int("block_timeout"); + if (val) sett.block_timeout = int(val.int_value()); + val = e.dict_find_int("block_ratelimit"); + if (val) sett.block_ratelimit = int(val.int_value()); + val = e.dict_find_int("read_only"); + if (val) sett.read_only = (val.int_value() != 0); + val = e.dict_find_int("item_lifetime"); + if (val) sett.item_lifetime = int(val.int_value()); + + return sett; + } + + entry save_dht_settings(dht_settings const& settings) + { + entry e; + entry::dictionary_type& dht_sett = e.dict(); + + dht_sett["max_peers_reply"] = settings.max_peers_reply; + dht_sett["search_branching"] = settings.search_branching; + dht_sett["max_fail_count"] = settings.max_fail_count; + dht_sett["max_torrents"] = settings.max_torrents; + dht_sett["max_dht_items"] = settings.max_dht_items; + dht_sett["max_peers"] = settings.max_peers; + dht_sett["max_torrent_search_reply"] = settings.max_torrent_search_reply; + dht_sett["restrict_routing_ips"] = settings.restrict_routing_ips; + dht_sett["restrict_search_ips"] = settings.restrict_search_ips; + dht_sett["extended_routing_table"] = settings.extended_routing_table; + dht_sett["aggressive_lookups"] = settings.aggressive_lookups; + dht_sett["privacy_lookups"] = settings.privacy_lookups; + dht_sett["enforce_node_id"] = settings.enforce_node_id; + dht_sett["ignore_dark_internet"] = settings.ignore_dark_internet; + dht_sett["block_timeout"] = settings.block_timeout; + dht_sett["block_ratelimit"] = settings.block_ratelimit; + dht_sett["read_only"] = settings.read_only; + dht_sett["item_lifetime"] = settings.item_lifetime; + + return e; + } +} +} diff --git a/src/kademlia/dht_state.cpp b/src/kademlia/dht_state.cpp new file mode 100644 index 0000000..111db00 --- /dev/null +++ b/src/kademlia/dht_state.cpp @@ -0,0 +1,133 @@ +/* + +Copyright (c) 2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dht_state.hpp" + +#include +#include + +namespace libtorrent { namespace dht { + + node_ids_t extract_node_ids(bdecode_node const& e, string_view key) + { + if (e.type() != bdecode_node::dict_t) return node_ids_t(); + node_ids_t ret; + // first look for an old-style nid + auto const old_nid = e.dict_find_string_value(key); + if (old_nid.size() == 20) + { + ret.emplace_back(address(), node_id(old_nid)); + return ret; + } + auto const nids = e.dict_find_list(key); + if (!nids) return ret; + for (int i = 0; i < nids.list_size(); i++) + { + bdecode_node nid = nids.list_at(i); + if (nid.type() != bdecode_node::string_t) continue; + if (nid.string_length() < 20) continue; + char const* in = nid.string_ptr(); + node_id id(in); + in += id.size(); + address addr; + if (nid.string_length() == 24) + addr = detail::read_v4_address(in); + else if (nid.string_length() == 36) + addr = detail::read_v6_address(in); + else + continue; + ret.emplace_back(addr, id); + } + + return ret; + } + +namespace { + entry save_nodes(std::vector const& nodes) + { + entry ret(entry::list_t); + entry::list_type& list = ret.list(); + for (auto const& ep : nodes) + { + std::string node; + std::back_insert_iterator out(node); + detail::write_endpoint(ep, out); + list.emplace_back(node); + } + return ret; + } +} // anonymous namespace + + void dht_state::clear() + { + nids.clear(); + nids.shrink_to_fit(); + + nodes.clear(); + nodes.shrink_to_fit(); + nodes6.clear(); + nodes6.shrink_to_fit(); + } + + dht_state read_dht_state(bdecode_node const& e) + { + dht_state ret; + + if (e.type() != bdecode_node::dict_t) return ret; + + ret.nids = extract_node_ids(e, "node-id"); + + if (bdecode_node const nodes = e.dict_find_list("nodes")) + ret.nodes = detail::read_endpoint_list(nodes); + if (bdecode_node const nodes = e.dict_find_list("nodes6")) + ret.nodes6 = detail::read_endpoint_list(nodes); + return ret; + } + + entry save_dht_state(dht_state const& state) + { + entry ret(entry::dictionary_t); + auto& nids = ret["node-id"].list(); + for (auto const& n : state.nids) + { + std::string nid; + std::copy(n.second.begin(), n.second.end(), std::back_inserter(nid)); + detail::write_address(n.first, std::back_inserter(nid)); + nids.emplace_back(std::move(nid)); + } + entry const nodes = save_nodes(state.nodes); + if (!nodes.list().empty()) ret["nodes"] = nodes; + entry const nodes6 = save_nodes(state.nodes6); + if (!nodes6.list().empty()) ret["nodes6"] = nodes6; + return ret; + } +}} diff --git a/src/kademlia/dht_storage.cpp b/src/kademlia/dht_storage.cpp new file mode 100644 index 0000000..7def4b1 --- /dev/null +++ b/src/kademlia/dht_storage.cpp @@ -0,0 +1,629 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include // for ip_v4 +#include + +namespace libtorrent { namespace dht { +namespace { + + // this is the entry for every peer + // the timestamp is there to make it possible + // to remove stale peers + struct peer_entry + { + time_point added; + tcp::endpoint addr; + bool seed = 0; + }; + + // internal + bool operator<(peer_entry const& lhs, peer_entry const& rhs) + { + return lhs.addr.address() == rhs.addr.address() + ? lhs.addr.port() < rhs.addr.port() + : lhs.addr.address() < rhs.addr.address(); + } + + // this is a group. It contains a set of group members + struct torrent_entry + { + std::string name; + std::vector peers4; + std::vector peers6; + }; + + // TODO: 2 make this configurable in dht_settings + constexpr time_duration announce_interval = minutes(30); + + struct dht_immutable_item + { + // the actual value + std::unique_ptr value; + // this counts the number of IPs we have seen + // announcing this item, this is used to determine + // popularity if we reach the limit of items to store + bloom_filter<128> ips; + // the last time we heard about this item + // the correct interpretation of this field + // requires a time reference + time_point last_seen; + // number of IPs in the bloom filter + int num_announcers = 0; + // size of malloced space pointed to by value + int size = 0; + }; + + struct dht_mutable_item : dht_immutable_item + { + signature sig{}; + sequence_number seq{}; + public_key key{}; + std::string salt; + }; + + void set_value(dht_immutable_item& item, span buf) + { + int const size = int(buf.size()); + if (item.size != size) + { + item.value.reset(new char[std::size_t(size)]); + item.size = size; + } + std::copy(buf.begin(), buf.end(), item.value.get()); + } + + void touch_item(dht_immutable_item& f, address const& addr) + { + f.last_seen = aux::time_now(); + + // maybe increase num_announcers if we haven't seen this IP before + sha1_hash const iphash = hash_address(addr); + if (!f.ips.find(iphash)) + { + f.ips.set(iphash); + ++f.num_announcers; + } + } + + // return true of the first argument is a better candidate for removal, i.e. + // less important to keep + struct immutable_item_comparator + { + explicit immutable_item_comparator(std::vector const& node_ids) : m_node_ids(node_ids) {} + immutable_item_comparator(immutable_item_comparator const&) = default; + + template + bool operator()(std::pair const& lhs + , std::pair const& rhs) const + { + int const l_distance = min_distance_exp(lhs.first, m_node_ids); + int const r_distance = min_distance_exp(rhs.first, m_node_ids); + + // this is a score taking the popularity (number of announcers) and the + // fit, in terms of distance from ideal storing node, into account. + // each additional 5 announcers is worth one extra bit in the distance. + // that is, an item with 10 announcers is allowed to be twice as far + // from another item with 5 announcers, from our node ID. Twice as far + // because it gets one more bit. + return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance; + } + + private: + + // explicitly disallow assignment, to silence msvc warning + immutable_item_comparator& operator=(immutable_item_comparator const&) = delete; + + std::vector const& m_node_ids; + }; + + // picks the least important one (i.e. the one + // the fewest peers are announcing, and farthest + // from our node IDs) + template + typename std::map::const_iterator pick_least_important_item( + std::vector const& node_ids, std::map const& table) + { + return std::min_element(table.begin(), table.end() + , immutable_item_comparator(node_ids)); + } + + constexpr int sample_infohashes_interval_max = 21600; + constexpr int infohashes_sample_count_max = 20; + + struct infohashes_sample + { + aux::vector samples; + time_point created = min_time(); + + int count() const { return int(samples.size()); } + }; + + class dht_default_storage final : public dht_storage_interface + { + public: + + explicit dht_default_storage(dht_settings const& settings) + : m_settings(settings) + { + m_counters.reset(); + } + + ~dht_default_storage() override = default; + + dht_default_storage(dht_default_storage const&) = delete; + dht_default_storage& operator=(dht_default_storage const&) = delete; + +#if TORRENT_ABI_VERSION == 1 + size_t num_torrents() const override { return m_map.size(); } + size_t num_peers() const override + { + size_t ret = 0; + for (auto const& t : m_map) + ret += t.second.peers4.size() + t.second.peers6.size(); + return ret; + } +#endif + void update_node_ids(std::vector const& ids) override + { + m_node_ids = ids; + } + + bool get_peers(sha1_hash const& info_hash + , bool const noseed, bool const scrape, address const& requester + , entry& peers) const override + { + auto const i = m_map.find(info_hash); + if (i == m_map.end()) return int(m_map.size()) >= m_settings.max_torrents; + + torrent_entry const& v = i->second; + auto const& peersv = requester.is_v4() ? v.peers4 : v.peers6; + + if (!v.name.empty()) peers["n"] = v.name; + + if (scrape) + { + bloom_filter<256> downloaders; + bloom_filter<256> seeds; + + for (auto const& p : peersv) + { + sha1_hash const iphash = hash_address(p.addr.address()); + if (p.seed) seeds.set(iphash); + else downloaders.set(iphash); + } + + peers["BFpe"] = downloaders.to_string(); + peers["BFsd"] = seeds.to_string(); + } + else + { + tcp const protocol = requester.is_v4() ? tcp::v4() : tcp::v6(); + int to_pick = m_settings.max_peers_reply; + TORRENT_ASSERT(to_pick >= 0); + // if these are IPv6 peers their addresses are 4x the size of IPv4 + // so reduce the max peers 4 fold to compensate + // max_peers_reply should probably be specified in bytes + if (!peersv.empty() && protocol == tcp::v6()) + to_pick /= 4; + entry::list_type& pe = peers["values"].list(); + + int candidates = int(std::count_if(peersv.begin(), peersv.end() + , [=](peer_entry const& e) { return !(noseed && e.seed); })); + + to_pick = std::min(to_pick, candidates); + + for (auto iter = peersv.begin(); to_pick > 0; ++iter) + { + // if the node asking for peers is a seed, skip seeds from the + // peer list + if (noseed && iter->seed) continue; + + TORRENT_ASSERT(candidates >= to_pick); + + // pick this peer with probability + // / + if (random(std::uint32_t(candidates--)) > std::uint32_t(to_pick)) + continue; + + pe.emplace_back(); + std::string& str = pe.back().string(); + + str.resize(18); + std::string::iterator out = str.begin(); + detail::write_endpoint(iter->addr, out); + str.resize(std::size_t(out - str.begin())); + + --to_pick; + } + } + + if (int(peersv.size()) < m_settings.max_peers) + return false; + + // we're at the max peers stored for this torrent + // only send a write token if the requester is already in the set + // only check for a match on IP because the peer may be announcing + // a different port than the one it is using to send DHT messages + peer_entry requester_entry; + requester_entry.addr.address(requester); + auto requester_iter = std::lower_bound(peersv.begin(), peersv.end(), requester_entry); + return requester_iter == peersv.end() + || requester_iter->addr.address() != requester; + } + + void announce_peer(sha1_hash const& info_hash + , tcp::endpoint const& endp + , string_view name, bool const seed) override + { + auto const ti = m_map.find(info_hash); + torrent_entry* v; + if (ti == m_map.end()) + { + if (int(m_map.size()) >= m_settings.max_torrents) + { + // we're at capacity, drop the announce + return; + } + + m_counters.torrents += 1; + v = &m_map[info_hash]; + } + else + { + v = &ti->second; + } + + // the peer announces a torrent name, and we don't have a name + // for this torrent. Store it. + if (!name.empty() && v->name.empty()) + { + v->name = name.substr(0, 100).to_string(); + } + + auto& peersv = is_v4(endp) ? v->peers4 : v->peers6; + + peer_entry peer; + peer.addr = endp; + peer.added = aux::time_now(); + peer.seed = seed; + auto i = std::lower_bound(peersv.begin(), peersv.end(), peer); + if (i != peersv.end() && i->addr == endp) + { + *i = peer; + } + else if (int(peersv.size()) >= m_settings.max_peers) + { + // we're at capacity, drop the announce + return; + } + else + { + peersv.insert(i, peer); + m_counters.peers += 1; + } + } + + bool get_immutable_item(sha1_hash const& target + , entry& item) const override + { + auto const i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) return false; + + error_code ec; + item["v"] = bdecode({i->second.value.get(), i->second.size}, ec); + return true; + } + + void put_immutable_item(sha1_hash const& target + , span buf + , address const& addr) override + { + TORRENT_ASSERT(!m_node_ids.empty()); + auto i = m_immutable_table.find(target); + if (i == m_immutable_table.end()) + { + // make sure we don't add too many items + if (int(m_immutable_table.size()) >= m_settings.max_dht_items) + { + auto const j = pick_least_important_item(m_node_ids + , m_immutable_table); + + TORRENT_ASSERT(j != m_immutable_table.end()); + m_immutable_table.erase(j); + m_counters.immutable_data -= 1; + } + dht_immutable_item to_add; + set_value(to_add, buf); + + std::tie(i, std::ignore) = m_immutable_table.insert( + std::make_pair(target, std::move(to_add))); + m_counters.immutable_data += 1; + } + +// std::fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size())); + + touch_item(i->second, addr); + } + + bool get_mutable_item_seq(sha1_hash const& target + , sequence_number& seq) const override + { + auto const i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) return false; + + seq = i->second.seq; + return true; + } + + bool get_mutable_item(sha1_hash const& target + , sequence_number const seq, bool const force_fill + , entry& item) const override + { + auto const i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) return false; + + dht_mutable_item const& f = i->second; + item["seq"] = f.seq.value; + if (force_fill || (sequence_number(0) <= seq && seq < f.seq)) + { + error_code ec; + item["v"] = bdecode({f.value.get(), f.size}, ec); + item["sig"] = f.sig.bytes; + item["k"] = f.key.bytes; + } + return true; + } + + void put_mutable_item(sha1_hash const& target + , span buf + , signature const& sig + , sequence_number const seq + , public_key const& pk + , span salt + , address const& addr) override + { + TORRENT_ASSERT(!m_node_ids.empty()); + auto i = m_mutable_table.find(target); + if (i == m_mutable_table.end()) + { + // this is the case where we don't have an item in this slot + // make sure we don't add too many items + if (int(m_mutable_table.size()) >= m_settings.max_dht_items) + { + auto const j = pick_least_important_item(m_node_ids + , m_mutable_table); + + TORRENT_ASSERT(j != m_mutable_table.end()); + m_mutable_table.erase(j); + m_counters.mutable_data -= 1; + } + dht_mutable_item to_add; + set_value(to_add, buf); + to_add.seq = seq; + to_add.salt = {salt.begin(), salt.end()}; + to_add.sig = sig; + to_add.key = pk; + + std::tie(i, std::ignore) = m_mutable_table.insert( + std::make_pair(target, std::move(to_add))); + m_counters.mutable_data += 1; + } + else + { + // this is the case where we already have an item in this slot + dht_mutable_item& item = i->second; + + if (item.seq < seq) + { + set_value(item, buf); + item.seq = seq; + item.sig = sig; + } + } + + touch_item(i->second, addr); + } + + int get_infohashes_sample(entry& item) override + { + item["interval"] = aux::clamp(m_settings.sample_infohashes_interval + , 0, sample_infohashes_interval_max); + item["num"] = int(m_map.size()); + + refresh_infohashes_sample(); + + aux::vector const& samples = m_infohashes_sample.samples; + item["samples"] = span( + reinterpret_cast(samples.data()), static_cast(samples.size()) * 20); + + return m_infohashes_sample.count(); + } + + void tick() override + { + // look through all peers and see if any have timed out + for (auto i = m_map.begin(), end(m_map.end()); i != end;) + { + torrent_entry& t = i->second; + purge_peers(t.peers4); + purge_peers(t.peers6); + + if (!t.peers4.empty() || !t.peers6.empty()) + { + ++i; + continue; + } + + // if there are no more peers, remove the entry altogether + i = m_map.erase(i); + m_counters.torrents -= 1;// peers is decreased by purge_peers + } + + if (0 == m_settings.item_lifetime) return; + + time_point const now = aux::time_now(); + time_duration lifetime = seconds(m_settings.item_lifetime); + // item lifetime must >= 120 minutes. + if (lifetime < minutes(120)) lifetime = minutes(120); + + for (auto i = m_immutable_table.begin(); i != m_immutable_table.end();) + { + if (i->second.last_seen + lifetime > now) + { + ++i; + continue; + } + i = m_immutable_table.erase(i); + m_counters.immutable_data -= 1; + } + + for (auto i = m_mutable_table.begin(); i != m_mutable_table.end();) + { + if (i->second.last_seen + lifetime > now) + { + ++i; + continue; + } + i = m_mutable_table.erase(i); + m_counters.mutable_data -= 1; + } + } + + dht_storage_counters counters() const override + { + return m_counters; + } + + private: + dht_settings const& m_settings; + dht_storage_counters m_counters; + + std::vector m_node_ids; + std::map m_map; + std::map m_immutable_table; + std::map m_mutable_table; + + infohashes_sample m_infohashes_sample; + + void purge_peers(std::vector& peers) + { + auto now = aux::time_now(); + auto new_end = std::remove_if(peers.begin(), peers.end() + , [=](peer_entry const& e) + { + return e.added + announce_interval * 3 / 2 < now; + }); + + m_counters.peers -= std::int32_t(std::distance(new_end, peers.end())); + peers.erase(new_end, peers.end()); + // if we're using less than 1/4 of the capacity free up the excess + if (!peers.empty() && peers.capacity() / peers.size() >= 4U) + peers.shrink_to_fit(); + } + + void refresh_infohashes_sample() + { + time_point const now = aux::time_now(); + int const interval = aux::clamp(m_settings.sample_infohashes_interval + , 0, sample_infohashes_interval_max); + + int const max_count = aux::clamp(m_settings.max_infohashes_sample_count + , 0, infohashes_sample_count_max); + int const count = std::min(max_count, int(m_map.size())); + + if (interval > 0 + && m_infohashes_sample.created + seconds(interval) > now + && m_infohashes_sample.count() >= max_count) + return; + + aux::vector& samples = m_infohashes_sample.samples; + samples.clear(); + samples.reserve(count); + + int to_pick = count; + int candidates = int(m_map.size()); + + for (auto const& t : m_map) + { + if (to_pick == 0) + break; + + TORRENT_ASSERT(candidates >= to_pick); + + // pick this key with probability + // / + if (random(std::uint32_t(candidates--)) > std::uint32_t(to_pick)) + continue; + + samples.push_back(t.first); + --to_pick; + } + + TORRENT_ASSERT(int(samples.size()) == count); + m_infohashes_sample.created = now; + } + }; +} + +void dht_storage_counters::reset() +{ + torrents = 0; + peers = 0; + immutable_data = 0; + mutable_data = 0; +} + +std::unique_ptr dht_default_storage_constructor( + dht_settings const& settings) +{ + return std::unique_ptr(new dht_default_storage(settings)); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp new file mode 100644 index 0000000..9d659fa --- /dev/null +++ b/src/kademlia/dht_tracker.cpp @@ -0,0 +1,705 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dht_tracker.hpp" + +#include + +#include +#include +#include + +#include +#include +#include +#include // for counters +#include +#include +#include // for is_local + +#ifndef TORRENT_DISABLE_LOGGING +#include // to_hex +#endif + +using namespace std::placeholders; + +namespace libtorrent { namespace dht { + + namespace { + + // generate a new write token key every 5 minutes + auto const key_refresh + = duration_cast(minutes(5)); + + void add_dht_counters(node const& dht, counters& c) + { + int nodes, replacements, allocated_observers; + std::tie(nodes, replacements, allocated_observers) = dht.get_stats_counters(); + + c.inc_stats_counter(counters::dht_nodes, nodes); + c.inc_stats_counter(counters::dht_node_cache, replacements); + c.inc_stats_counter(counters::dht_allocated_observers, allocated_observers); + } + + std::vector concat(std::vector const& v1 + , std::vector const& v2) + { + std::vector r = v1; + r.insert(r.end(), v2.begin(), v2.end()); + return r; + } + + } // anonymous namespace + + // class that puts the networking and the kademlia node in a single + // unit and connecting them together. + dht_tracker::dht_tracker(dht_observer* observer + , io_service& ios + , send_fun_t const& send_fun + , dht::settings const& settings + , counters& cnt + , dht_storage_interface& storage + , dht_state&& state) + : m_counters(cnt) + , m_storage(storage) + , m_state(std::move(state)) + , m_send_fun(send_fun) + , m_log(observer) + , m_key_refresh_timer(ios) + , m_refresh_timer(ios) + , m_settings(settings) + , m_running(false) + , m_host_resolver(ios) + , m_send_quota(settings.upload_rate_limit) + , m_last_tick(aux::time_now()) + { + m_blocker.set_block_timer(m_settings.block_timeout); + m_blocker.set_rate_limit(m_settings.block_ratelimit); + } + + void dht_tracker::update_node_id(aux::listen_socket_handle const& s) + { + auto n = m_nodes.find(s); + if (n != m_nodes.end()) + n->second.dht.update_node_id(); + update_storage_node_ids(); + } + + void dht_tracker::new_socket(aux::listen_socket_handle const& s) + { + address const local_address = s.get_local_endpoint().address(); + auto stored_nid = std::find_if(m_state.nids.begin(), m_state.nids.end() + , [&](node_ids_t::value_type const& nid) { return nid.first == local_address; }); + node_id const nid = stored_nid != m_state.nids.end() ? stored_nid->second : node_id(); + // must use piecewise construction because tracker_node::connection_timer + // is neither copyable nor movable + auto n = m_nodes.emplace(std::piecewise_construct_t(), std::forward_as_tuple(s) + , std::forward_as_tuple(get_io_service(m_key_refresh_timer) + , s, this, m_settings, nid, m_log, m_counters + , std::bind(&dht_tracker::get_node, this, _1, _2) + , m_storage)); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::tracker)) + { + m_log->log(dht_logger::tracker, "starting %s DHT tracker with node id: %s" + , local_address.is_v4() ? "IPv4" : "IPv6" + , aux::to_hex(n.first->second.dht.nid()).c_str()); + } +#endif + + if (m_running && n.second) + { + ADD_OUTSTANDING_ASYNC("dht_tracker::connection_timeout"); + error_code ec; + n.first->second.connection_timer.expires_from_now(seconds(1), ec); + n.first->second.connection_timer.async_wait( + std::bind(&dht_tracker::connection_timeout, self(), n.first->first, _1)); + n.first->second.dht.bootstrap({}, find_data::nodes_callback()); + } + } + + void dht_tracker::delete_socket(aux::listen_socket_handle const& s) + { + m_nodes.erase(s); + } + + void dht_tracker::start(find_data::nodes_callback const& f) + { + m_running = true; + error_code ec; + + ADD_OUTSTANDING_ASYNC("dht_tracker::refresh_key"); + refresh_key(ec); + + for (auto& n : m_nodes) + { + ADD_OUTSTANDING_ASYNC("dht_tracker::connection_timeout"); + n.second.connection_timer.expires_from_now(seconds(1), ec); + n.second.connection_timer.async_wait( + std::bind(&dht_tracker::connection_timeout, self(), n.first, _1)); + if (is_v6(n.first.get_local_endpoint())) + n.second.dht.bootstrap(concat(m_state.nodes6, m_state.nodes), f); + else + n.second.dht.bootstrap(concat(m_state.nodes, m_state.nodes6), f); + } + + ADD_OUTSTANDING_ASYNC("dht_tracker::refresh_timeout"); + m_refresh_timer.expires_from_now(seconds(5), ec); + m_refresh_timer.async_wait(std::bind(&dht_tracker::refresh_timeout, self(), _1)); + + m_state.clear(); + } + + void dht_tracker::stop() + { + m_running = false; + error_code ec; + m_key_refresh_timer.cancel(ec); + for (auto& n : m_nodes) + n.second.connection_timer.cancel(ec); + m_refresh_timer.cancel(ec); + m_host_resolver.cancel(); + } + +#if TORRENT_ABI_VERSION == 1 + void dht_tracker::dht_status(session_status& s) + { + s.dht_torrents += int(m_storage.num_torrents()); + + s.dht_nodes = 0; + s.dht_node_cache = 0; + s.dht_global_nodes = 0; + s.dht_torrents = 0; + s.active_requests.clear(); + s.dht_total_allocations = 0; + + for (auto& n : m_nodes) + n.second.dht.status(s); + } +#endif + + void dht_tracker::dht_status(std::vector& table + , std::vector& requests) + { + for (auto& n : m_nodes) + n.second.dht.status(table, requests); + } + + void dht_tracker::update_stats_counters(counters& c) const + { + const dht_storage_counters& dht_cnt = m_storage.counters(); + c.set_value(counters::dht_torrents, dht_cnt.torrents); + c.set_value(counters::dht_peers, dht_cnt.peers); + c.set_value(counters::dht_immutable_data, dht_cnt.immutable_data); + c.set_value(counters::dht_mutable_data, dht_cnt.mutable_data); + + c.set_value(counters::dht_nodes, 0); + c.set_value(counters::dht_node_cache, 0); + c.set_value(counters::dht_allocated_observers, 0); + + for (auto& n : m_nodes) + add_dht_counters(n.second.dht, c); + } + + void dht_tracker::connection_timeout(aux::listen_socket_handle const& s, error_code const& e) + { + COMPLETE_ASYNC("dht_tracker::connection_timeout"); + if (e || !m_running) return; + + auto const it = m_nodes.find(s); + // this could happen if the task is about to be executed (and not cancellable) and + // the socket is just removed + if (it == m_nodes.end()) return; // node already destroyed + + tracker_node& n = it->second; + time_duration const d = n.dht.connection_timeout(); + error_code ec; + deadline_timer& timer = n.connection_timer; + timer.expires_from_now(d, ec); + ADD_OUTSTANDING_ASYNC("dht_tracker::connection_timeout"); + timer.async_wait(std::bind(&dht_tracker::connection_timeout, self(), s, _1)); + } + + void dht_tracker::refresh_timeout(error_code const& e) + { + COMPLETE_ASYNC("dht_tracker::refresh_timeout"); + if (e || !m_running) return; + + for (auto& n : m_nodes) + n.second.dht.tick(); + + // periodically update the DOS blocker's settings from the dht_settings + m_blocker.set_block_timer(m_settings.block_timeout); + m_blocker.set_rate_limit(m_settings.block_ratelimit); + + error_code ec; + m_refresh_timer.expires_from_now(seconds(5), ec); + ADD_OUTSTANDING_ASYNC("dht_tracker::refresh_timeout"); + m_refresh_timer.async_wait( + std::bind(&dht_tracker::refresh_timeout, self(), _1)); + } + + void dht_tracker::refresh_key(error_code const& e) + { + COMPLETE_ASYNC("dht_tracker::refresh_key"); + if (e || !m_running) return; + + ADD_OUTSTANDING_ASYNC("dht_tracker::refresh_key"); + error_code ec; + m_key_refresh_timer.expires_from_now(key_refresh, ec); + m_key_refresh_timer.async_wait(std::bind(&dht_tracker::refresh_key, self(), _1)); + + for (auto& n : m_nodes) + n.second.dht.new_write_key(); + +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::tracker, "*** new write key***"); +#endif + } + + void dht_tracker::update_storage_node_ids() + { + std::vector ids; + for (auto& n : m_nodes) + ids.push_back(n.second.dht.nid()); + m_storage.update_node_ids(ids); + } + + node* dht_tracker::get_node(node_id const& id, std::string const& family_name) + { + TORRENT_UNUSED(id); + for (auto& n : m_nodes) + { + // TODO: pick the closest node rather than the first + if (n.second.dht.protocol_family_name() == family_name) + return &n.second.dht; + } + + return nullptr; + } + + void dht_tracker::get_peers(sha1_hash const& ih + , std::function const&)> f) + { + for (auto& n : m_nodes) + n.second.dht.get_peers(ih, f, {}, {}); + } + + void dht_tracker::announce(sha1_hash const& ih, int listen_port + , announce_flags_t const flags + , std::function const&)> f) + { + for (auto& n : m_nodes) + n.second.dht.announce(ih, listen_port, flags, f); + } + + void dht_tracker::sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f) + { + for (auto& n : m_nodes) + { + if (ep.protocol() != (n.first.get_external_address().is_v4() ? udp::v4() : udp::v6())) + continue; + n.second.dht.sample_infohashes(ep, target, f); + break; + } + } + + namespace { + + struct get_immutable_item_ctx + { + explicit get_immutable_item_ctx(int traversals) + : active_traversals(traversals) + , item_posted(false) + {} + int active_traversals; + bool item_posted; + }; + + // these functions provide a slightly higher level + // interface to the get/put functionality in the DHT + void get_immutable_item_callback(item const& it + , std::shared_ptr ctx + , std::function f) + { + // the reason to wrap here is to control the return value + // since it controls whether we re-put the content + TORRENT_ASSERT(!it.is_mutable()); + --ctx->active_traversals; + if (!ctx->item_posted && (!it.empty() || ctx->active_traversals == 0)) + { + ctx->item_posted = true; + f(it); + } + } + + struct get_mutable_item_ctx + { + explicit get_mutable_item_ctx(int traversals) : active_traversals(traversals) {} + int active_traversals; + item it; + }; + + void get_mutable_item_callback(item const& it, bool authoritative + , std::shared_ptr ctx + , std::function f) + { + TORRENT_ASSERT(it.is_mutable()); + if (authoritative) --ctx->active_traversals; + authoritative = authoritative && ctx->active_traversals == 0; + if ((ctx->it.empty() && !it.empty()) || (ctx->it.seq() < it.seq())) + { + ctx->it = it; + f(it, authoritative); + } + else if (authoritative) + f(it, authoritative); + } + + struct put_item_ctx + { + explicit put_item_ctx(int traversals) + : active_traversals(traversals) + , response_count(0) + {} + + int active_traversals; + int response_count; + }; + + void put_immutable_item_callback(int responses, std::shared_ptr ctx + , std::function f) + { + ctx->response_count += responses; + if (--ctx->active_traversals == 0) + f(ctx->response_count); + } + + void put_mutable_item_callback(item const& it, int responses, std::shared_ptr ctx + , std::function cb) + { + ctx->response_count += responses; + if (--ctx->active_traversals == 0) + cb(it, ctx->response_count); + } + + } // anonymous namespace + + void dht_tracker::get_item(sha1_hash const& target + , std::function cb) + { + auto ctx = std::make_shared(int(m_nodes.size())); + for (auto& n : m_nodes) + n.second.dht.get_item(target, std::bind(&get_immutable_item_callback, _1, ctx, cb)); + } + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + void dht_tracker::get_item(public_key const& key + , std::function cb + , std::string salt) + { + auto ctx = std::make_shared(int(m_nodes.size())); + for (auto& n : m_nodes) + n.second.dht.get_item(key, salt, std::bind(&get_mutable_item_callback, _1, _2, ctx, cb)); + } + + void dht_tracker::put_item(entry const& data + , std::function cb) + { + std::string flat_data; + bencode(std::back_inserter(flat_data), data); + sha1_hash const target = item_target_id(flat_data); + + auto ctx = std::make_shared(int(m_nodes.size())); + for (auto& n : m_nodes) + n.second.dht.put_item(target, data, std::bind(&put_immutable_item_callback + , _1, ctx, cb)); + } + + void dht_tracker::put_item(public_key const& key + , std::function cb + , std::function data_cb, std::string salt) + { + auto ctx = std::make_shared(int(m_nodes.size())); + for (auto& n : m_nodes) + n.second.dht.put_item(key, salt, std::bind(&put_mutable_item_callback + , _1, _2, ctx, cb), data_cb); + } + + void dht_tracker::direct_request(udp::endpoint const& ep, entry& e + , std::function f) + { + for (auto& n : m_nodes) + { + if (ep.protocol() != (n.first.get_external_address().is_v4() ? udp::v4() : udp::v6())) + continue; + n.second.dht.direct_request(ep, e, f); + break; + } + } + + void dht_tracker::incoming_error(error_code const& ec, udp::endpoint const& ep) + { + if (ec == boost::asio::error::connection_refused + || ec == boost::asio::error::connection_reset + || ec == boost::asio::error::connection_aborted +#ifdef _WIN32 + || ec == error_code(ERROR_HOST_UNREACHABLE, system_category()) + || ec == error_code(ERROR_PORT_UNREACHABLE, system_category()) + || ec == error_code(ERROR_CONNECTION_REFUSED, system_category()) + || ec == error_code(ERROR_CONNECTION_ABORTED, system_category()) +#endif + ) + { + for (auto& n : m_nodes) + n.second.dht.unreachable(ep); + } + } + + bool dht_tracker::incoming_packet(aux::listen_socket_handle const& s + , udp::endpoint const& ep, span const buf) + { + int const buf_size = int(buf.size()); + if (buf_size <= 20 + || buf.front() != 'd' + || buf.back() != 'e') return false; + + m_counters.inc_stats_counter(counters::dht_bytes_in, buf_size); + // account for IP and UDP overhead + m_counters.inc_stats_counter(counters::recv_ip_overhead_bytes + , is_v6(ep) ? 48 : 28); + m_counters.inc_stats_counter(counters::dht_messages_in); + + if (m_settings.ignore_dark_internet && is_v4(ep)) + { + address_v4::bytes_type b = ep.address().to_v4().to_bytes(); + + // these are class A networks not available to the public + // if we receive messages from here, that seems suspicious + static std::uint8_t const class_a[] = { 3, 6, 7, 9, 11, 19, 21, 22, 25 + , 26, 28, 29, 30, 33, 34, 48, 56 }; + + if (std::find(std::begin(class_a), std::end(class_a), b[0]) != std::end(class_a)) + { + m_counters.inc_stats_counter(counters::dht_messages_in_dropped); + return true; + } + } + + if (!m_blocker.incoming(ep.address(), clock_type::now(), m_log)) + { + m_counters.inc_stats_counter(counters::dht_messages_in_dropped); + return true; + } + + TORRENT_ASSERT(buf_size > 0); + + int pos; + error_code err; + int const ret = bdecode(buf.data(), buf.data() + buf_size, m_msg, err, &pos, 10, 500); + if (ret != 0) + { + m_counters.inc_stats_counter(counters::dht_messages_in_dropped); +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::incoming_message, buf, ep); +#endif + return false; + } + + if (m_msg.type() != bdecode_node::dict_t) + { + m_counters.inc_stats_counter(counters::dht_messages_in_dropped); +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::incoming_message, buf, ep); +#endif + // it's not a good idea to send a response to an invalid messages + return false; + } + +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::incoming_message, buf, ep); +#endif + + libtorrent::dht::msg const m(m_msg, ep); + for (auto& n : m_nodes) + n.second.dht.incoming(s, m); + return true; + } + + dht_tracker::tracker_node::tracker_node(io_service& ios + , aux::listen_socket_handle const& s, socket_manager* sock + , dht::settings const& settings + , node_id const& nid + , dht_observer* observer, counters& cnt + , get_foreign_node_t get_foreign_node + , dht_storage_interface& storage) + : dht(s, sock, settings, nid, observer, cnt, std::move(get_foreign_node), storage) + , connection_timer(ios) + {} + + std::vector> dht_tracker::live_nodes(node_id const& nid) + { + std::vector> ret; + + auto n = std::find_if(m_nodes.begin(), m_nodes.end() + , [&](tracker_nodes_t::value_type const& v) { return v.second.dht.nid() == nid; }); + + if (n != m_nodes.end()) + { + n->second.dht.m_table.for_each_node([&ret](node_entry const& e) + { ret.emplace_back(e.id, e.endpoint); }, nullptr); + } + + return ret; + } + +namespace { + + std::vector save_nodes(node const& dht) + { + std::vector ret; + + dht.m_table.for_each_node([&ret](node_entry const& e) + { ret.push_back(e.ep()); }); + + return ret; + } + +} // anonymous namespace + + dht_state dht_tracker::state() const + { + dht_state ret; + for (auto& n : m_nodes) + { + // use the local rather than external address because if the user is behind NAT + // we won't know the external IP on startup + ret.nids.emplace_back(n.first.get_local_endpoint().address(), n.second.dht.nid()); + auto nodes = save_nodes(n.second.dht); + ret.nodes.insert(ret.nodes.end(), nodes.begin(), nodes.end()); + } + return ret; + } + + void dht_tracker::add_node(udp::endpoint const& node) + { + for (auto& n : m_nodes) + n.second.dht.add_node(node); + } + + void dht_tracker::add_router_node(udp::endpoint const& node) + { + for (auto& n : m_nodes) + n.second.dht.add_router_node(node); + } + + bool dht_tracker::has_quota() + { + time_point const now = clock_type::now(); + time_duration const delta = now - m_last_tick; + m_last_tick = now; + + // add any new quota we've accrued since last time + m_send_quota += int(std::int64_t(m_settings.upload_rate_limit) + * total_microseconds(delta) / 1000000); + + // allow 3 seconds worth of burst + if (m_send_quota > 3 * m_settings.upload_rate_limit) + m_send_quota = 3 * m_settings.upload_rate_limit; + + return m_send_quota > 0; + } + + bool dht_tracker::send_packet(aux::listen_socket_handle const& s, entry& e, udp::endpoint const& addr) + { + TORRENT_ASSERT(m_nodes.find(s) != m_nodes.end()); + + static_assert(LIBTORRENT_VERSION_MINOR < 16, "version number not supported by DHT"); + static_assert(LIBTORRENT_VERSION_TINY < 16, "version number not supported by DHT"); + static char const version_str[] = {'L', 'T' + , LIBTORRENT_VERSION_MAJOR, (LIBTORRENT_VERSION_MINOR << 4) | LIBTORRENT_VERSION_TINY}; + e["v"] = std::string(version_str, version_str + 4); + + m_send_buf.clear(); + bencode(std::back_inserter(m_send_buf), e); + + // update the quota. We won't prevent the packet to be sent if we exceed + // the quota, we'll just (potentially) block the next incoming request. + + m_send_quota -= int(m_send_buf.size()); + + error_code ec; + if (s.get_local_endpoint().protocol().family() != addr.protocol().family()) + { + // the node is trying to send a packet to a different address family + // than its socket, this can happen during bootstrap + // pick a node with the right address family and use its socket + auto n = std::find_if(m_nodes.begin(), m_nodes.end() + , [&](tracker_nodes_t::value_type const& v) + { return v.first.get_local_endpoint().protocol().family() == addr.protocol().family(); }); + + if (n != m_nodes.end()) + m_send_fun(n->first, addr, m_send_buf, ec, {}); + else + ec = boost::asio::error::address_family_not_supported; + } + else + { + m_send_fun(s, addr, m_send_buf, ec, {}); + } + + if (ec) + { + m_counters.inc_stats_counter(counters::dht_messages_out_dropped); +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::outgoing_message, m_send_buf, addr); +#endif + return false; + } + + m_counters.inc_stats_counter(counters::dht_bytes_out, int(m_send_buf.size())); + // account for IP and UDP overhead + m_counters.inc_stats_counter(counters::sent_ip_overhead_bytes + , is_v6(addr) ? 48 : 28); + m_counters.inc_stats_counter(counters::dht_messages_out); +#ifndef TORRENT_DISABLE_LOGGING + m_log->log_packet(dht_logger::outgoing_message, m_send_buf, addr); +#endif + return true; + } + +}} diff --git a/src/kademlia/dos_blocker.cpp b/src/kademlia/dos_blocker.cpp new file mode 100644 index 0000000..51d1154 --- /dev/null +++ b/src/kademlia/dos_blocker.cpp @@ -0,0 +1,113 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/dos_blocker.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" // for print_address +#include "libtorrent/kademlia/dht_observer.hpp" // for dht_logger +#endif + +namespace libtorrent { namespace dht { + + dos_blocker::dos_blocker() + : m_message_rate_limit(5) + , m_block_timeout(5 * 60) + { + for (auto& e : m_ban_nodes) + { + e.count = 0; + e.limit = min_time(); + } + } + + bool dos_blocker::incoming(address const& addr, time_point const now, dht_logger* logger) + { + TORRENT_UNUSED(logger); + node_ban_entry* match = nullptr; + node_ban_entry* min = m_ban_nodes; + for (node_ban_entry* i = m_ban_nodes; i < m_ban_nodes + num_ban_nodes; ++i) + { + if (i->src == addr) + { + match = i; + break; + } + if (i->count < min->count) min = i; + else if (i->count == min->count + && i->limit < min->limit) min = i; + } + + if (match) + { + ++match->count; + + if (match->count >= m_message_rate_limit * 10) + { + if (now < match->limit) + { + if (match->count == m_message_rate_limit * 10) + { +#ifndef TORRENT_DISABLE_LOGGING + if (logger != nullptr && logger->should_log(dht_logger::tracker)) + { + logger->log(dht_logger::tracker, "BANNING PEER [ ip: %s time: %d ms count: %d ]" + , print_address(addr).c_str() + , int(total_milliseconds((now - match->limit) + seconds(10))) + , match->count); + } +#else + TORRENT_UNUSED(logger); +#endif // TORRENT_DISABLE_LOGGING + // we've received too many messages in less than 10 seconds + // from this node. Ignore it until it's silent for 5 minutes + match->limit = now + seconds(m_block_timeout); + } + + return false; + } + + // the messages we received from this peer took more than 10 + // seconds. Reset the counter and the timer + match->count = 0; + match->limit = now + seconds(10); + } + } + else + { + min->count = 1; + min->limit = now + seconds(10); + min->src = addr; + } + return true; + } +}} diff --git a/src/kademlia/ed25519.cpp b/src/kademlia/ed25519.cpp new file mode 100644 index 0000000..183514d --- /dev/null +++ b/src/kademlia/ed25519.cpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2016, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +namespace libtorrent { namespace dht { + + std::array ed25519_create_seed() + { + std::array seed; + aux::random_bytes(seed); + return seed; + } + + std::tuple ed25519_create_keypair( + std::array const& seed) + { + public_key pk; + secret_key sk; + + auto const pk_ptr = reinterpret_cast(pk.bytes.data()); + auto const sk_ptr = reinterpret_cast(sk.bytes.data()); + auto const seed_ptr = reinterpret_cast(seed.data()); + + libtorrent::ed25519_create_keypair(pk_ptr, sk_ptr, seed_ptr); + + return std::make_tuple(pk, sk); + } + + signature ed25519_sign(span msg + , public_key const& pk, secret_key const& sk) + { + signature sig; + + auto const sig_ptr = reinterpret_cast(sig.bytes.data()); + auto const msg_ptr = reinterpret_cast(msg.data()); + auto const pk_ptr = reinterpret_cast(pk.bytes.data()); + auto const sk_ptr = reinterpret_cast(sk.bytes.data()); + + libtorrent::ed25519_sign(sig_ptr, msg_ptr, msg.size(), pk_ptr, sk_ptr); + + return sig; + } + + bool ed25519_verify(signature const& sig + , span msg, public_key const& pk) + { + auto const sig_ptr = reinterpret_cast(sig.bytes.data()); + auto const msg_ptr = reinterpret_cast(msg.data()); + auto const pk_ptr = reinterpret_cast(pk.bytes.data()); + + return libtorrent::ed25519_verify(sig_ptr, msg_ptr, msg.size(), pk_ptr) == 1; + } + + public_key ed25519_add_scalar(public_key const& pk + , std::array const& scalar) + { + public_key ret(pk.bytes.data()); + + auto const ret_ptr = reinterpret_cast(ret.bytes.data()); + auto const scalar_ptr = reinterpret_cast(scalar.data()); + + libtorrent::ed25519_add_scalar(ret_ptr, nullptr, scalar_ptr); + + return ret; + } + + secret_key ed25519_add_scalar(secret_key const& sk + , std::array const& scalar) + { + secret_key ret(sk.bytes.data()); + + auto const ret_ptr = reinterpret_cast(ret.bytes.data()); + auto const scalar_ptr = reinterpret_cast(scalar.data()); + + libtorrent::ed25519_add_scalar(nullptr, ret_ptr, scalar_ptr); + + return ret; + } + + std::array ed25519_key_exchange( + public_key const& pk, secret_key const& sk) + { + std::array secret; + + auto const secret_ptr = reinterpret_cast(secret.data()); + auto const pk_ptr = reinterpret_cast(pk.bytes.data()); + auto const sk_ptr = reinterpret_cast(sk.bytes.data()); + + libtorrent::ed25519_key_exchange(secret_ptr, pk_ptr, sk_ptr); + + return secret; + } + +}} diff --git a/src/kademlia/find_data.cpp b/src/kademlia/find_data.cpp new file mode 100644 index 0000000..4c4b0fc --- /dev/null +++ b/src/kademlia/find_data.cpp @@ -0,0 +1,193 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include // to_hex +#endif + +namespace libtorrent { namespace dht { + +void find_data_observer::reply(msg const& m) +{ + bdecode_node const r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] missing response dict" + , algorithm()->id()); +#endif + timeout(); + return; + } + + bdecode_node const id = r.dict_find_string("id"); + if (!id || id.string_length() != 20) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] invalid id in response" + , algorithm()->id()); +#endif + timeout(); + return; + } + bdecode_node const token = r.dict_find_string("token"); + if (token) + { + static_cast(algorithm())->got_write_token( + node_id(id.string_ptr()), token.string_value().to_string()); + } + + traversal_observer::reply(m); + done(); +} + +find_data::find_data( + node& dht_node + , node_id const& target + , nodes_callback const& ncallback) + : traversal_algorithm(dht_node, target) + , m_nodes_callback(ncallback) + , m_done(false) +{ +} + +void find_data::start() +{ + // if the user didn't add seed-nodes manually, grab k (bucket size) + // nodes from routing table. + if (m_results.empty()) + { + std::vector nodes; + m_node.m_table.find_node(target(), nodes, routing_table::include_failed); + + for (auto const& n : nodes) + { + add_entry(n.id, n.ep(), observer::flag_initial); + } + } + + traversal_algorithm::start(); +} + +void find_data::got_write_token(node_id const& n, std::string write_token) +{ +#ifndef TORRENT_DISABLE_LOGGING + auto logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] adding write token '%s' under id '%s'" + , id(), aux::to_hex(write_token).c_str() + , aux::to_hex(n).c_str()); + } +#endif + m_write_tokens[n] = std::move(write_token); +} + +observer_ptr find_data::new_observer(udp::endpoint const& ep + , node_id const& id) +{ + auto o = m_node.m_rpc.allocate_observer(self(), ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; +} + +char const* find_data::name() const { return "find_data"; } + +void find_data::done() +{ + m_done = true; + +#ifndef TORRENT_DISABLE_LOGGING + auto logger = get_node().observer(); + if (logger != nullptr) + { + logger->log(dht_logger::traversal, "[%u] %s DONE" + , id(), name()); + } +#endif + + std::vector> results; + int num_results = m_node.m_table.bucket_size(); + for (auto i = m_results.begin() + , end(m_results.end()); i != end && num_results > 0; ++i) + { + observer_ptr const& o = *i; + if (!(o->flags & observer::flag_alive)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal, "[%u] not alive: %s" + , id(), print_endpoint(o->target_ep()).c_str()); + } +#endif + continue; + } + auto j = m_write_tokens.find(o->id()); + if (j == m_write_tokens.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal, "[%u] no write token: %s" + , id(), print_endpoint(o->target_ep()).c_str()); + } +#endif + continue; + } + results.emplace_back(node_entry(o->id(), o->target_ep()), j->second); +#ifndef TORRENT_DISABLE_LOGGING + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal, "[%u] %s" + , id(), print_endpoint(o->target_ep()).c_str()); + } +#endif + --num_results; + } + + if (m_nodes_callback) m_nodes_callback(results); + + traversal_algorithm::done(); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/get_item.cpp b/src/kademlia/get_item.cpp new file mode 100644 index 0000000..7a1154c --- /dev/null +++ b/src/kademlia/get_item.cpp @@ -0,0 +1,217 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht { + +void get_item::got_data(bdecode_node const& v, + public_key const& pk, + sequence_number const seq, + signature const& sig) +{ + // we received data! + // if no data_callback, we needn't care about the data we get. + // only put_immutable_item no data_callback + if (!m_data_callback) return; + + // for get_immutable_item + if (m_immutable) + { + // If m_data isn't empty, we should have post alert. + if (!m_data.empty()) return; + + sha1_hash incoming_target = item_target_id(v.data_section()); + if (incoming_target != target()) return; + + m_data.assign(v); + + // There can only be one true immutable item with a given id + // Now that we've got it and the user doesn't want to do a put + // there's no point in continuing to query other nodes + m_data_callback(m_data, true); + done(); + + return; + } + + // immutable data should have been handled before this line, only mutable + // data can reach here, which means pk, sig and seq must be valid. + + std::string const salt_copy(m_data.salt()); + sha1_hash const incoming_target = item_target_id(salt_copy, pk); + if (incoming_target != target()) return; + + // this is mutable data. If it passes the signature + // check, remember it. Just keep the version with + // the highest sequence number. + if (m_data.empty() || m_data.seq() < seq) + { + if (!m_data.assign(v, salt_copy, seq, pk, sig)) + return; + + // for get_item, we should call callback when we get data, + // even if the date is not authoritative, we can update later. + // so caller can get response ASAP without waiting transaction + // time-out (15 seconds). + // for put_item, the callback function will do nothing + // if the data is non-authoritative. + m_data_callback(m_data, false); + } +} + +get_item::get_item( + node& dht_node + , node_id const& target + , data_callback const& dcallback + , nodes_callback const& ncallback) + : find_data(dht_node, target, ncallback) + , m_data_callback(dcallback) + , m_immutable(true) +{ +} + +get_item::get_item( + node& dht_node + , public_key const& pk + , span salt + , data_callback const& dcallback + , nodes_callback const& ncallback) + : find_data(dht_node, item_target_id(salt, pk), ncallback) + , m_data_callback(dcallback) + , m_data(pk, salt) + , m_immutable(false) +{ +} + +char const* get_item::name() const { return "get"; } + +observer_ptr get_item::new_observer(udp::endpoint const& ep + , node_id const& id) +{ + auto o = m_node.m_rpc.allocate_observer(self(), ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; +} + +bool get_item::invoke(observer_ptr o) +{ + if (m_done) return false; + + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get"; + a["target"] = target().to_string(); + + m_node.stats_counters().inc_stats_counter(counters::dht_get_out); + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +void get_item::done() +{ + // no data_callback for immutable item put + if (!m_data_callback) return find_data::done(); + + if (m_data.is_mutable() || m_data.empty()) + { + // for mutable data, now we have authoritative data since + // we've heard from everyone, to be sure we got the + // latest version of the data (i.e. highest sequence number) + m_data_callback(m_data, true); + +#if TORRENT_USE_ASSERTS + if (m_data.is_mutable()) + { + TORRENT_ASSERT(target() == item_target_id(m_data.salt(), m_data.pk())); + } +#endif + } + + find_data::done(); +} + +void get_item_observer::reply(msg const& m) +{ + public_key pk{}; + signature sig{}; + sequence_number seq{0}; + + bdecode_node const r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%p] missing response dict" + , static_cast(algorithm())); +#endif + timeout(); + return; + } + + bdecode_node const k = r.dict_find_string("k"); + if (k && k.string_length() == public_key::len) + std::memcpy(pk.bytes.data(), k.string_ptr(), public_key::len); + + bdecode_node const s = r.dict_find_string("sig"); + if (s && s.string_length() == signature::len) + std::memcpy(sig.bytes.data(), s.string_ptr(), signature::len); + + bdecode_node const q = r.dict_find_int("seq"); + if (q) + { + seq = sequence_number(q.int_value()); + } + else if (k && s) + { + timeout(); + return; + } + + bdecode_node v = r.dict_find("v"); + if (v) + { + static_cast(algorithm())->got_data(v, pk, seq, sig); + } + + find_data_observer::reply(m); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/get_peers.cpp b/src/kademlia/get_peers.cpp new file mode 100644 index 0000000..93af53b --- /dev/null +++ b/src/kademlia/get_peers.cpp @@ -0,0 +1,327 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include // for is_v4 + +#ifndef TORRENT_DISABLE_LOGGING +#include // to_hex +#endif + +namespace libtorrent { namespace dht { + +void get_peers_observer::reply(msg const& m) +{ + bdecode_node const r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] missing response dict" + , algorithm()->id()); +#endif + timeout(); + return; + } + + // look for peers + bdecode_node const n = r.dict_find_list("values"); + if (n) + { + std::vector peer_list; + if (n.list_size() == 1 && n.list_at(0).type() == bdecode_node::string_t + && is_v4(m.addr)) + { + // assume it's mainline format + char const* peers = n.list_at(0).string_ptr(); + char const* end = peers + n.list_at(0).string_length(); + +#ifndef TORRENT_DISABLE_LOGGING + log_peers(m, r, int((end - peers) / 6)); +#endif + while (end - peers >= 6) + peer_list.push_back(detail::read_v4_endpoint(peers)); + } + else + { + // assume it's uTorrent/libtorrent format + peer_list = detail::read_endpoint_list(n); +#ifndef TORRENT_DISABLE_LOGGING + log_peers(m, r, n.list_size()); +#endif + } + static_cast(algorithm())->got_peers(peer_list); + } + + find_data_observer::reply(m); +} +#ifndef TORRENT_DISABLE_LOGGING +void get_peers_observer::log_peers(msg const& m, bdecode_node const& r, int const size) const +{ + auto logger = get_observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + bdecode_node const id = r.dict_find_string("id"); + if (id && id.string_length() == 20) + { + logger->log(dht_logger::traversal, "[%u] PEERS " + "invoke-count: %d branch-factor: %d addr: %s id: %s distance: %d p: %d" + , algorithm()->id() + , algorithm()->invoke_count() + , algorithm()->branch_factor() + , print_endpoint(m.addr).c_str() + , aux::to_hex({id.string_ptr(), id.string_length()}).c_str() + , distance_exp(algorithm()->target(), node_id(id.string_ptr())) + , size); + } + } +} +#endif +void get_peers::got_peers(std::vector const& peers) +{ + if (m_data_callback) m_data_callback(peers); +} + +get_peers::get_peers( + node& dht_node + , node_id const& target + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds) + : find_data(dht_node, target, ncallback) + , m_data_callback(dcallback) + , m_noseeds(noseeds) +{ +} + +char const* get_peers::name() const { return "get_peers"; } + +bool get_peers::invoke(observer_ptr o) +{ + if (m_done) return false; + + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get_peers"; + a["info_hash"] = target().to_string(); + if (m_noseeds) a["noseed"] = 1; + + if (m_node.observer() != nullptr) + { + m_node.observer()->outgoing_get_peers(target(), target(), o->target_ep()); + } + + m_node.stats_counters().inc_stats_counter(counters::dht_get_peers_out); + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +observer_ptr get_peers::new_observer(udp::endpoint const& ep + , node_id const& id) +{ + auto o = m_node.m_rpc.allocate_observer(self(), ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; +} + +obfuscated_get_peers::obfuscated_get_peers( + node& dht_node + , node_id const& info_hash + , data_callback const& dcallback + , nodes_callback const& ncallback + , bool noseeds) + : get_peers(dht_node, info_hash, dcallback, ncallback, noseeds) + , m_obfuscated(true) +{ +} + +char const* obfuscated_get_peers::name() const +{ return !m_obfuscated ? get_peers::name() : "get_peers [obfuscated]"; } + +observer_ptr obfuscated_get_peers::new_observer(udp::endpoint const& ep + , node_id const& id) +{ + if (m_obfuscated) + { + auto o = m_node.m_rpc.allocate_observer(self() + , ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; + } + else + { + auto o = m_node.m_rpc.allocate_observer(self() + , ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; + } +} + +bool obfuscated_get_peers::invoke(observer_ptr o) +{ + if (!m_obfuscated) return get_peers::invoke(o); + + node_id const& id = o->id(); + int const shared_prefix = 160 - distance_exp(id, target()); + + // when we get close to the target zone in the DHT + // start using the correct info-hash, in order to + // start receiving peers + if (shared_prefix > m_node.m_table.depth() - 4) + { + m_obfuscated = false; + // clear the queried bits on all successful nodes in + // our node-list for this traversal algorithm, to + // allow the get_peers traversal to regress in case + // nodes further down end up being dead + for (auto const& node : m_results) + { + // don't re-request from nodes that didn't respond + if (node->flags & observer::flag_failed) continue; + // don't interrupt with queries that are already in-flight + if (!(node->flags & observer::flag_alive)) continue; + node->flags &= ~(observer::flag_queried | observer::flag_alive); + } + return get_peers::invoke(o); + } + + entry e; + e["y"] = "q"; + e["q"] = "get_peers"; + entry& a = e["a"]; + + // This logic will obfuscate the target info-hash + // we're looking up, in order to preserve more privacy + // on the DHT. This is done by only including enough + // bits in the info-hash for the node we're querying to + // give a good answer, but not more. + + // now, obfuscate the bits past shared_prefix + 3 + node_id mask = generate_prefix_mask(shared_prefix + 3); + node_id obfuscated_target = generate_random_id() & ~mask; + obfuscated_target |= target() & mask; + a["info_hash"] = obfuscated_target.to_string(); + + if (m_node.observer() != nullptr) + { + m_node.observer()->outgoing_get_peers(target(), obfuscated_target + , o->target_ep()); + } + + m_node.stats_counters().inc_stats_counter(counters::dht_get_peers_out); + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +void obfuscated_get_peers::done() +{ + if (!m_obfuscated) return get_peers::done(); + + // oops, we failed to switch over to the non-obfuscated + // mode early enough. do it now + + auto ta = std::make_shared(m_node, target() + , m_data_callback, m_nodes_callback, m_noseeds); + + // don't call these when the obfuscated_get_peers + // is done, we're passing them on to be called when + // ta completes. + m_data_callback = nullptr; + m_nodes_callback = nullptr; + +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%u] obfuscated get_peers " + "phase 1 done, spawning get_peers [ %u ]" + , id(), ta->id()); +#endif + + int num_added = 0; + for (auto i = m_results.begin() + , end(m_results.end()); i != end && num_added < 16; ++i) + { + observer_ptr o = *i; + + // only add nodes whose node ID we know and that + // we know are alive + if (o->flags & observer::flag_no_id) continue; + if (!(o->flags & observer::flag_alive)) continue; + + ta->add_entry(o->id(), o->target_ep(), observer::flag_initial); + ++num_added; + } + + ta->start(); + + get_peers::done(); +} + +void obfuscated_get_peers_observer::reply(msg const& m) +{ + bdecode_node const r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] missing response dict" + , algorithm()->id()); +#endif + timeout(); + return; + } + + bdecode_node const id = r.dict_find_string("id"); + if (!id || id.string_length() != 20) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] invalid id in response" + , algorithm()->id()); +#endif + timeout(); + return; + } + + traversal_observer::reply(m); + + done(); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/item.cpp b/src/kademlia/item.cpp new file mode 100644 index 0000000..5489c37 --- /dev/null +++ b/src/kademlia/item.cpp @@ -0,0 +1,215 @@ +/* + +Copyright (c) 2013, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include // for snprintf +#include // for PRId64 et.al. +#include // for copy + +#if TORRENT_USE_ASSERTS +#include "libtorrent/bdecode.hpp" +#endif + +namespace libtorrent { namespace dht { + +namespace { + + int canonical_string(span v + , sequence_number const seq + , span salt + , span out) + { + // v must be valid bencoding! +#if TORRENT_USE_ASSERTS + bdecode_node e; + error_code ec; + TORRENT_ASSERT(bdecode(v.data(), v.data() + v.size(), e, ec) == 0); +#endif + char* ptr = out.data(); + + auto left = out.size() - (ptr - out.data()); + if (!salt.empty()) + { + ptr += std::snprintf(ptr, static_cast(left), "4:salt%d:", int(salt.size())); + left = out.size() - (ptr - out.data()); + std::copy(salt.begin(), salt.begin() + std::min(salt.size(), left), ptr); + ptr += std::min(salt.size(), left); + left = out.size() - (ptr - out.data()); + } + ptr += std::snprintf(ptr, static_cast(left), "3:seqi%" PRId64 "e1:v", seq.value); + left = out.size() - (ptr - out.data()); + std::copy(v.begin(), v.begin() + std::min(v.size(), left), ptr); + ptr += std::min(v.size(), left); + TORRENT_ASSERT((ptr - out.data()) <= int(out.size())); + return int(ptr - out.data()); + } +} + +// calculate the target hash for an immutable item. +sha1_hash item_target_id(span v) +{ + return hasher(v).final(); +} + +// calculate the target hash for a mutable item. +sha1_hash item_target_id(span salt + , public_key const& pk) +{ + hasher h(pk.bytes); + if (!salt.empty()) h.update(salt); + return h.final(); +} + +bool verify_mutable_item( + span v + , span salt + , sequence_number const seq + , public_key const& pk + , signature const& sig) +{ + char str[1200]; + int len = canonical_string(v, seq, salt, str); + + return ed25519_verify(sig, {str, len}, pk); +} + +// given the bencoded buffer ``v``, the salt (which is optional and may have +// a length of zero to be omitted), sequence number ``seq``, public key (32 +// bytes ed25519 key) ``pk`` and a secret/private key ``sk`` (64 bytes ed25519 +// key) a signature ``sig`` is produced. The ``sig`` pointer must point to +// at least 64 bytes of available space. This space is where the signature is +// written. +signature sign_mutable_item( + span v + , span salt + , sequence_number const seq + , public_key const& pk + , secret_key const& sk) +{ + char str[1200]; + int const len = canonical_string(v, seq, salt, str); + + return ed25519_sign({str, len}, pk, sk); +} + +item::item(public_key const& pk, span salt) + : m_salt(salt.data(), static_cast(salt.size())) + , m_pk(pk) + , m_seq(0) + , m_mutable(true) +{} + +item::item(entry v) + : m_value(std::move(v)) + , m_seq(0) + , m_mutable(false) +{} + +item::item(bdecode_node const& v) + : m_seq(0) + , m_mutable(false) +{ + // TODO: implement ctor for entry from bdecode_node? + m_value = v; +} + +item::item(entry v, span salt + , sequence_number const seq, public_key const& pk, secret_key const& sk) +{ + assign(std::move(v), salt, seq, pk, sk); +} + +void item::assign(entry v) +{ + m_mutable = false; + m_value = std::move(v); +} + +void item::assign(entry v, span salt + , sequence_number const seq, public_key const& pk, secret_key const& sk) +{ + std::array buffer; + int const bsize = bencode(buffer.begin(), v); + TORRENT_ASSERT(bsize <= 1000); + m_sig = sign_mutable_item(span(buffer).first(bsize) + , salt, seq, pk, sk); + m_salt.assign(salt.data(), static_cast(salt.size())); + m_pk = pk; + m_seq = seq; + m_mutable = true; + m_value = std::move(v); +} + +void item::assign(bdecode_node const& v) +{ + m_mutable = false; + m_value = v; +} + +bool item::assign(bdecode_node const& v, span salt + , sequence_number const seq, public_key const& pk, signature const& sig) +{ + TORRENT_ASSERT(v.data_section().size() <= 1000); + if (!verify_mutable_item(v.data_section(), salt, seq, pk, sig)) + return false; + m_pk = pk; + m_sig = sig; + if (!salt.empty()) + m_salt.assign(salt.data(), static_cast(salt.size())); + else + m_salt.clear(); + m_seq = seq; + m_mutable = true; + + m_value = v; + return true; +} + +void item::assign(entry v, span salt + , sequence_number const seq + , public_key const& pk, signature const& sig) +{ + + m_pk = pk; + m_sig = sig; + m_salt.assign(salt.data(), static_cast(salt.size())); + m_seq = seq; + m_mutable = true; + m_value = std::move(v); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/msg.cpp b/src/kademlia/msg.cpp new file mode 100644 index 0000000..a057541 --- /dev/null +++ b/src/kademlia/msg.cpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/msg.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/entry.hpp" + +namespace libtorrent { namespace dht { + +bool verify_message_impl(bdecode_node const& message, span desc + , span ret, span error) +{ + TORRENT_ASSERT(desc.size() == ret.size()); + + auto const size = ret.size(); + + // get a non-root bdecode_node that still + // points to the root. message should not be copied + bdecode_node msg = message.non_owning(); + + // clear the return buffer + for (int i = 0; i < size; ++i) + ret[i].clear(); + + // when parsing child nodes, this is the stack + // of bdecode_nodes to return to + bdecode_node stack[5]; + int stack_ptr = -1; + + if (msg.type() != bdecode_node::dict_t) + { + std::snprintf(error.data(), static_cast(error.size()), "not a dictionary"); + return false; + } + ++stack_ptr; + stack[stack_ptr] = msg; + for (int i = 0; i < size; ++i) + { + key_desc_t const& k = desc[i]; + + // std::fprintf(stderr, "looking for %s in %s\n", k.name, print_entry(*msg).c_str()); + + ret[i] = msg.dict_find(k.name); + // none_t means any type + if (ret[i] && ret[i].type() != k.type && k.type != bdecode_node::none_t) + ret[i].clear(); + if (!ret[i] && (k.flags & key_desc_t::optional) == 0) + { + // the key was not found, and it's not an optional key + std::snprintf(error.data(), static_cast(error.size()), "missing '%s' key", k.name); + return false; + } + + if (k.size > 0 + && ret[i] + && k.type == bdecode_node::string_t) + { + bool const invalid = (k.flags & key_desc_t::size_divisible) + ? (ret[i].string_length() % k.size) != 0 + : ret[i].string_length() != k.size; + + if (invalid) + { + // the string was not of the required size + ret[i].clear(); + if ((k.flags & key_desc_t::optional) == 0) + { + std::snprintf(error.data(), static_cast(error.size()) + , "invalid value for '%s'", k.name); + return false; + } + } + } + if (k.flags & key_desc_t::parse_children) + { + TORRENT_ASSERT(k.type == bdecode_node::dict_t); + + if (ret[i]) + { + ++stack_ptr; + TORRENT_ASSERT(stack_ptr < int(sizeof(stack) / sizeof(stack[0]))); + msg = ret[i]; + stack[stack_ptr] = msg; + } + else + { + // skip all children + while (i < size && (desc[i].flags & key_desc_t::last_child) == 0) ++i; + // if this assert is hit, desc is incorrect + TORRENT_ASSERT(i < size); + } + } + else if (k.flags & key_desc_t::last_child) + { + TORRENT_ASSERT(stack_ptr > 0); + // this can happen if the specification passed + // in is unbalanced. i.e. contain more last_child + // nodes than parse_children + if (stack_ptr == 0) return false; + --stack_ptr; + msg = stack[stack_ptr]; + } + } + return true; +} + +} } diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp new file mode 100644 index 0000000..efb403e --- /dev/null +++ b/src/kademlia/node.cpp @@ -0,0 +1,1250 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include +#include // for PRId64 et.al. +#include +#include +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/hex.hpp" // to_hex +#endif + +#include +#include +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/random.hpp" +#include +#include +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/alert_types.hpp" // for dht_lookup +#include "libtorrent/performance_counters.hpp" // for counters + +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/direct_request.hpp" +#include "libtorrent/kademlia/io.hpp" + +#include "libtorrent/kademlia/refresh.hpp" +#include "libtorrent/kademlia/get_peers.hpp" +#include "libtorrent/kademlia/get_item.hpp" +#include "libtorrent/kademlia/msg.hpp" +#include +#include + +using namespace std::placeholders; + +namespace libtorrent { namespace dht { + +namespace { + +// the write tokens we generate are 4 bytes +constexpr int write_token_size = 4; + +void nop() {} + +node_id calculate_node_id(node_id const& nid, aux::listen_socket_handle const& sock) +{ + address external_address; + external_address = sock.get_external_address(); + + // if we don't have an observer, don't pretend that external_address is valid + // generating an ID based on 0.0.0.0 would be terrible. random is better + if (external_address.is_unspecified()) + { + return generate_random_id(); + } + + if (nid.is_all_zeros() || !verify_id(nid, external_address)) + return generate_id(external_address); + + return nid; +} + +// generate an error response message +void incoming_error(entry& e, char const* msg, int error_code = 203) +{ + e["y"] = "e"; + entry::list_type& l = e["e"].list(); + l.emplace_back(error_code); + l.emplace_back(msg); +} + +} // anonymous namespace + +node::node(aux::listen_socket_handle const& sock, socket_manager* sock_man + , dht::settings const& settings + , node_id const& nid + , dht_observer* observer + , counters& cnt + , get_foreign_node_t get_foreign_node + , dht_storage_interface& storage) + : m_settings(settings) + , m_id(calculate_node_id(nid, sock)) + , m_table(m_id, is_v4(sock.get_local_endpoint()) ? udp::v4() : udp::v6(), 8, settings, observer) + , m_rpc(m_id, m_settings, m_table, sock, sock_man, observer) + , m_sock(sock) + , m_sock_man(sock_man) + , m_get_foreign_node(std::move(get_foreign_node)) + , m_observer(observer) + , m_protocol(map_protocol_to_descriptor(is_v4(sock.get_local_endpoint()) ? udp::v4() : udp::v6())) + , m_last_tracker_tick(aux::time_now()) + , m_last_self_refresh(min_time()) + , m_counters(cnt) + , m_storage(storage) +{ + m_secret[0] = random(0xffffffff); + m_secret[1] = random(0xffffffff); +} + +node::~node() = default; + +void node::update_node_id() +{ + // if we don't have an observer, we can't ask for the external IP (and our + // current node ID is likely not generated from an external address), so we + // can just stop here in that case. + if (m_observer == nullptr) return; + + auto ext_address = m_sock.get_external_address(); + + // it's possible that our external address hasn't actually changed. If our + // current ID is still valid, don't do anything. + if (verify_id(m_id, ext_address)) + return; + +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr) m_observer->log(dht_logger::node + , "updating node ID (because external IP address changed)"); +#endif + + m_id = generate_id(ext_address); + + m_table.update_node_id(m_id); + m_rpc.update_node_id(m_id); +} + +bool node::verify_token(string_view token, sha1_hash const& info_hash + , udp::endpoint const& addr) const +{ + if (token.length() != write_token_size) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr) + { + m_observer->log(dht_logger::node, "token of incorrect length: %d" + , int(token.length())); + } +#endif + return false; + } + + hasher h1; + error_code ec; + std::string const address = addr.address().to_string(ec); + if (ec) return false; + h1.update(address); + h1.update(reinterpret_cast(&m_secret[0]), sizeof(m_secret[0])); + h1.update(info_hash); + + sha1_hash h = h1.final(); + if (std::equal(token.begin(), token.end(), reinterpret_cast(&h[0]))) + return true; + + hasher h2; + h2.update(address); + h2.update(reinterpret_cast(&m_secret[1]), sizeof(m_secret[1])); + h2.update(info_hash); + h = h2.final(); + return std::equal(token.begin(), token.end(), reinterpret_cast(&h[0])); +} + +std::string node::generate_token(udp::endpoint const& addr + , sha1_hash const& info_hash) +{ + std::string token; + token.resize(write_token_size); + hasher h; + error_code ec; + std::string const address = addr.address().to_string(ec); + TORRENT_ASSERT(!ec); + h.update(address); + h.update(reinterpret_cast(&m_secret[0]), sizeof(m_secret[0])); + h.update(info_hash); + + sha1_hash const hash = h.final(); + std::copy(hash.begin(), hash.begin() + write_token_size, token.begin()); + TORRENT_ASSERT(std::equal(token.begin(), token.end(), hash.data())); + return token; +} + +void node::bootstrap(std::vector const& nodes + , find_data::nodes_callback const& f) +{ + node_id target = m_id; + make_id_secret(target); + + auto r = std::make_shared(*this, target, f); + m_last_self_refresh = aux::time_now(); + +#ifndef TORRENT_DISABLE_LOGGING + int count = 0; +#endif + + for (auto const& n : nodes) + { +#ifndef TORRENT_DISABLE_LOGGING + ++count; +#endif + r->add_entry(node_id(), n, observer::flag_initial); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr) + m_observer->log(dht_logger::node, "bootstrapping with %d nodes", count); +#endif + r->start(); +} + +int node::bucket_size(int bucket) +{ + return m_table.bucket_size(bucket); +} + +void node::new_write_key() +{ + m_secret[1] = m_secret[0]; + m_secret[0] = random(0xffffffff); +} + +void node::unreachable(udp::endpoint const& ep) +{ + m_rpc.unreachable(ep); +} + +void node::incoming(aux::listen_socket_handle const& s, msg const& m) +{ + // is this a reply? + bdecode_node const y_ent = m.message.dict_find_string("y"); + if (!y_ent || y_ent.string_length() != 1) + { + // don't respond to this obviously broken messages. We don't + // want to open up a magnification opportunity +// entry e; +// incoming_error(e, "missing 'y' entry"); +// m_sock.send_packet(e, m.addr); + return; + } + + char const y = *(y_ent.string_ptr()); + + // we can only ascribe the external IP this node is saying we have to the + // listen socket the packet was received on + if (s == m_sock) + { + bdecode_node ext_ip = m.message.dict_find_string("ip"); + + // backwards compatibility + if (!ext_ip) + { + bdecode_node const r = m.message.dict_find_dict("r"); + if (r) + ext_ip = r.dict_find_string("ip"); + } + + if (ext_ip && ext_ip.string_length() >= int(detail::address_size(udp::v6()))) + { + // this node claims we use the wrong node-ID! + char const* ptr = ext_ip.string_ptr(); + if (m_observer != nullptr) + m_observer->set_external_address(m_sock, detail::read_v6_address(ptr) + , m.addr.address()); + } + else if (ext_ip && ext_ip.string_length() >= int(detail::address_size(udp::v4()))) + { + char const* ptr = ext_ip.string_ptr(); + if (m_observer != nullptr) + m_observer->set_external_address(m_sock, detail::read_v4_address(ptr) + , m.addr.address()); + } + } + + switch (y) + { + case 'r': + { + node_id id; + m_rpc.incoming(m, &id); + break; + } + case 'q': + { + TORRENT_ASSERT(m.message.dict_find_string_value("y") == "q"); + // When a DHT node enters the read-only state, it no longer + // responds to 'query' messages that it receives. + if (m_settings.read_only) break; + + // ignore packets arriving on a different interface than the one we're + // associated with + if (s != m_sock) return; + + if (!m_sock_man->has_quota()) + { + m_counters.inc_stats_counter(counters::dht_messages_in_dropped); + return; + } + + entry e; + incoming_request(m, e); + m_sock_man->send_packet(m_sock, e, m.addr); + break; + } + case 'e': + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + bdecode_node const err = m.message.dict_find_list("e"); + if (err && err.list_size() >= 2 + && err.list_at(0).type() == bdecode_node::int_t + && err.list_at(1).type() == bdecode_node::string_t) + { + m_observer->log(dht_logger::node, "INCOMING ERROR: (%" PRId64 ") %s" + , err.list_int_value_at(0) + , err.list_string_value_at(1).to_string().c_str()); + } + else + { + m_observer->log(dht_logger::node, "INCOMING ERROR (malformed)"); + } + } +#endif + node_id id; + m_rpc.incoming(m, &id); + break; + } + } +} + +namespace { + + void announce_fun(std::vector> const& v + , node& node, int const listen_port, sha1_hash const& ih, announce_flags_t const flags) + { +#ifndef TORRENT_DISABLE_LOGGING + auto logger = node.observer(); + if (logger != nullptr && logger->should_log(dht_logger::node)) + { + logger->log(dht_logger::node, "sending announce_peer [ ih: %s " + " p: %d nodes: %d ]", aux::to_hex(ih).c_str(), listen_port, int(v.size())); + } +#endif + + // create a dummy traversal_algorithm + auto algo = std::make_shared(node, node_id()); + // store on the first k nodes + for (auto const& p : v) + { +#ifndef TORRENT_DISABLE_LOGGING + if (logger != nullptr && logger->should_log(dht_logger::node)) + { + logger->log(dht_logger::node, "announce-distance: %d" + , (160 - distance_exp(ih, p.first.id))); + } +#endif + + auto o = node.m_rpc.allocate_observer(algo + , p.first.ep(), p.first.id); + if (!o) return; +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + entry e; + e["y"] = "q"; + e["q"] = "announce_peer"; + entry& a = e["a"]; + a["info_hash"] = ih; + a["port"] = listen_port; + a["token"] = p.second; + a["seed"] = (flags & announce::seed) ? 1 : 0; + if (flags & announce::implied_port) a["implied_port"] = 1; + node.stats_counters().inc_stats_counter(counters::dht_announce_peer_out); + node.m_rpc.invoke(e, p.first.ep(), o); + } + } +} + +void node::add_router_node(udp::endpoint const& router) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + m_observer->log(dht_logger::node, "adding router node: %s" + , print_endpoint(router).c_str()); + } +#endif + m_table.add_router_node(router); +} + +void node::add_node(udp::endpoint const& node) +{ + if (!native_address(node)) return; + // ping the node, and if we get a reply, it + // will be added to the routing table + send_single_refresh(node, m_table.num_active_buckets()); +} + +void node::get_peers(sha1_hash const& info_hash + , std::function const&)> dcallback + , std::function> const&)> ncallback + , announce_flags_t const flags) +{ + // search for nodes with ids close to id or with peers + // for info-hash id. then send announce_peer to them. + bool const noseeds = bool(flags & announce::seed); + + auto ta = m_settings.privacy_lookups + ? std::make_shared(*this, info_hash, dcallback, ncallback, noseeds) + : std::make_shared(*this, info_hash, dcallback, ncallback, noseeds); + + ta->start(); +} + +void node::announce(sha1_hash const& info_hash, int listen_port, announce_flags_t const flags + , std::function const&)> f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + m_observer->log(dht_logger::node, "announcing [ ih: %s p: %d ]" + , aux::to_hex(info_hash).c_str(), listen_port); + } +#endif + + if (listen_port == 0 && m_observer != nullptr) + { + listen_port = m_observer->get_listen_port( + (flags & announce::ssl_torrent) ? aux::transport::ssl : aux::transport::plaintext + , m_sock); + } + + get_peers(info_hash, std::move(f) + , std::bind(&announce_fun, _1, std::ref(*this) + , listen_port, info_hash, flags), flags); +} + +void node::direct_request(udp::endpoint const& ep, entry& e + , std::function f) +{ + // not really a traversal + auto algo = std::make_shared(*this, node_id(), f); + + auto o = m_rpc.allocate_observer(std::move(algo), ep, node_id()); + if (!o) return; +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + m_rpc.invoke(e, ep, o); +} + +void node::get_item(sha1_hash const& target + , std::function f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + m_observer->log(dht_logger::node, "starting get for [ hash: %s ]" + , aux::to_hex(target).c_str()); + } +#endif + + auto ta = std::make_shared(*this, target + , std::bind(f, _1), find_data::nodes_callback()); + ta->start(); +} + +void node::get_item(public_key const& pk, std::string const& salt + , std::function f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + char hex_key[65]; + aux::to_hex(pk.bytes, hex_key); + m_observer->log(dht_logger::node, "starting get for [ key: %s ]", hex_key); + } +#endif + + auto ta = std::make_shared(*this, pk, salt, f + , find_data::nodes_callback()); + ta->start(); +} + +namespace { + +void put(std::vector> const& nodes + , std::shared_ptr const& ta) +{ + ta->set_targets(nodes); + ta->start(); +} + +void put_data_cb(item const& i, bool auth + , std::shared_ptr const& ta + , std::function const& f) +{ + // call data_callback only when we got authoritative data. + if (auth) + { + item copy(i); + f(copy); + ta->set_data(std::move(copy)); + } +} + +} // namespace + +void node::put_item(sha1_hash const& target, entry const& data, std::function f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + m_observer->log(dht_logger::node, "starting put for [ hash: %s ]" + , aux::to_hex(target).c_str()); + } +#endif + + item i; + i.assign(data); + auto put_ta = std::make_shared(*this, std::bind(f, _2)); + put_ta->set_data(std::move(i)); + + auto ta = std::make_shared(*this, target + , get_item::data_callback(), std::bind(&put, _1, put_ta)); + ta->start(); +} + +void node::put_item(public_key const& pk, std::string const& salt + , std::function f + , std::function data_cb) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + char hex_key[65]; + aux::to_hex(pk.bytes, hex_key); + m_observer->log(dht_logger::node, "starting put for [ key: %s ]", hex_key); + } +#endif + + auto put_ta = std::make_shared(*this, f); + + auto ta = std::make_shared(*this, pk, salt + , std::bind(&put_data_cb, _1, _2, put_ta, data_cb) + , std::bind(&put, _1, put_ta)); + ta->start(); +} + +void node::sample_infohashes(udp::endpoint const& ep, sha1_hash const& target + , std::function + , std::vector>)> f) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_observer != nullptr && m_observer->should_log(dht_logger::node)) + { + m_observer->log(dht_logger::node, "starting sample_infohashes for [ node: %s, target: %s ]" + , print_endpoint(ep).c_str(), aux::to_hex(target).c_str()); + } +#endif + + // not an actual traversal + auto ta = std::make_shared(*this, node_id(), std::move(f)); + + auto o = m_rpc.allocate_observer(ta, ep, node_id()); + if (!o) return; +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + + entry e; + + e["q"] = "sample_infohashes"; + e["a"]["target"] = target; + + stats_counters().inc_stats_counter(counters::dht_sample_infohashes_out); + + m_rpc.invoke(e, ep, o); +} + +struct ping_observer : observer +{ + ping_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : observer(std::move(algorithm), ep, id) + {} + + // parses out "nodes" + void reply(msg const& m) override + { + flags |= flag_done; + + bdecode_node const r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_observer()) + { + get_observer()->log(dht_logger::node + , "[%p] missing response dict" + , static_cast(algorithm())); + } +#endif + return; + } + look_for_nodes(algorithm()->get_node().protocol_nodes_key(), algorithm()->get_node().protocol(), r, + [this](node_endpoint const& nep) { algorithm()->get_node().m_table.heard_about(nep.id, nep.ep); }); + } +}; + +void node::tick() +{ + // every now and then we refresh our own ID, just to keep + // expanding the routing table buckets closer to us. + // if m_table.depth() < 4, means routing_table doesn't + // have enough nodes. + time_point const now = aux::time_now(); + if (m_last_self_refresh + minutes(10) < now && m_table.depth() < 4) + { + node_id target = m_id; + make_id_secret(target); + auto const r = std::make_shared(*this, target, std::bind(&nop)); + r->start(); + m_last_self_refresh = now; + return; + } + + node_entry const* ne = m_table.next_refresh(); + if (ne == nullptr) return; + + // this shouldn't happen + TORRENT_ASSERT(m_id != ne->id); + if (ne->id == m_id) return; + + int const bucket = 159 - distance_exp(m_id, ne->id); + TORRENT_ASSERT(bucket < 160); + send_single_refresh(ne->ep(), bucket, ne->id); +} + +void node::send_single_refresh(udp::endpoint const& ep, int const bucket + , node_id const& id) +{ + TORRENT_ASSERT(id != m_id); + TORRENT_ASSERT(bucket >= 0); + TORRENT_ASSERT(bucket <= 159); + + // generate a random node_id within the given bucket + // TODO: 2 it would be nice to have a bias towards node-id prefixes that + // are missing in the bucket + node_id mask = generate_prefix_mask(bucket + 1); + node_id target = generate_secret_id() & ~mask; + target |= m_id & mask; + + // create a dummy traversal_algorithm + auto algo = std::make_shared(*this, node_id()); + auto o = m_rpc.allocate_observer(std::move(algo), ep, id); + if (!o) return; +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + entry e; + e["y"] = "q"; + + if (m_table.is_full(bucket)) + { + // current bucket is full, just ping it. + e["q"] = "ping"; + m_counters.inc_stats_counter(counters::dht_ping_out); + } + else + { + // use get_peers instead of find_node. We'll get nodes in the response + // either way. + e["q"] = "get_peers"; + e["a"]["info_hash"] = target.to_string(); + m_counters.inc_stats_counter(counters::dht_get_peers_out); + } + + m_rpc.invoke(e, ep, o); +} + +time_duration node::connection_timeout() +{ + time_duration d = m_rpc.tick(); + time_point now(aux::time_now()); + if (now - minutes(2) < m_last_tracker_tick) return d; + m_last_tracker_tick = now; + + m_storage.tick(); + + return d; +} + +void node::status(std::vector& table + , std::vector& requests) +{ + std::lock_guard l(m_mutex); + + m_table.status(table); + + for (auto const& r : m_running_requests) + { + requests.emplace_back(); + dht_lookup& lookup = requests.back(); + r->status(lookup); + } +} + +std::tuple node::get_stats_counters() const +{ + int nodes, replacements; + std::tie(nodes, replacements, std::ignore) = size(); + return std::make_tuple(nodes, replacements, m_rpc.num_allocated_observers()); +} + +#if TORRENT_ABI_VERSION == 1 +// TODO: 2 use the non deprecated function instead of this one +void node::status(session_status& s) +{ + std::lock_guard l(m_mutex); + + m_table.status(s); + s.dht_total_allocations += m_rpc.num_allocated_observers(); + for (auto& r : m_running_requests) + { + s.active_requests.emplace_back(); + dht_lookup& lookup = s.active_requests.back(); + r->status(lookup); + } +} +#endif + +bool node::lookup_peers(sha1_hash const& info_hash, entry& reply + , bool noseed, bool scrape, address const& requester) const +{ + if (m_observer) + m_observer->get_peers(info_hash); + + return m_storage.get_peers(info_hash, noseed, scrape, requester, reply); +} + +entry write_nodes_entry(std::vector const& nodes) +{ + entry r; + std::back_insert_iterator out(r.string()); + for (auto const& n : nodes) + { + std::copy(n.id.begin(), n.id.end(), out); + detail::write_endpoint(n.ep(), out); + } + return r; +} + +// build response +void node::incoming_request(msg const& m, entry& e) +{ + e = entry(entry::dictionary_t); + e["y"] = "r"; + e["t"] = m.message.dict_find_string_value("t").to_string(); + + static key_desc_t const top_desc[] = { + {"q", bdecode_node::string_t, 0, 0}, + {"ro", bdecode_node::int_t, 0, key_desc_t::optional}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + bdecode_node top_level[4]; + char error_string[200]; + if (!verify_message(m.message, top_desc, top_level, error_string)) + { + incoming_error(e, error_string); + return; + } + + e["ip"] = endpoint_to_bytes(m.addr); + + bdecode_node const arg_ent = top_level[2]; + bool const read_only = top_level[1] && top_level[1].int_value() != 0; + node_id const id(top_level[3].string_ptr()); + + // if this nodes ID doesn't match its IP, tell it what + // its IP is with an error + // don't enforce this yet + if (m_settings.enforce_node_id && !verify_id(id, m.addr.address())) + { + incoming_error(e, "invalid node ID"); + return; + } + + if (!read_only) + m_table.heard_about(id, m.addr); + + entry& reply = e["r"]; + m_rpc.add_our_id(reply); + + // mirror back the other node's external port + reply["p"] = m.addr.port(); + + string_view const query = top_level[0].string_value(); + + if (m_observer && m_observer->on_dht_request(query, m, e)) + return; + + if (query == "ping") + { + m_counters.inc_stats_counter(counters::dht_ping_in); + // we already have 't' and 'id' in the response + // no more left to add + } + else if (query == "get_peers") + { + static key_desc_t const msg_desc[] = { + {"info_hash", bdecode_node::string_t, 20, 0}, + {"noseed", bdecode_node::int_t, 0, key_desc_t::optional}, + {"scrape", bdecode_node::int_t, 0, key_desc_t::optional}, + {"want", bdecode_node::list_t, 0, key_desc_t::optional}, + }; + + bdecode_node msg_keys[4]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_get_peers); + incoming_error(e, error_string); + return; + } + + sha1_hash const info_hash(msg_keys[0].string_ptr()); + + m_counters.inc_stats_counter(counters::dht_get_peers_in); + + // always return nodes as well as peers + write_nodes_entries(info_hash, msg_keys[3], reply); + + bool const noseed = msg_keys[1] && msg_keys[1].int_value() != 0; + bool const scrape = msg_keys[2] && msg_keys[2].int_value() != 0; + // If our storage is full we want to withhold the write token so that + // announces will spill over to our neighbors. This widens the + // perimeter of nodes which store peers for this torrent + bool const full = lookup_peers(info_hash, reply, noseed, scrape, m.addr.address()); + if (!full) reply["token"] = generate_token(m.addr, info_hash); + +#ifndef TORRENT_DISABLE_LOGGING + if (reply.find_key("values") && m_observer) + { + m_observer->log(dht_logger::node, "values: %d" + , int(reply["values"].list().size())); + } +#endif + } + else if (query == "find_node") + { + static key_desc_t const msg_desc[] = { + {"target", bdecode_node::string_t, 20, 0}, + {"want", bdecode_node::list_t, 0, key_desc_t::optional}, + }; + + bdecode_node msg_keys[2]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_find_node); + incoming_error(e, error_string); + return; + } + + m_counters.inc_stats_counter(counters::dht_find_node_in); + sha1_hash const target(msg_keys[0].string_ptr()); + + write_nodes_entries(target, msg_keys[1], reply); + } + else if (query == "announce_peer") + { + static key_desc_t const msg_desc[] = { + {"info_hash", bdecode_node::string_t, 20, 0}, + {"port", bdecode_node::int_t, 0, 0}, + {"token", bdecode_node::string_t, 0, 0}, + {"n", bdecode_node::string_t, 0, key_desc_t::optional}, + {"seed", bdecode_node::int_t, 0, key_desc_t::optional}, + {"implied_port", bdecode_node::int_t, 0, key_desc_t::optional}, + }; + + bdecode_node msg_keys[6]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_announce); + incoming_error(e, error_string); + return; + } + + auto port = int(msg_keys[1].int_value()); + + // is the announcer asking to ignore the explicit + // listen port and instead use the source port of the packet? + if (msg_keys[5] && msg_keys[5].int_value() != 0) + port = m.addr.port(); + + if (port < 0 || port >= 65536) + { + m_counters.inc_stats_counter(counters::dht_invalid_announce); + incoming_error(e, "invalid port"); + return; + } + + sha1_hash const info_hash(msg_keys[0].string_ptr()); + + if (m_observer) + m_observer->announce(info_hash, m.addr.address(), port); + + if (!verify_token(msg_keys[2].string_value() + , sha1_hash(msg_keys[0].string_ptr()), m.addr)) + { + m_counters.inc_stats_counter(counters::dht_invalid_announce); + incoming_error(e, "invalid token"); + return; + } + + m_counters.inc_stats_counter(counters::dht_announce_peer_in); + + // the token was correct. That means this + // node is not spoofing its address. So, let + // the table get a chance to add it. + m_table.node_seen(id, m.addr, 0xffff); + + tcp::endpoint const addr = tcp::endpoint(m.addr.address(), std::uint16_t(port)); + string_view const name = msg_keys[3] ? msg_keys[3].string_value() : string_view(); + bool const seed = msg_keys[4] && msg_keys[4].int_value(); + + m_storage.announce_peer(info_hash, addr, name, seed); + } + else if (query == "put") + { + // the first 2 entries are for both mutable and + // immutable puts + static key_desc_t const msg_desc[] = { + {"token", bdecode_node::string_t, 0, 0}, + {"v", bdecode_node::none_t, 0, 0}, + {"seq", bdecode_node::int_t, 0, key_desc_t::optional}, + // public key + {"k", bdecode_node::string_t, public_key::len, key_desc_t::optional}, + {"sig", bdecode_node::string_t, signature::len, key_desc_t::optional}, + {"cas", bdecode_node::int_t, 0, key_desc_t::optional}, + {"salt", bdecode_node::string_t, 0, key_desc_t::optional}, + }; + + // attempt to parse the message + // also reject the message if it has any non-fatal encoding errors + // because put messages contain a signed value they must have correct bencoding + // otherwise the value will not round-trip without breaking the signature + bdecode_node msg_keys[7]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string) + || arg_ent.has_soft_error(error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, error_string); + return; + } + + m_counters.inc_stats_counter(counters::dht_put_in); + + // is this a mutable put? + bool const mutable_put = (msg_keys[2] && msg_keys[3] && msg_keys[4]); + + // public key (only set if it's a mutable put) + char const* pub_key = nullptr; + if (msg_keys[3]) pub_key = msg_keys[3].string_ptr(); + + // signature (only set if it's a mutable put) + char const* sign = nullptr; + if (msg_keys[4]) sign = msg_keys[4].string_ptr(); + + // pointer and length to the whole entry + span buf = msg_keys[1].data_section(); + if (buf.size() > 1000 || buf.empty()) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "message too big", 205); + return; + } + + span salt; + if (msg_keys[6]) + salt = {msg_keys[6].string_ptr(), msg_keys[6].string_length()}; + if (salt.size() > 64) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "salt too big", 207); + return; + } + + sha1_hash const target = pub_key + ? item_target_id(salt, public_key(pub_key)) + : item_target_id(buf); + +// std::fprintf(stderr, "%s PUT target: %s salt: %s key: %s\n" +// , mutable_put ? "mutable":"immutable" +// , aux::to_hex(target).c_str() +// , salt.second > 0 ? std::string(salt.first, salt.second).c_str() : "" +// , pk ? aux::to_hex(pk).c_str() : ""); + + // verify the write-token. tokens are only valid to write to + // specific target hashes. it must match the one we got a "get" for + if (!verify_token(msg_keys[0].string_value(), target, m.addr)) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "invalid token"); + return; + } + + if (!mutable_put) + { + m_storage.put_immutable_item(target, buf, m.addr.address()); + } + else + { + // mutable put, we must verify the signature + sequence_number const seq(msg_keys[2].int_value()); + public_key const pk(pub_key); + signature const sig(sign); + + if (seq < sequence_number(0)) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "invalid (negative) sequence number"); + return; + } + + // msg_keys[4] is the signature, msg_keys[3] is the public key + if (!verify_mutable_item(buf, salt, seq, pk, sig)) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "invalid signature", 206); + return; + } + + TORRENT_ASSERT(signature::len == msg_keys[4].string_length()); + + sequence_number item_seq; + if (!m_storage.get_mutable_item_seq(target, item_seq)) + { + m_storage.put_mutable_item(target, buf, sig, seq, pk, salt + , m.addr.address()); + } + else + { + // this is the "cas" field in the put message + // if it was specified, we MUST make sure the current sequence + // number matches the expected value before replacing it + // this is critical for avoiding race conditions when multiple + // writers are accessing the same slot + if (msg_keys[5] && item_seq.value != msg_keys[5].int_value()) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "CAS mismatch", 301); + return; + } + + if (item_seq > seq) + { + m_counters.inc_stats_counter(counters::dht_invalid_put); + incoming_error(e, "old sequence number", 302); + return; + } + + m_storage.put_mutable_item(target, buf, sig, seq, pk, salt + , m.addr.address()); + } + } + + m_table.node_seen(id, m.addr, 0xffff); + } + else if (query == "get") + { + static key_desc_t const msg_desc[] = { + {"seq", bdecode_node::int_t, 0, key_desc_t::optional}, + {"target", bdecode_node::string_t, 20, 0}, + {"want", bdecode_node::list_t, 0, key_desc_t::optional}, + }; + + // k is not used for now + + // attempt to parse the message + bdecode_node msg_keys[3]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_get); + incoming_error(e, error_string); + return; + } + + m_counters.inc_stats_counter(counters::dht_get_in); + sha1_hash const target(msg_keys[1].string_ptr()); + +// std::fprintf(stderr, "%s GET target: %s\n" +// , msg_keys[1] ? "mutable":"immutable" +// , aux::to_hex(target).c_str()); + + reply["token"] = generate_token(m.addr, target); + + // always return nodes as well as peers + write_nodes_entries(target, msg_keys[2], reply); + + // if the get has a sequence number it must be for a mutable item + // so don't bother searching the immutable table + if (!msg_keys[0]) + { + if (!m_storage.get_immutable_item(target, reply)) // ok, check for a mutable one + { + m_storage.get_mutable_item(target, sequence_number(0) + , true, reply); + } + } + else + { + m_storage.get_mutable_item(target + , sequence_number(msg_keys[0].int_value()), false + , reply); + } + } + else if (query == "sample_infohashes") + { + static key_desc_t const msg_desc[] = { + {"target", bdecode_node::string_t, 20, 0}, + {"want", bdecode_node::list_t, 0, key_desc_t::optional}, + }; + + bdecode_node msg_keys[2]; + if (!verify_message(arg_ent, msg_desc, msg_keys, error_string)) + { + m_counters.inc_stats_counter(counters::dht_invalid_sample_infohashes); + incoming_error(e, error_string); + return; + } + + m_counters.inc_stats_counter(counters::dht_sample_infohashes_in); + sha1_hash const target(msg_keys[0].string_ptr()); + + // TODO: keep the returned value to pass as a limit + // to write_nodes_entries when implemented + m_storage.get_infohashes_sample(reply); + + write_nodes_entries(target, msg_keys[1], reply); + } + else + { + // if we don't recognize the message but there's a + // 'target' or 'info_hash' in the arguments, treat it + // as find_node to be future compatible + bdecode_node target_ent = arg_ent.dict_find_string("target"); + if (!target_ent || target_ent.string_length() != 20) + { + target_ent = arg_ent.dict_find_string("info_hash"); + if (!target_ent || target_ent.string_length() != 20) + { + incoming_error(e, "unknown message"); + return; + } + } + + sha1_hash const target(target_ent.string_ptr()); + // always return nodes as well as peers + write_nodes_entries(target, arg_ent.dict_find_list("want"), reply); + } +} + +// TODO: limit number of entries in the result +void node::write_nodes_entries(sha1_hash const& info_hash + , bdecode_node const& want, entry& r) +{ + // if no wants entry was specified, include a nodes + // entry based on the protocol the request came in with + if (want.type() != bdecode_node::list_t) + { + std::vector n; + m_table.find_node(info_hash, n, 0); + r[protocol_nodes_key()] = write_nodes_entry(n); + return; + } + + // if there is a wants entry then we may need to reach into + // another node's routing table to get nodes of the requested type + // we use a map maintained by the owning dht_tracker to find the + // node associated with each string in the want list, which may + // include this node + for (int i = 0; i < want.list_size(); ++i) + { + bdecode_node wanted = want.list_at(i); + if (wanted.type() != bdecode_node::string_t) + continue; + node* wanted_node = m_get_foreign_node(info_hash, wanted.string_value().to_string()); + if (!wanted_node) continue; + std::vector n; + wanted_node->m_table.find_node(info_hash, n, 0); + r[wanted_node->protocol_nodes_key()] = write_nodes_entry(n); + } +} + +node::protocol_descriptor const& node::map_protocol_to_descriptor(udp const protocol) +{ + static std::array const descriptors = + {{ + {udp::v4(), "n4", "nodes"}, + {udp::v6(), "n6", "nodes6"} + }}; + + auto const iter = std::find_if(descriptors.begin(), descriptors.end() + , [&protocol](protocol_descriptor const& d) { return d.protocol == protocol; }); + + if (iter == descriptors.end()) + { + TORRENT_ASSERT_FAIL(); + aux::throw_ex("unknown protocol"); + } + + return *iter; +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/node_entry.cpp b/src/kademlia/node_entry.cpp new file mode 100644 index 0000000..7973083 --- /dev/null +++ b/src/kademlia/node_entry.cpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/aux_/time.hpp" // for aux::time_now() + +namespace libtorrent { namespace dht { + + node_entry::node_entry(node_id const& id_, udp::endpoint const& ep + , int roundtriptime + , bool pinged) + : last_queried(pinged ? aux::time_now() : min_time()) + , id(id_) + , endpoint(ep) + , rtt(roundtriptime & 0xffff) + , timeout_count(pinged ? 0 : 0xff) + , verified(verify_id(id_, ep.address())) + { + } + + node_entry::node_entry(udp::endpoint const& ep) + : endpoint(ep) + {} + + void node_entry::update_rtt(int const new_rtt) + { + TORRENT_ASSERT(new_rtt <= 0xffff); + TORRENT_ASSERT(new_rtt >= 0); + if (new_rtt == 0xffff) return; + if (rtt == 0xffff) rtt = std::uint16_t(new_rtt); + else rtt = std::uint16_t(int(rtt) * 2 / 3 + new_rtt / 3); + } + +}} diff --git a/src/kademlia/node_id.cpp b/src/kademlia/node_id.cpp new file mode 100644 index 0000000..c2029cd --- /dev/null +++ b/src/kademlia/node_id.cpp @@ -0,0 +1,213 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_local et.al +#include "libtorrent/random.hpp" // for random +#include "libtorrent/hasher.hpp" // for hasher +#include "libtorrent/crc32c.hpp" // for crc32c + +namespace libtorrent { namespace dht { + +// returns the distance between the two nodes +// using the kademlia XOR-metric +node_id distance(node_id const& n1, node_id const& n2) +{ + return n1 ^ n2; +} + +// returns true if: distance(n1, ref) < distance(n2, ref) +bool compare_ref(node_id const& n1, node_id const& n2, node_id const& ref) +{ + node_id const lhs = n1 ^ ref; + node_id const rhs = n2 ^ ref; + return lhs < rhs; +} + +// returns n in: 2^n <= distance(n1, n2) < 2^(n+1) +// useful for finding out which bucket a node belongs to +int distance_exp(node_id const& n1, node_id const& n2) +{ + // TODO: it's a little bit weird to return 159 - leading zeroes. It should + // probably be 160 - leading zeroes, but all other code in here is tuned to + // this expectation now, and it doesn't really matter (other than complexity) + return std::max(159 - distance(n1, n2).count_leading_zeroes(), 0); +} + +int min_distance_exp(node_id const& n1, std::vector const& ids) +{ + TORRENT_ASSERT(ids.size() > 0); + + int min = 160; // see distance_exp for the why of this constant + for (auto const& node_id : ids) + { + min = std::min(min, distance_exp(n1, node_id)); + } + + return min; +} + +node_id generate_id_impl(address const& ip_, std::uint32_t r) +{ + std::uint8_t* ip = nullptr; + + static std::uint8_t const v4mask[] = { 0x03, 0x0f, 0x3f, 0xff }; + static std::uint8_t const v6mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; + std::uint8_t const* mask = nullptr; + int num_octets = 0; + + address_v4::bytes_type b4{}; + address_v6::bytes_type b6{}; + if (ip_.is_v6()) + { + b6 = ip_.to_v6().to_bytes(); + ip = b6.data(); + num_octets = 8; + mask = v6mask; + } + else + { + b4 = ip_.to_v4().to_bytes(); + ip = b4.data(); + num_octets = 4; + mask = v4mask; + } + + for (int i = 0; i < num_octets; ++i) + ip[i] &= mask[i]; + + ip[0] |= (r & 0x7) << 5; + + // this is the crc32c (Castagnoli) polynomial + std::uint32_t c; + if (num_octets == 4) + { + c = crc32c_32(*reinterpret_cast(ip)); + } + else + { + TORRENT_ASSERT(num_octets == 8); + c = crc32c(reinterpret_cast(ip), 1); + } + node_id id; + + id[0] = (c >> 24) & 0xff; + id[1] = (c >> 16) & 0xff; + id[2] = (((c >> 8) & 0xf8) | random(0x7)) & 0xff; + + for (int i = 3; i < 19; ++i) id[i] = random(0xff) & 0xff; + id[19] = r & 0xff; + + return id; +} + +static std::uint32_t secret = 0; + +void make_id_secret(node_id& in) +{ + if (secret == 0) secret = random(0xfffffffe) + 1; + + std::uint32_t const rand = random(0xffffffff); + + // generate the last 4 bytes as a "signature" of the previous 4 bytes. This + // lets us verify whether a hash came from this function or not in the future. + hasher h(reinterpret_cast(&secret), 4); + h.update(reinterpret_cast(&rand), 4); + sha1_hash const secret_hash = h.final(); + std::memcpy(&in[20 - 4], &secret_hash[0], 4); + std::memcpy(&in[20 - 8], &rand, 4); +} + +node_id generate_random_id() +{ + char r[20]; + aux::random_bytes(r); + return hasher(r, 20).final(); +} + +node_id generate_secret_id() +{ + node_id ret = generate_random_id(); + make_id_secret(ret); + return ret; +} + +bool verify_secret_id(node_id const& nid) +{ + if (secret == 0) return false; + + hasher h(reinterpret_cast(&secret), 4); + h.update(reinterpret_cast(&nid[20 - 8]), 4); + sha1_hash secret_hash = h.final(); + return std::memcmp(&nid[20 - 4], &secret_hash[0], 4) == 0; +} + +// verifies whether a node-id matches the IP it's used from +// returns true if the node-id is OK coming from this source +// and false otherwise. +bool verify_id(node_id const& nid, address const& source_ip) +{ + // no need to verify local IPs, they would be incorrect anyway + if (is_local(source_ip)) return true; + + node_id h = generate_id_impl(source_ip, nid[19]); + return nid[0] == h[0] && nid[1] == h[1] && (nid[2] & 0xf8) == (h[2] & 0xf8); +} + +node_id generate_id(address const& ip) +{ + return generate_id_impl(ip, random(0xffffffff)); +} + +bool matching_prefix(node_id const& nid, int mask, int prefix, int offset) +{ + node_id id = nid; + id <<= offset; + return (id[0] & mask) == prefix; +} + +node_id generate_prefix_mask(int const bits) +{ + TORRENT_ASSERT(bits >= 0); + TORRENT_ASSERT(bits <= 160); + node_id mask; + std::size_t b = 0; + for (; int(b) < bits - 7; b += 8) mask[b / 8] |= 0xff; + if (bits < 160) mask[b / 8] |= (0xff << (8 - (bits & 7))) & 0xff; + return mask; +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/put_data.cpp b/src/kademlia/put_data.cpp new file mode 100644 index 0000000..b137a12 --- /dev/null +++ b/src/kademlia/put_data.cpp @@ -0,0 +1,114 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht { + +put_data::put_data(node& dht_node, put_callback const& callback) + : traversal_algorithm(dht_node, {}) + , m_put_callback(callback) +{} + +char const* put_data::name() const { return "put_data"; } + +void put_data::start() +{ + // router nodes must not be added to puts + init(); + bool const is_done = add_requests(); + if (is_done) done(); +} + +void put_data::set_targets(std::vector> const& targets) +{ + for (auto const& p : targets) + { + auto o = m_node.m_rpc.allocate_observer(self(), p.first.ep() + , p.first.id, p.second); + if (!o) return; + +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + m_results.push_back(std::move(o)); + } +} + +void put_data::done() +{ + m_done = true; + +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%u] %s DONE, response %d, timeout %d" + , id(), name(), num_responses(), num_timeouts()); +#endif + + m_put_callback(m_data, num_responses()); + traversal_algorithm::done(); +} + +bool put_data::invoke(observer_ptr o) +{ + if (m_done) return false; + + // TODO: what if o is not an instance of put_data_observer? This need to be + // redesigned for better type safety. + auto* po = static_cast(o.get()); + + entry e; + e["y"] = "q"; + e["q"] = "put"; + entry& a = e["a"]; + a["v"] = m_data.value(); + a["token"] = po->m_token; + if (m_data.is_mutable()) + { + a["k"] = m_data.pk().bytes; + a["seq"] = m_data.seq().value; + a["sig"] = m_data.sig().bytes; + if (!m_data.salt().empty()) + { + a["salt"] = m_data.salt(); + } + } + + m_node.stats_counters().inc_stats_counter(counters::dht_put_out); + + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/refresh.cpp b/src/kademlia/refresh.cpp new file mode 100644 index 0000000..efc2251 --- /dev/null +++ b/src/kademlia/refresh.cpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +namespace libtorrent { namespace dht { + +observer_ptr bootstrap::new_observer(udp::endpoint const& ep + , node_id const& id) +{ + auto o = m_node.m_rpc.allocate_observer(self(), ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; +} + +bool bootstrap::invoke(observer_ptr o) +{ + entry e; + e["y"] = "q"; + entry& a = e["a"]; + + e["q"] = "get_peers"; + // in case our node id changes during the bootstrap, make sure to always use + // the current node id (rather than the target stored in the traversal + // algorithm) + node_id target = get_node().nid(); + make_id_secret(target); + a["info_hash"] = target.to_string(); + + if (o->flags & observer::flag_initial) + { + // if this packet is being sent to a bootstrap/router node, let it know + // that we're actually bootstrapping (as opposed to being collateral + // traffic). + a["bs"] = 1; + } + +// e["q"] = "find_node"; +// a["target"] = target.to_string(); + m_node.stats_counters().inc_stats_counter(counters::dht_get_peers_out); + return m_node.m_rpc.invoke(e, o->target_ep(), o); +} + +bootstrap::bootstrap( + node& dht_node + , node_id const& target + , done_callback const& callback) + : get_peers(dht_node, target, get_peers::data_callback(), callback, false) +{ +} + +char const* bootstrap::name() const { return "bootstrap"; } + +void bootstrap::done() +{ +#ifndef TORRENT_DISABLE_LOGGING + get_node().observer()->log(dht_logger::traversal, "[%u] bootstrap done, pinging remaining nodes" + , id()); +#endif + + for (auto const& o : m_results) + { + if (o->flags & observer::flag_queried) continue; + // this will send a ping + m_node.add_node(o->target_ep()); + } + get_peers::done(); +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/routing_table.cpp b/src/kademlia/routing_table.cpp new file mode 100644 index 0000000..b44e319 --- /dev/null +++ b/src/kademlia/routing_table.cpp @@ -0,0 +1,1230 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include // std::distance(), std::next +#include // std::copy, std::remove_copy_if +#include +#include +#include // for snprintf +#include // for PRId64 et.al. +#include + +#include "libtorrent/config.hpp" + +#include // to_hex +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/alert_types.hpp" // for dht_routing_bucket +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/aux_/array.hpp" + +using namespace std::placeholders; + +namespace libtorrent { namespace dht { + +namespace { + + template + void erase_one(T& container, K const& key) + { + auto const i = container.find(key); + TORRENT_ASSERT(i != container.end()); + container.erase(i); + } + + bool verify_node_address(dht::settings const& settings + , node_id const& id, address const& addr) + { + // only when the node_id pass the verification, add it to routing table. + return !settings.enforce_node_id || verify_id(id, addr); + } +} + +void ip_set::insert(address const& addr) +{ + if (addr.is_v6()) + m_ip6s.insert(addr.to_v6().to_bytes()); + else + m_ip4s.insert(addr.to_v4().to_bytes()); +} + +bool ip_set::exists(address const& addr) const +{ + if (addr.is_v6()) + return m_ip6s.find(addr.to_v6().to_bytes()) != m_ip6s.end(); + else + return m_ip4s.find(addr.to_v4().to_bytes()) != m_ip4s.end(); +} + +void ip_set::erase(address const& addr) +{ + if (addr.is_v6()) + erase_one(m_ip6s, addr.to_v6().to_bytes()); + else + erase_one(m_ip4s, addr.to_v4().to_bytes()); +} + +bool mostly_verified_nodes(bucket_t const& b) +{ + int const num_verified = static_cast(std::count_if(b.begin(), b.end() + , [](node_entry const& e) { return e.verified; })); + if (num_verified == 0 && b.size() > 0) return false; + return num_verified >= static_cast(b.size()) * 2 / 3; +} + +std::uint8_t classify_prefix(int const bucket_idx, bool const last_bucket + , int const bucket_size, node_id nid) +{ + TORRENT_ASSERT_VAL(bucket_size > 0, bucket_size); + TORRENT_ASSERT_VAL(bucket_size <= 256, bucket_size); + + std::uint32_t mask = static_cast(bucket_size) - 1; + // bucket sizes must be even powers of two. + TORRENT_ASSERT_VAL((mask & static_cast(bucket_size)) == 0, bucket_size); + + // this is a bit weird. count_leading_zeros treats the span we pass to it as + // an array of chars, but we pass a span of uint32_t as an optimization, to + // allow it to operate on 32 bits at a time. In this case, the value fits in + // a single byte, so we could get away with just passing in the least + // significant byte of `mask`, but we can't with the current API of + // count_leading_zeros(). + int const mask_shift = aux::count_leading_zeros(aux::little_endian_to_host(mask)); + TORRENT_ASSERT_VAL(mask_shift >= 0, mask_shift); + TORRENT_ASSERT_VAL(mask_shift < 8, mask_shift); + mask <<= mask_shift; + TORRENT_ASSERT_VAL(mask > 0, mask); + TORRENT_ASSERT_VAL(bool((mask & 0x80) != 0), mask); + + // the reason to shift one bit extra (except for the last bucket) is that the + // first bit *defines* the bucket. That bit will be the same for all entries. + // We're not interested in that one. However, the last bucket hasn't split + // yet, so it will contain entries from both "sides", so we need to include + // the top bit. + nid <<= bucket_idx + int(!last_bucket); + std::uint8_t const ret = (nid[0] & mask) >> mask_shift; + TORRENT_ASSERT_VAL(ret < bucket_size, ret); + return ret; +} + +routing_table::add_node_status_t replace_node_impl(node_entry const& e + , bucket_t& b, ip_set& ips, int const bucket_index + , int const bucket_size_limit, bool const last_bucket +#ifndef TORRENT_DISABLE_LOGGING + , dht_logger* log +#endif + ) +{ + // if the bucket isn't full, we're not replacing anything, and this function + // should not have been called + TORRENT_ASSERT(int(b.size()) >= bucket_size_limit); + + bucket_t::iterator j = std::max_element(b.begin(), b.end() + , [](node_entry const& lhs, node_entry const& rhs) + { return lhs.fail_count() < rhs.fail_count(); }); + TORRENT_ASSERT(j != b.end()); + + if (j->fail_count() > 0) + { + // i points to a node that has been marked + // as stale. Replace it with this new one + ips.erase(j->addr()); + *j = e; + ips.insert(e.addr()); + return routing_table::node_added; + } + + // then we look for nodes with the same 3 bit prefix (or however + // many bits prefix the bucket size warrants). If there is no other + // node with this prefix, remove the duplicate with the highest RTT. + // as the last replacement strategy, if the node we found matching our + // bit prefix has higher RTT than the new node, replace it. + + // in order to provide as few lookups as possible before finding + // the data someone is looking for, make sure there is an affinity + // towards having a good spread of node IDs in each bucket + std::uint8_t const to_add_prefix = classify_prefix(bucket_index + , last_bucket, bucket_size_limit, e.id); + + // nodes organized by their prefix + aux::array, 128> nodes_storage; + auto const nodes = span>{nodes_storage}.first(bucket_size_limit); + + for (j = b.begin(); j != b.end(); ++j) + { + std::uint8_t const prefix = classify_prefix( + bucket_index, last_bucket, bucket_size_limit, j->id); + TORRENT_ASSERT(prefix < nodes.size()); + nodes[prefix].push_back(j); + } + + if (!nodes[to_add_prefix].empty()) + { + j = *std::max_element(nodes[to_add_prefix].begin(), nodes[to_add_prefix].end() + , [](bucket_t::iterator lhs, bucket_t::iterator rhs) + { return *lhs < *rhs; }); + + // only if e is better than the worst node in this prefix slot do we + // replace it. resetting j means we're not replacing it + if (!(e < *j)) j = b.end(); + } + else + { + // there is no node in this prefix slot. We definitely want to add it. + // Now we just need to figure out which one to replace + std::vector replace_candidates; + for (auto const& n : nodes) + { + if (n.size() > 1) replace_candidates.insert(replace_candidates.end(), n.begin(), n.end()); + } + + // since the bucket is full, and there's no node in the prefix-slot + // we're about to add to, there must be at least one prefix slot that + // has more than one node. + TORRENT_ASSERT(!replace_candidates.empty()); + + // from these nodes, pick the "worst" one and replace it + j = *std::max_element(replace_candidates.begin(), replace_candidates.end() + , [](bucket_t::iterator lhs, bucket_t::iterator rhs) + { return *lhs < *rhs; }); + } + + if (j != b.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (log != nullptr && log->should_log(dht_logger::routing_table)) + { + log->log(dht_logger::routing_table, "replacing node with better one: %s %s [%s %dms %d] vs. [%s %dms %d]" + , aux::to_hex(e.id).c_str(), print_address(e.addr()).c_str() + , e.verified ? "verified" : "not-verified", e.rtt + , classify_prefix(bucket_index, last_bucket, bucket_size_limit, e.id) + , j->verified ? "verified" : "not-verified", j->rtt + , classify_prefix(bucket_index, last_bucket, bucket_size_limit, j->id) + ); + } +#endif + ips.erase(j->addr()); + *j = e; + ips.insert(e.addr()); + return routing_table::node_added; + } + return routing_table::need_bucket_split; +} + +routing_table::routing_table(node_id const& id, udp const proto, int const bucket_size + , dht::settings const& settings + , dht_logger* log) + : +#ifndef TORRENT_DISABLE_LOGGING + m_log(log), +#endif + m_settings(settings) + , m_id(id) + , m_protocol(proto) + , m_depth(0) + , m_last_self_refresh(min_time()) + , m_bucket_size(bucket_size) +{ + // bucket sizes must be a power of 2 + TORRENT_ASSERT_VAL(((bucket_size - 1) & bucket_size) == 0, bucket_size); + TORRENT_UNUSED(log); + m_buckets.reserve(30); +} + +int routing_table::bucket_limit(int bucket) const +{ + if (!m_settings.extended_routing_table) return m_bucket_size; + + static const aux::array size_exceptions{{{16, 8, 4, 2}}}; + if (bucket < size_exceptions.end_index()) + return m_bucket_size * size_exceptions[bucket]; + return m_bucket_size; +} + +void routing_table::status(std::vector& s) const +{ + // TODO: This is temporary. For now, only report the largest routing table + // (of potentially multiple ones, for multi-homed systems) + // in next major version, break the ABI and support reporting all of them in + // the dht_stats_alert + if (s.size() > m_buckets.size()) return; + s.clear(); + for (auto const& i : m_buckets) + { + dht_routing_bucket b; + b.num_nodes = int(i.live_nodes.size()); + b.num_replacements = int(i.replacements.size()); + s.push_back(b); + } +} + +#if TORRENT_ABI_VERSION == 1 +// TODO: 2 use the non deprecated function instead of this one +void routing_table::status(session_status& s) const +{ + int dht_nodes; + int dht_node_cache; + int ignore; + std::tie(dht_nodes, dht_node_cache, ignore) = size(); + s.dht_nodes += dht_nodes; + s.dht_node_cache += dht_node_cache; + // TODO: arvidn note + // when it's across IPv4 and IPv6, adding (dht_global_nodes) would + // make sense. in the future though, where we may have one DHT node + // per external interface (which may be multiple of the same address + // family), then it becomes a bit trickier + s.dht_global_nodes += num_global_nodes(); + + for (auto const& i : m_buckets) + { + dht_routing_bucket b; + b.num_nodes = int(i.live_nodes.size()); + b.num_replacements = int(i.replacements.size()); +#if TORRENT_ABI_VERSION == 1 + b.last_active = 0; +#endif + s.dht_routing_table.push_back(b); + } +} +#endif + +std::tuple routing_table::size() const +{ + int nodes = 0; + int replacements = 0; + int confirmed = 0; + for (auto const& i : m_buckets) + { + nodes += int(i.live_nodes.size()); + confirmed += static_cast(std::count_if(i.live_nodes.begin(), i.live_nodes.end() + , [](node_entry const& k) { return k.confirmed(); } )); + + replacements += int(i.replacements.size()); + } + return std::make_tuple(nodes, replacements, confirmed); +} + +std::int64_t routing_table::num_global_nodes() const +{ + int deepest_bucket = 0; + int deepest_size = 0; + for (auto const& i : m_buckets) + { + deepest_size = i.live_nodes.end_index(); // + i.replacements.size(); + if (deepest_size < m_bucket_size) break; + // this bucket is full + ++deepest_bucket; + } + + if (deepest_bucket == 0) return 1 + deepest_size; + + if (deepest_size < m_bucket_size / 2) return (std::int64_t(1) << deepest_bucket) * m_bucket_size; + else return (std::int64_t(2) << deepest_bucket) * deepest_size; +} + +int routing_table::depth() const +{ + if (m_depth >= int(m_buckets.size())) + m_depth = int(m_buckets.size()) - 1; + + if (m_depth < 0) return m_depth; + + // maybe the table is deeper now? + while (m_depth < int(m_buckets.size()) - 1 + && int(m_buckets[m_depth + 1].live_nodes.size()) >= m_bucket_size / 2) + { + ++m_depth; + } + + // maybe the table is more shallow now? + while (m_depth > 0 + && int(m_buckets[m_depth - 1].live_nodes.size()) < m_bucket_size / 2) + { + --m_depth; + } + + return m_depth; +} + +node_entry const* routing_table::next_refresh() +{ + // find the node with the least recent 'last_queried' field. if it's too + // recent, return false. Otherwise return a random target ID that's close to + // a missing prefix for that bucket + + node_entry* candidate = nullptr; + + // this will have a bias towards pinging nodes close to us first. + for (auto i = m_buckets.rbegin(), end(m_buckets.rend()); i != end; ++i) + { + for (auto& n : i->live_nodes) + { + // this shouldn't happen + TORRENT_ASSERT(m_id != n.id); + if (n.id == m_id) continue; + + if (n.last_queried == min_time()) + { + candidate = &n; + goto out; + } + + if (candidate == nullptr || n.last_queried < candidate->last_queried) + { + candidate = &n; + } + } + + if (i == m_buckets.rbegin() + || int(i->live_nodes.size()) < bucket_limit(int(std::distance(i, end)) - 1)) + { + // this bucket isn't full or it can be split + // check for an unpinged replacement + // node which may be eligible for the live bucket if confirmed + auto r = std::find_if(i->replacements.begin(), i->replacements.end() + , [](node_entry const& e) { return !e.pinged() && e.last_queried == min_time(); }); + if (r != i->replacements.end()) + { + candidate = &*r; + goto out; + } + } + } +out: + + // make sure we don't pick the same node again next time we want to refresh + // the routing table + if (candidate) + candidate->last_queried = aux::time_now(); + + return candidate; +} + +routing_table::table_t::iterator routing_table::find_bucket(node_id const& id) +{ +// TORRENT_ASSERT(id != m_id); + + int num_buckets = int(m_buckets.size()); + if (num_buckets == 0) + { + m_buckets.push_back(routing_table_node()); + ++num_buckets; + } + + int bucket_index = std::min(159 - distance_exp(m_id, id), num_buckets - 1); + TORRENT_ASSERT(bucket_index < int(m_buckets.size())); + TORRENT_ASSERT(bucket_index >= 0); + + auto i = m_buckets.begin(); + std::advance(i, bucket_index); + return i; +} + +// returns true if the two IPs are "too close" to each other to be allowed in +// the same DHT lookup. If they are, the last one to be found will be ignored +bool compare_ip_cidr(address const& lhs, address const& rhs) +{ + TORRENT_ASSERT(lhs.is_v4() == rhs.is_v4()); + + if (lhs.is_v6()) + { + // if IPv6 addresses is in the same /64, they're too close and we won't + // trust the second one + std::uint64_t lhs_ip; + std::memcpy(&lhs_ip, lhs.to_v6().to_bytes().data(), 8); + std::uint64_t rhs_ip; + std::memcpy(&rhs_ip, rhs.to_v6().to_bytes().data(), 8); + + // since the condition we're looking for is all the first bits being + // zero, there's no need to byte-swap into host byte order here. + std::uint64_t const mask = lhs_ip ^ rhs_ip; + return mask == 0; + } + else + { + // if IPv4 addresses is in the same /24, they're too close and we won't + // trust the second one + std::uint32_t const mask + = std::uint32_t(lhs.to_v4().to_ulong() ^ rhs.to_v4().to_ulong()); + return mask <= 0x000000ff; + } +} + +std::tuple +routing_table::find_node(udp::endpoint const& ep) +{ + for (auto i = m_buckets.begin() , end(m_buckets.end()); i != end; ++i) + { + for (auto j = i->replacements.begin(); j != i->replacements.end(); ++j) + { + if (j->addr() != ep.address()) continue; + if (j->port() != ep.port()) continue; + return std::make_tuple(&*j, i, &i->replacements); + } + for (auto j = i->live_nodes.begin(); j != i->live_nodes.end(); ++j) + { + if (j->addr() != ep.address()) continue; + if (j->port() != ep.port()) continue; + return std::make_tuple(&*j, i, &i->live_nodes); + } + } + return std::tuple( + nullptr, m_buckets.end(), nullptr); +} + +// TODO: this need to take bucket "prefix" into account. It should be unified +// with add_node_impl() +void routing_table::fill_from_replacements(table_t::iterator bucket) +{ + bucket_t& b = bucket->live_nodes; + bucket_t& rb = bucket->replacements; + int const bucket_size = bucket_limit(int(std::distance(m_buckets.begin(), bucket))); + + if (int(b.size()) >= bucket_size) return; + + // sort by RTT first, to find the node with the lowest + // RTT that is pinged + std::sort(rb.begin(), rb.end()); + + while (int(b.size()) < bucket_size && !rb.empty()) + { + auto j = std::find_if(rb.begin(), rb.end(), std::bind(&node_entry::pinged, _1)); + if (j == rb.end()) break; + b.push_back(*j); + rb.erase(j); + } +} + +void routing_table::prune_empty_bucket() +{ + if (m_buckets.back().live_nodes.empty() + && m_buckets.back().replacements.empty()) + { + m_buckets.erase(m_buckets.end() - 1); + } +} + +void routing_table::remove_node(node_entry* n, bucket_t* b) +{ + std::ptrdiff_t const idx = n - b->data(); + TORRENT_ASSERT(idx >= 0); + TORRENT_ASSERT(idx < intptr_t(b->size())); + TORRENT_ASSERT(m_ips.exists(n->addr())); + m_ips.erase(n->addr()); + b->erase(b->begin() + idx); +} + +bool routing_table::add_node(node_entry const& e) +{ + add_node_status_t s = add_node_impl(e); + if (s == failed_to_add) return false; + if (s == node_added) return true; + + while (s == need_bucket_split) + { + split_bucket(); + + // if this assert triggers a lot in the wild, we should probably + // harden our resistance towards this attack. Perhaps by never + // splitting a bucket (and discard nodes) if the two buckets above it + // are empty or close to empty +// TORRENT_ASSERT(m_buckets.size() <= 50); + if (m_buckets.size() > 50) + { + // this is a sanity check. In the wild, we shouldn't see routing + // tables deeper than 26 or 27. If we get this deep, there might + // be a bug in the bucket splitting logic, or there may be someone + // playing a prank on us, spoofing node IDs. + s = add_node_impl(e); + return s == node_added; + } + + // if the new bucket still has too many nodes in it, we need to keep + // splitting + if (int(m_buckets.back().live_nodes.size()) > bucket_limit(int(m_buckets.size()) - 1)) + continue; + + s = add_node_impl(e); + + // we just split the last bucket and tried to insert a new node. If none + // of the nodes in the split bucket, nor the new node ended up in the new + // bucket, erase it + if (m_buckets.back().live_nodes.empty()) + { + m_buckets.erase(m_buckets.end() - 1); + // we just split, trying to add the node again should not request + // another split + TORRENT_ASSERT(s != need_bucket_split); + } + if (s == failed_to_add) return false; + if (s == node_added) return true; + } + return false; +} + +bool all_in_same_bucket(span b, node_id const& id, int const bucket_index) +{ + int const byte_offset = bucket_index / 8; + int const bit_offset = bucket_index % 8; + std::uint8_t const mask = 0x80 >> bit_offset; + int counter[2] = {0, 0}; + int const i = (id[byte_offset] & mask) ? 1 : 0; + ++counter[i]; + for (auto const& e : b) + { + int const idx = (e.id[byte_offset] & mask) ? 1 : 0; + ++counter[idx]; + } + return counter[0] == 0 || counter[1] == 0; +} + +routing_table::add_node_status_t routing_table::add_node_impl(node_entry e) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS +// INVARIANT_CHECK; +#endif + + // don't add if the address isn't the right type + if (!native_endpoint(e.ep())) + return failed_to_add; + + // if we already have this (IP,port), don't do anything + if (m_router_nodes.find(e.ep()) != m_router_nodes.end()) + return failed_to_add; + + // do we already have this IP in the table? + if (m_ips.exists(e.addr())) + { + // This exact IP already exists in the table. A node with the same IP and + // port but a different ID may be a sign of a malicious node. To be + // conservative in this case the node is removed. + // pinged means that we have sent a message to the IP, port and received + // a response with a correct transaction ID, i.e. it is verified to not + // be the result of a poisoned routing table + + node_entry * existing; + routing_table::table_t::iterator existing_bucket; + bucket_t* bucket; + std::tie(existing, existing_bucket, bucket) = find_node(e.ep()); + if (existing == nullptr) + { + // the node we're trying to add is not a match with an existing node. we + // should ignore it, unless we allow duplicate IPs in our routing + // table. There could be a node with the same IP, but with a different + // port. m_ips just contain IP addresses, whereas the lookup we just + // performed was for full endpoints (address, port). + if (m_settings.restrict_routing_ips) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_log != nullptr && m_log->should_log(dht_logger::routing_table)) + { + m_log->log(dht_logger::routing_table, "ignoring node (duplicate IP): %s %s" + , aux::to_hex(e.id).c_str(), print_address(e.addr()).c_str()); + } +#endif + return failed_to_add; + } + } + else if (existing->id == e.id) + { + // if the node ID is the same, just update the failcount + // and be done with it. + existing->timeout_count = 0; + if (e.pinged()) + { + existing->update_rtt(e.rtt); + existing->last_queried = e.last_queried; + } + // if this was a replacement node it may be elligible for + // promotion to the live bucket + fill_from_replacements(existing_bucket); + prune_empty_bucket(); + return node_added; + } + else if (existing->id.is_all_zeros()) + { + // this node's ID was unknown. remove the old entry and + // replace it with the node's real ID + remove_node(existing, bucket); + } + else if (!e.pinged()) + { + // this may be a routing table poison attack. If we haven't confirmed + // that this peer actually exist with this new node ID yet, ignore it. + // we definitely don't want to replace the existing entry with this one + if (m_settings.restrict_routing_ips) + return failed_to_add; + } + else + { + TORRENT_ASSERT(existing->id != e.id); + // This is the same IP and port, but with a new node ID. + // This may indicate a malicious node so remove the entry. +#ifndef TORRENT_DISABLE_LOGGING + if (m_log != nullptr && m_log->should_log(dht_logger::routing_table)) + { + m_log->log(dht_logger::routing_table, "evicting node (changed ID): old: %s new: %s %s" + , aux::to_hex(existing->id).c_str(), aux::to_hex(e.id).c_str(), print_address(e.addr()).c_str()); + } +#endif + + remove_node(existing, bucket); + fill_from_replacements(existing_bucket); + + // when we detect possible malicious activity in a bucket, + // schedule the other nodes in the bucket to be pinged soon + // to clean out any other malicious nodes + auto const now = aux::time_now(); + for (auto& node : existing_bucket->live_nodes) + { + if (node.last_queried + minutes(5) < now) + node.last_queried = min_time(); + } + + prune_empty_bucket(); + return failed_to_add; + } + } + + // don't add ourself + if (e.id == m_id) return failed_to_add; + + auto const i = find_bucket(e.id); + bucket_t& b = i->live_nodes; + bucket_t& rb = i->replacements; + int const bucket_index = int(std::distance(m_buckets.begin(), i)); + // compare against the max size of the next bucket. Otherwise we may wait too + // long to split, and lose nodes (in the case where lower-numbered buckets + // are larger) + int const bucket_size_limit = bucket_limit(bucket_index); + + bucket_t::iterator j; + + // if the node already exists, we don't need it + j = std::find_if(b.begin(), b.end() + , [&e](node_entry const& ne) { return ne.id == e.id; }); + + if (j != b.end()) + { + // a new IP address just claimed this node-ID + // ignore it + if (j->addr() != e.addr() || j->port() != e.port()) + return failed_to_add; + + // we already have the node in our bucket + TORRENT_ASSERT(j->id == e.id && j->ep() == e.ep()); + j->timeout_count = 0; + j->update_rtt(e.rtt); + return node_added; + } + + // if this node exists in the replacement bucket. update it and + // pull it out from there. We may add it back to the replacement + // bucket, but we may also replace a node in the main bucket, now + // that we have an updated RTT + j = std::find_if(rb.begin(), rb.end() + , [&e](node_entry const& ne) { return ne.id == e.id; }); + if (j != rb.end()) + { + // a new IP address just claimed this node-ID + // ignore it + if (j->addr() != e.addr() || j->port() != e.port()) + return failed_to_add; + + TORRENT_ASSERT(j->id == e.id && j->ep() == e.ep()); + j->timeout_count = 0; + j->update_rtt(e.rtt); + e = *j; + m_ips.erase(j->addr()); + rb.erase(j); + } + + if (m_settings.restrict_routing_ips) + { + // don't allow multiple entries from IPs very close to each other + address const& cmp = e.addr(); + j = std::find_if(b.begin(), b.end(), [&](node_entry const& a) { return compare_ip_cidr(a.addr(), cmp); }); + if (j == b.end()) + { + j = std::find_if(rb.begin(), rb.end(), [&](node_entry const& a) { return compare_ip_cidr(a.addr(), cmp); }); + if (j == rb.end()) goto ip_ok; + } + + // we already have a node in this bucket with an IP very + // close to this one. We know that it's not the same, because + // it claims a different node-ID. Ignore this to avoid attacks +#ifndef TORRENT_DISABLE_LOGGING + if (m_log != nullptr && m_log->should_log(dht_logger::routing_table)) + { + m_log->log(dht_logger::routing_table, "ignoring node: %s %s existing node: %s %s" + , aux::to_hex(e.id).c_str(), print_address(e.addr()).c_str() + , aux::to_hex(j->id).c_str(), print_address(j->addr()).c_str()); + } +#endif + return failed_to_add; + } +ip_ok: + + // if there's room in the main bucket, just insert it + // if we can split the bucket (i.e. it's the last bucket) use the next + // bucket's size limit. This makes use split the low-numbered buckets split + // earlier when we have larger low buckets, to make it less likely that we + // lose nodes + if (e.pinged() && int(b.size()) < bucket_size_limit) + { + if (b.empty()) b.reserve(bucket_size_limit); + b.push_back(e); + m_ips.insert(e.addr()); + return node_added; + } + + // if there is no room, we look for nodes marked as stale + // in the k-bucket. If we find one, we can replace it. + + // A node is considered stale if it has failed at least one + // time. Here we choose the node that has failed most times. + // If we don't find one, place this node in the replacement- + // cache and replace any nodes that will fail in the future + // with nodes from that cache. + + bool const last_bucket = bucket_index + 1 == int(m_buckets.size()); + + // only nodes that have been confirmed can split the bucket, and we can only + // split the last bucket + // if all nodes in the bucket, including the new node id (e.id) fall in the + // same bucket, splitting isn't going to do anything. + bool const can_split = (std::next(i) == m_buckets.end() + && m_buckets.size() < 159) + && (m_settings.prefer_verified_node_ids == false + || (e.verified && mostly_verified_nodes(b))) + && e.confirmed() + && (i == m_buckets.begin() || std::prev(i)->live_nodes.size() > 1) + && !all_in_same_bucket(b, e.id, bucket_index); + + if (can_split) return need_bucket_split; + + if (e.confirmed()) + { + auto const ret = replace_node_impl(e, b, m_ips, bucket_index, bucket_size_limit, last_bucket +#ifndef TORRENT_DISABLE_LOGGING + , m_log +#endif + ); + if (ret != need_bucket_split) return ret; + } + + // if we can't split, nor replace anything in the live buckets try to insert + // into the replacement bucket + + // if we don't have any identified stale nodes in + // the bucket, and the bucket is full, we have to + // cache this node and wait until some node fails + // and then replace it. + j = std::find_if(rb.begin(), rb.end() + , [&e](node_entry const& ne) { return ne.id == e.id; }); + + // if the node is already in the replacement bucket + // just return. + if (j != rb.end()) + { + // if the IP address matches, it's the same node + // make sure it's marked as pinged + if (j->ep() == e.ep()) j->set_pinged(); + return node_added; + } + + if (int(rb.size()) >= m_bucket_size) + { + // if the replacement bucket is full, remove the oldest entry + // but prefer nodes that haven't been pinged, since they are + // less reliable than this one, that has been pinged + j = std::find_if(rb.begin(), rb.end() + , [] (node_entry const& ne) { return !ne.pinged(); }); + if (j == rb.end()) + { + auto const ret = replace_node_impl(e, rb, m_ips, bucket_index, m_bucket_size, last_bucket +#ifndef TORRENT_DISABLE_LOGGING + , nullptr +#endif + ); + return ret == node_added ? node_added : failed_to_add; + } + m_ips.erase(j->addr()); + rb.erase(j); + } + + if (rb.empty()) rb.reserve(m_bucket_size); + rb.push_back(e); + m_ips.insert(e.addr()); + return node_added; +} + +void routing_table::split_bucket() +{ + INVARIANT_CHECK; + + int const bucket_index = int(m_buckets.size()) - 1; + int const bucket_size_limit = bucket_limit(bucket_index); + TORRENT_ASSERT(int(m_buckets.back().live_nodes.size()) >= bucket_limit(bucket_index + 1)); + + // this is the last bucket, and it's full already. Split + // it by adding another bucket + m_buckets.push_back(routing_table_node()); + bucket_t& new_bucket = m_buckets.back().live_nodes; + bucket_t& new_replacement_bucket = m_buckets.back().replacements; + + bucket_t& b = m_buckets[bucket_index].live_nodes; + bucket_t& rb = m_buckets[bucket_index].replacements; + + // move any node whose (160 - distance_exp(m_id, id)) >= (i - m_buckets.begin()) + // to the new bucket + int const new_bucket_size = bucket_limit(bucket_index + 1); + for (auto j = b.begin(); j != b.end();) + { + int const d = distance_exp(m_id, j->id); + if (d >= 159 - bucket_index) + { + ++j; + continue; + } + // this entry belongs in the new bucket + new_bucket.push_back(*j); + j = b.erase(j); + } + + if (int(b.size()) > bucket_size_limit) + { + // TODO: 2 move the lowest priority nodes to the replacement bucket + for (auto i = b.begin() + bucket_size_limit + , end(b.end()); i != end; ++i) + { + rb.push_back(*i); + } + + b.resize(bucket_size_limit); + } + + // split the replacement bucket as well. If the live bucket + // is not full anymore, also move the replacement entries + // into the main bucket + for (auto j = rb.begin(); j != rb.end();) + { + if (distance_exp(m_id, j->id) >= 159 - bucket_index) + { + if (!j->pinged() || int(b.size()) >= bucket_size_limit) + { + ++j; + continue; + } + b.push_back(*j); + } + else + { + // this entry belongs in the new bucket + if (j->pinged() && int(new_bucket.size()) < new_bucket_size) + new_bucket.push_back(*j); + else + new_replacement_bucket.push_back(*j); + } + j = rb.erase(j); + } +} + +void routing_table::update_node_id(node_id const& id) +{ + m_id = id; + + m_ips.clear(); + + // pull all nodes out of the routing table, effectively emptying it + table_t old_buckets; + old_buckets.swap(m_buckets); + + // then add them all back. First add the main nodes, then the replacement + // nodes + for (auto const& b : old_buckets) + for (auto const& n : b.live_nodes) + add_node(n); + + // now add back the replacement nodes + for (auto const& b : old_buckets) + for (auto const& n : b.replacements) + add_node(n); +} + +void routing_table::for_each_node(std::function live_cb + , std::function replacements_cb) const +{ + for (auto const& i : m_buckets) + { + if (live_cb) + { + for (auto const& j : i.live_nodes) + live_cb(j); + } + if (replacements_cb) + { + for (auto const& j : i.replacements) + replacements_cb(j); + } + } +} + +void routing_table::node_failed(node_id const& nid, udp::endpoint const& ep) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + // if messages to ourself fails, ignore it + if (nid == m_id) return; + + auto const i = find_bucket(nid); + bucket_t& b = i->live_nodes; + bucket_t& rb = i->replacements; + + auto j = std::find_if(b.begin(), b.end() + , [&nid](node_entry const& ne) { return ne.id == nid; }); + + if (j == b.end()) + { + j = std::find_if(rb.begin(), rb.end() + , [&nid](node_entry const& ne) { return ne.id == nid; }); + + if (j == rb.end() + || j->ep() != ep) return; + + j->timed_out(); + +#ifndef TORRENT_DISABLE_LOGGING + log_node_failed(nid, *j); +#endif + return; + } + + // if the endpoint doesn't match, it's a different node + // claiming the same ID. The node we have in our routing + // table is not necessarily stale + if (j->ep() != ep) return; + + if (rb.empty()) + { + j->timed_out(); + +#ifndef TORRENT_DISABLE_LOGGING + log_node_failed(nid, *j); +#endif + + // if this node has failed too many times, or if this node + // has never responded at all, remove it + if (j->fail_count() >= m_settings.max_fail_count || !j->pinged()) + { + m_ips.erase(j->addr()); + b.erase(j); + } + return; + } + + m_ips.erase(j->addr()); + b.erase(j); + + fill_from_replacements(i); + prune_empty_bucket(); +} + +void routing_table::add_router_node(udp::endpoint const& router) +{ + m_router_nodes.insert(router); +} + +// we heard from this node, but we don't know if it was spoofed or not (i.e. +// pinged == false) +void routing_table::heard_about(node_id const& id, udp::endpoint const& ep) +{ + if (!verify_node_address(m_settings, id, ep.address())) return; + add_node(node_entry(id, ep)); +} + +// this function is called every time the node sees a sign of a node being +// alive. This node will either be inserted in the k-buckets or be moved to the +// top of its bucket. the return value indicates if the table needs a refresh. +// if true, the node should refresh the table (i.e. do a find_node on its own +// id) +bool routing_table::node_seen(node_id const& id, udp::endpoint const& ep, int const rtt) +{ + return verify_node_address(m_settings, id, ep.address()) && add_node(node_entry(id, ep, rtt, true)); +} + +// fills the vector with the k nodes from our buckets that +// are nearest to the given id. +void routing_table::find_node(node_id const& target + , std::vector& l, int const options, int count) +{ + l.clear(); + if (count == 0) count = m_bucket_size; + + auto const i = find_bucket(target); + int const bucket_index = int(std::distance(m_buckets.begin(), i)); + int const bucket_size_limit = bucket_limit(bucket_index); + + l.reserve(aux::numeric_cast(bucket_size_limit)); + + table_t::iterator j = i; + + int unsorted_start_idx = 0; + for (; j != m_buckets.end() && int(l.size()) < count; ++j) + { + bucket_t const& b = j->live_nodes; + if (options & include_failed) + { + std::copy(b.begin(), b.end(), std::back_inserter(l)); + } + else + { + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) + , [](node_entry const& ne) { return !ne.confirmed(); }); + } + + if (int(l.size()) == count) return; + + if (int(l.size()) > count) + { + // sort the nodes by how close they are to the target + std::sort(l.begin() + unsorted_start_idx, l.end() + , [&target](node_entry const& lhs, node_entry const& rhs) + { return compare_ref(lhs.id, rhs.id, target); }); + + l.resize(aux::numeric_cast(count)); + return; + } + unsorted_start_idx = int(l.size()); + } + + // if we still don't have enough nodes, copy nodes + // further away from us + + if (i == m_buckets.begin()) + return; + + j = i; + + unsorted_start_idx = int(l.size()); + do + { + --j; + bucket_t const& b = j->live_nodes; + + if (options & include_failed) + { + std::copy(b.begin(), b.end(), std::back_inserter(l)); + } + else + { + std::remove_copy_if(b.begin(), b.end(), std::back_inserter(l) + , [](node_entry const& ne) { return !ne.confirmed(); }); + } + + if (int(l.size()) == count) return; + + if (int(l.size()) > count) + { + // sort the nodes by how close they are to the target + std::sort(l.begin() + unsorted_start_idx, l.end() + , [&target](node_entry const& lhs, node_entry const& rhs) + { return compare_ref(lhs.id, rhs.id, target); }); + + l.resize(aux::numeric_cast(count)); + return; + } + unsorted_start_idx = int(l.size()); + } + while (j != m_buckets.begin() && int(l.size()) < count); + + TORRENT_ASSERT(int(l.size()) <= count); +} + +#if TORRENT_USE_INVARIANT_CHECKS +void routing_table::check_invariant() const +{ + ip_set all_ips; + + for (auto const& i : m_buckets) + { + for (auto const& j : i.replacements) + { + all_ips.insert(j.addr()); + } + for (auto const& j : i.live_nodes) + { + TORRENT_ASSERT(j.addr().is_v4() == i.live_nodes.begin()->addr().is_v4()); + TORRENT_ASSERT(j.pinged()); + all_ips.insert(j.addr()); + } + } + + TORRENT_ASSERT(all_ips == m_ips); +} +#endif + +bool routing_table::is_full(int const bucket) const +{ + int const num_buckets = int(m_buckets.size()); + if (num_buckets == 0) return false; + if (bucket >= num_buckets) return false; + + auto i = m_buckets.cbegin(); + std::advance(i, bucket); + return (int(i->live_nodes.size()) >= bucket_limit(bucket) + && int(i->replacements.size()) >= m_bucket_size); +} + +#ifndef TORRENT_DISABLE_LOGGING +void routing_table::log_node_failed(node_id const& nid, node_entry const& ne) const +{ + if (m_log != nullptr && m_log->should_log(dht_logger::routing_table)) + { + m_log->log(dht_logger::routing_table, "NODE FAILED id: %s ip: %s fails: %d pinged: %d up-time: %d" + , aux::to_hex(nid).c_str(), print_endpoint(ne.ep()).c_str() + , ne.fail_count() + , int(ne.pinged()) + , int(total_seconds(aux::time_now() - ne.first_seen))); + } +} +#endif + +} } // namespace libtorrent::dht diff --git a/src/kademlia/rpc_manager.cpp b/src/kademlia/rpc_manager.cpp new file mode 100644 index 0000000..29ebffd --- /dev/null +++ b/src/kademlia/rpc_manager.cpp @@ -0,0 +1,517 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for print_endpoint +#include // for aux::time_now +#include +#include // for is_v6 + +#include +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include // for PRId64 et.al. +#endif + +using namespace std::placeholders; + +namespace libtorrent { namespace dht { + +// TODO: 3 move this into it's own .cpp file + +constexpr observer_flags_t observer::flag_queried; +constexpr observer_flags_t observer::flag_initial; +constexpr observer_flags_t observer::flag_no_id; +constexpr observer_flags_t observer::flag_short_timeout; +constexpr observer_flags_t observer::flag_failed; +constexpr observer_flags_t observer::flag_ipv6_address; +constexpr observer_flags_t observer::flag_alive; +constexpr observer_flags_t observer::flag_done; + +dht_observer* observer::get_observer() const +{ + return m_algorithm->get_node().observer(); +} + +void observer::set_target(udp::endpoint const& ep) +{ + m_sent = clock_type::now(); + + m_port = ep.port(); + if (is_v6(ep)) + { + flags |= flag_ipv6_address; + m_addr.v6 = ep.address().to_v6().to_bytes(); + } + else + { + flags &= ~flag_ipv6_address; + m_addr.v4 = ep.address().to_v4().to_bytes(); + } +} + +address observer::target_addr() const +{ + if (flags & flag_ipv6_address) + return address_v6(m_addr.v6); + else + return address_v4(m_addr.v4); +} + +udp::endpoint observer::target_ep() const +{ + return udp::endpoint(target_addr(), m_port); +} + +void observer::abort() +{ + if (flags & flag_done) return; + flags |= flag_done; + m_algorithm->failed(self(), traversal_algorithm::prevent_request); +} + +void observer::done() +{ + if (flags & flag_done) return; + flags |= flag_done; + m_algorithm->finished(self()); +} + +void observer::short_timeout() +{ + if (flags & flag_short_timeout) return; + m_algorithm->failed(self(), traversal_algorithm::short_timeout); +} + +void observer::timeout() +{ + if (flags & flag_done) return; + flags |= flag_done; + m_algorithm->failed(self()); +} + +void observer::set_id(node_id const& id) +{ + if (m_id == id) return; + m_id = id; + if (m_algorithm) m_algorithm->resort_result(this); +} + +using observer_storage = aux::aligned_union<1 + , find_data_observer + , announce_observer + , put_data_observer + , direct_observer + , get_item_observer + , get_peers_observer + , obfuscated_get_peers_observer + , sample_infohashes_observer + , null_observer + , traversal_observer>::type; + +rpc_manager::rpc_manager(node_id const& our_id + , dht_settings const& settings + , routing_table& table + , aux::listen_socket_handle const& sock + , socket_manager* sock_man + , dht_logger* log) + : m_pool_allocator(sizeof(observer_storage), 10) + , m_sock(sock) + , m_sock_man(sock_man) +#ifndef TORRENT_DISABLE_LOGGING + , m_log(log) +#endif + , m_settings(settings) + , m_table(table) + , m_our_id(our_id) + , m_allocated_observers(0) + , m_destructing(false) +{ +#ifdef TORRENT_DISABLE_LOGGING + TORRENT_UNUSED(log); +#endif +} + +rpc_manager::~rpc_manager() +{ + TORRENT_ASSERT(!m_destructing); + m_destructing = true; + + for (auto const& t : m_transactions) + { + t.second->abort(); + } +} + +void* rpc_manager::allocate_observer() +{ + m_pool_allocator.set_next_size(10); + void* ret = m_pool_allocator.malloc(); + if (ret != nullptr) ++m_allocated_observers; + return ret; +} + +void rpc_manager::free_observer(void* ptr) +{ + if (ptr == nullptr) return; + --m_allocated_observers; + m_pool_allocator.free(ptr); +} + +#if TORRENT_USE_ASSERTS +size_t rpc_manager::allocation_size() const +{ + return sizeof(observer_storage); +} +#endif +#if TORRENT_USE_INVARIANT_CHECKS +void rpc_manager::check_invariant() const +{ + for (auto const& t : m_transactions) + { + TORRENT_ASSERT(t.second); + } +} +#endif + +void rpc_manager::unreachable(udp::endpoint const& ep) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "PORT_UNREACHABLE [ ip: %s ]" + , print_endpoint(ep).c_str()); + } +#endif + + for (auto i = m_transactions.begin(); i != m_transactions.end();) + { + TORRENT_ASSERT(i->second); + if (i->second->target_ep() != ep) { ++i; continue; } + observer_ptr o = i->second; +#ifndef TORRENT_DISABLE_LOGGING + m_log->log(dht_logger::rpc_manager, "[%u] found transaction [ tid: %d ]" + , o->algorithm()->id(), i->first); +#endif + i = m_transactions.erase(i); + o->timeout(); + break; + } +} + +bool rpc_manager::incoming(msg const& m, node_id* id) +{ + INVARIANT_CHECK; + + if (m_destructing) return false; + + // we only deal with replies and errors, not queries + TORRENT_ASSERT(m.message.dict_find_string_value("y") == "r" + || m.message.dict_find_string_value("y") == "e"); + + // if we don't have the transaction id in our + // request list, ignore the packet + + auto transaction_id = m.message.dict_find_string_value("t"); + if (transaction_id.empty()) return false; + + auto ptr = transaction_id.begin(); + int tid = transaction_id.size() != 2 ? -1 : detail::read_uint16(ptr); + + observer_ptr o; + auto range = m_transactions.equal_range(tid); + for (auto i = range.first; i != range.second; ++i) + { + if (m.addr.address() != i->second->target_addr()) continue; + o = i->second; + i = m_transactions.erase(i); + break; + } + + if (!o) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_table.native_endpoint(m.addr) && m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "reply with unknown transaction id size: %d from %s" + , int(transaction_id.size()), print_endpoint(m.addr).c_str()); + } +#endif + // this isn't necessarily because the other end is doing + // something wrong. This can also happen when we restart + // the node, and we prematurely abort all outstanding + // requests. Also, this opens up a potential magnification + // attack. +// entry e; +// incoming_error(e, "invalid transaction id"); +// m_sock->send_packet(e, m.addr); + return false; + } + + time_point const now = clock_type::now(); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "[%u] round trip time(ms): %" PRId64 " from %s" + , o->algorithm()->id(), total_milliseconds(now - o->sent()) + , print_endpoint(m.addr).c_str()); + } +#endif + + if (m.message.dict_find_string_value("y") == "e") + { + // It's an error. +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::rpc_manager)) + { + bdecode_node err = m.message.dict_find_list("e"); + if (err && err.list_size() >= 2 + && err.list_at(0).type() == bdecode_node::int_t + && err.list_at(1).type() == bdecode_node::string_t) + { + m_log->log(dht_logger::rpc_manager, "[%u] reply with error from %s: (%" PRId64 ") %s" + , o->algorithm()->id() + , print_endpoint(m.addr).c_str() + , err.list_int_value_at(0) + , err.list_string_value_at(1).to_string().c_str()); + } + else + { + m_log->log(dht_logger::rpc_manager, "[%u] reply with (malformed) error from %s" + , o->algorithm()->id(), print_endpoint(m.addr).c_str()); + } + } +#endif + // Logically, we should call o->reply(m) since we get a reply. + // a reply could be "response" or "error", here the reply is an "error". + // if the reply is an "error", basically the observer could/will + // do nothing with it, especially when observer::reply() is intended to + // handle a "response", not an "error". + // A "response" should somehow call algorithm->finished(), and an error/timeout + // should call algorithm->failed(). From this point of view, + // we should call o->timeout() instead of o->reply(m) because o->reply() + // will call algorithm->finished(). + o->timeout(); + return false; + } + + bdecode_node const ret_ent = m.message.dict_find_dict("r"); + if (!ret_ent) + { + o->timeout(); + return false; + } + + bdecode_node const node_id_ent = ret_ent.dict_find_string("id"); + if (!node_id_ent || node_id_ent.string_length() != 20) + { + o->timeout(); + return false; + } + + node_id const nid = node_id(node_id_ent.string_ptr()); + if (m_settings.enforce_node_id && !verify_id(nid, m.addr.address())) + { + o->timeout(); + return false; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "[%u] reply with transaction id: %d from %s" + , o->algorithm()->id(), int(transaction_id.size()) + , print_endpoint(m.addr).c_str()); + } +#endif + o->reply(m); + *id = nid; + + int rtt = int(total_milliseconds(now - o->sent())); + + // we found an observer for this reply, hence the node is not spoofing + // add it to the routing table + return m_table.node_seen(*id, m.addr, rtt); +} + +time_duration rpc_manager::tick() +{ + INVARIANT_CHECK; + + constexpr int short_timeout = 1; + constexpr int timeout = 15; + + // look for observers that have timed out + + if (m_transactions.empty()) return seconds(short_timeout); + + std::vector timeouts; + std::vector short_timeouts; + + time_duration ret = seconds(short_timeout); + time_point now = aux::time_now(); + + for (auto i = m_transactions.begin(); i != m_transactions.end();) + { + observer_ptr o = i->second; + + time_duration diff = now - o->sent(); + if (diff >= seconds(timeout)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "[%u] timing out transaction id: %d from: %s" + , o->algorithm()->id(), i->first + , print_endpoint(o->target_ep()).c_str()); + } +#endif + i = m_transactions.erase(i); + timeouts.push_back(o); + continue; + } + + // don't call short_timeout() again if we've + // already called it once + if (diff >= seconds(short_timeout) && !o->has_short_timeout()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "[%u] short-timing out transaction id: %d from: %s" + , o->algorithm()->id(), i->first + , print_endpoint(o->target_ep()).c_str()); + } +#endif + ++i; + + short_timeouts.push_back(o); + continue; + } + + ret = std::min(seconds(timeout) - diff, ret); + ++i; + } + + std::for_each(timeouts.begin(), timeouts.end(), std::bind(&observer::timeout, _1)); + std::for_each(short_timeouts.begin(), short_timeouts.end(), std::bind(&observer::short_timeout, _1)); + + return std::max(ret, duration_cast(milliseconds(200))); +} + +void rpc_manager::add_our_id(entry& e) +{ + e["id"] = m_our_id.to_string(); +} + +bool rpc_manager::invoke(entry& e, udp::endpoint const& target_addr + , observer_ptr o) +{ + INVARIANT_CHECK; + + if (m_destructing) return false; + + e["y"] = "q"; + entry& a = e["a"]; + add_our_id(a); + + std::string transaction_id; + transaction_id.resize(2); + char* out = &transaction_id[0]; + std::uint16_t const tid = std::uint16_t(random(0x7fff)); + detail::write_uint16(tid, out); + e["t"] = transaction_id; + + // When a DHT node enters the read-only state, in each outgoing query message, + // places a 'ro' key in the top-level message dictionary and sets its value to 1. + if (m_settings.read_only) e["ro"] = 1; + + node& n = o->algorithm()->get_node(); + if (!n.native_address(o->target_addr())) + { + a["want"].list().emplace_back(n.protocol_family_name()); + } + + o->set_target(target_addr); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_log != nullptr && m_log->should_log(dht_logger::rpc_manager)) + { + m_log->log(dht_logger::rpc_manager, "[%u] invoking %s -> %s" + , o->algorithm()->id(), e["q"].string().c_str() + , print_endpoint(target_addr).c_str()); + } +#endif + + if (m_sock_man->send_packet(m_sock, e, target_addr)) + { + m_transactions.insert(std::make_pair(tid, o)); +#if TORRENT_USE_ASSERTS + o->m_was_sent = true; +#endif + return true; + } + return false; +} + +observer::~observer() +{ + // if the message was sent, it must have been + // reported back to the traversal_algorithm as + // well. If it wasn't sent, it cannot have been + // reported back + TORRENT_ASSERT(m_was_sent == bool(flags & flag_done) || m_was_abandoned); + TORRENT_ASSERT(!m_in_constructor); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_in_use); + m_in_use = false; +#endif +} + +} } // namespace libtorrent::dht diff --git a/src/kademlia/sample_infohashes.cpp b/src/kademlia/sample_infohashes.cpp new file mode 100644 index 0000000..b911d55 --- /dev/null +++ b/src/kademlia/sample_infohashes.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include + +namespace libtorrent { namespace dht +{ + +sample_infohashes::sample_infohashes(node& dht_node + , node_id const& target + , data_callback const& dcallback) + : traversal_algorithm(dht_node, target) + , m_data_callback(dcallback) {} + +char const* sample_infohashes::name() const { return "sample_infohashes"; } + +void sample_infohashes::got_samples(time_duration interval + , int num, std::vector samples + , std::vector> nodes) +{ + if (m_data_callback) + { + m_data_callback(interval, num, std::move(samples), std::move(nodes)); + m_data_callback = nullptr; + done(); + } +} + +sample_infohashes_observer::sample_infohashes_observer( + std::shared_ptr algorithm + , udp::endpoint const& ep, node_id const& id) + : traversal_observer(std::move(algorithm), ep, id) {} + +void sample_infohashes_observer::reply(msg const& m) +{ + bdecode_node r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] missing response dict" + , algorithm()->id()); +#endif + timeout(); + return; + } + + // look for nodes + std::vector> nodes; + udp const protocol = algorithm()->get_node().protocol(); + int const protocol_size = int(detail::address_size(protocol)); + char const* nodes_key = algorithm()->get_node().protocol_nodes_key(); + bdecode_node const n = r.dict_find_string(nodes_key); + if (n) + { + char const* ptr = n.string_ptr(); + char const* end = ptr + n.string_length(); + + while (end - ptr >= 20 + protocol_size + 2) + { + node_endpoint nep = read_node_endpoint(protocol, ptr); + nodes.emplace_back(nep.id, nep.ep); + } + } + + std::int64_t const interval = r.dict_find_int_value("interval", -1); + if (interval < 0 || interval > 21600) // TODO: put constant in a common place + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] wrong or missing interval value" + , algorithm()->id()); +#endif + timeout(); + return; + } + + std::int64_t const num = r.dict_find_int_value("num", -1); + if (num < 0 || num > std::numeric_limits::max()) + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] wrong or missing num value" + , algorithm()->id()); +#endif + timeout(); + return; + } + + bdecode_node samples = r.dict_find_string("samples"); + if (samples && (samples.string_length() % 20 == 0)) + { + std::vector v(aux::numeric_cast(samples.string_length() / 20)); + std::memcpy(v.data(), samples.string_ptr(), v.size() * 20); + + static_cast(algorithm())->got_samples( + seconds(interval), int(num), std::move(v), std::move(nodes)); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + get_observer()->log(dht_logger::traversal, "[%u] wrong or missing samples value" + , algorithm()->id()); +#endif + timeout(); + } + + // we deliberately do not call + traversal_observer::reply(m); + // this is necessary to play nice with + // observer::abort(), observer::done() and observer::timeout() + flags |= flag_done; +} + +}} // namespace libtorrent::dht diff --git a/src/kademlia/traversal_algorithm.cpp b/src/kademlia/traversal_algorithm.cpp new file mode 100644 index 0000000..af36abc --- /dev/null +++ b/src/kademlia/traversal_algorithm.cpp @@ -0,0 +1,671 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg & Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include // for dht_logger +#include +#include +#include // for read_*_endpoint +#include // for dht_lookup +#include + +#ifndef TORRENT_DISABLE_LOGGING +#include // to_hex +#endif + +using namespace std::placeholders; + +namespace libtorrent { +namespace dht { + +constexpr traversal_flags_t traversal_algorithm::prevent_request; +constexpr traversal_flags_t traversal_algorithm::short_timeout; + +#if TORRENT_USE_ASSERTS +template +bool is_sorted(It b, It e, Cmp cmp) +{ + if (b == e) return true; + + typename std::iterator_traits::value_type v = *b; + ++b; + while (b != e) + { + if (cmp(*b, v)) return false; + v = *b; + ++b; + } + return true; +} +#endif + +observer_ptr traversal_algorithm::new_observer(udp::endpoint const& ep + , node_id const& id) +{ + auto o = m_node.m_rpc.allocate_observer(self(), ep, id); +#if TORRENT_USE_ASSERTS + if (o) o->m_in_constructor = false; +#endif + return o; +} + +traversal_algorithm::traversal_algorithm(node& dht_node, node_id const& target) + : m_node(dht_node) + , m_target(target) +{ +#ifndef TORRENT_DISABLE_LOGGING + m_id = m_node.search_id(); + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal, "[%u] NEW target: %s k: %d" + , m_id, aux::to_hex(target).c_str(), m_node.m_table.bucket_size()); + } +#endif +} + +void traversal_algorithm::resort_result(observer* o) +{ + // find the given observer, remove it and insert it in its sorted location + auto it = std::find_if(m_results.begin(), m_results.end() + , [=](observer_ptr const& ptr) { return ptr.get() == o; }); + + if (it == m_results.end()) return; + + if (it - m_results.begin() < m_sorted_results) + --m_sorted_results; + + observer_ptr ptr = std::move(*it); + m_results.erase(it); + + TORRENT_ASSERT(std::size_t(m_sorted_results) <= m_results.size()); + auto end = m_results.begin() + m_sorted_results; + + TORRENT_ASSERT(libtorrent::dht::is_sorted(m_results.begin(), end + , [this](observer_ptr const& lhs, observer_ptr const& rhs) + { return compare_ref(lhs->id(), rhs->id(), m_target); })); + + auto iter = std::lower_bound(m_results.begin(), end, ptr + , [this](observer_ptr const& lhs, observer_ptr const& rhs) + { return compare_ref(lhs->id(), rhs->id(), m_target); }); + + m_results.insert(iter, ptr); + ++m_sorted_results; +} + +void traversal_algorithm::add_entry(node_id const& id + , udp::endpoint const& addr, observer_flags_t const flags) +{ + if (m_done) return; + + TORRENT_ASSERT(m_node.m_rpc.allocation_size() >= sizeof(find_data_observer)); + auto o = new_observer(addr, id); + if (!o) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer() != nullptr) + { + get_node().observer()->log(dht_logger::traversal, "[%u] failed to allocate memory or observer. aborting!" + , m_id); + } +#endif + done(); + return; + } + + o->flags |= flags; + + if (id.is_all_zeros()) + { + o->set_id(generate_random_id()); + o->flags |= observer::flag_no_id; + + m_results.push_back(o); + +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] ADD (no-id) id: %s addr: %s distance: %d invoke-count: %d type: %s" + , m_id, aux::to_hex(id).c_str(), print_endpoint(addr).c_str() + , distance_exp(m_target, id), m_invoke_count, name()); + } +#endif + } + else + { + TORRENT_ASSERT(std::size_t(m_sorted_results) <= m_results.size()); + auto end = m_results.begin() + m_sorted_results; + + TORRENT_ASSERT(libtorrent::dht::is_sorted(m_results.begin(), end + , [this](observer_ptr const& lhs, observer_ptr const& rhs) + { return compare_ref(lhs->id(), rhs->id(), m_target); })); + + auto iter = std::lower_bound(m_results.begin(), end, o + , [this](observer_ptr const& lhs, observer_ptr const& rhs) + { return compare_ref(lhs->id(), rhs->id(), m_target); }); + + if (iter == end || (*iter)->id() != id) + { + // this IP restriction does not apply to the nodes we loaded from out + // node cache + if (m_node.settings().restrict_search_ips + && !(flags & observer::flag_initial)) + { + if (o->target_addr().is_v6()) + { + address_v6::bytes_type addr_bytes = o->target_addr().to_v6().to_bytes(); + auto prefix_it = addr_bytes.cbegin(); + std::uint64_t const prefix6 = detail::read_uint64(prefix_it); + + if (m_peer6_prefixes.insert(prefix6).second) + goto add_result; + } + else + { + // mask the lower octet + std::uint32_t const prefix4 + = o->target_addr().to_v4().to_ulong() & 0xffffff00; + + if (m_peer4_prefixes.insert(prefix4).second) + goto add_result; + } + + // we already have a node in this search with an IP very + // close to this one. We know that it's not the same, because + // it claims a different node-ID. Ignore this to avoid attacks +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] traversal DUPLICATE node. id: %s addr: %s type: %s" + , m_id, aux::to_hex(o->id()).c_str(), print_address(o->target_addr()).c_str(), name()); + } +#endif + return; + } + + add_result: + + TORRENT_ASSERT((o->flags & observer::flag_no_id) + || std::none_of(m_results.begin(), end + , [&id](observer_ptr const& ob) { return ob->id() == id; })); + +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] ADD id: %s addr: %s distance: %d invoke-count: %d type: %s" + , m_id, aux::to_hex(id).c_str(), print_endpoint(addr).c_str() + , distance_exp(m_target, id), m_invoke_count, name()); + } +#endif + m_results.insert(iter, o); + ++m_sorted_results; + } + } + + TORRENT_ASSERT(std::size_t(m_sorted_results) <= m_results.size()); + TORRENT_ASSERT(libtorrent::dht::is_sorted(m_results.begin() + , m_results.begin() + m_sorted_results + , [this](observer_ptr const& lhs, observer_ptr const& rhs) + { return compare_ref(lhs->id(), rhs->id(), m_target); })); + + if (m_results.size() > 100) + { + std::for_each(m_results.begin() + 100, m_results.end() + , [this](std::shared_ptr const& ptr) + { + if ((ptr->flags & (observer::flag_queried | observer::flag_failed | observer::flag_alive)) + == observer::flag_queried) + { + // set the done flag on any outstanding queries to prevent them from + // calling finished() or failed() + ptr->flags |= observer::flag_done; + TORRENT_ASSERT(m_invoke_count > 0); + --m_invoke_count; + } + +#if TORRENT_USE_ASSERTS + ptr->m_was_abandoned = true; +#endif + }); + m_results.resize(100); + m_sorted_results = std::min(std::int8_t(100), m_sorted_results); + } +} + +void traversal_algorithm::start() +{ + // in case the routing table is empty, use the + // router nodes in the table + if (m_results.size() < 3) add_router_entries(); + init(); + bool const is_done = add_requests(); + if (is_done) done(); +} + +char const* traversal_algorithm::name() const +{ + return "traversal_algorithm"; +} + +void traversal_algorithm::traverse(node_id const& id, udp::endpoint const& addr) +{ + if (m_done) return; + +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal) && id.is_all_zeros()) + { + logger->log(dht_logger::traversal + , "[%u] WARNING node returned a list which included a node with id 0" + , m_id); + } +#endif + + // let the routing table know this node may exist + m_node.m_table.heard_about(id, addr); + + add_entry(id, addr, {}); +} + +void traversal_algorithm::finished(observer_ptr o) +{ +#if TORRENT_USE_ASSERTS + auto i = std::find(m_results.begin(), m_results.end(), o); + TORRENT_ASSERT(i != m_results.end() || m_results.size() == 100); +#endif + + // if this flag is set, it means we increased the + // branch factor for it, and we should restore it + if (o->flags & observer::flag_short_timeout) + { + TORRENT_ASSERT(m_branch_factor > 0); + --m_branch_factor; + } + + TORRENT_ASSERT(o->flags & observer::flag_queried); + o->flags |= observer::flag_alive; + + ++m_responses; + TORRENT_ASSERT(m_invoke_count > 0); + --m_invoke_count; + bool const is_done = add_requests(); + if (is_done) done(); +} + +// prevent request means that the total number of requests has +// overflown. This query failed because it was the oldest one. +// So, if this is true, don't make another request +void traversal_algorithm::failed(observer_ptr o, traversal_flags_t const flags) +{ + // don't tell the routing table about + // node ids that we just generated ourself + if (!(o->flags & observer::flag_no_id)) + m_node.m_table.node_failed(o->id(), o->target_ep()); + + if (m_results.empty()) return; + + bool decrement_branch_factor = false; + + TORRENT_ASSERT(o->flags & observer::flag_queried); + if (flags & short_timeout) + { + // short timeout means that it has been more than + // two seconds since we sent the request, and that + // we'll most likely not get a response. But, in case + // we do get a late response, keep the handler + // around for some more, but open up the slot + // by increasing the branch factor + if (!(o->flags & observer::flag_short_timeout) + && m_branch_factor < std::numeric_limits::max()) + { + ++m_branch_factor; + o->flags |= observer::flag_short_timeout; + } +#ifndef TORRENT_DISABLE_LOGGING + log_timeout(o, "1ST_"); +#endif + } + else + { + o->flags |= observer::flag_failed; + // if this flag is set, it means we increased the + // branch factor for it, and we should restore it + decrement_branch_factor = bool(o->flags & observer::flag_short_timeout); + +#ifndef TORRENT_DISABLE_LOGGING + log_timeout(o,""); +#endif + + ++m_timeouts; + TORRENT_ASSERT(m_invoke_count > 0); + --m_invoke_count; + } + + // this is another reason to decrement the branch factor, to prevent another + // request from filling this slot. Only ever decrement once per response though + decrement_branch_factor |= bool(flags & prevent_request); + + if (decrement_branch_factor) + { + TORRENT_ASSERT(m_branch_factor > 0); + --m_branch_factor; + if (m_branch_factor <= 0) m_branch_factor = 1; + } + + bool const is_done = add_requests(); + if (is_done) done(); +} + +#ifndef TORRENT_DISABLE_LOGGING +void traversal_algorithm::log_timeout(observer_ptr const& o, char const* prefix) const +{ + dht_observer * logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] %sTIMEOUT id: %s distance: %d addr: %s branch-factor: %d " + "invoke-count: %d type: %s" + , m_id, prefix, aux::to_hex(o->id()).c_str(), distance_exp(m_target, o->id()) + , print_address(o->target_addr()).c_str(), m_branch_factor + , m_invoke_count, name()); + } + +} +#endif + +void traversal_algorithm::done() +{ + TORRENT_ASSERT(m_done == false); + m_done = true; +#ifndef TORRENT_DISABLE_LOGGING + int results_target = m_node.m_table.bucket_size(); + int closest_target = 160; +#endif + + for (auto const& o : m_results) + { + if ((o->flags & (observer::flag_queried | observer::flag_failed)) == observer::flag_queried) + { + // set the done flag on any outstanding queries to prevent them from + // calling finished() or failed() after we've already declared the traversal + // done + o->flags |= observer::flag_done; + } + +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (results_target > 0 && (o->flags & observer::flag_alive) + && logger != nullptr && logger->should_log(dht_logger::traversal)) + { + TORRENT_ASSERT(o->flags & observer::flag_queried); + logger->log(dht_logger::traversal + , "[%u] id: %s distance: %d addr: %s" + , m_id, aux::to_hex(o->id()).c_str(), closest_target + , print_endpoint(o->target_ep()).c_str()); + + --results_target; + int const dist = distance_exp(m_target, o->id()); + if (dist < closest_target) closest_target = dist; + } +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + if (get_node().observer() != nullptr) + { + get_node().observer()->log(dht_logger::traversal + , "[%u] COMPLETED distance: %d type: %s" + , m_id, closest_target, name()); + } +#endif + + // delete all our references to the observer objects so + // they will in turn release the traversal algorithm + m_results.clear(); + m_sorted_results = 0; + m_invoke_count = 0; +} + +bool traversal_algorithm::add_requests() +{ + if (m_done) return true; + + int results_target = m_node.m_table.bucket_size(); + + // this only counts outstanding requests at the top of the + // target list. This is <= m_invoke count. m_invoke_count + // is the total number of outstanding requests, including + // old ones that may be waiting on nodes much farther behind + // the current point we've reached in the search. + int outstanding = 0; + + // if we're doing aggressive lookups, we keep branch-factor + // outstanding requests _at the tops_ of the result list. Otherwise + // we just keep any branch-factor outstanding requests + bool const agg = m_node.settings().aggressive_lookups; + + // Find the first node that hasn't already been queried. + // and make sure that the 'm_branch_factor' top nodes + // stay queried at all times (obviously ignoring failed nodes) + // and without surpassing the 'result_target' nodes (i.e. k=8) + // this is a slight variation of the original paper which instead + // limits the number of outstanding requests, this limits the + // number of good outstanding requests. It will use more traffic, + // but is intended to speed up lookups + for (auto i = m_results.begin() + , end(m_results.end()); i != end + && results_target > 0 + && (agg ? outstanding < m_branch_factor + : m_invoke_count < m_branch_factor); + ++i) + { + observer* o = i->get(); + if (o->flags & observer::flag_alive) + { + TORRENT_ASSERT(o->flags & observer::flag_queried); + --results_target; + continue; + } + if (o->flags & observer::flag_queried) + { + // if it's queried, not alive and not failed, it + // must be currently in flight + if (!(o->flags & observer::flag_failed)) + ++outstanding; + + continue; + } + +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] INVOKE nodes-left: %d top-invoke-count: %d " + "invoke-count: %d branch-factor: %d " + "distance: %d id: %s addr: %s type: %s" + , m_id, int(m_results.end() - i), outstanding, int(m_invoke_count) + , int(m_branch_factor), distance_exp(m_target, o->id()), aux::to_hex(o->id()).c_str() + , print_address(o->target_addr()).c_str(), name()); + } +#endif + + o->flags |= observer::flag_queried; + if (invoke(*i)) + { + TORRENT_ASSERT(m_invoke_count < std::numeric_limits::max()); + ++m_invoke_count; + ++outstanding; + } + else + { + o->flags |= observer::flag_failed; + } + } + + // this is the completion condition. If we found m_node.m_table.bucket_size() + // (i.e. k=8) completed results, without finding any still + // outstanding requests, we're done. + // also, if invoke count is 0, it means we didn't even find 'k' + // working nodes, we still have to terminate though. + return (results_target == 0 && outstanding == 0) || m_invoke_count == 0; +} + +void traversal_algorithm::add_router_entries() +{ +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_node().observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + logger->log(dht_logger::traversal + , "[%u] using router nodes to initiate traversal algorithm %d routers" + , m_id, int(std::distance(m_node.m_table.begin(), m_node.m_table.end()))); + } +#endif + for (auto const& n : m_node.m_table) + add_entry(node_id(), n, observer::flag_initial); +} + +void traversal_algorithm::init() +{ + m_branch_factor = aux::numeric_cast(m_node.branch_factor()); + m_node.add_traversal_algorithm(this); +} + +traversal_algorithm::~traversal_algorithm() +{ + m_node.remove_traversal_algorithm(this); +} + +void traversal_algorithm::status(dht_lookup& l) +{ + l.timeouts = m_timeouts; + l.responses = m_responses; + l.outstanding_requests = m_invoke_count; + l.branch_factor = m_branch_factor; + l.type = name(); + l.nodes_left = 0; + l.first_timeout = 0; + l.target = m_target; + + int last_sent = INT_MAX; + time_point const now = aux::time_now(); + for (auto const& r : m_results) + { + observer const& o = *r; + if (o.flags & observer::flag_queried) + { + last_sent = std::min(last_sent, int(total_seconds(now - o.sent()))); + if (o.has_short_timeout()) ++l.first_timeout; + continue; + } + ++l.nodes_left; + } + l.last_sent = last_sent; +} + +void look_for_nodes(char const* nodes_key, udp const& protocol, bdecode_node const& r, std::function f) +{ + bdecode_node const n = r.dict_find_string(nodes_key); + if (n) + { + char const* nodes = n.string_ptr(); + char const* end = nodes + n.string_length(); + int const protocol_size = int(detail::address_size(protocol)); + + while (end - nodes >= 20 + protocol_size + 2) + { + f(read_node_endpoint(protocol, nodes)); + } + } +} + +void traversal_observer::reply(msg const& m) +{ + bdecode_node const r = m.message.dict_find_dict("r"); + if (!r) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_observer() != nullptr) + { + get_observer()->log(dht_logger::traversal + , "[%u] missing response dict" + , algorithm()->id()); + } +#endif + return; + } + + bdecode_node const id = r.dict_find_string("id"); + +#ifndef TORRENT_DISABLE_LOGGING + dht_observer* logger = get_observer(); + if (logger != nullptr && logger->should_log(dht_logger::traversal)) + { + char hex_id[41]; + aux::to_hex({id.string_ptr(), 20}, hex_id); + logger->log(dht_logger::traversal + , "[%u] RESPONSE id: %s invoke-count: %d addr: %s type: %s" + , algorithm()->id(), hex_id, algorithm()->invoke_count() + , print_endpoint(target_ep()).c_str(), algorithm()->name()); + } +#endif + + look_for_nodes(algorithm()->get_node().protocol_nodes_key(), algorithm()->get_node().protocol(), r, + [this](node_endpoint const& nep) { algorithm()->traverse(nep.id, nep.ep); }); + + if (!id || id.string_length() != 20) + { +#ifndef TORRENT_DISABLE_LOGGING + if (get_observer() != nullptr) + { + get_observer()->log(dht_logger::traversal, "[%u] invalid id in response" + , algorithm()->id()); + } +#endif + return; + } + + // in case we didn't know the id of this peer when we sent the message to + // it. For instance if it's a bootstrap node. + set_id(node_id(id.string_ptr())); +} + +} } // namespace libtorrent::dht diff --git a/src/lazy_bdecode.cpp b/src/lazy_bdecode.cpp new file mode 100644 index 0000000..15ad22f --- /dev/null +++ b/src/lazy_bdecode.cpp @@ -0,0 +1,665 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#if TORRENT_ABI_VERSION == 1 + +#include "libtorrent/lazy_entry.hpp" +#include "libtorrent/bdecode.hpp" // for error codes and escape_string +#include "libtorrent/string_util.hpp" // for is_digit +#include +#include // for memset +#include // for numeric_limits +#include // for snprintf +#include // for PRId64 et.al. + +namespace { + + const int lazy_entry_grow_factor = 150; // percent + const int lazy_entry_dict_init = 5; + const int lazy_entry_list_init = 5; +} + +namespace libtorrent { + +namespace { + + int fail(int* error_pos + , std::vector& stack + , char const* start + , char const* orig_start) + { + while (!stack.empty()) { + lazy_entry* top = stack.back(); + if (top->type() == lazy_entry::dict_t || top->type() == lazy_entry::list_t) + { + top->pop(); + break; + } + stack.pop_back(); + } + if (error_pos) *error_pos = int(start - orig_start); + return -1; + } + +#define TORRENT_FAIL_BDECODE(code) do { ec = make_error_code(code); return fail(error_pos, stack, start, orig_start); } TORRENT_WHILE_0 + + char const* find_char(char const* start, char const* end, char delimiter) + { + while (start < end && *start != delimiter) ++start; + return start; + } + + char const* parse_string(char const* start, char const* end + , bdecode_errors::error_code_enum& e, std::int64_t& len) + { + start = parse_int(start, end, ':', len, e); + if (e) return start; + if (start == end) + { + e = bdecode_errors::expected_colon; + } + else + { + // remaining buffer size excluding ':' + const ptrdiff_t buff_size = end - start - 1; + if (len > buff_size) + { + e = bdecode_errors::unexpected_eof; + } + else if (len < 0) + { + e = bdecode_errors::overflow; + } + else + { + ++start; + if (start >= end) e = bdecode_errors::unexpected_eof; + } + } + return start; + } + + } // anonymous namespace + +#if TORRENT_ABI_VERSION == 1 + int lazy_bdecode(char const* start, char const* end + , lazy_entry& ret, int depth_limit, int item_limit) + { + error_code ec; + int pos; + return lazy_bdecode(start, end, ret, ec, &pos, depth_limit, item_limit); + } +#endif + + // return 0 = success + int lazy_bdecode(char const* start, char const* end, lazy_entry& ret + , error_code& ec, int* error_pos, int depth_limit, int item_limit) + { + char const* const orig_start = start; + ret.clear(); + + std::vector stack; + + if (start == end) + TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + stack.push_back(&ret); + while (start <= end) + { + if (stack.empty()) break; // done! + + lazy_entry* top = stack.back(); + + if (int(stack.size()) > depth_limit) TORRENT_FAIL_BDECODE(bdecode_errors::depth_exceeded); + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + char t = *start; + ++start; + if (start >= end && t != 'e') TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + + switch (top->type()) + { + case lazy_entry::dict_t: + { + if (t == 'e') + { + top->set_end(start); + stack.pop_back(); + continue; + } + if (!is_digit(t)) TORRENT_FAIL_BDECODE(bdecode_errors::expected_digit); + std::int64_t len = t - '0'; + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + start = parse_string(start, end, e, len); + if (e) TORRENT_FAIL_BDECODE(e); + + lazy_entry* ent = top->dict_append(start); + if (ent == nullptr) TORRENT_FAIL_BDECODE(boost::system::errc::not_enough_memory); + start += len; + if (start >= end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + stack.push_back(ent); + t = *start; + ++start; + break; + } + case lazy_entry::list_t: + { + if (t == 'e') + { + top->set_end(start); + stack.pop_back(); + continue; + } + lazy_entry* ent = top->list_append(); + if (ent == nullptr) TORRENT_FAIL_BDECODE(boost::system::errc::not_enough_memory); + stack.push_back(ent); + break; + } + case lazy_entry::int_t: + case lazy_entry::string_t: + case lazy_entry::none_t: + break; + } + + --item_limit; + if (item_limit <= 0) TORRENT_FAIL_BDECODE(bdecode_errors::limit_exceeded); + + top = stack.back(); + switch (t) + { + case 'd': + top->construct_dict(start - 1); + break; + case 'l': + top->construct_list(start - 1); + break; + case 'i': + { + char const* int_start = start; + start = find_char(start, end, 'e'); + top->construct_int(int_start, int(start - int_start)); + if (start == end) TORRENT_FAIL_BDECODE(bdecode_errors::unexpected_eof); + TORRENT_ASSERT(*start == 'e'); + ++start; + stack.pop_back(); + break; + } + default: + { + + if (!is_digit(t)) TORRENT_FAIL_BDECODE(bdecode_errors::expected_value); + std::int64_t len = t - '0'; + bdecode_errors::error_code_enum e = bdecode_errors::no_error; + start = parse_string(start, end, e, len); + if (e) TORRENT_FAIL_BDECODE(e); + + top->construct_string(start, int(len)); + start += len; + stack.pop_back(); + break; + } + } + } + return 0; + } + + int lazy_entry::capacity() const + { + TORRENT_ASSERT(m_type == dict_t || m_type == list_t); + if (m_data.list == nullptr) return 0; + if (m_type == dict_t) + return int(m_data.dict[0].val.m_len); + else + return int(m_data.list[0].m_len); + } + + std::int64_t lazy_entry::int_value() const + { + TORRENT_ASSERT(m_type == int_t); + std::int64_t val = 0; + bool const negative = (*m_data.start == '-'); + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + parse_int(m_data.start + int(negative) + , m_data.start + m_size, 'e', val, ec); + if (ec) return 0; + if (negative) val = -val; + return val; + } + + lazy_entry* lazy_entry::dict_append(char const* name) + { + TORRENT_ASSERT(m_type == dict_t); + TORRENT_ASSERT(int(m_size) <= this->capacity()); + if (m_data.dict == nullptr) + { + int const capacity = lazy_entry_dict_init; + m_data.dict = new (std::nothrow) lazy_dict_entry[capacity + 1]; + if (m_data.dict == nullptr) return nullptr; + m_data.dict[0].val.m_len = std::uint32_t(capacity); + } + else if (int(m_size) == this->capacity()) + { + std::size_t const capacity = std::size_t(this->capacity()) * lazy_entry_grow_factor / 100; + auto* tmp = new (std::nothrow) lazy_dict_entry[capacity + 1]; + if (tmp == nullptr) return nullptr; + std::move(m_data.dict, m_data.dict + m_size + 1, tmp); + + delete[] m_data.dict; + m_data.dict = tmp; + m_data.dict[0].val.m_len = std::uint32_t(capacity); + } + + TORRENT_ASSERT(int(m_size) < this->capacity()); + lazy_dict_entry& ret = m_data.dict[1 + m_size++]; + ret.name = name; + return &ret.val; + } + + void lazy_entry::pop() + { + if (m_size > 0) --m_size; + } + +namespace { + + // the number of decimal digits needed + // to represent the given value + int num_digits(int val) + { + int ret = 1; + while (val >= 10) + { + ++ret; + val /= 10; + } + return ret; + } + } + + void lazy_entry::construct_string(char const* start, int const length) + { + TORRENT_ASSERT(m_type == none_t); + TORRENT_ASSERT(length >= 0); + m_type = string_t; + m_data.start = start; + m_size = std::uint32_t(length); + m_begin = start - 1 - num_digits(length); + m_len = std::uint32_t(start - m_begin + length); + } + +namespace { + + // str1 is 0-terminated + // str2 is not, str2 is len2 chars + bool string_equal(char const* str1, char const* str2, int len2) + { + while (len2 > 0) + { + if (*str1 != *str2) return false; + if (*str1 == 0) return false; + ++str1; + ++str2; + --len2; + } + return *str1 == 0; + } + } + + std::pair lazy_entry::dict_at(int const i) const + { + TORRENT_ASSERT(m_type == dict_t); + TORRENT_ASSERT(i < int(m_size)); + lazy_dict_entry const& e = m_data.dict[i + 1]; + TORRENT_ASSERT(e.val.m_begin >= e.name); + return std::make_pair(std::string(e.name, std::size_t(e.val.m_begin - e.name)), &e.val); + } + + std::string lazy_entry::dict_find_string_value(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::string_t) return std::string(); + return e->string_value(); + } + + pascal_string lazy_entry::dict_find_pstr(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::string_t) return {nullptr, 0}; + return e->string_pstr(); + } + + lazy_entry const* lazy_entry::dict_find_string(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::string_t) return nullptr; + return e; + } + + lazy_entry const* lazy_entry::dict_find_int(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::int_t) return nullptr; + return e; + } + + std::int64_t lazy_entry::dict_find_int_value(char const* name + , std::int64_t default_val) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::int_t) return default_val; + return e->int_value(); + } + + lazy_entry const* lazy_entry::dict_find_dict(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::dict_t) return nullptr; + return e; + } + + lazy_entry const* lazy_entry::dict_find_dict(std::string const& name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::dict_t) return nullptr; + return e; + } + + lazy_entry const* lazy_entry::dict_find_list(char const* name) const + { + lazy_entry const* e = dict_find(name); + if (e == nullptr || e->type() != lazy_entry::list_t) return nullptr; + return e; + } + + lazy_entry* lazy_entry::dict_find(char const* name) + { + TORRENT_ASSERT(m_type == dict_t); + for (int i = 0; i < int(m_size); ++i) + { + lazy_dict_entry& e = m_data.dict[i + 1]; + if (string_equal(name, e.name, int(e.val.m_begin - e.name))) + return &e.val; + } + return nullptr; + } + + lazy_entry* lazy_entry::dict_find(std::string const& name) + { + TORRENT_ASSERT(m_type == dict_t); + for (int i = 0; i < int(m_size); ++i) + { + lazy_dict_entry& e = m_data.dict[i+1]; + if (int(name.size()) != e.val.m_begin - e.name) continue; + if (std::equal(name.begin(), name.end(), e.name)) + return &e.val; + } + return nullptr; + } + + lazy_entry* lazy_entry::list_append() + { + TORRENT_ASSERT(m_type == list_t); + TORRENT_ASSERT(int(m_size) <= this->capacity()); + if (m_data.start == nullptr) + { + int const capacity = lazy_entry_list_init; + m_data.list = new (std::nothrow) lazy_entry[capacity + 1]; + if (m_data.list == nullptr) return nullptr; + m_data.list[0].m_len = std::uint32_t(capacity); + } + else if (int(m_size) == this->capacity()) + { + std::size_t const capacity = std::size_t(this->capacity()) * lazy_entry_grow_factor / 100; + lazy_entry* tmp = new (std::nothrow) lazy_entry[capacity + 1]; + if (tmp == nullptr) return nullptr; + std::move(m_data.list, m_data.list + m_size + 1, tmp); + + delete[] m_data.list; + m_data.list = tmp; + m_data.list[0].m_len = std::uint32_t(capacity); + } + + TORRENT_ASSERT(int(m_size) < this->capacity()); + return &m_data.list[1 + (m_size++)]; + } + + std::string lazy_entry::list_string_value_at(int i) const + { + lazy_entry const* e = list_at(i); + if (e == nullptr || e->type() != lazy_entry::string_t) return std::string(); + return e->string_value(); + } + + pascal_string lazy_entry::list_pstr_at(int i) const + { + lazy_entry const* e = list_at(i); + if (e == nullptr || e->type() != lazy_entry::string_t) return {nullptr, 0}; + return e->string_pstr(); + } + + std::int64_t lazy_entry::list_int_value_at(int i, std::int64_t default_val) const + { + lazy_entry const* e = list_at(i); + if (e == nullptr || e->type() != lazy_entry::int_t) return default_val; + return e->int_value(); + } + + void lazy_entry::clear() + { + switch (m_type) + { + case list_t: + delete[] m_data.list; + break; + case dict_t: + delete[] m_data.dict; + break; + default: break; + } + m_data.start = nullptr; + m_size = 0; + m_type = none_t; + } + + std::pair lazy_entry::data_section() const + { + return {m_begin, m_len}; + } + + lazy_entry::lazy_entry(lazy_entry&& other) + : lazy_entry() + { + this->swap(other); + } + + lazy_entry& lazy_entry::operator=(lazy_entry&& other) + { + this->swap(other); + return *this; + } + + namespace { + + int line_longer_than(lazy_entry const& e, int limit) + { + int line_len = 0; + switch (e.type()) + { + case lazy_entry::list_t: + line_len += 4; + if (line_len > limit) return -1; + for (int i = 0; i < e.list_size(); ++i) + { + int ret = line_longer_than(*e.list_at(i), limit - line_len); + if (ret == -1) return -1; + line_len += ret + 2; + } + break; + case lazy_entry::dict_t: + line_len += 4; + if (line_len > limit) return -1; + for (int i = 0; i < e.dict_size(); ++i) + { + line_len += 4 + int(e.dict_at(i).first.size()); + if (line_len > limit) return -1; + int ret = line_longer_than(*e.dict_at(i).second, limit - line_len); + if (ret == -1) return -1; + line_len += ret + 1; + } + break; + case lazy_entry::string_t: + line_len += 3 + e.string_length(); + break; + case lazy_entry::int_t: + { + std::int64_t val = e.int_value(); + while (val > 0) + { + ++line_len; + val /= 10; + } + line_len += 2; + } + break; + case lazy_entry::none_t: + line_len += 4; + break; + } + + if (line_len > limit) return -1; + return line_len; + } + + void print_string(std::string& ret, char const* str, int const len, bool single_line) + { + TORRENT_ASSERT(len >= 0); + bool printable = true; + for (int i = 0; i < len; ++i) + { + char c = str[i]; + if (c >= 32 && c < 127) continue; + printable = false; + break; + } + ret += "'"; + if (printable) + { + if (single_line && len > 30) + { + ret.append(str, 14); + ret += "..."; + ret.append(str + len - 14, 14); + } + else + ret.append(str, std::size_t(len)); + ret += "'"; + return; + } + if (single_line && len > 20) + { + detail::escape_string(ret, str, 9); + ret += "..."; + detail::escape_string(ret, str + len - 9, 9); + } + else + { + detail::escape_string(ret, str, len); + } + ret += "'"; + } + } // anonymous namespace + + std::string print_entry(lazy_entry const& e, bool single_line, int indent) + { + char indent_str[200]; + std::memset(indent_str, ' ', 200); + indent_str[0] = ','; + indent_str[1] = '\n'; + indent_str[199] = 0; + if (indent < 197 && indent >= 0) indent_str[indent + 2] = 0; + std::string ret; + switch (e.type()) + { + case lazy_entry::none_t: return "none"; + case lazy_entry::int_t: + { + char str[100]; + std::snprintf(str, sizeof(str), "%" PRId64, e.int_value()); + return str; + } + case lazy_entry::string_t: + { + print_string(ret, e.string_ptr(), e.string_length(), single_line); + return ret; + } + case lazy_entry::list_t: + { + ret += '['; + bool one_liner = line_longer_than(e, 200) != -1 || single_line; + + if (!one_liner) ret += indent_str + 1; + for (int i = 0; i < e.list_size(); ++i) + { + if (i == 0 && one_liner) ret += " "; + ret += print_entry(*e.list_at(i), single_line, indent + 2); + if (i < e.list_size() - 1) ret += (one_liner?", ":indent_str); + else ret += (one_liner?" ":indent_str+1); + } + ret += ']'; + return ret; + } + case lazy_entry::dict_t: + { + ret += '{'; + bool one_liner = line_longer_than(e, 200) != -1 || single_line; + + if (!one_liner) ret += indent_str+1; + for (int i = 0; i < e.dict_size(); ++i) + { + if (i == 0 && one_liner) ret += ' '; + std::pair ent = e.dict_at(i); + print_string(ret, ent.first.c_str(), int(ent.first.size()), true); + ret += ": "; + ret += print_entry(*ent.second, single_line, indent + 2); + if (i < e.dict_size() - 1) ret += (one_liner?", ":indent_str); + else ret += (one_liner?" ":indent_str+1); + } + ret += "}"; + return ret; + } + } + return ret; + } +} + +#endif // TORRENT_ABI_VERSION diff --git a/src/listen_socket_handle.cpp b/src/listen_socket_handle.cpp new file mode 100644 index 0000000..c72a9ab --- /dev/null +++ b/src/listen_socket_handle.cpp @@ -0,0 +1,74 @@ +/* + +Copyright (c) 2017, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +namespace libtorrent { namespace aux { + + address listen_socket_handle::get_external_address() const + { + auto s = m_sock.lock(); + TORRENT_ASSERT(s); + if (!s) throw_ex(); + return s->external_address.external_address(); + } + + tcp::endpoint listen_socket_handle::get_local_endpoint() const + { + auto s = m_sock.lock(); + TORRENT_ASSERT(s); + if (!s) throw_ex(); + return s->local_endpoint; + } + + bool listen_socket_handle::is_ssl() const + { + auto s = m_sock.lock(); + TORRENT_ASSERT(s); + if (!s) throw_ex(); + return s->ssl == transport::ssl; + } + + listen_socket_t* listen_socket_handle::get() const + { + return m_sock.lock().get(); + } + + bool listen_socket_handle::can_route(address const& a) const + { + auto s = m_sock.lock(); + if (!s) return false; + return s->can_route(a); + } + +} } diff --git a/src/lsd.cpp b/src/lsd.cpp new file mode 100644 index 0000000..833c082 --- /dev/null +++ b/src/lsd.cpp @@ -0,0 +1,338 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include // for vsnprintf + +#include "libtorrent/lsd.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/socket_io.hpp" // for print_address +#include "libtorrent/debug.hpp" +#include "libtorrent/hex.hpp" // to_hex, from_hex +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/enum_net.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +using namespace std::placeholders; + +namespace libtorrent { + +namespace { + +int render_lsd_packet(char* dst, int const len, int const listen_port + , char const* info_hash_hex, int const cookie, char const* host) +{ + TORRENT_ASSERT(len > 0); + return std::snprintf(dst, aux::numeric_cast(len), + "BT-SEARCH * HTTP/1.1\r\n" + "Host: %s:6771\r\n" + "Port: %d\r\n" + "Infohash: %s\r\n" + "cookie: %x\r\n" + "\r\n\r\n", host, listen_port, info_hash_hex, cookie); +} +} // anonymous namespace + +lsd::lsd(io_service& ios, aux::lsd_callback& cb + , address const& listen_address, address const& netmask) + : m_callback(cb) + , m_listen_address(listen_address) + , m_netmask(netmask) + , m_socket(ios) + , m_broadcast_timer(ios) + , m_cookie((random(0x7fffffff) ^ std::uintptr_t(this)) & 0x7fffffff) +{ +} + +#ifndef TORRENT_DISABLE_LOGGING +bool lsd::should_log() const +{ + return m_callback.should_log_lsd(); +} + +TORRENT_FORMAT(2, 3) +void lsd::debug_log(char const* fmt, ...) const +{ + if (!should_log()) return; + va_list v; + va_start(v, fmt); + + char buf[1024]; + std::vsnprintf(buf, sizeof(buf), fmt, v); + va_end(v); + m_callback.log_lsd(buf); +} +#endif + +namespace { + address_v4 const lsd_multicast_addr4 = make_address_v4("239.192.152.143"); + address_v6 const lsd_multicast_addr6 = make_address_v6("ff15::efc0:988f"); + int const lsd_port = 6771; +} + +void lsd::start(error_code& ec) +{ + using namespace boost::asio::ip::multicast; + bool const v4 = m_listen_address.is_v4(); + m_socket.open(v4 ? udp::v4() : udp::v6(), ec); + if (ec) return; + + m_socket.set_option(udp::socket::reuse_address(true), ec); + if (ec) return; + + m_socket.bind(udp::endpoint(v4 ? address(address_v4::any()) : address(address_v6::any()), lsd_port), ec); + if (ec) return; + if (v4) + m_socket.set_option(join_group(lsd_multicast_addr4, m_listen_address.to_v4()), ec); + else + m_socket.set_option(join_group(lsd_multicast_addr6, m_listen_address.to_v6().scope_id()), ec); + if (ec) return; + m_socket.set_option(hops(32), ec); + if (ec) return; + m_socket.set_option(enable_loopback(true), ec); + if (ec) return; + if (v4) + { + m_socket.set_option(outbound_interface(m_listen_address.to_v4()), ec); + if (ec) return; + } + + ADD_OUTSTANDING_ASYNC("lsd::on_announce"); + m_socket.async_receive(boost::asio::null_buffers{} + , std::bind(&lsd::on_announce, self(), _1)); +} + +lsd::~lsd() = default; + +void lsd::announce(sha1_hash const& ih, int listen_port) +{ + announce_impl(ih, listen_port, 0); +} + +void lsd::announce_impl(sha1_hash const& ih, int const listen_port + , int retry_count) +{ + if (m_disabled) return; + + char msg[200]; + + error_code ec; + if (!m_disabled) + { + bool const v4 = m_listen_address.is_v4(); + char const* v4_address = "239.192.152.143"; + char const* v6_address = "[ff15::efc0:988f]"; + + int const msg_len = render_lsd_packet(msg, sizeof(msg), listen_port, aux::to_hex(ih).c_str() + , m_cookie, v4 ? v4_address : v6_address); + + udp::endpoint const to(v4 ? address(lsd_multicast_addr4) : address(lsd_multicast_addr6) + , lsd_port); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("==> LSD: ih: %s port: %u [iface: %s]", aux::to_hex(ih).c_str() + , listen_port, m_listen_address.to_string().c_str()); +#endif + + m_socket.send_to(boost::asio::buffer(msg, static_cast(msg_len)) + , to, {}, ec); + if (ec) + { + m_disabled = true; +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** LSD: failed to send message: (%d) %s", ec.value() + , ec.message().c_str()); + } +#endif + } + } + + ++retry_count; + if (retry_count >= 3) return; + + if (m_disabled) return; + + ADD_OUTSTANDING_ASYNC("lsd::resend_announce"); + m_broadcast_timer.expires_from_now(seconds(2 * retry_count), ec); + m_broadcast_timer.async_wait(std::bind(&lsd::resend_announce, self(), _1 + , ih, listen_port, retry_count)); +} + +void lsd::resend_announce(error_code const& e, sha1_hash const& info_hash + , int listen_port, int retry_count) +{ + COMPLETE_ASYNC("lsd::resend_announce"); + if (e) return; + + announce_impl(info_hash, listen_port, retry_count); +} + +void lsd::on_announce(error_code const& ec) +{ + COMPLETE_ASYNC("lsd::on_announce"); + if (ec) return; + + std::array buffer; + udp::endpoint from; + error_code err; + int const len = static_cast(m_socket.receive_from( + boost::asio::buffer(buffer), from, {}, err)); + + ADD_OUTSTANDING_ASYNC("lsd::on_announce"); + m_socket.async_receive(boost::asio::null_buffers{} + , std::bind(&lsd::on_announce, self(), _1)); + + if (!match_addr_mask(from.address(), m_listen_address, m_netmask)) + { + // we don't care about this network. Ignore this packet +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: receive from out of network: %s" + , from.address().to_string().c_str()); +#endif + return; + } + + if (err) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: receive error: %s", err.message().c_str()); +#endif + return; + } + + http_parser p; + + bool error = false; + p.incoming(span{buffer.data(), len}, error); + + if (!p.header_finished() || error) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: incomplete HTTP message"); +#endif + return; + } + + if (p.method() != "bt-search") + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: invalid HTTP method: %s", p.method().c_str()); +#endif + return; + } + + std::string const& port_str = p.header("port"); + if (port_str.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: invalid BT-SEARCH, missing port"); +#endif + return; + } + + long const port = std::strtol(port_str.c_str(), nullptr, 10); + if (port <= 0 || port >= int(std::numeric_limits::max())) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: invalid BT-SEARCH port value: %s", port_str.c_str()); +#endif + return; + } + + auto const& headers = p.headers(); + + auto const cookie_iter = headers.find("cookie"); + if (cookie_iter != headers.end()) + { + // we expect it to be hexadecimal + // if it isn't, it's not our cookie anyway + long const cookie = std::strtol(cookie_iter->second.c_str(), nullptr, 16); + if (cookie == m_cookie) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: ignoring packet (cookie matched our own): %x" + , m_cookie); +#endif + return; + } + } + + auto const ihs = headers.equal_range("infohash"); + for (auto i = ihs.first; i != ihs.second; ++i) + { + std::string const& ih_str = i->second; + if (ih_str.size() != 40) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== LSD: invalid BT-SEARCH, invalid infohash: %s" + , ih_str.c_str()); +#endif + continue; + } + + sha1_hash ih; + aux::from_hex(ih_str, ih.data()); + + if (!ih.is_all_zeros()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("<== LSD: %s:%d ih: %s" + , print_address(from.address()).c_str() + , int(port), ih_str.c_str()); + } +#endif + // we got an announce, pass it on through the callback + m_callback.on_lsd_peer(tcp::endpoint(from.address(), std::uint16_t(port)), ih); + } + } +} + +void lsd::close() +{ + error_code ec; + m_socket.close(ec); + m_broadcast_timer.cancel(ec); + m_disabled = true; +} + +} // libtorrent namespace diff --git a/src/magnet_uri.cpp b/src/magnet_uri.cpp new file mode 100644 index 0000000..7abc96b --- /dev/null +++ b/src/magnet_uri.cpp @@ -0,0 +1,345 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/hex.hpp" // to_hex, from_hex +#include "libtorrent/socket_io.hpp" + +namespace libtorrent { + + std::string make_magnet_uri(torrent_handle const& handle) + { + if (!handle.is_valid()) return ""; + + std::string ret; + sha1_hash const& ih = handle.info_hash(); + ret += "magnet:?xt=urn:btih:"; + ret += aux::to_hex(ih); + + torrent_status st = handle.status(torrent_handle::query_name); + if (!st.name.empty()) + { + ret += "&dn="; + ret += escape_string(st.name); + } + + for (auto const& tr : handle.trackers()) + { + ret += "&tr="; + ret += escape_string(tr.url); + } + + for (auto const& s : handle.url_seeds()) + { + ret += "&ws="; + ret += escape_string(s); + } + + return ret; + } + + std::string make_magnet_uri(torrent_info const& info) + { + std::string ret; + sha1_hash const& ih = info.info_hash(); + ret += "magnet:?xt=urn:btih:"; + ret += aux::to_hex(ih); + + std::string const& name = info.name(); + + if (!name.empty()) + { + ret += "&dn="; + ret += escape_string(name); + } + + for (auto const& tr : info.trackers()) + { + ret += "&tr="; + ret += escape_string(tr.url); + } + + for (auto const& s : info.web_seeds()) + { + if (s.type != web_seed_entry::url_seed) continue; + + ret += "&ws="; + ret += escape_string(s.url); + } + + return ret; + } + +#if TORRENT_ABI_VERSION == 1 + + namespace { + torrent_handle add_magnet_uri_deprecated(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec) + { + add_torrent_params params(p); + parse_magnet_uri(uri, params, ec); + if (ec) return torrent_handle(); + return ses.add_torrent(std::move(params), ec); + } + } + + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p, error_code& ec) + { + return add_magnet_uri_deprecated(ses, uri, p, ec); + } + +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , std::string const& save_path + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + add_torrent_params params(std::move(sc)); + error_code ec; + parse_magnet_uri(uri, params, ec); + params.storage_mode = storage_mode; + params.userdata = userdata; + params.save_path = save_path; + + if (paused) params.flags |= add_torrent_params::flag_paused; + else params.flags &= ~add_torrent_params::flag_paused; + + return ses.add_torrent(std::move(params)); + } + + torrent_handle add_magnet_uri(session& ses, std::string const& uri + , add_torrent_params const& p) + { + error_code ec; + torrent_handle ret = add_magnet_uri_deprecated(ses, uri, p, ec); + if (ec) aux::throw_ex(ec); + return ret; + } +#endif // BOOST_NO_EXCEPTIONS +#endif // TORRENT_ABI_VERSION + + add_torrent_params parse_magnet_uri(string_view uri, error_code& ec) + { + add_torrent_params ret; + parse_magnet_uri(uri, ret, ec); + return ret; + } + + void parse_magnet_uri(string_view uri, add_torrent_params& p, error_code& ec) + { + ec.clear(); + std::string display_name; + + string_view sv(uri); + if (sv.substr(0, 8) != "magnet:?"_sv) + { + ec = errors::unsupported_url_protocol; + return; + } + sv = sv.substr(8); + + int tier = 0; + bool has_ih = false; + while (!sv.empty()) + { + string_view name; + std::tie(name, sv) = split_string(sv, '='); + string_view value; + std::tie(value, sv) = split_string(sv, '&'); + + // parameter names are allowed to have a .-suffix. + // the number has no meaning, just strip it + // if the characters after the period are not digits, don't strip + // anything + string_view number; + string_view stripped_name; + std::tie(stripped_name, number) = split_string(name, '.'); + if (std::all_of(number.begin(), number.end(), [](char const c) { return is_digit(c); } )) + name = stripped_name; + + if (name == "dn"_sv) // display name + { + error_code e; + display_name = unescape_string(value, e); + } + else if (name == "tr"_sv) // tracker + { + // since we're about to assign tiers to the trackers, make sure the two + // vectors are aligned + if (p.tracker_tiers.size() != p.trackers.size()) + p.tracker_tiers.resize(p.trackers.size(), 0); + error_code e; + std::string tracker = unescape_string(value, e); + if (!e && !tracker.empty()) + { + p.trackers.push_back(std::move(tracker)); + p.tracker_tiers.push_back(tier++); + } + } + else if (name == "ws"_sv) // web seed + { + error_code e; + std::string webseed = unescape_string(value, e); + if (!e) p.url_seeds.push_back(std::move(webseed)); + } + else if (name == "xt"_sv) + { + std::string unescaped_btih; + if (value.find('%') != string_view::npos) + { + unescaped_btih = unescape_string(value, ec); + if (ec) return; + value = unescaped_btih; + } + + if (value.substr(0, 9) != "urn:btih:") continue; + value = value.substr(9); + + sha1_hash info_hash; + if (value.size() == 40) aux::from_hex({value.data(), 40}, info_hash.data()); + else if (value.size() == 32) + { + std::string const ih = base32decode(value); + if (ih.size() != 20) + { + ec = errors::invalid_info_hash; + return; + } + info_hash.assign(ih); + } + else + { + ec = errors::invalid_info_hash; + return; + } + p.info_hash = info_hash; + has_ih = true; + } + else if (name == "so"_sv) // select-only (files) + { + // accept only digits, '-' and ',' + if (std::any_of(value.begin(), value.end(), [](char c) + { return !is_digit(c) && c != '-' && c != ','; })) + continue; + + do + { + string_view token; + std::tie(token, value) = split_string(value, ','); + + if (token.empty()) continue; + + int idx1, idx2; + // TODO: what's the right number here? + constexpr int max_index = 10000; // can't risk out of memory + + auto const divider = token.find_first_of('-'); + if (divider != std::string::npos) // it's a range + { + if (divider == 0) // no start index + continue; + if (divider == token.size() - 1) // no end index + continue; + + idx1 = std::atoi(token.substr(0, divider).to_string().c_str()); + if (idx1 < 0 || idx1 > max_index) // invalid index + continue; + idx2 = std::atoi(token.substr(divider + 1).to_string().c_str()); + if (idx2 < 0 || idx2 > max_index) // invalid index + continue; + + if (idx1 > idx2) // wrong range limits + continue; + } + else // it's an index + { + idx1 = std::atoi(token.to_string().c_str()); + if (idx1 < 0 || idx1 > max_index) // invalid index + continue; + idx2 = idx1; + } + + if (int(p.file_priorities.size()) <= idx2) + p.file_priorities.resize(static_cast(idx2) + 1, dont_download); + + for (int i = idx1; i <= idx2; i++) + p.file_priorities[std::size_t(i)] = default_priority; + + } while (!value.empty()); + } + else if (name == "x.pe") + { + error_code e; + tcp::endpoint endp = parse_endpoint(value, e); + if (!e) p.peers.push_back(std::move(endp)); + } +#ifndef TORRENT_DISABLE_DHT + else if (name == "dht"_sv) + { + auto const divider = value.find_last_of(':'); + if (divider != std::string::npos) + { + int const port = std::atoi(value.substr(divider + 1).to_string().c_str()); + if (port > 0 && port < int(std::numeric_limits::max())) + p.dht_nodes.emplace_back(value.substr(0, divider).to_string(), port); + } + } +#endif + } + + if (!has_ih) + { + ec = errors::missing_info_hash_in_uri; + return; + } + + if (!display_name.empty()) p.name = display_name; + } + + add_torrent_params parse_magnet_uri(string_view uri) + { + error_code ec; + add_torrent_params ret; + parse_magnet_uri(uri, ret, ec); + if (ec) aux::throw_ex(ec); + return ret; + } +} diff --git a/src/merkle.cpp b/src/merkle.cpp new file mode 100644 index 0000000..215cf48 --- /dev/null +++ b/src/merkle.cpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/merkle.hpp" + +namespace libtorrent { + + int merkle_get_parent(int tree_node) + { + // node 0 doesn't have a parent + TORRENT_ASSERT(tree_node > 0); + return (tree_node - 1) / 2; + } + + int merkle_get_sibling(int tree_node) + { + // node 0 doesn't have a sibling + TORRENT_ASSERT(tree_node > 0); + // even numbers have their sibling to the left + // odd numbers have their sibling to the right + return tree_node + ((tree_node&1)?1:-1); + } + + int merkle_num_nodes(int leafs) + { + TORRENT_ASSERT(leafs > 0); + return (leafs << 1) - 1; + } + + int merkle_num_leafs(int pieces) + { + TORRENT_ASSERT(pieces > 0); + // round up to nearest 2 exponent + int ret = 1; + while (pieces > ret) ret <<= 1; + return ret; + } + +} + diff --git a/src/natpmp.cpp b/src/natpmp.cpp new file mode 100644 index 0000000..aa186c4 --- /dev/null +++ b/src/natpmp.cpp @@ -0,0 +1,926 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ +#include "libtorrent/config.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if defined TORRENT_OS2 +#include +#endif + +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include // for snprintf +#include // for PRId64 et.al. +#include +#include +#include // for memcpy + +#include "libtorrent/natpmp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_local +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { + +struct pcp_error_category final : boost::system::error_category +{ + const char* name() const BOOST_SYSTEM_NOEXCEPT override + { return "pcp error"; } + std::string message(int ev) const override + { + static char const* msgs[] = + { + "success", + "unsupported version", + "not authorized", + "malformed request", + "unsupported opcode", + "unsupported option", + "malformed option", + "network failure", + "no resources", + "unsupported protocol", + "user exceeded quota", + "cannot provide external", + "address mismatch", + "excessive remote peers", + }; + if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) + return "Unknown error"; + return msgs[ev]; + } + boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT override + { return boost::system::error_condition(ev, *this); } +}; + +boost::system::error_category& pcp_category() +{ + static pcp_error_category pcp_category; + return pcp_category; +} + +namespace errors +{ + // hidden + boost::system::error_code make_error_code(pcp_errors e) + { + return boost::system::error_code(e, pcp_category()); + } +} + +error_code natpmp::from_result_code(int const version, int result) +{ + if (version == version_natpmp) + { + // a few nat-pmp result codes map to different codes + // in pcp + switch (result) + { + case 3:result = 7; break; + case 4:result = 8; break; + case 5:result = 4; break; + } + } + return errors::pcp_errors(result); +} + +char const* natpmp::version_to_string(protocol_version version) +{ + return version == version_natpmp ? "NAT-PMP" : "PCP"; +} + +using namespace aux; +using namespace std::placeholders; + +natpmp::natpmp(io_service& ios + , aux::portmap_callback& cb) + : m_callback(cb) + , m_socket(ios) + , m_send_timer(ios) + , m_refresh_timer(ios) +{ + // unfortunately async operations rely on the storage + // for this array not to be reallocated, by passing + // around pointers to its elements. so reserve size for now + m_mappings.reserve(10); +} + +void natpmp::start(ip_interface const& ip) +{ + TORRENT_ASSERT(is_single_thread()); + + // assume servers support PCP and fall back to NAT-PMP + // if necessary + m_version = version_pcp; + + address const& local_address = ip.interface_address; + + error_code ec; + auto const routes = enum_routes(get_io_service(m_socket), ec); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("failed to enumerate routes: %s" + , convert_from_native(ec.message()).c_str()); + } +#endif + disable(ec); + } + + auto const route = get_gateway(ip, routes); + + if (!route) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("failed to find default route for \"%s\" %s: %s" + , ip.name, local_address.to_string().c_str() + , convert_from_native(ec.message()).c_str()); + } +#endif + disable(ec); + return; + } + + m_disabled = false; + + udp::endpoint const nat_endpoint(*route, 5351); + if (nat_endpoint == m_nat_endpoint) return; + m_nat_endpoint = nat_endpoint; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("found gateway at: %s" + , print_address(m_nat_endpoint.address()).c_str()); + } +#endif + + m_socket.open(local_address.is_v4() ? udp::v4() : udp::v6(), ec); + if (ec) + { + disable(ec); + return; + } + m_socket.bind({local_address, 0}, ec); + if (ec) + { + disable(ec); + return; + } + + ADD_OUTSTANDING_ASYNC("natpmp::on_reply"); + m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0] + , sizeof(m_response_buffer)) + , m_remote, std::bind(&natpmp::on_reply, self(), _1, _2)); + if (m_version == version_natpmp) + send_get_ip_address_request(); + + for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == portmap_protocol::none + || i->act != portmap_action::none) + continue; + i->act = portmap_action::add; + update_mapping(port_mapping_t(int(i - m_mappings.begin()))); + } +} + +void natpmp::send_get_ip_address_request() +{ + TORRENT_ASSERT(is_single_thread()); + using namespace libtorrent::detail; + + // this opcode only exists in NAT-PMP + // PCP routers report the external IP in the response to a MAP operation + TORRENT_ASSERT(m_version == version_natpmp); + if (m_version != version_natpmp) + return; + + char buf[2]; + char* out = buf; + write_uint8(version_natpmp, out); + write_uint8(0, out); // public IP address request opcode +#ifndef TORRENT_DISABLE_LOGGING + log("==> get public IP address"); +#endif + + error_code ec; + m_socket.send_to(boost::asio::buffer(buf, sizeof(buf)), m_nat_endpoint, 0, ec); +} + +bool natpmp::get_mapping(port_mapping_t const index, int& local_port + , int& external_port, portmap_protocol& protocol) const +{ + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(index < m_mappings.end_index() && index >= port_mapping_t{}); + if (index >= m_mappings.end_index() || index < port_mapping_t{}) return false; + mapping_t const& m = m_mappings[index]; + if (m.protocol == portmap_protocol::none) return false; + local_port = m.local_port; + external_port = m.external_port; + protocol = m.protocol; + return true; +} + +#ifndef TORRENT_DISABLE_LOGGING +bool natpmp::should_log() const +{ + return m_callback.should_log_portmap(portmap_transport::natpmp); +} + +void natpmp::mapping_log(char const* op, mapping_t const& m) const +{ + if (should_log()) + { + log("%s-mapping: proto: %s port: %d local-port: %d action: %s ttl: %" PRId64 + , op + , m.protocol == portmap_protocol::none + ? "none" : to_string(m.protocol) + , m.external_port + , m.local_port + , to_string(m.act) + , (m.expires.time_since_epoch() != seconds(0)) + ? total_seconds(m.expires - aux::time_now()) + : std::int64_t(0)); + } +} + +TORRENT_FORMAT(2, 3) +void natpmp::log(char const* fmt, ...) const +{ + TORRENT_ASSERT(is_single_thread()); + if (!should_log()) return; + char msg[200]; + va_list v; + va_start(v, fmt); + std::vsnprintf(msg, sizeof(msg), fmt, v); + va_end(v); + m_callback.log_portmap(portmap_transport::natpmp, msg); +} +#endif + +void natpmp::disable(error_code const& ec) +{ + TORRENT_ASSERT(is_single_thread()); + m_disabled = true; + + for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == portmap_protocol::none) continue; + portmap_protocol const proto = i->protocol; + i->protocol = portmap_protocol::none; + port_mapping_t const index(static_cast(i - m_mappings.begin())); + m_callback.on_port_mapping(index, address(), 0, proto, ec + , portmap_transport::natpmp); + } + close_impl(); +} + +void natpmp::delete_mapping(port_mapping_t const index) +{ + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(index < m_mappings.end_index() && index >= port_mapping_t{}); + if (index >= m_mappings.end_index() || index < port_mapping_t{}) return; + mapping_t& m = m_mappings[index]; + + if (m.protocol == portmap_protocol::none) return; + if (!m.map_sent) + { + m.act = portmap_action::none; + m.protocol = portmap_protocol::none; + return; + } + + m.act = portmap_action::del; + update_mapping(index); +} + +port_mapping_t natpmp::add_mapping(portmap_protocol const p, int const external_port + , tcp::endpoint const local_ep) +{ + TORRENT_ASSERT(is_single_thread()); + + if (m_disabled) return port_mapping_t{-1}; + + auto i = std::find_if(m_mappings.begin() + , m_mappings.end(), [] (mapping_t const& m) { return m.protocol == portmap_protocol::none; }); + if (i == m_mappings.end()) + { + m_mappings.push_back(mapping_t()); + i = m_mappings.end() - 1; + } + aux::random_bytes(i->nonce); + i->protocol = p; + i->external_port = external_port; + i->local_port = local_ep.port(); + i->act = portmap_action::add; + + port_mapping_t const mapping_index(static_cast(i - m_mappings.begin())); +#ifndef TORRENT_DISABLE_LOGGING + mapping_log("add",*i); +#endif + + update_mapping(mapping_index); + return port_mapping_t{mapping_index}; +} + +void natpmp::try_next_mapping(port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + if (i < prev(m_mappings.end_index())) + { + update_mapping(next(i)); + return; + } + + auto const m = std::find_if( + m_mappings.begin(), m_mappings.end() + , [] (mapping_t const& ma) { return ma.act != portmap_action::none + && ma.protocol != portmap_protocol::none; }); + + if (m == m_mappings.end()) + { + if (m_abort) + { + error_code ec; + m_send_timer.cancel(ec); + m_socket.close(ec); + } + return; + } + + update_mapping(port_mapping_t(static_cast(m - m_mappings.begin()))); +} + +void natpmp::update_mapping(port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + if (i == m_mappings.end_index()) + { + if (m_abort) + { + error_code ec; + m_send_timer.cancel(ec); + m_socket.close(ec); + } + return; + } + + mapping_t const& m = m_mappings[i]; + +#ifndef TORRENT_DISABLE_LOGGING + mapping_log("update", m); +#endif + + if (m.act == portmap_action::none + || m.protocol == portmap_protocol::none) + { + try_next_mapping(i); + return; + } + + if (m_currently_mapping == port_mapping_t{-1}) + { + // the socket is not currently in use + // send out a mapping request + m_retry_count = 0; + send_map_request(i); + } +} + +void natpmp::send_map_request(port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + using namespace libtorrent::detail; + + TORRENT_ASSERT(m_currently_mapping == port_mapping_t{-1} + || m_currently_mapping == i); + m_currently_mapping = i; + mapping_t& m = m_mappings[i]; + TORRENT_ASSERT(m.act != portmap_action::none); + char buf[60]; + char* out = buf; + int ttl = m.act == portmap_action::add ? 3600 : 0; + if (m_version == version_natpmp) + { + write_uint8(m_version, out); + write_uint8(m.protocol == portmap_protocol::udp ? 1 : 2, out); // map "protocol" + write_uint16(0, out); // reserved + write_uint16(m.local_port, out); // private port + write_uint16(m.external_port, out); // requested public port + write_uint32(ttl, out); // port mapping lifetime + } + else if (m_version == version_pcp) + { + // PCP requires the use of IPv6 addresses even for IPv4 messages + write_uint8(m_version, out); + write_uint8(opcode_map, out); + write_uint16(0, out); // reserved + write_uint32(ttl, out); + error_code ec; + address const local_addr = m_socket.local_endpoint(ec).address(); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if ( should_log()) + { + log("*** port map, local_endpoint [ ec: %s:%d %s ]" + , ec.category().name() + , ec.value() + , ec.message().c_str()); + } +#endif + m_currently_mapping = port_mapping_t{-1}; + m.act = portmap_action::none; + return; + } + auto const local_bytes = local_addr.is_v4() + ? address_v6::v4_mapped(local_addr.to_v4()).to_bytes() + : local_addr.to_v6().to_bytes(); + out = std::copy(local_bytes.begin(), local_bytes.end(), out); + out = std::copy(m.nonce.begin(), m.nonce.end(), out); + // translate portmap_protocol to an IANA protocol number + int const protocol = + (m.protocol == portmap_protocol::tcp) ? 6 + : (m.protocol == portmap_protocol::udp) ? 17 + : 0; + write_int8(protocol, out); + write_uint8(0, out); // reserved + write_uint16(0, out); // reserved + write_uint16(m.local_port, out); + write_uint16(m.external_port, out); + address_v6 external_addr; + if (!m.external_address.is_unspecified()) + { + external_addr = m.external_address.is_v4() + ? address_v6::v4_mapped(m.external_address.to_v4()) + : m.external_address.to_v6(); + } + else if (is_local(local_addr)) + { + external_addr = local_addr.is_v4() + ? address_v6::v4_mapped(address_v4()) + : address_v6(); + } + else if (local_addr.is_v4()) + { + external_addr = address_v6::v4_mapped(local_addr.to_v4()); + } + else + { + external_addr = local_addr.to_v6(); + } + write_address(external_addr, out); + } + else + { + TORRENT_ASSERT_FAIL(); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("==> port map [ mapping: %d action: %s" + " transport: %s proto: %s local: %u external: %u ttl: %u ]" + , static_cast(i), to_string(m.act) + , version_to_string(m_version) + , to_string(m.protocol) + , m.local_port, m.external_port, ttl); + } +#endif + + error_code ec; + m_socket.send_to(boost::asio::buffer(buf, std::size_t(out - buf)), m_nat_endpoint, 0, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + log("*** port map [ ec: %s:%d %s ]" + , ec.category().name() + , ec.value() + , ec.message().c_str()); + } +#endif + m.map_sent = true; + m.outstanding_request = true; + + if (m_abort) + { + // when we're shutting down, ignore the + // responses and just remove all mappings + // immediately + m_currently_mapping = port_mapping_t{-1}; + m.act = portmap_action::none; + try_next_mapping(i); + } + else + { + ADD_OUTSTANDING_ASYNC("natpmp::resend_request"); + // linear back-off instead of exponential + ++m_retry_count; + m_send_timer.expires_from_now(milliseconds(250 * m_retry_count), ec); + m_send_timer.async_wait(std::bind(&natpmp::on_resend_request, self(), i, _1)); + } +} + +void natpmp::on_resend_request(port_mapping_t const i, error_code const& e) +{ + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("natpmp::resend_request"); + if (e) return; + resend_request(i); +} + +void natpmp::resend_request(port_mapping_t const i) +{ + if (m_currently_mapping != i) return; + + // if we're shutting down, don't retry, just move on + // to the next mapping + if (m_retry_count >= 9 || m_abort) + { + m_currently_mapping = port_mapping_t{-1}; + m_mappings[i].act = portmap_action::none; + // try again in two hours + m_mappings[i].expires = aux::time_now() + hours(2); + try_next_mapping(i); + return; + } + send_map_request(i); +} + +void natpmp::on_reply(error_code const& e + , std::size_t const bytes_transferred) +{ + TORRENT_ASSERT(is_single_thread()); + + COMPLETE_ASYNC("natpmp::on_reply"); + + using namespace libtorrent::detail; + if (e) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error on receiving reply: %s" + , convert_from_native(e.message()).c_str()); + } +#endif + return; + } + + if (m_abort) return; + + ADD_OUTSTANDING_ASYNC("natpmp::on_reply"); + // make a copy of the response packet buffer + // to avoid overwriting it in the next receive call + std::array msg_buf; + std::memcpy(msg_buf.data(), m_response_buffer, bytes_transferred); + + m_socket.async_receive_from(boost::asio::buffer(&m_response_buffer[0] + , sizeof(m_response_buffer)) + , m_remote, std::bind(&natpmp::on_reply, self(), _1, _2)); + + if (m_remote != m_nat_endpoint) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("received packet from wrong IP: %s" + , print_endpoint(m_remote).c_str()); + } +#endif + return; + } + + error_code ec; + m_send_timer.cancel(ec); + + if (bytes_transferred < 4) + { +#ifndef TORRENT_DISABLE_LOGGING + log("received packet of invalid size: %d", int(bytes_transferred)); +#endif + return; + } + + char* in = msg_buf.data(); + int const version = read_uint8(in); + + if (version != version_natpmp && version != version_pcp) + { +#ifndef TORRENT_DISABLE_LOGGING + log("unexpected version: %u", version); +#endif + return; + } + + int cmd = read_uint8(in); + if (version == version_pcp) + { + cmd &= 0x7f; + } + int result; + if (version == version_pcp) + { + ++in; // reserved + result = read_uint8(in); + } + else + { + result = read_uint16(in); + } + + if (result == errors::pcp_unsupp_version) + { +#ifndef TORRENT_DISABLE_LOGGING + log("unsupported version"); +#endif + // ignore errors from local_endpoint + if (m_version == version_pcp && !is_v6(m_socket.local_endpoint(ec))) + { + m_version = version_natpmp; + resend_request(m_currently_mapping); + send_get_ip_address_request(); + } + return; + } + + if ((version == version_natpmp && bytes_transferred < 12) + || (version == version_pcp && bytes_transferred < 24)) + { +#ifndef TORRENT_DISABLE_LOGGING + log("received packet of invalid size: %d", int(bytes_transferred)); +#endif + return; + } + + int lifetime = 0; + if (version == version_pcp) + { + lifetime = aux::numeric_cast(read_uint32(in)); + } + int const time = aux::numeric_cast(read_uint32(in)); + if (version == version_pcp) in += 12; // reserved + TORRENT_UNUSED(time); + + if (version == version_natpmp && cmd == 128) + { + // public IP request response + m_external_ip = read_v4_address(in); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("<== public IP address [ %s ]", print_address(m_external_ip).c_str()); + } +#endif + return; + + } + + if ((version == version_natpmp && bytes_transferred != 16) + || (version == version_pcp && bytes_transferred != 60)) + { +#ifndef TORRENT_DISABLE_LOGGING + log("received packet of invalid size: %d", int(bytes_transferred)); +#endif + return; + } + + std::array nonce; + portmap_protocol protocol = portmap_protocol::none; + if (version == version_pcp) + { + std::memcpy(nonce.data(), in, nonce.size()); + in += nonce.size(); + int p = read_uint8(in); + protocol = p == 6 ? portmap_protocol::tcp + : portmap_protocol::udp; + in += 3; // reserved + } + int const private_port = read_uint16(in); + int const public_port = read_uint16(in); + if (version == version_natpmp) + lifetime = aux::numeric_cast(read_uint32(in)); + address external_addr; + if (version == version_pcp) + { + external_addr = read_v6_address(in); + if (external_addr.to_v6().is_v4_mapped()) + external_addr = external_addr.to_v6().to_v4(); + } + + if (version == version_natpmp) + { + protocol = (cmd - 128 == 1) + ? portmap_protocol::udp + : portmap_protocol::tcp; + } + +#ifndef TORRENT_DISABLE_LOGGING + char msg[200]; + int const num_chars = std::snprintf(msg, sizeof(msg), "<== port map [" + " transport: %s protocol: %s local: %d external: %d ttl: %d ]" + , version_to_string(protocol_version(version)) + , (protocol == portmap_protocol::udp ? "udp" : "tcp") + , private_port, public_port, lifetime); +#endif + + mapping_t* m = nullptr; + port_mapping_t index{-1}; + for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) + { + if (private_port != i->local_port) continue; + if (protocol != i->protocol) continue; + if (!i->map_sent) continue; + if (!i->outstanding_request) continue; + if (version == version_pcp && nonce != i->nonce) continue; + m = &*i; + index = port_mapping_t(static_cast(i - m_mappings.begin())); + break; + } + + if (m == nullptr) + { +#ifndef TORRENT_DISABLE_LOGGING + snprintf(msg + num_chars, sizeof(msg) - aux::numeric_cast(num_chars), " not found in map table"); + log("%s", msg); +#endif + return; + } + m->outstanding_request = false; + +#ifndef TORRENT_DISABLE_LOGGING + log("%s", msg); +#endif + + if (public_port == 0 || lifetime == 0) + { + // this means the mapping was + // successfully closed + m->protocol = portmap_protocol::none; + } + else + { + m->expires = aux::time_now() + seconds(lifetime * 3 / 4); + m->external_port = public_port; + if (!external_addr.is_unspecified()) + m->external_address = external_addr; + } + + if (result != 0) + { + m->expires = aux::time_now() + hours(2); + portmap_protocol const proto = m->protocol; + m_callback.on_port_mapping(port_mapping_t{index}, address(), 0, proto + , from_result_code(version, result), portmap_transport::natpmp); + } + else if (m->act == portmap_action::add) + { + portmap_protocol const proto = m->protocol; + address const ext_ip = version == version_pcp ? m->external_address : m_external_ip; + m_callback.on_port_mapping(port_mapping_t{index}, ext_ip, m->external_port, proto + , errors::pcp_success, portmap_transport::natpmp); + } + + m_currently_mapping = port_mapping_t{-1}; + m->act = portmap_action::none; + m_send_timer.cancel(ec); + update_expiration_timer(); + try_next_mapping(index); +} + +void natpmp::update_expiration_timer() +{ + TORRENT_ASSERT(is_single_thread()); + if (m_abort) return; + + time_point const now = aux::time_now() + milliseconds(100); + time_point min_expire = now + seconds(3600); + port_mapping_t min_index{-1}; + for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == portmap_protocol::none + || i->act != portmap_action::none) continue; + port_mapping_t const index(static_cast(i - m_mappings.begin())); + if (i->expires < now) + { +#ifndef TORRENT_DISABLE_LOGGING + log("mapping %u expired", static_cast(index)); +#endif + i->act = portmap_action::add; + if (m_next_refresh == index) m_next_refresh = port_mapping_t{-1}; + update_mapping(index); + } + else if (i->expires < min_expire) + { + min_expire = i->expires; + min_index = index; + } + } + + // this is already the mapping we're waiting for + if (m_next_refresh == min_index) return; + + if (min_index >= port_mapping_t{}) + { +#ifndef TORRENT_DISABLE_LOGGING + log("next expiration [ idx: %d ttl: %" PRId64 " ]" + , static_cast(min_index), total_seconds(min_expire - aux::time_now())); +#endif + error_code ec; + if (m_next_refresh >= port_mapping_t{}) m_refresh_timer.cancel(ec); + + ADD_OUTSTANDING_ASYNC("natpmp::mapping_expired"); + m_refresh_timer.expires_from_now(min_expire - now, ec); + m_refresh_timer.async_wait(std::bind(&natpmp::mapping_expired, self(), _1, min_index)); + m_next_refresh = min_index; + } +} + +void natpmp::mapping_expired(error_code const& e, port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("natpmp::mapping_expired"); + if (e || m_abort) return; +#ifndef TORRENT_DISABLE_LOGGING + log("mapping %u expired", static_cast(i)); +#endif + m_mappings[i].act = portmap_action::add; + if (m_next_refresh == i) m_next_refresh = port_mapping_t{-1}; + update_mapping(i); +} + +void natpmp::close() +{ + TORRENT_ASSERT(is_single_thread()); + close_impl(); +} + +void natpmp::close_impl() +{ + TORRENT_ASSERT(is_single_thread()); + m_abort = true; +#ifndef TORRENT_DISABLE_LOGGING + log("closing"); +#endif + if (m_disabled) return; + for (auto& m : m_mappings) + { + if (m.protocol == portmap_protocol::none) continue; + m.act = portmap_action::del; + } + error_code ec; + m_refresh_timer.cancel(ec); + m_currently_mapping = port_mapping_t{-1}; + update_mapping(port_mapping_t{}); +} + +} // namespace libtorrent diff --git a/src/openssl.cpp b/src/openssl.cpp new file mode 100644 index 0000000..638931d --- /dev/null +++ b/src/openssl.cpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2020, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/openssl.hpp" +#include "libtorrent/settings_pack.hpp" + +namespace libtorrent { +namespace aux { + +#ifdef TORRENT_USE_OPENSSL + +// all of OpenSSL causes warnings, so we just have to disable them +#include "libtorrent/aux_/disable_warnings_push.hpp" + +void openssl_set_tlsext_hostname(SSL* s, char const* name) +{ +#if OPENSSL_VERSION_NUMBER >= 0x90812f + SSL_set_tlsext_host_name(s, name); +#endif +} + +#if OPENSSL_VERSION_NUMBER >= 0x90812f + +void openssl_set_tlsext_servername_callback(SSL_CTX* ctx + , int (*servername_callback)(SSL*, int*, void*)) +{ + SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback); +} + +void openssl_set_tlsext_servername_arg(SSL_CTX* ctx, void* userdata) +{ + SSL_CTX_set_tlsext_servername_arg(ctx, userdata); +} + +int openssl_num_general_names(GENERAL_NAMES* gens) +{ + return sk_GENERAL_NAME_num(gens); +} + +GENERAL_NAME* openssl_general_name_value(GENERAL_NAMES* gens, int i) +{ + return sk_GENERAL_NAME_value(gens, i); +} + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif // OPENSSL_VERSION_NUMBER + +#endif // TORRENT_USE_OPENSSL + +} +} diff --git a/src/packet_buffer.cpp b/src/packet_buffer.cpp new file mode 100644 index 0000000..b31d90a --- /dev/null +++ b/src/packet_buffer.cpp @@ -0,0 +1,192 @@ +/* + +Copyright (c) 2010-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/packet_buffer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/invariant_check.hpp" + +namespace libtorrent { + + bool compare_less_wrap(std::uint32_t lhs, std::uint32_t rhs + , std::uint32_t mask); + +#if TORRENT_USE_INVARIANT_CHECKS + void packet_buffer::check_invariant() const + { + int count = 0; + for (index_type i = 0; i < m_capacity; ++i) + { + count += m_storage[i] ? 1 : 0; + } + TORRENT_ASSERT(count == m_size); + } +#endif + + packet_ptr packet_buffer::insert(index_type idx, packet_ptr value) + { + INVARIANT_CHECK; + + TORRENT_ASSERT_VAL(idx <= 0xffff, idx); + // you're not allowed to insert NULLs! + TORRENT_ASSERT(value); + + if (!value) return remove(idx); + + if (m_size != 0) + { + if (compare_less_wrap(idx, m_first, 0xffff)) + { + // Index comes before m_first. If we have room, we can simply + // adjust m_first backward. + + std::uint32_t free_space = 0; + + for (index_type i = (m_first - 1) & (m_capacity - 1); + i != (m_first & (m_capacity - 1)); i = (i - 1) & (m_capacity - 1)) + { + if (m_storage[i & (m_capacity - 1)]) + break; + ++free_space; + } + + if (((m_first - idx) & 0xffff) > free_space) + reserve(((m_first - idx) & 0xffff) + m_capacity - free_space); + + m_first = idx; + } + else if (idx >= m_first + m_capacity) + { + reserve(idx - m_first + 1); + } + else if (idx < m_first) + { + // We have wrapped. + if (idx >= ((m_first + m_capacity) & 0xffff) && m_capacity < 0xffff) + { + reserve(m_capacity + (idx + 1 - ((m_first + m_capacity) & 0xffff))); + } + } + if (compare_less_wrap(m_last, (idx + 1) & 0xffff, 0xffff)) + m_last = (idx + 1) & 0xffff; + } + else + { + m_first = idx; + m_last = (idx + 1) & 0xffff; + } + + if (m_capacity == 0) reserve(16); + + packet_ptr old_value = std::move(m_storage[idx & (m_capacity - 1)]); + m_storage[idx & (m_capacity - 1)] = std::move(value); + + if (m_size == 0) m_first = idx; + // if we're just replacing an old value, the number + // of elements in the buffer doesn't actually increase + if (!old_value) ++m_size; + + TORRENT_ASSERT_VAL(m_first <= 0xffff, m_first); + return old_value; + } + + packet* packet_buffer::at(index_type idx) const + { + INVARIANT_CHECK; + if (idx >= m_first + m_capacity) + return nullptr; + + if (compare_less_wrap(idx, m_first, 0xffff)) + return nullptr; + + std::size_t const mask = m_capacity - 1; + return m_storage[idx & mask].get(); + } + + void packet_buffer::reserve(std::uint32_t size) + { + INVARIANT_CHECK; + TORRENT_ASSERT_VAL(size <= 0xffff, size); + std::uint32_t new_size = m_capacity == 0 ? 16 : m_capacity; + + while (new_size < size) + new_size <<= 1; + + aux::unique_ptr new_storage(new packet_ptr[new_size]); + + for (index_type i = m_first; i < (m_first + m_capacity); ++i) + new_storage[i & (new_size - 1)] = std::move(m_storage[i & (m_capacity - 1)]); + + m_storage = std::move(new_storage); + m_capacity = new_size; + } + + packet_ptr packet_buffer::remove(index_type idx) + { + INVARIANT_CHECK; + // TODO: use compare_less_wrap for this comparison as well + if (idx >= m_first + m_capacity) + return packet_ptr(); + + if (compare_less_wrap(idx, m_first, 0xffff)) + return packet_ptr(); + + std::size_t const mask = m_capacity - 1; + packet_ptr old_value = std::move(m_storage[idx & mask]); + m_storage[idx & mask].reset(); + + if (old_value) + { + --m_size; + if (m_size == 0) m_last = m_first; + } + + if (idx == m_first && m_size != 0) + { + ++m_first; + for (index_type i = 0; i < m_capacity; ++i, ++m_first) + if (m_storage[m_first & mask]) break; + m_first &= 0xffff; + } + + if (((idx + 1) & 0xffff) == m_last && m_size != 0) + { + --m_last; + for (index_type i = 0; i < m_capacity; ++i, --m_last) + if (m_storage[m_last & mask]) break; + ++m_last; + m_last &= 0xffff; + } + + TORRENT_ASSERT_VAL(m_first <= 0xffff, m_first); + return old_value; + } +} diff --git a/src/parse_url.cpp b/src/parse_url.cpp new file mode 100644 index 0000000..7efd77d --- /dev/null +++ b/src/parse_url.cpp @@ -0,0 +1,165 @@ +/* + +Copyright (c) 2008-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/parse_url.hpp" +#include "libtorrent/string_util.hpp" + +namespace libtorrent { + + // returns protocol, auth, hostname, port, path + std::tuple + parse_url_components(std::string url, error_code& ec) + { + std::string hostname; // hostname only + std::string auth; // user:pass + std::string protocol; // http or https for instance + int port = -1; + + std::string::iterator at; + std::string::iterator colon; + std::string::iterator port_pos; + + // PARSE URL + auto start = url.begin(); + // remove white spaces in front of the url + while (start != url.end() && is_space(*start)) + ++start; + auto end = std::find(url.begin(), url.end(), ':'); + protocol.assign(start, end); + + if (end == url.end()) + { + ec = errors::unsupported_url_protocol; + goto exit; + } + ++end; + if (end == url.end() || *end != '/') + { + ec = errors::unsupported_url_protocol; + goto exit; + } + ++end; + if (end == url.end() || *end != '/') + { + ec = errors::unsupported_url_protocol; + goto exit; + } + ++end; + start = end; + + at = std::find(start, url.end(), '@'); + colon = std::find(start, url.end(), ':'); + end = std::find(start, url.end(), '/'); + + if (at != url.end() + && colon != url.end() + && colon < at + && at < end) + { + auth.assign(start, at); + start = at; + ++start; + } + + // this is for IPv6 addresses + if (start != url.end() && *start == '[') + { + port_pos = std::find(start, url.end(), ']'); + if (port_pos == url.end()) + { + ec = errors::expected_close_bracket_in_address; + goto exit; + } + // strip the brackets + hostname.assign(start + 1, port_pos); + port_pos = std::find(port_pos, url.end(), ':'); + } + else + { + port_pos = std::find(start, url.end(), ':'); + if (port_pos < end) hostname.assign(start, port_pos); + else hostname.assign(start, end); + } + + if (port_pos < end) + { + ++port_pos; + for (auto i = port_pos; i < end; ++i) + { + if (is_digit(*i)) continue; + ec = errors::invalid_port; + goto exit; + } + port = std::atoi(std::string(port_pos, end).c_str()); + } + + start = end; +exit: + return std::make_tuple(std::move(protocol) + , std::move(auth) + , std::move(hostname) + , port + , std::string(start, url.end())); + } + + // splits a url into the base url and the path + std::tuple + split_url(std::string url, error_code& ec) + { + std::string base; + std::string path; + + // PARSE URL + auto pos = std::find(url.begin(), url.end(), ':'); + + if (pos == url.end() || url.end() - pos < 3 + || *(pos + 1) != '/' || *(pos + 2) != '/') + { + ec = errors::unsupported_url_protocol; + return std::make_tuple(std::move(url), std::move(path)); + } + pos += 3; // skip "://" + + pos = std::find(pos, url.end(), '/'); + if (pos == url.end()) + { + return std::make_tuple(std::move(url), std::move(path)); + } + + base.assign(url.begin(), pos); + path.assign(pos, url.end()); + return std::make_tuple(std::move(base), std::move(path)); + } + +} diff --git a/src/part_file.cpp b/src/part_file.cpp new file mode 100644 index 0000000..a6ae3c8 --- /dev/null +++ b/src/part_file.cpp @@ -0,0 +1,391 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +/* + + The part_file file format is an array of piece sized blocks with + a simple header. For a given number of pieces, the header has a + fixed size. The header size is rounded up to an even multiple of + 1024, in an attempt at improving disk I/O performance by aligning + reads and writes to clusters on the drive. This is the file header + format. All values are stored big endian on disk. + + + // the size of the torrent (and can be used to calculate the size + // of the file header) + uint32_t num_pieces; + + // the number of bytes in each piece. This determines the size of + // each slot in the part file. This is typically an even power of 2, + // but it is not guaranteed to be. + uint32_t piece_size; + + // this is an array specifying which slots a particular piece resides in, + // A value of 0xffffffff (-1 if you will) means the piece is not in the part_file + // Any other value means the piece resides in the slot with that index + uint32_t piece[num_pieces]; + + // unused, n is defined as the number to align the size of this + // header to an even multiple of 1024 bytes. + uint8_t padding[n]; + +*/ + +#include "libtorrent/part_file.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" + +#include // for std::function +#include + +namespace { + + // round up to even kilobyte + int round_up(int n) + { return (n + 1023) & ~0x3ff; } +} + +namespace libtorrent { + + part_file::part_file(std::string const& path, std::string const& name + , int const num_pieces, int const piece_size) + : m_path(path) + , m_name(name) + , m_max_pieces(num_pieces) + , m_piece_size(piece_size) + , m_header_size(round_up((2 + num_pieces) * 4)) + { + TORRENT_ASSERT(num_pieces > 0); + TORRENT_ASSERT(m_piece_size > 0); + + error_code ec; + auto f = open_file(open_mode::read_only, ec); + if (ec) return; + + // parse header + std::vector header(static_cast(m_header_size)); + iovec_t b = header; + int n = int(f.readv(0, b, ec)); + if (ec) return; + + // we don't have a full header. consider the file empty + if (n < m_header_size) return; + using namespace libtorrent::detail; + + char* ptr = header.data(); + // we have a header. Parse it + int const num_pieces_ = int(read_uint32(ptr)); + int const piece_size_ = int(read_uint32(ptr)); + + // if there is a mismatch in number of pieces or piece size + // consider the file empty and overwrite anything in there + if (num_pieces != num_pieces_ || m_piece_size != piece_size_) return; + + // this is used to determine which slots are free, and how many + // slots are allocated + aux::vector free_slots; + free_slots.resize(num_pieces, true); + + for (piece_index_t i = piece_index_t(0); i < piece_index_t(num_pieces); ++i) + { + slot_index_t const slot(read_int32(ptr)); + if (static_cast(slot) < 0) continue; + + // invalid part-file + TORRENT_ASSERT(slot < slot_index_t(num_pieces)); + if (slot >= slot_index_t(num_pieces)) continue; + + if (slot >= m_num_allocated) + m_num_allocated = next(slot); + + free_slots[slot] = false; + m_piece_map[i] = slot; + } + + // now, populate the free_list with the "holes" + for (slot_index_t i(0); i < m_num_allocated; ++i) + { + if (free_slots[i]) m_free_slots.push_back(i); + } + } + + part_file::~part_file() + { + error_code ec; + flush_metadata_impl(ec); + } + + slot_index_t part_file::allocate_slot(piece_index_t const piece) + { + // the mutex is assumed to be held here, since this is a private function + + TORRENT_ASSERT(m_piece_map.find(piece) == m_piece_map.end()); + slot_index_t slot(-1); + if (!m_free_slots.empty()) + { + slot = m_free_slots.front(); + m_free_slots.erase(m_free_slots.begin()); + } + else + { + slot = m_num_allocated; + ++m_num_allocated; + } + + m_piece_map[piece] = slot; + m_dirty_metadata = true; + return slot; + } + + int part_file::writev(span bufs, piece_index_t const piece + , int const offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + std::unique_lock l(m_mutex); + + auto f = open_file(open_mode::read_write | open_mode::attribute_hidden, ec); + if (ec) return -1; + + auto const i = m_piece_map.find(piece); + slot_index_t const slot = (i == m_piece_map.end()) + ? allocate_slot(piece) : i->second; + + l.unlock(); + + return int(f.writev(slot_offset(slot) + offset, bufs, ec)); + } + + int part_file::readv(span bufs + , piece_index_t const piece, int offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + std::unique_lock l(m_mutex); + + auto const i = m_piece_map.find(piece); + if (i == m_piece_map.end()) + { + ec = make_error_code(boost::system::errc::no_such_file_or_directory); + return -1; + } + + slot_index_t const slot = i->second; + l.unlock(); + + auto f = open_file(open_mode::read_only | open_mode::attribute_hidden, ec); + if (ec) return -1; + + return int(f.readv(slot_offset(slot) + offset, bufs, ec)); + } + + file part_file::open_file(open_mode_t const mode, error_code& ec) + { + std::string const fn = combine_path(m_path, m_name); + file f(fn, mode, ec); + if (((mode & open_mode::rw_mask) != open_mode::read_only) + && ec == boost::system::errc::no_such_file_or_directory) + { + // this means the directory the file is in doesn't exist. + // so create it + ec.clear(); + create_directories(m_path, ec); + + if (ec) return {}; + f = file(fn, mode, ec); + } + if (ec) return {}; + return f; + } + + void part_file::free_piece(piece_index_t const piece) + { + std::lock_guard l(m_mutex); + + auto const i = m_piece_map.find(piece); + if (i == m_piece_map.end()) return; + + // TODO: what do we do if someone is currently reading from the disk + // from this piece? does it matter? Since we won't actively erase the + // data from disk, but it may be overwritten soon, it's probably not that + // big of a deal + + m_free_slots.push_back(i->second); + m_piece_map.erase(i); + m_dirty_metadata = true; + } + + void part_file::move_partfile(std::string const& path, error_code& ec) + { + std::lock_guard l(m_mutex); + + flush_metadata_impl(ec); + if (ec) return; + + if (!m_piece_map.empty()) + { + std::string old_path = combine_path(m_path, m_name); + std::string new_path = combine_path(path, m_name); + + rename(old_path, new_path, ec); + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + + if (ec) + { + copy_file(old_path, new_path, ec); + if (ec) return; + remove(old_path, ec); + } + } + m_path = path; + } + + void part_file::export_file(std::function)> f + , std::int64_t const offset, std::int64_t size, error_code& ec) + { + std::unique_lock l(m_mutex); + + // there's nothing stored in the part_file. Nothing to do + if (m_piece_map.empty()) return; + + piece_index_t piece(int(offset / m_piece_size)); + piece_index_t const end = piece_index_t(int(((offset + size) + m_piece_size - 1) / m_piece_size)); + + std::unique_ptr buf; + + std::int64_t piece_offset = offset - std::int64_t(static_cast(piece)) + * m_piece_size; + std::int64_t file_offset = 0; + auto file = open_file(open_mode::read_only, ec); + if (ec) return; + + for (; piece < end; ++piece) + { + auto const i = m_piece_map.find(piece); + int const block_to_copy = int(std::min(m_piece_size - piece_offset, size)); + if (i != m_piece_map.end()) + { + slot_index_t const slot = i->second; + + if (!buf) buf.reset(new char[std::size_t(m_piece_size)]); + + // don't hold the lock during disk I/O + l.unlock(); + + iovec_t v = {buf.get(), block_to_copy}; + auto bytes_read = file.readv(slot_offset(slot) + piece_offset, v, ec); + v = v.first(static_cast(bytes_read)); + TORRENT_ASSERT(!ec); + if (ec || v.empty()) return; + + f(file_offset, {buf.get(), block_to_copy}); + + // we're done with the disk I/O, grab the lock again to update + // the slot map + l.lock(); + + if (block_to_copy == m_piece_size) + { + // since we released the lock, it's technically possible that + // another thread removed this slot map entry, and invalidated + // our iterator. Now that we hold the lock again, perform + // another lookup to be sure. + auto const j = m_piece_map.find(piece); + if (j != m_piece_map.end()) + { + // if the slot moved, that's really suspicious + TORRENT_ASSERT(j->second == slot); + m_free_slots.push_back(j->second); + m_piece_map.erase(j); + m_dirty_metadata = true; + } + } + } + file_offset += block_to_copy; + piece_offset = 0; + size -= block_to_copy; + } + } + + void part_file::flush_metadata(error_code& ec) + { + std::lock_guard l(m_mutex); + + flush_metadata_impl(ec); + } + + // TODO: instead of rebuilding the whole file header + // and flushing it, update the slot entries as we go + void part_file::flush_metadata_impl(error_code& ec) + { + // do we need to flush the metadata? + if (m_dirty_metadata == false) return; + + if (m_piece_map.empty()) + { + // if we don't have any pieces left in the + // part file, remove it + std::string const p = combine_path(m_path, m_name); + remove(p, ec); + + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + return; + } + + auto f = open_file(open_mode::read_write | open_mode::attribute_hidden, ec); + if (ec) return; + + std::vector header(static_cast(m_header_size)); + + using namespace libtorrent::detail; + + char* ptr = header.data(); + write_uint32(m_max_pieces, ptr); + write_uint32(m_piece_size, ptr); + + for (piece_index_t piece(0); piece < piece_index_t(m_max_pieces); ++piece) + { + auto const i = m_piece_map.find(piece); + slot_index_t const slot(i == m_piece_map.end() + ? slot_index_t(-1) : i->second); + write_int32(static_cast(slot), ptr); + } + std::memset(ptr, 0, std::size_t(m_header_size - (ptr - header.data()))); + iovec_t b = header; + f.writev(0, b, ec); + if (ec) return; + m_dirty_metadata = false; + } +} diff --git a/src/path.cpp b/src/path.cpp new file mode 100644 index 0000000..1a51bef --- /dev/null +++ b/src/path.cpp @@ -0,0 +1,955 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-macros" +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wunused-macros" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#endif + +// these defines are just in case the system we're on needs them for 64 bit file +// support +#define _FILE_OFFSET_BITS 64 +#define _LARGE_FILES 1 + +// on mingw this is necessary to enable 64-bit time_t, specifically used for +// the stat struct. Without this, modification times returned by stat may be +// incorrect and consistently fail resume data +#ifndef __MINGW_USE_VC2005_COMPAT +# define __MINGW_USE_VC2005_COMPAT +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/file.hpp" // for directory +#include "libtorrent/string_util.hpp" +#include + +#include "libtorrent/aux_/escape_string.hpp" // for convert_to_native +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/throw.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include // for IOV_MAX + +#ifdef TORRENT_WINDOWS +// windows part + +#include "libtorrent/utf8.hpp" +#include "libtorrent/aux_/win_util.hpp" + +#include "libtorrent/aux_/windows.hpp" +#include +#ifndef TORRENT_MINGW +#include // for _getcwd, _mkdir +#else +#include +#endif +#include +#else +// posix part + +#include +#include +#include +#include + +#ifdef TORRENT_LINUX +// linux specifics + +#include + +#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 +// mac specifics + +#include + +#endif + +#endif // posix part + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + int bufs_size(span bufs) + { + std::ptrdiff_t size = 0; + for (auto buf : bufs) size += buf.size(); + return int(size); + } + +#if defined TORRENT_WINDOWS + std::string convert_from_native_path(wchar_t const* s) + { + if (s[0] == L'\\' && s[1] == L'\\' && s[2] == L'?' && s[3] == L'\\') s += 4; + return convert_from_wstring(s); + } +#else + std::string convert_from_native_path(char const* s) { return convert_from_native(s); } +#endif + +namespace { + struct free_function + { + void operator()(void* ptr) const noexcept { std::free(ptr); } + }; + + template + std::unique_ptr make_free_holder(T* ptr) + { + return std::unique_ptr(ptr, free_function{}); + } + +#ifdef TORRENT_WINDOWS + time_t file_time_to_posix(FILETIME f) + { + const std::uint64_t posix_time_offset = 11644473600LL; + std::uint64_t ft = (std::uint64_t(f.dwHighDateTime) << 32) + | f.dwLowDateTime; + + // windows filetime is specified in 100 nanoseconds resolution. + // convert to seconds + return time_t(ft / 10000000 - posix_time_offset); + } +#endif +} // anonymous namespace + + native_path_string convert_to_native_path_string(std::string const& path) + { +#if TORRENT_USE_UNC_PATHS + // UNC paths must be absolute + // network paths are already UNC paths + std::string prepared_path = complete(path); + if (prepared_path.substr(0,2) != "\\\\") + prepared_path = "\\\\?\\" + prepared_path; + std::replace(prepared_path.begin(), prepared_path.end(), '/', '\\'); + + return convert_to_wstring(prepared_path); +#else // TORRENT_WINDOWS + return convert_to_native(path); +#endif + } + + void stat_file(std::string const& inf, file_status* s + , error_code& ec, int const flags) + { + ec.clear(); + native_path_string f = convert_to_native_path_string(inf); +#ifdef TORRENT_WINDOWS + + TORRENT_UNUSED(flags); + + // in order to open a directory, we need the FILE_FLAG_BACKUP_SEMANTICS + HANDLE h = CreateFileW(f.c_str(), 0, FILE_SHARE_DELETE | FILE_SHARE_READ + | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (h == INVALID_HANDLE_VALUE) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + return; + } + + BY_HANDLE_FILE_INFORMATION data; + if (!GetFileInformationByHandle(h, &data)) + { + ec.assign(GetLastError(), system_category()); + TORRENT_ASSERT(ec); + CloseHandle(h); + return; + } + + s->file_size = (std::uint64_t(data.nFileSizeHigh) << 32) | data.nFileSizeLow; + s->ctime = file_time_to_posix(data.ftCreationTime); + s->atime = file_time_to_posix(data.ftLastAccessTime); + s->mtime = file_time_to_posix(data.ftLastWriteTime); + + s->mode = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ? file_status::directory + : (data.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) + ? file_status::character_special : file_status::regular_file; + CloseHandle(h); +#else + + // posix version + + struct ::stat ret{}; + int retval; + if (flags & dont_follow_links) + retval = ::lstat(f.c_str(), &ret); + else + retval = ::stat(f.c_str(), &ret); + if (retval < 0) + { + ec.assign(errno, system_category()); + return; + } + + s->file_size = ret.st_size; + s->atime = std::uint64_t(ret.st_atime); + s->mtime = std::uint64_t(ret.st_mtime); + s->ctime = std::uint64_t(ret.st_ctime); + + s->mode = (S_ISREG(ret.st_mode) ? file_status::regular_file : 0) + | (S_ISDIR(ret.st_mode) ? file_status::directory : 0) + | (S_ISLNK(ret.st_mode) ? file_status::link : 0) + | (S_ISFIFO(ret.st_mode) ? file_status::fifo : 0) + | (S_ISCHR(ret.st_mode) ? file_status::character_special : 0) + | (S_ISBLK(ret.st_mode) ? file_status::block_special : 0) + | (S_ISSOCK(ret.st_mode) ? file_status::socket : 0); + +#endif // TORRENT_WINDOWS + } + + void rename(std::string const& inf, std::string const& newf, error_code& ec) + { + ec.clear(); + + native_path_string f1 = convert_to_native_path_string(inf); + native_path_string f2 = convert_to_native_path_string(newf); + +#if defined TORRENT_WINDOWS +#define RenameFunction_ ::_wrename +#else +#define RenameFunction_ ::rename +#endif + + if (RenameFunction_(f1.c_str(), f2.c_str()) < 0) + { + ec.assign(errno, generic_category()); + } +#undef RenameFunction_ + } + + void create_directories(std::string const& f, error_code& ec) + { + ec.clear(); + if (is_directory(f, ec)) return; + if (ec != boost::system::errc::no_such_file_or_directory) + return; + ec.clear(); + if (is_root_path(f)) + { + // this is just to set ec correctly, in case this root path isn't + // mounted + file_status s; + stat_file(f, &s, ec); + return; + } + if (has_parent_path(f)) + { + create_directories(parent_path(f), ec); + if (ec) return; + } + create_directory(f, ec); + } + + void create_directory(std::string const& f, error_code& ec) + { + ec.clear(); + + native_path_string n = convert_to_native_path_string(f); +#ifdef TORRENT_WINDOWS + if (CreateDirectoryW(n.c_str(), 0) == 0 + && GetLastError() != ERROR_ALREADY_EXISTS) + ec.assign(GetLastError(), system_category()); +#else + int ret = ::mkdir(n.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); + if (ret < 0 && errno != EEXIST) + ec.assign(errno, system_category()); +#endif + } + + void hard_link(std::string const& file, std::string const& link + , error_code& ec) + { + native_path_string n_exist = convert_to_native_path_string(file); + native_path_string n_link = convert_to_native_path_string(link); +#ifdef TORRENT_WINDOWS + + BOOL ret = CreateHardLinkW(n_link.c_str(), n_exist.c_str(), nullptr); + if (ret) + { + ec.clear(); + return; + } + // something failed. Does the filesystem not support hard links? + DWORD const error = GetLastError(); + if (error != ERROR_INVALID_FUNCTION) + { + // it's possible CreateHardLink will copy the file internally too, + // if the filesystem does not support it. + ec.assign(GetLastError(), system_category()); + return; + } + + // fall back to making a copy +#else + // assume posix's link() function exists + int ret = ::link(n_exist.c_str(), n_link.c_str()); + + if (ret == 0) + { + ec.clear(); + return; + } + + // most errors are passed through, except for the ones that indicate that + // hard links are not supported and require a copy. + // TODO: 2 test this on a FAT volume to see what error we get! + if (errno != EMLINK && errno != EXDEV) + { + // some error happened, report up to the caller + ec.assign(errno, system_category()); + return; + } + + // fall back to making a copy + +#endif + + // if we get here, we should copy the file + copy_file(file, link, ec); + } + + bool is_directory(std::string const& f, error_code& ec) + { + ec.clear(); + error_code e; + file_status s; + stat_file(f, &s, e); + if (!e && s.mode & file_status::directory) return true; + ec = e; + return false; + } + + void recursive_copy(std::string const& old_path, std::string const& new_path, error_code& ec) + { + TORRENT_ASSERT(!ec); + if (is_directory(old_path, ec)) + { + create_directory(new_path, ec); + if (ec) return; + for (directory i(old_path, ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + if (f == ".." || f == ".") continue; + recursive_copy(combine_path(old_path, f), combine_path(new_path, f), ec); + if (ec) return; + } + } + else if (!ec) + { + copy_file(old_path, new_path, ec); + } + } + + void copy_file(std::string const& inf, std::string const& newf, error_code& ec) + { + ec.clear(); + native_path_string f1 = convert_to_native_path_string(inf); + native_path_string f2 = convert_to_native_path_string(newf); + +#ifdef TORRENT_WINDOWS + + if (CopyFileW(f1.c_str(), f2.c_str(), false) == 0) + ec.assign(GetLastError(), system_category()); + +#elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + // this only works on 10.5 + copyfile_state_t state = copyfile_state_alloc(); + if (copyfile(f1.c_str(), f2.c_str(), state, COPYFILE_ALL) < 0) + ec.assign(errno, system_category()); + copyfile_state_free(state); +#else + int const infd = ::open(f1.c_str(), O_RDONLY); + if (infd < 0) + { + ec.assign(errno, system_category()); + return; + } + + // rely on default umask to filter x and w permissions + // for group and others + int const permissions = S_IRUSR | S_IWUSR + | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH; + + int const outfd = ::open(f2.c_str(), O_WRONLY | O_CREAT, permissions); + if (outfd < 0) + { + close(infd); + ec.assign(errno, system_category()); + return; + } + char buffer[4096]; + for (;;) + { + int const num_read = int(read(infd, buffer, sizeof(buffer))); + if (num_read == 0) break; + if (num_read < 0) + { + ec.assign(errno, system_category()); + break; + } + int const num_written = int(write(outfd, buffer, std::size_t(num_read))); + if (num_written < num_read) + { + ec.assign(errno, system_category()); + break; + } + if (num_read < int(sizeof(buffer))) break; + } + close(infd); + close(outfd); +#endif // TORRENT_WINDOWS + } + + void move_file(std::string const& inf, std::string const& newf, error_code& ec) + { + ec.clear(); + + file_status s; + stat_file(inf, &s, ec); + if (ec) return; + + if (has_parent_path(newf)) + { + create_directories(parent_path(newf), ec); + if (ec) return; + } + + rename(inf, newf, ec); + } + + std::string extension(std::string const& f) + { + for (int i = int(f.size()) - 1; i >= 0; --i) + { + std::size_t const idx = std::size_t(i); + if (f[idx] == '/') break; +#ifdef TORRENT_WINDOWS + if (f[idx] == '\\') break; +#endif + if (f[idx] != '.') continue; + return f.substr(idx); + } + return ""; + } + + std::string remove_extension(std::string const& f) + { + char const* slash = std::strrchr(f.c_str(), '/'); +#ifdef TORRENT_WINDOWS + slash = std::max((char const*)std::strrchr(f.c_str(), '\\'), slash); +#endif + char const* ext = std::strrchr(f.c_str(), '.'); + // if we don't have an extension, just return f + if (ext == nullptr || ext == &f[0] || (slash != nullptr && ext < slash)) return f; + return f.substr(0, aux::numeric_cast(ext - &f[0])); + } + + bool is_root_path(std::string const& f) + { + if (f.empty()) return false; + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + // match \\ form + if (f == "\\\\") return true; + int i = 0; + // match the xx:\ or xx:/ form + while (f[i] && is_alpha(f[i])) ++i; + if (i == int(f.size()-2) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) + return true; + // match network paths \\computer_name\ form + if (f.size() > 2 && f[0] == '\\' && f[1] == '\\') + { + // we don't care about the last character, since it's OK for it + // to be a slash or a back slash + bool found = false; + for (int j = 2; j < int(f.size()) - 1; ++j) + { + if (f[j] != '\\' && f[j] != '/') continue; + // there is a directory separator in here, + // i.e. this is not the root + found = true; + break; + } + if (!found) return true; + } +#else + // as well as parent_path("/") should be "/". + if (f == "/") return true; +#endif + return false; + } + + bool compare_path(std::string const& lhs, std::string const& rhs) + { + std::string::size_type const lhs_size = !lhs.empty() + && (lhs[lhs.size()-1] == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || lhs[lhs.size()-1] == '\\' +#endif + ) ? lhs.size() - 1 : lhs.size(); + + std::string::size_type const rhs_size = !rhs.empty() + && (rhs[rhs.size()-1] == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || rhs[rhs.size()-1] == '\\' +#endif + ) ? rhs.size() - 1 : rhs.size(); + return lhs.compare(0, lhs_size, rhs, 0, rhs_size) == 0; + } + + bool has_parent_path(std::string const& f) + { + if (f.empty()) return false; + if (is_root_path(f)) return false; + + int len = int(f.size()) - 1; + // if the last character is / or \ ignore it + if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') --len; + while (len >= 0) + { + if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') + break; + --len; + } + + return len >= 0; + } + + std::string parent_path(std::string const& f) + { + if (f.empty()) return f; + +#ifdef TORRENT_WINDOWS + if (f == "\\\\") return ""; +#endif + if (f == "/") return ""; + + int len = int(f.size()); + // if the last character is / or \ ignore it + if (f[std::size_t(len - 1)] == '/' || f[std::size_t(len - 1)] == '\\') --len; + while (len > 0) + { + --len; + if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') + break; + } + + if (f[std::size_t(len)] == '/' || f[std::size_t(len)] == '\\') ++len; + return std::string(f.c_str(), std::size_t(len)); + } + + std::string filename(std::string const& f) + { + if (f.empty()) return ""; + char const* first = f.c_str(); + char const* sep = std::strrchr(first, '/'); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + char const* altsep = std::strrchr(first, '\\'); + if (sep == 0 || altsep > sep) sep = altsep; +#endif + if (sep == nullptr) return f; + + if (sep - first == int(f.size()) - 1) + { + // if the last character is a / (or \) + // ignore it + int len = 0; + while (sep > first) + { + --sep; + if (*sep == '/' +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || *sep == '\\' +#endif + ) + return std::string(sep + 1, std::size_t(len)); + ++len; + } + return std::string(first, std::size_t(len)); + + } + return std::string(sep + 1); + } + + void append_path(std::string& branch, string_view leaf) + { + TORRENT_ASSERT(!is_complete(leaf)); + if (branch.empty() || branch == ".") + { + branch.assign(leaf.data(), leaf.size()); + return; + } + if (leaf.empty()) return; + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR_CHAR '\\' + bool const need_sep = branch[branch.size()-1] != '\\' + && branch[branch.size()-1] != '/'; +#else +#define TORRENT_SEPARATOR_CHAR '/' + bool const need_sep = branch[branch.size()-1] != '/'; +#endif + + if (need_sep) branch += TORRENT_SEPARATOR_CHAR; + branch.append(leaf.data(), leaf.size()); + } + + std::string combine_path(string_view lhs, string_view rhs) + { + TORRENT_ASSERT(!is_complete(rhs)); + if (lhs.empty() || lhs == ".") return rhs.to_string(); + if (rhs.empty() || rhs == ".") return lhs.to_string(); + +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) +#define TORRENT_SEPARATOR "\\" + bool const need_sep = lhs[lhs.size() - 1] != '\\' && lhs[lhs.size() - 1] != '/'; +#else +#define TORRENT_SEPARATOR "/" + bool const need_sep = lhs[lhs.size() - 1] != '/'; +#endif + std::string ret; + std::size_t target_size = lhs.size() + rhs.size() + 2; + ret.resize(target_size); + target_size = aux::numeric_cast(std::snprintf(&ret[0], target_size, "%*s%s%*s" + , int(lhs.size()), lhs.data() + , (need_sep ? TORRENT_SEPARATOR : "") + , int(rhs.size()), rhs.data())); + ret.resize(target_size); + return ret; + } + + std::string lexically_relative(string_view base, string_view target) + { + // first, strip trailing directory separators + if (!base.empty() && base.back() == TORRENT_SEPARATOR_CHAR) + base.remove_suffix(1); + if (!target.empty() && target.back() == TORRENT_SEPARATOR_CHAR) + target.remove_suffix(1); + + // strip common path elements + for (;;) + { + if (base.empty()) break; + string_view prev_base = base; + string_view prev_target = target; + + string_view base_element; + string_view target_element; + std::tie(base_element, base) = split_string(base, TORRENT_SEPARATOR_CHAR); + std::tie(target_element, target) = split_string(target, TORRENT_SEPARATOR_CHAR); + if (base_element == target_element) continue; + + base = prev_base; + target = prev_target; + break; + } + + // count number of path elements left in base, and prepend that number of + // "../" to target + + // base alwaus points to a directory. There's an implied directory + // separator at the end of it + int const num_steps = static_cast(std::count( + base.begin(), base.end(), TORRENT_SEPARATOR_CHAR)) + (base.empty() ? 0 : 1); + std::string ret; + for (int i = 0; i < num_steps; ++i) + ret += ".." TORRENT_SEPARATOR; + + ret += target.to_string(); + return ret; + } + + std::string current_working_directory() + { +#if defined TORRENT_WINDOWS +#define GetCurrentDir_ ::_wgetcwd +#else +#define GetCurrentDir_ ::getcwd +#endif + auto cwd = GetCurrentDir_(nullptr, 0); + if (cwd == nullptr) + aux::throw_ex(error_code(errno, generic_category())); + auto holder = make_free_holder(cwd); + return convert_from_native_path(cwd); +#undef GetCurrentDir_ + } + +#if TORRENT_USE_UNC_PATHS + std::string canonicalize_path(string_view f) + { + std::string ret; + ret.resize(f.size()); + char* write_cur = &ret[0]; + char* last_write_sep = write_cur; + + char const* read_cur = f.data(); + char const* last_read_sep = read_cur; + + // the last_*_sep pointers point to one past + // the last path separator encountered and is + // initialized to the first character in the path + for (int i = 0; i < int(f.size()); ++i) + { + if (*read_cur != '\\') + { + *write_cur++ = *read_cur++; + continue; + } + int element_len = int(read_cur - last_read_sep); + if (element_len == 1 && std::memcmp(last_read_sep, ".", 1) == 0) + { + --write_cur; + ++read_cur; + last_read_sep = read_cur; + continue; + } + if (element_len == 2 && std::memcmp(last_read_sep, "..", 2) == 0) + { + // find the previous path separator + if (last_write_sep > &ret[0]) + { + --last_write_sep; + while (last_write_sep > &ret[0] + && last_write_sep[-1] != '\\') + --last_write_sep; + } + write_cur = last_write_sep; + // find the previous path separator + if (last_write_sep > &ret[0]) + { + --last_write_sep; + while (last_write_sep > &ret[0] + && last_write_sep[-1] != '\\') + --last_write_sep; + } + ++read_cur; + last_read_sep = read_cur; + continue; + } + *write_cur++ = *read_cur++; + last_write_sep = write_cur; + last_read_sep = read_cur; + } + // terminate destination string + *write_cur = 0; + ret.resize(write_cur - &ret[0]); + return ret; + } +#endif + + std::int64_t file_size(std::string const& f) + { + error_code ec; + file_status s; + stat_file(f, &s, ec); + if (ec) return 0; + return s.file_size; + } + + bool exists(std::string const& f, error_code& ec) + { + file_status s; + stat_file(f, &s, ec); + if (ec) + { + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + return false; + } + return true; + } + + bool exists(std::string const& f) + { + error_code ec; + return exists(f, ec); + } + + void remove(std::string const& inf, error_code& ec) + { + ec.clear(); + native_path_string f = convert_to_native_path_string(inf); + +#ifdef TORRENT_WINDOWS + // windows does not allow trailing / or \ in + // the path when removing files + while (!f.empty() && ( + f.back() == '/' || + f.back() == '\\' + )) f.pop_back(); + + if (DeleteFileW(f.c_str()) == 0) + { + if (GetLastError() == ERROR_ACCESS_DENIED) + { + if (RemoveDirectoryW(f.c_str()) != 0) + return; + } + ec.assign(GetLastError(), system_category()); + return; + } +#else // TORRENT_WINDOWS + if (::remove(f.c_str()) < 0) + { + ec.assign(errno, system_category()); + return; + } +#endif // TORRENT_WINDOWS + } + + void remove_all(std::string const& f, error_code& ec) + { + ec.clear(); + + file_status s; + stat_file(f, &s, ec); + if (ec) return; + + if (s.mode & file_status::directory) + { + for (directory i(f, ec); !i.done(); i.next(ec)) + { + if (ec) return; + std::string p = i.file(); + if (p == "." || p == "..") continue; + remove_all(combine_path(f, p), ec); + if (ec) return; + } + } + remove(f, ec); + } + + std::pair rsplit_path(string_view p) + { + if (p.empty()) return {{}, {}}; + if (p.back() == TORRENT_SEPARATOR_CHAR) p.remove_suffix(1); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + else if (p.back() == '/') p.remove_suffix(1); +#endif +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + auto const sep = p.find_last_of("/\\"); +#else + auto const sep = p.find_last_of(TORRENT_SEPARATOR_CHAR); +#endif + if (sep == string_view::npos) return {{}, p}; + return { p.substr(0, sep), p.substr(sep + 1) }; + } + + std::pair lsplit_path(string_view p) + { + if (p.empty()) return {{}, {}}; + // for absolute paths, skip the initial "/" + if (p.front() == TORRENT_SEPARATOR_CHAR) p.remove_prefix(1); +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + else if (p.front() == '/') p.remove_prefix(1); +#endif +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + auto const sep = p.find_first_of("/\\"); +#else + auto const sep = p.find_first_of(TORRENT_SEPARATOR_CHAR); +#endif + if (sep == string_view::npos) return {p, {}}; + return { p.substr(0, sep), p.substr(sep + 1) }; + } + + std::pair lsplit_path(string_view p, std::size_t pos) + { + if (p.empty()) return {{}, {}}; + // for absolute paths, skip the initial "/" + if (p.front() == TORRENT_SEPARATOR_CHAR +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + || p.front() == '/' +#endif + ) + { p.remove_prefix(1); if (pos > 0) --pos; } +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + auto const sep = find_first_of(p, "/\\", std::string::size_type(pos)); +#else + auto const sep = find_first_of(p, TORRENT_SEPARATOR_CHAR, std::string::size_type(pos)); +#endif + if (sep == string_view::npos) return {p, {}}; + return { p.substr(0, sep), p.substr(sep + 1) }; + } + + std::string complete(string_view f) + { + if (is_complete(f)) return f.to_string(); + if (f == ".") return current_working_directory(); + return combine_path(current_working_directory(), f); + } + + bool is_complete(string_view f) + { + if (f.empty()) return false; +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + int i = 0; + // match the xx:\ or xx:/ form + while (f[i] && is_alpha(f[i])) ++i; + if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/')) + return true; + + // match the \\ form + if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\') + return true; + return false; +#else + if (f[0] == '/') return true; + return false; +#endif + } +} diff --git a/src/pe_crypto.cpp b/src/pe_crypto.cpp new file mode 100644 index 0000000..7cece6e --- /dev/null +++ b/src/pe_crypto.cpp @@ -0,0 +1,415 @@ +/* + +Copyright (c) 2007-2018, Un Shyam, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#if !defined TORRENT_DISABLE_ENCRYPTION + +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include +#include + +// for backwards compatibility with boost < 1.60 which was before export_bits +// and import_bits were introduced +#if BOOST_VERSION < 106000 +#include "libtorrent/aux_/cppint_import_export.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/hasher.hpp" + +namespace libtorrent { + + namespace mp = boost::multiprecision; + + namespace { + // TODO: it would be nice to get the literal working + key_t const dh_prime + ("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563"); + } + + std::array export_key(key_t const& k) + { + std::array ret; + auto* begin = reinterpret_cast(ret.data()); + std::uint8_t* end = mp::export_bits(k, begin, 8); + + // TODO: it would be nice to be able to export to a fixed width field, so + // we wouldn't have to shift it later + if (end < begin + 96) + { + int const len = int(end - begin); + std::memmove(begin + 96 - len, begin, aux::numeric_cast(len)); + std::memset(begin, 0, aux::numeric_cast(96 - len)); + } + return ret; + } + + void rc4_init(const unsigned char* in, std::size_t len, rc4 *state); + std::size_t rc4_encrypt(unsigned char *out, std::size_t outlen, rc4 *state); + + // Set the prime P and the generator, generate local public key + dh_key_exchange::dh_key_exchange() + { + aux::array random_key; + aux::random_bytes({reinterpret_cast(random_key.data()) + , static_cast(random_key.size())}); + + // create local key (random) + mp::import_bits(m_dh_local_secret, random_key.begin(), random_key.end()); + + // key = (2 ^ secret) % prime + m_dh_local_key = mp::powm(key_t(2), m_dh_local_secret, dh_prime); + } + + // compute shared secret given remote public key + void dh_key_exchange::compute_secret(std::uint8_t const* remote_pubkey) + { + TORRENT_ASSERT(remote_pubkey); + key_t key; + mp::import_bits(key, remote_pubkey, remote_pubkey + 96); + compute_secret(key); + } + + void dh_key_exchange::compute_secret(key_t const& remote_pubkey) + { + // shared_secret = (remote_pubkey ^ local_secret) % prime + m_dh_shared_secret = mp::powm(remote_pubkey, m_dh_local_secret, dh_prime); + + std::array buffer; + mp::export_bits(m_dh_shared_secret, reinterpret_cast(buffer.data()), 8); + + static char const req3[4] = {'r', 'e', 'q', '3'}; + // calculate the xor mask for the obfuscated hash + m_xor_mask = hasher(req3).update(buffer).final(); + } + + std::tuple>> + encryption_handler::encrypt( + span> iovec) + { + TORRENT_ASSERT(!m_send_barriers.empty()); + TORRENT_ASSERT(m_send_barriers.front().enc_handler); + + int to_process = m_send_barriers.front().next; + + span> bufs; + bool need_destruct = false; + if (to_process != INT_MAX) + { + TORRENT_ALLOCA(abufs, span, iovec.size()); + bufs = abufs; + need_destruct = true; + int num_bufs = 0; + for (int i = 0; to_process > 0 && i < iovec.size(); ++i) + { + ++num_bufs; + int const size = int(iovec[i].size()); + if (to_process < size) + { + new (&bufs[i]) span( + iovec[i].data(), to_process); + to_process = 0; + } + else + { + new (&bufs[i]) span(iovec[i]); + to_process -= size; + } + } + bufs = bufs.first(num_bufs); + } + else + { + bufs = iovec; + } + + int next_barrier = 0; + span> out_iovec; + if (!bufs.empty()) + { + std::tie(next_barrier, out_iovec) + = m_send_barriers.front().enc_handler->encrypt(bufs); + } + + if (m_send_barriers.front().next != INT_MAX) + { + // to_process holds the difference between the size of the buffers + // and the bytes left to the next barrier + // if it's zero then pop the barrier + // otherwise update the number of bytes remaining to the next barrier + if (to_process == 0) + { + if (m_send_barriers.size() == 1) + { + // transitioning back to plaintext + next_barrier = INT_MAX; + } + m_send_barriers.pop_front(); + } + else + { + m_send_barriers.front().next = to_process; + } + } + +#if TORRENT_USE_ASSERTS + if (next_barrier != INT_MAX && next_barrier != 0) + { + int payload = 0; + for (auto buf : bufs) + payload += int(buf.size()); + + int overhead = 0; + for (auto buf : out_iovec) + overhead += int(buf.size()); + TORRENT_ASSERT(overhead + payload == next_barrier); + } +#endif + if (need_destruct) + { + for (auto buf : bufs) + buf.~span(); + } + return std::make_tuple(next_barrier, out_iovec); + } + + int encryption_handler::decrypt(crypto_receive_buffer& recv_buffer + , std::size_t& bytes_transferred) + { + TORRENT_ASSERT(!is_recv_plaintext()); + int consume = 0; + if (recv_buffer.crypto_packet_finished()) + { + span wr_buf = recv_buffer.mutable_buffer(int(bytes_transferred)); + int produce = 0; + int packet_size = 0; + std::tie(consume, produce, packet_size) = m_dec_handler->decrypt(wr_buf); + TORRENT_ASSERT(packet_size || produce); + TORRENT_ASSERT(packet_size >= 0); + TORRENT_ASSERT(produce >= 0); + bytes_transferred = std::size_t(produce); + if (packet_size) + recv_buffer.crypto_cut(consume, packet_size); + } + else + bytes_transferred = 0; + return consume; + } + + bool encryption_handler::switch_send_crypto(std::shared_ptr crypto + , int pending_encryption) + { + bool place_barrier = false; + if (!m_send_barriers.empty()) + { + auto const end = std::prev(m_send_barriers.end()); + for (auto b = m_send_barriers.begin(); b != end; ++b) + pending_encryption -= b->next; + TORRENT_ASSERT(pending_encryption >= 0); + m_send_barriers.back().next = pending_encryption; + } + else if (crypto) + place_barrier = true; + + if (crypto) + m_send_barriers.push_back(barrier(crypto, INT_MAX)); + + return place_barrier; + } + + void encryption_handler::switch_recv_crypto(std::shared_ptr crypto + , crypto_receive_buffer& recv_buffer) + { + m_dec_handler = crypto; + int packet_size = 0; + if (crypto) + { + int consume = 0; + int produce = 0; + std::vector> wr_buf; + std::tie(consume, produce, packet_size) = crypto->decrypt(wr_buf); + TORRENT_ASSERT(wr_buf.empty()); + TORRENT_ASSERT(consume == 0); + TORRENT_ASSERT(produce == 0); + } + recv_buffer.crypto_reset(packet_size); + } + + rc4_handler::rc4_handler() + : m_encrypt(false) + , m_decrypt(false) + { + m_rc4_incoming.x = 0; + m_rc4_incoming.y = 0; + m_rc4_outgoing.x = 0; + m_rc4_outgoing.y = 0; + } + + void rc4_handler::set_incoming_key(span key) + { + m_decrypt = true; + rc4_init(reinterpret_cast(key.data()) + , std::size_t(key.size()), &m_rc4_incoming); + // Discard first 1024 bytes + char buf[1024]; + span vec(buf, sizeof(buf)); + decrypt(vec); + } + + void rc4_handler::set_outgoing_key(span key) + { + m_encrypt = true; + rc4_init(reinterpret_cast(key.data()) + , std::size_t(key.size()), &m_rc4_outgoing); + // Discard first 1024 bytes + char buf[1024]; + span vec(buf, sizeof(buf)); + encrypt(vec); + } + + std::tuple>> + rc4_handler::encrypt(span> bufs) + { + span> empty; + if (!m_encrypt) return std::make_tuple(0, empty); + if (bufs.empty()) return std::make_tuple(0, empty); + + int bytes_processed = 0; + for (auto& buf : bufs) + { + auto* const pos = reinterpret_cast(buf.data()); + int const len = int(buf.size()); + + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(pos); + + bytes_processed += len; + rc4_encrypt(pos, std::uint32_t(len), &m_rc4_outgoing); + } + return std::make_tuple(bytes_processed, empty); + } + + std::tuple rc4_handler::decrypt(span> bufs) + { + if (!m_decrypt) return std::make_tuple(0, 0, 0); + + int bytes_processed = 0; + for (auto& buf : bufs) + { + auto* const pos = reinterpret_cast(buf.data()); + int const len = int(buf.size()); + + TORRENT_ASSERT(len >= 0); + TORRENT_ASSERT(pos); + + bytes_processed += len; + rc4_encrypt(pos, std::uint32_t(len), &m_rc4_incoming); + } + return std::make_tuple(0, bytes_processed, 0); + } + +// All this code is based on libTomCrypt (http://www.libtomcrypt.com/) +// this library is public domain and has been specially +// tailored for libtorrent by Arvid Norberg + +void rc4_init(const unsigned char* in, std::size_t len, rc4 *state) +{ + std::size_t const key_size = sizeof(state->buf); + aux::array key; + std::uint8_t tmp, *s; + int keylen, x, y, j; + + TORRENT_ASSERT(state != nullptr); + TORRENT_ASSERT(len <= key_size); + if (len > key_size) len = key_size; + + state->x = 0; + while (len--) { + state->buf[state->x++] = *in++; + } + + /* extract the key */ + s = state->buf.data(); + std::memcpy(key.data(), s, key_size); + keylen = state->x; + + /* make RC4 perm and shuffle */ + for (x = 0; x < int(key_size); ++x) { + s[x] = x & 0xff; + } + + for (j = x = y = 0; x < int(key_size); x++) { + y = (y + state->buf[x] + key[j++]) & 255; + if (j == keylen) { + j = 0; + } + tmp = s[x]; s[x] = s[y]; s[y] = tmp; + } + state->x = 0; + state->y = 0; +} + +std::size_t rc4_encrypt(unsigned char *out, std::size_t outlen, rc4 *state) +{ + std::uint8_t x, y, *s, tmp; + std::size_t n; + + TORRENT_ASSERT(out != nullptr); + TORRENT_ASSERT(state != nullptr); + + n = outlen; + x = state->x & 0xff; + y = state->y & 0xff; + s = state->buf.data(); + while (outlen--) { + x = (x + 1) & 255; + y = (y + s[x]) & 255; + tmp = s[x]; s[x] = s[y]; s[y] = tmp; + tmp = (s[x] + s[y]) & 255; + *out++ ^= s[tmp]; + } + state->x = x; + state->y = y; + return n; +} + +} // namespace libtorrent + +#endif // TORRENT_DISABLE_ENCRYPTION diff --git a/src/peer_class.cpp b/src/peer_class.cpp new file mode 100644 index 0000000..6055c3d --- /dev/null +++ b/src/peer_class.cpp @@ -0,0 +1,125 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_connection.hpp" + +namespace libtorrent { + + void peer_class::set_upload_limit(int limit) + { + TORRENT_ASSERT(limit >= -1); + if (limit < 0) limit = 0; + if (limit < 10 && limit > 0) limit = 10; + channel[peer_connection::upload_channel].throttle(limit); + } + + void peer_class::set_download_limit(int limit) + { + TORRENT_ASSERT(limit >= -1); + if (limit < 0) limit = 0; + if (limit < 10 && limit > 0) limit = 10; + channel[peer_connection::download_channel].throttle(limit); + } + + void peer_class::get_info(peer_class_info* pci) const + { + pci->ignore_unchoke_slots = ignore_unchoke_slots; + pci->connection_limit_factor = connection_limit_factor; + pci->label = label; + pci->upload_limit = channel[peer_connection::upload_channel].throttle(); + pci->download_limit = channel[peer_connection::download_channel].throttle(); + pci->upload_priority = priority[peer_connection::upload_channel]; + pci->download_priority = priority[peer_connection::download_channel]; + } + + void peer_class::set_info(peer_class_info const* pci) + { + ignore_unchoke_slots = pci->ignore_unchoke_slots; + connection_limit_factor = pci->connection_limit_factor; + label = pci->label; + set_upload_limit(pci->upload_limit); + set_download_limit(pci->download_limit); + priority[peer_connection::upload_channel] = std::max(1, std::min(255, pci->upload_priority)); + priority[peer_connection::download_channel] = std::max(1, std::min(255, pci->download_priority)); + } + + peer_class_t peer_class_pool::new_peer_class(std::string label) + { + peer_class_t ret{0}; + if (!m_free_list.empty()) + { + ret = m_free_list.back(); + m_free_list.pop_back(); + m_peer_classes[ret] = peer_class(std::move(label)); + } + else + { + ret = m_peer_classes.end_index(); + m_peer_classes.emplace_back(std::move(label)); + } + + return ret; + } + + void peer_class_pool::decref(peer_class_t c) + { + TORRENT_ASSERT(c < m_peer_classes.end_index()); + TORRENT_ASSERT(m_peer_classes[c].in_use); + TORRENT_ASSERT(m_peer_classes[c].references > 0); + + --m_peer_classes[c].references; + if (m_peer_classes[c].references) return; + m_peer_classes[c].clear(); + m_free_list.push_back(c); + } + + void peer_class_pool::incref(peer_class_t c) + { + TORRENT_ASSERT(c < m_peer_classes.end_index()); + TORRENT_ASSERT(m_peer_classes[c].in_use); + + ++m_peer_classes[c].references; + } + + peer_class* peer_class_pool::at(peer_class_t c) + { + if (c >= m_peer_classes.end_index() || !m_peer_classes[c].in_use) return nullptr; + return &m_peer_classes[c]; + } + + peer_class const* peer_class_pool::at(peer_class_t c) const + { + if (c >= m_peer_classes.end_index() || !m_peer_classes[c].in_use) return nullptr; + return &m_peer_classes[c]; + } +} diff --git a/src/peer_class_set.cpp b/src/peer_class_set.cpp new file mode 100644 index 0000000..ec6eca7 --- /dev/null +++ b/src/peer_class_set.cpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_class_set.hpp" + +#include // for find + +namespace libtorrent { + + void peer_class_set::add_class(peer_class_pool& pool, peer_class_t c) + { + if (std::find(m_class.begin(), m_class.begin() + m_size, c) + != m_class.begin() + m_size) return; + if (m_size >= int(m_class.size()) - 1) + { + TORRENT_ASSERT_FAIL(); + return; + } + m_class[m_size] = c; + pool.incref(c); + ++m_size; + } + + bool peer_class_set::has_class(peer_class_t c) const + { + return std::find(m_class.begin(), m_class.begin() + m_size, c) + != m_class.begin() + m_size; + } + + void peer_class_set::remove_class(peer_class_pool& pool, peer_class_t const c) + { + auto const i = std::find(m_class.begin(), m_class.begin() + m_size, c); + int const idx = int(i - m_class.begin()); + if (idx == m_size) return; // not found + if (idx < m_size - 1) + { + // place the last element in the slot of the erased one + m_class[idx] = m_class[m_size - 1]; + } + --m_size; + pool.decref(c); + } +} diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp new file mode 100644 index 0000000..1d658f5 --- /dev/null +++ b/src/peer_connection.cpp @@ -0,0 +1,6632 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/request_blocks.hpp" // for request_a_block +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" // for alert_manager +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/close_reason.hpp" +#include "libtorrent/aux_/has_block.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/buffer.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/set_socket_buffer.hpp" + +#if TORRENT_USE_ASSERTS +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include +#endif + +#ifndef TORRENT_DISABLE_LOGGING +#include // for va_start, va_end +#include // for vsnprintf +#include "libtorrent/socket_io.hpp" +#include "libtorrent/hex.hpp" // to_hex +#endif + +#include "libtorrent/aux_/torrent_impl.hpp" + +//#define TORRENT_CORRUPT_DATA + +using namespace std::placeholders; + +namespace libtorrent { + + constexpr request_flags_t peer_connection::time_critical; + constexpr request_flags_t peer_connection::busy; + + namespace { + + // the limits of the download queue size + constexpr int min_request_queue = 2; + + bool pending_block_in_buffer(pending_block const& pb) + { + return pb.send_buffer_offset != pending_block::not_in_buffer; + } + + } + + constexpr piece_index_t piece_block_progress::invalid_index; + + constexpr disconnect_severity_t peer_connection_interface::normal; + constexpr disconnect_severity_t peer_connection_interface::failure; + constexpr disconnect_severity_t peer_connection_interface::peer_error; + +#if TORRENT_USE_ASSERTS + bool peer_connection::is_single_thread() const + { +#ifdef TORRENT_USE_INVARIANT_CHECKS + std::shared_ptr t = m_torrent.lock(); + if (!t) return true; + return t->is_single_thread(); +#else + return true; +#endif + } +#endif + + peer_connection::peer_connection(peer_connection_args const& pack) + : peer_connection_hot_members(pack.tor, *pack.ses, *pack.sett) + , m_socket(pack.s) + , m_peer_info(pack.peerinfo) + , m_counters(*pack.stats_counters) + , m_num_pieces(0) + , m_max_out_request_queue(m_settings.get_int(settings_pack::max_out_request_queue)) + , m_remote(pack.endp) + , m_disk_thread(*pack.disk_thread) + , m_ios(*pack.ios) + , m_work(m_ios) + , m_outstanding_piece_verification(0) + , m_outgoing(!pack.tor.expired()) + , m_received_listen_port(false) + , m_fast_reconnect(false) + , m_failed(false) + , m_connected(pack.tor.expired()) + , m_request_large_blocks(false) +#ifndef TORRENT_DISABLE_SHARE_MODE + , m_share_mode(false) +#endif + , m_upload_only(false) + , m_bitfield_received(false) + , m_no_download(false) + , m_holepunch_mode(false) + , m_peer_choked(true) + , m_have_all(false) + , m_peer_interested(false) + , m_need_interest_update(false) + , m_has_metadata(true) + , m_exceeded_limit(false) + , m_slow_start(true) + { + m_counters.inc_stats_counter(counters::num_tcp_peers + m_socket->type() - 1); + std::shared_ptr t = m_torrent.lock(); + + if (m_connected) + m_counters.inc_stats_counter(counters::num_peers_connected); + else if (m_connecting) + m_counters.inc_stats_counter(counters::num_peers_half_open); + + // if t is nullptr, we better not be connecting, since + // we can't decrement the connecting counter + TORRENT_ASSERT(t || !m_connecting); +#if TORRENT_ABI_VERSION == 1 + m_est_reciprocation_rate = m_settings.get_int(settings_pack::default_est_reciprocation_rate); +#endif + + m_channel_state[upload_channel] = peer_info::bw_idle; + m_channel_state[download_channel] = peer_info::bw_idle; + + m_quota[0] = 0; + m_quota[1] = 0; + + TORRENT_ASSERT(pack.peerinfo == nullptr || pack.peerinfo->banned == false); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(m_outgoing ? peer_log_alert::outgoing : peer_log_alert::incoming)) + { + error_code ec; + TORRENT_ASSERT(m_socket->remote_endpoint(ec) == m_remote || ec); + tcp::endpoint local_ep = m_socket->local_endpoint(ec); + + peer_log(m_outgoing ? peer_log_alert::outgoing : peer_log_alert::incoming + , m_outgoing ? "OUTGOING_CONNECTION" : "INCOMING_CONNECTION" + , "ep: %s type: %s seed: %d p: %p local: %s" + , print_endpoint(m_remote).c_str() + , m_socket->type_name() + , m_peer_info ? m_peer_info->seed : 0 + , static_cast(m_peer_info) + , print_endpoint(local_ep).c_str()); + } +#endif + + // this counter should not be incremented until we know constructing this + // peer object can't fail anymore + if (m_connecting && t) t->inc_num_connecting(m_peer_info); + +#if TORRENT_USE_ASSERTS + piece_failed = false; + m_in_constructor = false; +#endif + } + + template + void peer_connection::wrap(Fun f, Args&&... a) +#ifndef BOOST_NO_EXCEPTIONS + try +#endif + { + (this->*f)(std::forward(a)...); + } +#ifndef BOOST_NO_EXCEPTIONS + catch (std::bad_alloc const&) { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "EXCEPTION", "bad_alloc"); +#endif + disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::unknown); + } + catch (system_error const& e) { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "EXCEPTION", "(%d %s) %s" + , e.code().value() + , e.code().message().c_str() + , e.what()); +#endif + disconnect(e.code(), operation_t::unknown); + } + catch (std::exception const& e) { + TORRENT_UNUSED(e); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "EXCEPTION", "%s", e.what()); +#endif + disconnect(make_error_code(boost::system::errc::not_enough_memory) + , operation_t::sock_write); + } +#endif // BOOST_NO_EXCEPTIONS + + int peer_connection::timeout() const + { + TORRENT_ASSERT(is_single_thread()); + int ret = m_settings.get_int(settings_pack::peer_timeout); +#if TORRENT_USE_I2P + if (m_peer_info && m_peer_info->is_i2p_addr) + { + // quadruple the timeout for i2p peers + ret *= 4; + } +#endif + return ret; + } + + void peer_connection::on_exception(std::exception const& e) + { + TORRENT_UNUSED(e); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PEER_ERROR", "ERROR: %s" + , e.what()); +#endif + disconnect(error_code(), operation_t::unknown, peer_error); + } + + void peer_connection::on_error(error_code const& ec) + { + disconnect(ec, operation_t::unknown, peer_error); + } + +#if TORRENT_ABI_VERSION == 1 + void peer_connection::increase_est_reciprocation_rate() + { + TORRENT_ASSERT(is_single_thread()); + m_est_reciprocation_rate += m_est_reciprocation_rate + * m_settings.get_int(settings_pack::increase_est_reciprocation_rate) / 100; + } + + void peer_connection::decrease_est_reciprocation_rate() + { + TORRENT_ASSERT(is_single_thread()); + m_est_reciprocation_rate -= m_est_reciprocation_rate + * m_settings.get_int(settings_pack::decrease_est_reciprocation_rate) / 100; + } +#endif + + int peer_connection::get_priority(int const channel) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(channel >= 0 && channel < 2); + int prio = 1; + for (int i = 0; i < num_classes(); ++i) + { + int class_prio = m_ses.peer_classes().at(class_at(i))->priority[channel]; + if (prio < class_prio) prio = class_prio; + } + + std::shared_ptr t = associated_torrent().lock(); + + if (t) + { + for (int i = 0; i < t->num_classes(); ++i) + { + int class_prio = m_ses.peer_classes().at(t->class_at(i))->priority[channel]; + if (prio < class_prio) prio = class_prio; + } + } + return prio; + } + + void peer_connection::reset_choke_counters() + { + TORRENT_ASSERT(is_single_thread()); + m_downloaded_at_last_round= m_statistics.total_payload_download(); + m_uploaded_at_last_round = m_statistics.total_payload_upload(); + } + + void peer_connection::start() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_peer_info == nullptr || m_peer_info->connection == this); + std::shared_ptr t = m_torrent.lock(); + + if (!m_outgoing) + { + error_code ec; + m_socket->non_blocking(true, ec); + if (ec) + { + disconnect(ec, operation_t::iocontrol); + return; + } + m_remote = m_socket->remote_endpoint(ec); + if (ec) + { + disconnect(ec, operation_t::getpeername); + return; + } + m_local = m_socket->local_endpoint(ec); + if (ec) + { + disconnect(ec, operation_t::getname); + return; + } + if (is_v4(m_remote) && m_settings.get_int(settings_pack::peer_tos) != 0) + { + m_socket->set_option(type_of_service(char(m_settings.get_int(settings_pack::peer_tos))), ec); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , m_settings.get_int(settings_pack::peer_tos), ec.message().c_str()); + } +#endif + } +#if defined IPV6_TCLASS + else if (is_v6(m_remote) && m_settings.get_int(settings_pack::peer_tos) != 0) + { + m_socket->set_option(traffic_class(char(m_settings.get_int(settings_pack::peer_tos))), ec); + } +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "SET_PEER_CLASS", "a: %s" + , print_address(m_remote.address()).c_str()); + } +#endif + + m_ses.set_peer_classes(this, m_remote.address(), m_socket->type()); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + std::string classes; + for (int i = 0; i < num_classes(); ++i) + { + classes += m_ses.peer_classes().at(class_at(i))->label; + classes += ' '; + } + peer_log(peer_log_alert::info, "CLASS", "%s" + , classes.c_str()); + } +#endif + + if (t && t->ready_for_connections()) + { + init(); + } + + // if this is an incoming connection, we're done here + if (!m_connecting) + { + error_code err; + aux::set_socket_buffer_size(*m_socket, m_settings, err); +#ifndef TORRENT_DISABLE_LOGGING + if (err && should_log(peer_log_alert::incoming)) + { + peer_log(peer_log_alert::incoming, "SOCKET_BUFFER", "%s %s" + , print_endpoint(m_remote).c_str() + , print_error(err).c_str()); + } +#endif + + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "OPEN", "protocol: %s" + , (is_v4(m_remote) ? "IPv4" : "IPv6")); + } +#endif + error_code ec; + m_socket->open(m_remote.protocol(), ec); + if (ec) + { + disconnect(ec, operation_t::sock_open); + return; + } + + tcp::endpoint const bound_ip = m_ses.bind_outgoing_socket(*m_socket + , m_remote.address(), ec); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "BIND", "dst: %s ec: %s" + , print_endpoint(bound_ip).c_str() + , ec.message().c_str()); + } +#else + TORRENT_UNUSED(bound_ip); +#endif + if (ec) + { + disconnect(ec, operation_t::sock_bind); + return; + } + + { + error_code err; + aux::set_socket_buffer_size(*m_socket, m_settings, err); +#ifndef TORRENT_DISABLE_LOGGING + if (err && should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SOCKET_BUFFER", "%s %s" + , print_endpoint(m_remote).c_str() + , print_error(err).c_str()); + } +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "ASYNC_CONNECT", "dst: %s" + , print_endpoint(m_remote).c_str()); + } +#endif + ADD_OUTSTANDING_ASYNC("peer_connection::on_connection_complete"); + + auto conn = self(); + m_socket->async_connect(m_remote + , [conn](error_code const& e) { conn->wrap(&peer_connection::on_connection_complete, e); }); + m_connect = aux::time_now(); + + sent_syn(is_v6(m_remote)); + + if (t && t->alerts().should_post()) + { + t->alerts().emplace_alert( + t->get_handle(), remote(), pid(), m_socket->type()); + } +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "LOCAL ENDPOINT", "e: %s" + , print_endpoint(m_socket->local_endpoint(ec)).c_str()); + } +#endif + } + + void peer_connection::update_interest() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_need_interest_update) + { + // we're the first to request an interest update + // post a message in order to delay it enough for + // any potential other messages already in the queue + // to not trigger another one. This effectively defer + // the update until the current message queue is + // flushed + auto conn = self(); + m_ios.post([conn] { conn->wrap(&peer_connection::do_update_interest); }); + } + m_need_interest_update = true; + } + + void peer_connection::do_update_interest() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_need_interest_update); + m_need_interest_update = false; + + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + + // if m_have_piece is 0, it means the connections + // have not been initialized yet. The interested + // flag will be updated once they are. + if (m_have_piece.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UPDATE_INTEREST", "connections not initialized"); +#endif + return; + } + if (!t->ready_for_connections()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UPDATE_INTEREST", "not ready for connections"); +#endif + return; + } + + bool interested = false; + if (!t->is_upload_only()) + { + t->need_picker(); + piece_picker const& p = t->picker(); + piece_index_t const end_piece(p.num_pieces()); + for (piece_index_t j(0); j != end_piece; ++j) + { + if (m_have_piece[j] + && t->piece_priority(j) > dont_download + && !p.has_piece_passed(j)) + { + interested = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UPDATE_INTEREST", "interesting, piece: %d" + , static_cast(j)); +#endif + break; + } + } + } + +#ifndef TORRENT_DISABLE_LOGGING + if (!interested) + peer_log(peer_log_alert::info, "UPDATE_INTEREST", "not interesting"); +#endif + + if (!interested) send_not_interested(); + else t->peer_is_interesting(*this); + + TORRENT_ASSERT(in_handshake() || is_interesting() == interested); + + disconnect_if_redundant(); + } + +#ifndef TORRENT_DISABLE_LOGGING + bool peer_connection::should_log(peer_log_alert::direction_t) const + { + return m_ses.alerts().should_post(); + } + + void peer_connection::peer_log(peer_log_alert::direction_t direction + , char const* event) const noexcept + { + peer_log(direction, event, ""); + } + + TORRENT_FORMAT(4,5) + void peer_connection::peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const noexcept try + { + TORRENT_ASSERT(is_single_thread()); + + if (!m_ses.alerts().should_post()) return; + + va_list v; + va_start(v, fmt); + + torrent_handle h; + std::shared_ptr t = m_torrent.lock(); + if (t) h = t->get_handle(); + + m_ses.alerts().emplace_alert( + h, m_remote, m_peer_id, direction, event, fmt, v); + + va_end(v); + + } + catch (std::exception const&) {} +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + void peer_connection::add_extension(std::shared_ptr ext) + { + TORRENT_ASSERT(is_single_thread()); + m_extensions.push_back(ext); + } + + peer_plugin const* peer_connection::find_plugin(string_view type) + { + TORRENT_ASSERT(is_single_thread()); + auto p = std::find_if(m_extensions.begin(), m_extensions.end() + , [&](std::shared_ptr const& e) { return e->type() == type; }); + return p != m_extensions.end() ? p->get() : nullptr; + } +#endif + + void peer_connection::send_allowed_set() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (!t->valid_metadata()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ALLOWED", "skipping allowed set because we don't have metadata"); +#endif + return; + } + +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (t->super_seeding()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ALLOWED", "skipping allowed set because of super seeding"); +#endif + return; + } +#endif + + if (upload_only()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ALLOWED", "skipping allowed set because peer is upload only"); +#endif + return; + } + + int const num_allowed_pieces = m_settings.get_int(settings_pack::allowed_fast_set_size); + if (num_allowed_pieces <= 0) return; + + if (!t->valid_metadata()) return; + + int const num_pieces = t->torrent_file().num_pieces(); + + if (num_allowed_pieces >= num_pieces) + { + // this is a special case where we have more allowed + // fast pieces than pieces in the torrent. Just send + // an allowed fast message for every single piece + for (auto const i : t->torrent_file().piece_range()) + { + // there's no point in offering fast pieces + // that the peer already has + if (has_piece(i)) continue; + + write_allow_fast(i); + TORRENT_ASSERT(std::find(m_accept_fast.begin() + , m_accept_fast.end(), i) + == m_accept_fast.end()); + if (m_accept_fast.empty()) + { + m_accept_fast.reserve(10); + m_accept_fast_piece_cnt.reserve(10); + } + m_accept_fast.push_back(i); + m_accept_fast_piece_cnt.push_back(0); + } + return; + } + + std::string x; + address const& addr = m_remote.address(); + if (addr.is_v4()) + { + address_v4::bytes_type bytes = addr.to_v4().to_bytes(); + x.assign(reinterpret_cast(bytes.data()), bytes.size()); + } + else + { + address_v6::bytes_type bytes = addr.to_v6().to_bytes(); + x.assign(reinterpret_cast(bytes.data()), bytes.size()); + } + x.append(t->torrent_file().info_hash().data(), 20); + + sha1_hash hash = hasher(x).final(); + int attempts = 0; + int loops = 0; + for (;;) + { + char const* p = hash.data(); + for (int i = 0; i < int(hash.size() / sizeof(std::uint32_t)); ++i) + { + ++loops; + TORRENT_ASSERT(num_pieces > 0); + piece_index_t const piece(int(detail::read_uint32(p) % std::uint32_t(num_pieces))); + if (std::find(m_accept_fast.begin(), m_accept_fast.end(), piece) + != m_accept_fast.end()) + { + // this is our safety-net to make sure this loop terminates, even + // under the worst conditions + if (++loops > 500) return; + continue; + } + + if (!has_piece(piece)) + { + write_allow_fast(piece); + if (m_accept_fast.empty()) + { + m_accept_fast.reserve(10); + m_accept_fast_piece_cnt.reserve(10); + } + m_accept_fast.push_back(piece); + m_accept_fast_piece_cnt.push_back(0); + } + if (++attempts >= num_allowed_pieces) return; + } + hash = hasher(hash).final(); + } + } + + void peer_connection::on_metadata_impl() + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr t = associated_torrent().lock(); + m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); + m_num_pieces = m_have_piece.count(); + + piece_index_t const limit(m_num_pieces); + + // now that we know how many pieces there are + // remove any invalid allowed_fast and suggest pieces + // now that we know what the number of pieces are + m_allowed_fast.erase(std::remove_if(m_allowed_fast.begin(), m_allowed_fast.end() + , [=](piece_index_t const p) { return p >= limit; }) + , m_allowed_fast.end()); + + // remove any piece suggested to us whose index is invalid + // now that we know how many pieces there are + m_suggested_pieces.erase( + std::remove_if(m_suggested_pieces.begin(), m_suggested_pieces.end() + , [=](piece_index_t const p) { return p >= limit; }) + , m_suggested_pieces.end()); + + on_metadata(); + if (m_disconnecting) return; + } + + void peer_connection::init() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(t->ready_for_connections()); + + m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); + + if (m_have_all) + { + m_num_pieces = t->torrent_file().num_pieces(); + m_have_piece.set_all(); + } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(!m_initialized); + m_initialized = true; +#endif + // now that we have a piece_picker, + // update it with this peer's pieces + + TORRENT_ASSERT(m_num_pieces == m_have_piece.count()); + + if (m_num_pieces == m_have_piece.size()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INIT", "this is a seed p: %p" + , static_cast(m_peer_info)); +#endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + + // if this is a web seed. we don't have a peer_info struct + t->set_seed(m_peer_info, true); + m_upload_only = true; + + t->peer_has_all(this); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + if (t->is_upload_only()) send_not_interested(); + else t->peer_is_interesting(*this); + disconnect_if_redundant(); + return; + } + + // if we're a seed, we don't keep track of piece availability + if (t->has_picker()) + { + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + t->peer_has(m_have_piece, this); + bool interesting = false; + for (auto const i : m_have_piece.range()) + { + if (!m_have_piece[i]) continue; + // if the peer has a piece and we don't, the peer is interesting + if (!t->have_piece(i) + && t->picker().piece_priority(i) != dont_download) + interesting = true; + } + if (interesting) t->peer_is_interesting(*this); + else send_not_interested(); + } + else + { + update_interest(); + } + } + + peer_connection::~peer_connection() + { + m_counters.inc_stats_counter(counters::num_tcp_peers + m_socket->type() - 1, -1); + +// INVARIANT_CHECK; + TORRENT_ASSERT(!m_in_constructor); + TORRENT_ASSERT(!m_destructed); +#if TORRENT_USE_ASSERTS + m_destructed = true; +#endif + +#if TORRENT_USE_ASSERTS + m_in_use = 0; +#endif + + // decrement the stats counter + set_endgame(false); + + if (m_interesting) + m_counters.inc_stats_counter(counters::num_peers_down_interested, -1); + if (m_peer_interested) + m_counters.inc_stats_counter(counters::num_peers_up_interested, -1); + if (!m_choked) + { + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_all, -1); + if (!ignore_unchoke_slots()) + m_counters.inc_stats_counter(counters::num_peers_up_unchoked, -1); + } + if (!m_peer_choked) + m_counters.inc_stats_counter(counters::num_peers_down_unchoked, -1); + if (m_connected) + m_counters.inc_stats_counter(counters::num_peers_connected, -1); + m_connected = false; + if (!m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests, -1); + + // defensive + std::shared_ptr t = m_torrent.lock(); + // if t is nullptr, we better not be connecting, since + // we can't decrement the connecting counter + TORRENT_ASSERT(t || !m_connecting); + + // we should really have dealt with this already + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(m_peer_info); + m_connecting = false; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + m_extensions.clear(); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONNECTION CLOSED"); +#endif + TORRENT_ASSERT(m_request_queue.empty()); + TORRENT_ASSERT(m_download_queue.empty()); + } + + bool peer_connection::on_parole() const + { return peer_info_struct() && peer_info_struct()->on_parole; } + + picker_options_t peer_connection::picker_options() const + { + TORRENT_ASSERT(is_single_thread()); + picker_options_t ret = m_picker_options; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + if (!t) return {}; + + if (t->num_time_critical_pieces() > 0) + { + ret |= piece_picker::time_critical_mode; + } + + if (t->is_sequential_download()) + { + ret |= piece_picker::sequential; + } + else if (t->num_have() < m_settings.get_int(settings_pack::initial_picker_threshold)) + { + // if we have fewer pieces than a certain threshold + // don't pick rare pieces, just pick random ones, + // and prioritize finishing them + ret |= piece_picker::prioritize_partials; + } + else + { + ret |= piece_picker::rarest_first; + + if (m_snubbed) + { + // snubbed peers should request + // the common pieces first, just to make + // it more likely for all snubbed peers to + // request blocks from the same piece + ret |= piece_picker::reverse; + } + else + { + if (m_settings.get_bool(settings_pack::piece_extent_affinity) + && t->num_time_critical_pieces() == 0) + ret |= piece_picker::piece_extent_affinity; + } + } + + if (m_settings.get_bool(settings_pack::prioritize_partial_pieces)) + ret |= piece_picker::prioritize_partials; + + if (on_parole()) ret |= piece_picker::on_parole + | piece_picker::prioritize_partials; + + // only one of rarest_first and sequential can be set. i.e. the sum of + // whether the bit is set or not may only be 0 or 1 (never 2) + TORRENT_ASSERT(((ret & piece_picker::rarest_first) ? 1 : 0) + + ((ret & piece_picker::sequential) ? 1 : 0) <= 1); + return ret; + } + + void peer_connection::fast_reconnect(bool r) + { + TORRENT_ASSERT(is_single_thread()); + if (!peer_info_struct() || peer_info_struct()->fast_reconnects > 1) + return; + m_fast_reconnect = r; + peer_info_struct()->last_connected = std::uint16_t(m_ses.session_time()); + int const rewind = m_settings.get_int(settings_pack::min_reconnect_time) + * m_settings.get_int(settings_pack::max_failcount); + if (int(peer_info_struct()->last_connected) < rewind) peer_info_struct()->last_connected = 0; + else peer_info_struct()->last_connected -= std::uint16_t(rewind); + + if (peer_info_struct()->fast_reconnects < 15) + ++peer_info_struct()->fast_reconnects; + } + + void peer_connection::received_piece(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + // dont announce during handshake + if (in_handshake()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "RECEIVED", "piece: %d" + , static_cast(index)); +#endif + + // remove suggested pieces once we have them + auto i = std::find(m_suggested_pieces.begin(), m_suggested_pieces.end(), index); + if (i != m_suggested_pieces.end()) m_suggested_pieces.erase(i); + + // remove allowed fast pieces + i = std::find(m_allowed_fast.begin(), m_allowed_fast.end(), index); + if (i != m_allowed_fast.end()) m_allowed_fast.erase(i); + + if (has_piece(index)) + { + // if we got a piece that this peer has + // it might have been the last interesting + // piece this peer had. We might not be + // interested anymore + update_interest(); + if (is_disconnecting()) return; + } + + if (disconnect_if_redundant()) return; + +#if TORRENT_USE_ASSERTS + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); +#endif + } + + void peer_connection::announce_piece(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + // dont announce during handshake + if (in_handshake()) return; + + // optimization, don't send have messages + // to peers that already have the piece + if (!m_settings.get_bool(settings_pack::send_redundant_have) + && has_piece(index)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE", "piece: %d SUPRESSED" + , static_cast(index)); +#endif + return; + } + + if (disconnect_if_redundant()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE", "piece: %d" + , static_cast(index)); +#endif + write_have(index); +#if TORRENT_USE_ASSERTS + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); +#endif + } + + bool peer_connection::has_piece(piece_index_t const i) const + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(i >= piece_index_t(0)); + TORRENT_ASSERT(i < t->torrent_file().end_piece()); + return m_have_piece[i]; + } + + std::vector const& peer_connection::request_queue() const + { + TORRENT_ASSERT(is_single_thread()); + return m_request_queue; + } + + std::vector const& peer_connection::download_queue() const + { + TORRENT_ASSERT(is_single_thread()); + return m_download_queue; + } + + std::vector const& peer_connection::upload_queue() const + { + TORRENT_ASSERT(is_single_thread()); + return m_requests; + } + + time_duration peer_connection::download_queue_time(int const extra_bytes) const + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + int rate = 0; + + // if we haven't received any data recently, the current download rate + // is not representative + if (aux::time_now() - m_last_piece > seconds(30) && m_download_rate_peak > 0) + { + rate = m_download_rate_peak; + } + else if (aux::time_now() - m_last_unchoked < seconds(5) + && m_statistics.total_payload_upload() < 2 * 0x4000) + { + // if we're have only been unchoked for a short period of time, + // we don't know what rate we can get from this peer. Instead of assuming + // the lowest possible rate, assume the average. + + int peers_with_requests = int(stats_counters()[counters::num_peers_down_requests]); + // avoid division by 0 + if (peers_with_requests == 0) peers_with_requests = 1; + + // TODO: this should be the global download rate + rate = t->statistics().transfer_rate(stat::download_payload) / peers_with_requests; + } + else + { + // current download rate in bytes per seconds + rate = m_statistics.transfer_rate(stat::download_payload); + } + + // avoid division by zero + if (rate < 50) rate = 50; + + // average of current rate and peak +// rate = (rate + m_download_rate_peak) / 2; + + return milliseconds((m_outstanding_bytes + extra_bytes + + m_queued_time_critical * t->block_size() * 1000) / rate); + } + + void peer_connection::add_stat(std::int64_t const downloaded, std::int64_t const uploaded) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.add_stat(downloaded, uploaded); + } + + void peer_connection::received_bytes(int const bytes_payload, int const bytes_protocol) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.received_bytes(bytes_payload, bytes_protocol); + if (m_ignore_stats) return; + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->received_bytes(bytes_payload, bytes_protocol); + } + + void peer_connection::sent_bytes(int const bytes_payload, int const bytes_protocol) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.sent_bytes(bytes_payload, bytes_protocol); +#ifndef TORRENT_DISABLE_EXTENSIONS + if (bytes_payload) + { + for (auto const& e : m_extensions) + { + e->sent_payload(bytes_payload); + } + } +#endif + if (m_ignore_stats) return; + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->sent_bytes(bytes_payload, bytes_protocol); + } + + void peer_connection::trancieve_ip_packet(int const bytes, bool const ipv6) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.trancieve_ip_packet(bytes, ipv6); + if (m_ignore_stats) return; + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->trancieve_ip_packet(bytes, ipv6); + } + + void peer_connection::sent_syn(bool const ipv6) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.sent_syn(ipv6); + if (m_ignore_stats) return; + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->sent_syn(ipv6); + } + + void peer_connection::received_synack(bool const ipv6) + { + TORRENT_ASSERT(is_single_thread()); + m_statistics.received_synack(ipv6); + if (m_ignore_stats) return; + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->received_synack(ipv6); + } + + typed_bitfield const& peer_connection::get_bitfield() const + { + TORRENT_ASSERT(is_single_thread()); + return m_have_piece; + } + + void peer_connection::received_valid_data(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + // this fails because we haven't had time to disconnect + // seeds yet, and we might have just become one +// INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + e->on_piece_pass(index); + } +#else + TORRENT_UNUSED(index); +#endif + } + + // single_peer is true if the entire piece was received by a single + // peer + bool peer_connection::received_invalid_data(piece_index_t const index, bool single_peer) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + TORRENT_UNUSED(single_peer); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + e->on_piece_failed(index); + } +#else + TORRENT_UNUSED(index); +#endif + return true; + } + + // verifies a piece to see if it is valid (is within a valid range) + // and if it can correspond to a request generated by libtorrent. + bool peer_connection::verify_piece(peer_request const& p) const + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + TORRENT_ASSERT(t->valid_metadata()); + torrent_info const& ti = t->torrent_file(); + + return p.piece >= piece_index_t(0) + && p.piece < ti.end_piece() + && p.start >= 0 + && p.start < ti.piece_length() + && t->to_req(piece_block(p.piece, p.start / t->block_size())) == p; + } + + void peer_connection::attach_to_torrent(sha1_hash const& ih) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ATTACH", "attached to torrent"); +#endif + + TORRENT_ASSERT(!m_disconnecting); + TORRENT_ASSERT(m_torrent.expired()); + std::weak_ptr wpt = m_ses.find_torrent(ih); + std::shared_ptr t = wpt.lock(); + + if (t && t->is_aborted()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ATTACH", "the torrent has been aborted"); +#endif + t.reset(); + } + + if (!t) + { + t = m_ses.delay_load_torrent(ih, this); +#ifndef TORRENT_DISABLE_LOGGING + if (t && should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ATTACH" + , "Delay loaded torrent: %s:", aux::to_hex(ih).c_str()); + } +#endif + } + + if (!t) + { + // we couldn't find the torrent! +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ATTACH" + , "couldn't find a torrent with the given info_hash: %s torrents:" + , aux::to_hex(ih).c_str()); + } +#endif + +#ifndef TORRENT_DISABLE_DHT + if (dht::verify_secret_id(ih)) + { + // this means the hash was generated from our generate_secret_id() + // as part of DHT traffic. The fact that we got an incoming + // connection on this info-hash, means the other end, making this + // connection fished it out of the DHT chatter. That's suspicious. + m_ses.ban_ip(m_remote.address()); + } +#endif + disconnect(errors::invalid_info_hash, operation_t::bittorrent, failure); + return; + } + + if (t->is_paused() + && t->is_auto_managed() + && m_settings.get_bool(settings_pack::incoming_starts_queued_torrents) + && !t->is_aborted()) + { + t->resume(); + } + + if (t->is_paused() || t->is_aborted() || t->graceful_pause()) + { + // paused torrents will not accept + // incoming connections unless they are auto managed + // and incoming_starts_queued_torrents is true + // torrents that have errors should always reject + // incoming peers +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ATTACH", "rejected connection to paused torrent"); +#endif + disconnect(errors::torrent_paused, operation_t::bittorrent, peer_error); + return; + } + +#if TORRENT_USE_I2P + auto* i2ps = m_socket->get(); + if (!i2ps && t->torrent_file().is_i2p() + && !m_settings.get_bool(settings_pack::allow_i2p_mixed)) + { + // the torrent is an i2p torrent, the peer is a regular peer + // and we don't allow mixed mode. Disconnect the peer. +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ATTACH", "rejected regular connection to i2p torrent"); +#endif + disconnect(errors::peer_banned, operation_t::bittorrent, peer_error); + return; + } +#endif // TORRENT_USE_I2P + + TORRENT_ASSERT(m_torrent.expired()); + + // check to make sure we don't have another connection with the same + // info_hash and peer_id. If we do. close this connection. + t->attach_peer(this); + if (m_disconnecting) return; + // it's important to assign the torrent after successfully attaching. + // if the peer disconnects while attaching, it's not a proper member + // of the torrent and peer_connection::disconnect() will fail if it + // think it is + m_torrent = t; + + if (m_exceeded_limit) + { + // find a peer in some torrent (presumably the one with most peers) + // and disconnect the lowest ranking peer + std::weak_ptr torr = m_ses.find_disconnect_candidate_torrent(); + std::shared_ptr other_t = torr.lock(); + + if (other_t) + { + if (other_t->num_peers() <= t->num_peers()) + { + disconnect(errors::too_many_connections, operation_t::bittorrent); + return; + } + // find the lowest ranking peer and disconnect that + peer_connection* p = other_t->find_lowest_ranking_peer(); + if (p != nullptr) + { + p->disconnect(errors::too_many_connections, operation_t::bittorrent); + peer_disconnected_other(); + } + else + { + disconnect(errors::too_many_connections, operation_t::bittorrent); + return; + } + } + else + { + disconnect(errors::too_many_connections, operation_t::bittorrent); + return; + } + } + + TORRENT_ASSERT(!m_torrent.expired()); + + // if the torrent isn't ready to accept + // connections yet, we'll have to wait with + // our initialization + if (t->ready_for_connections()) init(); + + TORRENT_ASSERT(!m_torrent.expired()); + + // assume the other end has no pieces + // if we don't have valid metadata yet, + // leave the vector unallocated + TORRENT_ASSERT(m_num_pieces == 0); + m_have_piece.clear_all(); + TORRENT_ASSERT(!m_torrent.expired()); + } + + std::uint32_t peer_connection::peer_rank() const + { + TORRENT_ASSERT(is_single_thread()); + return m_peer_info == nullptr ? 0 + : m_peer_info->rank(m_ses.external_address(), m_ses.listen_port()); + } + + // message handlers + + // ----------------------------- + // --------- KEEPALIVE --------- + // ----------------------------- + + void peer_connection::incoming_keepalive() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "KEEPALIVE"); +#endif + } + + // ----------------------------- + // ----------- CHOKE ----------- + // ----------------------------- + + void peer_connection::set_endgame(bool b) + { + TORRENT_ASSERT(is_single_thread()); + if (m_endgame_mode == b) return; + m_endgame_mode = b; + if (m_endgame_mode) + m_counters.inc_stats_counter(counters::num_peers_end_game); + else + m_counters.inc_stats_counter(counters::num_peers_end_game, -1); + } + + void peer_connection::incoming_choke() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_choke()) return; + } +#endif + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "CHOKE"); +#endif + if (m_peer_choked == false) + m_counters.inc_stats_counter(counters::num_peers_down_unchoked, -1); + + m_peer_choked = true; + set_endgame(false); + + clear_request_queue(); + } + + void peer_connection::clear_request_queue() + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + if (!t->has_picker()) + { + m_request_queue.clear(); + return; + } + + // clear the requests that haven't been sent yet + if (peer_info_struct() == nullptr || !peer_info_struct()->on_parole) + { + // if the peer is not in parole mode, clear the queued + // up block requests + piece_picker& p = t->picker(); + for (auto const& r : m_request_queue) + { + p.abort_download(r.block, peer_info_struct()); + } + m_request_queue.clear(); + m_queued_time_critical = 0; + } + } + + void peer_connection::clear_download_queue() + { + std::shared_ptr t = m_torrent.lock(); + piece_picker& picker = t->picker(); + torrent_peer* self_peer = peer_info_struct(); + while (!m_download_queue.empty()) + { + pending_block& qe = m_download_queue.back(); + if (!qe.timed_out && !qe.not_wanted) + picker.abort_download(qe.block, self_peer); + m_outstanding_bytes -= t->to_req(qe.block).length; + if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; + m_download_queue.pop_back(); + } + } + + // ----------------------------- + // -------- REJECT PIECE ------- + // ----------------------------- + + void peer_connection::incoming_reject_request(peer_request const& r) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "REJECT_PIECE", "piece: %d s: %x l: %x" + , static_cast(r.piece), r.start, r.length); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_reject(r)) return; + } +#endif + + if (is_disconnecting()) return; + + int const block_size = t->block_size(); + if (r.piece < piece_index_t{} + || r.piece >= t->torrent_file().files().end_piece() + || r.start < 0 + || r.start >= t->torrent_file().piece_length() + || (r.start % block_size) != 0 + || r.length != std::min(t->torrent_file().piece_size(r.piece) - r.start, block_size)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "REJECT_PIECE", "invalid reject message (%d, %d, %d)" + , int(r.piece), int(r.start), int(r.length)); +#endif + return; + } + + auto const dlq_iter = std::find_if( + m_download_queue.begin(), m_download_queue.end() + , [&r, block_size](pending_block const& pb) + { + auto const& b = pb.block; + if (b.piece_index != r.piece) return false; + if (b.block_index != r.start / block_size) return false; + return true; + }); + + if (dlq_iter != m_download_queue.end()) + { + pending_block const b = *dlq_iter; + bool const remove_from_picker = !dlq_iter->timed_out && !dlq_iter->not_wanted; + m_download_queue.erase(dlq_iter); + TORRENT_ASSERT(m_outstanding_bytes >= r.length); + m_outstanding_bytes -= r.length; + if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; + + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests, -1); + + // if the peer is in parole mode, keep the request + if (peer_info_struct() && peer_info_struct()->on_parole) + { + // we should only add it if the block is marked as + // busy in the piece-picker + if (remove_from_picker) + m_request_queue.insert(m_request_queue.begin(), b); + } + else if (!t->is_seed() && remove_from_picker) + { + piece_picker& p = t->picker(); + p.abort_download(b.block, peer_info_struct()); + } +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } +#ifndef TORRENT_DISABLE_LOGGING + else + { + peer_log(peer_log_alert::info, "REJECT_PIECE", "piece not in request queue (%d, %d, %d)" + , int(r.piece), int(r.start), int(r.length)); + } +#endif + if (has_peer_choked()) + { + // if we're choked and we got a rejection of + // a piece in the allowed fast set, remove it + // from the allow fast set. + auto const i = std::find(m_allowed_fast.begin(), m_allowed_fast.end(), r.piece); + if (i != m_allowed_fast.end()) m_allowed_fast.erase(i); + } + else + { + auto const i = std::find(m_suggested_pieces.begin(), m_suggested_pieces.end(), r.piece); + if (i != m_suggested_pieces.end()) m_suggested_pieces.erase(i); + } + + check_graceful_pause(); + if (is_disconnecting()) return; + + if (m_request_queue.empty() && m_download_queue.size() < 2) + { + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::reject_piece_picks); + } + + send_block_requests(); + } + + // ----------------------------- + // ------- SUGGEST PIECE ------- + // ----------------------------- + + void peer_connection::incoming_suggest(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "SUGGEST_PIECE" + , "piece: %d", static_cast(index)); +#endif + std::shared_ptr t = m_torrent.lock(); + if (!t) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_suggest(index)) return; + } +#endif + + if (is_disconnecting()) return; + if (index < piece_index_t(0)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_SUGGEST_PIECE" + , "%d", static_cast(index)); +#endif + return; + } + + if (t->valid_metadata()) + { + if (index >= m_have_piece.end_index()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_SUGGEST" + , "%d s: %d", static_cast(index), m_have_piece.size()); +#endif + return; + } + + // if we already have the piece, we can + // ignore this message + if (t->have_piece(index)) + return; + } + + // the piece picker will prioritize the pieces from the beginning to end. + // the later the suggestion is received, the higher priority we should + // ascribe to it, so we need to insert suggestions at the front of the + // queue. + if (m_suggested_pieces.end_index() > m_settings.get_int(settings_pack::max_suggest_pieces)) + m_suggested_pieces.resize(m_settings.get_int(settings_pack::max_suggest_pieces) - 1); + + m_suggested_pieces.insert(m_suggested_pieces.begin(), index); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SUGGEST_PIECE", "piece: %d added to set: %d" + , static_cast(index), m_suggested_pieces.end_index()); +#endif + } + + // ----------------------------- + // ---------- UNCHOKE ---------- + // ----------------------------- + + void peer_connection::incoming_unchoke() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_unchoke()) return; + } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "UNCHOKE"); +#endif + if (m_peer_choked) + m_counters.inc_stats_counter(counters::num_peers_down_unchoked); + + m_peer_choked = false; + m_last_unchoked = aux::time_now(); + if (is_disconnecting()) return; + + if (is_interesting()) + { + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::unchoke_piece_picks); + send_block_requests(); + } + } + + // ----------------------------- + // -------- INTERESTED --------- + // ----------------------------- + + void peer_connection::incoming_interested() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_interested()) return; + } +#endif + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INTERESTED"); +#endif + if (m_peer_interested == false) + m_counters.inc_stats_counter(counters::num_peers_up_interested); + + m_peer_interested = true; + if (is_disconnecting()) return; + + // if the peer is ready to download stuff, it must have metadata + m_has_metadata = true; + + disconnect_if_redundant(); + if (is_disconnecting()) return; + + if (t->graceful_pause()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UNCHOKE" + , "did not unchoke, graceful pause mode"); +#endif + return; + } + + if (!is_choked()) + { + // the reason to send an extra unchoke message here is that + // because of the handshake-round-trip optimization, we may + // end up sending an unchoke before the other end sends us + // an interested message. This may confuse clients, not reacting + // to the first unchoke, and then not check whether it's unchoked + // when sending the interested message. If the other end's client + // has this problem, sending another unchoke here will kick it + // to react to the fact that it's unchoked. +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UNCHOKE", "sending redundant unchoke"); +#endif + write_unchoke(); + return; + } + + maybe_unchoke_this_peer(); + } + + void peer_connection::maybe_unchoke_this_peer() + { + TORRENT_ASSERT(is_single_thread()); + if (ignore_unchoke_slots()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UNCHOKE", "about to unchoke, peer ignores unchoke slots"); +#endif + // if this peer is exempted from the choker + // just unchoke it immediately + send_unchoke(); + } + else if (m_ses.preemptive_unchoke()) + { + // if the peer is choked and we have upload slots left, + // then unchoke it. + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + t->unchoke_peer(*this); + } +#ifndef TORRENT_DISABLE_LOGGING + else if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "UNCHOKE", "did not unchoke, the number of uploads (%d) " + "is more than or equal to the available slots (%d), limit (%d)" + , int(m_counters[counters::num_peers_up_unchoked]) + , int(m_counters[counters::num_unchoke_slots]) + , m_settings.get_int(settings_pack::unchoke_slots_limit)); + } +#endif + } + + // ----------------------------- + // ------ NOT INTERESTED ------- + // ----------------------------- + + void peer_connection::incoming_not_interested() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_not_interested()) return; + } +#endif + + m_became_uninterested = aux::time_now(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "NOT_INTERESTED"); +#endif + if (m_peer_interested) + m_counters.inc_stats_counter(counters::num_peers_up_interested, -1); + + m_peer_interested = false; + if (is_disconnecting()) return; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + choke_this_peer(); + } + + void peer_connection::choke_this_peer() + { + TORRENT_ASSERT(is_single_thread()); + if (is_choked()) return; + if (ignore_unchoke_slots()) + { + send_choke(); + return; + } + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (m_peer_info && m_peer_info->optimistically_unchoked) + { + m_peer_info->optimistically_unchoked = false; + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_optimistic, -1); + t->trigger_optimistic_unchoke(); + } + t->choke_peer(*this); + t->trigger_unchoke(); + } + + // ----------------------------- + // ----------- HAVE ------------ + // ----------------------------- + + void peer_connection::incoming_have(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_have(index)) return; + } +#endif + + if (is_disconnecting()) return; + + // if we haven't received a bitfield, it was + // probably omitted, which is the same as 'have_none' + if (!m_bitfield_received) incoming_have_none(); + + // if this peer is choked, there's no point in sending suggest messages to + // it. They would just be out-of-date by the time we unchoke the peer + // anyway. + if (m_settings.get_int(settings_pack::suggest_mode) == settings_pack::suggest_read_cache + && !is_choked() + && std::any_of(m_suggest_pieces.begin(), m_suggest_pieces.end() + , [=](piece_index_t const idx) { return idx == index; })) + { + send_piece_suggestions(2); + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HAVE", "piece: %d" + , static_cast(index)); +#endif + + if (is_disconnecting()) return; + + if (!t->valid_metadata() && index >= m_have_piece.end_index()) + { + if (index < piece_index_t(0x200000)) + { + // if we don't have metadata + // and we might not have received a bitfield + // extend the bitmask to fit the new + // have message + m_have_piece.resize(static_cast(index) + 1, false); + } + else + { + // unless the index > 64k, in which case + // we just ignore it + return; + } + } + + // if we got an invalid message, abort + if (index >= m_have_piece.end_index() || index < piece_index_t(0)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR", "have-metadata have_piece: %d size: %d" + , static_cast(index), m_have_piece.size()); +#endif + disconnect(errors::invalid_have, operation_t::bittorrent, peer_error); + return; + } + +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (t->super_seeding() +#if TORRENT_ABI_VERSION == 1 + && !m_settings.get_bool(settings_pack::strict_super_seeding) +#endif + ) + { + // if we're super-seeding and the peer just told + // us that it completed the piece we're super-seeding + // to it, change the super-seeding piece for this peer + // if the peer optimizes out redundant have messages + // this will be handled when the peer sends not-interested + // instead. + if (super_seeded_piece(index)) + { + superseed_piece(index, t->get_piece_to_super_seed(m_have_piece)); + } + } +#endif + + if (m_have_piece[index]) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "HAVE" + , "got redundant HAVE message for index: %d" + , static_cast(index)); +#endif + return; + } + + m_have_piece.set_bit(index); + ++m_num_pieces; + + // if the peer is downloading stuff, it must have metadata + m_has_metadata = true; + + // only update the piece_picker if + // we have the metadata and if + // we're not a seed (in which case + // we won't have a piece picker) + if (!t->valid_metadata()) return; + + t->peer_has(index, this); + + // it's important to not disconnect before we have + // updated the piece picker, otherwise we will incorrectly + // decrement the piece count without first incrementing it + if (is_seed()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED", "this is a seed. p: %p" + , static_cast(m_peer_info)); +#endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + + t->seen_complete(); + t->set_seed(m_peer_info, true); + m_upload_only = true; + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + if (disconnect_if_redundant()) return; + } + + // it's important to update whether we're interested in this peer before + // calling disconnect_if_redundant, otherwise we may disconnect even if + // we are interested + if (!t->has_piece_passed(index) + && !t->is_upload_only() + && !is_interesting() + && (!t->has_picker() || t->picker().piece_priority(index) != dont_download)) + t->peer_is_interesting(*this); + + disconnect_if_redundant(); + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_SUPERSEEDING +#if TORRENT_ABI_VERSION == 1 + // if we're super seeding, this might mean that somebody + // forwarded this piece. In which case we need to give + // a new piece to that peer + if (t->super_seeding() + && m_settings.get_bool(settings_pack::strict_super_seeding) + && (!super_seeded_piece(index) || t->num_peers() == 1)) + { + for (auto& p : *t) + { + if (!p->super_seeded_piece(index)) continue; + if (!p->has_piece(index)) continue; + p->superseed_piece(index, t->get_piece_to_super_seed(p->get_bitfield())); + } + } +#endif // TORRENT_ABI_VERSION +#endif // TORRENT_DISABLE_SUPERSEEDING + } + + // ----------------------------- + // -------- DONT HAVE ---------- + // ----------------------------- + + void peer_connection::incoming_dont_have(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (index < piece_index_t{} + || index >= t->torrent_file().end_piece()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "DONT_HAVE" + , "invalid piece: %d", static_cast(index)); +#endif + return; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_dont_have(index)) return; + } +#endif + + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "DONT_HAVE", "piece: %d" + , static_cast(index)); +#endif + + // if we got an invalid message, abort + if (index >= m_have_piece.end_index() || index < piece_index_t(0)) + { + disconnect(errors::invalid_dont_have, operation_t::bittorrent, peer_error); + return; + } + + if (!m_have_piece[index]) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "DONT_HAVE" + , "got redundant DONT_HAVE message for index: %d" + , static_cast(index)); +#endif + return; + } + + bool was_seed = is_seed(); + m_have_piece.clear_bit(index); + TORRENT_ASSERT(m_num_pieces > 0); + --m_num_pieces; + + // only update the piece_picker if + // we have the metadata and if + // we're not a seed (in which case + // we won't have a piece picker) + if (!t->valid_metadata()) return; + + t->peer_lost(index, this); + + if (was_seed) + t->set_seed(m_peer_info, false); + } + + // ----------------------------- + // --------- BITFIELD ---------- + // ----------------------------- + + void peer_connection::incoming_bitfield(typed_bitfield const& bits) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_bitfield(bits)) return; + } +#endif + + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + std::string bitfield_str; + bitfield_str.resize(aux::numeric_cast(bits.size())); + for (auto const i : bits.range()) + bitfield_str[std::size_t(static_cast(i))] = bits[i] ? '1' : '0'; + peer_log(peer_log_alert::incoming_message, "BITFIELD" + , "%s", bitfield_str.c_str()); + } +#endif + + // if we don't have the metadata, we cannot + // verify the bitfield size + if (t->valid_metadata() + && bits.size() != m_have_piece.size()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "BITFIELD" + , "invalid size: %d expected %d", bits.size() + , m_have_piece.size()); + } +#endif + disconnect(errors::invalid_bitfield_size, operation_t::bittorrent, peer_error); + return; + } + + if (m_bitfield_received) + { + // if we've already received a bitfield message + // we first need to count down all the pieces + // we believe the peer has first + t->peer_lost(m_have_piece, this); + } + + m_bitfield_received = true; + + // if we don't have metadata yet + // just remember the bitmask + // don't update the piecepicker + // (since it doesn't exist yet) + if (!t->ready_for_connections()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_num_pieces == bits.size()) + peer_log(peer_log_alert::info, "SEED", "this is a seed. p: %p" + , static_cast(m_peer_info)); +#endif + m_have_piece = bits; + m_num_pieces = bits.count(); + t->set_seed(m_peer_info, m_num_pieces == bits.size()); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + return; + } + + TORRENT_ASSERT(t->valid_metadata()); + + int num_pieces = bits.count(); + if (num_pieces == m_have_piece.size()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED", "this is a seed. p: %p" + , static_cast(m_peer_info)); +#endif + + t->set_seed(m_peer_info, true); + m_upload_only = true; + + m_have_piece.set_all(); + m_num_pieces = num_pieces; + t->peer_has_all(this); + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + + // this will cause us to send the INTERESTED message + if (!t->is_upload_only()) + t->peer_is_interesting(*this); + + disconnect_if_redundant(); + + return; + } + + // let the torrent know which pieces the peer has if we're a seed, we + // don't keep track of piece availability + t->peer_has(bits, this); + + m_have_piece = bits; + m_num_pieces = num_pieces; + + update_interest(); + } + + bool peer_connection::disconnect_if_redundant() + { + TORRENT_ASSERT(is_single_thread()); + if (m_disconnecting) return false; + if (m_need_interest_update) return false; + + // we cannot disconnect in a constructor + TORRENT_ASSERT(m_in_constructor == false); + if (!m_settings.get_bool(settings_pack::close_redundant_connections)) return false; + + std::shared_ptr t = m_torrent.lock(); + if (!t) return false; + + // if we don't have the metadata yet, don't disconnect + // also, if the peer doesn't have metadata we shouldn't + // disconnect it, since it may want to request the + // metadata from us + if (!t->valid_metadata() || !has_metadata()) return false; + +#ifndef TORRENT_DISABLE_SHARE_MODE + // don't close connections in share mode, we don't know if we need them + if (t->share_mode()) return false; +#endif + + if (m_upload_only && t->is_upload_only() + && can_disconnect(errors::upload_upload_connection)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UPLOAD_ONLY", "the peer is upload-only and our torrent is also upload-only"); +#endif + disconnect(errors::upload_upload_connection, operation_t::bittorrent); + return true; + } + + if (m_upload_only + && !m_interesting + && m_bitfield_received + && t->are_files_checked() + && can_disconnect(errors::uninteresting_upload_peer)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "UPLOAD_ONLY", "the peer is upload-only and we're not interested in it"); +#endif + disconnect(errors::uninteresting_upload_peer, operation_t::bittorrent); + return true; + } + + return false; + } + + bool peer_connection::can_disconnect(error_code const& ec) const + { + TORRENT_ASSERT(is_single_thread()); +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (!e->can_disconnect(ec)) return false; + } +#else + TORRENT_UNUSED(ec); +#endif + return true; + } + + // ----------------------------- + // ---------- REQUEST ---------- + // ----------------------------- + + void peer_connection::incoming_request(peer_request const& r) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + torrent_info const& ti = t->torrent_file(); + + m_counters.inc_stats_counter(counters::piece_requests); + +#ifndef TORRENT_DISABLE_LOGGING + const bool valid_piece_index + = r.piece >= piece_index_t(0) + && r.piece < t->torrent_file().end_piece(); + + peer_log(peer_log_alert::incoming_message, "REQUEST" + , "piece: %d s: %x l: %x", static_cast(r.piece), r.start, r.length); +#endif + +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (t->super_seeding() + && !super_seeded_piece(r.piece)) + { + m_counters.inc_stats_counter(counters::invalid_piece_requests); + ++m_num_invalid_requests; +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "INVALID_REQUEST", "piece not superseeded " + "i: %d t: %d n: %d h: %d ss1: %d ss2: %d" + , m_peer_interested + , valid_piece_index + ? t->torrent_file().piece_size(r.piece) : -1 + , t->torrent_file().num_pieces() + , valid_piece_index ? t->has_piece_passed(r.piece) : 0 + , static_cast(m_superseed_piece[0]) + , static_cast(m_superseed_piece[1])); + } +#endif + + write_reject_request(r); + + if (t->alerts().should_post()) + { + // msvc 12 appears to deduce the rvalue reference template + // incorrectly for bool temporaries. So, create a dummy instance + bool const peer_interested = bool(m_peer_interested); + t->alerts().emplace_alert( + t->get_handle(), m_remote, m_peer_id, r + , t->has_piece_passed(r.piece), peer_interested, true); + } + return; + } +#endif // TORRENT_DISABLE_SUPERSEEDING + + // if we haven't received a bitfield, it was + // probably omitted, which is the same as 'have_none' + if (!m_bitfield_received) incoming_have_none(); + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_request(r)) return; + } + if (is_disconnecting()) return; +#endif + + if (!t->valid_metadata()) + { + m_counters.inc_stats_counter(counters::invalid_piece_requests); + // if we don't have valid metadata yet, + // we shouldn't get a request +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "we don't have metadata yet"); + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %x l: %x no metadata" + , static_cast(r.piece), r.start, r.length); +#endif + write_reject_request(r); + return; + } + + if (int(m_requests.size()) > m_settings.get_int(settings_pack::max_allowed_in_request_queue)) + { + m_counters.inc_stats_counter(counters::max_piece_requests); + // don't allow clients to abuse our + // memory consumption. + // ignore requests if the client + // is making too many of them. +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "incoming request queue full %d" + , int(m_requests.size())); + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %x l: %x too many requests" + , static_cast(r.piece), r.start, r.length); +#endif + write_reject_request(r); + return; + } + + int fast_idx = -1; + auto const fast_iter = std::find(m_accept_fast.begin() + , m_accept_fast.end(), r.piece); + if (fast_iter != m_accept_fast.end()) fast_idx = int(fast_iter - m_accept_fast.begin()); + + if (!m_peer_interested) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "INVALID_REQUEST", "peer is not interested " + " t: %d n: %d block_limit: %d" + , valid_piece_index + ? t->torrent_file().piece_size(r.piece) : -1 + , t->torrent_file().num_pieces() + , t->block_size()); + peer_log(peer_log_alert::info, "INTERESTED", "artificial incoming INTERESTED message"); + } +#endif + if (t->alerts().should_post()) + { + t->alerts().emplace_alert( + t->get_handle(), m_remote, m_peer_id, r + , t->has_piece_passed(r.piece) + , false, false); + } + + // be lenient and pretend that the peer said it was interested + incoming_interested(); + } + + // make sure this request + // is legal and that the peer + // is not choked + if (r.piece < piece_index_t(0) + || r.piece >= t->torrent_file().end_piece() + || (!t->has_piece_passed(r.piece) +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + && !t->is_predictive_piece(r.piece) +#endif + && !t->seed_mode()) + || r.start < 0 + || r.start >= ti.piece_size(r.piece) + || r.length <= 0 + || r.length + r.start > ti.piece_size(r.piece) + || r.length > t->block_size()) + { + m_counters.inc_stats_counter(counters::invalid_piece_requests); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "INVALID_REQUEST" + , "i: %d t: %d n: %d h: %d block_limit: %d" + , m_peer_interested + , valid_piece_index + ? t->torrent_file().piece_size(r.piece) : -1 + , ti.num_pieces() + , t->has_piece_passed(r.piece) + , t->block_size()); + } + + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %d l: %d invalid request" + , static_cast(r.piece), r.start , r.length); +#endif + + write_reject_request(r); + ++m_num_invalid_requests; + + if (t->alerts().should_post()) + { + // msvc 12 appears to deduce the rvalue reference template + // incorrectly for bool temporaries. So, create a dummy instance + bool const peer_interested = bool(m_peer_interested); + t->alerts().emplace_alert( + t->get_handle(), m_remote, m_peer_id, r + , t->has_piece_passed(r.piece), peer_interested, false); + } + + // every ten invalid request, remind the peer that it's choked + if (!m_peer_interested && m_num_invalid_requests % 10 == 0 && m_choked) + { + // TODO: 2 this should probably be based on time instead of number + // of request messages. For a very high throughput connection, 300 + // may be a legitimate number of requests to have in flight when + // getting choked + if (m_num_invalid_requests > 300 && !m_peer_choked + && can_disconnect(errors::too_many_requests_when_choked)) + { + disconnect(errors::too_many_requests_when_choked, operation_t::bittorrent, peer_error); + return; + } +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "CHOKE"); +#endif + write_choke(); + } + + return; + } + + // if we have choked the client + // ignore the request + int const blocks_per_piece = + (ti.piece_length() + t->block_size() - 1) / t->block_size(); + + // disconnect peers that downloads more than foo times an allowed + // fast piece + if (m_choked && fast_idx != -1 && m_accept_fast_piece_cnt[fast_idx] >= 3 * blocks_per_piece + && can_disconnect(errors::too_many_requests_when_choked)) + { + disconnect(errors::too_many_requests_when_choked, operation_t::bittorrent, peer_error); + return; + } + + if (m_choked && fast_idx == -1) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "REJECTING REQUEST", "peer choked and piece not in allowed fast set"); + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %d l: %d peer choked" + , static_cast(r.piece), r.start, r.length); +#endif + m_counters.inc_stats_counter(counters::choked_piece_requests); + write_reject_request(r); + + // allow peers to send request up to 2 seconds after getting choked, + // then disconnect them + if (aux::time_now() - seconds(2) > m_last_choke + && can_disconnect(errors::too_many_requests_when_choked)) + { + disconnect(errors::too_many_requests_when_choked, operation_t::bittorrent, peer_error); + return; + } + } + else + { + // increase the allowed fast set counter + if (fast_idx != -1) + ++m_accept_fast_piece_cnt[fast_idx]; + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests); + + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(r.piece >= piece_index_t(0)); + TORRENT_ASSERT(r.piece < t->torrent_file().end_piece()); + + m_requests.push_back(r); + + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(r, t->get_handle() + , m_remote, m_peer_id); + } + + m_last_incoming_request = aux::time_now(); + fill_send_buffer(); + } + } + + // reject all requests to this piece + void peer_connection::reject_piece(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + for (auto i = m_requests.begin(), end(m_requests.end()); i != end; ++i) + { + peer_request const& r = *i; + if (r.piece != index) continue; + write_reject_request(r); + i = m_requests.erase(i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + } + } + + void peer_connection::incoming_piece_fragment(int const bytes) + { + TORRENT_ASSERT(is_single_thread()); + m_last_piece = aux::time_now(); + TORRENT_ASSERT(m_outstanding_bytes >= bytes); + m_outstanding_bytes -= bytes; + if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; + std::shared_ptr t = associated_torrent().lock(); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_received_in_piece + bytes <= t->block_size()); + m_received_in_piece += bytes; +#endif + + // progress of this torrent increased + t->state_updated(); + +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } + + void peer_connection::start_receive_piece(peer_request const& r) + { + TORRENT_ASSERT(is_single_thread()); +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif +#if TORRENT_USE_ASSERTS + span recv_buffer = m_recv_buffer.get(); + int recv_pos = int(recv_buffer.end() - recv_buffer.begin()); + TORRENT_ASSERT(recv_pos >= 9); +#endif + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + if (!verify_piece(r)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_PIECE", "piece: %d s: %d l: %d" + , static_cast(r.piece), r.start, r.length); +#endif + disconnect(errors::invalid_piece, operation_t::bittorrent, peer_error); + return; + } + + piece_block const b(r.piece, r.start / t->block_size()); + m_receiving_block = b; + + bool in_req_queue = false; + for (auto const& pb : m_download_queue) + { + if (pb.block != b) continue; + in_req_queue = true; + break; + } + + // if this is not in the request queue, we have to + // assume our outstanding bytes includes this piece too + // if we're disconnecting, we shouldn't add pieces + if (!in_req_queue && !m_disconnecting) + { + for (auto i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) + { + if (i->block != b) continue; + in_req_queue = true; + if (i - m_request_queue.begin() < m_queued_time_critical) + --m_queued_time_critical; + m_request_queue.erase(i); + break; + } + + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests); + + m_download_queue.insert(m_download_queue.begin(), b); + if (!in_req_queue) + { + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , m_remote, m_peer_id, b.block_index, b.piece_index); + } +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST" + , "The block we just got was not in the request queue"); +#endif + TORRENT_ASSERT(m_download_queue.front().block == b); + m_download_queue.front().not_wanted = true; + } + m_outstanding_bytes += r.length; + } + } + +#if TORRENT_USE_INVARIANT_CHECKS + struct check_postcondition + { + explicit check_postcondition(std::shared_ptr const& t_ + , bool init_check = true): t(t_) { if (init_check) check(); } + + ~check_postcondition() { check(); } + + void check() + { + if (!t->is_seed()) + { + const int blocks_per_piece = static_cast( + (t->torrent_file().piece_length() + t->block_size() - 1) / t->block_size()); + + std::vector const& dl_queue + = t->picker().get_download_queue(); + + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + TORRENT_ASSERT(i->finished <= blocks_per_piece); + } + } + } + + std::shared_ptr t; + }; +#endif + + + // ----------------------------- + // ----------- PIECE ----------- + // ----------------------------- + + void peer_connection::incoming_piece(peer_request const& p, char const* data) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + // we're not receiving any block right now + m_receiving_block = piece_block::invalid; + +#ifdef TORRENT_CORRUPT_DATA + // corrupt all pieces from certain peers + if (is_v4(m_remote) + && (m_remote.address().to_v4().to_ulong() & 0xf) == 0) + { + data[0] = ~data[0]; + } +#endif + + // if we haven't received a bitfield, it was + // probably omitted, which is the same as 'have_none' + if (!m_bitfield_received) incoming_have_none(); + if (is_disconnecting()) return; + + // slow-start + if (m_slow_start) + m_desired_queue_size += 1; + + update_desired_queue_size(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_piece(p, {data, p.length})) + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_received_in_piece == p.length); + m_received_in_piece = 0; +#endif + return; + } + } +#endif + if (is_disconnecting()) return; + +#if TORRENT_USE_INVARIANT_CHECKS + check_postcondition post_checker_(t); +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + t->check_invariant(); +#endif +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming_message)) + { + peer_log(peer_log_alert::incoming_message, "PIECE", "piece: %d s: %x l: %x ds: %d qs: %d q: %d" + , static_cast(p.piece), p.start, p.length, statistics().download_rate() + , int(m_desired_queue_size), int(m_download_queue.size())); + } +#endif + + if (p.length == 0) + { + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle(), m_remote + , m_peer_id, operation_t::bittorrent, errors::peer_sent_empty_piece); + } + // This is used as a reject-request by bitcomet + incoming_reject_request(p); + return; + } + + // if we're already seeding, don't bother, + // just ignore it + if (t->is_seed()) + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_received_in_piece == p.length); + m_received_in_piece = 0; +#endif + if (!m_download_queue.empty()) + { + m_download_queue.erase(m_download_queue.begin()); + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests, -1); + } + t->add_redundant_bytes(p.length, waste_reason::piece_seed); + return; + } + + time_point const now = clock_type::now(); + + t->need_picker(); + + piece_picker& picker = t->picker(); + + piece_block block_finished(p.piece, p.start / t->block_size()); + TORRENT_ASSERT(verify_piece(p)); + + auto const b = std::find_if(m_download_queue.begin() + , m_download_queue.end(), aux::has_block(block_finished)); + + if (b == m_download_queue.end()) + { + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , m_remote, m_peer_id, block_finished.block_index + , block_finished.piece_index); + } +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_REQUEST", "The block we just got was not in the request queue"); +#endif +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT_VAL(m_received_in_piece == p.length, m_received_in_piece); + m_received_in_piece = 0; +#endif + t->add_redundant_bytes(p.length, waste_reason::piece_unknown); + + // the bytes of the piece we just completed have been deducted from + // m_outstanding_bytes as we received it, in incoming_piece_fragment. + // however, it now turns out the piece we received wasn't in the + // download queue, so we still have the same number of pieces in the + // download queue, which is why we need to add the bytes back. + m_outstanding_bytes += p.length; +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + return; + } + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT_VAL(m_received_in_piece == p.length, m_received_in_piece); + m_received_in_piece = 0; +#endif + // if the block we got is already finished, then ignore it + if (picker.is_downloaded(block_finished)) + { + waste_reason const reason + = (b->timed_out) ? waste_reason::piece_timed_out + : (b->not_wanted) ? waste_reason::piece_cancelled + : (b->busy) ? waste_reason::piece_end_game + : waste_reason::piece_unknown; + + t->add_redundant_bytes(p.length, reason); + + m_download_queue.erase(b); + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests, -1); + + if (m_disconnecting) return; + + m_request_time.add_sample(int(total_milliseconds(now - m_requested))); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "REQUEST_TIME", "%d +- %d ms" + , m_request_time.mean(), m_request_time.avg_deviation()); + } +#endif + + // we completed an incoming block, and there are still outstanding + // requests. The next block we expect to receive now has another + // timeout period until we time out. So, reset the timer. + if (!m_download_queue.empty()) + m_requested = now; + + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::incoming_redundant_piece_picks); + send_block_requests(); + return; + } + + // we received a request within the timeout, make sure this peer is + // not snubbed anymore + if (total_seconds(now - m_requested) < request_timeout() + && m_snubbed) + { + m_snubbed = false; + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , m_remote, m_peer_id); + } + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "FILE_ASYNC_WRITE", "piece: %d s: %x l: %x" + , static_cast(p.piece), p.start, p.length); +#endif + m_download_queue.erase(b); + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests, -1); + + if (t->is_deleted()) return; + + auto conn = self(); + bool const exceeded = m_disk_thread.async_write(t->storage(), p, data, self() + , [conn, p, t] (storage_error const& e) + { conn->wrap(&peer_connection::on_disk_write_complete, e, p, t); }); + + // every peer is entitled to have two disk blocks allocated at any given + // time, regardless of whether the cache size is exceeded or not. If this + // was not the case, when the cache size setting is very small, most peers + // would be blocked most of the time, because the disk cache would + // continuously be in exceeded state. Only rarely would it actually drop + // down to 0 and unblock all peers. + if (exceeded && m_outstanding_writing_bytes > 0) + { + if (!(m_channel_state[download_channel] & peer_info::bw_disk)) + m_counters.inc_stats_counter(counters::num_peers_down_disk); + m_channel_state[download_channel] |= peer_info::bw_disk; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "DISK", "exceeded disk buffer watermark"); +#endif + } + + std::int64_t const write_queue_size = m_counters.inc_stats_counter( + counters::queued_write_bytes, p.length); + m_outstanding_writing_bytes += p.length; + + std::int64_t const max_queue_size = m_settings.get_int( + settings_pack::max_queued_disk_bytes); + if (write_queue_size > max_queue_size + && write_queue_size - p.length < max_queue_size + && m_settings.get_int(settings_pack::cache_size) > 5 + && t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , performance_alert::too_high_disk_queue_limit); + } + + m_request_time.add_sample(int(total_milliseconds(now - m_requested))); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "REQUEST_TIME", "%d +- %d ms" + , m_request_time.mean(), m_request_time.avg_deviation()); + } +#endif + + // we completed an incoming block, and there are still outstanding + // requests. The next block we expect to receive now has another + // timeout period until we time out. So, reset the timer. + if (!m_download_queue.empty()) + m_requested = now; + + bool const was_finished = picker.is_piece_finished(p.piece); + // did we request this block from any other peers? + bool const multi = picker.num_peers(block_finished) > 1; +// std::fprintf(stderr, "peer_connection mark_as_writing peer: %p piece: %d block: %d\n" +// , peer_info_struct(), block_finished.piece_index, block_finished.block_index); + picker.mark_as_writing(block_finished, peer_info_struct()); + + TORRENT_ASSERT(picker.num_peers(block_finished) == 0); + // if we requested this block from other peers, cancel it now + if (multi) t->cancel_block(block_finished); + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + if (m_settings.get_int(settings_pack::predictive_piece_announce)) + { + piece_index_t const piece = block_finished.piece_index; + piece_picker::downloading_piece st; + t->picker().piece_info(piece, st); + + int const num_blocks = t->picker().blocks_in_piece(piece); + if (st.requested > 0 && st.writing + st.finished + st.requested == num_blocks) + { + std::vector d; + t->picker().get_downloaders(d, piece); + if (d.size() == 1) + { + // only make predictions if all remaining + // blocks are requested from the same peer + torrent_peer* peer = d[0]; + if (peer->connection) + { + // we have a connection. now, what is the current + // download rate from this peer, and how many blocks + // do we have left to download? + std::int64_t const rate = peer->connection->statistics().download_payload_rate(); + std::int64_t const bytes_left = std::int64_t(st.requested) * t->block_size(); + // the settings unit is milliseconds, so calculate the + // number of milliseconds worth of bytes left in the piece + if (rate > 1000 + && (bytes_left * 1000) / rate < m_settings.get_int(settings_pack::predictive_piece_announce)) + { + // we predict we will complete this piece very soon. + t->predicted_have_piece(piece, int((bytes_left * 1000) / rate)); + } + } + } + } + } +#endif // TORRENT_DISABLE_PREDICTIVE_PIECES + + TORRENT_ASSERT(picker.num_peers(block_finished) == 0); + +#if TORRENT_USE_INVARIANT_CHECKS \ + && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + t->check_invariant(); +#endif + +#if TORRENT_USE_ASSERTS + piece_picker::downloading_piece pi; + picker.piece_info(p.piece, pi); + int num_blocks = picker.blocks_in_piece(p.piece); + TORRENT_ASSERT(pi.writing + pi.finished + pi.requested <= num_blocks); + TORRENT_ASSERT(picker.is_piece_finished(p.piece) == (pi.writing + pi.finished == num_blocks)); +#endif + + // did we just finish the piece? + // this means all blocks are either written + // to disk or are in the disk write cache + if (picker.is_piece_finished(p.piece) && !was_finished) + { +#if TORRENT_USE_INVARIANT_CHECKS + check_postcondition post_checker2_(t, false); +#endif + t->verify_piece(p.piece); + } + + check_graceful_pause(); + + if (is_disconnecting()) return; + + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::incoming_piece_picks); + send_block_requests(); + } + + void peer_connection::check_graceful_pause() + { + // TODO: 3 instead of having to ask the torrent whether it's in graceful + // pause mode or not, the peers should keep that state (and the torrent + // should update them when it enters graceful pause). When a peer enters + // graceful pause mode, it should cancel all outstanding requests and + // clear its request queue. + std::shared_ptr t = m_torrent.lock(); + if (!t || !t->graceful_pause()) return; + + if (m_outstanding_bytes > 0) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "GRACEFUL_PAUSE", "NO MORE DOWNLOAD"); +#endif + disconnect(errors::torrent_paused, operation_t::bittorrent); + } + + void peer_connection::on_disk_write_complete(storage_error const& error + , peer_request const& p, std::shared_ptr t) + { + TORRENT_ASSERT(is_single_thread()); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "FILE_ASYNC_WRITE_COMPLETE", "piece: %d s: %x l: %x e: %s" + , static_cast(p.piece), p.start, p.length, error.ec.message().c_str()); + } +#endif + + m_counters.inc_stats_counter(counters::queued_write_bytes, -p.length); + m_outstanding_writing_bytes -= p.length; + + TORRENT_ASSERT(m_outstanding_writing_bytes >= 0); + + // every peer is entitled to allocate a disk buffer if it has no writes outstanding + // see the comment in incoming_piece + if (m_outstanding_writing_bytes == 0 + && m_channel_state[download_channel] & peer_info::bw_disk) + { + m_counters.inc_stats_counter(counters::num_peers_down_disk, -1); + m_channel_state[download_channel] &= ~peer_info::bw_disk; + } + + INVARIANT_CHECK; + + if (!t) + { + disconnect(error.ec, operation_t::file_write); + return; + } + + // in case the outstanding bytes just dropped down + // to allow to receive more data + setup_receive(); + + piece_block const block_finished(p.piece, p.start / t->block_size()); + + if (error) + { + // we failed to write the piece to disk tell the piece picker + // this will block any other peer from issuing requests + // to this piece, until we've cleared it. + if (error.ec == boost::asio::error::operation_aborted) + { + if (t->has_picker()) + t->picker().mark_as_canceled(block_finished, nullptr); + } + else + { + // if any other peer has a busy request to this block, we need + // to cancel it too + t->cancel_block(block_finished); + if (t->has_picker()) + t->picker().write_failed(block_finished); + + if (t->has_storage()) + { + // when this returns, all outstanding jobs to the + // piece are done, and we can restore it, allowing + // new requests to it + m_disk_thread.async_clear_piece(t->storage(), p.piece + , [t, block_finished] (piece_index_t pi) + { t->wrap(&torrent::on_piece_fail_sync, pi, block_finished); }); + } + else + { + // is m_abort true? if so, we should probably just + // exit this function early, no need to keep the picker + // state up-to-date, right? + t->on_piece_fail_sync(p.piece, block_finished); + } + } + t->update_gauge(); + // handle_disk_error may disconnect us + t->handle_disk_error("write", error, this, torrent::disk_class::write); + return; + } + + if (!t->has_picker()) return; + + piece_picker& picker = t->picker(); + + TORRENT_ASSERT(picker.num_peers(block_finished) == 0); + +// std::fprintf(stderr, "peer_connection mark_as_finished peer: %p piece: %d block: %d\n" +// , peer_info_struct(), block_finished.piece_index, block_finished.block_index); + picker.mark_as_finished(block_finished, peer_info_struct()); + + t->maybe_done_flushing(); + + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle(), + remote(), pid(), block_finished.block_index + , block_finished.piece_index); + } + + disconnect_if_redundant(); + + if (m_disconnecting) return; + +#if TORRENT_USE_ASSERTS + if (t->has_picker()) + { + auto const& q = picker.get_download_queue(); + + for (auto const& dp : q) + { + if (dp.index != block_finished.piece_index) continue; + auto const info = picker.blocks_for_piece(dp); + TORRENT_ASSERT(info[block_finished.block_index].state + == piece_picker::block_info::state_finished); + } + } +#endif + if (t->is_aborted()) return; + } + + // ----------------------------- + // ---------- CANCEL ----------- + // ----------------------------- + + void peer_connection::incoming_cancel(peer_request const& r) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_cancel(r)) return; + } +#endif + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "CANCEL" + , "piece: %d s: %x l: %x", static_cast(r.piece), r.start, r.length); +#endif + + auto const i = std::find(m_requests.begin(), m_requests.end(), r); + + if (i != m_requests.end()) + { + m_counters.inc_stats_counter(counters::cancelled_piece_requests); + m_requests.erase(i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE", "piece: %d s: %x l: %x cancelled" + , static_cast(r.piece), r.start , r.length); +#endif + write_reject_request(r); + } + else + { + // TODO: 2 since we throw away the queue entry once we issue + // the disk job, this may happen. Instead, we should keep the + // queue entry around, mark it as having been requested from + // disk and once the disk job comes back, discard it if it has + // been cancelled. Maybe even be able to cancel disk jobs? +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "INVALID_CANCEL", "got cancel not in the queue"); +#endif + } + } + + // ----------------------------- + // --------- DHT PORT ---------- + // ----------------------------- + + void peer_connection::incoming_dht_port(int const listen_port) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "DHT_PORT", "p: %d", listen_port); +#endif +#ifndef TORRENT_DISABLE_DHT + m_ses.add_dht_node({m_remote.address(), std::uint16_t(listen_port)}); +#else + TORRENT_UNUSED(listen_port); +#endif + } + + // ----------------------------- + // --------- HAVE ALL ---------- + // ----------------------------- + + void peer_connection::incoming_have_all() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + // we cannot disconnect in a constructor, and + // this function may end up doing that + TORRENT_ASSERT(m_in_constructor == false); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HAVE_ALL"); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_have_all()) return; + } +#endif + if (is_disconnecting()) return; + + if (m_bitfield_received) + t->peer_lost(m_have_piece, this); + + m_have_all = true; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED", "this is a seed p: %p" + , static_cast(m_peer_info)); +#endif + + t->set_seed(m_peer_info, true); + m_upload_only = true; + m_bitfield_received = true; + + // if we don't have metadata yet + // just remember the bitmask + // don't update the piecepicker + // (since it doesn't exist yet) + if (!t->ready_for_connections()) + { + // assume seeds are interesting when we + // don't even have the metadata + t->peer_is_interesting(*this); + + disconnect_if_redundant(); + return; + } + + TORRENT_ASSERT(!m_have_piece.empty()); + m_have_piece.set_all(); + m_num_pieces = m_have_piece.size(); + + t->peer_has_all(this); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + + // if we're finished, we're not interested + if (t->is_upload_only()) send_not_interested(); + else t->peer_is_interesting(*this); + + disconnect_if_redundant(); + } + + // ----------------------------- + // --------- HAVE NONE --------- + // ----------------------------- + + void peer_connection::incoming_have_none() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "HAVE_NONE"); +#endif + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_have_none()) return; + } +#endif + if (is_disconnecting()) return; + + if (m_bitfield_received) + t->peer_lost(m_have_piece, this); + + t->set_seed(m_peer_info, false); + m_bitfield_received = true; + + m_have_piece.clear_all(); + m_num_pieces = 0; + + // if the peer is ready to download stuff, it must have metadata + m_has_metadata = true; + + // we're never interested in a peer that doesn't have anything + send_not_interested(); + + TORRENT_ASSERT(!m_have_piece.empty() || !t->ready_for_connections()); + disconnect_if_redundant(); + } + + // ----------------------------- + // ------- ALLOWED FAST -------- + // ----------------------------- + + void peer_connection::incoming_allowed_fast(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "ALLOWED_FAST", "%d" + , static_cast(index)); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + if (e->on_allowed_fast(index)) return; + } +#endif + if (is_disconnecting()) return; + + if (index < piece_index_t(0)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_ALLOWED_FAST" + , "%d", static_cast(index)); +#endif + return; + } + + if (t->valid_metadata()) + { + if (index >= m_have_piece.end_index()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INVALID_ALLOWED_FAST" + , "%d s: %d", static_cast(index), m_have_piece.size()); +#endif + return; + } + + // if we already have the piece, we can + // ignore this message + if (t->have_piece(index)) + return; + } + + // if we don't have the metadata, we'll verify + // this piece index later + m_allowed_fast.push_back(index); + + // if the peer has the piece and we want + // to download it, request it + if (index < m_have_piece.end_index() + && m_have_piece[index] + && !t->has_piece_passed(index) + && t->valid_metadata() + && t->has_picker() + && t->picker().piece_priority(index) > dont_download) + { + t->peer_is_interesting(*this); + } + } + + std::vector const& peer_connection::allowed_fast() + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + // TODO: sort the allowed fast set in priority order + return m_allowed_fast; + } + + bool peer_connection::can_request_time_critical() const + { + TORRENT_ASSERT(is_single_thread()); + if (has_peer_choked() || !is_interesting()) return false; + if (int(m_download_queue.size()) + int(m_request_queue.size()) + > m_desired_queue_size * 2) return false; + if (on_parole()) return false; + if (m_disconnecting) return false; + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + if (t->upload_mode()) return false; + + // ignore snubbed peers, since they're not likely to return pieces in a + // timely manner anyway + if (m_snubbed) return false; + return true; + } + + bool peer_connection::make_time_critical(piece_block const& block) + { + TORRENT_ASSERT(is_single_thread()); + auto const rit = std::find_if(m_request_queue.begin() + , m_request_queue.end(), aux::has_block(block)); + if (rit == m_request_queue.end()) return false; +#if TORRENT_USE_ASSERTS + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->has_picker()); + TORRENT_ASSERT(t->picker().is_requested(block)); +#endif + // ignore it if it's already time critical + if (rit - m_request_queue.begin() < m_queued_time_critical) return false; + pending_block b = *rit; + m_request_queue.erase(rit); + m_request_queue.insert(m_request_queue.begin() + m_queued_time_critical, b); + ++m_queued_time_critical; + return true; + } + + bool peer_connection::add_request(piece_block const& block + , request_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + TORRENT_ASSERT(!m_disconnecting); + TORRENT_ASSERT(t->valid_metadata()); + + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < t->torrent_file().end_piece()); + TORRENT_ASSERT(block.block_index < t->torrent_file().piece_size(block.piece_index)); + TORRENT_ASSERT(!t->picker().is_requested(block) || (t->picker().num_peers(block) > 0)); + TORRENT_ASSERT(!t->have_piece(block.piece_index)); + TORRENT_ASSERT(std::find_if(m_download_queue.begin(), m_download_queue.end() + , aux::has_block(block)) == m_download_queue.end()); + TORRENT_ASSERT(std::find(m_request_queue.begin(), m_request_queue.end() + , block) == m_request_queue.end()); + + if (t->upload_mode()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d upload_mode" + , static_cast(block.piece_index), block.block_index); +#endif + return false; + } + if (m_disconnecting) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d disconnecting" + , static_cast(block.piece_index), block.block_index); +#endif + return false; + } + + if ((flags & busy) && !(flags & time_critical)) + { + // this block is busy (i.e. it has been requested + // from another peer already). Only allow one busy + // request in the pipeline at the time + // this rule does not apply to time critical pieces, + // in which case we are allowed to pick more than one + // busy blocks + if (std::any_of(m_download_queue.begin(), m_download_queue.end() + , [](pending_block const& i) { return i.busy; })) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d already in download queue & busy" + , static_cast(block.piece_index), block.block_index); +#endif + return false; + } + + if (std::any_of(m_request_queue.begin(), m_request_queue.end() + , [](pending_block const& i) { return i.busy; })) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d already in request queue & busy" + , static_cast(block.piece_index), block.block_index); +#endif + return false; + } + } + + if (!t->picker().mark_as_downloading(block, peer_info_struct() + , picker_options())) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d failed to mark_as_downloading" + , static_cast(block.piece_index), block.block_index); +#endif + return false; + } + + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , remote(), pid(), block.block_index, block.piece_index); + } + + pending_block pb(block); + pb.busy = (flags & busy) ? true : false; + if (flags & time_critical) + { + m_request_queue.insert(m_request_queue.begin() + m_queued_time_critical + , pb); + ++m_queued_time_critical; + } + else + { + m_request_queue.push_back(pb); + } + return true; + } + + void peer_connection::cancel_all_requests() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + // this peer might be disconnecting + if (!t) return; + + TORRENT_ASSERT(t->valid_metadata()); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CANCEL_ALL_REQUESTS"); +#endif + + while (!m_request_queue.empty()) + { + t->picker().abort_download(m_request_queue.back().block, peer_info_struct()); + m_request_queue.pop_back(); + } + m_queued_time_critical = 0; + + // make a local temporary copy of the download queue, since it + // may be modified when we call write_cancel (for peers that don't + // support the FAST extensions). + std::vector temp_copy = m_download_queue; + + for (auto const& pb : temp_copy) + { + piece_block const b = pb.block; + + int const block_offset = b.block_index * t->block_size(); + int const block_size + = std::min(t->torrent_file().piece_size(b.piece_index)-block_offset, + t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); + + // we can't cancel the piece if we've started receiving it + if (m_receiving_block == b) continue; + + peer_request r; + r.piece = b.piece_index; + r.start = block_offset; + r.length = block_size; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "CANCEL" + , "piece: %d s: %d l: %d b: %d" + , static_cast(b.piece_index), block_offset, block_size, b.block_index); +#endif + write_cancel(r); + } + } + + void peer_connection::cancel_request(piece_block const& block, bool const force) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + // this peer might be disconnecting + if (!t) return; + + TORRENT_ASSERT(t->valid_metadata()); + + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < t->torrent_file().end_piece()); + TORRENT_ASSERT(block.block_index < t->torrent_file().piece_size(block.piece_index)); + + // if all the peers that requested this block has been + // cancelled, then just ignore the cancel. + if (!t->picker().is_requested(block)) return; + + auto const it = std::find_if(m_download_queue.begin(), m_download_queue.end() + , aux::has_block(block)); + if (it == m_download_queue.end()) + { + auto const rit = std::find_if(m_request_queue.begin() + , m_request_queue.end(), aux::has_block(block)); + + // when a multi block is received, it is cancelled + // from all peers, so if this one hasn't requested + // the block, just ignore to cancel it. + if (rit == m_request_queue.end()) return; + + if (rit - m_request_queue.begin() < m_queued_time_critical) + --m_queued_time_critical; + + t->picker().abort_download(block, peer_info_struct()); + m_request_queue.erase(rit); + // since we found it in the request queue, it means it hasn't been + // sent yet, so we don't have to send a cancel. + return; + } + + int const block_offset = block.block_index * t->block_size(); + int const block_size + = std::min(t->torrent_file().piece_size(block.piece_index) - block_offset, + t->block_size()); + TORRENT_ASSERT(block_size > 0); + TORRENT_ASSERT(block_size <= t->block_size()); + + it->not_wanted = true; + + if (force) t->picker().abort_download(block, peer_info_struct()); + + if (m_outstanding_bytes < block_size) return; + + peer_request r; + r.piece = block.piece_index; + r.start = block_offset; + r.length = block_size; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "CANCEL" + , "piece: %d s: %d l: %d b: %d" + , static_cast(block.piece_index), block_offset, block_size, block.block_index); +#endif + write_cancel(r); + } + + bool peer_connection::send_choke() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(!is_connecting()); + + if (m_choked) + { + TORRENT_ASSERT(m_peer_info == nullptr + || m_peer_info->optimistically_unchoked == false); + return false; + } + + if (m_peer_info && m_peer_info->optimistically_unchoked) + { + m_peer_info->optimistically_unchoked = false; + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_optimistic, -1); + } + + m_suggest_pieces.clear(); + m_suggest_pieces.shrink_to_fit(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "CHOKE"); +#endif + write_choke(); + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_all, -1); + if (!ignore_unchoke_slots()) + m_counters.inc_stats_counter(counters::num_peers_up_unchoked, -1); + m_choked = true; + + m_last_choke = aux::time_now(); + m_num_invalid_requests = 0; + + // reject the requests we have in the queue + // except the allowed fast pieces + for (auto i = m_requests.begin(); i != m_requests.end();) + { + if (std::find(m_accept_fast.begin(), m_accept_fast.end(), i->piece) + != m_accept_fast.end()) + { + ++i; + continue; + } + peer_request const& r = *i; + m_counters.inc_stats_counter(counters::choked_piece_requests); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %d l: %d choking" + , static_cast(r.piece), r.start , r.length); +#endif + write_reject_request(r); + i = m_requests.erase(i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + } + return true; + } + + bool peer_connection::send_unchoke() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (!m_choked) return false; + std::shared_ptr t = m_torrent.lock(); + if (!t->ready_for_connections()) return false; + + if (m_settings.get_int(settings_pack::suggest_mode) + == settings_pack::suggest_read_cache) + { + // immediately before unchoking this peer, we should send some + // suggested pieces for it to request + send_piece_suggestions(2); + } + + m_last_unchoke = aux::time_now(); + write_unchoke(); + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_all); + if (!ignore_unchoke_slots()) + m_counters.inc_stats_counter(counters::num_peers_up_unchoked); + m_choked = false; + + m_uploaded_at_last_unchoke = m_statistics.total_payload_upload(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "UNCHOKE"); +#endif + return true; + } + + void peer_connection::send_interested() + { + TORRENT_ASSERT(is_single_thread()); + if (m_interesting) return; + std::shared_ptr t = m_torrent.lock(); + if (!t->ready_for_connections()) return; + m_interesting = true; + m_counters.inc_stats_counter(counters::num_peers_down_interested); + write_interested(); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "INTERESTED"); +#endif + } + + void peer_connection::send_not_interested() + { + TORRENT_ASSERT(is_single_thread()); + // we cannot disconnect in a constructor, and + // this function may end up doing that + TORRENT_ASSERT(m_in_constructor == false); + + if (!m_interesting) + { + disconnect_if_redundant(); + return; + } + + std::shared_ptr t = m_torrent.lock(); + if (!t->ready_for_connections()) return; + m_interesting = false; + m_slow_start = false; + m_counters.inc_stats_counter(counters::num_peers_down_interested, -1); + + disconnect_if_redundant(); + if (m_disconnecting) return; + + write_not_interested(); + + m_became_uninteresting = aux::time_now(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "NOT_INTERESTED"); + } +#endif + } + + void peer_connection::send_upload_only(bool const enabled) + { + TORRENT_ASSERT(is_single_thread()); + if (m_connecting || in_handshake()) return; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "UPLOAD_ONLY", "%d" + , int(enabled)); + } +#endif + + write_upload_only(enabled); + } + + void peer_connection::send_piece_suggestions(int const num) + { + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + int const new_suggestions = t->get_suggest_pieces(m_suggest_pieces + , m_have_piece, num); + + // higher priority pieces are farther back in the vector, the last + // suggested piece to be received is the highest priority, so send the + // highest priority piece last. + for (auto i = m_suggest_pieces.end() - new_suggestions; + i != m_suggest_pieces.end(); ++i) + { + send_suggest(*i); + } + int const max = m_settings.get_int(settings_pack::max_suggest_pieces); + if (m_suggest_pieces.end_index() > max) + { + int const to_erase = m_suggest_pieces.end_index() - max; + m_suggest_pieces.erase(m_suggest_pieces.begin() + , m_suggest_pieces.begin() + to_erase); + } + } + + void peer_connection::send_suggest(piece_index_t const piece) + { + TORRENT_ASSERT(is_single_thread()); + if (m_connecting || in_handshake()) return; + + // don't suggest a piece that the peer already has + if (has_piece(piece)) return; + + // we cannot suggest a piece we don't have! +#if TORRENT_USE_ASSERTS + { + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->has_piece_passed(piece)); + TORRENT_ASSERT(piece < t->torrent_file().end_piece()); + } +#endif + + write_suggest(piece); + } + + void peer_connection::send_block_requests() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (m_disconnecting) return; + + // TODO: 3 once peers are properly put in graceful pause mode, they can + // cancel all outstanding requests and this test can be removed. + if (t->graceful_pause()) return; + + // we can't download pieces in these states + if (t->state() == torrent_status::checking_files + || t->state() == torrent_status::checking_resume_data + || t->state() == torrent_status::downloading_metadata + || t->state() == torrent_status::allocating) + return; + + if (int(m_download_queue.size()) >= m_desired_queue_size + || t->upload_mode()) return; + + bool const empty_download_queue = m_download_queue.empty(); + + while (!m_request_queue.empty() + && (int(m_download_queue.size()) < m_desired_queue_size + || m_queued_time_critical > 0)) + { + pending_block block = m_request_queue.front(); + + m_request_queue.erase(m_request_queue.begin()); + if (m_queued_time_critical) --m_queued_time_critical; + + // if we're a seed, we don't have a piece picker + // so we don't have to worry about invariants getting + // out of sync with it + if (!t->has_picker()) continue; + + // this can happen if a block times out, is re-requested and + // then arrives "unexpectedly" + if (t->picker().is_downloaded(block.block)) + { + t->picker().abort_download(block.block, peer_info_struct()); + continue; + } + + int block_offset = block.block.block_index * t->block_size(); + int bs = std::min(t->torrent_file().piece_size( + block.block.piece_index) - block_offset, t->block_size()); + TORRENT_ASSERT(bs > 0); + TORRENT_ASSERT(bs <= t->block_size()); + + peer_request r; + r.piece = block.block.piece_index; + r.start = block_offset; + r.length = bs; + + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests); + + TORRENT_ASSERT(verify_piece(t->to_req(block.block))); + block.send_buffer_offset = aux::numeric_cast(m_send_buffer.size()); + m_download_queue.push_back(block); + m_outstanding_bytes += bs; +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + + // if we are requesting large blocks, merge the smaller + // blocks that are in the same piece into larger requests + if (m_request_large_blocks) + { + int const blocks_per_piece = t->torrent_file().piece_length() / t->block_size(); + + while (!m_request_queue.empty()) + { + // check to see if this block is connected to the previous one + // if it is, merge them, otherwise, break this merge loop + pending_block const& front = m_request_queue.front(); + if (static_cast(front.block.piece_index) * blocks_per_piece + front.block.block_index + != static_cast(block.block.piece_index) * blocks_per_piece + block.block.block_index + 1) + break; + block = m_request_queue.front(); + m_request_queue.erase(m_request_queue.begin()); + TORRENT_ASSERT(verify_piece(t->to_req(block.block))); + + if (m_download_queue.empty()) + m_counters.inc_stats_counter(counters::num_peers_down_requests); + + block.send_buffer_offset = aux::numeric_cast(m_send_buffer.size()); + m_download_queue.push_back(block); + if (m_queued_time_critical) --m_queued_time_critical; + + block_offset = block.block.block_index * t->block_size(); + bs = std::min(t->torrent_file().piece_size( + block.block.piece_index) - block_offset, t->block_size()); + TORRENT_ASSERT(bs > 0); + TORRENT_ASSERT(bs <= t->block_size()); + + r.length += bs; + m_outstanding_bytes += bs; +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "MERGING_REQUESTS" + , "piece: %d start: %d length: %d", static_cast(r.piece) + , r.start, r.length); +#endif + + } + + // the verification will fail for coalesced blocks + TORRENT_ASSERT(verify_piece(r) || m_request_large_blocks); + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool handled = false; + for (auto const& e : m_extensions) + { + handled = e->write_request(r); + if (handled) break; + } + if (is_disconnecting()) return; + if (!handled) +#endif + { + write_request(r); + m_last_request = aux::time_now(); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing_message)) + { + peer_log(peer_log_alert::outgoing_message, "REQUEST" + , "piece: %d s: %x l: %x ds: %dB/s dqs: %d rqs: %d blk: %s" + , static_cast(r.piece), r.start, r.length, statistics().download_rate() + , int(m_desired_queue_size), int(m_download_queue.size()) + , m_request_large_blocks?"large":"single"); + } +#endif + } + m_last_piece = aux::time_now(); + + if (!m_download_queue.empty() + && empty_download_queue) + { + // This means we just added a request to this connection that + // previously did not have a request. That's when we start the + // request timeout. + m_requested = aux::time_now(); + } + } + + void peer_connection::connect_failed(error_code const& e) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(e); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "CONNECTION FAILED" + , "%s %s", print_endpoint(m_remote).c_str(), print_error(e).c_str()); + } +#endif +#ifndef TORRENT_DISABLE_LOGGING + if (m_ses.should_log()) + m_ses.session_log("CONNECTION FAILED: %s", print_endpoint(m_remote).c_str()); +#endif + + m_counters.inc_stats_counter(counters::connect_timeouts); + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(!m_connecting || t); + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t && m_peer_info) t->dec_num_connecting(m_peer_info); + m_connecting = false; + } + + // a connection attempt using uTP just failed + // mark this peer as not supporting uTP + // we'll never try it again (unless we're trying holepunch) + if (is_utp(*m_socket) + && m_peer_info + && m_peer_info->supports_utp + && !m_holepunch_mode) + { + m_peer_info->supports_utp = false; + // reconnect immediately using TCP + fast_reconnect(true); + disconnect(e, operation_t::connect, normal); + if (t && m_peer_info) + { + std::weak_ptr weak_t = t; + std::weak_ptr weak_self = shared_from_this(); + + // we can't touch m_connections here, since we're likely looping + // over it. So defer the actual reconnection to after we've handled + // the existing message queue + m_ses.get_io_service().post([weak_t, weak_self]() + { + std::shared_ptr tor = weak_t.lock(); + std::shared_ptr p = weak_self.lock(); + if (tor && p) + { + torrent_peer* pi = p->peer_info_struct(); + tor->connect_to_peer(pi, true); + } + }); + } + return; + } + + if (m_holepunch_mode) + fast_reconnect(true); + +#ifndef TORRENT_DISABLE_EXTENSIONS + if ((!is_utp(*m_socket) + || !m_settings.get_bool(settings_pack::enable_outgoing_tcp)) + && m_peer_info + && m_peer_info->supports_holepunch + && !m_holepunch_mode) + { + // see if we can try a holepunch + bt_peer_connection* p = t->find_introducer(remote()); + if (p) + p->write_holepunch_msg(bt_peer_connection::hp_message::rendezvous, remote()); + } +#endif + + disconnect(e, operation_t::connect, failure); + } + + // the error argument defaults to 0, which means deliberate disconnect + // 1 means unexpected disconnect/error + // 2 protocol error (client sent something invalid) + void peer_connection::disconnect(error_code const& ec + , operation_t const op, disconnect_severity_t const error) + { + TORRENT_ASSERT(is_single_thread()); +#if TORRENT_USE_ASSERTS + m_disconnect_started = true; +#endif + + if (m_disconnecting) return; + + m_socket->set_close_reason(error_to_close_reason(ec)); + close_reason_t const close_reason = m_socket->get_close_reason(); +#ifndef TORRENT_DISABLE_LOGGING + if (close_reason != close_reason_t::none) + { + peer_log(peer_log_alert::info, "CLOSE_REASON", "%d", int(close_reason)); + } +#endif + + // while being disconnected, it's possible that our torrent_peer + // pointer gets cleared. Make sure we save it to be able to keep + // proper books in the piece_picker (when debugging is enabled) + torrent_peer* self_peer = peer_info_struct(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) try + { + static aux::array const str{{{ + "CONNECTION_CLOSED", "CONNECTION_FAILED", "PEER_ERROR"}}}; + peer_log(peer_log_alert::info, str[error], "op: %d %s" + , static_cast(op), print_error(ec).c_str()); + + if (ec == boost::asio::error::eof + && !in_handshake() + && !is_connecting() + && aux::time_now() - connected_time() < seconds(15)) + { + peer_log(peer_log_alert::info, "SHORT_LIVED_DISCONNECT", ""); + } + } + catch (std::exception const& err) + { + peer_log(peer_log_alert::info, "PEER_ERROR" ,"op: %d ERROR: unknown error (failed with exception) %s" + , static_cast(op), err.what()); + } +#endif + + if (!(m_channel_state[upload_channel] & peer_info::bw_network)) + { + // make sure we free up all send buffers that are owned + // by the disk thread + m_send_buffer.clear(); + } + + // we cannot do this in a constructor + TORRENT_ASSERT(m_in_constructor == false); + if (error > normal) + { + m_failed = true; + } + + if (m_connected) + m_counters.inc_stats_counter(counters::num_peers_connected, -1); + m_connected = false; + + // for incoming connections, we get invalid argument errors + // when asking for the remote endpoint and the socket already + // closed, which is an edge case, but possible to happen when + // a peer makes a TCP and uTP connection in parallel. + // for outgoing connections however, why would we get this? +// TORRENT_ASSERT(ec != error::invalid_argument || !m_outgoing); + + m_counters.inc_stats_counter(counters::disconnected_peers); + if (error == peer_error) m_counters.inc_stats_counter(counters::error_peers); + + if (ec == error::connection_reset) + m_counters.inc_stats_counter(counters::connreset_peers); + else if (ec == error::eof) + m_counters.inc_stats_counter(counters::eof_peers); + else if (ec == error::connection_refused) + m_counters.inc_stats_counter(counters::connrefused_peers); + else if (ec == error::connection_aborted) + m_counters.inc_stats_counter(counters::connaborted_peers); + else if (ec == error::not_connected) + m_counters.inc_stats_counter(counters::notconnected_peers); + else if (ec == error::no_permission) + m_counters.inc_stats_counter(counters::perm_peers); + else if (ec == error::no_buffer_space) + m_counters.inc_stats_counter(counters::buffer_peers); + else if (ec == error::host_unreachable) + m_counters.inc_stats_counter(counters::unreachable_peers); + else if (ec == error::broken_pipe) + m_counters.inc_stats_counter(counters::broken_pipe_peers); + else if (ec == error::address_in_use) + m_counters.inc_stats_counter(counters::addrinuse_peers); + else if (ec == error::access_denied) + m_counters.inc_stats_counter(counters::no_access_peers); + else if (ec == error::invalid_argument) + m_counters.inc_stats_counter(counters::invalid_arg_peers); + else if (ec == error::operation_aborted) + m_counters.inc_stats_counter(counters::aborted_peers); + else if (ec == errors::upload_upload_connection + || ec == errors::uninteresting_upload_peer + || ec == errors::torrent_aborted + || ec == errors::self_connection + || ec == errors::torrent_paused) + m_counters.inc_stats_counter(counters::uninteresting_peers); + + if (ec == errors::timed_out + || ec == error::timed_out) + m_counters.inc_stats_counter(counters::transport_timeout_peers); + + if (ec == errors::timed_out_inactivity + || ec == errors::timed_out_no_request + || ec == errors::timed_out_no_interest) + m_counters.inc_stats_counter(counters::timeout_peers); + + if (ec == errors::no_memory) + m_counters.inc_stats_counter(counters::no_memory_peers); + + if (ec == errors::too_many_connections) + m_counters.inc_stats_counter(counters::too_many_peers); + + if (ec == errors::timed_out_no_handshake) + m_counters.inc_stats_counter(counters::connect_timeouts); + + if (error > normal) + { + if (is_utp(*m_socket)) m_counters.inc_stats_counter(counters::error_utp_peers); + else m_counters.inc_stats_counter(counters::error_tcp_peers); + + if (m_outgoing) m_counters.inc_stats_counter(counters::error_outgoing_peers); + else m_counters.inc_stats_counter(counters::error_incoming_peers); + +#if !defined TORRENT_DISABLE_ENCRYPTION + if (type() == connection_type::bittorrent && op != operation_t::connect) + { + auto* bt = static_cast(this); + if (bt->supports_encryption()) m_counters.inc_stats_counter( + counters::error_encrypted_peers); + if (bt->rc4_encrypted() && bt->supports_encryption()) + m_counters.inc_stats_counter(counters::error_rc4_peers); + } +#endif // TORRENT_DISABLE_ENCRYPTION + } + + std::shared_ptr me(self()); + + INVARIANT_CHECK; + + if (m_channel_state[upload_channel] & peer_info::bw_disk) + { + m_counters.inc_stats_counter(counters::num_peers_up_disk, -1); + m_channel_state[upload_channel] &= ~peer_info::bw_disk; + } + if (m_channel_state[download_channel] & peer_info::bw_disk) + { + m_counters.inc_stats_counter(counters::num_peers_down_disk, -1); + m_channel_state[download_channel] &= ~peer_info::bw_disk; + } + + std::shared_ptr t = m_torrent.lock(); + + // don't try to connect to ourself again + if (ec == errors::self_connection && m_peer_info && t) + t->ban_peer(m_peer_info); + + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(m_peer_info); + m_connecting = false; + } + + torrent_handle handle; + if (t) handle = t->get_handle(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + e->on_disconnect(ec); + } +#endif + + if (ec == error::address_in_use + && m_settings.get_int(settings_pack::outgoing_port) != 0 + && t) + { + if (t->alerts().should_post()) + t->alerts().emplace_alert( + handle, performance_alert::too_few_outgoing_ports); + } + + m_disconnecting = true; + + if (t) + { + if (ec) + { + if ((error > failure || ec.category() == socks_category()) + && t->alerts().should_post()) + { + t->alerts().emplace_alert(handle, remote() + , pid(), op, ec); + } + + if (error <= failure && t->alerts().should_post()) + { + t->alerts().emplace_alert(handle + , remote(), pid(), op, m_socket->type(), ec, close_reason); + } + } + + // make sure we keep all the stats! + if (!m_ignore_stats) + { + // report any partially received payload as redundant + piece_block_progress pbp = downloading_piece_progress(); + if (pbp.piece_index != piece_block_progress::invalid_index + && pbp.bytes_downloaded > 0 + && pbp.bytes_downloaded < pbp.full_block_bytes) + { + t->add_redundant_bytes(pbp.bytes_downloaded, waste_reason::piece_closing); + } + } + + if (t->has_picker()) + { + clear_download_queue(); + piece_picker& picker = t->picker(); + while (!m_request_queue.empty()) + { + pending_block const& qe = m_request_queue.back(); + if (!qe.timed_out && !qe.not_wanted) + picker.abort_download(qe.block, self_peer); + m_request_queue.pop_back(); + } + } + else + { + m_download_queue.clear(); + m_request_queue.clear(); + m_outstanding_bytes = 0; + } + m_queued_time_critical = 0; + +#if TORRENT_USE_INVARIANT_CHECKS + try { check_invariant(); } catch (std::exception const&) {} +#endif + t->remove_peer(self()); + + // we need to do this here to maintain accurate accounting of number of + // unchoke slots. Ideally the updating of choked state and the + // accounting should be tighter + if (!m_choked) + { + m_choked = true; + m_counters.inc_stats_counter(counters::num_peers_up_unchoked_all, -1); + if (!ignore_unchoke_slots()) + m_counters.inc_stats_counter(counters::num_peers_up_unchoked, -1); + } + } + else + { + TORRENT_ASSERT(m_download_queue.empty()); + TORRENT_ASSERT(m_request_queue.empty()); + m_ses.close_connection(this); + } + + async_shutdown(*m_socket, m_socket); + } + + bool peer_connection::ignore_unchoke_slots() const + { + TORRENT_ASSERT(is_single_thread()); + if (num_classes() == 0) return true; + + if (m_ses.ignore_unchoke_slots_set(*this)) return true; + std::shared_ptr t = m_torrent.lock(); + if (t && m_ses.ignore_unchoke_slots_set(*t)) return true; + return false; + } + + bool peer_connection::on_local_network() const + { + TORRENT_ASSERT(is_single_thread()); + return is_local(m_remote.address()) + || is_loopback(m_remote.address()); + } + + int peer_connection::request_timeout() const + { + const int deviation = m_request_time.avg_deviation(); + const int avg = m_request_time.mean(); + + int ret; + if (m_request_time.num_samples() < 2) + { + if (m_request_time.num_samples() == 0) + return m_settings.get_int(settings_pack::request_timeout); + + ret = avg + avg / 5; + } + else + { + ret = avg + deviation * 4; + } + + // ret is milliseconds, the return value is seconds. Convert to + // seconds and round up + ret = std::min((ret + 999) / 1000 + , m_settings.get_int(settings_pack::request_timeout)); + + // timeouts should never be less than 2 seconds. The granularity is whole + // seconds, and only checked once per second. 2 is the minimum to avoid + // being considered timed out instantly + return std::max(2, ret); + } + + void peer_connection::get_peer_info(peer_info& p) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!associated_torrent().expired()); + + time_point const now = aux::time_now(); + + p.download_rate_peak = m_download_rate_peak; + p.upload_rate_peak = m_upload_rate_peak; + p.rtt = m_request_time.mean(); + p.down_speed = statistics().download_rate(); + p.up_speed = statistics().upload_rate(); + p.payload_down_speed = statistics().download_payload_rate(); + p.payload_up_speed = statistics().upload_payload_rate(); + p.pid = pid(); + p.ip = remote(); + p.pending_disk_bytes = m_outstanding_writing_bytes; + p.pending_disk_read_bytes = m_reading_bytes; + p.send_quota = m_quota[upload_channel]; + p.receive_quota = m_quota[download_channel]; + p.num_pieces = m_num_pieces; + if (m_download_queue.empty()) p.request_timeout = -1; + else p.request_timeout = int(total_seconds(m_requested - now) + + request_timeout()); + + p.download_queue_time = download_queue_time(); + p.queue_bytes = m_outstanding_bytes; + + p.total_download = statistics().total_payload_download(); + p.total_upload = statistics().total_payload_upload(); +#if TORRENT_ABI_VERSION == 1 + p.upload_limit = -1; + p.download_limit = -1; + p.load_balancing = 0; +#endif + + p.download_queue_length = int(download_queue().size() + m_request_queue.size()); + p.requests_in_buffer = int(std::count_if(m_download_queue.begin() + , m_download_queue.end() + , &pending_block_in_buffer)); + + p.target_dl_queue_length = desired_queue_size(); + p.upload_queue_length = int(upload_queue().size()); + p.timed_out_requests = 0; + p.busy_requests = 0; + for (auto const& pb : m_download_queue) + { + if (pb.timed_out) ++p.timed_out_requests; + if (pb.busy) ++p.busy_requests; + } + + piece_block_progress const ret = downloading_piece_progress(); + if (ret.piece_index != piece_block_progress::invalid_index) + { + p.downloading_piece_index = ret.piece_index; + p.downloading_block_index = ret.block_index; + p.downloading_progress = ret.bytes_downloaded; + p.downloading_total = ret.full_block_bytes; + } + else + { + p.downloading_piece_index = piece_index_t(-1); + p.downloading_block_index = -1; + p.downloading_progress = 0; + p.downloading_total = 0; + } + + p.pieces = get_bitfield(); + p.last_request = now - m_last_request; + p.last_active = now - std::max(m_last_sent, m_last_receive); + + // this will set the flags so that we can update them later + p.flags = {}; + get_specific_peer_info(p); + + if (is_seed()) p.flags |= peer_info::seed; + if (m_snubbed) p.flags |= peer_info::snubbed; + if (m_upload_only) p.flags |= peer_info::upload_only; + if (m_endgame_mode) p.flags |= peer_info::endgame_mode; + if (m_holepunch_mode) p.flags |= peer_info::holepunched; + if (peer_info_struct()) + { + torrent_peer* pi = peer_info_struct(); + TORRENT_ASSERT(pi->in_use); + p.source = peer_source_flags_t(pi->source); + p.failcount = pi->failcount; + p.num_hashfails = pi->hashfails; + if (pi->on_parole) p.flags |= peer_info::on_parole; + if (pi->optimistically_unchoked) p.flags |= peer_info::optimistic_unchoke; + } + else + { + p.source = {}; + p.failcount = 0; + p.num_hashfails = 0; + } + +#if TORRENT_ABI_VERSION == 1 + p.remote_dl_rate = 0; +#endif + p.send_buffer_size = m_send_buffer.capacity(); + p.used_send_buffer = m_send_buffer.size(); + p.receive_buffer_size = m_recv_buffer.capacity(); + p.used_receive_buffer = m_recv_buffer.pos(); + p.receive_buffer_watermark = m_recv_buffer.watermark(); + p.write_state = m_channel_state[upload_channel]; + p.read_state = m_channel_state[download_channel]; + + // pieces may be empty if we don't have metadata yet + if (p.pieces.empty()) + { + p.progress = 0.f; + p.progress_ppm = 0; + } + else + { +#if TORRENT_NO_FPU + p.progress = 0.f; +#else + p.progress = float(p.pieces.count()) / float(p.pieces.size()); +#endif + p.progress_ppm = int(std::int64_t(p.pieces.count()) * 1000000 / p.pieces.size()); + } + +#if TORRENT_ABI_VERSION == 1 + p.estimated_reciprocation_rate = m_est_reciprocation_rate; +#endif + + error_code ec; + p.local_endpoint = get_socket()->local_endpoint(ec); + } + +#ifndef TORRENT_DISABLE_SUPERSEEDING + // TODO: 3 new_piece should be an optional. piece index -1 + // should not be allowed + void peer_connection::superseed_piece(piece_index_t const replace_piece + , piece_index_t const new_piece) + { + TORRENT_ASSERT(is_single_thread()); + + if (is_connecting()) return; + if (in_handshake()) return; + + if (new_piece == piece_index_t(-1)) + { + if (m_superseed_piece[0] == piece_index_t(-1)) return; + m_superseed_piece[0] = piece_index_t(-1); + m_superseed_piece[1] = piece_index_t(-1); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SUPER_SEEDING", "ending"); +#endif + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + // this will either send a full bitfield or + // a have-all message, effectively terminating + // super-seeding, since the peer may pick any piece + write_bitfield(); + + return; + } + + TORRENT_ASSERT(!has_piece(new_piece)); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "HAVE", "piece: %d (super seed)" + , static_cast(new_piece)); +#endif + write_have(new_piece); + + if (replace_piece >= piece_index_t(0)) + { + // move the piece we're replacing to the tail + if (m_superseed_piece[0] == replace_piece) + std::swap(m_superseed_piece[0], m_superseed_piece[1]); + } + + m_superseed_piece[1] = m_superseed_piece[0]; + m_superseed_piece[0] = new_piece; + } +#endif // TORRENT_DISABLE_SUPERSEEDING + + void peer_connection::max_out_request_queue(int s) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "MAX_OUT_QUEUE_SIZE", "%d -> %d" + , m_max_out_request_queue, s); +#endif + m_max_out_request_queue = s; + } + + int peer_connection::max_out_request_queue() const + { + return m_max_out_request_queue; + } + + void peer_connection::update_desired_queue_size() + { + TORRENT_ASSERT(is_single_thread()); + if (m_snubbed) + { + m_desired_queue_size = 1; + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + int const previous_queue_size = m_desired_queue_size; +#endif + + int const download_rate = statistics().download_payload_rate(); + + // the desired download queue size + int const queue_time = m_settings.get_int(settings_pack::request_queue_time); + + // when we're in slow-start mode we increase the desired queue size every + // time we receive a piece, no need to adjust it here (other than + // enforcing the upper limit) + if (!m_slow_start) + { + // (if the latency is more than this, the download will stall) + // so, the queue size is queue_time * down_rate / 16 kiB + // (16 kB is the size of each request) + // the minimum number of requests is 2 and the maximum is 48 + // the block size doesn't have to be 16. So we first query the + // torrent for it + std::shared_ptr t = m_torrent.lock(); + int const bs = t->block_size(); + + TORRENT_ASSERT(bs > 0); + + m_desired_queue_size = std::uint16_t(queue_time * download_rate / bs); + } + + if (m_desired_queue_size > m_max_out_request_queue) + m_desired_queue_size = std::uint16_t(m_max_out_request_queue); + if (m_desired_queue_size < min_request_queue) + m_desired_queue_size = min_request_queue; + +#ifndef TORRENT_DISABLE_LOGGING + if (previous_queue_size != m_desired_queue_size) + { + peer_log(peer_log_alert::info, "UPDATE_QUEUE_SIZE" + , "dqs: %d max: %d dl: %d qt: %d snubbed: %d slow-start: %d" + , m_desired_queue_size, m_max_out_request_queue + , download_rate, queue_time, int(m_snubbed), int(m_slow_start)); + } +#endif + } + + void peer_connection::second_tick(int const tick_interval_ms) + { + TORRENT_ASSERT(is_single_thread()); + time_point const now = aux::time_now(); + std::shared_ptr me(self()); + + // the invariant check must be run before me is destructed + // in case the peer got disconnected + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + + int warning = 0; + // drain the IP overhead from the bandwidth limiters + if (m_settings.get_bool(settings_pack::rate_limit_ip_overhead) && t) + { + warning |= m_ses.use_quota_overhead(*this, m_statistics.download_ip_overhead() + , m_statistics.upload_ip_overhead()); + warning |= m_ses.use_quota_overhead(*t, m_statistics.download_ip_overhead() + , m_statistics.upload_ip_overhead()); + } + + if (warning && t->alerts().should_post()) + { + for (int channel = 0; channel < 2; ++channel) + { + if ((warning & (1 << channel)) == 0) continue; + t->alerts().emplace_alert(t->get_handle() + , channel == peer_connection::download_channel + ? performance_alert::download_limit_too_low + : performance_alert::upload_limit_too_low); + } + } + + if (!t || m_disconnecting) + { + TORRENT_ASSERT(t || !m_connecting); + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(m_peer_info); + m_connecting = false; + } + disconnect(errors::torrent_aborted, operation_t::bittorrent); + return; + } + + if (m_endgame_mode + && m_interesting + && m_download_queue.empty() + && m_request_queue.empty() + && now - seconds(5) >= m_last_request) + { + // this happens when we're in strict end-game + // mode and the peer could not request any blocks + // because they were all taken but there were still + // unrequested blocks. Now, 5 seconds later, there + // might not be any unrequested blocks anymore, so + // we should try to pick another block to see + // if we can pick a busy one + m_last_request = now; + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::end_game_piece_picks); + if (m_disconnecting) return; + send_block_requests(); + } + +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (t->super_seeding() + && t->ready_for_connections() + && !m_peer_interested + && m_became_uninterested + seconds(10) < now) + { + // maybe we need to try another piece, to see if the peer + // become interested in us then + superseed_piece(piece_index_t(-1), t->get_piece_to_super_seed(m_have_piece)); + } +#endif + + on_tick(); + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& e : m_extensions) + { + e->tick(); + } + if (is_disconnecting()) return; +#endif + + // if the peer hasn't said a thing for a certain + // time, it is considered to have timed out + time_duration d = now - m_last_receive; + + if (m_connecting) + { + int connect_timeout = m_settings.get_int(settings_pack::peer_connect_timeout); + if (m_peer_info) connect_timeout += 3 * m_peer_info->failcount; + + // SSL and i2p handshakes are slow + if (is_ssl(*m_socket)) + connect_timeout += 10; + +#if TORRENT_USE_I2P + if (is_i2p(*m_socket)) + connect_timeout += 20; +#endif + + if (d > seconds(connect_timeout) + && can_disconnect(errors::timed_out)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CONNECT_FAILED", "waited %d seconds" + , int(total_seconds(d))); +#endif + connect_failed(errors::timed_out); + return; + } + } + + // if we can't read, it means we're blocked on the rate-limiter + // or the disk, not the peer itself. In this case, don't blame + // the peer and disconnect it + bool const may_timeout = bool(m_channel_state[download_channel] & peer_info::bw_network); + + // TODO: 2 use a deadline_timer for timeouts. Don't rely on second_tick()! + // Hook this up to connect timeout as well. This would improve performance + // because of less work in second_tick(), and might let use remove ticking + // entirely eventually + if (may_timeout && d > seconds(timeout()) && !m_connecting && m_reading_bytes == 0 + && can_disconnect(errors::timed_out_inactivity)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "LAST_ACTIVITY", "%d seconds ago" + , int(total_seconds(d))); +#endif + disconnect(errors::timed_out_inactivity, operation_t::bittorrent); + return; + } + + // do not stall waiting for a handshake + int timeout = m_settings.get_int (settings_pack::handshake_timeout); +#if TORRENT_USE_I2P + timeout *= is_i2p(*m_socket) ? 4 : 1; +#endif + if (may_timeout + && !m_connecting + && in_handshake() + && d > seconds(timeout)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "NO_HANDSHAKE", "waited %d seconds" + , int(total_seconds(d))); +#endif + disconnect(errors::timed_out_no_handshake, operation_t::bittorrent); + return; + } + + // disconnect peers that we unchoked, but they didn't send a request in + // the last 60 seconds, and we haven't been working on servicing a request + // for more than 60 seconds. + // but only if we're a seed + d = now - std::max(std::max(m_last_unchoke, m_last_incoming_request) + , m_last_sent_payload); + + if (may_timeout + && !m_connecting + && m_requests.empty() + && m_reading_bytes == 0 + && !m_choked + && m_peer_interested + && t && t->is_upload_only() + && d > seconds(60) + && can_disconnect(errors::timed_out_no_request)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "NO_REQUEST", "waited %d seconds" + , int(total_seconds(d))); +#endif + disconnect(errors::timed_out_no_request, operation_t::bittorrent); + return; + } + + // if the peer hasn't become interested and we haven't + // become interested in the peer for 10 minutes, it + // has also timed out. + time_duration const d1 = now - m_became_uninterested; + time_duration const d2 = now - m_became_uninteresting; + time_duration const time_limit = seconds( + m_settings.get_int(settings_pack::inactivity_timeout)); + + // don't bother disconnect peers we haven't been interested + // in (and that hasn't been interested in us) for a while + // unless we have used up all our connection slots + if (may_timeout + && !m_interesting + && !m_peer_interested + && d1 > time_limit + && d2 > time_limit + && (m_ses.num_connections() >= m_settings.get_int(settings_pack::connections_limit) + || (t && t->num_peers() >= t->max_connections())) + && can_disconnect(errors::timed_out_no_interest)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "MUTUAL_NO_INTEREST", "t1: %d t2: %d" + , int(total_seconds(d1)), int(total_seconds(d2))); + } +#endif + disconnect(errors::timed_out_no_interest, operation_t::bittorrent); + return; + } + + if (may_timeout + && !m_download_queue.empty() + && m_quota[download_channel] > 0 + && now > m_requested + seconds(request_timeout())) + { + snub_peer(); + } + + // if we haven't sent something in too long, send a keep-alive + keep_alive(); + + // if our download rate isn't increasing significantly anymore, end slow + // start. The 10kB is to have some slack here. + // we can't do this when we're choked, because we aren't sending any + // requests yet, so there hasn't been an opportunity to ramp up the + // connection yet. + if (m_slow_start + && !m_peer_choked + && m_downloaded_last_second > 0 + && m_downloaded_last_second + 5000 + >= m_statistics.last_payload_downloaded()) + { + m_slow_start = false; +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "SLOW_START", "exit slow start: " + "prev-dl: %d dl: %d" + , int(m_downloaded_last_second) + , m_statistics.last_payload_downloaded()); + } +#endif + } + m_downloaded_last_second = m_statistics.last_payload_downloaded(); + m_uploaded_last_second = m_statistics.last_payload_uploaded(); + + m_statistics.second_tick(tick_interval_ms); + + if (m_statistics.upload_payload_rate() > m_upload_rate_peak) + { + m_upload_rate_peak = m_statistics.upload_payload_rate(); + } + if (m_statistics.download_payload_rate() > m_download_rate_peak) + { + m_download_rate_peak = m_statistics.download_payload_rate(); + } + if (is_disconnecting()) return; + + if (!t->ready_for_connections()) return; + + update_desired_queue_size(); + + if (m_desired_queue_size == m_max_out_request_queue + && t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , performance_alert::outstanding_request_limit_reached); + } + + int const piece_timeout = m_settings.get_int(settings_pack::piece_timeout); + + if (!m_download_queue.empty() + && m_quota[download_channel] > 0 + && now - m_last_piece > seconds(piece_timeout)) + { + // this peer isn't sending the pieces we've + // requested (this has been observed by BitComet) + // in this case we'll clear our download queue and + // re-request the blocks. +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "PIECE_REQUEST_TIMED_OUT" + , "%d time: %d to: %d" + , int(m_download_queue.size()), int(total_seconds(now - m_last_piece)) + , piece_timeout); + } +#endif + + snub_peer(); + } + + fill_send_buffer(); + } + + void peer_connection::snub_peer() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + + if (!m_snubbed) + { + m_snubbed = true; + m_slow_start = false; + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , m_remote, m_peer_id); + } + } + m_desired_queue_size = 1; + + if (on_parole()) return; + + if (!t->has_picker()) return; + piece_picker& picker = t->picker(); + + // first, if we have any unsent requests, just + // wipe those out + while (!m_request_queue.empty()) + { + t->picker().abort_download(m_request_queue.back().block, peer_info_struct()); + m_request_queue.pop_back(); + } + m_queued_time_critical = 0; + + TORRENT_ASSERT(!m_download_queue.empty()); + + // time out the last request eligible + // block in the queue + int i = int(m_download_queue.size()) - 1; + for (; i >= 0; --i) + { + if (!m_download_queue[i].timed_out + && !m_download_queue[i].not_wanted) + break; + } + + if (i >= 0) + { + pending_block& qe = m_download_queue[i]; + piece_block const r = qe.block; + + // only cancel a request if it blocks the piece from being completed + // (i.e. no free blocks to request from it) + piece_picker::downloading_piece p; + picker.piece_info(qe.block.piece_index, p); + int const free_blocks = picker.blocks_in_piece(qe.block.piece_index) + - p.finished - p.writing - p.requested; + + // if there are still blocks available for other peers to pick, we're + // still not holding up the completion of the piece and there's no + // need to cancel the requests. For more information, see: + // http://blog.libtorrent.org/2011/11/block-request-time-outs/ + if (free_blocks > 0) + { + send_block_requests(); + return; + } + + if (t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , remote(), pid(), qe.block.block_index + , qe.block.piece_index); + } + + // request a new block before removing the previous + // one, in order to prevent it from + // picking the same block again, stalling the + // same piece indefinitely. + m_desired_queue_size = 2; + if (request_a_block(*t, *this)) + m_counters.inc_stats_counter(counters::snubbed_piece_picks); + + // the block we just picked (potentially) + // hasn't been put in m_download_queue yet. + // it's in m_request_queue and will be sent + // once send_block_requests() is called. + + m_desired_queue_size = 1; + + qe.timed_out = true; + picker.abort_download(r, peer_info_struct()); + } + + send_block_requests(); + } + + void peer_connection::fill_send_buffer() + { + TORRENT_ASSERT(is_single_thread()); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#ifndef TORRENT_DISABLE_SHARE_MODE + bool sent_a_piece = false; +#endif + std::shared_ptr t = m_torrent.lock(); + if (!t || t->is_aborted() || m_requests.empty()) return; + + // only add new piece-chunks if the send buffer is small enough + // otherwise there will be no end to how large it will be! + + int buffer_size_watermark = int(std::int64_t(m_uploaded_last_second) + * m_settings.get_int(settings_pack::send_buffer_watermark_factor) / 100); + + if (buffer_size_watermark < m_settings.get_int(settings_pack::send_buffer_low_watermark)) + { + buffer_size_watermark = m_settings.get_int(settings_pack::send_buffer_low_watermark); + } + else if (buffer_size_watermark > m_settings.get_int(settings_pack::send_buffer_watermark)) + { + buffer_size_watermark = m_settings.get_int(settings_pack::send_buffer_watermark); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SEND_BUFFER_WATERMARK" + , "current watermark: %d max: %d min: %d factor: %d uploaded: %d B/s" + , buffer_size_watermark + , m_ses.settings().get_int(settings_pack::send_buffer_watermark) + , m_ses.settings().get_int(settings_pack::send_buffer_low_watermark) + , m_ses.settings().get_int(settings_pack::send_buffer_watermark_factor) + , int(m_uploaded_last_second)); + } +#endif + + // don't just pop the front element here, since in seed mode one request may + // be blocked because we have to verify the hash first, so keep going with the + // next request. However, only let each peer have one hash verification outstanding + // at any given time + for (int i = 0; i < int(m_requests.size()) + && (send_buffer_size() + m_reading_bytes < buffer_size_watermark); ++i) + { + TORRENT_ASSERT(t->ready_for_connections()); + peer_request& r = m_requests[i]; + + TORRENT_ASSERT(r.piece >= piece_index_t(0)); + TORRENT_ASSERT(r.piece < piece_index_t(m_have_piece.size())); + TORRENT_ASSERT(r.start + r.length <= t->torrent_file().piece_size(r.piece)); + TORRENT_ASSERT(r.length > 0 && r.start >= 0); + + if (t->is_deleted()) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %x l: %x torrent deleted" + , static_cast(r.piece), r.start , r.length); +#endif + write_reject_request(r); + continue; + } + + bool const seed_mode = t->seed_mode(); + + if (seed_mode + && !t->verified_piece(r.piece) + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + // we're still verifying the hash of this piece + // so we can't return it yet. + if (t->verifying_piece(r.piece)) continue; + + // only have three outstanding hash check per peer + if (m_outstanding_piece_verification >= 3) continue; + + ++m_outstanding_piece_verification; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED_MODE_FILE_ASYNC_HASH" + , "piece: %d", static_cast(r.piece)); +#endif + // this means we're in seed mode and we haven't yet + // verified this piece (r.piece) + auto conn = self(); + m_disk_thread.async_hash(t->storage(), r.piece, {} + , [conn](piece_index_t p, sha1_hash const& ph, storage_error const& e) { + conn->wrap(&peer_connection::on_seed_mode_hashed, p, ph, e); }); + t->verifying(r.piece); + continue; + } + + if (!t->has_piece_passed(r.piece) && !seed_mode) + { +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // we don't have this piece yet, but we anticipate to have + // it very soon, so we have told our peers we have it. + // hold off on sending it. If the piece fails later + // we will reject this request + if (t->is_predictive_piece(r.piece)) continue; +#endif +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REJECT_PIECE" + , "piece: %d s: %x l: %x piece not passed hash check" + , static_cast(r.piece), r.start , r.length); +#endif + write_reject_request(r); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "FILE_ASYNC_READ" + , "piece: %d s: %x l: %x", static_cast(r.piece), r.start, r.length); +#endif + m_reading_bytes += r.length; +#ifndef TORRENT_DISABLE_SHARE_MODE + sent_a_piece = true; +#endif + + // the callback function may be called immediately, instead of being posted + + TORRENT_ASSERT(t->valid_metadata()); + TORRENT_ASSERT(r.piece >= piece_index_t(0)); + TORRENT_ASSERT(r.piece < t->torrent_file().end_piece()); + + auto conn = self(); + m_disk_thread.async_read(t->storage(), r + , [conn, r](disk_buffer_holder buf, disk_job_flags_t f, storage_error const& ec) + { conn->wrap(&peer_connection::on_disk_read_complete, std::move(buf), f, ec, r, clock_type::now()); }); + } + m_last_sent_payload = clock_type::now(); + m_requests.erase(m_requests.begin() + i); + + if (m_requests.empty()) + m_counters.inc_stats_counter(counters::num_peers_up_requests, -1); + + --i; + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (t->share_mode() && sent_a_piece) + t->recalc_share_mode(); +#endif + } + + // this is called when a previously unchecked piece has been + // checked, while in seed-mode + void peer_connection::on_seed_mode_hashed(piece_index_t const piece + , sha1_hash const& piece_hash, storage_error const& error) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + + TORRENT_ASSERT(m_outstanding_piece_verification > 0); + --m_outstanding_piece_verification; + + if (!t || t->is_aborted()) return; + + if (error) + { + t->handle_disk_error("hash", error, this); + t->leave_seed_mode(torrent::seed_mode_t::check_files); + return; + } + + // we're using the piece hashes here, we need the torrent to be loaded + if (!m_settings.get_bool(settings_pack::disable_hash_checks) + && piece_hash != t->torrent_file().hash_for_piece(piece)) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED_MODE_FILE_HASH" + , "piece: %d failed", static_cast(piece)); +#endif + + t->leave_seed_mode(torrent::seed_mode_t::check_files); + } + else + { + if (t->seed_mode()) + { + TORRENT_ASSERT(t->verifying_piece(piece)); + t->verified(piece); + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SEED_MODE_FILE_HASH" + , "piece: %d passed", static_cast(piece)); +#endif + if (t->seed_mode() && t->all_verified()) + t->leave_seed_mode(torrent::seed_mode_t::skip_checking); + } + + // try to service the requests again, now that the piece + // has been verified + fill_send_buffer(); + } + + void peer_connection::on_disk_read_complete(disk_buffer_holder buffer + , disk_job_flags_t const flags, storage_error const& error + , peer_request const& r, time_point const issue_time) + { + TORRENT_ASSERT(is_single_thread()); + // return value: + // 0: success, piece passed hash check + // -1: disk failure + + int const disk_rtt = int(total_microseconds(clock_type::now() - issue_time)); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "FILE_ASYNC_READ_COMPLETE" + , "piece: %d s: %x l: %x b: %p c: %s e: %s rtt: %d us" + , static_cast(r.piece), r.start, r.length + , static_cast(buffer.get()) + , ((flags & disk_interface::cache_hit) ? "cache hit" : "cache miss") + , error.ec.message().c_str(), disk_rtt); + } +#endif + + m_reading_bytes -= r.length; + + std::shared_ptr t = m_torrent.lock(); + if (error) + { + if (!t) + { + disconnect(error.ec, operation_t::file_read); + return; + } + + write_dont_have(r.piece); + write_reject_request(r); + if (t->alerts().should_post()) + t->alerts().emplace_alert(error.ec + , t->resolve_filename(error.file()) + , error.operation, t->get_handle()); + + ++m_disk_read_failures; + if (m_disk_read_failures > 100) disconnect(error.ec, operation_t::file_read); + return; + } + + // we're only interested in failures in a row. + // if we every now and then successfully send a + // block, the peer is still useful + m_disk_read_failures = 0; + + if (t && m_settings.get_int(settings_pack::suggest_mode) + == settings_pack::suggest_read_cache) + { + // tell the torrent that we just read a block from this piece. + // if this piece is low-availability, it's now a candidate for being + // suggested to other peers + t->add_suggest_piece(r.piece); + } + + if (m_disconnecting) return; + + if (!t) + { + disconnect(error.ec, operation_t::file_read); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message + , "PIECE", "piece: %d s: %x l: %x" + , static_cast(r.piece), r.start, r.length); +#endif + + m_counters.blend_stats_counter(counters::request_latency, disk_rtt, 5); + + // we probably just pulled this piece into the cache. + // if it's rare enough to make it into the suggested piece + // push another piece out + if (m_settings.get_int(settings_pack::suggest_mode) == settings_pack::suggest_read_cache + && !(flags & disk_interface::cache_hit)) + { + t->add_suggest_piece(r.piece); + } + write_piece(r, std::move(buffer)); + } + + void peer_connection::assign_bandwidth(int const channel, int const amount) + { + TORRENT_ASSERT(is_single_thread()); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(channel == upload_channel + ? peer_log_alert::outgoing : peer_log_alert::incoming + , "ASSIGN_BANDWIDTH", "bytes: %d", amount); +#endif + + TORRENT_ASSERT(amount > 0 || is_disconnecting()); + m_quota[channel] += amount; + TORRENT_ASSERT(m_channel_state[channel] & peer_info::bw_limit); + m_channel_state[channel] &= ~peer_info::bw_limit; + +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + + if (is_disconnecting()) return; + if (channel == upload_channel) + { + setup_send(); + } + else if (channel == download_channel) + { + setup_receive(); + } + } + + // the number of bytes we expect to receive, or want to send + // channel either refer to upload or download. This is used + // by the rate limiter to allocate quota for this peer + int peer_connection::wanted_transfer(int const channel) + { + TORRENT_ASSERT(is_single_thread()); + + const int tick_interval = std::max(1, m_settings.get_int(settings_pack::tick_interval)); + + if (channel == download_channel) + { + std::int64_t const download_rate = std::int64_t(m_statistics.download_rate()) * 3 / 2; + return std::max({m_outstanding_bytes + 30 + , m_recv_buffer.packet_bytes_remaining() + 30 + , int(download_rate * tick_interval / 1000)}); + } + else + { + std::int64_t const upload_rate = std::int64_t(m_statistics.upload_rate()) * 2; + return std::max({m_reading_bytes + , m_send_buffer.size() + , int(upload_rate * tick_interval / 1000)}); + } + } + + int peer_connection::request_bandwidth(int const channel, int bytes) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + // we can only have one outstanding bandwidth request at a time + if (m_channel_state[channel] & peer_info::bw_limit) return 0; + + std::shared_ptr t = m_torrent.lock(); + + bytes = std::max(wanted_transfer(channel), bytes); + + // we already have enough quota + if (m_quota[channel] >= bytes) return 0; + + // deduct the bytes we already have quota for + bytes -= m_quota[channel]; + + int const priority = get_priority(channel); + + int const max_channels = num_classes() + (t ? t->num_classes() : 0) + 2; + TORRENT_ALLOCA(channels, bandwidth_channel*, max_channels); + + // collect the pointers to all bandwidth channels + // that apply to this torrent + int c = 0; + + c += m_ses.copy_pertinent_channels(*this, channel + , channels.subspan(c).data(), max_channels - c); + if (t) + { + c += m_ses.copy_pertinent_channels(*t, channel + , channels.subspan(c).data(), max_channels - c); + } + +#if TORRENT_USE_ASSERTS + // make sure we don't have duplicates + std::set unique_classes; + for (int i = 0; i < c; ++i) + { + TORRENT_ASSERT(unique_classes.count(channels[i]) == 0); + unique_classes.insert(channels[i]); + } +#endif + + TORRENT_ASSERT(!(m_channel_state[channel] & peer_info::bw_limit)); + + bandwidth_manager* manager = m_ses.get_bandwidth_manager(channel); + + int const ret = manager->request_bandwidth(self() + , bytes, priority, channels.data(), c); + + if (ret == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + auto const dir = channel == download_channel ? peer_log_alert::incoming + : peer_log_alert::outgoing; + if (should_log(dir)) + { + peer_log(dir, + "REQUEST_BANDWIDTH", "bytes: %d quota: %d wanted_transfer: %d " + "prio: %d num_channels: %d", bytes, m_quota[channel] + , wanted_transfer(channel), priority, c); + } +#endif + m_channel_state[channel] |= peer_info::bw_limit; + } + else + { + m_quota[channel] += ret; + } + + return ret; + } + + void peer_connection::setup_send() + { + TORRENT_ASSERT(is_single_thread()); + + if (m_disconnecting || m_send_buffer.empty()) return; + + // we may want to request more quota at this point + request_bandwidth(upload_channel); + + // if we already have an outstanding send operation, don't issue another + // one, instead accrue more send buffer to coalesce for the next write + if (m_channel_state[upload_channel] & peer_info::bw_network) + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "CORKED_WRITE", "bytes: %d" + , m_send_buffer.size()); +#endif + return; + } + + if (m_send_barrier == 0) + { + std::vector> vec; + // limit outgoing crypto messages to 1MB + int const send_bytes = std::min(m_send_buffer.size(), 1024 * 1024); + m_send_buffer.build_mutable_iovec(send_bytes, vec); + int next_barrier; + span> inject_vec; + std::tie(next_barrier, inject_vec) = hit_send_barrier(vec); + for (auto i = inject_vec.rbegin(); i != inject_vec.rend(); ++i) + { + // this const_cast is a here because chained_buffer need to be + // fixed. + auto* ptr = const_cast(i->data()); + m_send_buffer.prepend_buffer(span(ptr, i->size()) + , static_cast(i->size())); + } + set_send_barrier(next_barrier); + } + + if ((m_quota[upload_channel] == 0 || m_send_barrier == 0) + && !m_send_buffer.empty() + && !m_connecting) + { + return; + } + + int const quota_left = m_quota[upload_channel]; + if (m_send_buffer.empty() + && m_reading_bytes > 0 + && quota_left > 0) + { + if (!(m_channel_state[upload_channel] & peer_info::bw_disk)) + m_counters.inc_stats_counter(counters::num_peers_up_disk); + m_channel_state[upload_channel] |= peer_info::bw_disk; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "WAITING_FOR_DISK", "outstanding: %d" + , m_reading_bytes); +#endif + + if (!m_connecting + && !m_requests.empty() + && m_reading_bytes > m_settings.get_int(settings_pack::send_buffer_watermark) - 0x4000) + { + std::shared_ptr t = m_torrent.lock(); + + // we're stalled on the disk. We want to write and we can write + // but our send buffer is empty, waiting to be refilled from the disk + // this either means the disk is slower than the network connection + // or that our send buffer watermark is too small, because we can + // send it all before the disk gets back to us. That's why we only + // trigger this if we've also filled the allowed send buffer. The + // first request would not fill it all the way up because of the + // upload rate being virtually 0. If m_requests is empty, it doesn't + // matter anyway, because we don't have any more requests from the + // peer to hang on to the disk + if (t && t->alerts().should_post()) + { + t->alerts().emplace_alert(t->get_handle() + , performance_alert::send_buffer_watermark_too_low); + } + } + } + else + { + if (m_channel_state[upload_channel] & peer_info::bw_disk) + m_counters.inc_stats_counter(counters::num_peers_up_disk, -1); + m_channel_state[upload_channel] &= ~peer_info::bw_disk; + } + + if (!can_write()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + if (m_send_buffer.empty()) + { + peer_log(peer_log_alert::outgoing, "SEND_BUFFER_DEPLETED" + , "quota: %d buf: %d connecting: %s disconnecting: %s " + "pending_disk: %d piece-requests: %d" + , m_quota[upload_channel] + , m_send_buffer.size(), m_connecting?"yes":"no" + , m_disconnecting?"yes":"no", m_reading_bytes + , int(m_requests.size())); + } + else + { + peer_log(peer_log_alert::outgoing, "CANNOT_WRITE" + , "quota: %d buf: %d connecting: %s disconnecting: %s " + "pending_disk: %d" + , m_quota[upload_channel] + , m_send_buffer.size(), m_connecting?"yes":"no" + , m_disconnecting?"yes":"no", m_reading_bytes); + } + } +#endif + return; + } + + int const amount_to_send = std::min({ + m_send_buffer.size() + , quota_left + , m_send_barrier}); + + TORRENT_ASSERT(amount_to_send > 0); + + TORRENT_ASSERT(!(m_channel_state[upload_channel] & peer_info::bw_network)); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "ASYNC_WRITE", "bytes: %d", amount_to_send); +#endif + auto const vec = m_send_buffer.build_iovec(amount_to_send); + ADD_OUTSTANDING_ASYNC("peer_connection::on_send_data"); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(!m_socket_is_writing); + m_socket_is_writing = true; +#endif + + auto conn = self(); + m_socket->async_write_some(vec, make_handler( + std::bind(&peer_connection::on_send_data, conn, _1, _2) + , m_write_handler_storage, *this)); + + m_channel_state[upload_channel] |= peer_info::bw_network; + m_last_sent = aux::time_now(); + } + + void peer_connection::on_disk() + { + TORRENT_ASSERT(is_single_thread()); + if (!(m_channel_state[download_channel] & peer_info::bw_disk)) return; + std::shared_ptr me(self()); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "DISK", "dropped below disk buffer watermark"); +#endif + m_counters.inc_stats_counter(counters::num_peers_down_disk, -1); + m_channel_state[download_channel] &= ~peer_info::bw_disk; + setup_receive(); + } + + void peer_connection::setup_receive() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_disconnecting) return; + + if (m_recv_buffer.capacity() < 100 + && m_recv_buffer.max_receive() == 0) + { + m_recv_buffer.reserve(100); + } + + // we may want to request more quota at this point + int const buffer_size = m_recv_buffer.max_receive(); + request_bandwidth(download_channel, buffer_size); + + if (m_channel_state[download_channel] & peer_info::bw_network) return; + + if (m_quota[download_channel] == 0 + && !m_connecting) + { + return; + } + + if (!can_read()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming)) + { + peer_log(peer_log_alert::incoming, "CANNOT_READ", "quota: %d " + "can-write-to-disk: %s queue-limit: %d disconnecting: %s " + " connecting: %s" + , m_quota[download_channel] + , ((m_channel_state[download_channel] & peer_info::bw_disk)?"no":"yes") + , m_settings.get_int(settings_pack::max_queued_disk_bytes) + , (m_disconnecting?"yes":"no") + , (m_connecting?"yes":"no")); + } +#endif + // if we block reading, waiting for the disk, we will wake up + // by the disk_io_thread posting a message every time it drops + // from being at or exceeding the limit down to below the limit + return; + } + TORRENT_ASSERT(m_connected); + if (m_quota[download_channel] == 0) return; + + int const quota_left = m_quota[download_channel]; + int const max_receive = std::min(buffer_size, quota_left); + + if (max_receive == 0) return; + + span const vec = m_recv_buffer.reserve(max_receive); + TORRENT_ASSERT(!(m_channel_state[download_channel] & peer_info::bw_network)); + m_channel_state[download_channel] |= peer_info::bw_network; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "ASYNC_READ" + , "max: %d bytes", max_receive); +#endif + + ADD_OUTSTANDING_ASYNC("peer_connection::on_receive_data"); + auto conn = self(); + m_socket->async_read_some( + boost::asio::mutable_buffers_1(vec.data(), std::size_t(vec.size())), make_handler( + std::bind(&peer_connection::on_receive_data, conn, _1, _2) + , m_read_handler_storage, *this)); + } + + piece_block_progress peer_connection::downloading_piece_progress() const + { +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "ERROR" + , "downloading_piece_progress() dispatched to the base class!"); +#endif + return {}; + } + + void peer_connection::send_buffer(span buf) + { + TORRENT_ASSERT(is_single_thread()); + + int const free_space = std::min( + m_send_buffer.space_in_last_buffer(), int(buf.size())); + if (free_space > 0) + { + char* dst = m_send_buffer.append(buf.first(free_space)); + + // this should always succeed, because we checked how much space + // there was up-front + TORRENT_UNUSED(dst); + TORRENT_ASSERT(dst != nullptr); + buf = buf.subspan(free_space); + } + if (buf.empty()) return; + + // allocate a buffer and initialize the beginning of it with 'buf' + buffer snd_buf(std::max(int(buf.size()), 128), buf); + m_send_buffer.append_buffer(std::move(snd_buf), int(buf.size())); + + setup_send(); + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + void peer_connection::account_received_bytes(int const bytes_transferred) + { + // tell the receive buffer we just fed it this many bytes of incoming data + TORRENT_ASSERT(bytes_transferred > 0); + m_recv_buffer.received(bytes_transferred); + + // update the dl quota + TORRENT_ASSERT(bytes_transferred <= m_quota[download_channel]); + m_quota[download_channel] -= bytes_transferred; + + // account receiver buffer size stats to the session + m_ses.received_buffer(bytes_transferred); + + // estimate transport protocol overhead + trancieve_ip_packet(bytes_transferred, is_v6(m_remote)); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "READ" + , "%d bytes", bytes_transferred); +#endif + } + + void peer_connection::on_receive_data(const error_code& error + , std::size_t bytes_transferred) + { + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("peer_connection::on_receive_data"); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming)) + { + peer_log(peer_log_alert::incoming, "ON_RECEIVE_DATA" + , "bytes: %d %s" + , int(bytes_transferred), print_error(error).c_str()); + } +#endif + + // leave this bit set until we're done looping, reading from the socket. + // that way we don't trigger any async read calls until the end of this + // function. + TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); + + TORRENT_ASSERT(bytes_transferred > 0 || error); + + m_counters.inc_stats_counter(counters::on_read_counter); + + INVARIANT_CHECK; + + if (error) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ERROR" + , "in peer_connection::on_receive_data_impl %s" + , print_error(error).c_str()); + } +#endif + on_receive(error, bytes_transferred); + disconnect(error, operation_t::sock_read); + return; + } + + m_last_receive = aux::time_now(); + + // submit all disk jobs later + m_ses.deferred_submit_jobs(); + + // keep ourselves alive in until this function exits in + // case we disconnect + // this needs to be created before the invariant check, + // to keep the object alive through the exit check + std::shared_ptr me(self()); + + TORRENT_ASSERT(bytes_transferred > 0); + + // flush the send buffer at the end of this function + cork _c(*this); + + // if we received exactly as many bytes as we provided a receive buffer + // for. There most likely are more bytes to read, and we should grow our + // receive buffer. + TORRENT_ASSERT(int(bytes_transferred) <= m_recv_buffer.max_receive()); + bool const grow_buffer = (int(bytes_transferred) == m_recv_buffer.max_receive()); + account_received_bytes(int(bytes_transferred)); + + if (m_extension_outstanding_bytes > 0) + m_extension_outstanding_bytes -= std::min(m_extension_outstanding_bytes, int(bytes_transferred)); + + check_graceful_pause(); + if (m_disconnecting) return; + + // this is the case where we try to grow the receive buffer and try to + // drain the socket + if (grow_buffer) + { + error_code ec; + int buffer_size = int(m_socket->available(ec)); + if (ec) + { + disconnect(ec, operation_t::available); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "AVAILABLE" + , "%d bytes", buffer_size); +#endif + + request_bandwidth(download_channel, buffer_size); + + int const quota_left = m_quota[download_channel]; + if (buffer_size > quota_left) buffer_size = quota_left; + if (buffer_size > 0) + { + span const vec = m_recv_buffer.reserve(buffer_size); + std::size_t const bytes = m_socket->read_some( + boost::asio::mutable_buffers_1(vec.data(), std::size_t(vec.size())), ec); + + // this is weird. You would imagine read_some() would do this + if (bytes == 0 && !ec) ec = boost::asio::error::eof; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming)) + { + peer_log(peer_log_alert::incoming, "SYNC_READ", "max: %d ret: %d e: %s" + , buffer_size, int(bytes), ec ? ec.message().c_str() : ""); + } +#endif + + TORRENT_ASSERT(bytes > 0 || ec); + if (ec) + { + if (ec != boost::asio::error::would_block + && ec != boost::asio::error::try_again) + { + disconnect(ec, operation_t::sock_read); + return; + } + } + else + { + account_received_bytes(int(bytes)); + bytes_transferred += bytes; + } + } + } + + // feed bytes in receive buffer to upper layer by calling on_receive() + + bool const prev_choked = m_peer_choked; + int bytes = int(bytes_transferred); + int sub_transferred = 0; + do { + sub_transferred = m_recv_buffer.advance_pos(bytes); + TORRENT_ASSERT(sub_transferred > 0); + on_receive(error, std::size_t(sub_transferred)); + bytes -= sub_transferred; + if (m_disconnecting) return; + } while (bytes > 0 && sub_transferred > 0); + + // if the peer went from unchoked to choked, suggest to the receive + // buffer that it shrinks to 100 bytes + int const force_shrink = (m_peer_choked && !prev_choked) + ? 100 : 0; + m_recv_buffer.normalize(force_shrink); + + if (m_recv_buffer.max_receive() == 0) + { + // the message we're receiving is larger than our receive + // buffer, we must grow. + int const buffer_size_limit + = m_settings.get_int(settings_pack::max_peer_recv_buffer_size); + m_recv_buffer.grow(buffer_size_limit); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "GROW_BUFFER", "%d bytes" + , m_recv_buffer.capacity()); +#endif + } + + TORRENT_ASSERT(m_recv_buffer.pos_at_end()); + TORRENT_ASSERT(m_recv_buffer.packet_size() > 0); + + if (is_seed()) + { + std::shared_ptr t = m_torrent.lock(); + if (t) t->seen_complete(); + } + + // allow reading from the socket again + TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); + m_channel_state[download_channel] &= ~peer_info::bw_network; + + setup_receive(); + } + + bool peer_connection::can_write() const + { + TORRENT_ASSERT(is_single_thread()); + // if we have requests or pending data to be sent or announcements to be made + // we want to send data + return !m_send_buffer.empty() + && m_quota[upload_channel] > 0 + && (m_send_barrier > 0) + && !m_connecting; + } + + bool peer_connection::can_read() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + std::shared_ptr t = m_torrent.lock(); + + bool bw_limit = m_quota[download_channel] > 0; + + if (!bw_limit) return false; + + if (m_outstanding_bytes > 0) + { + // if we're expecting to download piece data, we might not + // want to read from the socket in case we're out of disk + // cache space right now + + if (m_channel_state[download_channel] & peer_info::bw_disk) return false; + } + + return !m_connecting && !m_disconnecting; + } + + void peer_connection::on_connection_complete(error_code const& e) + { + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("peer_connection::on_connection_complete"); + + INVARIANT_CHECK; + + // if t is nullptr, we better not be connecting, since + // we can't decrement the connecting counter + std::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t || !m_connecting); + if (m_connecting) + { + m_counters.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(m_peer_info); + m_connecting = false; + } + + if (m_disconnecting) return; + + if (e) + { + connect_failed(e); + return; + } + + TORRENT_ASSERT(!m_connected); + m_connected = true; + m_counters.inc_stats_counter(counters::num_peers_connected); + + if (m_disconnecting) return; + m_last_receive = aux::time_now(); + + error_code ec; + m_local = m_socket->local_endpoint(ec); + if (ec) + { + disconnect(ec, operation_t::getname); + return; + } + + // if there are outgoing interfaces specified, verify this + // peer is correctly bound to one of them + if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty()) + { + if (!m_ses.verify_bound_address(m_local.address() + , is_utp(*m_socket), ec)) + { + if (ec) + { + disconnect(ec, operation_t::get_interface); + return; + } + disconnect(error_code( + boost::system::errc::no_such_device, generic_category()) + , operation_t::connect); + return; + } + } + + if (is_utp(*m_socket) && m_peer_info) + { + m_peer_info->confirmed_supports_utp = true; + m_peer_info->supports_utp = false; + } + + // this means the connection just succeeded + + received_synack(is_v6(m_remote)); + + TORRENT_ASSERT(m_socket); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "COMPLETED" + , "ep: %s", print_endpoint(m_remote).c_str()); + } +#endif + + // set the socket to non-blocking, so that we can + // read the entire buffer on each read event we get +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "SET_NON_BLOCKING"); +#endif + m_socket->non_blocking(true, ec); + if (ec) + { + disconnect(ec, operation_t::iocontrol); + return; + } + + if (m_remote == m_socket->local_endpoint(ec)) + { + disconnect(errors::self_connection, operation_t::bittorrent, failure); + return; + } + + if (is_v4(m_remote) && m_settings.get_int(settings_pack::peer_tos) != 0) + { + error_code err; + m_socket->set_option(type_of_service(char(m_settings.get_int(settings_pack::peer_tos))), err); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , m_settings.get_int(settings_pack::peer_tos), err.message().c_str()); + } +#endif + } +#if defined IPV6_TCLASS + else if (is_v6(m_remote) && m_settings.get_int(settings_pack::peer_tos) != 0) + { + error_code err; + m_socket->set_option(traffic_class(char(m_settings.get_int(settings_pack::peer_tos))), err); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::outgoing)) + { + peer_log(peer_log_alert::outgoing, "SET_TOS", "tos: %d e: %s" + , m_settings.get_int(settings_pack::peer_tos), err.message().c_str()); + } +#endif + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& ext : m_extensions) + { + ext->on_connected(); + } +#endif + + on_connected(); + setup_send(); + setup_receive(); + } + + // -------------------------- + // SEND DATA + // -------------------------- + + void peer_connection::on_send_data(error_code const& error + , std::size_t const bytes_transferred) + { + TORRENT_ASSERT(is_single_thread()); + m_counters.inc_stats_counter(counters::on_write_counter); + m_ses.sent_buffer(int(bytes_transferred)); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_socket_is_writing); + m_socket_is_writing = false; +#endif + + // submit all disk jobs when we've processed all messages + // in the current message queue + m_ses.deferred_submit_jobs(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ON_SEND_DATA", "bytes: %d %s" + , int(bytes_transferred), print_error(error).c_str()); + } +#endif + + INVARIANT_CHECK; + + COMPLETE_ASYNC("peer_connection::on_send_data"); + // keep ourselves alive in until this function exits in + // case we disconnect + std::shared_ptr me(self()); + + TORRENT_ASSERT(m_channel_state[upload_channel] & peer_info::bw_network); + + m_send_buffer.pop_front(int(bytes_transferred)); + + time_point const now = clock_type::now(); + + for (auto& block : m_download_queue) + { + if (block.send_buffer_offset == pending_block::not_in_buffer) + continue; + if (block.send_buffer_offset < int(bytes_transferred)) + block.send_buffer_offset = pending_block::not_in_buffer; + else + block.send_buffer_offset -= int(bytes_transferred); + } + + m_channel_state[upload_channel] &= ~peer_info::bw_network; + + TORRENT_ASSERT(int(bytes_transferred) <= m_quota[upload_channel]); + m_quota[upload_channel] -= int(bytes_transferred); + + trancieve_ip_packet(int(bytes_transferred), is_v6(m_remote)); + + if (m_send_barrier != INT_MAX) + m_send_barrier -= int(bytes_transferred); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing, "WROTE" + , "%d bytes", int(bytes_transferred)); +#endif + + if (error) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ERROR" + , "%s in peer_connection::on_send_data", error.message().c_str()); + } +#endif + disconnect(error, operation_t::sock_write); + return; + } + if (m_disconnecting) + { + // make sure we free up all send buffers that are owned + // by the disk thread + m_send_buffer.clear(); + return; + } + + TORRENT_ASSERT(!m_connecting); + TORRENT_ASSERT(bytes_transferred > 0); + + m_last_sent = now; + +#if TORRENT_USE_ASSERTS + std::int64_t const cur_payload_ul = m_statistics.last_payload_uploaded(); + std::int64_t const cur_protocol_ul = m_statistics.last_protocol_uploaded(); +#endif + on_sent(error, bytes_transferred); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_statistics.last_payload_uploaded() - cur_payload_ul >= 0); + TORRENT_ASSERT(m_statistics.last_protocol_uploaded() - cur_protocol_ul >= 0); + std::int64_t stats_diff = m_statistics.last_payload_uploaded() - cur_payload_ul + + m_statistics.last_protocol_uploaded() - cur_protocol_ul; + TORRENT_ASSERT(stats_diff == int(bytes_transferred)); +#endif + + fill_send_buffer(); + + setup_send(); + } + +#if TORRENT_USE_INVARIANT_CHECKS + struct peer_count_t + { + peer_count_t(): num_peers(0), num_peers_with_timeouts(0), num_peers_with_nowant(0), num_not_requested(0) {} + int num_peers; + int num_peers_with_timeouts; + int num_peers_with_nowant; + int num_not_requested; +// std::vector peers; + }; + + void peer_connection::check_invariant() const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_in_use == 1337); + TORRENT_ASSERT(m_queued_time_critical <= int(m_request_queue.size())); + TORRENT_ASSERT(m_accept_fast.size() == m_accept_fast_piece_cnt.size()); + + m_recv_buffer.check_invariant(); + + for (int i = 0; i < 2; ++i) + { + if (m_channel_state[i] & peer_info::bw_limit) + { + // if we're waiting for bandwidth, we should be in the + // bandwidth manager's queue + TORRENT_ASSERT(m_ses.get_bandwidth_manager(i)->is_queued(this)); + } + } + + std::shared_ptr t = m_torrent.lock(); + +#if TORRENT_USE_INVARIANT_CHECKS \ + && !defined TORRENT_NO_EXPENSIVE_INVARIANT_CHECK + if (t && t->has_picker() && !m_disconnecting) + t->picker().check_peer_invariant(m_have_piece, peer_info_struct()); +#endif + + if (!m_disconnect_started && m_initialized) + { + // none of this matters if we're disconnecting anyway + if (t->is_finished()) + TORRENT_ASSERT(!is_interesting() || m_need_interest_update); + if (is_seed()) + TORRENT_ASSERT(upload_only()); + } + + if (m_disconnecting) + { + TORRENT_ASSERT(m_download_queue.empty()); + TORRENT_ASSERT(m_request_queue.empty()); + TORRENT_ASSERT(m_disconnect_started); + } + + TORRENT_ASSERT(m_outstanding_bytes >= 0); + if (t && t->valid_metadata() && !m_disconnecting) + { + torrent_info const& ti = t->torrent_file(); + // if the piece is fully downloaded, we might have popped it from the + // download queue already + int outstanding_bytes = 0; +// bool in_download_queue = false; + int const bs = t->block_size(); + piece_block last_block(ti.last_piece() + , (ti.piece_size(ti.last_piece()) + bs - 1) / bs); + for (std::vector::const_iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + TORRENT_ASSERT(i->block.piece_index <= last_block.piece_index); + TORRENT_ASSERT(i->block.piece_index < last_block.piece_index + || i->block.block_index <= last_block.block_index); + if (m_received_in_piece && i == m_download_queue.begin()) + { +// in_download_queue = true; + // this assert is not correct since block may have different sizes + // and may not be returned in the order they were requested +// TORRENT_ASSERT(t->to_req(i->block).length >= m_received_in_piece); + outstanding_bytes += t->to_req(i->block).length - m_received_in_piece; + } + else + { + outstanding_bytes += t->to_req(i->block).length; + } + } + //if (p && p->bytes_downloaded < p->full_block_bytes) TORRENT_ASSERT(in_download_queue); + + if (m_outstanding_bytes != outstanding_bytes) + { + std::fprintf(stderr, "m_outstanding_bytes = %d\noutstanding_bytes = %d\n" + , m_outstanding_bytes, outstanding_bytes); + } + + TORRENT_ASSERT(m_outstanding_bytes == outstanding_bytes); + } + + std::set unique; + std::transform(m_download_queue.begin(), m_download_queue.end() + , std::inserter(unique, unique.begin()), std::bind(&pending_block::block, _1)); + std::transform(m_request_queue.begin(), m_request_queue.end() + , std::inserter(unique, unique.begin()), std::bind(&pending_block::block, _1)); + TORRENT_ASSERT(unique.size() == m_download_queue.size() + m_request_queue.size()); + if (m_peer_info) + { + TORRENT_ASSERT(m_peer_info->prev_amount_upload == 0); + TORRENT_ASSERT(m_peer_info->prev_amount_download == 0); + TORRENT_ASSERT(m_peer_info->connection == this + || m_peer_info->connection == nullptr); + + if (m_peer_info->optimistically_unchoked) + TORRENT_ASSERT(!is_choked()); + } + + TORRENT_ASSERT(m_have_piece.count() == m_num_pieces); + + if (!t) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + // since this connection doesn't have a torrent reference + // no torrent should have a reference to this connection either + TORRENT_ASSERT(!m_ses.any_torrent_has_peer(this)); +#endif + return; + } + + if (t->ready_for_connections() && m_initialized) + TORRENT_ASSERT(t->torrent_file().num_pieces() == int(m_have_piece.size())); + + // in share mode we don't close redundant connections + if (m_settings.get_bool(settings_pack::close_redundant_connections) +#ifndef TORRENT_DISABLE_SHARE_MODE + && !t->share_mode() +#endif + ) + { + bool const ok_to_disconnect = + can_disconnect(errors::upload_upload_connection) + || can_disconnect(errors::uninteresting_upload_peer) + || can_disconnect(errors::too_many_requests_when_choked) + || can_disconnect(errors::timed_out_no_interest) + || can_disconnect(errors::timed_out_no_request) + || can_disconnect(errors::timed_out_inactivity); + + // make sure upload only peers are disconnected + if (t->is_upload_only() + && m_upload_only + && !m_need_interest_update + && t->valid_metadata() + && has_metadata() + && ok_to_disconnect) + TORRENT_ASSERT(m_disconnect_started || t->graceful_pause() || t->has_error()); + + if (m_upload_only + && !m_interesting + && !m_need_interest_update + && m_bitfield_received + && t->are_files_checked() + && t->valid_metadata() + && has_metadata() + && ok_to_disconnect) + TORRENT_ASSERT(m_disconnect_started); + } + + if (!m_disconnect_started && m_initialized + && m_settings.get_bool(settings_pack::close_redundant_connections)) + { + // none of this matters if we're disconnecting anyway + if (t->is_upload_only() && !m_need_interest_update) + TORRENT_ASSERT(!m_interesting || t->graceful_pause() || t->has_error()); + if (is_seed()) + TORRENT_ASSERT(m_upload_only); + } + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + if (t->has_picker()) + { + std::map num_requests; + for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) + { + // make sure this peer is not a dangling pointer + TORRENT_ASSERT(m_ses.has_peer(*i)); + peer_connection const& p = *(*i); + for (std::vector::const_iterator j = p.request_queue().begin() + , end(p.request_queue().end()); j != end; ++j) + { + ++num_requests[j->block].num_peers; + ++num_requests[j->block].num_peers_with_timeouts; + ++num_requests[j->block].num_peers_with_nowant; + ++num_requests[j->block].num_not_requested; +// num_requests[j->block].peers.push_back(&p); + } + for (std::vector::const_iterator j = p.download_queue().begin() + , end(p.download_queue().end()); j != end; ++j) + { + if (!j->not_wanted && !j->timed_out) ++num_requests[j->block].num_peers; + if (j->timed_out) ++num_requests[j->block].num_peers_with_timeouts; + if (j->not_wanted) ++num_requests[j->block].num_peers_with_nowant; +// num_requests[j->block].peers.push_back(&p); + } + } + for (std::map::iterator j = num_requests.begin() + , end(num_requests.end()); j != end; ++j) + { + piece_block b = j->first; + peer_count_t const& pc = j->second; + int count = pc.num_peers; + int count_with_timeouts = pc.num_peers_with_timeouts; + int count_with_nowant = pc.num_peers_with_nowant; + (void)count_with_timeouts; + (void)count_with_nowant; + int picker_count = t->picker().num_peers(b); + if (!t->picker().is_downloaded(b)) + TORRENT_ASSERT(picker_count == count); + } + } +#endif +/* + if (t->has_picker() && !t->is_aborted()) + { + for (std::vector::const_iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) + { + pending_block const& pb = *i; + if (pb.timed_out || pb.not_wanted) continue; + TORRENT_ASSERT(t->picker().get_block_state(pb.block) != piece_picker::block_info::state_none); + TORRENT_ASSERT(complete); + } + } +*/ +// extremely expensive invariant check +/* + if (!t->is_seed()) + { + piece_picker& p = t->picker(); + const std::vector& dlq = p.get_download_queue(); + const int blocks_per_piece = static_cast( + t->torrent_file().piece_length() / t->block_size()); + + for (std::vector::const_iterator i = + dlq.begin(); i != dlq.end(); ++i) + { + for (int j = 0; j < blocks_per_piece; ++j) + { + if (std::find(m_request_queue.begin(), m_request_queue.end() + , piece_block(i->index, j)) != m_request_queue.end() + || + std::find(m_download_queue.begin(), m_download_queue.end() + , piece_block(i->index, j)) != m_download_queue.end()) + { + TORRENT_ASSERT(i->info[j].peer == m_remote); + } + else + { + TORRENT_ASSERT(i->info[j].peer != m_remote || i->info[j].finished); + } + } + } + } +*/ + } +#endif + + void peer_connection::set_holepunch_mode() + { + m_holepunch_mode = true; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "HOLEPUNCH_MODE", "[ on ]"); +#endif + } + + void peer_connection::keep_alive() + { + TORRENT_ASSERT(is_single_thread()); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + time_duration const d = aux::time_now() - m_last_sent; + if (total_seconds(d) < timeout() / 2) return; + + if (m_connecting) return; + if (in_handshake()) return; + + // if the last send has not completed yet, do not send a keep + // alive + if (m_channel_state[upload_channel] & peer_info::bw_network) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "KEEPALIVE"); +#endif + + write_keepalive(); + } + + bool peer_connection::is_seed() const + { + TORRENT_ASSERT(is_single_thread()); + // if m_num_pieces == 0, we probably don't have the + // metadata yet. + std::shared_ptr t = m_torrent.lock(); + return m_num_pieces == m_have_piece.size() + && m_num_pieces > 0 && t && t->valid_metadata(); + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void peer_connection::set_share_mode(bool u) + { + TORRENT_ASSERT(is_single_thread()); + // if the peer is a seed, ignore share mode messages + if (is_seed()) return; + + m_share_mode = u; + } +#endif + + void peer_connection::set_upload_only(bool u) + { + TORRENT_ASSERT(is_single_thread()); + // if the peer is a seed, don't allow setting + // upload_only to false + if (m_upload_only || is_seed()) return; + + m_upload_only = u; + std::shared_ptr t = associated_torrent().lock(); + t->set_seed(m_peer_info, u); + disconnect_if_redundant(); + } + +} diff --git a/src/peer_connection_handle.cpp b/src/peer_connection_handle.cpp new file mode 100644 index 0000000..09abcde --- /dev/null +++ b/src/peer_connection_handle.cpp @@ -0,0 +1,355 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/bt_peer_connection.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include // for va_start, va_end +#endif + +namespace libtorrent { + +connection_type peer_connection_handle::type() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->type(); +} + +void peer_connection_handle::add_extension(std::shared_ptr ext) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->add_extension(std::move(ext)); +#else + TORRENT_UNUSED(ext); +#endif +} + +peer_plugin const* peer_connection_handle::find_plugin(string_view type) const +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->find_plugin(type); +#else + TORRENT_UNUSED(type); + return nullptr; +#endif +} + +bool peer_connection_handle::is_seed() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_seed(); +} + +bool peer_connection_handle::upload_only() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->upload_only(); +} + +peer_id const& peer_connection_handle::pid() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->pid(); +} + +bool peer_connection_handle::has_piece(piece_index_t i) const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->has_piece(i); +} + +bool peer_connection_handle::is_interesting() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_interesting(); +} + +bool peer_connection_handle::is_choked() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_choked(); +} + +bool peer_connection_handle::is_peer_interested() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_peer_interested(); +} + +bool peer_connection_handle::has_peer_choked() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->has_peer_choked(); +} + +void peer_connection_handle::choke_this_peer() +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->choke_this_peer(); +} + +void peer_connection_handle::maybe_unchoke_this_peer() +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->maybe_unchoke_this_peer(); +} + +void peer_connection_handle::get_peer_info(peer_info& p) const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->get_peer_info(p); +} + +torrent_handle peer_connection_handle::associated_torrent() const +{ + std::shared_ptr pc = native_handle(); + if (!pc) return torrent_handle(); + std::shared_ptr t = pc->associated_torrent().lock(); + if (!t) return torrent_handle(); + return t->get_handle(); +} + +tcp::endpoint const& peer_connection_handle::remote() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->remote(); +} + +tcp::endpoint peer_connection_handle::local_endpoint() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->local_endpoint(); +} + +void peer_connection_handle::disconnect(error_code const& ec, operation_t const op + , disconnect_severity_t const error) +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->disconnect(ec, op, error); +} + +bool peer_connection_handle::is_disconnecting() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_disconnecting(); +} + +bool peer_connection_handle::is_connecting() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_connecting(); +} + +bool peer_connection_handle::is_outgoing() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->is_outgoing(); +} + +bool peer_connection_handle::on_local_network() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->on_local_network(); +} + +bool peer_connection_handle::ignore_unchoke_slots() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->ignore_unchoke_slots(); +} + +bool peer_connection_handle::failed() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->failed(); +} + +bool peer_connection_handle::should_log(peer_log_alert::direction_t direction) const +{ +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->should_log(direction); +#else + TORRENT_UNUSED(direction); + return false; +#endif +} + +TORRENT_FORMAT(4,5) +void peer_connection_handle::peer_log(peer_log_alert::direction_t direction + , char const* event, char const* fmt, ...) const +{ +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + va_list v; + va_start(v, fmt); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#pragma clang diagnostic ignored "-Wclass-varargs" +#endif + pc->peer_log(direction, event, fmt, v); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + va_end(v); +#else // TORRENT_DISABLE_LOGGING + TORRENT_UNUSED(direction); + TORRENT_UNUSED(event); + TORRENT_UNUSED(fmt); +#endif +} + +bool peer_connection_handle::can_disconnect(error_code const& ec) const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->can_disconnect(ec); +} + +bool peer_connection_handle::has_metadata() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->has_metadata(); +} + +bool peer_connection_handle::in_handshake() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->in_handshake(); +} + +void peer_connection_handle::send_buffer(char const* begin, int size) +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->send_buffer({begin, size}); +} + +std::time_t peer_connection_handle::last_seen_complete() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->last_seen_complete(); +} + +time_point peer_connection_handle::time_of_last_unchoke() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->time_of_last_unchoke(); +} + +bool bt_peer_connection_handle::packet_finished() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->packet_finished(); +} + +bool bt_peer_connection_handle::support_extensions() const +{ + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->support_extensions(); +} + +bool bt_peer_connection_handle::supports_encryption() const +{ +#if !defined TORRENT_DISABLE_ENCRYPTION + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + return pc->supports_encryption(); +#else + return false; +#endif +} + +void bt_peer_connection_handle::switch_send_crypto(std::shared_ptr crypto) +{ +#if !defined TORRENT_DISABLE_ENCRYPTION + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->switch_send_crypto(std::move(crypto)); +#else + TORRENT_UNUSED(crypto); +#endif +} + +void bt_peer_connection_handle::switch_recv_crypto(std::shared_ptr crypto) +{ +#if !defined TORRENT_DISABLE_ENCRYPTION + std::shared_ptr pc = native_handle(); + TORRENT_ASSERT(pc); + pc->switch_recv_crypto(std::move(crypto)); +#else + TORRENT_UNUSED(crypto); +#endif +} + +std::shared_ptr bt_peer_connection_handle::native_handle() const +{ + return std::static_pointer_cast( + peer_connection_handle::native_handle()); +} + +} // namespace libtorrent diff --git a/src/peer_info.cpp b/src/peer_info.cpp new file mode 100644 index 0000000..64e93c3 --- /dev/null +++ b/src/peer_info.cpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + peer_info::peer_info() = default; + peer_info::~peer_info() = default; + peer_info::peer_info(peer_info const&) = default; + peer_info::peer_info(peer_info&&) = default; + peer_info& peer_info::operator=(peer_info const&) = default; + + // This will no longer be necessary with C++17 + constexpr peer_flags_t peer_info::interesting; + constexpr peer_flags_t peer_info::choked; + constexpr peer_flags_t peer_info::remote_interested; + constexpr peer_flags_t peer_info::remote_choked; + constexpr peer_flags_t peer_info::supports_extensions; + constexpr peer_flags_t peer_info::local_connection; + constexpr peer_flags_t peer_info::handshake; + constexpr peer_flags_t peer_info::connecting; +#if TORRENT_ABI_VERSION == 1 + constexpr peer_flags_t peer_info::queued; +#endif + constexpr peer_flags_t peer_info::on_parole; + constexpr peer_flags_t peer_info::seed; + constexpr peer_flags_t peer_info::optimistic_unchoke; + constexpr peer_flags_t peer_info::snubbed; + constexpr peer_flags_t peer_info::upload_only; + constexpr peer_flags_t peer_info::endgame_mode; + constexpr peer_flags_t peer_info::holepunched; + constexpr peer_flags_t peer_info::i2p_socket; + constexpr peer_flags_t peer_info::utp_socket; + constexpr peer_flags_t peer_info::ssl_socket; + constexpr peer_flags_t peer_info::rc4_encrypted; + constexpr peer_flags_t peer_info::plaintext_encrypted; + + constexpr peer_source_flags_t peer_info::tracker; + constexpr peer_source_flags_t peer_info::dht; + constexpr peer_source_flags_t peer_info::pex; + constexpr peer_source_flags_t peer_info::lsd; + constexpr peer_source_flags_t peer_info::resume_data; + constexpr peer_source_flags_t peer_info::incoming; + + constexpr bandwidth_state_flags_t peer_info::bw_idle; + constexpr bandwidth_state_flags_t peer_info::bw_limit; + constexpr bandwidth_state_flags_t peer_info::bw_network; + constexpr bandwidth_state_flags_t peer_info::bw_disk; + +#if TORRENT_ABI_VERSION == 1 + constexpr bandwidth_state_flags_t peer_info::bw_torrent; + constexpr bandwidth_state_flags_t peer_info::bw_global; +#endif + +} diff --git a/src/peer_list.cpp b/src/peer_list.cpp new file mode 100644 index 0000000..bf7b85a --- /dev/null +++ b/src/peer_list.cpp @@ -0,0 +1,1323 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/peer_list.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/ip_voter.hpp" // for external_ip +#include "libtorrent/broadcast_socket.hpp" // for is_v6 + +#if TORRENT_USE_ASSERTS +#include "libtorrent/socket_io.hpp" // for print_endpoint +#endif + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" // for print_endpoint +#endif + +using namespace std::placeholders; + +namespace { + + using namespace libtorrent; + + struct match_peer_endpoint + { + match_peer_endpoint(address const& addr, std::uint16_t port) + : m_addr(addr), m_port(port) + {} + + bool operator()(torrent_peer const* p) const + { + TORRENT_ASSERT(p->in_use); + return p->address() == m_addr && p->port == m_port; + } + + address const& m_addr; + std::uint16_t m_port; + }; +} + +namespace libtorrent { + + constexpr erase_peer_flags_t peer_list::force_erase; + + peer_list::peer_list(torrent_peer_allocator_interface& alloc) + : m_locked_peer(nullptr) + , m_peer_allocator(alloc) + , m_num_seeds(0) + , m_finished(0) + { + thread_started(); + } + + void peer_list::clear() + { + for (auto const p : m_peers) + m_peer_allocator.free_peer_entry(p); + m_peers.clear(); + m_num_connect_candidates = 0; + } + + peer_list::~peer_list() + { + for (auto const p : m_peers) + m_peer_allocator.free_peer_entry(p); + } + + void peer_list::set_max_failcount(torrent_state* state) + { + INVARIANT_CHECK; + if (state->max_failcount == m_max_failcount) return; + + recalculate_connect_candidates(state); + } + + // disconnects and removes all peers that are now filtered fills in 'erased' + // with torrent_peer pointers that were removed from the peer list. Any + // references to these peers must be cleared immediately after this call + // returns. For instance, in the piece picker. + void peer_list::apply_ip_filter(ip_filter const& filter + , torrent_state* state, std::vector
    & banned) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + for (iterator i = m_peers.begin(); i != m_peers.end();) + { + if ((filter.access((*i)->address()) & ip_filter::blocked) == 0) + { + ++i; + continue; + } + if (*i == m_locked_peer) + { + ++i; + continue; + } + + int const current = int(i - m_peers.begin()); + TORRENT_ASSERT(current >= 0); + TORRENT_ASSERT(m_peers.size() > 0); + TORRENT_ASSERT(i != m_peers.end()); + + if ((*i)->connection) + { + // disconnecting the peer here may also delete the + // peer_info_struct. If that is the case, just continue + size_t count = m_peers.size(); + peer_connection_interface* p = (*i)->connection; + + banned.push_back(p->remote().address()); + + p->disconnect(errors::banned_by_ip_filter + , operation_t::bittorrent); + + // what *i refers to has changed, i.e. cur was deleted + if (m_peers.size() < count) + { + i = m_peers.begin() + current; + continue; + } + TORRENT_ASSERT((*i)->connection == nullptr + || (*i)->connection->peer_info_struct() == nullptr); + } + + erase_peer(i, state); + i = m_peers.begin() + current; + } + } + + void peer_list::clear_peer_prio() + { + INVARIANT_CHECK; + for (auto& p : m_peers) + p->peer_rank = 0; + } + + // disconnects and removes all peers that are now filtered + // fills in 'erased' with torrent_peer pointers that were removed + // from the peer list. Any references to these peers must be cleared + // immediately after this call returns. For instance, in the piece picker. + void peer_list::apply_port_filter(port_filter const& filter + , torrent_state* state, std::vector
    & banned) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + for (iterator i = m_peers.begin(); i != m_peers.end();) + { + if ((filter.access((*i)->port) & port_filter::blocked) == 0) + { + ++i; + continue; + } + if (*i == m_locked_peer) + { + ++i; + continue; + } + + int const current = int(i - m_peers.begin()); + TORRENT_ASSERT(current >= 0); + TORRENT_ASSERT(m_peers.size() > 0); + TORRENT_ASSERT(i != m_peers.end()); + + if ((*i)->connection) + { + // disconnecting the peer here may also delete the + // peer_info_struct. If that is the case, just continue + int count = int(m_peers.size()); + peer_connection_interface* p = (*i)->connection; + + banned.push_back(p->remote().address()); + + p->disconnect(errors::banned_by_port_filter, operation_t::bittorrent); + // what *i refers to has changed, i.e. cur was deleted + if (int(m_peers.size()) < count) + { + i = m_peers.begin() + current; + continue; + } + TORRENT_ASSERT((*i)->connection == nullptr + || (*i)->connection->peer_info_struct() == nullptr); + } + + erase_peer(i, state); + i = m_peers.begin() + current; + } + } + + void peer_list::erase_peer(torrent_peer* p, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(m_locked_peer != p); + + auto const addr = p->address(); + auto const range = find_peers(addr); + auto const iter = std::find_if(range.first, range.second, match_peer_endpoint(addr, p->port)); + if (iter == range.second) return; + erase_peer(iter, state); + } + + // any peer that is erased from m_peers will be + // erased through this function. This way we can make + // sure that any references to the peer are removed + // as well, such as in the piece picker. + void peer_list::erase_peer(iterator i, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + TORRENT_ASSERT(i != m_peers.end()); + TORRENT_ASSERT(m_locked_peer != *i); + + state->erased.push_back(*i); + if ((*i)->seed) + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + if (is_connect_candidate(**i)) + update_connect_candidates(-1); + TORRENT_ASSERT(m_num_connect_candidates < int(m_peers.size())); + if (m_round_robin > i - m_peers.begin()) --m_round_robin; + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + + // if this peer is in the connect candidate + // cache, erase it from there as well + std::vector::iterator ci = std::find(m_candidate_cache.begin(), m_candidate_cache.end(), *i); + if (ci != m_candidate_cache.end()) m_candidate_cache.erase(ci); + + m_peer_allocator.free_peer_entry(*i); + m_peers.erase(i); + } + + bool peer_list::should_erase_immediately(torrent_peer const& p) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(p.in_use); + if (&p == m_locked_peer) return false; + return p.peer_source() == peer_info::resume_data; + } + + bool peer_list::is_erase_candidate(torrent_peer const& pe) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(pe.in_use); + if (&pe == m_locked_peer) return false; + if (pe.connection) return false; + if (is_connect_candidate(pe)) return false; + + return (pe.failcount > 0) + || (pe.peer_source() == peer_info::resume_data); + } + + bool peer_list::is_force_erase_candidate(torrent_peer const& pe) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(pe.in_use); + if (&pe == m_locked_peer) return false; + return pe.connection == nullptr; + } + + void peer_list::erase_peers(torrent_state* state, erase_peer_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + int max_peerlist_size = state->max_peerlist_size; + + if (max_peerlist_size == 0 || m_peers.empty()) return; + + int erase_candidate = -1; + int force_erase_candidate = -1; + + if (bool(m_finished) != state->is_finished) + recalculate_connect_candidates(state); + + int round_robin = aux::numeric_cast(random(std::uint32_t(m_peers.size() - 1))); + + int low_watermark = max_peerlist_size * 95 / 100; + if (low_watermark == max_peerlist_size) --low_watermark; + + for (int iterations = std::min(int(m_peers.size()), 300); + iterations > 0; --iterations) + { + if (int(m_peers.size()) < low_watermark) + break; + + if (round_robin == int(m_peers.size())) round_robin = 0; + + torrent_peer& pe = *m_peers[round_robin]; + TORRENT_ASSERT(pe.in_use); + int const current = round_robin; + + if (is_erase_candidate(pe) + && (erase_candidate == -1 + || !compare_peer_erase(*m_peers[erase_candidate], pe))) + { + if (should_erase_immediately(pe)) + { + if (erase_candidate > current) --erase_candidate; + if (force_erase_candidate > current) --force_erase_candidate; + TORRENT_ASSERT(current >= 0 && current < int(m_peers.size())); + erase_peer(m_peers.begin() + current, state); + continue; + } + else + { + erase_candidate = current; + } + } + if (is_force_erase_candidate(pe) + && (force_erase_candidate == -1 + || !compare_peer_erase(*m_peers[force_erase_candidate], pe))) + { + force_erase_candidate = current; + } + + ++round_robin; + } + + if (erase_candidate > -1) + { + TORRENT_ASSERT(erase_candidate >= 0 && erase_candidate < int(m_peers.size())); + erase_peer(m_peers.begin() + erase_candidate, state); + } + else if ((flags & force_erase) && force_erase_candidate > -1) + { + TORRENT_ASSERT(force_erase_candidate >= 0 && force_erase_candidate < int(m_peers.size())); + erase_peer(m_peers.begin() + force_erase_candidate, state); + } + } + + // returns true if the peer was actually banned + bool peer_list::ban_peer(torrent_peer* p) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + + if (is_connect_candidate(*p)) + update_connect_candidates(-1); + + p->banned = true; + TORRENT_ASSERT(!is_connect_candidate(*p)); + return true; + } + + void peer_list::set_connection(torrent_peer* p, peer_connection_interface* c) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(c); + + const bool was_conn_cand = is_connect_candidate(*p); + p->connection = c; + if (was_conn_cand) update_connect_candidates(-1); + } + + void peer_list::inc_failcount(torrent_peer* p) + { + INVARIANT_CHECK; + // failcount is a 5 bit value + if (p->failcount == 31) return; + + bool const was_conn_cand = is_connect_candidate(*p); + ++p->failcount; + if (was_conn_cand && !is_connect_candidate(*p)) + update_connect_candidates(-1); + } + + void peer_list::set_failcount(torrent_peer* p, int const f) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(p->in_use); + bool const was_conn_cand = is_connect_candidate(*p); + p->failcount = aux::numeric_cast(f); + if (was_conn_cand != is_connect_candidate(*p)) + { + update_connect_candidates(was_conn_cand ? -1 : 1); + } + } + + bool peer_list::is_connect_candidate(torrent_peer const& p) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(p.in_use); + if (p.connection + || p.banned + || p.web_seed + || !p.connectable + || (p.seed && m_finished) + || int(p.failcount) >= m_max_failcount) + return false; + + return true; + } + + void peer_list::find_connect_candidates(std::vector& peers + , int session_time, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + const int candidate_count = 10; + peers.reserve(candidate_count); + + int erase_candidate = -1; + + if (bool(m_finished) != state->is_finished) + recalculate_connect_candidates(state); + + external_ip const& external = state->ip; + int external_port = state->port; + + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + + int max_peerlist_size = state->max_peerlist_size; + + // TODO: 2 it would be nice if there was a way to iterate over these + // torrent_peer objects in the order they are allocated in the pool + // instead. It would probably be more efficient + for (int iterations = std::min(int(m_peers.size()), 300); + iterations > 0; --iterations) + { + ++state->loop_counter; + + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + + torrent_peer& pe = *m_peers[m_round_robin]; + TORRENT_ASSERT(pe.in_use); + int current = m_round_robin; + + // if the number of peers is growing large + // we need to start weeding. + + if (int(m_peers.size()) >= max_peerlist_size * 0.95 + && max_peerlist_size > 0) + { + if (is_erase_candidate(pe) + && (erase_candidate == -1 + || !compare_peer_erase(*m_peers[erase_candidate], pe))) + { + if (should_erase_immediately(pe)) + { + if (erase_candidate > current) --erase_candidate; + erase_peer(m_peers.begin() + current, state); + continue; + } + else + { + erase_candidate = current; + } + } + } + + ++m_round_robin; + + if (!is_connect_candidate(pe)) continue; + + if (pe.last_connected + && session_time - pe.last_connected < + (int(pe.failcount) + 1) * state->min_reconnect_time) + continue; + + // compare peer returns true if lhs is better than rhs. In this + // case, it returns true if the current candidate is better than + // pe, which is the peer m_round_robin points to. If it is, just + // keep looking. + if (peers.size() == candidate_count + && compare_peer(peers.back(), &pe, external, external_port)) continue; + + if (peers.size() >= candidate_count) + peers.resize(candidate_count - 1); + + // insert this candidate sorted into peers + auto const i = std::lower_bound(peers.begin(), peers.end() + , &pe, std::bind(&peer_list::compare_peer, this, _1, _2, std::cref(external), external_port)); + + peers.insert(i, &pe); + } + + if (erase_candidate > -1) + { + erase_peer(m_peers.begin() + erase_candidate, state); + } + } + + bool peer_list::new_connection(peer_connection_interface& c, int session_time + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); +// TORRENT_ASSERT(!c.is_outgoing()); + + INVARIANT_CHECK; + + TORRENT_ASSERT(!state->is_paused); + + iterator iter; + torrent_peer* i = nullptr; + + bool found = false; + if (state->allow_multiple_connections_per_ip) + { + auto const& remote = c.remote(); + auto const addr = remote.address(); + auto const range = find_peers(addr); + iter = std::find_if(range.first, range.second, match_peer_endpoint(addr, remote.port())); + + if (iter != range.second) + { + TORRENT_ASSERT((*iter)->in_use); + found = true; + } + } + else + { + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , c.remote().address(), peer_address_compare() + ); + + if (iter != m_peers.end() && (*iter)->address() == c.remote().address()) + { + TORRENT_ASSERT((*iter)->in_use); + found = true; + } + } + + // make sure the iterator we got is properly sorted relative + // to the connection's address +// TORRENT_ASSERT(m_peers.empty() +// || (iter == m_peers.end() && (*(iter-1))->address() < c.remote().address()) +// || (iter != m_peers.end() && c.remote().address() < (*iter)->address()) +// || (iter != m_peers.end() && iter != m_peers.begin() && (*(iter-1))->address() < c.remote().address())); + + if (found) + { + i = *iter; + TORRENT_ASSERT(i->in_use); + TORRENT_ASSERT(i->connection != &c); + TORRENT_ASSERT(i->address() == c.remote().address()); + +#ifndef TORRENT_DISABLE_LOGGING + if (i->connection != nullptr && c.should_log(peer_log_alert::info)) + { + c.peer_log(peer_log_alert::info, "DUPLICATE PEER", "this: \"%s\" that: \"%s\"" + , print_address(c.remote().address()).c_str() + , print_address(i->address()).c_str()); + } +#endif + if (i->banned) + { + c.disconnect(errors::peer_banned, operation_t::bittorrent); + return false; + } + + if (i->connection != nullptr) + { + bool const self_connection = + i->connection->remote() == c.local_endpoint() + || i->connection->local_endpoint() == c.remote(); + + if (self_connection) + { + c.disconnect(errors::self_connection, operation_t::bittorrent, peer_connection_interface::failure); + TORRENT_ASSERT(i->connection->peer_info_struct() == i); + i->connection->disconnect(errors::self_connection, operation_t::bittorrent, peer_connection_interface::failure); + TORRENT_ASSERT(i->connection == nullptr); + return false; + } + + TORRENT_ASSERT(i->connection != &c); + // the new connection is a local (outgoing) connection + // or the current one is already connected + if (i->connection->is_outgoing() == c.is_outgoing()) + { + // if the other end connected to us both times, just drop + // the second one. Or if we made both connections. + c.disconnect(errors::duplicate_peer_id, operation_t::bittorrent); + return false; + } + else + { + // at this point, we need to disconnect either + // i->connection or c. In order for both this client + // and the client on the other end to decide to + // disconnect the same one, we need a consistent rule to + // select which one. + + bool const outgoing1 = c.is_outgoing(); + + // for this, we compare our ports and whoever has the lower port + // should be the one keeping its outgoing connection. Since + // outgoing ports are selected at random by the OS, we need to + // be careful to only look at the target end of a connection for + // the endpoint. + + int const our_port = outgoing1 ? i->connection->local_endpoint().port() : c.local_endpoint().port(); + int const other_port = outgoing1 ? c.remote().port() : i->connection->remote().port(); + + // decide which peer connection to disconnect + // if the ports are equal, pick on at random + bool const disconnect1 = ((our_port < other_port) && !outgoing1) + || ((our_port > other_port) && outgoing1) + || ((our_port == other_port) && random(1)); + +#ifndef TORRENT_DISABLE_LOGGING + if (c.should_log(peer_log_alert::info)) + { + c.peer_log(peer_log_alert::info, "DUPLICATE_PEER_RESOLUTION" + , "our: %d other: %d disconnecting: %s" + , our_port, other_port, disconnect1 ? "yes" : "no"); + i->connection->peer_log(peer_log_alert::info, "DUPLICATE_PEER_RESOLUTION" + , "our: %d other: %d disconnecting: %s" + , our_port, other_port, disconnect1 ? "no" : "yes"); + } +#endif + + if (disconnect1) + { + c.disconnect(errors::duplicate_peer_id, operation_t::bittorrent); + return false; + } + TORRENT_ASSERT(m_locked_peer == nullptr); + m_locked_peer = i; + i->connection->disconnect(errors::duplicate_peer_id, operation_t::bittorrent); + m_locked_peer = nullptr; + } + } + + if (is_connect_candidate(*i)) + update_connect_candidates(-1); + } + else + { + // we don't have any info about this peer. + // add a new entry + + if (state->max_peerlist_size + && int(m_peers.size()) >= state->max_peerlist_size) + { + // this may invalidate our iterator! + erase_peers(state, force_erase); + if (int(m_peers.size()) >= state->max_peerlist_size) + { + c.disconnect(errors::too_many_connections, operation_t::bittorrent); + return false; + } + // restore it + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , c.remote().address(), peer_address_compare() + ); + } + + bool const is_v6 = lt::is_v6(c.remote()); + torrent_peer* p = m_peer_allocator.allocate_peer_entry( + is_v6 ? torrent_peer_allocator_interface::ipv6_peer_type + : torrent_peer_allocator_interface::ipv4_peer_type); + if (p == nullptr) return false; + + if (is_v6) + p = new (p) ipv6_peer(c.remote(), false, {}); + else + p = new (p) ipv4_peer(c.remote(), false, {}); + + iter = m_peers.insert(iter, p); + + if (m_round_robin >= iter - m_peers.begin()) ++m_round_robin; + + i = *iter; + + i->source = static_cast(peer_info::incoming); + } + + TORRENT_ASSERT(i); + c.set_peer_info(i); + TORRENT_ASSERT(i->connection == nullptr); + c.add_stat(std::int64_t(i->prev_amount_download) << 10, std::int64_t(i->prev_amount_upload) << 10); + + i->prev_amount_download = 0; + i->prev_amount_upload = 0; + i->connection = &c; + TORRENT_ASSERT(i->connection); + if (!c.fast_reconnect()) + i->last_connected = std::uint16_t(session_time); + + // this cannot be a connect candidate anymore, since i->connection is set + TORRENT_ASSERT(!is_connect_candidate(*i)); + TORRENT_ASSERT(has_connection(&c)); + return true; + } + + bool peer_list::update_peer_port(int const port, torrent_peer* p + , peer_source_flags_t const src, torrent_state* state) + { + TORRENT_ASSERT(p != nullptr); + TORRENT_ASSERT(p->connection); + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + if (p->port == port) return true; + + if (state->allow_multiple_connections_per_ip) + { + auto const addr = p->address(); + auto const range = find_peers(addr); + auto const i = std::find_if(range.first, range.second + , match_peer_endpoint(addr, std::uint16_t(port))); + if (i != range.second) + { + torrent_peer& pp = **i; + TORRENT_ASSERT(pp.in_use); + if (pp.connection) + { + bool const was_conn_cand = is_connect_candidate(pp); + // if we already have an entry with this + // new endpoint, disconnect this one + pp.connectable = true; + pp.source |= static_cast(src); + if (!was_conn_cand && is_connect_candidate(pp)) + update_connect_candidates(1); + // calling disconnect() on a peer, may actually end + // up "garbage collecting" its torrent_peer entry + // as well, if it's considered useless (which this specific) + // case will, since it was an incoming peer that just disconnected + // and we allow multiple connections per IP. Because of that, + // we need to make sure we don't let it do that, locking i + TORRENT_ASSERT(m_locked_peer == nullptr); + m_locked_peer = p; + p->connection->disconnect(errors::duplicate_peer_id, operation_t::bittorrent); + m_locked_peer = nullptr; + erase_peer(p, state); + return false; + } + erase_peer(i, state); + } + } +#if TORRENT_USE_ASSERTS + else + { +#if TORRENT_USE_I2P + if (!p->is_i2p_addr) +#endif + { + std::pair range = find_peers(p->address()); + TORRENT_ASSERT(std::distance(range.first, range.second) == 1); + } + } +#endif + + bool const was_conn_cand = is_connect_candidate(*p); + p->port = std::uint16_t(port); + p->source |= static_cast(src); + p->connectable = true; + + if (was_conn_cand != is_connect_candidate(*p)) + update_connect_candidates(was_conn_cand ? -1 : 1); + return true; + } + + // it's important that we don't dereference + // p here, since it is allowed to be a dangling + // pointer. see smart_ban.cpp + bool peer_list::has_peer(torrent_peer const* p) const + { + TORRENT_ASSERT(is_single_thread()); + // find p in m_peers + return std::find(m_peers.begin(), m_peers.end(), p) != m_peers.end(); + } + + void peer_list::set_seed(torrent_peer* p, bool s) + { + TORRENT_ASSERT(is_single_thread()); + if (p == nullptr) return; + TORRENT_ASSERT(p->in_use); + if (p->seed == s) return; + bool const was_conn_cand = is_connect_candidate(*p); + p->seed = s; + if (was_conn_cand && !is_connect_candidate(*p)) + update_connect_candidates(-1); + + if (p->web_seed) return; + if (s) + { + TORRENT_ASSERT(m_num_seeds < int(m_peers.size())); + ++m_num_seeds; + } + else + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + } + + // this is an internal function + bool peer_list::insert_peer(torrent_peer* p, iterator iter + , pex_flags_t const flags + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(p); + TORRENT_ASSERT(p->in_use); + + int const max_peerlist_size = state->max_peerlist_size; + + if (max_peerlist_size + && int(m_peers.size()) >= max_peerlist_size) + { + if (p->peer_source() == peer_info::resume_data) return false; + + erase_peers(state); + if (int(m_peers.size()) >= max_peerlist_size) + return false; + + // since some peers were removed, we need to + // update the iterator to make it valid again +#if TORRENT_USE_I2P + if (p->is_i2p_addr) + { + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , p->dest(), peer_address_compare()); + } + else +#endif + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , p->address(), peer_address_compare()); + } + + iter = m_peers.insert(iter, p); + + if (m_round_robin >= iter - m_peers.begin()) ++m_round_robin; + +#if !defined TORRENT_DISABLE_ENCRYPTION + if (flags & pex_encryption) p->pe_support = true; +#endif + if (flags & pex_seed) + { + p->seed = true; + TORRENT_ASSERT(m_num_seeds < int(m_peers.size())); + ++m_num_seeds; + } + if (flags & pex_utp) + p->supports_utp = true; + if (flags & pex_holepunch) + p->supports_holepunch = true; + if (is_connect_candidate(*p)) + update_connect_candidates(1); + + return true; + } + + void peer_list::update_peer(torrent_peer* p, peer_source_flags_t const src + , pex_flags_t const flags, tcp::endpoint const& remote) + { + TORRENT_ASSERT(is_single_thread()); + bool const was_conn_cand = is_connect_candidate(*p); + + TORRENT_ASSERT(p->in_use); + p->connectable = true; + + TORRENT_ASSERT(p->address() == remote.address()); + p->port = remote.port(); + p->source |= static_cast(src); + + // if this peer has failed before, decrease the + // counter to allow it another try, since somebody + // else is apparently able to connect to it + // only trust this if it comes from the tracker + if (p->failcount > 0 && src == peer_info::tracker) + --p->failcount; + + // if we're connected to this peer + // we already know if it's a seed or not + // so we don't have to trust this source + if ((flags & pex_seed) && !p->connection) + { + if (!p->seed) + { + TORRENT_ASSERT(m_num_seeds < int(m_peers.size())); + ++m_num_seeds; + } + p->seed = true; + } + if (flags & pex_utp) + p->supports_utp = true; + if (flags & pex_holepunch) + p->supports_holepunch = true; + + if (was_conn_cand != is_connect_candidate(*p)) + { + update_connect_candidates(was_conn_cand ? -1 : 1); + } + } + + void peer_list::update_connect_candidates(int delta) + { + TORRENT_ASSERT(is_single_thread()); + if (delta == 0) return; + m_num_connect_candidates += delta; + if (delta < 0) + { + TORRENT_ASSERT(m_num_connect_candidates >= 0); + if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; + } + } + +#if TORRENT_USE_I2P + torrent_peer* peer_list::add_i2p_peer(string_view const destination + , peer_source_flags_t const src, pex_flags_t const flags + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + iterator iter = std::lower_bound(m_peers.begin(), m_peers.end() + , destination, peer_address_compare()); + + if (iter != m_peers.end() && (*iter)->dest() == destination) + { + update_peer(*iter, src, flags, tcp::endpoint()); + return *iter; + } + + // we don't have any info about this peer. + // add a new entry + torrent_peer* p = m_peer_allocator.allocate_peer_entry( + torrent_peer_allocator_interface::i2p_peer_type); + if (p == nullptr) return nullptr; + p = new (p) i2p_peer(destination, true, src); + + if (!insert_peer(p, iter, flags, state)) + { + m_peer_allocator.free_peer_entry(p); + return nullptr; + } + return p; + } +#endif // TORRENT_USE_I2P + + // if this returns non-nullptr, the torrent need to post status update + torrent_peer* peer_list::add_peer(tcp::endpoint const& remote + , peer_source_flags_t const src, pex_flags_t const flags + , torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + auto const remote_address = remote.address(); + + // just ignore the obviously invalid entries + if (remote_address == address() || remote.port() == 0) + return nullptr; + + // don't allow link-local IPv6 addresses since they + // can't be used like normal addresses, they require an interface + // and will just cause connect() to fail with EINVAL + if (remote_address.is_v6() && remote_address.to_v6().is_link_local()) + return nullptr; + + iterator iter; + torrent_peer* p = nullptr; + + bool found = false; + if (state->allow_multiple_connections_per_ip) + { + auto const range = find_peers(remote_address); + iter = std::find_if(range.first, range.second + , match_peer_endpoint(remote_address, remote.port())); + if (iter != range.second) found = true; + } + else + { + iter = std::lower_bound(m_peers.begin(), m_peers.end() + , remote_address, peer_address_compare()); + + if (iter != m_peers.end() && (*iter)->address() == remote_address) found = true; + } + + if (!found) + { + // we don't have any info about this peer. + // add a new entry + + bool const is_v6 = remote_address.is_v6(); + p = m_peer_allocator.allocate_peer_entry( + is_v6 ? torrent_peer_allocator_interface::ipv6_peer_type + : torrent_peer_allocator_interface::ipv4_peer_type); + if (p == nullptr) return nullptr; + + if (is_v6) + p = new (p) ipv6_peer(remote, true, src); + else + p = new (p) ipv4_peer(remote, true, src); + + try + { + if (!insert_peer(p, iter, flags, state)) + { + m_peer_allocator.free_peer_entry(p); + return nullptr; + } + } + catch (std::exception const&) + { + m_peer_allocator.free_peer_entry(p); + return nullptr; + } + state->first_time_seen = true; + } + else + { + p = *iter; + TORRENT_ASSERT(p->in_use); + update_peer(p, src, flags, remote); + state->first_time_seen = false; + } + + return p; + } + + torrent_peer* peer_list::connect_one_peer(int session_time, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (bool(m_finished) != state->is_finished) + recalculate_connect_candidates(state); + + // clear out any peers from the cache that no longer + // are connection candidates + for (auto i = m_candidate_cache.begin(); i != m_candidate_cache.end();) + { + if (!is_connect_candidate(**i)) + i = m_candidate_cache.erase(i); + else + ++i; + } + + if (m_candidate_cache.empty()) + { + find_connect_candidates(m_candidate_cache, session_time, state); + if (m_candidate_cache.empty()) return nullptr; + } + + torrent_peer* p = m_candidate_cache.front(); + m_candidate_cache.erase(m_candidate_cache.begin()); + + TORRENT_ASSERT(p->in_use); + + TORRENT_ASSERT(!p->banned); + TORRENT_ASSERT(!p->connection); + TORRENT_ASSERT(p->connectable); + + // this should hold because find_connect_candidates should have done this + TORRENT_ASSERT(bool(m_finished) == state->is_finished); + + TORRENT_ASSERT(is_connect_candidate(*p)); + return p; + } + + // this is called whenever a peer connection is closed + void peer_list::connection_closed(const peer_connection_interface& c + , int session_time, torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + torrent_peer* p = c.peer_info_struct(); + + // if we couldn't find the connection in our list, just ignore it. + if (p == nullptr) return; + + TORRENT_ASSERT(p->in_use); + +#if TORRENT_USE_INVARIANT_CHECKS + // web seeds are special, they're not connected via the peer list + // so they're not kept in m_peers + TORRENT_ASSERT(p->web_seed + || std::any_of(m_peers.begin(), m_peers.end() + , [&c](torrent_peer const* tp) + { + TORRENT_ASSERT(tp->in_use); + return tp->connection == &c; + })); +#endif + + TORRENT_ASSERT(p->connection == &c); + TORRENT_ASSERT(!is_connect_candidate(*p)); + + p->connection = nullptr; + p->optimistically_unchoked = false; + + // if fast reconnect is true, we won't + // update the timestamp, and it will remain + // the time when we initiated the connection. + if (!c.fast_reconnect()) + p->last_connected = std::uint16_t(session_time); + + if (c.failed()) + { + // failcount is a 5 bit value + if (p->failcount < 31) ++p->failcount; + } + + if (is_connect_candidate(*p)) + update_connect_candidates(1); + + // if we're already a seed, it's not as important + // to keep all the possibly stale peers + // if we're not a seed, but we have too many peers + // start weeding the ones we only know from resume + // data first + // at this point it may be tempting to erase peers + // from the peer list, but keep in mind that we might + // have gotten to this point through new_connection, just + // disconnecting an old peer, relying on this torrent_peer + // to still exist when we get back there, to assign the new + // peer connection pointer to it. The peer list must + // be left intact. + + // if we allow multiple connections per IP, and this peer + // was incoming and it never advertised its listen + // port, we don't really know which peer it was. In order + // to avoid adding one entry for every single connection + // the peer makes to us, don't save this entry + if (state->allow_multiple_connections_per_ip + && !p->connectable + && p != m_locked_peer) + { + erase_peer(p, state); + } + } + + void peer_list::recalculate_connect_candidates(torrent_state* state) + { + TORRENT_ASSERT(is_single_thread()); + + m_num_connect_candidates = 0; + m_finished = state->is_finished; + m_max_failcount = state->max_failcount; + + m_num_connect_candidates += static_cast(std::count_if(m_peers.begin(), m_peers.end() + , [this](torrent_peer const* p) { return this->is_connect_candidate(*p); } )); + +#if TORRENT_USE_INVARIANT_CHECKS + // the invariant is not likely to be upheld at the entry of this function + // but it is likely to have been restored by the end of it + check_invariant(); +#endif + } + +#if TORRENT_USE_ASSERTS + bool peer_list::has_connection(const peer_connection_interface* c) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(c); + + iterator iter = std::lower_bound(m_peers.begin(), m_peers.end() + , c->remote().address(), peer_address_compare()); + + if (iter != m_peers.end() && (*iter)->address() == c->remote().address()) + return true; + + return std::any_of(m_peers.begin(), m_peers.end() + , [c](torrent_peer const* p) + { + TORRENT_ASSERT(p->in_use); + return p->connection == c + || (p->ip() == c->remote() && p->connectable); + }); + } +#endif + +#if TORRENT_USE_INVARIANT_CHECKS + void peer_list::check_invariant() const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_num_connect_candidates >= 0); + TORRENT_ASSERT(m_num_connect_candidates <= int(m_peers.size())); + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + int connect_candidates = 0; + + const_iterator prev = m_peers.end(); + for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) + { + if (prev != m_peers.end()) ++prev; + if (i == m_peers.begin() + 1) prev = m_peers.begin(); + if (prev != m_peers.end()) + { + TORRENT_ASSERT(!((*i)->address() < (*prev)->address())); + } + torrent_peer const& p = **i; + TORRENT_ASSERT(p.in_use); + if (is_connect_candidate(p)) ++connect_candidates; + if (!p.connection) + { + continue; + } + if (p.optimistically_unchoked) + { + TORRENT_ASSERT(p.connection); + TORRENT_ASSERT(!p.connection->is_choked()); + } + TORRENT_ASSERT(p.connection->peer_info_struct() == nullptr + || p.connection->peer_info_struct() == &p); + } + + TORRENT_ASSERT(m_num_connect_candidates == connect_candidates); +#endif // TORRENT_EXPENSIVE_INVARIANT_CHECKS + + } +#endif + + // this returns true if lhs is a better erase candidate than rhs + bool peer_list::compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) const + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(lhs.connection == nullptr); + TORRENT_ASSERT(rhs.connection == nullptr); + + // primarily, prefer getting rid of peers we've already tried and failed + if (lhs.failcount != rhs.failcount) + return lhs.failcount > rhs.failcount; + + bool const lhs_resume_data_source = lhs.peer_source() == peer_info::resume_data; + bool const rhs_resume_data_source = rhs.peer_source() == peer_info::resume_data; + + // prefer to drop peers whose only source is resume data + if (lhs_resume_data_source != rhs_resume_data_source) + return int(lhs_resume_data_source) > int(rhs_resume_data_source); + + if (lhs.connectable != rhs.connectable) + return int(lhs.connectable) < int(rhs.connectable); + + return lhs.trust_points < rhs.trust_points; + } + + // this returns true if lhs is a better connect candidate than rhs + bool peer_list::compare_peer(torrent_peer const* lhs, torrent_peer const* rhs + , external_ip const& external, int external_port) const + { + TORRENT_ASSERT(is_single_thread()); + // prefer peers with lower failcount + if (lhs->failcount != rhs->failcount) + return lhs->failcount < rhs->failcount; + + // Local peers should always be tried first + bool const lhs_local = is_local(lhs->address()); + bool const rhs_local = is_local(rhs->address()); + if (lhs_local != rhs_local) return int(lhs_local) > int(rhs_local); + + if (lhs->last_connected != rhs->last_connected) + return lhs->last_connected < rhs->last_connected; + + int const lhs_rank = source_rank(lhs->peer_source()); + int const rhs_rank = source_rank(rhs->peer_source()); + if (lhs_rank != rhs_rank) return lhs_rank > rhs_rank; + + std::uint32_t const lhs_peer_rank = lhs->rank(external, external_port); + std::uint32_t const rhs_peer_rank = rhs->rank(external, external_port); + return lhs_peer_rank > rhs_peer_rank; + } +} diff --git a/src/performance_counters.cpp b/src/performance_counters.cpp new file mode 100644 index 0000000..ab684ec --- /dev/null +++ b/src/performance_counters.cpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/assert.hpp" +#include // for memset + +namespace libtorrent { + + counters::counters() TORRENT_COUNTER_NOEXCEPT + { +#ifdef ATOMIC_LLONG_LOCK_FREE + for (auto& counter : m_stats_counter) + counter.store(0, std::memory_order_relaxed); +#else + m_stats_counter.fill(0); +#endif + } + + counters::counters(counters const& c) TORRENT_COUNTER_NOEXCEPT + { +#ifdef ATOMIC_LLONG_LOCK_FREE + for (int i = 0; i < m_stats_counter.end_index(); ++i) + m_stats_counter[i].store( + c.m_stats_counter[i].load(std::memory_order_relaxed) + , std::memory_order_relaxed); +#else + std::lock_guard l(c.m_mutex); + m_stats_counter = c.m_stats_counter; +#endif + } + + counters& counters::operator=(counters const& c) TORRENT_COUNTER_NOEXCEPT + { + if (&c == this) return *this; +#ifdef ATOMIC_LLONG_LOCK_FREE + for (int i = 0; i < m_stats_counter.end_index(); ++i) + m_stats_counter[i].store( + c.m_stats_counter[i].load(std::memory_order_relaxed) + , std::memory_order_relaxed); +#else + std::lock_guard l(m_mutex); + std::lock_guard l2(c.m_mutex); + m_stats_counter = c.m_stats_counter; +#endif + return *this; + } + + std::int64_t counters::operator[](int i) const TORRENT_COUNTER_NOEXCEPT + { + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < num_counters); + +#ifdef ATOMIC_LLONG_LOCK_FREE + return m_stats_counter[i].load(std::memory_order_relaxed); +#else + std::lock_guard l(m_mutex); + return m_stats_counter[i]; +#endif + } + + // the argument specifies which counter to + // increment or decrement + std::int64_t counters::inc_stats_counter(int const c, std::int64_t const value) TORRENT_COUNTER_NOEXCEPT + { + // if c >= num_stats_counters, it means it's not + // a monotonically increasing counter, but a gauge + // and it's allowed to be decremented + TORRENT_ASSERT(value >= 0 || c >= num_stats_counters); + TORRENT_ASSERT(c >= 0); + TORRENT_ASSERT(c < num_counters); + +#ifdef ATOMIC_LLONG_LOCK_FREE + std::int64_t pv = m_stats_counter[c].fetch_add(value, std::memory_order_relaxed); + TORRENT_ASSERT(pv + value >= 0); + return pv + value; +#else + std::lock_guard l(m_mutex); + TORRENT_ASSERT(m_stats_counter[c] + value >= 0); + return m_stats_counter[c] += value; +#endif + } + + // ratio is a value between 0 and 100 representing the percentage the value + // is blended in at. + void counters::blend_stats_counter(int const c, std::int64_t const value, int const ratio) TORRENT_COUNTER_NOEXCEPT + { + TORRENT_ASSERT(c >= num_stats_counters); + TORRENT_ASSERT(c < num_counters); + TORRENT_ASSERT(ratio >= 0); + TORRENT_ASSERT(ratio <= 100); + +#ifdef ATOMIC_LLONG_LOCK_FREE + std::int64_t current = m_stats_counter[c].load(std::memory_order_relaxed); + std::int64_t new_value = (current * (100 - ratio) + value * ratio) / 100; + + while (!m_stats_counter[c].compare_exchange_weak(current, new_value + , std::memory_order_relaxed)) + { + new_value = (current * (100 - ratio) + value * ratio) / 100; + } +#else + std::lock_guard l(m_mutex); + std::int64_t current = m_stats_counter[c]; + m_stats_counter[c] = (current * (100 - ratio) + value * ratio) / 100; +#endif + } + + void counters::set_value(int const c, std::int64_t const value) TORRENT_COUNTER_NOEXCEPT + { + TORRENT_ASSERT(c >= 0); + TORRENT_ASSERT(c < num_counters); + +#ifdef ATOMIC_LLONG_LOCK_FREE + m_stats_counter[c].store(value); +#else + std::lock_guard l(m_mutex); + + // if this assert fires, someone is trying to decrement a counter + // which is not allowed. Counters are monotonically increasing + TORRENT_ASSERT(value >= m_stats_counter[c] || c >= num_stats_counters); + + m_stats_counter[c] = value; +#endif + } + +} diff --git a/src/piece_picker.cpp b/src/piece_picker.cpp new file mode 100644 index 0000000..00bfa38 --- /dev/null +++ b/src/piece_picker.cpp @@ -0,0 +1,3838 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/range.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_types.hpp" // for picker_log_alert +#include "libtorrent/download_priority.hpp" +#include "libtorrent/disk_interface.hpp" // for default_block_size + +#if TORRENT_USE_ASSERTS +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_peer.hpp" +#endif + +#include "libtorrent/invariant_check.hpp" + +// this is really only useful for debugging unit tests +//#define TORRENT_PICKER_LOG + +using namespace std::placeholders; + +#if defined TORRENT_PICKER_LOG +#include + +namespace libtorrent { + void print_pieces(piece_picker const& p) + { + int limit = 20; + std::cerr << "[" << &p << "] "; + if (p.m_dirty) + { + std::cerr << " === dirty ===" << std::endl; + return; + } + + for (prio_index_t b : p.m_priority_boundaries) + std::cerr << b << " "; + + std::cerr << std::endl; + prio_index_t index(0); + std::cerr << "[" << &p << "] "; + auto j = p.m_priority_boundaries.begin(); + for (auto i = p.m_pieces.begin(), end(p.m_pieces.end()); i != end; ++i, ++index) + { + if (limit == 0) + { + std::cerr << " ..."; + break; + } + if (*i == -1) break; + while (j != p.m_priority_boundaries.end() && *j <= index) + { + std::cerr << "| "; + ++j; + } + std::cerr << *i << "(" << p.m_piece_map[*i].index << ") "; + --limit; + } + std::cerr << std::endl; + } +} +#endif // TORRENT_PICKER_LOG +namespace libtorrent { + + // TODO: find a better place for this + const piece_block piece_block::invalid( + std::numeric_limits::max() + , std::numeric_limits::max()); + + constexpr prio_index_t piece_picker::piece_pos::we_have_index; + + constexpr picker_options_t piece_picker::rarest_first; + constexpr picker_options_t piece_picker::reverse; + constexpr picker_options_t piece_picker::on_parole; + constexpr picker_options_t piece_picker::prioritize_partials; + constexpr picker_options_t piece_picker::sequential; + constexpr picker_options_t piece_picker::time_critical_mode; + constexpr picker_options_t piece_picker::align_expanded_pieces; + constexpr picker_options_t piece_picker::piece_extent_affinity; + + constexpr download_queue_t piece_picker::piece_pos::piece_downloading; + constexpr download_queue_t piece_picker::piece_pos::piece_full; + constexpr download_queue_t piece_picker::piece_pos::piece_finished; + constexpr download_queue_t piece_picker::piece_pos::piece_zero_prio; + constexpr download_queue_t piece_picker::piece_pos::num_download_categories; + constexpr download_queue_t piece_picker::piece_pos::piece_open; + constexpr download_queue_t piece_picker::piece_pos::piece_downloading_reverse; + constexpr download_queue_t piece_picker::piece_pos::piece_full_reverse; + + // the max number of blocks to create an affinity for + constexpr int max_piece_affinity_extent = 4 * 1024 * 1024 / default_block_size; + + piece_picker::piece_picker(int const blocks_per_piece + , int const blocks_in_last_piece, int const total_num_pieces) + : m_priority_boundaries(1, m_pieces.end_index()) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "new piece_picker" << std::endl; +#endif +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + + resize(blocks_per_piece, blocks_in_last_piece, total_num_pieces); + } + + void piece_picker::resize(int const blocks_per_piece + , int const blocks_in_last_piece, int const total_num_pieces) + { + TORRENT_ASSERT(blocks_per_piece > 0); + TORRENT_ASSERT(total_num_pieces > 0); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::resize()" << std::endl; +#endif + + if (blocks_per_piece > max_blocks_per_piece) + throw system_error(errors::invalid_piece_size); + + // allocate the piece_map to cover all pieces + // and make them invalid (as if we don't have a single piece) + m_piece_map.resize(total_num_pieces, piece_pos(0, 0)); + m_reverse_cursor = m_piece_map.end_index(); + m_cursor = piece_index_t(0); + + for (auto& c : m_downloads) c.clear(); + m_block_info.clear(); + m_free_block_infos.clear(); + + m_num_filtered += m_num_have_filtered; + m_num_have_filtered = 0; + m_num_have = 0; + m_have_pad_blocks = 0; + m_filtered_pad_blocks = 0; + m_have_filtered_pad_blocks = 0; + m_num_passed = 0; + m_dirty = true; + for (auto& m : m_piece_map) + { + m.peer_count = 0; + m.state(piece_pos::piece_open); + m.index = prio_index_t(0); +#ifdef TORRENT_DEBUG_REFCOUNTS + m.have_peers.clear(); +#endif + } + + for (auto i = m_piece_map.begin() + static_cast(m_cursor) + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_cursor); + + for (auto i = m_piece_map.rend() - static_cast(m_reverse_cursor); + m_reverse_cursor > piece_index_t(0) && (i->have() || i->filtered()); + ++i, --m_reverse_cursor); + + m_blocks_per_piece = aux::numeric_cast(blocks_per_piece); + m_blocks_in_last_piece = aux::numeric_cast(blocks_in_last_piece); + if (m_blocks_in_last_piece == 0) m_blocks_in_last_piece = aux::numeric_cast(blocks_per_piece); + + TORRENT_ASSERT(m_blocks_in_last_piece <= m_blocks_per_piece); + } + + void piece_picker::piece_info(piece_index_t const index, piece_picker::downloading_piece& st) const + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + auto const state = m_piece_map[index].download_queue(); + if (state != piece_pos::piece_open) + { + auto piece = find_dl_piece(state, index); + TORRENT_ASSERT(piece != m_downloads[state].end()); + st = *piece; + return; + } + st.info_idx = 0; + st.index = index; + st.writing = 0; + st.requested = 0; + if (m_piece_map[index].have()) + { + st.finished = std::uint16_t(blocks_in_piece(index)); + return; + } + st.finished = 0; + } + + piece_picker::piece_stats_t piece_picker::piece_stats(piece_index_t const index) const + { + piece_pos const& pp = m_piece_map[index]; + piece_stats_t ret = { + int(pp.peer_count + m_seeds), + pp.priority(this), + pp.have(), + pp.downloading() + }; + return ret; + } + + std::vector::iterator + piece_picker::add_download_piece(piece_index_t const piece) + { + TORRENT_ASSERT(piece >= piece_index_t(0)); + TORRENT_ASSERT(piece < m_piece_map.end_index()); +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + int block_index; + + if (m_free_block_infos.empty()) + { + // we need to allocate more space in m_block_info + block_index = int(m_block_info.size() / m_blocks_per_piece); + TORRENT_ASSERT((m_block_info.size() % m_blocks_per_piece) == 0); + m_block_info.resize(m_block_info.size() + m_blocks_per_piece); + } + else + { + // there is already free space in m_block_info, grab one range + block_index = int(m_free_block_infos.back()); + m_free_block_infos.pop_back(); + } + + // always insert into bucket 0 (piece_downloading) + downloading_piece ret; + ret.index = piece; + auto const download_state = piece_pos::piece_downloading; + auto downloading_iter = std::lower_bound(m_downloads[download_state].begin() + , m_downloads[download_state].end(), ret); + TORRENT_ASSERT(downloading_iter == m_downloads[download_state].end() + || downloading_iter->index != piece); + TORRENT_ASSERT(block_index >= 0); + TORRENT_ASSERT(block_index < std::numeric_limits::max()); + ret.info_idx = std::uint16_t(block_index); + TORRENT_ASSERT(int(ret.info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); + + int block_idx = 0; + for (auto& info : mutable_blocks_for_piece(ret)) + { + info.num_peers = 0; + info.state = block_info::state_none; + if (!m_pad_blocks.empty() && m_pad_blocks.get_bit(static_cast(piece) * m_blocks_per_piece + block_idx)) + { + info.state = block_info::state_finished; + ++ret.finished; + } + else + { + info.state = block_info::state_none; + } + ++block_idx; + info.peer = nullptr; +#if TORRENT_USE_ASSERTS + info.piece_index = piece; + info.peers.clear(); +#endif + } + downloading_iter = m_downloads[download_state].insert(downloading_iter, ret); + + // in case every block was a pad block, we need to make sure the piece + // structure is correctly categorised + downloading_iter = update_piece_state(downloading_iter); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + return downloading_iter; + } + + void piece_picker::erase_download_piece(std::vector::iterator i) + { +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + auto const download_state = m_piece_map[i->index].download_queue(); + TORRENT_ASSERT(download_state != piece_pos::piece_open); + TORRENT_ASSERT(find_dl_piece(download_state, i->index) == i); +#if TORRENT_USE_ASSERTS + int prev_size = int(m_downloads[download_state].size()); +#endif + + // since we're removing a downloading_piece, we also need to free its + // blocks that are allocated from the m_block_info array. + m_free_block_infos.push_back(i->info_idx); + + TORRENT_ASSERT(find_dl_piece(download_state, i->index) == i); + m_piece_map[i->index].state(piece_pos::piece_open); + m_downloads[download_state].erase(i); + + TORRENT_ASSERT(prev_size == int(m_downloads[download_state].size()) + 1); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + std::vector piece_picker::get_download_queue() const + { +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + std::vector ret; + for (auto const& c : m_downloads) + ret.insert(ret.end(), c.begin(), c.end()); + return ret; + } + + int piece_picker::get_download_queue_size() const + { + return std::accumulate(m_downloads.begin(), m_downloads.end(), 0 + , [](int const acc, aux::vector const& q) { return acc + int(q.size()); }); + } + + void piece_picker::get_download_queue_sizes(int* partial + , int* full, int* finished, int* zero_prio) const + { + *partial = int(m_downloads[piece_pos::piece_downloading].size()); + *full = int(m_downloads[piece_pos::piece_full].size()); + *finished = int(m_downloads[piece_pos::piece_finished].size()); + *zero_prio = int(m_downloads[piece_pos::piece_zero_prio].size()); + } + + span piece_picker::mutable_blocks_for_piece( + downloading_piece const& dp) + { + int idx = int(dp.info_idx) * m_blocks_per_piece; + TORRENT_ASSERT(idx + m_blocks_per_piece <= int(m_block_info.size())); + return { &m_block_info[idx], blocks_in_piece(dp.index) }; + } + + span piece_picker::blocks_for_piece( + downloading_piece const& dp) const + { + return const_cast(this)->mutable_blocks_for_piece(dp); + } + +#if TORRENT_USE_INVARIANT_CHECKS + + void piece_picker::check_piece_state() const + { + for (auto const k : categories()) + { + if (m_downloads[k].empty()) continue; + for (auto i = m_downloads[k].begin(); i != m_downloads[k].end() - 1; ++i) + { + downloading_piece const& dp = *i; + downloading_piece const& next = *(i + 1); + TORRENT_ASSERT(dp.index < next.index); + TORRENT_ASSERT(int(dp.info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); + for (auto const& bl : blocks_for_piece(dp)) + { + if (!bl.peer) continue; + torrent_peer* p = bl.peer; + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(p->connection == nullptr + || static_cast(p->connection)->m_in_use); + } + } + } + } + + void piece_picker::verify_pick(std::vector const& picked + , typed_bitfield const& bits) const + { + TORRENT_ASSERT(bits.size() == num_pieces()); + for (piece_block const& pb : picked) + { + TORRENT_ASSERT(bits[pb.piece_index]); + TORRENT_ASSERT(!m_piece_map[pb.piece_index].have()); + TORRENT_ASSERT(!m_piece_map[pb.piece_index].filtered()); + } + } + + void piece_picker::verify_priority(prio_index_t const range_start + , prio_index_t const range_end + , int const prio) const + { + TORRENT_ASSERT(range_start <= range_end); + TORRENT_ASSERT(range_end <= m_pieces.end_index()); + for (auto index : range(m_pieces, range_start, range_end)) + { + int p = m_piece_map[index].priority(this); + TORRENT_ASSERT(p == prio); + } + } +#endif // TORRENT_USE_INVARIANT_CHECKS + +#if TORRENT_USE_INVARIANT_CHECKS + void piece_picker::check_peer_invariant(typed_bitfield const& have + , torrent_peer const* p) const + { +#ifdef TORRENT_DEBUG_REFCOUNTS + for (piece_index_t i(0); i < have.end_index(); ++i) + { + bool const h = have[i]; + TORRENT_ASSERT(int(m_piece_map[i].have_peers.count(p)) == (h ? 1 : 0)); + } +#else + TORRENT_UNUSED(have); + TORRENT_UNUSED(p); +#endif + } + + void piece_picker::check_invariant(torrent const* t) const + { + TORRENT_ASSERT(m_num_have >= 0); + TORRENT_ASSERT(m_num_have_filtered >= 0); + TORRENT_ASSERT(m_num_have_filtered <= m_num_have); + TORRENT_ASSERT(m_num_have_filtered + m_num_filtered <= num_pieces()); + TORRENT_ASSERT(m_num_filtered >= 0); + TORRENT_ASSERT(m_seeds >= 0); + TORRENT_ASSERT(m_have_pad_blocks <= num_pad_blocks()); + TORRENT_ASSERT(m_have_pad_blocks >= 0); + TORRENT_ASSERT(m_filtered_pad_blocks <= num_pad_blocks()); + TORRENT_ASSERT(m_filtered_pad_blocks >= 0); + TORRENT_ASSERT(m_have_filtered_pad_blocks <= num_pad_blocks()); + TORRENT_ASSERT(m_have_filtered_pad_blocks >= 0); + TORRENT_ASSERT(m_have_filtered_pad_blocks + m_filtered_pad_blocks <= num_pad_blocks()); + TORRENT_ASSERT(m_have_filtered_pad_blocks <= m_have_pad_blocks); + + // make sure the priority boundaries are monotonically increasing. The + // difference between two cursors cannot be negative, but ranges are + // allowed to be empty. + prio_index_t last(0); + for (prio_index_t b : m_priority_boundaries) + { + TORRENT_ASSERT(b >= last); + last = b; + } + + check_piece_state(); + + if (t != nullptr) + TORRENT_ASSERT(num_pieces() == t->torrent_file().num_pieces()); + + for (auto const j : categories()) + { + for (auto const& dp : m_downloads[j]) + { + TORRENT_ASSERT(m_piece_map[dp.index].download_queue() == j); + const int num_blocks = blocks_in_piece(dp.index); + int num_requested = 0; + int num_finished = 0; + int num_writing = 0; + int num_open = 0; + for (auto const& bl : blocks_for_piece(dp)) + { + TORRENT_ASSERT(bl.piece_index == dp.index); + TORRENT_ASSERT(bl.peer == nullptr + || bl.peer->in_use); + + if (bl.state == block_info::state_finished) + { + ++num_finished; + TORRENT_ASSERT(bl.num_peers == 0); + } + else if (bl.state == block_info::state_requested) + { + ++num_requested; + TORRENT_ASSERT(bl.num_peers > 0); + } + else if (bl.state == block_info::state_writing) + { + ++num_writing; + TORRENT_ASSERT(bl.num_peers == 0); + } + else if (bl.state == block_info::state_none) + { + ++num_open; + TORRENT_ASSERT(bl.num_peers == 0); + } + } + + if (j == piece_pos::piece_downloading) + { + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + TORRENT_ASSERT(num_open > 0); + } + else if (j == piece_pos::piece_full) + { + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + TORRENT_ASSERT(num_open == 0); + // if requested == 0, the piece should be in the finished state + TORRENT_ASSERT(num_requested > 0); + } + else if (j == piece_pos::piece_finished) + { + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + TORRENT_ASSERT(num_open == 0); + TORRENT_ASSERT(num_requested == 0); + TORRENT_ASSERT(num_finished + num_writing == num_blocks); + } + else if (j == piece_pos::piece_zero_prio) + { + TORRENT_ASSERT(m_piece_map[dp.index].filtered()); + } + + TORRENT_ASSERT(num_requested == dp.requested); + TORRENT_ASSERT(num_writing == dp.writing); + TORRENT_ASSERT(num_finished == dp.finished); + + if (m_piece_map[dp.index].download_queue() == piece_pos::piece_full + || m_piece_map[dp.index].download_queue() == piece_pos::piece_finished) + TORRENT_ASSERT(num_finished + num_writing + num_requested == num_blocks); + } + } + TORRENT_ASSERT(m_cursor >= piece_index_t(0)); + TORRENT_ASSERT(m_cursor <= m_piece_map.end_index()); + TORRENT_ASSERT(m_reverse_cursor >= piece_index_t(0)); + TORRENT_ASSERT(m_reverse_cursor <= m_piece_map.end_index()); + TORRENT_ASSERT(m_reverse_cursor > m_cursor + || (m_cursor == m_piece_map.end_index() + && m_reverse_cursor == piece_index_t(0))); + + if (!m_dirty) + { + TORRENT_ASSERT(!m_priority_boundaries.empty()); + int prio = 0; + prio_index_t start(0); + for (prio_index_t b : m_priority_boundaries) + { + verify_priority(start, b, prio); + ++prio; + start = b; + } + TORRENT_ASSERT(m_priority_boundaries.back() == m_pieces.end_index()); + } + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + { + piece_index_t index(0); + for (auto i = m_piece_map.begin() + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++index); + TORRENT_ASSERT(m_cursor == index); + index = m_piece_map.end_index(); + if (num_pieces() > 0) + { + for (auto i = m_piece_map.rend() - static_cast(index); index > piece_index_t(0) + && (i->have() || i->filtered()); ++i, --index); + TORRENT_ASSERT(index == m_piece_map.end_index() + || m_piece_map[index].have() + || m_piece_map[index].filtered()); + TORRENT_ASSERT(m_reverse_cursor == index); + } + else + { + TORRENT_ASSERT(m_reverse_cursor == piece_index_t(0)); + } + } + + int num_filtered = 0; + int num_have_filtered = 0; + int num_have = 0; + int num_have_pad_blocks = 0; + int num_filtered_pad_blocks = 0; + int num_have_filtered_pad_blocks = 0; + piece_index_t piece(0); + for (auto i = m_piece_map.begin(); i != m_piece_map.end(); ++i, ++piece) + { + piece_pos const& p = *i; + + if (p.filtered()) + { + if (p.index != piece_pos::we_have_index) + { + ++num_filtered; + num_filtered_pad_blocks += pad_blocks_in_piece(piece); + } + else + { + ++num_have_filtered; + num_have_filtered_pad_blocks += pad_blocks_in_piece(piece); + } + } + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(int(p.have_peers.size()) == p.peer_count + m_seeds); +#endif + if (p.index == piece_pos::we_have_index) + { + ++num_have; + num_have_pad_blocks += pad_blocks_in_piece(piece); + } + + if (p.index == piece_pos::we_have_index) + { + TORRENT_ASSERT(t == nullptr || t->have_piece(piece)); + TORRENT_ASSERT(p.downloading() == false); + } + + if (t != nullptr) + TORRENT_ASSERT(!t->have_piece(piece)); + + int const prio = p.priority(this); + + if (p.downloading()) + { + if (p.reverse()) + TORRENT_ASSERT(prio == -1 || (prio % piece_picker::prio_factor == 2)); + else + TORRENT_ASSERT(prio == -1 || (prio % piece_picker::prio_factor == 0)); + } + else + { + TORRENT_ASSERT(prio == -1 || (prio % piece_picker::prio_factor == 1)); + } + + if (!m_dirty) + { + TORRENT_ASSERT(prio < int(m_priority_boundaries.size())); + if (prio >= 0) + { + TORRENT_ASSERT(p.index < m_pieces.end_index()); + TORRENT_ASSERT(m_pieces[p.index] == piece); + } + else + { + TORRENT_ASSERT(prio == -1); + // make sure there's no entry + // with this index. (there shouldn't + // be since the priority is -1) + TORRENT_ASSERT(std::count(m_pieces.begin(), m_pieces.end(), piece) == 0); + } + } + + int const count_downloading = int(std::count_if( + m_downloads[piece_pos::piece_downloading].begin() + , m_downloads[piece_pos::piece_downloading].end() + , has_index(piece))); + + int const count_full = int(std::count_if( + m_downloads[piece_pos::piece_full].begin() + , m_downloads[piece_pos::piece_full].end() + , has_index(piece))); + + int const count_finished = int(std::count_if( + m_downloads[piece_pos::piece_finished].begin() + , m_downloads[piece_pos::piece_finished].end() + , has_index(piece))); + + int const count_zero = int(std::count_if( + m_downloads[piece_pos::piece_zero_prio].begin() + , m_downloads[piece_pos::piece_zero_prio].end() + , has_index(piece))); + + TORRENT_ASSERT(i->download_queue() == piece_pos::piece_open + || count_zero + count_downloading + count_full + + count_finished == 1); + + auto const dq = i->download_queue(); + if (dq == piece_pos::piece_open) { + TORRENT_ASSERT(count_downloading + + count_full + count_finished + count_zero == 0); + } + else if (dq == piece_pos::piece_downloading) { + TORRENT_ASSERT(count_downloading == 1); + } + else if (dq == piece_pos::piece_full) { + TORRENT_ASSERT(count_full == 1); + } + else if (dq == piece_pos::piece_finished) { + TORRENT_ASSERT(count_finished == 1); + } + else if (dq == piece_pos::piece_zero_prio) { + TORRENT_ASSERT(count_zero == 1); + } + } + TORRENT_ASSERT(num_have == m_num_have); + TORRENT_ASSERT(num_filtered == m_num_filtered); + TORRENT_ASSERT(num_have_filtered == m_num_have_filtered); + TORRENT_ASSERT(num_have_pad_blocks == m_have_pad_blocks); + TORRENT_ASSERT(num_filtered_pad_blocks == m_filtered_pad_blocks); + TORRENT_ASSERT(num_have_filtered_pad_blocks == m_have_filtered_pad_blocks); + + if (!m_dirty) + { + for (piece_index_t i : m_pieces) + { + TORRENT_ASSERT(m_piece_map[i].priority(this) >= 0); + } + } +#endif // TORRENT_EXPENSIVE_INVARIANT_CHECKS + } +#endif + + std::pair piece_picker::distributed_copies() const + { + TORRENT_ASSERT(m_seeds >= 0); + const int npieces = num_pieces(); + + if (npieces == 0) return std::make_pair(1, 0); + int min_availability = piece_pos::max_peer_count; + // find the lowest availability count + // count the number of pieces that have that availability + // and also the number of pieces that have more than that. + int integer_part = 0; + int fraction_part = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int peer_count = int(i->peer_count); + // take ourself into account + if (i->have()) ++peer_count; + if (min_availability > peer_count) + { + min_availability = peer_count; + fraction_part += integer_part; + integer_part = 1; + } + else if (peer_count == min_availability) + { + ++integer_part; + } + else + { + TORRENT_ASSERT(peer_count > min_availability); + ++fraction_part; + } + } + TORRENT_ASSERT(integer_part + fraction_part == npieces); + return std::make_pair(min_availability + m_seeds, fraction_part * 1000 / npieces); + } + + prio_index_t piece_picker::priority_begin(int const prio) const + { + TORRENT_ASSERT(prio >= 0); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size())); + return prio == 0 ? prio_index_t(0) : m_priority_boundaries[prio - 1]; + } + + prio_index_t piece_picker::priority_end(int const prio) const + { + TORRENT_ASSERT(prio >= 0); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size())); + return m_priority_boundaries[prio]; + } + + std::pair piece_picker::priority_range(int const prio) const + { + TORRENT_ASSERT(prio >= 0); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size())); + return {priority_begin(prio), priority_end(prio)}; + } + + void piece_picker::add(piece_index_t index) + { + TORRENT_ASSERT(!m_dirty); + piece_pos const& p = m_piece_map[index]; + TORRENT_ASSERT(!p.filtered()); + TORRENT_ASSERT(!p.have()); + + int priority = p.priority(this); + TORRENT_ASSERT(priority >= 0); + if (priority < 0) return; + + if (int(m_priority_boundaries.size()) <= priority) + m_priority_boundaries.resize(priority + 1, m_pieces.end_index()); + + TORRENT_ASSERT(int(m_priority_boundaries.size()) >= priority); + + auto const range = priority_range(priority); + prio_index_t new_index = (range.second == range.first) + ? range.first + : prio_index_t( + int(random(aux::numeric_cast(static_cast(range.second - range.first)))) + + static_cast(range.first)); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "add " << index << " (" << priority << ")" << std::endl; + std::cerr << "[" << this << "] " << " p: state: " << p.download_state + << " peer_count: " << p.peer_count + << " prio: " << p.piece_priority + << " index: " << p.index << std::endl; + print_pieces(*this); +#endif + m_pieces.push_back(piece_index_t(-1)); + + for (;;) + { + TORRENT_ASSERT(new_index < m_pieces.end_index()); + { + piece_index_t temp = m_pieces[new_index]; + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; + index = temp; + } + prio_index_t temp(-1); + do + { + temp = m_priority_boundaries[priority]++; + ++priority; + } while (temp == new_index && priority < int(m_priority_boundaries.size())); + new_index = temp; +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); + std::cerr << "[" << this << "] " << " index: " << index + << " prio: " << priority + << " new_index: " << new_index + << std::endl; +#endif + if (priority >= int(m_priority_boundaries.size())) break; + TORRENT_ASSERT(temp >= prio_index_t(0)); + } + if (index != piece_index_t(-1)) + { + TORRENT_ASSERT(new_index == prev(m_pieces.end_index())); + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; + +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + } + } + + void piece_picker::remove(int priority, prio_index_t elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "remove " << m_pieces[elem_index] << " (" << priority << ")" << std::endl; +#endif + prio_index_t next_index = elem_index; + TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == -1); + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + TORRENT_ASSERT(elem_index < m_pieces.end_index()); + prio_index_t temp{}; + do + { + temp = --m_priority_boundaries[priority]; + ++priority; + } while (next_index == temp && priority < int(m_priority_boundaries.size())); + if (next_index == temp) break; + next_index = temp; + + piece_index_t const piece = m_pieces[next_index]; + m_pieces[elem_index] = piece; + m_piece_map[piece].index = elem_index; + TORRENT_ASSERT(m_piece_map[piece].priority(this) == priority - 1); + TORRENT_ASSERT(elem_index < prev(m_pieces.end_index())); + elem_index = next_index; + + if (priority == int(m_priority_boundaries.size())) + break; + } + m_pieces.pop_back(); + TORRENT_ASSERT(next_index == m_pieces.end_index()); +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + } + + // will update the piece with the given properties (priority, elem_index) + // to place it at the correct position + void piece_picker::update(int priority, prio_index_t elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(int(m_priority_boundaries.size()) > priority); + + // make sure the passed in elem_index actually lives in the specified + // priority bucket. If it doesn't, it means this piece changed + // state without updating the corresponding entry in the pieces list + TORRENT_ASSERT(m_priority_boundaries[priority] >= elem_index); + TORRENT_ASSERT(elem_index >= priority_begin(priority)); + TORRENT_ASSERT(elem_index < priority_end(priority)); + + piece_index_t const index = m_pieces[elem_index]; + // update the piece_map + piece_pos& p = m_piece_map[index]; + TORRENT_ASSERT(p.index == elem_index || p.have()); + + int const new_priority = p.priority(this); + + if (new_priority == priority) return; + + if (new_priority == -1) + { + remove(priority, elem_index); + return; + } + + if (int(m_priority_boundaries.size()) <= new_priority) + m_priority_boundaries.resize(new_priority + 1, m_pieces.end_index()); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "update " << index << " (" << priority << "->" << new_priority << ")" << std::endl; +#endif + if (priority > new_priority) + { + prio_index_t new_index{}; + piece_index_t temp = index; + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + TORRENT_ASSERT(priority > 0); + --priority; + new_index = m_priority_boundaries[priority]++; + if (temp != m_pieces[new_index]) + { + temp = m_pieces[new_index]; + m_pieces[elem_index] = temp; + m_piece_map[temp].index = elem_index; + TORRENT_ASSERT(elem_index < m_pieces.end_index()); + } + elem_index = new_index; + if (priority == new_priority) break; + } +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < m_pieces.end_index()); +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); + } + else + { + prio_index_t new_index{}; + piece_index_t temp = index; + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(priority < int(m_priority_boundaries.size())); + new_index = --m_priority_boundaries[priority]; + if (temp != m_pieces[new_index]) + { + temp = m_pieces[new_index]; + m_pieces[elem_index] = temp; + m_piece_map[temp].index = elem_index; + TORRENT_ASSERT(elem_index < m_pieces.end_index()); + } + elem_index = new_index; + ++priority; + if (priority == new_priority) break; + } +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < m_pieces.end_index()); +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); + } + } + + void piece_picker::shuffle(int const priority, prio_index_t const elem_index) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "shuffle()" << std::endl; +#endif + + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(elem_index >= prio_index_t(0)); + TORRENT_ASSERT(elem_index < m_pieces.end_index()); + TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == priority); + + auto const range = priority_range(priority); + prio_index_t const other_index( + int(random(aux::numeric_cast(static_cast(range.second - range.first) - 1))) + + static_cast(range.first)); + + if (other_index == elem_index) return; + + // swap other_index with elem_index + piece_pos& p1 = m_piece_map[m_pieces[other_index]]; + piece_pos& p2 = m_piece_map[m_pieces[elem_index]]; + + std::swap(p1.index, p2.index); + std::swap(m_pieces[other_index], m_pieces[elem_index]); + } + + void piece_picker::restore_piece(piece_index_t const index) + { + INVARIANT_CHECK; + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "restore_piece(" << index << ")" << std::endl; +#endif + auto const download_state = m_piece_map[index].download_queue(); + TORRENT_ASSERT(download_state != piece_pos::piece_open); + if (download_state == piece_pos::piece_open) return; + + auto i = find_dl_piece(download_state, index); + + TORRENT_ASSERT(i != m_downloads[download_state].end()); + TORRENT_ASSERT(int(i->info_idx) * m_blocks_per_piece + + m_blocks_per_piece <= int(m_block_info.size())); + + i->locked = false; + + piece_pos& p = m_piece_map[index]; + int const prev_priority = p.priority(this); + erase_download_piece(i); + int const new_priority = p.priority(this); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + if (new_priority == prev_priority) return; + if (m_dirty) return; + if (prev_priority == -1) add(index); + else update(prev_priority, p.index); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + void piece_picker::inc_refcount_all(const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + ++m_seeds; + if (m_seeds == 1) + { + // when m_seeds is increased from 0 to 1 + // we may have to add pieces that previously + // didn't have any peers + m_dirty = true; + } +#ifdef TORRENT_DEBUG_REFCOUNTS + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + TORRENT_ASSERT(i->have_peers.count(peer) == 0); + i->have_peers.insert(peer); + } +#else + TORRENT_UNUSED(peer); +#endif + } + + void piece_picker::dec_refcount_all(const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + if (m_seeds > 0) + { + --m_seeds; + if (m_seeds == 0) + { + // when m_seeds is decreased from 1 to 0 + // we may have to remove pieces that previously + // didn't have any peers + m_dirty = true; + } +#ifdef TORRENT_DEBUG_REFCOUNTS + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + TORRENT_ASSERT(i->have_peers.count(peer) == 1); + i->have_peers.erase(peer); + } +#else + TORRENT_UNUSED(peer); +#endif + return; + } + TORRENT_ASSERT(m_seeds == 0); + + for (auto& i : m_piece_map) + { +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(i.have_peers.count(peer) == 1); + i.have_peers.erase(peer); +#else + TORRENT_UNUSED(peer); +#endif + + TORRENT_ASSERT(i.peer_count > 0); + --i.peer_count; + } + + m_dirty = true; + } + + void piece_picker::inc_refcount(piece_index_t const index + , const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "inc_refcount(" << index << ")" << std::endl; +#endif + piece_pos& p = m_piece_map[index]; + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 0); + p.have_peers.insert(peer); +#else + TORRENT_UNUSED(peer); +#endif + + int prev_priority = p.priority(this); + ++p.peer_count; + if (m_dirty) return; + int new_priority = p.priority(this); + if (prev_priority == new_priority) return; + if (prev_priority == -1) + add(index); + else + update(prev_priority, p.index); + } + + // this function decrements the m_seeds counter + // and increments the peer counter on every piece + // instead. Sometimes of we connect to a seed that + // later sends us a dont-have message, we'll need to + // turn that m_seed into counts on the pieces since + // they can't be negative + void piece_picker::break_one_seed() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(m_seeds > 0); + --m_seeds; + + for (auto& m : m_piece_map) + ++m.peer_count; + + m_dirty = true; + } + + void piece_picker::dec_refcount(piece_index_t const index + , const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "dec_refcount(" << index << ")" << std::endl; +#endif + + piece_pos& p = m_piece_map[index]; + + if (p.peer_count == 0) + { + TORRENT_ASSERT(m_seeds > 0); + // this is the case where we have one or more + // seeds, and one of them saying: I don't have this + // piece anymore. we need to break up one of the seed + // counters into actual peer counters on the pieces + break_one_seed(); + } + + int const prev_priority = p.priority(this); + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 1); + p.have_peers.erase(peer); +#else + TORRENT_UNUSED(peer); +#endif + + TORRENT_ASSERT(p.peer_count > 0); + --p.peer_count; + if (m_dirty) return; + if (prev_priority >= 0) update(prev_priority, p.index); + } + + void piece_picker::inc_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "inc_refcount(bitfield)" << std::endl; +#endif + + // nothing set, nothing to do here + if (bitmask.none_set()) return; + + if (bitmask.all_set() && bitmask.size() == int(m_piece_map.size())) + { + inc_refcount_all(peer); + return; + } + + int const size = std::min(50, int(bitmask.size() / 2)); + + // this is an optimization where if just a few + // pieces end up changing, instead of making + // the piece list dirty, just update those pieces + // instead + TORRENT_ALLOCA(incremented, piece_index_t, size); + + if (!m_dirty) + { + // first count how many pieces we're updating. If it's few (less than half) + // we'll just update them one at a time. Otherwise we'll just update the counters + // and mark the picker as dirty, so we'll rebuild it next time we need it. + // this only matters if we're not already dirty, in which case the fasted + // thing to do is to just update the counters and be done + piece_index_t index = piece_index_t(0); + int num_inc = 0; + for (auto i = bitmask.begin(), end(bitmask.end()); i != end; ++i, ++index) + { + if (!*i) continue; + if (num_inc < size) incremented[num_inc] = index; + ++num_inc; + if (num_inc >= size) break; + } + + if (num_inc < size) + { + // not that many pieces were updated + // just update those individually instead of + // rebuilding the whole piece list + for (int i = 0; i < num_inc; ++i) + { + piece_index_t const piece = incremented[i]; + piece_pos& p = m_piece_map[piece]; + int prev_priority = p.priority(this); + ++p.peer_count; +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 0); + p.have_peers.insert(peer); +#else + TORRENT_UNUSED(peer); +#endif + int new_priority = p.priority(this); + if (prev_priority == new_priority) continue; + else if (prev_priority >= 0) update(prev_priority, p.index); + else add(piece); + } + return; + } + } + + piece_index_t index = piece_index_t(0); + bool updated = false; + for (auto i = bitmask.begin(), end(bitmask.end()); i != end; ++i, ++index) + { + if (*i) + { +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(m_piece_map[index].have_peers.count(peer) == 0); + m_piece_map[index].have_peers.insert(peer); +#else + TORRENT_UNUSED(peer); +#endif + + ++m_piece_map[index].peer_count; + updated = true; + } + } + + // if we're already dirty, no point in doing anything more + if (m_dirty) return; + + if (updated) m_dirty = true; + } + + void piece_picker::dec_refcount(typed_bitfield const& bitmask + , const torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + TORRENT_ASSERT(bitmask.size() <= int(m_piece_map.size())); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "dec_refcount(bitfield)" << std::endl; +#endif + + // nothing set, nothing to do here + if (bitmask.none_set()) return; + + if (bitmask.all_set() && bitmask.size() == int(m_piece_map.size())) + { + dec_refcount_all(peer); + return; + } + + int const size = std::min(50, int(bitmask.size() / 2)); + + // this is an optimization where if just a few + // pieces end up changing, instead of making + // the piece list dirty, just update those pieces + // instead + TORRENT_ALLOCA(decremented, piece_index_t, size); + + if (!m_dirty) + { + // first count how many pieces we're updating. If it's few (less than half) + // we'll just update them one at a time. Otherwise we'll just update the counters + // and mark the picker as dirty, so we'll rebuild it next time we need it. + // this only matters if we're not already dirty, in which case the fasted + // thing to do is to just update the counters and be done + piece_index_t index = piece_index_t(0); + int num_dec = 0; + for (auto i = bitmask.begin(), end(bitmask.end()); i != end; ++i, ++index) + { + if (!*i) continue; + if (num_dec < size) decremented[num_dec] = index; + ++num_dec; + if (num_dec >= size) break; + } + + if (num_dec < size) + { + // not that many pieces were updated + // just update those individually instead of + // rebuilding the whole piece list + for (int i = 0; i < num_dec; ++i) + { + piece_index_t const piece = decremented[i]; + piece_pos& p = m_piece_map[piece]; + int prev_priority = p.priority(this); + + if (p.peer_count == 0) + { + TORRENT_ASSERT(m_seeds > 0); + // this is the case where we have one or more + // seeds, and one of them saying: I don't have this + // piece anymore. we need to break up one of the seed + // counters into actual peer counters on the pieces + break_one_seed(); + } + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 1); + p.have_peers.erase(peer); +#else + TORRENT_UNUSED(peer); +#endif + TORRENT_ASSERT(p.peer_count > 0); + --p.peer_count; + if (!m_dirty && prev_priority >= 0) update(prev_priority, p.index); + } + return; + } + } + + piece_index_t index = piece_index_t(0); + bool updated = false; + for (auto i = bitmask.begin(), end(bitmask.end()); i != end; ++i, ++index) + { + if (*i) + { + piece_pos& p = m_piece_map[index]; + if (p.peer_count == 0) + { + TORRENT_ASSERT(m_seeds > 0); + // this is the case where we have one or more + // seeds, and one of them saying: I don't have this + // piece anymore. we need to break up one of the seed + // counters into actual peer counters on the pieces + break_one_seed(); + } + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 1); + p.have_peers.erase(peer); +#else + TORRENT_UNUSED(peer); +#endif + + TORRENT_ASSERT(p.peer_count > 0); + --p.peer_count; + updated = true; + } + } + + // if we're already dirty, no point in doing anything more + if (m_dirty) return; + + if (updated) m_dirty = true; + } + + void piece_picker::update_pieces() const + { + TORRENT_ASSERT(m_dirty); + if (m_priority_boundaries.empty()) m_priority_boundaries.resize(1, prio_index_t(0)); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "update_pieces" << std::endl; +#endif + + // This code is unfortunately not very straight-forward. What we do here + // is to count the number of pieces at every priority level. After this + // first step, m_priority_boundaries will contain *deltas* rather than + // absolute indices. This is fixed up in a second pass below + std::fill(m_priority_boundaries.begin(), m_priority_boundaries.end(), prio_index_t(0)); + for (auto& pos : m_piece_map) + { + int prio = pos.priority(this); + if (prio == -1) continue; + if (prio >= int(m_priority_boundaries.size())) + m_priority_boundaries.resize(prio + 1, prio_index_t(0)); + pos.index = m_priority_boundaries[prio]; + ++m_priority_boundaries[prio]; + } + +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + + // m_priority_boundaries just contain counters of + // each priority level at this point. Now, make the m_priority_boundaries + // be cumulative indices into m_pieces (but m_pieces hasn't been set up + // yet) + int new_size = 0; + for (prio_index_t& b : m_priority_boundaries) + { + new_size += static_cast(b); + b = prio_index_t(new_size); + } + m_pieces.resize(new_size, piece_index_t(0)); + +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + + // set up m_pieces to contain valid piece indices, based on piece + // priority. m_piece_map[].index is still just an index relative to the + // respective priority range. + piece_index_t piece = piece_index_t(0); + for (auto i = m_piece_map.begin(), end(m_piece_map.end()); i != end; ++i, ++piece) + { + piece_pos& p = *i; + int const prio = p.priority(this); + if (prio == -1) continue; + prio_index_t const new_index(priority_begin(prio) + + prio_index_t::diff_type(static_cast(p.index))); + m_pieces[new_index] = piece; + } + + prio_index_t start(0); + for (auto b : m_priority_boundaries) + { + if (start == b) continue; + span r(&m_pieces[start], static_cast(b - start)); + aux::random_shuffle(r); + start = b; + } + + // this is where we set fix up the m_piece_map[].index to actually map + // back to the piece list ordered by priority (m_pieces) + prio_index_t index(0); + for (auto p : m_pieces) + { + m_piece_map[p].index = index; + ++index; + } + + m_dirty = false; +#ifdef TORRENT_PICKER_LOG + print_pieces(*this); +#endif + } + + void piece_picker::piece_passed(piece_index_t const index) + { + piece_pos& p = m_piece_map[index]; + auto const download_state = p.download_queue(); + + // this is kind of odd. Could this happen? + TORRENT_ASSERT(download_state != piece_pos::piece_open); + if (download_state == piece_pos::piece_open) return; + + auto const i = find_dl_piece(download_state, index); + TORRENT_ASSERT(i != m_downloads[download_state].end()); + + TORRENT_ASSERT(i->locked == false); + if (i->locked) return; + + TORRENT_ASSERT(!i->passed_hash_check); + i->passed_hash_check = true; + ++m_num_passed; + + if (i->finished < blocks_in_piece(index)) return; + + we_have(index); + } + + void piece_picker::we_dont_have(piece_index_t const index) + { + INVARIANT_CHECK; + piece_pos& p = m_piece_map[index]; + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::we_dont_have(" + << index << ")" << std::endl; +#endif + + if (!p.have()) + { + // even though we don't have the piece, it + // might still have passed hash check + auto const download_state = p.download_queue(); + if (download_state == piece_pos::piece_open) return; + + auto const i = find_dl_piece(download_state, index); + if (i->passed_hash_check) + { + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + erase_download_piece(i); + return; + } + + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + if (p.filtered()) + { + m_filtered_pad_blocks += pad_blocks_in_piece(index); + ++m_num_filtered; + + TORRENT_ASSERT(m_have_filtered_pad_blocks >= pad_blocks_in_piece(index)); + m_have_filtered_pad_blocks -= pad_blocks_in_piece(index); + TORRENT_ASSERT(m_num_have_filtered > 0); + --m_num_have_filtered; + } + else + { + // update cursors + if (index < m_cursor) m_cursor = index; + if (index >= m_reverse_cursor) m_reverse_cursor = next(index); + if (m_reverse_cursor == m_cursor) + { + m_reverse_cursor = piece_index_t(0); + m_cursor = m_piece_map.end_index(); + } + } + + --m_num_have; + m_have_pad_blocks -= pad_blocks_in_piece(index); + TORRENT_ASSERT(m_have_pad_blocks >= 0); + p.set_not_have(); + + if (m_dirty) return; + if (p.priority(this) >= 0) add(index); + } + + // this is used to indicate that we successfully have + // downloaded a piece, and that no further attempts + // to pick that piece should be made. The piece will + // be removed from the available piece list. + void piece_picker::we_have(piece_index_t const index) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::we_have(" + << index << ")" << std::endl; +#endif + piece_pos& p = m_piece_map[index]; + prio_index_t const info_index = p.index; + int const priority = p.priority(this); + TORRENT_ASSERT(priority < int(m_priority_boundaries.size()) || m_dirty); + + if (p.have()) return; + + auto const state = p.download_queue(); + if (state != piece_pos::piece_open) + { + auto const i = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + // decrement num_passed here to compensate + // for the unconditional increment further down + if (i->passed_hash_check) --m_num_passed; + erase_download_piece(i); + } + + if (p.filtered()) + { + TORRENT_ASSERT(m_filtered_pad_blocks >= pad_blocks_in_piece(index)); + m_filtered_pad_blocks -= pad_blocks_in_piece(index); + TORRENT_ASSERT(m_num_filtered > 0); + --m_num_filtered; + + m_have_filtered_pad_blocks += pad_blocks_in_piece(index); + ++m_num_have_filtered; + } + ++m_num_have; + ++m_num_passed; + m_have_pad_blocks += pad_blocks_in_piece(index); + TORRENT_ASSERT(m_have_pad_blocks <= num_pad_blocks()); + p.set_have(); + if (m_cursor == prev(m_reverse_cursor) + && m_cursor == index) + { + m_cursor = m_piece_map.end_index(); + m_reverse_cursor = piece_index_t(0); + TORRENT_ASSERT(num_pieces() > 0); + } + else if (m_cursor == index) + { + ++m_cursor; + for (auto i = m_piece_map.begin() + static_cast(m_cursor) + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_cursor); + } + else if (prev(m_reverse_cursor) == index) + { + --m_reverse_cursor; + TORRENT_ASSERT(m_piece_map[m_reverse_cursor].have() + || m_piece_map[m_reverse_cursor].filtered()); + for (auto i = m_piece_map.begin() + static_cast(m_reverse_cursor) - 1; + m_reverse_cursor > piece_index_t(0) && (i->have() || i->filtered()); + --i, --m_reverse_cursor); + TORRENT_ASSERT(m_piece_map[m_reverse_cursor].have() + || m_piece_map[m_reverse_cursor].filtered()); + } + TORRENT_ASSERT(m_reverse_cursor > m_cursor + || (m_cursor == m_piece_map.end_index() && m_reverse_cursor == piece_index_t(0))); + if (priority == -1) return; + if (m_dirty) return; + remove(priority, info_index); + TORRENT_ASSERT(p.priority(this) == -1); + } + + void piece_picker::we_have_all() + { + INVARIANT_CHECK; +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "piece_picker::we_have_all()\n"; +#endif + + m_priority_boundaries.clear(); + m_priority_boundaries.resize(1, prio_index_t(0)); + m_block_info.clear(); + m_free_block_infos.clear(); + m_pieces.clear(); + + m_dirty = false; + m_num_have_filtered += m_num_filtered; + m_num_filtered = 0; + m_have_filtered_pad_blocks += m_filtered_pad_blocks; + m_filtered_pad_blocks = 0; + m_cursor = m_piece_map.end_index(); + m_reverse_cursor = piece_index_t{0}; + m_num_passed = num_pieces(); + m_num_have = num_pieces(); + + for (auto& queue : m_downloads) queue.clear(); + for (auto& p : m_piece_map) + { + p.set_have(); + p.state(piece_pos::piece_open); + } + } + + bool piece_picker::set_piece_priority(piece_index_t const index + , download_priority_t const new_piece_priority) + { + INVARIANT_CHECK; + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "set_piece_priority(" << index + << ", " << new_piece_priority << ")" << std::endl; +#endif + + static_assert(std::is_unsigned::value + , "we need assert new_piece_priority >= dont_download"); + TORRENT_ASSERT(new_piece_priority <= top_priority); + + piece_pos& p = m_piece_map[index]; + + // if the priority isn't changed, don't do anything + if (new_piece_priority == download_priority_t(p.piece_priority)) return false; + + int const prev_priority = p.priority(this); + TORRENT_ASSERT(m_dirty || prev_priority < int(m_priority_boundaries.size())); + + bool ret = false; + if (new_piece_priority == dont_download + && p.piece_priority != piece_pos::filter_priority) + { + // the piece just got filtered + if (p.have()) + { + m_have_filtered_pad_blocks += pad_blocks_in_piece(index); + ++m_num_have_filtered; + } + else + { + m_filtered_pad_blocks += pad_blocks_in_piece(index); + ++m_num_filtered; + + // update m_cursor + if (m_cursor == prev(m_reverse_cursor) && m_cursor == index) + { + m_cursor = m_piece_map.end_index(); + m_reverse_cursor = piece_index_t(0); + } + else if (m_cursor == index) + { + ++m_cursor; + while (m_cursor < m_piece_map.end_index() + && (m_piece_map[m_cursor].have() + || m_piece_map[m_cursor].filtered())) + ++m_cursor; + } + else if (m_reverse_cursor == next(index)) + { + --m_reverse_cursor; + while (m_reverse_cursor > piece_index_t(0) + && (m_piece_map[prev(m_reverse_cursor)].have() + || m_piece_map[prev(m_reverse_cursor)].filtered())) + --m_reverse_cursor; + } + } + ret = true; + } + else if (new_piece_priority != dont_download + && p.piece_priority == piece_pos::filter_priority) + { + // the piece just got unfiltered + if (p.have()) + { + TORRENT_ASSERT(m_have_filtered_pad_blocks >= pad_blocks_in_piece(index)); + m_have_filtered_pad_blocks -= pad_blocks_in_piece(index); + TORRENT_ASSERT(m_num_have_filtered > 0); + --m_num_have_filtered; + } + else + { + TORRENT_ASSERT(m_filtered_pad_blocks >= pad_blocks_in_piece(index)); + m_filtered_pad_blocks -= pad_blocks_in_piece(index); + TORRENT_ASSERT(m_num_filtered > 0); + --m_num_filtered; + // update cursors + if (index < m_cursor) m_cursor = index; + if (index >= m_reverse_cursor) m_reverse_cursor = next(index); + if (m_reverse_cursor == m_cursor) + { + m_reverse_cursor = piece_index_t(0); + m_cursor = m_piece_map.end_index(); + } + } + ret = true; + } + TORRENT_ASSERT(m_num_filtered >= 0); + TORRENT_ASSERT(m_num_have_filtered >= 0); + + p.piece_priority = static_cast(new_piece_priority); + int const new_priority = p.priority(this); + + if (prev_priority != new_priority && !m_dirty) + { + if (prev_priority == -1) add(index); + else update(prev_priority, p.index); + } + + if (p.downloading()) + { + auto const i = find_dl_piece(p.download_queue(), index); + if (i != m_downloads[p.download_queue()].end()) + update_piece_state(i); + } + + return ret; + } + + download_priority_t piece_picker::piece_priority(piece_index_t const index) const + { + return download_priority_t(m_piece_map[index].piece_priority); + } + + void piece_picker::piece_priorities(std::vector& pieces) const + { + pieces.resize(m_piece_map.size()); + auto j = pieces.begin(); + for (auto i = m_piece_map.begin(), + end(m_piece_map.end()); i != end; ++i, ++j) + { + *j = download_priority_t(i->piece_priority); + } + } + +namespace { + + int append_blocks(std::vector& dst, std::vector& src + , int const num_blocks) + { + if (src.empty()) return num_blocks; + int const to_copy = std::min(int(src.size()), num_blocks); + + dst.insert(dst.end(), src.begin(), src.begin() + to_copy); + src.erase(src.begin(), src.begin() + to_copy); + return num_blocks - to_copy; + } + } + + // lower availability comes first. This is a less-than comparison, it returns + // true if lhs has lower availability than rhs + bool piece_picker::partial_compare_rarest_first(downloading_piece const* lhs + , downloading_piece const* rhs) const + { + int lhs_availability = m_piece_map[lhs->index].peer_count; + int rhs_availability = m_piece_map[rhs->index].peer_count; + if (lhs_availability != rhs_availability) + return lhs_availability < rhs_availability; + + // if the availability is the same, prefer the piece that's closest to + // being complete. + int lhs_blocks_left = m_blocks_per_piece - lhs->finished - lhs->writing + - lhs->requested; + TORRENT_ASSERT(lhs_blocks_left > 0); + int rhs_blocks_left = m_blocks_per_piece - rhs->finished - rhs->writing + - rhs->requested; + TORRENT_ASSERT(rhs_blocks_left > 0); + return lhs_blocks_left < rhs_blocks_left; + } + + // pieces describes which pieces the peer we're requesting from has. + // interesting_blocks is an out parameter, and will be filled with (up to) + // num_blocks of interesting blocks that the peer has. + // prefer_contiguous_blocks can be set if this peer should download whole + // pieces rather than trying to download blocks from the same piece as other + // peers. the peer argument is the torrent_peer of the peer we're + // picking pieces from. This is used when downloading whole pieces, to only + // pick from the same piece the same peer is downloading from. + + // options are: + // * rarest_first + // pick the rarest pieces first + // * reverse + // reverse the piece picking. Pick the most common + // pieces first or the last pieces (if picking sequential) + // * sequential + // download pieces in-order + // * on_parole + // the peer is on parole, only pick whole pieces which + // has only been downloaded and requested from the same + // peer + // * prioritize_partials + // pick blocks from downloading pieces first + + // only one of rarest_first or sequential can be set + + // the return value is a combination of picker_flags_t, + // indicating which path thought the picker we took to arrive at the + // returned block picks. + picker_flags_t piece_picker::pick_pieces(typed_bitfield const& pieces + , std::vector& interesting_blocks, int num_blocks + , int prefer_contiguous_blocks, torrent_peer* peer + , picker_options_t options, std::vector const& suggested_pieces + , int num_peers + , counters& pc + ) const + { + TORRENT_ASSERT(peer == nullptr || peer->in_use); + picker_flags_t ret; + + // prevent the number of partial pieces to grow indefinitely + // make this scale by the number of peers we have. For large + // scale clients, we would have more peers, and allow a higher + // threshold for the number of partials + // the second condition is to make sure we cap the number of partial + // _bytes_. The larger the pieces are, the fewer partial pieces we want. + // 2048 corresponds to 32 MiB + // TODO: 2 make the 2048 limit configurable + const int num_partials = int(m_downloads[piece_pos::piece_downloading].size()); + if (num_partials > num_peers * 3 / 2 + || num_partials * m_blocks_per_piece > 2048) + { + // if we have too many partial pieces, prioritize completing + // them. In order for this to have an affect, also disable + // prefer whole pieces (otherwise partial pieces would be de-prioritized) + options |= prioritize_partials; + prefer_contiguous_blocks = 0; + + ret |= picker_log_alert::partial_ratio; + } + + if (prefer_contiguous_blocks) ret |= picker_log_alert::prefer_contiguous; + + // only one of rarest_first and sequential can be set. + TORRENT_ASSERT(((options & rarest_first) ? 1 : 0) + + ((options & sequential) ? 1 : 0) <= 1); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + TORRENT_ASSERT(num_blocks > 0); + TORRENT_ASSERT(pieces.size() == int(m_piece_map.size())); + + TORRENT_ASSERT(!m_priority_boundaries.empty() || m_dirty); + + // this will be filled with blocks that we should not request + // unless we can't find num_blocks among the other ones. + std::vector backup_blocks; + std::vector backup_blocks2; + static const std::vector empty_vector; + + // When prefer_contiguous_blocks is set (usually set when downloading from + // fast peers) the partial pieces will not be prioritized, but actually + // ignored as long as possible. All blocks found in downloading + // pieces are regarded as backup blocks + + if (options & prioritize_partials) + { + // first, allocate a small array on the stack of all the partial + // pieces (downloading_piece). We'll then sort this list by + // availability or by some other condition. The list of partial pieces + // in m_downloads is ordered by piece index, this is to have O(log n) + // lookups when finding a downloading_piece for a specific piece index. + // this is important and needs to stay sorted that way, that's why + // we're copying it here + TORRENT_ALLOCA(ordered_partials, downloading_piece const* + , m_downloads[piece_pos::piece_downloading].size()); + int num_ordered_partials = 0; + + // now, copy over the pointers. We also apply a filter here to not + // include ineligible pieces in certain modes. For instance, a piece + // that the current peer doesn't have is not included. + for (auto& dp : m_downloads[piece_pos::piece_downloading]) + { + pc.inc_stats_counter(counters::piece_picker_partial_loops); + + // in time critical mode, only pick high priority pieces + if ((options & time_critical_mode) + && piece_priority(dp.index) != top_priority) + continue; + + if (!is_piece_free(dp.index, pieces)) continue; + + TORRENT_ASSERT(m_piece_map[dp.index].download_queue() + == piece_pos::piece_downloading); + + ordered_partials[num_ordered_partials++] = &dp; + } + + // now, sort the list. + if (options & rarest_first) + { + ret |= picker_log_alert::rarest_first_partials; + + // TODO: this could probably be optimized by incrementally + // calling partial_sort to sort one more element in the list. Because + // chances are that we'll just need a single piece, and once we've + // picked from it we're done. Sorting the rest of the list in that + // case is a waste of time. + std::sort(ordered_partials.begin(), ordered_partials.begin() + num_ordered_partials + , std::bind(&piece_picker::partial_compare_rarest_first, this + , _1, _2)); + } + + for (int i = 0; i < num_ordered_partials; ++i) + { + ret |= picker_log_alert::prioritize_partials; + + num_blocks = add_blocks_downloading(*ordered_partials[i], pieces + , interesting_blocks, backup_blocks, backup_blocks2 + , num_blocks, prefer_contiguous_blocks, peer, options); + if (num_blocks <= 0) return ret; + if (int(backup_blocks.size()) >= num_blocks + && int(backup_blocks2.size()) >= num_blocks) + break; + } + + num_blocks = append_blocks(interesting_blocks, backup_blocks + , num_blocks); + if (num_blocks <= 0) return ret; + + num_blocks = append_blocks(interesting_blocks, backup_blocks2 + , num_blocks); + if (num_blocks <= 0) return ret; + } + + if (!suggested_pieces.empty()) + { + for (piece_index_t i : suggested_pieces) + { + // in time critical mode, only pick high priority pieces + if ((options & time_critical_mode) + && piece_priority(i) != top_priority) + continue; + + pc.inc_stats_counter(counters::piece_picker_suggest_loops); + if (!is_piece_free(i, pieces)) continue; + + ret |= picker_log_alert::suggested_pieces; + + num_blocks = add_blocks(i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, empty_vector + , options); + if (num_blocks <= 0) return ret; + } + } + + if (options & sequential) + { + if (m_dirty) update_pieces(); + TORRENT_ASSERT(!m_dirty); + + for (auto i = m_pieces.begin(); + i != m_pieces.end() && piece_priority(*i) == top_priority; ++i) + { + if (!is_piece_free(*i, pieces)) continue; + + ret |= picker_log_alert::prio_sequential_pieces; + + num_blocks = add_blocks(*i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + + // in time critical mode, only pick high priority pieces + if (!(options & time_critical_mode)) + { + if (options & reverse) + { + for (piece_index_t i = prev(m_reverse_cursor); i >= m_cursor; --i) + { + if (!is_piece_free(i, pieces)) continue; + // we've already added high priority pieces + if (piece_priority(i) == top_priority) continue; + + ret |= picker_log_alert::reverse_sequential; + + num_blocks = add_blocks(i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + else + { + for (piece_index_t i = m_cursor; i < m_reverse_cursor; ++i) + { + if (!is_piece_free(i, pieces)) continue; + // we've already added high priority pieces + if (piece_priority(i) == top_priority) continue; + + ret |= picker_log_alert::sequential_pieces; + + num_blocks = add_blocks(i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + } + } + else if (options & rarest_first) + { + if (m_dirty) update_pieces(); + TORRENT_ASSERT(!m_dirty); + + // in time critical mode, we're only allowed to pick high priority + // pieces. This is why reverse mode is disabled when we're in + // time-critical mode, because all high priority pieces are at the + // front of the list + if ((options & reverse) && !(options & time_critical_mode)) + { + for (int i = int(m_priority_boundaries.size()) - 1; i >= 0; --i) + { + prio_index_t const start = priority_begin(i); + prio_index_t const end = priority_end(i); + for (prio_index_t p = prev(end); p >= start; --p) + { + pc.inc_stats_counter(counters::piece_picker_reverse_rare_loops); + + if (!is_piece_free(m_pieces[p], pieces)) continue; + + ret |= picker_log_alert::reverse_rarest_first; + + num_blocks = add_blocks(m_pieces[p], pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + } + else + { + // TODO: Is it a good idea that this affinity takes precedence over + // piece priority? + if (options & piece_extent_affinity) + { + int to_erase = -1; + int idx = -1; + for (piece_extent_t const e : m_recent_extents) + { + ++idx; + bool have_all = true; + for (piece_index_t const p : extent_for(e)) + { + if (!m_piece_map[p].have()) have_all = false; + if (!is_piece_free(p, pieces)) continue; + + ret |= picker_log_alert::extent_affinity; + + num_blocks = add_blocks(p, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) + { + // if we have all pieces belonging to this extent, remove it + if (to_erase != -1) m_recent_extents.erase(m_recent_extents.begin() + to_erase); + return ret; + } + } + // if we have all pieces belonging to this extent, remove it + if (have_all) to_erase = idx; + } + if (to_erase != -1) m_recent_extents.erase(m_recent_extents.begin() + to_erase); + } + + for (piece_index_t i : m_pieces) + { + pc.inc_stats_counter(counters::piece_picker_rare_loops); + + // in time critical mode, only pick high priority pieces + // it's safe to break here because in this mode we + // pick pieces in priority order. Once we hit a lower priority + // piece, we won't encounter any more high priority ones + if ((options & time_critical_mode) + && piece_priority(i) != top_priority) + break; + + if (!is_piece_free(i, pieces)) continue; + + ret |= picker_log_alert::rarest_first; + + num_blocks = add_blocks(i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + } + else if (options & time_critical_mode) + { + // if we're in time-critical mode, we are only allowed to pick + // high priority pieces. + for (auto i = m_pieces.begin(); + i != m_pieces.end() && piece_priority(*i) == top_priority; ++i) + { + if (!is_piece_free(*i, pieces)) continue; + + ret |= picker_log_alert::time_critical; + + num_blocks = add_blocks(*i, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, suggested_pieces + , options); + if (num_blocks <= 0) return ret; + } + } + else + { + // we're not using rarest first (only for the first + // bucket, since that's where the currently downloading + // pieces are) + piece_index_t const start_piece = piece_index_t(int(random(aux::numeric_cast(m_piece_map.size() - 1)))); + + piece_index_t piece = start_piece; + while (num_blocks > 0) + { + // skip pieces we can't pick, and suggested pieces + // since we've already picked those + while (!is_piece_free(piece, pieces) + || std::find(suggested_pieces.begin() + , suggested_pieces.end(), piece) + != suggested_pieces.end()) + { + pc.inc_stats_counter(counters::piece_picker_rand_start_loops); + ++piece; + if (piece == m_piece_map.end_index()) piece = piece_index_t(0); + // could not find any more pieces + if (piece == start_piece) { goto get_out; } + } + + if (prefer_contiguous_blocks > 1 && !m_piece_map[piece].downloading()) + { + TORRENT_ASSERT(can_pick(piece, pieces)); + TORRENT_ASSERT(m_piece_map[piece].downloading() == false); + + piece_index_t start, end; + std::tie(start, end) = expand_piece(piece + , prefer_contiguous_blocks, pieces, options); + TORRENT_ASSERT(end > start); + for (piece_index_t k = start; k < end; ++k) + { + TORRENT_ASSERT(m_piece_map[k].downloading() == false); + TORRENT_ASSERT(m_piece_map[k].priority(this) >= 0); + const int num_blocks_in_piece = blocks_in_piece(k); + + ret |= picker_log_alert::random_pieces; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + pc.inc_stats_counter(counters::piece_picker_rand_loops); + TORRENT_ASSERT(is_piece_free(k, pieces)); + interesting_blocks.emplace_back(k, j); + --num_blocks; + --prefer_contiguous_blocks; + if (prefer_contiguous_blocks <= 0 + && num_blocks <= 0) break; + } + } + piece = end; + } + else + { + ret |= picker_log_alert::random_pieces; + + num_blocks = add_blocks(piece, pieces + , interesting_blocks, backup_blocks + , backup_blocks2, num_blocks + , prefer_contiguous_blocks, peer, empty_vector + , options); + ++piece; + } + + if (piece == m_piece_map.end_index()) piece = piece_index_t(0); + // could not find any more pieces + if (piece == start_piece) break; + } + } +get_out: + + if (num_blocks <= 0) return ret; + +#if TORRENT_USE_INVARIANT_CHECKS + verify_pick(interesting_blocks, pieces); + verify_pick(backup_blocks, pieces); + verify_pick(backup_blocks2, pieces); +#endif + + ret |= picker_log_alert::backup1; + num_blocks = append_blocks(interesting_blocks, backup_blocks, num_blocks); + if (num_blocks <= 0) return ret; + + ret |= picker_log_alert::backup2; + num_blocks = append_blocks(interesting_blocks, backup_blocks2, num_blocks); + if (num_blocks <= 0) return ret; + + // ===== THIS IS FOR END-GAME MODE ===== + + // don't double-pick anything if the peer is on parole + if (options & on_parole) return ret; + + // in end game mode we pick a single block + // that has already been requested from someone + // all pieces that are interesting are in + // m_downloads[0] and m_download[1] + // (i.e. partial and full pieces) + + std::vector temp; + + // pick one random block from one random partial piece. + // only pick from non-downloaded blocks. + // first, create a temporary array of the partial pieces + // this peer has, and can pick from. Cap the stack allocation + // at 200 pieces. + + int partials_size = std::min(200, int( + m_downloads[piece_pos::piece_downloading].size() + + m_downloads[piece_pos::piece_full].size())); + if (partials_size == 0) return ret; + + TORRENT_ALLOCA(partials, downloading_piece const*, partials_size); + int c = 0; + +#if TORRENT_USE_INVARIANT_CHECKS + // if we get here, we're about to pick a busy block. First, make sure + // we really exhausted the available blocks + for (std::vector::const_iterator i + = m_downloads[piece_pos::piece_downloading].begin() + , end(m_downloads[piece_pos::piece_downloading].end()); i != end; ++i) + { + downloading_piece const& dp = *i; + + if ((options & time_critical_mode) + && piece_priority(dp.index) != top_priority) + continue; + + // we either don't have this piece, or we've already requested from it + if (!pieces[dp.index]) continue; + + // if we already have the piece, obviously we should not have + // since this is a partial piece in the piece_downloading state, we + // should not already have it + TORRENT_ASSERT(!m_piece_map[dp.index].have()); + + // if it was filtered, it would be in the prio_zero queue + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + + // we're not allowed to pick from locked pieces + if (dp.locked) continue; + + bool found = false; + for (std::vector::const_iterator j + = interesting_blocks.begin(), end2(interesting_blocks.end()); + j != end2; ++j) + { + if (j->piece_index != dp.index) continue; + found = true; + break; + } + + // we expect to find this piece in our interesting_blocks list + TORRENT_ASSERT(found); + } +#endif + + for (auto const& dp : m_downloads[piece_pos::piece_full]) + { + if (c == partials_size) break; + + TORRENT_ASSERT(dp.requested > 0); + // this peer doesn't have this piece, try again + if (!pieces[dp.index]) continue; + // don't pick pieces with priority 0 + TORRENT_ASSERT(piece_priority(dp.index) > dont_download); + + if ((options & time_critical_mode) + && piece_priority(dp.index) != top_priority) + continue; + + partials[c++] = &dp; + } + + partials_size = c; + while (partials_size > 0) + { + pc.inc_stats_counter(counters::piece_picker_busy_loops); + int piece = int(random(aux::numeric_cast(partials_size - 1))); + downloading_piece const* dp = partials[piece]; + TORRENT_ASSERT(pieces[dp->index]); + TORRENT_ASSERT(piece_priority(dp->index) > dont_download); + // fill in with blocks requested from other peers + // as backups + TORRENT_ASSERT(dp->requested > 0); + int idx = -1; + for (auto const& info : blocks_for_piece(*dp)) + { + ++idx; + TORRENT_ASSERT(info.peer == nullptr || info.peer->in_use); + TORRENT_ASSERT(info.piece_index == dp->index); + if (info.state != block_info::state_requested || info.peer == peer) + continue; + temp.emplace_back(dp->index, idx); + } + // are we done? + if (!temp.empty()) + { + ret |= picker_log_alert::end_game; + interesting_blocks.push_back(temp[random(std::uint32_t(temp.size()) - 1)]); + --num_blocks; + break; + } + + // the piece we picked only had blocks outstanding requested + // by ourself. Remove it and pick another one. + partials[piece] = partials[partials_size - 1]; + --partials_size; + } + +#if TORRENT_USE_INVARIANT_CHECKS +// make sure that we at this point have added requests to all unrequested blocks +// in all downloading pieces + + for (auto const& i : m_downloads[piece_pos::piece_downloading]) + { + if (!pieces[i.index]) continue; + if (piece_priority(i.index) == dont_download) continue; + if (i.locked) continue; + + if ((options & time_critical_mode) + && piece_priority(i.index) != top_priority) + continue; + + int idx = -1; + for (auto const& info : blocks_for_piece(i)) + { + ++idx; + TORRENT_ASSERT(info.piece_index == i.index); + if (info.state != block_info::state_none) continue; + auto k = std::find(interesting_blocks.begin(), interesting_blocks.end() + , piece_block(i.index, idx)); + if (k != interesting_blocks.end()) continue; + + std::fprintf(stderr, "interesting blocks:\n"); + for (auto const& p : interesting_blocks) + { + std::fprintf(stderr, "(%d, %d)" + , static_cast(p.piece_index), p.block_index); + } + std::fprintf(stderr, "\nnum_blocks: %d\n", num_blocks); + + for (auto const& l : m_downloads[piece_pos::piece_downloading]) + { + auto const binfo2 = blocks_for_piece(l); + std::fprintf(stderr, "%d : ", static_cast(l.index)); + const int cnt = blocks_in_piece(l.index); + for (int m = 0; m < cnt; ++m) + std::fprintf(stderr, "%d", binfo2[m].state); + std::fprintf(stderr, "\n"); + } + + TORRENT_ASSERT_FAIL(); + } + } + + if (interesting_blocks.empty()) + { + for (piece_index_t i = piece_index_t(0); + i != m_piece_map.end_index(); ++i) + { + if (!pieces[i]) continue; + if (m_piece_map[i].priority(this) <= 0) continue; + if (have_piece(i)) continue; + + auto const download_state = m_piece_map[i].download_queue(); + if (download_state == piece_pos::piece_open) continue; + std::vector::const_iterator k + = find_dl_piece(download_state, i); + + TORRENT_ASSERT(k != m_downloads[download_state].end()); + if (k == m_downloads[download_state].end()) continue; + } + } +#endif + return ret; + } + + // have piece means that the piece passed hash check + // AND has been successfully written to disk + bool piece_picker::have_piece(piece_index_t const index) const + { + piece_pos const& p = m_piece_map[index]; + return p.index == piece_pos::we_have_index; + } + + int piece_picker::blocks_in_piece(piece_index_t const index) const + { + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_piece_map.end_index()); + if (next(index) == m_piece_map.end_index()) + return m_blocks_in_last_piece; + else + return m_blocks_per_piece; + } + + bool piece_picker::is_piece_free(piece_index_t const piece + , typed_bitfield const& bitmask) const + { + return bitmask[piece] + && !m_piece_map[piece].have() + && !m_piece_map[piece].filtered(); + } + + bool piece_picker::can_pick(piece_index_t const piece + , typed_bitfield const& bitmask) const + { + return bitmask[piece] + && !m_piece_map[piece].have() + // TODO: when expanding pieces for cache stripe reasons, + // the !downloading condition doesn't make much sense + && !m_piece_map[piece].downloading() + && !m_piece_map[piece].filtered(); + } + +#if TORRENT_USE_INVARIANT_CHECKS + void piece_picker::check_peers() + { + for (auto const& b : m_block_info) + { + TORRENT_ASSERT(b.peer == nullptr || static_cast(b.peer)->in_use); + } + } +#endif + + void piece_picker::clear_peer(torrent_peer* peer) + { + for (auto& b : m_block_info) + { + if (b.peer == peer) b.peer = nullptr; + } + } + + // the first bool is true if this is the only peer that has requested and downloaded + // blocks from this piece. + // the second bool is true if this is the only active peer that is requesting + // and downloading blocks from this piece. Active means having a connection. + // TODO: 2 the first_block returned here is the largest free range, not + // the first-fit range, which would be better + std::tuple piece_picker::requested_from( + piece_picker::downloading_piece const& p + , int const num_blocks_in_piece, torrent_peer* peer) const + { + bool exclusive = true; + bool exclusive_active = true; + int contiguous_blocks = 0; + int max_contiguous = 0; + int first_block = 0; + int idx = -1; + for (auto const& info : blocks_for_piece(p)) + { + ++idx; + TORRENT_ASSERT(info.peer == nullptr || info.peer->in_use); + TORRENT_ASSERT(info.piece_index == p.index); + if (info.state == piece_picker::block_info::state_none) + { + ++contiguous_blocks; + continue; + } + if (contiguous_blocks > max_contiguous) + { + max_contiguous = contiguous_blocks; + first_block = idx - contiguous_blocks; + } + contiguous_blocks = 0; + if (info.peer != peer) + { + exclusive = false; + if (info.state == piece_picker::block_info::state_requested + && info.peer != nullptr) + { + exclusive_active = false; + } + } + } + if (contiguous_blocks > max_contiguous) + { + max_contiguous = contiguous_blocks; + first_block = num_blocks_in_piece - contiguous_blocks; + } + return std::make_tuple(exclusive, exclusive_active, max_contiguous + , first_block); + } + + int piece_picker::add_blocks(piece_index_t piece + , typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, std::vector const& ignore + , picker_options_t const options) const + { + TORRENT_ASSERT(is_piece_free(piece, pieces)); + + // ignore pieces found in the ignore list + if (std::find(ignore.begin(), ignore.end(), piece) != ignore.end()) return num_blocks; + + auto const state = m_piece_map[piece].download_queue(); + if (state != piece_pos::piece_open + && state != piece_pos::piece_downloading) + return num_blocks; + + TORRENT_ASSERT(m_piece_map[piece].priority(this) >= 0); + if (state == piece_pos::piece_downloading) + { + // if we're prioritizing partials, we've already + // looked through the downloading pieces + if (options & prioritize_partials) return num_blocks; + + auto i = find_dl_piece(piece_pos::piece_downloading, piece); + TORRENT_ASSERT(i != m_downloads[state].end()); + + return add_blocks_downloading(*i, pieces + , interesting_blocks, backup_blocks, backup_blocks2 + , num_blocks, prefer_contiguous_blocks, peer, options); + } + + int num_blocks_in_piece = blocks_in_piece(piece); + + // pick a new piece + if (prefer_contiguous_blocks == 0) + { + if (num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + TORRENT_ASSERT(is_piece_free(piece, pieces)); + for (int j = 0; j < num_blocks_in_piece; ++j) + interesting_blocks.emplace_back(piece, j); + num_blocks -= num_blocks_in_piece; + } + else + { + piece_index_t start, end; + std::tie(start, end) = expand_piece(piece, prefer_contiguous_blocks + , pieces, options); + for (piece_index_t k = start; k < end; ++k) + { + TORRENT_ASSERT(m_piece_map[k].priority(this) > 0); + num_blocks_in_piece = blocks_in_piece(k); + TORRENT_ASSERT(is_piece_free(k, pieces)); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + interesting_blocks.emplace_back(k, j); + --num_blocks; + --prefer_contiguous_blocks; + if (prefer_contiguous_blocks == 0 + && num_blocks <= 0) break; + } + } + } +#if TORRENT_USE_INVARIANT_CHECKS + verify_pick(interesting_blocks, pieces); +#endif + return std::max(num_blocks, 0); + } + + int piece_picker::add_blocks_downloading(downloading_piece const& dp + , typed_bitfield const& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , std::vector& backup_blocks2 + , int num_blocks, int prefer_contiguous_blocks + , torrent_peer* peer, picker_options_t const options) const + { + if (!pieces[dp.index]) return num_blocks; + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + + // this piece failed to write. We're currently restoring + // it. It's not OK to send more requests to it right now. + if (dp.locked) return num_blocks; + + int num_blocks_in_piece = blocks_in_piece(dp.index); + + // is true if all the other pieces that are currently + // requested from this piece are from the same + // peer as 'peer'. + bool exclusive; + bool exclusive_active; + + // used to report back the largest contiguous block run + int contiguous_blocks; + int first_block; + std::tie(exclusive, exclusive_active, contiguous_blocks, first_block) + = requested_from(dp, num_blocks_in_piece, peer); + + // no need in picking from the largest contiguous block run unless + // we're interested in it. In fact, we really want the opposite. + if (prefer_contiguous_blocks == 0) first_block = 0; + + // peers on parole are only allowed to pick blocks from + // pieces that only they have downloaded/requested from + if ((options & on_parole) && !exclusive) return num_blocks; + + auto const binfo = blocks_for_piece(dp); + + // we prefer whole blocks, but there are other peers + // downloading from this piece and there aren't enough contiguous blocks + // to pick, add it as backups. + // if we're on parole, don't let the contiguous blocks stop us, we want + // to primarily request from a piece all by ourselves. + if (prefer_contiguous_blocks > contiguous_blocks + && !exclusive_active + && !(options & on_parole)) + { + if (int(backup_blocks2.size()) >= num_blocks) + return num_blocks; + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + // ignore completed blocks and already requested blocks + int const block_idx = (j + first_block) % num_blocks_in_piece; + block_info const& info = binfo[block_idx]; + TORRENT_ASSERT(info.piece_index == dp.index); + if (info.state != block_info::state_none) continue; + backup_blocks2.emplace_back(dp.index, block_idx); + } + return num_blocks; + } + + for (int j = 0; j < num_blocks_in_piece; ++j) + { + // ignore completed blocks and already requested blocks + int const block_idx = (j + first_block) % num_blocks_in_piece; + block_info const& info = binfo[block_idx]; + TORRENT_ASSERT(info.piece_index == dp.index); + if (info.state != block_info::state_none) continue; + + // this block is interesting (we don't have it yet). + interesting_blocks.emplace_back(dp.index, block_idx); + // we have found a block that's free to download + --num_blocks; + // if we prefer contiguous blocks, continue picking from this + // piece even though we have num_blocks + if (prefer_contiguous_blocks > 0) + { + --prefer_contiguous_blocks; + continue; + } + if (num_blocks <= 0) return 0; + } + + if (num_blocks <= 0) return 0; + if (options & on_parole) return num_blocks; + + if (int(backup_blocks.size()) >= num_blocks) return num_blocks; + +#if TORRENT_USE_INVARIANT_CHECKS + verify_pick(backup_blocks, pieces); +#endif + return num_blocks; + } + + std::pair + piece_picker::expand_piece(piece_index_t const piece, int const contiguous_blocks + , typed_bitfield const& have, picker_options_t const options) const + { + if (contiguous_blocks == 0) return std::make_pair(piece, next(piece)); + + // round to even pieces and expand in order to get the number of + // contiguous pieces we want + int const whole_pieces = (contiguous_blocks + m_blocks_per_piece - 1) + / m_blocks_per_piece; + + piece_index_t start = piece; + piece_index_t lower_limit; + + if (options & align_expanded_pieces) + { + lower_limit = piece_index_t(static_cast(piece) - (static_cast(piece) % whole_pieces)); + } + else + { + lower_limit = piece_index_t(static_cast(piece) - whole_pieces + 1); + if (lower_limit < piece_index_t(0)) lower_limit = piece_index_t(0); + } + + while (start > lower_limit && can_pick(prev(start), have)) + --start; + + TORRENT_ASSERT(start >= piece_index_t(0)); + piece_index_t end = next(piece); + piece_index_t upper_limit; + if (options & align_expanded_pieces) + { + upper_limit = piece_index_t(static_cast(lower_limit) + whole_pieces); + } + else + { + upper_limit = piece_index_t(static_cast(start) + whole_pieces); + } + if (upper_limit > have.end_index()) upper_limit = have.end_index(); + while (end < upper_limit && can_pick(end, have)) + ++end; + return std::make_pair(start, end); + } + + bool piece_picker::is_piece_finished(piece_index_t const index) const + { + piece_pos const& p = m_piece_map[index]; + if (p.index == piece_pos::we_have_index) return true; + + auto const state = p.download_queue(); + if (state == piece_pos::piece_open) + { +#if TORRENT_USE_ASSERTS + for (auto const i : categories()) + TORRENT_ASSERT(find_dl_piece(i, index) == m_downloads[i].end()); +#endif + return false; + } + auto const i = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + TORRENT_ASSERT(int(i->finished) <= m_blocks_per_piece); + int const max_blocks = blocks_in_piece(index); + if (int(i->finished) + int(i->writing) < max_blocks) return false; + TORRENT_ASSERT(int(i->finished) + int(i->writing) == max_blocks); + +#if TORRENT_USE_INVARIANT_CHECKS + for (auto const& info : blocks_for_piece(*i)) + { + TORRENT_ASSERT(info.piece_index == index); + TORRENT_ASSERT(info.state == block_info::state_finished + || info.state == block_info::state_writing); + } +#endif + + return true; + } + + bool piece_picker::has_piece_passed(piece_index_t const index) const + { + TORRENT_ASSERT(index < m_piece_map.end_index()); + TORRENT_ASSERT(index >= piece_index_t(0)); + + piece_pos const& p = m_piece_map[index]; + if (p.index == piece_pos::we_have_index) return true; + + auto const state = p.download_queue(); + if (state == piece_pos::piece_open) + { +#if TORRENT_USE_ASSERTS + for (auto const i : categories()) + TORRENT_ASSERT(find_dl_piece(i, index) == m_downloads[i].end()); +#endif + return false; + } + auto const i = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + return bool(i->passed_hash_check); + } + + std::vector::iterator piece_picker::find_dl_piece( + download_queue_t const queue, piece_index_t const index) + { + TORRENT_ASSERT(queue == piece_pos::piece_downloading + || queue == piece_pos::piece_full + || queue == piece_pos::piece_finished + || queue == piece_pos::piece_zero_prio); + + downloading_piece cmp; + cmp.index = index; + auto const i = std::lower_bound( + m_downloads[queue].begin(), m_downloads[queue].end(), cmp); + if (i == m_downloads[queue].end()) return i; + if (i->index == index) return i; + return m_downloads[queue].end(); + } + + std::vector::const_iterator piece_picker::find_dl_piece( + download_queue_t const queue, piece_index_t const index) const + { + return const_cast(this)->find_dl_piece(queue, index); + } + + std::vector::iterator + piece_picker::update_piece_state( + std::vector::iterator dp) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "update_piece_state(" << dp->index << ")" << std::endl; +#endif + + int const num_blocks = blocks_in_piece(dp->index); + piece_pos& p = m_piece_map[dp->index]; + auto const current_state = p.state(); + TORRENT_ASSERT(current_state != piece_pos::piece_open); + if (current_state == piece_pos::piece_open) + return dp; + + // this function is not allowed to create new downloading pieces + download_queue_t new_state{}; + if (p.filtered()) + { + new_state = piece_pos::piece_zero_prio; + } + else if (dp->requested + dp->finished + dp->writing == 0) + { + new_state = piece_pos::piece_open; + } + else if (dp->requested + dp->finished + dp->writing < num_blocks) + { + new_state = p.reverse() + ? piece_pos::piece_downloading_reverse + : piece_pos::piece_downloading; + } + else if (dp->requested > 0) + { + TORRENT_ASSERT(dp->requested + dp->finished + dp->writing == num_blocks); + new_state = p.reverse() + ? piece_pos::piece_full_reverse + : piece_pos::piece_full; + } + else + { + TORRENT_ASSERT(dp->finished + dp->writing == num_blocks); + new_state = piece_pos::piece_finished; + } + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << " new_state: " << new_state << " current_state: " << current_state << std::endl; +#endif + if (new_state == current_state) return dp; + if (new_state == piece_pos::piece_open) return dp; + + // assert that the iterator that was passed-in in fact lives in + // the correct list + TORRENT_ASSERT(find_dl_piece(p.download_queue(), dp->index) == dp); + + // remove the downloading_piece from the list corresponding + // to the old state + downloading_piece dp_info = *dp; + m_downloads[p.download_queue()].erase(dp); + + int const prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size()) || m_dirty); + p.state(new_state); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << " " << dp_info.index << " state (" << current_state << " -> " << new_state << ")" << std::endl; +#endif + + // insert the downloading_piece in the list corresponding to + // the new state + downloading_piece cmp; + cmp.index = dp_info.index; + auto i = std::lower_bound(m_downloads[p.download_queue()].begin() + , m_downloads[p.download_queue()].end(), cmp); + TORRENT_ASSERT(i == m_downloads[p.download_queue()].end() + || i->index != dp_info.index); + i = m_downloads[p.download_queue()].insert(i, dp_info); + + if (!m_dirty) + { + if (prio == -1 && p.priority(this) != -1) add(dp_info.index); + else if (prio != -1) update(prio, p.index); + } + + return i; + } + + bool piece_picker::is_requested(piece_block const block) const + { + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.end_index()); + + auto const state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return false; + auto const i = find_dl_piece(state, block.piece_index); + + TORRENT_ASSERT(i != m_downloads[state].end()); + + auto const info = blocks_for_piece(*i); + TORRENT_ASSERT(info[block.block_index].piece_index == block.piece_index); + return info[block.block_index].state == block_info::state_requested; + } + + bool piece_picker::is_downloaded(piece_block const block) const + { + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.end_index()); + + piece_pos const& p = m_piece_map[block.piece_index]; + if (p.index == piece_pos::we_have_index) return true; + auto const state = p.download_queue(); + if (state == piece_pos::piece_open) return false; + auto const i = find_dl_piece(state, block.piece_index); + TORRENT_ASSERT(i != m_downloads[state].end()); + + auto const info = blocks_for_piece(*i); + TORRENT_ASSERT(info[block.block_index].piece_index == block.piece_index); + return info[block.block_index].state == block_info::state_finished + || info[block.block_index].state == block_info::state_writing; + } + + bool piece_picker::is_finished(piece_block const block) const + { + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.end_index()); + + piece_pos const& p = m_piece_map[block.piece_index]; + if (p.index == piece_pos::we_have_index) return true; + auto const state = p.download_queue(); + if (state == piece_pos::piece_open) return false; + auto const i = find_dl_piece(state, block.piece_index); + TORRENT_ASSERT(i != m_downloads[state].end()); + + auto const info = blocks_for_piece(*i); + TORRENT_ASSERT(info[block.block_index].piece_index == block.piece_index); + return info[block.block_index].state == block_info::state_finished; + } + + piece_extent_t piece_picker::extent_for(piece_index_t const p) const + { + int const extent_size = max_piece_affinity_extent / m_blocks_per_piece; + return piece_extent_t{static_cast(p) / extent_size}; + } + + index_range piece_picker::extent_for(piece_extent_t const e) const + { + int const extent_size = max_piece_affinity_extent / m_blocks_per_piece; + int const begin = static_cast(e) * extent_size; + int const end = std::min(begin + extent_size, num_pieces()); + return { piece_index_t{begin}, piece_index_t{end}}; + } + + void piece_picker::record_downloading_piece(piece_index_t const p) + { + // if a single piece is large enough, don't bother with the affinity of + // adjecent pieces. + if (m_blocks_per_piece >= max_piece_affinity_extent) return; + + piece_extent_t const this_extent = extent_for(p); + + // if the extent is already in the list, nothing to do + if (std::find(m_recent_extents.begin() + , m_recent_extents.end(), this_extent) != m_recent_extents.end()) + return; + + download_priority_t const this_prio = piece_priority(p); + + // figure out if it's worth recording this downloading piece + // if we already have all blocks in this extent, there's no point in + // adding it + bool have_all = true; + + for (auto const piece : extent_for(this_extent)) + { + if (piece == p) continue; + + if (!m_piece_map[piece].have()) have_all = false; + + // if at least one piece in this extent has a different priority than + // the one we just started downloading, don't create an affinity for + // adjecent pieces. This probably means the pieces belong to different + // files, or that some other mechanism determining the priority should + // take precedence. + if (piece_priority(piece) != this_prio) return; + } + + // if we already have all the *other* pieces in this extent, there's no + // need to inflate their priorities + if (have_all) return; + + // TODO: should 5 be configurable? + if (m_recent_extents.size() < 5) + m_recent_extents.push_back(this_extent); + + // limit the number of extent affinities active at any given time to limit + // the cost of checking them. Also, don't replace them, commit to + // finishing them before starting another extent. This is analoguous to + // limiting the number of partial pieces. + } + + // options may be 0 or piece_picker::reverse + // returns false if the block could not be marked as downloading + bool piece_picker::mark_as_downloading(piece_block const block + , torrent_peer* peer, picker_options_t const options) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_downloading( {" + << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == nullptr || peer->in_use); + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.end_index()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); + TORRENT_ASSERT(!m_piece_map[block.piece_index].have()); + + piece_pos& p = m_piece_map[block.piece_index]; + if (p.download_queue() == piece_pos::piece_open) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + int const prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size()) + || m_dirty); + + p.state((options & reverse) + ? piece_pos::piece_downloading_reverse + : piece_pos::piece_downloading); + + if (prio >= 0 && !m_dirty) update(prio, p.index); + + // if the piece extent affinity is enabled, (maybe) record downloading a + // block from this piece to make other peers prefer adjecent pieces + // if reverse is set, don't encourage other peers to pick nearby + // pieces, as that's assumed to be low priority. + // if time critical mode is enabled, we're likely to either download + // adjacent pieces anyway, but more importantly, we don't want to + // create artificially higher priority for adjecent pieces if they + // aren't important or urgent + if (options & piece_extent_affinity) + record_downloading_piece(block.piece_index); + + auto const dp = add_download_piece(block.piece_index); + auto const binfo = mutable_blocks_for_piece(*dp); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(info.piece_index == block.piece_index); + if (info.state == block_info::state_finished) + return false; + + info.state = block_info::state_requested; + info.peer = peer; + info.num_peers = 1; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(info.peers.count(peer) == 0); + info.peers.insert(peer); +#endif + ++dp->requested; + // update_full may move the downloading piece to + // a different vector, so 'dp' may be invalid after + // this call + update_piece_state(dp); + } + else + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + auto i = find_dl_piece(p.download_queue(), block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + auto const binfo = mutable_blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(info.piece_index == block.piece_index); + if (info.state == block_info::state_writing + || info.state == block_info::state_finished) + { + return false; + } + + if ((options & reverse) && !p.reverse() && i->requested == 0) + { + // this piece isn't reverse, but there's no other peer + // downloading from it and we just requested a block from a + // reverse peer. Make it reverse + int prio = p.priority(this); + p.make_reverse(); + if (prio >= 0 && !m_dirty) update(prio, p.index); + } + + TORRENT_ASSERT(info.state == block_info::state_none + || (info.state == block_info::state_requested + && (info.num_peers > 0))); + info.peer = peer; + if (info.state != block_info::state_requested) + { + info.state = block_info::state_requested; + ++i->requested; + i = update_piece_state(i); + } + ++info.num_peers; + + // if we make a non-reverse request from a reversed piece, + // undo the reverse state + if (!(options & reverse) && p.reverse()) + { + int prio = p.priority(this); + // make it non-reverse + p.unreverse(); + if (prio >= 0 && !m_dirty) update(prio, p.index); + } + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(info.peers.count(peer) == 0); + info.peers.insert(peer); +#endif + } + return true; + } + + int piece_picker::num_peers(piece_block const block) const + { + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.end_index()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); + + piece_pos const& p = m_piece_map[block.piece_index]; + if (!p.downloading()) return 0; + + auto const i = find_dl_piece(p.download_queue(), block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + + auto const binfo = blocks_for_piece(*i); + block_info const& info = binfo[block.block_index]; + TORRENT_ASSERT(&info >= &m_block_info[0]); + TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT(info.piece_index == block.piece_index); + return info.num_peers; + } + + void piece_picker::get_availability(aux::vector& avail) const + { + TORRENT_ASSERT(m_seeds >= 0); + INVARIANT_CHECK; + + avail.resize(m_piece_map.size()); + auto j = avail.begin(); + for (auto i = m_piece_map.begin(), end(m_piece_map.end()); i != end; ++i, ++j) + *j = i->peer_count + m_seeds; + } + + int piece_picker::get_availability(piece_index_t const piece) const + { + return m_piece_map[piece].peer_count + m_seeds; + } + + bool piece_picker::mark_as_writing(piece_block const block, torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_writing( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == nullptr || static_cast(peer)->in_use); + + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.end_index()); + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); + // this is not valid for web peers + // TORRENT_ASSERT(peer != 0); + + piece_pos& p = m_piece_map[block.piece_index]; + if (p.downloading() == 0) + { + // if we already have this piece, just ignore this + if (have_piece(block.piece_index)) return false; + + int const prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size()) + || m_dirty); + p.state(piece_pos::piece_downloading); + // prio being -1 can happen if a block is requested before + // the piece priority was set to 0 + if (prio >= 0 && !m_dirty) update(prio, p.index); + + auto const dp = add_download_piece(block.piece_index); + auto const binfo = mutable_blocks_for_piece(*dp); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(&info >= &m_block_info[0]); + TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT(info.piece_index == block.piece_index); + + TORRENT_ASSERT(info.state == block_info::state_none); + if (info.state == block_info::state_finished) + return false; + + info.state = block_info::state_writing; + info.peer = peer; + info.num_peers = 0; +#if TORRENT_USE_ASSERTS + info.peers.clear(); +#endif + dp->writing = 1; + + update_piece_state(dp); + } + else + { + auto i = find_dl_piece(p.download_queue(), block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + auto const binfo = mutable_blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + + TORRENT_ASSERT(&info >= &m_block_info[0]); + TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT(info.piece_index == block.piece_index); + + info.peer = peer; + if (info.state == block_info::state_requested) --i->requested; + if (info.state == block_info::state_writing + || info.state == block_info::state_finished) + return false; + + ++i->writing; + info.state = block_info::state_writing; + TORRENT_ASSERT(info.piece_index == block.piece_index); + + // all other requests for this block should have been + // cancelled now + info.num_peers = 0; +#if TORRENT_USE_ASSERTS + info.peers.clear(); +#endif + + update_piece_state(i); + } + return true; + } + + // calling this function prevents this piece from being picked + // by the piece picker until the pieces is restored. This allow + // the disk thread to synchronize and flush any failed state + // (used for disk write failures and piece hash failures). + void piece_picker::lock_piece(piece_index_t const piece) + { + INVARIANT_CHECK; + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "lock_piece(" << piece << ")" << std::endl; +#endif + + auto const state = m_piece_map[piece].download_queue(); + if (state == piece_pos::piece_open) return; + auto const i = find_dl_piece(state, piece); + if (i == m_downloads[state].end()) return; + + TORRENT_ASSERT(i->passed_hash_check == false); + if (i->passed_hash_check) + { + // it's not clear why this would happen, + // but it seems reasonable to not break the + // accounting over it. + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + + // prevent this piece from being picked until it's restored + i->locked = true; + } + + // TODO: 2 it would be nice if this could be folded into lock_piece() + // the main distinction is that this also maintains the m_num_passed + // counter and the passed_hash_check member + // Is there ever a case where we call write filed without also locking + // the piece? Perhaps write_failed() should imply locking it. + void piece_picker::write_failed(piece_block const block) + { + INVARIANT_CHECK; + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "write_failed( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + auto const state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return; + auto i = find_dl_piece(state, block.piece_index); + if (i == m_downloads[state].end()) return; + + auto const binfo = mutable_blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(&info >= &m_block_info[0]); + TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT(info.piece_index == block.piece_index); + TORRENT_ASSERT(info.state == block_info::state_writing); + TORRENT_ASSERT(info.num_peers == 0); + + TORRENT_ASSERT(i->writing > 0); + TORRENT_ASSERT(info.state == block_info::state_writing); + + if (info.state == block_info::state_finished) return; + if (info.state == block_info::state_writing) --i->writing; + + info.peer = nullptr; + info.state = block_info::state_none; + if (i->passed_hash_check) + { + // the hash was good, but we failed to write + // some of the blocks to disk, which means we + // can't consider the piece complete + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + + // prevent this hash job from actually completing + // this piece, by setting the failure state. + // the piece is unlocked in the call to restore_piece() + i->locked = true; + + i = update_piece_state(i); + + if (i->finished + i->writing + i->requested == 0) + { + piece_pos& p = m_piece_map[block.piece_index]; + int const prev_priority = p.priority(this); + erase_download_piece(i); + int const new_priority = p.priority(this); + + if (m_dirty) return; + if (new_priority == prev_priority) return; + if (prev_priority == -1) add(block.piece_index); + else update(prev_priority, p.index); + } + } + + void piece_picker::mark_as_canceled(piece_block const block, torrent_peer* peer) + { +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_cancelled( {" + << block.piece_index << ", " << block.block_index + << "} )" << std::endl; +#endif + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + TORRENT_ASSERT(block.block_index >= 0); + piece_pos& p = m_piece_map[block.piece_index]; + + if (p.download_queue() == piece_pos::piece_open) return; + + auto i = find_dl_piece(p.download_queue(), block.piece_index); + + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + auto const binfo = mutable_blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + + if (info.state == block_info::state_finished) return; + + TORRENT_ASSERT(info.num_peers == 0); + info.peer = peer; + TORRENT_ASSERT(info.state == block_info::state_writing + || peer == nullptr); + if (info.state == block_info::state_writing) + { + --i->writing; + info.state = block_info::state_none; + // i may be invalid after this call + i = update_piece_state(i); + + if (i->finished + i->writing + i->requested == 0) + { + int const prev_priority = p.priority(this); + erase_download_piece(i); + int const new_priority = p.priority(this); + + if (m_dirty) return; + if (new_priority == prev_priority) return; + if (prev_priority == -1) add(block.piece_index); + else update(prev_priority, p.index); + } + } + else + { + TORRENT_ASSERT(info.state == block_info::state_none); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + void piece_picker::mark_as_finished(piece_block const block, torrent_peer* peer) + { +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_finished( {" + << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == nullptr || static_cast(peer)->in_use); + TORRENT_ASSERT(block.block_index >= 0); + + piece_pos& p = m_piece_map[block.piece_index]; + + if (p.download_queue() == piece_pos::piece_open) + { + // if we already have this piece, just ignore this + if (have_piece(block.piece_index)) return; + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + int const prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundaries.size()) + || m_dirty); + p.state(piece_pos::piece_downloading); + if (prio >= 0 && !m_dirty) update(prio, p.index); + + auto const dp = add_download_piece(block.piece_index); + auto const binfo = mutable_blocks_for_piece(*dp); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(&info >= &m_block_info[0]); + TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT(info.piece_index == block.piece_index); + if (info.state == block_info::state_finished) + return; + info.peer = peer; + TORRENT_ASSERT(info.state == block_info::state_none); + TORRENT_ASSERT(info.num_peers == 0); + ++dp->finished; + info.state = block_info::state_finished; + // dp may be invalid after this call + update_piece_state(dp); + } + else + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + auto i = find_dl_piece(p.download_queue(), block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.download_queue()].end()); + auto const binfo = mutable_blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(info.piece_index == block.piece_index); + + if (info.state == block_info::state_finished) return; + + TORRENT_ASSERT(info.num_peers == 0); + + // peers may have been disconnected in between mark_as_writing + // and mark_as_finished. When a peer disconnects, its m_peer_info + // pointer is set to nullptr. If so, preserve the previous peer + // pointer, instead of forgetting who we downloaded this block from + if (info.state != block_info::state_writing || peer != nullptr) + info.peer = peer; + + ++i->finished; + if (info.state == block_info::state_writing) + { + TORRENT_ASSERT(i->writing > 0); + --i->writing; + info.state = block_info::state_finished; + } + else + { + TORRENT_ASSERT(info.state == block_info::state_none); + info.state = block_info::state_finished; + } + + i = update_piece_state(i); + + if (i->finished < blocks_in_piece(i->index)) + return; + + if (i->passed_hash_check) + we_have(i->index); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + void piece_picker::mark_as_pad(piece_block block) + { + // if this is the first block we mark as a pad, allocate the bitfield + if (m_pad_blocks.empty()) + { + m_pad_blocks.resize(int(m_piece_map.size() * m_blocks_per_piece)); + } + + int const block_index = static_cast(block.piece_index) * m_blocks_per_piece + block.block_index; + TORRENT_ASSERT(block_index < m_pad_blocks.size()); + TORRENT_ASSERT(block_index >= 0); + TORRENT_ASSERT(m_pad_blocks.get_bit(block_index) == false); + + m_pad_blocks.set_bit(block_index); + ++m_num_pad_blocks; + TORRENT_ASSERT(m_pad_blocks.count() == m_num_pad_blocks); + + ++m_pads_in_piece[block.piece_index]; + + piece_pos& p = m_piece_map[block.piece_index]; + if (p.filtered()) + { + ++m_filtered_pad_blocks; + } + + // if we mark and entire piece as a pad file, we need to also + // consder that piece as "had" and increment some counters + int const blocks = blocks_in_piece(block.piece_index); + if (pad_blocks_in_piece(block.piece_index) == blocks) + { + // the entire piece is a pad file + we_have(block.piece_index); + } + } + + int piece_picker::pad_blocks_in_piece(piece_index_t const index) const + { + auto const it = m_pads_in_piece.find(index); + if (it == m_pads_in_piece.end()) return 0; + return it->second; + } + + void piece_picker::get_downloaders(std::vector& d + , piece_index_t const index) const + { + d.clear(); + auto const state = m_piece_map[index].download_queue(); + int const num_blocks = blocks_in_piece(index); + d.reserve(aux::numeric_cast(num_blocks)); + + if (state == piece_pos::piece_open) + { + for (int i = 0; i < num_blocks; ++i) d.push_back(nullptr); + return; + } + + auto const i = find_dl_piece(state, index); + TORRENT_ASSERT(i != m_downloads[state].end()); + auto const binfo = blocks_for_piece(*i); + for (int j = 0; j != num_blocks; ++j) + { + TORRENT_ASSERT(binfo[j].peer == nullptr + || binfo[j].peer->in_use); + d.push_back(binfo[j].peer); + } + } + + torrent_peer* piece_picker::get_downloader(piece_block const block) const + { + auto const state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return nullptr; + + auto const i = find_dl_piece(state, block.piece_index); + + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + auto const binfo = blocks_for_piece(*i); + TORRENT_ASSERT(binfo[block.block_index].piece_index == block.piece_index); + if (binfo[block.block_index].state == block_info::state_none) + return nullptr; + + torrent_peer* peer = binfo[block.block_index].peer; + TORRENT_ASSERT(peer == nullptr || static_cast(peer)->in_use); + return peer; + } + + // this is called when a request is rejected or when + // a peer disconnects. The piece might be in any state + void piece_picker::abort_download(piece_block const block, torrent_peer* peer) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "abort_download( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + TORRENT_ASSERT(peer == nullptr || peer->in_use); + + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + + auto const state = m_piece_map[block.piece_index].download_queue(); + if (state == piece_pos::piece_open) return; + + auto i = find_dl_piece(state, block.piece_index); + TORRENT_ASSERT(i != m_downloads[state].end()); + + auto const binfo = mutable_blocks_for_piece(*i); + block_info& info = binfo[block.block_index]; + TORRENT_ASSERT(info.peer == nullptr || info.peer->in_use); + TORRENT_ASSERT(info.piece_index == block.piece_index); + + TORRENT_ASSERT(info.state != block_info::state_none); + + if (info.state != block_info::state_requested) return; + + piece_pos const& p = m_piece_map[block.piece_index]; + int const prev_prio = p.priority(this); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(info.peers.count(peer)); + info.peers.erase(peer); +#endif + TORRENT_ASSERT(info.num_peers > 0); + if (info.num_peers > 0) --info.num_peers; + if (info.peer == peer) info.peer = nullptr; + TORRENT_ASSERT(info.peers.size() == info.num_peers); + + TORRENT_ASSERT(block.block_index < blocks_in_piece(block.piece_index)); + + // if there are other peers, leave the block requested + if (info.num_peers > 0) return; + + // clear the downloader of this block + info.peer = nullptr; + + // clear this block as being downloaded + info.state = block_info::state_none; + TORRENT_ASSERT(i->requested > 0); + --i->requested; + + // if there are no other blocks in this piece + // that's being downloaded, remove it from the list + if (i->requested + i->finished + i->writing == 0) + { + TORRENT_ASSERT(prev_prio < int(m_priority_boundaries.size()) + || m_dirty); + erase_download_piece(i); + int const prio = p.priority(this); + if (!m_dirty) + { + if (prev_prio == -1 && prio >= 0) add(block.piece_index); + else if (prev_prio >= 0) update(prev_prio, p.index); + } + return; + } + + i = update_piece_state(i); + } + + piece_count piece_picker::want() const + { + bool const want_last = piece_priority(piece_index_t(num_pieces() - 1)) != dont_download; + piece_count ret{ num_pieces() - m_num_filtered - m_num_have_filtered + , num_pad_blocks() - m_filtered_pad_blocks - m_have_filtered_pad_blocks + , want_last }; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_blocks > 0)); + TORRENT_ASSERT(!(ret.num_pieces == num_pieces() && ret.last_piece == false)); + return ret; + } + + piece_count piece_picker::have_want() const + { + bool const have_last = have_piece(piece_index_t(num_pieces() - 1)); + bool const want_last = piece_priority(piece_index_t(num_pieces() - 1)) != dont_download; + piece_count ret{ m_num_have - m_num_have_filtered + , m_have_pad_blocks - m_have_filtered_pad_blocks + , have_last && want_last }; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_blocks > 0)); + TORRENT_ASSERT(!(ret.num_pieces == num_pieces() && ret.last_piece == false)); + return ret; + } + + piece_count piece_picker::have() const + { + bool const have_last = have_piece(piece_index_t(num_pieces() - 1)); + piece_count ret{ m_num_have + , m_have_pad_blocks + , have_last }; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_blocks > 0)); + TORRENT_ASSERT(!(ret.num_pieces == num_pieces() && ret.last_piece == false)); + return ret; + } + + piece_count piece_picker::all_pieces() const + { + piece_count ret{ num_pieces() + , num_pad_blocks() + , true}; + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.last_piece == true)); + TORRENT_ASSERT(!(ret.num_pieces == 0 && ret.pad_blocks > 0)); + TORRENT_ASSERT(!(ret.num_pieces == num_pieces() && ret.last_piece == false)); + return ret; + } + +} diff --git a/src/platform_util.cpp b/src/platform_util.cpp new file mode 100644 index 0000000..8b5298d --- /dev/null +++ b/src/platform_util.cpp @@ -0,0 +1,139 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/platform_util.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if TORRENT_USE_RLIMIT + +#include + +// capture this here where warnings are disabled (the macro generates warnings) +const rlim_t rlimit_as = RLIMIT_AS; +const rlim_t rlimit_nofile = RLIMIT_NOFILE; +const rlim_t rlim_infinity = RLIM_INFINITY; + +#endif // TORRENT_USE_RLIMIT + +#ifdef TORRENT_BSD +#include +#include +#endif + +#if defined TORRENT_WINDOWS +#include "libtorrent/aux_/windows.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + + int max_open_files() + { +#if defined TORRENT_BUILD_SIMULATOR + return 256; +#elif TORRENT_USE_RLIMIT + + struct rlimit rl{}; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + { + if (rl.rlim_cur == rlim_infinity) + return std::numeric_limits::max(); + + return rl.rlim_cur <= static_cast(std::numeric_limits::max()) + ? static_cast(rl.rlim_cur) : std::numeric_limits::max(); + } + return 1024; +#else + // this seems like a reasonable limit for windows. + // http://blogs.msdn.com/b/oldnewthing/archive/2007/07/18/3926581.aspx + return 10000; +#endif + } + + std::int64_t total_physical_ram() + { +#if defined TORRENT_BUILD_SIMULATOR + return std::int64_t(4) * 1024 * 1024 * 1024; +#else + // figure out how much physical RAM there is in + // this machine. This is used for automatically + // sizing the disk cache size when it's set to + // automatic. + std::int64_t ret = 0; + +#ifdef TORRENT_BSD +#ifdef HW_MEMSIZE + int mib[2] = { CTL_HW, HW_MEMSIZE }; +#else + // not entirely sure this sysctl supports 64 + // bit return values, but it's probably better + // than not building + int mib[2] = { CTL_HW, HW_PHYSMEM }; +#endif + std::size_t len = sizeof(ret); + if (sysctl(mib, 2, &ret, &len, nullptr, 0) != 0) + ret = 0; +#elif defined TORRENT_WINDOWS + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&ms)) + ret = ms.ullTotalPhys; + else + ret = 0; +#elif defined TORRENT_LINUX + ret = sysconf(_SC_PHYS_PAGES); + ret *= sysconf(_SC_PAGESIZE); +#elif defined TORRENT_AMIGA + ret = AvailMem(MEMF_PUBLIC); +#endif + +#if TORRENT_USE_RLIMIT + if (ret > 0) + { + struct rlimit r{}; + if (getrlimit(rlimit_as, &r) == 0 && r.rlim_cur != rlim_infinity) + { + if (ret > std::int64_t(r.rlim_cur)) + ret = std::int64_t(r.rlim_cur); + } + } +#endif + return ret; +#endif // TORRENT_BUILD_SIMULATOR + } +} diff --git a/src/proxy_base.cpp b/src/proxy_base.cpp new file mode 100644 index 0000000..c3b37bf --- /dev/null +++ b/src/proxy_base.cpp @@ -0,0 +1,53 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/proxy_base.hpp" + +namespace libtorrent { + + proxy_base::proxy_base(io_service& io_service) + : m_sock(io_service) + , m_port(0) + , m_resolver(io_service) + {} + + proxy_base::~proxy_base() = default; + + bool proxy_base::handle_error(error_code const& e, handler_type const& h) + { + if (!e) return false; + h(e); + error_code ec; + close(ec); + return true; + } +} diff --git a/src/proxy_settings.cpp b/src/proxy_settings.cpp new file mode 100644 index 0000000..2af4af0 --- /dev/null +++ b/src/proxy_settings.cpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/proxy_settings.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +namespace libtorrent { namespace aux { + +namespace { + +template +void init(proxy_settings& p, Settings const& sett) +{ + p.hostname = sett.get_str(settings_pack::proxy_hostname); + p.username = sett.get_str(settings_pack::proxy_username); + p.password = sett.get_str(settings_pack::proxy_password); + p.type = settings_pack::proxy_type_t(sett.get_int(settings_pack::proxy_type)); + p.port = std::uint16_t(sett.get_int(settings_pack::proxy_port)); + p.proxy_hostnames = sett.get_bool(settings_pack::proxy_hostnames); + p.proxy_peer_connections = sett.get_bool( + settings_pack::proxy_peer_connections); + p.proxy_tracker_connections = sett.get_bool( + settings_pack::proxy_tracker_connections); +} + +} + +proxy_settings::proxy_settings() = default; + +proxy_settings::proxy_settings(settings_pack const& sett) +{ init(*this, sett); } + +proxy_settings::proxy_settings(aux::session_settings const& sett) +{ init(*this, sett); } + +} // namespace aux +} // namespace libtorrent diff --git a/src/puff.cpp b/src/puff.cpp new file mode 100644 index 0000000..d100416 --- /dev/null +++ b/src/puff.cpp @@ -0,0 +1,844 @@ +/* + * puff.c + * Copyright (C) 2002-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 2.3, 21 Jan 2013 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format. It is not written for speed but rather simplicity. As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications. For typical deflate + * data, zlib's inflate() is about four times as fast as puff(). zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack. The stack required + * is less than 2K bytes. This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits. puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to conserve memory. The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format. This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + * http://www.zlib.org/rfc-deflate.html + */ + +/* + * Change history: + * + * 1.0 10 Feb 2002 - First version + * 1.1 17 Feb 2002 - Clarifications of some comments and notes + * - Update puff() dest and source pointers on negative + * errors to facilitate debugging deflators + * - Remove longest from struct huffman -- not needed + * - Simplify offs[] index in construct() + * - Add input size and checking, using longjmp() to + * maintain easy readability + * - Use short data type for large arrays + * - Use pointers instead of long to specify source and + * destination sizes to avoid arbitrary 4 GB limits + * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), + * but leave simple version for readabilty + * - Make sure invalid distances detected if pointers + * are 16 bits + * - Fix fixed codes table error + * - Provide a scanning mode for determining size of + * uncompressed data + * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly] + * - Add a puff.h file for the interface + * - Add braces in puff() for else do [Gailly] + * - Use indexes instead of pointers for readability + * 1.4 31 Mar 2002 - Simplify construct() code set check + * - Fix some comments + * - Add FIXLCODES #define + * 1.5 6 Apr 2002 - Minor comment fixes + * 1.6 7 Aug 2002 - Minor format changes + * 1.7 3 Mar 2003 - Added test code for distribution + * - Added zlib-like license + * 1.8 9 Jan 2004 - Added some comments on no distance codes case + * 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland] + * - Catch missing end-of-block symbol error + * 2.0 25 Jul 2008 - Add #define to permit distance too far back + * - Add option in TEST code for puff to write the data + * - Add option in TEST code to skip input bytes + * - Allow TEST code to read from piped stdin + * 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers + * - Avoid unsigned comparisons for even happier compilers + * 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer] + * - Add const where appropriate [Oberhumer] + * - Split if's and ?'s for coverage testing + * - Break out test code to separate file + * - Move NIL to puff.h + * - Allow incomplete code only if single code length is 1 + * - Add full code coverage test to Makefile + * 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks + */ + +// this whole file is just preserved and warnings are suppressed +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include /* for nullptr */ +#include "libtorrent/puff.hpp" /* prototype for puff() */ + +#define local static /* for local function definitions */ + +/* + * Maximums for allocations and loops. It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15 /* maximum bits in a code */ +#define MAXLCODES 286 /* maximum number of literal/length codes */ +#define MAXDCODES 30 /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ +#define FIXLCODES 288 /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { + /* output state */ + unsigned char *out; /* output buffer */ + unsigned long outlen; /* available space at out */ + unsigned long outcnt; /* bytes written to out so far */ + + /* input state */ + const unsigned char *in; /* input buffer */ + unsigned long inlen; /* available input at in */ + unsigned long incnt; /* bytes read so far */ + int bitbuf; /* bit buffer */ + int bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + std::jmp_buf env; +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int bits(struct state *s, int need) +{ + long val; /* bit accumulator (can use up to 20 bits) */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->incnt == s->inlen) + std::longjmp(s->env, 1); /* out of input */ + val |= long(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = int(val >> need); + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return int(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + * stored bytes are byte-aligned for fast copying. Therefore any leftover + * bits in the byte that has the last bit of the type, as many as seven, are + * discarded. The value of the discarded bits are not defined and should not + * be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + * checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length. This is sometimes used to byte-align + * subsets of the compressed data for random access or partial recovery. + */ +local int stored(struct state *s) +{ + unsigned len; /* length of stored block */ + + /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ + s->bitbuf = 0; + s->bitcnt = 0; + + /* get length and check against its one's complement */ + if (s->incnt + 4 > s->inlen) + return 2; /* not enough input */ + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if (s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff)) + return -2; /* didn't match complement! */ + + /* copy len bytes from in to out */ + if (s->incnt + len > s->inlen) + return 2; /* not enough input */ + if (s->out != nullptr) { + if (s->outcnt + len > s->outlen) + return 1; /* not enough output space */ + while (len--) + s->out[s->outcnt++] = s->in[s->incnt++]; + } + else { /* just scanning */ + s->outcnt += len; + s->incnt += len; + } + + /* done with a valid stored block */ + return 0; +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + short *count; /* number of symbols of each length */ + short *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -10 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. A table-based decoding + * scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros. Subsequent codes of + * the same length are simply integer increments of the previous code. When + * moving up a length, a zero bit is appended to the code. For a complete + * code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + * in the deflate format. See the format notes for fixed() and dynamic(). + */ +#ifdef SLOW +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + + code = first = index = 0; + for (len = 1; len <= MAXBITS; len++) { + code |= bits(s, 1); /* get next bit */ + count = h->count[len]; + if (code - count < first) /* if length len, return symbol */ + return h->symbol[index + (code - first)]; + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + } + return -10; /* ran out of codes */ +} + +/* + * A faster version of decode() for real applications of this code. It's not + * as readable, but it makes puff() twice as fast. And it only makes the code + * a few percent larger. + */ +#else /* !SLOW */ +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + int bitbuf; /* bits from stream */ + int left; /* bits left in next or left to process */ + short *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + for (;;) { + while (left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if (code - count < first) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) + break; + if (s->incnt == s->inlen) + std::longjmp(s->env, 1); /* out of input */ + bitbuf = s->in[s->incnt++]; + if (left > 8) + left = 8; + } + return -10; /* ran out of codes */ +} +#endif /* SLOW */ + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code. So n - h->count[0] is the number of + * codes. This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + * codes and any code with a single symbol which in deflate is coded as one + * bit instead of zero bits. See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + * the code bits definition. + */ +local int construct(struct huffman *h, const short *length, int n) +{ + int symbol; /* current symbol when stepping through length[] */ + int len; /* current length when stepping through h->count[] */ + int left; /* number of possible codes left of current length */ + short offs[MAXBITS+1]; /* offsets in symbol table for each length */ + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) + return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + * description if dynamic is a combination of literals and length/distance + * pairs terminated by and end-of-block code. Literals are simply Huffman + * coded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + * code of up to 286 symbols. They are 256 literals (0..255), 29 length + * symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + * to represent all of those. Lengths 3..10 and 258 are in fact represented + * by just a length symbol. Lengths 11..257 are represented as a symbol and + * some number of extra bits that are added as an integer to the base length + * of the length symbol. The number of extra bits is determined by the base + * length symbol. These are in the static arrays below, lens[] for the base + * lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + * often in highly redundant files. Note that 258 can also be coded as the + * base value 227 plus the maximum extra value of 31. While a good deflate + * should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + * followed a distance code. There are up to 30 distance symbols. Again + * there are many more possible distances (1..32768), so extra bits are added + * to a base value represented by the symbol. The distances 1..4 get their + * own symbol, but the rest require extra bits. The base distances and + * corresponding number of extra bits are below in the static arrays dist[] + * and dext[]. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 258 + * simply copies the last byte 258 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. You should not use memcpy() since its behavior is not + * defined for overlapped arrays. You should not use memmove() or bcopy() + * since though their behavior -is- defined for overlapping arrays, it is + * defined to do the wrong thing in this case. + */ +local int codes(struct state *s, + const struct huffman *lencode, + const struct huffman *distcode) +{ + int symbol; /* decoded symbol */ + int len; /* length for copy */ + unsigned dist; /* distance for copy */ + static const short lens[29] = { /* Size base for length codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + static const short lext[29] = { /* Extra bits for length codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const short dists[30] = { /* Offset base for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; + static const short dext[30] = { /* Extra bits for distance codes 0..29 */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + /* decode literals and length/distance pairs */ + do { + symbol = decode(s, lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 256) { /* literal: symbol is the byte */ + /* write out the literal */ + if (s->out != nullptr) { + if (s->outcnt == s->outlen) + return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } + else if (symbol > 256) { /* length */ + /* get and compute length */ + symbol -= 257; + if (symbol >= 29) + return -10; /* invalid fixed code */ + len = lens[symbol] + bits(s, lext[symbol]); + + /* get and check distance */ + symbol = decode(s, distcode); + if (symbol < 0) + return symbol; /* invalid symbol */ + dist = dists[symbol] + bits(s, dext[symbol]); +#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (dist > s->outcnt) + return -11; /* distance too far back */ +#endif + + /* copy length bytes from distance bytes back */ + if (s->out != nullptr) { + if (s->outcnt + len > s->outlen) + return 1; + while (len--) { + s->out[s->outcnt] = +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + dist > s->outcnt ? + 0 : +#endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } + else + s->outcnt += len; + } + } while (symbol != 256); /* end of block symbol */ + + /* done with a valid fixed or dynamic block */ + return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + * which the size of the code descriptions in a dynamic block exceeds the + * benefit of custom codes for that block. For fixed codes, no bits are + * spent on code descriptions. Instead the code lengths for literal/length + * codes and distance codes are fixed. The specific lengths for each symbol + * can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + * and should result in an error if received. This cannot be implemented + * simply as an incomplete code since those two symbols are in the "middle" + * of the code. They are eight bits long and the longest literal/length\ + * code is nine bits. Therefore the code must be constructed with those + * symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + * in an error if received. Since all of the distance codes are the same + * length, this can be implemented as an incomplete code. Then the invalid + * codes are detected while decoding. + */ +local int fixed(struct state *s) +{ + static int virgin = 1; + static short lencnt[MAXBITS+1], lensym[FIXLCODES]; + static short distcnt[MAXBITS+1], distsym[MAXDCODES]; + static struct huffman lencode, distcode; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + int symbol; + short lengths[FIXLCODES]; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* literal/length table */ + for (symbol = 0; symbol < 144; symbol++) + lengths[symbol] = 8; + for (; symbol < 256; symbol++) + lengths[symbol] = 9; + for (; symbol < 280; symbol++) + lengths[symbol] = 7; + for (; symbol < FIXLCODES; symbol++) + lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + /* distance table */ + for (symbol = 0; symbol < MAXDCODES; symbol++) + lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + /* do this just once */ + virgin = 0; + } + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + * distance codes for that block. New dynamic blocks allow the compressor to + * rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + * the actual bits of the codes are generated in an unambiguous way simply + * from the number of bits in each code. Therefore the code descriptions + * are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + * provided for each of the literal/length symbols, and for each of the + * distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as + * as the code length. This does not mean a zero-length code, but rather + * that no code should be created for this symbol. There is no way in the + * deflate format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + * any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + * interesting consequence. Normally if only one symbol is used for a given + * code, then in fact that code could be represented with zero bits. However + * in deflate, that code has to be at least one bit. So for example, if + * only a single distance base symbol appears in a block, then it will be + * represented by a single code of length one, in particular one 0 bit. This + * is an incomplete code, since if a 1 bit is received, it has no meaning, + * and should result in an error. So incomplete distance codes of one symbol + * should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + * must be the end-of-block code, since every dynamic block has one. This + * is not the most efficient way to create an empty block (an empty fixed + * block is fewer bits), but it is allowed by the format. So incomplete + * literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + * codes. This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + * are themselves compressed using Huffman codes and run-length encoding. In + * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + * that length, and the symbols 16, 17, and 18 are run-length instructions. + * Each of 16, 17, and 18 are follwed by extra bits to define the length of + * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 + * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols + * are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + * described first. This is simply a sequence of up to 19 three-bit values + * representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + * the number of literal/length code lengths, the number of distance code + * lengths, and the number of code length code lengths (ok, you come up with + * a better name!) in the code descriptions. For the literal/length and + * distance codes, lengths after those provided are considered zero, i.e. no + * code. The code length code lengths are received in a permuted order (see + * the order[] array below) to make a short code length code length list more + * likely. As it turns out, very short and very long codes are less likely + * to be seen in a dynamic code description, hence what may appear initially + * to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + * lengths (ndist), then they are treated as one long list of nlen + ndist + * code lengths. Therefore run-length coding can and often does cross the + * boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + * three counts for the number of code lengths for the literal/length codes, + * the distance codes, and the code length codes. This is followed by the + * code length code lengths, three bits each. This is used to construct the + * code length code which is used to read the remainder of the lengths. Then + * the literal/length code lengths and distance lengths are read as a single + * set of lengths using the code length codes. Codes are constructed from + * the resulting two sets of lengths, and then finally you can start + * decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + * block is around 80 bytes. + */ +local int dynamic(struct state *s) +{ + int nlen, ndist, ncode; /* number of lengths in descriptor */ + int index; /* index of lengths[] */ + int err; /* construct() return value */ + short lengths[MAXCODES]; /* descriptor code lengths */ + short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ + short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ + struct huffman lencode, distcode; /* length and distance codes */ + static const short order[19] = /* permutation of code length codes */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* get number of lengths in each table, check lengths */ + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if (nlen > MAXLCODES || ndist > MAXDCODES) + return -3; /* bad counts */ + + /* read code length code lengths (really), missing lengths are zero */ + for (index = 0; index < ncode; index++) + lengths[order[index]] = bits(s, 3); + for (; index < 19; index++) + lengths[order[index]] = 0; + + /* build huffman table for code lengths codes (use lencode temporarily) */ + err = construct(&lencode, lengths, 19); + if (err != 0) /* require complete code set here */ + return -4; + + /* read length/literal and distance code length tables */ + index = 0; + while (index < nlen + ndist) { + int symbol; /* decoded value */ + + symbol = decode(s, &lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + int len = 0; /* last length to repeat */ + /* assume repeating zeros */ + if (symbol == 16) { /* repeat last length 3..6 times */ + if (index == 0) + return -5; /* no last length! */ + len = lengths[index - 1]; /* last length */ + symbol = 3 + bits(s, 2); + } + else if (symbol == 17) /* repeat zero 3..10 times */ + symbol = 3 + bits(s, 3); + else /* == 18, repeat zero 11..138 times */ + symbol = 11 + bits(s, 7); + if (index + symbol > nlen + ndist) + return -6; /* too many lengths! */ + while (symbol--) /* repeat last or zero symbol times */ + lengths[index++] = len; + } + } + + /* check for end-of-block code -- there better be one! */ + if (lengths[256] == 0) + return -9; + + /* build huffman table for literal/length codes */ + err = construct(&lencode, lengths, nlen); + if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1])) + return -7; /* incomplete code ok only for single length 1 code */ + + /* build huffman table for distance codes */ + err = construct(&distcode, lengths + nlen, ndist); + if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1])) + return -8; /* incomplete code ok only for single length 1 code */ + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest. On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero. If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space. In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written. For this dest must be (unsigned char *)0. In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + * 2: available inflate data did not terminate + * 1: output space exhausted before completing inflate + * 0: successful inflate + * -1: invalid block type (type == 3) + * -2: stored block length did not match one's complement + * -3: dynamic block code description: too many length or distance codes + * -4: dynamic block code description: code lengths codes incomplete + * -5: dynamic block code description: repeat lengths with no first length + * -6: dynamic block code description: repeat more than specified lengths + * -7: dynamic block code description: invalid literal/length code lengths + * -8: dynamic block code description: invalid distance code lengths + * -9: dynamic block code description: missing end-of-block code + * -10: invalid literal/length or distance code in fixed or dynamic block + * -11: distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + * whether or not it is the last block. Then the block is decoded and the + * process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + * block (if it was a fixed or dynamic block) are undefined and have no + * expected values to check. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen) /* amount of input available */ +{ + struct state s; /* input/output state */ + int last, type; /* block information */ + int err; /* return value */ + + /* initialize output state */ + s.out = dest; + s.outlen = *destlen; /* ignored if dest is NIL */ + s.outcnt = 0; + + /* initialize input state */ + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp() */ + err = 2; /* then skip do-loop, return error */ + else { + /* process blocks until last block or error */ + do { + last = bits(&s, 1); /* one if last block */ + type = bits(&s, 2); /* block type 0..3 */ + err = type == 0 ? + stored(&s) : + (type == 1 ? + fixed(&s) : + (type == 2 ? + dynamic(&s) : + -1)); /* type == 3, invalid */ + if (err != 0) + break; /* return with error */ + } while (!last); + } + + /* update the lengths and return */ + if (err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + return err; +} diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 0000000..3943aa8 --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2011-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/openssl.hpp" +#include "libtorrent/aux_/throw.hpp" + +#if defined BOOST_NO_CXX11_THREAD_LOCAL +#include +#endif + +#if TORRENT_BROKEN_RANDOM_DEVICE +#include "libtorrent/time.hpp" +#include +#endif + +#if TORRENT_USE_CRYPTOAPI +#include "libtorrent/aux_/win_crypto_provider.hpp" + +#elif defined TORRENT_USE_LIBCRYPTO + +#include "libtorrent/aux_/disable_warnings_push.hpp" +extern "C" { +#include +#include +} +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#endif + +#if TORRENT_USE_DEV_RANDOM +#include "libtorrent/aux_/dev_random.hpp" +#endif + +#ifdef BOOST_NO_CXX11_THREAD_LOCAL +namespace { + // if the random number generator can't be thread local, just protect it with + // a mutex. Not ideal, but hopefully not too many people are affected by old + // systems + std::mutex rng_mutex; +} +#endif + +namespace libtorrent { +namespace aux { + + std::mt19937& random_engine() + { +#ifdef TORRENT_BUILD_SIMULATOR + // make sure random numbers are deterministic. Seed with a fixed number + static std::mt19937 rng(0x82daf973); +#else + +#if TORRENT_BROKEN_RANDOM_DEVICE + struct { + std::uint32_t operator()() const + { + static std::atomic seed{static_cast(duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count())}; + return seed++; + } + } dev; +#else + static std::random_device dev; +#endif +#ifdef BOOST_NO_CXX11_THREAD_LOCAL + static std::mt19937 rng(dev()); +#else + thread_local static std::mt19937 rng(dev()); +#endif +#endif + return rng; + } + + void random_bytes(span buffer) + { +#ifdef TORRENT_BUILD_SIMULATOR + // simulator + + for (auto& b : buffer) b = char(random(0xff)); + +#elif TORRENT_USE_CRYPTOAPI + // windows + + aux::crypt_gen_random(buffer); + +#elif TORRENT_USE_DEV_RANDOM + // /dev/random + + static dev_random dev; + dev.read(buffer); + +#elif defined TORRENT_USE_LIBCRYPTO + // openssl + + int r = RAND_bytes(reinterpret_cast(buffer.data()) + , int(buffer.size())); + if (r != 1) aux::throw_ex(errors::no_entropy); +#else + // fallback + + std::generate(buffer.begin(), buffer.end(), [] { return char(random(0xff)); }); +#endif + } + } + + std::uint32_t random(std::uint32_t const max) + { +#ifdef BOOST_NO_CXX11_THREAD_LOCAL + std::lock_guard l(rng_mutex); +#endif + return std::uniform_int_distribution(0, max)(aux::random_engine()); + } +} diff --git a/src/read_resume_data.cpp b/src/read_resume_data.cpp new file mode 100644 index 0000000..7f6a625 --- /dev/null +++ b/src/read_resume_data.cpp @@ -0,0 +1,396 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/bdecode.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/socket_io.hpp" // for read_*_endpoint() +#include "libtorrent/hasher.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/download_priority.hpp" // for default_priority + +namespace libtorrent { + +namespace { + + void apply_flag(torrent_flags_t& current_flags + , bdecode_node const& n + , char const* name + , torrent_flags_t const flag) + { + if (n.dict_find_int_value(name, 0) == 0) + { + current_flags &= ~flag; + } + else + { + current_flags |= flag; + } + } + +} // anonyous namespace + + add_torrent_params read_resume_data(bdecode_node const& rd, error_code& ec) + { + add_torrent_params ret; + if (rd.type() != bdecode_node::dict_t) + { + ec = errors::not_a_dictionary; + return ret; + } + + if (bdecode_node const alloc = rd.dict_find_string("allocation")) + { + ret.storage_mode = (alloc.string_value() == "allocate" + || alloc.string_value() == "full") + ? storage_mode_allocate : storage_mode_sparse; + } + + if (rd.dict_find_string_value("file-format") + != "libtorrent resume file") + { + ec = errors::invalid_file_tag; + return ret; + } + + auto info_hash = rd.dict_find_string_value("info-hash"); + if (info_hash.size() != static_cast(sha1_hash::size())) + { + ec = errors::missing_info_hash; + return ret; + } + + ret.name = rd.dict_find_string_value("name").to_string(); + + ret.info_hash.assign(info_hash.data()); + + bdecode_node const info = rd.dict_find_dict("info"); + if (info) + { + // verify the info-hash of the metadata stored in the resume file matches + // the torrent we're loading + sha1_hash const resume_ih = hasher(info.data_section()).final(); + + // if url is set, the info_hash is not actually the info-hash of the + // torrent, but the hash of the URL, until we have the full torrent + // only require the info-hash to match if we actually passed in one + if (resume_ih == ret.info_hash) + { + ret.ti = std::make_shared(resume_ih); + + error_code err; + if (!ret.ti->parse_info_section(info, err)) + { + ec = err; + } + else + { + // time_t might be 32 bit if we're unlucky, but there isn't + // much to do about it + ret.ti->internal_set_creation_date(static_cast( + rd.dict_find_int_value("creation date", 0))); + ret.ti->internal_set_creator(rd.dict_find_string_value("created by", "")); + ret.ti->internal_set_comment(rd.dict_find_string_value("comment", "")); + } + } + } + + ret.total_uploaded = rd.dict_find_int_value("total_uploaded"); + ret.total_downloaded = rd.dict_find_int_value("total_downloaded"); + + ret.active_time = int(rd.dict_find_int_value("active_time")); + ret.finished_time = int(rd.dict_find_int_value("finished_time")); + ret.seeding_time = int(rd.dict_find_int_value("seeding_time")); + + ret.last_seen_complete = std::time_t(rd.dict_find_int_value("last_seen_complete")); + + ret.last_download = std::time_t(rd.dict_find_int_value("last_download", 0)); + ret.last_upload = std::time_t(rd.dict_find_int_value("last_upload", 0)); + + // scrape data cache + ret.num_complete = int(rd.dict_find_int_value("num_complete", -1)); + ret.num_incomplete = int(rd.dict_find_int_value("num_incomplete", -1)); + ret.num_downloaded = int(rd.dict_find_int_value("num_downloaded", -1)); + + // torrent settings + ret.max_uploads = int(rd.dict_find_int_value("max_uploads", -1)); + ret.max_connections = int(rd.dict_find_int_value("max_connections", -1)); + ret.upload_limit = int(rd.dict_find_int_value("upload_rate_limit", -1)); + ret.download_limit = int(rd.dict_find_int_value("download_rate_limit", -1)); + + // torrent flags + apply_flag(ret.flags, rd, "seed_mode", torrent_flags::seed_mode); + apply_flag(ret.flags, rd, "upload_mode", torrent_flags::upload_mode); +#ifndef TORRENT_DISABLE_SHARE_MODE + apply_flag(ret.flags, rd, "share_mode", torrent_flags::share_mode); +#endif + apply_flag(ret.flags, rd, "apply_ip_filter", torrent_flags::apply_ip_filter); + apply_flag(ret.flags, rd, "paused", torrent_flags::paused); + apply_flag(ret.flags, rd, "auto_managed", torrent_flags::auto_managed); +#ifndef TORRENT_DISABLE_SUPERSEEDING + apply_flag(ret.flags, rd, "super_seeding", torrent_flags::super_seeding); +#endif + apply_flag(ret.flags, rd, "sequential_download", torrent_flags::sequential_download); + apply_flag(ret.flags, rd, "stop_when_ready", torrent_flags::stop_when_ready); + apply_flag(ret.flags, rd, "disable_dht", torrent_flags::disable_dht); + apply_flag(ret.flags, rd, "disable_lsd", torrent_flags::disable_lsd); + apply_flag(ret.flags, rd, "disable_pex", torrent_flags::disable_pex); + + ret.save_path = rd.dict_find_string_value("save_path").to_string(); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + ret.url = rd.dict_find_string_value("url").to_string(); + ret.uuid = rd.dict_find_string_value("uuid").to_string(); +#endif + + bdecode_node const mapped_files = rd.dict_find_list("mapped_files"); + if (mapped_files) + { + for (int i = 0; i < mapped_files.list_size(); ++i) + { + auto new_filename = mapped_files.list_string_value_at(i); + if (new_filename.empty()) continue; + ret.renamed_files[file_index_t(i)] = new_filename.to_string(); + } + } + + ret.added_time = std::time_t(rd.dict_find_int_value("added_time", 0)); + ret.completed_time = std::time_t(rd.dict_find_int_value("completed_time", 0)); + + // load file priorities except if the add_torrent_param file was set to + // override resume data + bdecode_node const file_priority = rd.dict_find_list("file_priority"); + if (file_priority) + { + int const num_files = file_priority.list_size(); + ret.file_priorities.resize(aux::numeric_cast(num_files) + , default_priority); + for (int i = 0; i < num_files; ++i) + { + std::size_t const idx = std::size_t(i); + ret.file_priorities[idx] = aux::clamp( + download_priority_t(static_cast( + file_priority.list_int_value_at(i + , static_cast(default_priority)))) + , dont_download, top_priority); + // this is suspicious, leave seed mode + if (ret.file_priorities[idx] == dont_download) + { + ret.flags &= ~torrent_flags::seed_mode; + } + } + } + + bdecode_node const trackers = rd.dict_find_list("trackers"); + if (trackers) + { + // it's possible to delete the trackers from a torrent and then save + // resume data with an empty trackers list. Since we found a trackers + // list here, these should replace whatever we find in the .torrent + // file. + ret.flags |= torrent_flags::override_trackers; + + int tier = 0; + for (int i = 0; i < trackers.list_size(); ++i) + { + bdecode_node const tier_list = trackers.list_at(i); + if (!tier_list || tier_list.type() != bdecode_node::list_t) + continue; + + for (int j = 0; j < tier_list.list_size(); ++j) + { + ret.trackers.push_back(tier_list.list_string_value_at(j).to_string()); + ret.tracker_tiers.push_back(tier); + } + ++tier; + } + } + + // if merge resume http seeds is not set, we need to clear whatever web + // seeds we loaded from the .torrent file, because we want whatever's in + // the resume file to take precedence. If there aren't even any fields in + // the resume data though, keep the ones from the torrent + bdecode_node const url_list = rd.dict_find_list("url-list"); + bdecode_node const httpseeds = rd.dict_find_list("httpseeds"); + if (url_list || httpseeds) + { + // since we found http seeds in the resume data, they should replace + // whatever web seeds are specified in the .torrent, by default + ret.flags |= torrent_flags::override_web_seeds; + } + + if (url_list) + { + for (int i = 0; i < url_list.list_size(); ++i) + { + auto url = url_list.list_string_value_at(i); + if (url.empty()) continue; + ret.url_seeds.push_back(url.to_string()); + } + } + + if (httpseeds) + { + for (int i = 0; i < httpseeds.list_size(); ++i) + { + auto url = httpseeds.list_string_value_at(i); + if (url.empty()) continue; + ret.http_seeds.push_back(url.to_string()); + } + } + + bdecode_node const mt = rd.dict_find_string("merkle tree"); + if (mt && mt.string_length() >= int(sha1_hash::size())) + { + ret.merkle_tree.resize(aux::numeric_cast(mt.string_length()) / sha1_hash::size()); + std::memcpy(ret.merkle_tree.data(), mt.string_ptr() + , ret.merkle_tree.size() * sha1_hash::size()); + } + + // some sanity checking. Maybe we shouldn't be in seed mode anymore + if (bdecode_node const pieces = rd.dict_find_string("pieces")) + { + char const* pieces_str = pieces.string_ptr(); + int const pieces_len = pieces.string_length(); + ret.have_pieces.resize(pieces_len); + ret.verified_pieces.resize(pieces_len); + for (piece_index_t i(0); i < ret.verified_pieces.end_index(); ++i) + { + // being in seed mode and missing a piece is not compatible. + // Leave seed mode if that happens + if (pieces_str[static_cast(i)] & 1) ret.have_pieces.set_bit(i); + else ret.have_pieces.clear_bit(i); + + if (pieces_str[static_cast(i)] & 2) ret.verified_pieces.set_bit(i); + else ret.verified_pieces.clear_bit(i); + } + } + + if (bdecode_node const piece_priority = rd.dict_find_string("piece_priority")) + { + char const* prio_str = piece_priority.string_ptr(); + ret.piece_priorities.resize(aux::numeric_cast(piece_priority.string_length())); + for (std::size_t i = 0; i < ret.piece_priorities.size(); ++i) + { + ret.piece_priorities[i] = download_priority_t(aux::clamp( + static_cast(prio_str[i]) + , static_cast(dont_download) + , static_cast(top_priority))); + } + } + + int const v6_size = 18; + int const v4_size = 6; + using namespace libtorrent::detail; // for read_*_endpoint() + if (bdecode_node const peers_entry = rd.dict_find_string("peers")) + { + char const* ptr = peers_entry.string_ptr(); + for (int i = v4_size - 1; i < peers_entry.string_length(); i += v4_size) + ret.peers.push_back(read_v4_endpoint(ptr)); + } + + if (bdecode_node const peers_entry = rd.dict_find_string("peers6")) + { + char const* ptr = peers_entry.string_ptr(); + for (int i = v6_size - 1; i < peers_entry.string_length(); i += v6_size) + ret.peers.push_back(read_v6_endpoint(ptr)); + } + + if (bdecode_node const peers_entry = rd.dict_find_string("banned_peers")) + { + char const* ptr = peers_entry.string_ptr(); + for (int i = v4_size; i < peers_entry.string_length(); i += v4_size) + ret.banned_peers.push_back(read_v4_endpoint(ptr)); + } + + if (bdecode_node const peers_entry = rd.dict_find_string("banned_peers6")) + { + char const* ptr = peers_entry.string_ptr(); + for (int i = v6_size - 1; i < peers_entry.string_length(); i += v6_size) + ret.banned_peers.push_back(read_v6_endpoint(ptr)); + } + + // parse unfinished pieces + if (bdecode_node const unfinished_entry = rd.dict_find_list("unfinished")) + { + for (int i = 0; i < unfinished_entry.list_size(); ++i) + { + bdecode_node const e = unfinished_entry.list_at(i); + if (e.type() != bdecode_node::dict_t) continue; + piece_index_t const piece = piece_index_t(int(e.dict_find_int_value("piece", -1))); + if (piece < piece_index_t(0)) continue; + + bdecode_node const bitmask = e.dict_find_string("bitmask"); + if (!bitmask || bitmask.string_length() == 0) continue; + ret.unfinished_pieces[piece].assign( + bitmask.string_ptr(), bitmask.string_length() * CHAR_BIT); + } + } + + // we're loading this torrent from resume data. There's no need to + // re-save the resume data immediately. + ret.flags &= ~torrent_flags::need_save_resume; + + return ret; + } + + add_torrent_params read_resume_data(span buffer, error_code& ec) + { + bdecode_node rd = bdecode(buffer, ec); + if (ec) return add_torrent_params(); + + return read_resume_data(rd, ec); + } + + add_torrent_params read_resume_data(bdecode_node const& rd) + { + error_code ec; + auto ret = read_resume_data(rd, ec); + if (ec) throw system_error(ec); + return ret; + } + + add_torrent_params read_resume_data(span buffer) + { + error_code ec; + bdecode_node rd = bdecode(buffer, ec); + if (ec) throw system_error(ec); + + auto ret = read_resume_data(rd, ec); + if (ec) throw system_error(ec); + return ret; + } +} diff --git a/src/receive_buffer.cpp b/src/receive_buffer.cpp new file mode 100644 index 0000000..fe7d588 --- /dev/null +++ b/src/receive_buffer.cpp @@ -0,0 +1,336 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/receive_buffer.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/span.hpp" + +namespace libtorrent { + +int receive_buffer::max_receive() const +{ + return int(m_recv_buffer.size()) - m_recv_end; +} + +span receive_buffer::reserve(int const size) +{ + INVARIANT_CHECK; + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(m_recv_pos >= 0); + + // normalize() must be called before receiving more data + TORRENT_ASSERT(m_recv_start == 0); + + if (int(m_recv_buffer.size()) < m_recv_end + size) + { + int const new_size = std::max(m_recv_end + size, m_packet_size); + buffer new_buffer(new_size, {m_recv_buffer.data(), m_recv_end}); + m_recv_buffer = std::move(new_buffer); + + // since we just increased the size of the buffer, reset the watermark to + // start at our new size (avoid flapping the buffer size) + m_watermark = {}; + } + + return span(m_recv_buffer).subspan(m_recv_end, size); +} + +void receive_buffer::grow(int const limit) +{ + INVARIANT_CHECK; + int const current_size = int(m_recv_buffer.size()); + TORRENT_ASSERT(current_size < std::numeric_limits::max() / 3); + + // first grow to one piece message, then grow by 50% each time + int const new_size = (current_size < m_packet_size) + ? m_packet_size : std::min(current_size * 3 / 2, limit); + + // re-allocate the buffer and copy over the part of it that's used + buffer new_buffer(new_size, {m_recv_buffer.data(), m_recv_end}); + m_recv_buffer = std::move(new_buffer); + + // since we just increased the size of the buffer, reset the watermark to + // start at our new size (avoid flapping the buffer size) + m_watermark = {}; +} + +int receive_buffer::advance_pos(int const bytes) +{ + INVARIANT_CHECK; + int const limit = m_packet_size > m_recv_pos ? m_packet_size - m_recv_pos : m_packet_size; + int const sub_transferred = std::min(bytes, limit); + m_recv_pos += sub_transferred; + return sub_transferred; +} + +// size = the packet size to remove from the receive buffer +// packet_size = the next packet size to receive in the buffer +// offset = the offset into the receive buffer where to remove `size` bytes +void receive_buffer::cut(int const size, int const packet_size, int const offset) +{ + INVARIANT_CHECK; + TORRENT_ASSERT(packet_size > 0); + TORRENT_ASSERT(int(m_recv_buffer.size()) >= size); + TORRENT_ASSERT(int(m_recv_buffer.size()) >= m_recv_pos); + TORRENT_ASSERT(m_recv_pos >= size + offset); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(int(m_recv_buffer.size()) >= m_recv_end); + TORRENT_ASSERT(m_recv_start <= m_recv_end); + TORRENT_ASSERT(size >= 0); + + if (offset > 0) + { + TORRENT_ASSERT(m_recv_start - size <= m_recv_end); + + if (size > 0) + { + std::memmove(&m_recv_buffer[0] + m_recv_start + offset + , &m_recv_buffer[0] + m_recv_start + offset + size + , aux::numeric_cast(m_recv_end - m_recv_start - size - offset)); + } + + m_recv_pos -= size; + m_recv_end -= size; + +#if TORRENT_USE_ASSERTS + std::fill(m_recv_buffer.begin() + m_recv_end, m_recv_buffer.end(), std::uint8_t{0xcc}); +#endif + } + else + { + TORRENT_ASSERT(m_recv_start + size <= m_recv_end); + m_recv_start += size; + m_recv_pos -= size; + } + + m_packet_size = packet_size; +} + +span receive_buffer::get() const +{ + if (m_recv_buffer.empty()) + { + TORRENT_ASSERT(m_recv_pos == 0); + return {}; + } + + TORRENT_ASSERT(m_recv_start + m_recv_pos <= int(m_recv_buffer.size())); + return span(m_recv_buffer).subspan(m_recv_start, m_recv_pos); +} + +#if !defined TORRENT_DISABLE_ENCRYPTION +span receive_buffer::mutable_buffer() +{ + INVARIANT_CHECK; + return span(m_recv_buffer).subspan(m_recv_start, m_recv_pos); +} + +span receive_buffer::mutable_buffer(int const bytes) +{ + INVARIANT_CHECK; + // bytes is the number of bytes we just received, and m_recv_pos has + // already been adjusted for these bytes. The receive pos immediately + // before we received these bytes was (m_recv_pos - bytes) + return span(m_recv_buffer).subspan(m_recv_start + m_recv_pos - bytes, bytes); +} +#endif + +// the purpose of this function is to free up and cut off all messages +// in the receive buffer that have been parsed and processed. +// it may also shrink the size of the buffer allocation if we haven't been using +// enough of it lately. +void receive_buffer::normalize(int const force_shrink) +{ + INVARIANT_CHECK; + TORRENT_ASSERT(m_recv_end >= m_recv_start); + + m_watermark.add_sample(std::max(m_recv_end, m_packet_size)); + + // if the running average drops below half of the current buffer size, + // reallocate a smaller one. + bool const shrink_buffer = std::int64_t(m_recv_buffer.size()) / 2 > m_watermark.mean() + && m_watermark.mean() > (m_recv_end - m_recv_start); + + span bytes_to_shift(m_recv_buffer.data() + m_recv_start + , m_recv_end - m_recv_start); + + if (force_shrink) + { + int const target_size = std::max(std::max(force_shrink + , int(bytes_to_shift.size())), m_packet_size); + buffer new_buffer(target_size, bytes_to_shift); + m_recv_buffer = std::move(new_buffer); + } + else if (shrink_buffer) + { + buffer new_buffer(m_watermark.mean(), bytes_to_shift); + m_recv_buffer = std::move(new_buffer); + } + else if (m_recv_end > m_recv_start + && m_recv_start > 0) + { + std::memmove(m_recv_buffer.data(), bytes_to_shift.data() + , std::size_t(bytes_to_shift.size())); + } + + m_recv_end -= m_recv_start; + m_recv_start = 0; + +#if TORRENT_USE_ASSERTS + std::fill(m_recv_buffer.begin() + m_recv_end, m_recv_buffer.end(), std::uint8_t{0xcc}); +#endif +} + +void receive_buffer::reset(int const packet_size) +{ + INVARIANT_CHECK; + TORRENT_ASSERT(int(m_recv_buffer.size()) >= m_recv_end); + TORRENT_ASSERT(packet_size > 0); + if (m_recv_end > m_packet_size) + { + cut(m_packet_size, packet_size); + return; + } + + m_recv_pos = 0; + m_recv_start = 0; + m_recv_end = 0; + m_packet_size = packet_size; +} + +#if !defined TORRENT_DISABLE_ENCRYPTION +bool crypto_receive_buffer::packet_finished() const +{ + if (m_recv_pos == INT_MAX) + return m_connection_buffer.packet_finished(); + else + return m_packet_size <= m_recv_pos; +} + +int crypto_receive_buffer::packet_size() const +{ + if (m_recv_pos == INT_MAX) + return m_connection_buffer.packet_size(); + else + return m_packet_size; +} + +int crypto_receive_buffer::pos() const +{ + if (m_recv_pos == INT_MAX) + return m_connection_buffer.pos(); + else + return m_recv_pos; +} + +void crypto_receive_buffer::cut(int size, int packet_size, int offset) +{ + if (m_recv_pos != INT_MAX) + { + TORRENT_ASSERT(size <= m_recv_pos); + m_packet_size = packet_size; + packet_size = m_connection_buffer.packet_size() - size; + m_recv_pos -= size; + } + m_connection_buffer.cut(size, packet_size, offset); +} + +void crypto_receive_buffer::reset(int packet_size) +{ + if (m_recv_pos != INT_MAX) + { + if (m_connection_buffer.m_recv_end > m_packet_size) + { + cut(m_packet_size, packet_size); + return; + } + m_packet_size = packet_size; + packet_size = m_connection_buffer.packet_size() - m_recv_pos; + m_recv_pos = 0; + } + m_connection_buffer.reset(packet_size); +} + +void crypto_receive_buffer::crypto_reset(int packet_size) +{ + TORRENT_ASSERT(packet_finished()); + TORRENT_ASSERT(crypto_packet_finished()); + TORRENT_ASSERT(m_recv_pos == INT_MAX || m_recv_pos == m_connection_buffer.pos()); + TORRENT_ASSERT(m_recv_pos == INT_MAX || m_connection_buffer.pos_at_end()); + + if (packet_size == 0) + { + if (m_recv_pos != INT_MAX) + m_connection_buffer.cut(0, m_packet_size); + m_recv_pos = INT_MAX; + } + else + { + if (m_recv_pos == INT_MAX) + m_packet_size = m_connection_buffer.packet_size(); + m_recv_pos = m_connection_buffer.pos(); + TORRENT_ASSERT(m_recv_pos >= 0); + m_connection_buffer.cut(0, m_recv_pos + packet_size); + } +} + +int crypto_receive_buffer::advance_pos(int bytes) +{ + if (m_recv_pos == INT_MAX) return bytes; + + int const limit = m_packet_size > m_recv_pos ? m_packet_size - m_recv_pos : m_packet_size; + int const sub_transferred = std::min(bytes, limit); + m_recv_pos += sub_transferred; + m_connection_buffer.cut(0, m_connection_buffer.packet_size() + sub_transferred); + return sub_transferred; +} + +span crypto_receive_buffer::get() const +{ + span recv_buffer = m_connection_buffer.get(); + if (m_recv_pos < m_connection_buffer.pos()) + recv_buffer = recv_buffer.first(m_recv_pos); + return recv_buffer; +} + +span crypto_receive_buffer::mutable_buffer( + int const bytes) +{ + int const pending_decryption = (m_recv_pos != INT_MAX) + ? m_connection_buffer.packet_size() - m_recv_pos + : bytes; + return m_connection_buffer.mutable_buffer(pending_decryption); +} +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace libtorrent diff --git a/src/request_blocks.cpp b/src/request_blocks.cpp new file mode 100644 index 0000000..fd14986 --- /dev/null +++ b/src/request_blocks.cpp @@ -0,0 +1,319 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/peer_info.hpp" // for peer_info flags +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/aux_/has_block.hpp" + +#include + +namespace libtorrent { + + int source_rank(peer_source_flags_t const source_bitmask) + { + int ret = 0; + if (source_bitmask & peer_info::tracker) ret |= 1 << 5; + if (source_bitmask & peer_info::lsd) ret |= 1 << 4; + if (source_bitmask & peer_info::dht) ret |= 1 << 3; + if (source_bitmask & peer_info::pex) ret |= 1 << 2; + return ret; + } + + // the case where ignore_peer is motivated is if two peers + // have only one piece that we don't have, and it's the + // same piece for both peers. Then they might get into an + // infinite loop, fighting to request the same blocks. + // returns false if the function is aborted by an early-exit + // condition. + bool request_a_block(torrent& t, peer_connection& c) + { + if (t.is_seed()) return false; + if (c.no_download()) return false; + if (t.upload_mode()) return false; + if (c.is_disconnecting()) return false; + + // don't request pieces before we have the metadata + if (!t.valid_metadata()) return false; + + // don't request pieces before the peer is properly + // initialized after we have the metadata + if (!t.are_files_checked()) return false; + + // we don't want to request more blocks while trying to gracefully pause + if (t.graceful_pause()) return false; + + TORRENT_ASSERT(c.peer_info_struct() != nullptr + || c.type() != connection_type::bittorrent); + + bool const time_critical_mode = t.num_time_critical_pieces() > 0; + + // in time critical mode, only have 1 outstanding request at a time + // via normal requests + int const desired_queue_size = time_critical_mode + ? 1 : c.desired_queue_size(); + + int num_requests = desired_queue_size + - int(c.download_queue().size()) + - int(c.request_queue().size()); + +#ifndef TORRENT_DISABLE_LOGGING + if (c.should_log(peer_log_alert::info)) + { + c.peer_log(peer_log_alert::info, "PIECE_PICKER" + , "dlq: %d rqq: %d target: %d req: %d engame: %d" + , int(c.download_queue().size()), int(c.request_queue().size()) + , desired_queue_size, num_requests, c.endgame()); + } +#endif + TORRENT_ASSERT(desired_queue_size > 0); + // if our request queue is already full, we + // don't have to make any new requests yet + if (num_requests <= 0) return false; + + t.need_picker(); + + piece_picker& p = t.picker(); + std::vector interesting_pieces; + interesting_pieces.reserve(100); + + int prefer_contiguous_blocks = c.prefer_contiguous_blocks(); + + if (prefer_contiguous_blocks == 0 + && !time_critical_mode + && t.settings().get_int(settings_pack::whole_pieces_threshold) > 0) + { + // if our download rate lets us download a whole piece in + // "whole_pieces_threshold" seconds, we prefer to pick an entire piece. + // If we can download multiple whole pieces, we prefer to download that + // many contiguous pieces. + + // download_rate times the whole piece threshold (seconds) gives the + // number of bytes downloaded in one window of that threshold, divided + // by the piece size give us the number of (whole) pieces downloaded + // in the window. + int const contiguous_pieces = + std::min(c.statistics().download_payload_rate() + * t.settings().get_int(settings_pack::whole_pieces_threshold) + , 8 * 1024 * 1024) + / t.torrent_file().piece_length(); + + int const blocks_per_piece = t.torrent_file().piece_length() / t.block_size(); + + prefer_contiguous_blocks = contiguous_pieces * blocks_per_piece; + } + + // if we prefer whole pieces, the piece picker will pick at least + // the number of blocks we want, but it will try to make the picked + // blocks be from whole pieces, possibly by returning more blocks + // than we requested. +#if TORRENT_USE_ASSERTS + error_code ec; + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); +#endif + + aux::session_interface& ses = t.session(); + + std::vector const& dq = c.download_queue(); + std::vector const& rq = c.request_queue(); + + std::vector const& suggested = c.suggested_pieces(); + auto const* bits = &c.get_bitfield(); + typed_bitfield fast_mask; + + if (c.has_peer_choked()) + { + // if we are choked we can only pick pieces from the + // allowed fast set. The allowed fast set is sorted + // in ascending priority order + + // build a bitmask with only the allowed pieces in it + fast_mask.resize(c.get_bitfield().size(), false); + for (auto const& i : c.allowed_fast()) + { + if ((*bits)[i]) fast_mask.set_bit(i); + } + bits = &fast_mask; + } + + // picks the interesting pieces from this peer + // the integer is the number of pieces that + // should be guaranteed to be available for download + // (if num_requests is too big, too many pieces are + // picked and cpu-time is wasted) + // the last argument is if we should prefer whole pieces + // for this peer. If we're downloading one piece in 20 seconds + // then use this mode. + picker_flags_t const flags = p.pick_pieces(*bits, interesting_pieces + , num_requests, prefer_contiguous_blocks, c.peer_info_struct() + , c.picker_options(), suggested, t.num_peers() + , ses.stats_counters()); + +#ifndef TORRENT_DISABLE_LOGGING + if (t.alerts().should_post() + && !interesting_pieces.empty()) + { + t.alerts().emplace_alert(t.get_handle(), c.remote() + , c.pid(), flags, interesting_pieces); + } + c.peer_log(peer_log_alert::info, "PIECE_PICKER" + , "prefer_contiguous: %d picked: %d" + , prefer_contiguous_blocks, int(interesting_pieces.size())); +#else + TORRENT_UNUSED(flags); +#endif + + // if the number of pieces we have + the number of pieces + // we're requesting from is less than the number of pieces + // in the torrent, there are still some unrequested pieces + // and we're not strictly speaking in end-game mode yet + // also, if we already have at least one outstanding + // request, we shouldn't pick any busy pieces either + // in time critical mode, it's OK to request busy blocks + bool const dont_pick_busy_blocks + = ((ses.settings().get_bool(settings_pack::strict_end_game_mode) + && p.get_download_queue_size() < p.num_want_left()) + || dq.size() + rq.size() > 0) + && !time_critical_mode; + + // this is filled with an interesting piece + // that some other peer is currently downloading + piece_block busy_block = piece_block::invalid; + + for (piece_block const& pb : interesting_pieces) + { + if (prefer_contiguous_blocks == 0 && num_requests <= 0) break; + + if (time_critical_mode && p.piece_priority(pb.piece_index) != top_priority) + { + // assume the subsequent pieces are not prio 7 and + // be done + break; + } + + int num_block_requests = p.num_peers(pb); + if (num_block_requests > 0) + { + // have we picked enough pieces? + if (num_requests <= 0) break; + + // this block is busy. This means all the following blocks + // in the interesting_pieces list are busy as well, we might + // as well just exit the loop + if (dont_pick_busy_blocks) break; + + TORRENT_ASSERT(p.num_peers(pb) > 0); + busy_block = pb; + continue; + } + + TORRENT_ASSERT(p.num_peers(pb) == 0); + + // don't request pieces we already have in our request queue + // This happens when pieces time out or the peer sends us + // pieces we didn't request. Those aren't marked in the + // piece picker, but we still keep track of them in the + // download queue + if (std::find_if(dq.begin(), dq.end(), aux::has_block(pb)) != dq.end() + || std::find_if(rq.begin(), rq.end(), aux::has_block(pb)) != rq.end()) + { +#if TORRENT_USE_ASSERTS + std::vector::const_iterator j + = std::find_if(dq.begin(), dq.end(), aux::has_block(pb)); + if (j != dq.end()) TORRENT_ASSERT(j->timed_out || j->not_wanted); +#endif +#ifndef TORRENT_DISABLE_LOGGING + c.peer_log(peer_log_alert::info, "PIECE_PICKER" + , "not_picking: %d,%d already in queue" + , static_cast(pb.piece_index), pb.block_index); +#endif + continue; + } + + // ok, we found a piece that's not being downloaded + // by somebody else. request it from this peer + // and return + if (!c.add_request(pb, {})) continue; + TORRENT_ASSERT(p.num_peers(pb) == 1); + TORRENT_ASSERT(p.is_requested(pb)); + num_requests--; + } + + // we have picked as many blocks as we should + // we're done! + if (num_requests <= 0) + { + // since we could pick as many blocks as we + // requested without having to resort to picking + // busy ones, we're not in end-game mode + c.set_endgame(false); + return true; + } + + // we did not pick as many pieces as we wanted, because + // there aren't enough. This means we're in end-game mode + // as long as we have at least one request outstanding, + // we shouldn't pick another piece + // if we are attempting to download 'allowed' pieces + // and can't find any, that doesn't count as end-game + if (!c.has_peer_choked()) + c.set_endgame(true); + + // if we don't have any potential busy blocks to request + // or if we already have outstanding requests, don't + // pick a busy piece + if (busy_block == piece_block::invalid + || dq.size() + rq.size() > 0) + { + return true; + } + +#if TORRENT_USE_ASSERTS + piece_picker::downloading_piece st; + p.piece_info(busy_block.piece_index, st); + TORRENT_ASSERT(st.requested + st.finished + st.writing + == p.blocks_in_piece(busy_block.piece_index)); +#endif + TORRENT_ASSERT(p.is_requested(busy_block)); + TORRENT_ASSERT(!p.is_downloaded(busy_block)); + TORRENT_ASSERT(!p.is_finished(busy_block)); + TORRENT_ASSERT(p.num_peers(busy_block) > 0); + + c.add_request(busy_block, peer_connection::busy); + return true; + } + +} diff --git a/src/resolve_links.cpp b/src/resolve_links.cpp new file mode 100644 index 0000000..7f57e4d --- /dev/null +++ b/src/resolve_links.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS +resolve_links::resolve_links(std::shared_ptr ti) + : m_torrent_file(ti) +{ + TORRENT_ASSERT(ti); + + int piece_size = ti->piece_length(); + + file_storage const& fs = ti->files(); + m_file_sizes.reserve(aux::numeric_cast(fs.num_files())); + for (auto const i : fs.file_range()) + { + // don't match pad-files, and don't match files that aren't aligned to + // pieces. Files are matched by comparing piece hashes, so pieces must + // be aligned and the same size + if (fs.pad_file_at(i)) continue; + if ((fs.file_offset(i) % piece_size) != 0) continue; + + m_file_sizes.insert(std::make_pair(fs.file_size(i), i)); + } + + m_links.resize(m_torrent_file->num_files()); +} + +void resolve_links::match(std::shared_ptr const& ti + , std::string const& save_path) +{ + if (!ti) return; + + // only torrents with the same piece size + if (ti->piece_length() != m_torrent_file->piece_length()) return; + + int piece_size = ti->piece_length(); + + file_storage const& fs = ti->files(); + m_file_sizes.reserve(aux::numeric_cast(fs.num_files())); + for (auto const i : fs.file_range()) + { + // for every file in the other torrent, see if we have one that match + // it in m_torrent_file + + // if the file base is not aligned to pieces, we're not going to match + // it anyway (we only compare piece hashes) + if ((fs.file_offset(i) % piece_size) != 0) continue; + if (fs.pad_file_at(i)) continue; + + std::int64_t const file_size = fs.file_size(i); + + auto const range = m_file_sizes.equal_range(file_size); + for (auto iter = range.first; iter != range.second; ++iter) + { + TORRENT_ASSERT(iter->second >= file_index_t(0)); + TORRENT_ASSERT(iter->second < m_torrent_file->files().end_file()); + + // if we already have found a duplicate for this file, no need + // to keep looking + if (m_links[iter->second].ti) continue; + + // files are aligned and have the same size, now start comparing + // piece hashes, to see if the files are identical + + // the pieces of the incoming file + piece_index_t their_piece = fs.map_file(i, 0, 0).piece; + // the pieces of "this" file (from m_torrent_file) + piece_index_t our_piece = m_torrent_file->files().map_file( + iter->second, 0, 0).piece; + + int num_pieces = int((file_size + piece_size - 1) / piece_size); + + bool match = true; + for (int p = 0; p < num_pieces; ++p, ++their_piece, ++our_piece) + { + if (m_torrent_file->hash_for_piece(our_piece) + != ti->hash_for_piece(their_piece)) + { + match = false; + break; + } + } + if (!match) continue; + + m_links[iter->second].ti = ti; + m_links[iter->second].save_path = save_path; + m_links[iter->second].file_idx = i; + + // since we have a duplicate for this file, we may as well remove + // it from the file-size map, so we won't find it again. + m_file_sizes.erase(iter); + break; + } + } + +} +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +} // namespace libtorrent diff --git a/src/resolver.cpp b/src/resolver.cpp new file mode 100644 index 0000000..a2712d8 --- /dev/null +++ b/src/resolver.cpp @@ -0,0 +1,179 @@ +/* + +Copyright (c) 2013-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/resolver.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent { + + + constexpr resolver_flags resolver_interface::cache_only; + constexpr resolver_flags resolver_interface::abort_on_shutdown; + + resolver::resolver(io_service& ios) + : m_ios(ios) + , m_resolver(ios) + , m_critical_resolver(ios) + , m_max_size(700) + , m_timeout(seconds(1200)) + {} + + + void resolver::callback(resolver_interface::callback_t h + , error_code const& ec, std::vector
    const& ips) + { + try { + h(ec, ips); + } catch (std::exception&) { + TORRENT_ASSERT_FAIL(); + } + } + + void resolver::on_lookup(error_code const& ec, tcp::resolver::iterator i + , std::string const& hostname) + { + COMPLETE_ASYNC("resolver::on_lookup"); + if (ec) + { + auto const range = m_callbacks.equal_range(hostname); + for (auto c = range.first; c != range.second; ++c) + callback(std::move(c->second), ec, {}); + m_callbacks.erase(range.first, range.second); + return; + } + + dns_cache_entry& ce = m_cache[hostname]; + ce.last_seen = aux::time_now(); + ce.addresses.clear(); + while (i != tcp::resolver::iterator()) + { + ce.addresses.push_back(i->endpoint().address()); + ++i; + } + + auto const range = m_callbacks.equal_range(hostname); + for (auto c = range.first; c != range.second; ++c) + callback(std::move(c->second), ec, ce.addresses); + m_callbacks.erase(range.first, range.second); + + // if m_cache grows too big, weed out the + // oldest entries + if (int(m_cache.size()) > m_max_size) + { + auto oldest = m_cache.begin(); + for (auto k = m_cache.begin(); k != m_cache.end(); ++k) + { + if (k->second.last_seen < oldest->second.last_seen) + oldest = k; + } + + // remove the oldest entry + m_cache.erase(oldest); + } + } + + void resolver::async_resolve(std::string const& host, resolver_flags const flags + , resolver_interface::callback_t h) + { + // special handling for raw IP addresses. There's no need to get in line + // behind actual lookups if we can just resolve it immediately. + error_code ec; + address const ip = make_address(host, ec); + if (!ec) + { + m_ios.post([=]{ callback(h, ec, std::vector
    {ip}); }); + return; + } + ec.clear(); + + auto const i = m_cache.find(host); + if (i != m_cache.end()) + { + // keep cache entries valid for m_timeout seconds + if ((flags & resolver_interface::cache_only) + || i->second.last_seen + m_timeout >= aux::time_now()) + { + std::vector
    ips = i->second.addresses; + m_ios.post([=] { callback(h, ec, ips); }); + return; + } + } + + if (flags & resolver_interface::cache_only) + { + // we did not find a cache entry, fail the lookup + m_ios.post([=] { + callback(h, boost::asio::error::host_not_found, std::vector
    {}); + }); + return; + } + + auto iter = m_callbacks.find(host); + bool const done = (iter != m_callbacks.end()); + + m_callbacks.insert(iter, {host, std::move(h)}); + + // if there is an existing outtanding lookup, our callback will be + // called once it completes. We're done here. + if (done) return; + + // the port is ignored + tcp::resolver::query const q(host, "80"); + + using namespace std::placeholders; + ADD_OUTSTANDING_ASYNC("resolver::on_lookup"); + if (flags & resolver_interface::abort_on_shutdown) + { + m_resolver.async_resolve(q, std::bind(&resolver::on_lookup, this, _1, _2 + , host)); + } + else + { + m_critical_resolver.async_resolve(q, std::bind(&resolver::on_lookup, this, _1, _2 + , host)); + } + } + + void resolver::abort() + { + m_resolver.cancel(); + } + + void resolver::set_cache_timeout(seconds const timeout) + { + if (timeout >= seconds(0)) + m_timeout = timeout; + else + m_timeout = seconds(0); + } +} diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..0a47f99 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,467 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/extensions.hpp" // for add_peer_flags_t + +namespace libtorrent { + +#ifndef TORRENT_DISABLE_EXTENSIONS + // declared in extensions.hpp + // remove this once C++17 is required + constexpr feature_flags_t plugin::optimistic_unchoke_feature; + constexpr feature_flags_t plugin::tick_feature; + constexpr feature_flags_t plugin::dht_request_feature; + constexpr feature_flags_t plugin::alert_feature; +#endif + +namespace aux { + constexpr torrent_list_index_t session_interface::torrent_state_updates; + constexpr torrent_list_index_t session_interface::torrent_want_tick; + constexpr torrent_list_index_t session_interface::torrent_want_peers_download; + constexpr torrent_list_index_t session_interface::torrent_want_peers_finished; + constexpr torrent_list_index_t session_interface::torrent_want_scrape; + constexpr torrent_list_index_t session_interface::torrent_downloading_auto_managed; + constexpr torrent_list_index_t session_interface::torrent_seeding_auto_managed; + constexpr torrent_list_index_t session_interface::torrent_checking_auto_managed; +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +constexpr add_peer_flags_t torrent_plugin::first_time; +constexpr add_peer_flags_t torrent_plugin::filtered; +#endif + +namespace { + +#if defined TORRENT_ASIO_DEBUGGING + void wait_for_asio_handlers() + { + int counter = 0; + while (log_async()) + { + std::this_thread::sleep_for(seconds(1)); + ++counter; + std::printf("\x1b[2J\x1b[0;0H\x1b[33m==== Waiting to shut down: %d ==== \x1b[0m\n\n" + , counter); + } + async_dec_threads(); + + std::fprintf(stderr, "\n\nEXPECTS NO MORE ASYNC OPS\n\n\n"); + } +#endif +} // anonymous namespace + + settings_pack min_memory_usage() + { + settings_pack set; +#if TORRENT_ABI_VERSION == 1 + // receive data directly into disk buffers + // this yields more system calls to read() and + // kqueue(), but saves RAM. + set.set_bool(settings_pack::contiguous_recv_buffer, false); +#endif + + set.set_int(settings_pack::max_peer_recv_buffer_size, 32 * 1024 + 200); + + set.set_int(settings_pack::disk_io_write_mode, settings_pack::disable_os_cache); + set.set_int(settings_pack::disk_io_read_mode, settings_pack::disable_os_cache); + + // keep 2 blocks outstanding when hashing + set.set_int(settings_pack::checking_mem_usage, 2); + + // don't use any extra threads to do SHA-1 hashing + set.set_int(settings_pack::aio_threads, 1); + + set.set_int(settings_pack::alert_queue_size, 100); + + set.set_int(settings_pack::max_out_request_queue, 300); + set.set_int(settings_pack::max_allowed_in_request_queue, 100); + + // setting this to a low limit, means more + // peers are more likely to request from the + // same piece. Which means fewer partial + // pieces and fewer entries in the partial + // piece list + set.set_int(settings_pack::whole_pieces_threshold, 2); + set.set_bool(settings_pack::use_parole_mode, false); + set.set_bool(settings_pack::prioritize_partial_pieces, true); + + // connect to 5 peers per second + set.set_int(settings_pack::connection_speed, 5); + + // only have 4 files open at a time + set.set_int(settings_pack::file_pool_size, 4); + + // we want to keep the peer list as small as possible + set.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + set.set_int(settings_pack::max_failcount, 2); + set.set_int(settings_pack::inactivity_timeout, 120); + + // whenever a peer has downloaded one block, write + // it to disk, and don't read anything from the + // socket until the disk write is complete + set.set_int(settings_pack::max_queued_disk_bytes, 1); + + // never keep more than one 16kB block in + // the send buffer + set.set_int(settings_pack::send_buffer_watermark, 9); + + // don't use any disk cache + set.set_int(settings_pack::cache_size, 0); + set.set_bool(settings_pack::use_read_cache, false); + + set.set_bool(settings_pack::close_redundant_connections, true); + + set.set_int(settings_pack::max_peerlist_size, 500); + set.set_int(settings_pack::max_paused_peerlist_size, 50); + + // udp trackers are cheaper to talk to + set.set_bool(settings_pack::prefer_udp_trackers, true); + + set.set_int(settings_pack::max_rejects, 10); + + set.set_int(settings_pack::recv_socket_buffer_size, 16 * 1024); + set.set_int(settings_pack::send_socket_buffer_size, 16 * 1024); + + // use less memory when reading and writing + // whole pieces + set.set_bool(settings_pack::coalesce_reads, false); + set.set_bool(settings_pack::coalesce_writes, false); + return set; + } + + settings_pack high_performance_seed() + { + settings_pack set; + // don't throttle TCP, assume there is + // plenty of bandwidth + set.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); + + set.set_int(settings_pack::max_out_request_queue, 1500); + set.set_int(settings_pack::max_allowed_in_request_queue, 2000); + + set.set_int(settings_pack::max_peer_recv_buffer_size, 5 * 1024 * 1024); + + // we will probably see a high rate of alerts, make it less + // likely to loose alerts + set.set_int(settings_pack::alert_queue_size, 10000); + + // allow 500 files open at a time + set.set_int(settings_pack::file_pool_size, 500); + + // don't update access time for each read/write + set.set_bool(settings_pack::no_atime_storage, true); + + // as a seed box, we must accept multiple peers behind + // the same NAT +// set.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + + // connect to 50 peers per second + set.set_int(settings_pack::connection_speed, 500); + + // allow 8000 peer connections + set.set_int(settings_pack::connections_limit, 8000); + + // allow lots of peers to try to connect simultaneously + set.set_int(settings_pack::listen_queue_size, 3000); + + // unchoke all peers + set.set_int(settings_pack::unchoke_slots_limit, -1); + + // use 1 GB of cache + set.set_int(settings_pack::cache_size, 32768 * 2); + set.set_bool(settings_pack::use_read_cache, true); + set.set_int(settings_pack::read_cache_line_size, 32); + set.set_int(settings_pack::write_cache_line_size, 256); + // 30 seconds expiration to save cache + // space for active pieces + set.set_int(settings_pack::cache_expiry, 30); + + // in case the OS we're running on doesn't support + // readv/writev, allocate contiguous buffers for + // reads and writes + // disable, since it uses a lot more RAM and a significant + // amount of CPU to copy it around + set.set_bool(settings_pack::coalesce_reads, false); + set.set_bool(settings_pack::coalesce_writes, false); + + // the max number of bytes pending write before we throttle + // download rate + set.set_int(settings_pack::max_queued_disk_bytes, 7 * 1024 * 1024); + + // prevent fast pieces to interfere with suggested pieces + // since we unchoke everyone, we don't need fast pieces anyway + set.set_int(settings_pack::allowed_fast_set_size, 0); + + // suggest pieces in the read cache for higher cache hit rate + set.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + + set.set_bool(settings_pack::close_redundant_connections, true); + + set.set_int(settings_pack::max_rejects, 10); + + set.set_int(settings_pack::send_not_sent_low_watermark, 524288); + + // don't let connections linger for too long + set.set_int(settings_pack::request_timeout, 10); + set.set_int(settings_pack::peer_timeout, 20); + set.set_int(settings_pack::inactivity_timeout, 20); + + set.set_int(settings_pack::active_limit, 20000); + set.set_int(settings_pack::active_tracker_limit, 2000); + set.set_int(settings_pack::active_dht_limit, 600); + set.set_int(settings_pack::active_seeds, 2000); + + set.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); + + // of 500 ms, and a send rate of 4 MB/s, the upper + // limit should be 2 MB + set.set_int(settings_pack::send_buffer_watermark, 3 * 1024 * 1024); + + // put 1.5 seconds worth of data in the send buffer + // this gives the disk I/O more heads-up on disk + // reads, and can maximize throughput + set.set_int(settings_pack::send_buffer_watermark_factor, 150); + + // always stuff at least 1 MiB down each peer + // pipe, to quickly ramp up send rates + set.set_int(settings_pack::send_buffer_low_watermark, 1 * 1024 * 1024); + + // don't retry peers if they fail once. Let them + // connect to us if they want to + set.set_int(settings_pack::max_failcount, 1); + + // number of disk threads for low level file operations + set.set_int(settings_pack::aio_threads, 8); + + set.set_int(settings_pack::checking_mem_usage, 2048); + + return set; + } + + session_params read_session_params(bdecode_node const& e, save_state_flags_t const flags) + { + session_params params; + + bdecode_node settings; + if (e.type() != bdecode_node::dict_t) return params; + + if (flags & session_handle::save_settings) + { + settings = e.dict_find_dict("settings"); + if (settings) + { + params.settings = load_pack_from_dict(settings); + } + } + +#ifndef TORRENT_DISABLE_DHT + if (flags & session_handle::save_dht_settings) + { + settings = e.dict_find_dict("dht"); + if (settings) + { + params.dht_settings = dht::read_dht_settings(settings); + } + } + + if (flags & session_handle::save_dht_state) + { + settings = e.dict_find_dict("dht state"); + if (settings) + { + params.dht_state = dht::read_dht_state(settings); + } + } +#endif + + return params; + } + + void session::start(session_params&& params, io_service* ios) + { + bool const internal_executor = ios == nullptr; + + if (internal_executor) + { + // the user did not provide an executor, we have to use our own + m_io_service = std::make_shared(1); + ios = m_io_service.get(); + } + + m_impl = std::make_shared(std::ref(*ios), std::ref(params.settings)); + *static_cast(this) = session_handle(m_impl); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : params.extensions) + { + m_impl->add_ses_extension(std::move(ext)); + } +#endif + +#ifndef TORRENT_DISABLE_DHT + if (params.settings.has_val(settings_pack::dht_upload_rate_limit)) + params.dht_settings.upload_rate_limit = params.settings.get_int(settings_pack::dht_upload_rate_limit); + + m_impl->set_dht_settings(std::move(params.dht_settings)); + m_impl->set_dht_state(std::move(params.dht_state)); + + TORRENT_ASSERT(params.dht_storage_constructor); + m_impl->set_dht_storage(std::move(params.dht_storage_constructor)); +#endif + + m_impl->start_session(); + + if (internal_executor) + { + // start a thread for the message pump + m_thread = std::make_shared( + [&] { m_io_service->run(); }); + } + } + +namespace { + + std::vector> default_plugins( + bool empty = false) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (empty) return {}; + using wrapper = aux::session_impl::session_plugin_wrapper; + return { + std::make_shared(create_ut_pex_plugin), + std::make_shared(create_ut_metadata_plugin), + std::make_shared(create_smart_ban_plugin) + }; +#else + TORRENT_UNUSED(empty); + return {}; +#endif + } + } + + void session::start(session_flags_t const flags, settings_pack&& sp, io_service* ios) + { + start({std::move(sp), + default_plugins(!(flags & add_default_plugins))}, ios); + } + + session::~session() + { + if (!m_impl) return; + + aux::dump_call_profile(); + + // capture the shared_ptr in the dispatched function + // to keep the session_impl alive + m_impl->call_abort(); + + if (m_thread && m_thread.use_count() == 1) + { +#if defined TORRENT_ASIO_DEBUGGING + wait_for_asio_handlers(); +#endif + m_thread->join(); + } + } + + session_proxy session::abort() + { + // stop calling the alert notify function now, to avoid it thinking the + // session is still alive + m_impl->alerts().set_notify_function({}); + return session_proxy(m_io_service, m_thread, m_impl); + } + + session_proxy::session_proxy() = default; + session_proxy::session_proxy(std::shared_ptr ios + , std::shared_ptr t + , std::shared_ptr impl) + : m_io_service(std::move(ios)) + , m_thread(std::move(t)) + , m_impl(std::move(impl)) + {} + session_proxy::session_proxy(session_proxy const&) = default; + session_proxy& session_proxy::operator=(session_proxy const&) = default; + session_proxy::session_proxy(session_proxy&&) noexcept = default; + session_proxy& session_proxy::operator=(session_proxy&&) noexcept = default; + session_proxy::~session_proxy() + { + if (m_thread && m_thread.use_count() == 1) + { +#if defined TORRENT_ASIO_DEBUGGING + wait_for_asio_handlers(); +#endif + m_thread->join(); + } + } + + session_params::session_params(settings_pack&& sp) + : session_params(std::move(sp), default_plugins()) + {} + + session_params::session_params(settings_pack const& sp) + : session_params(sp, default_plugins()) + {} + + session_params::session_params() + : extensions(default_plugins()) +#ifndef TORRENT_DISABLE_DHT + , dht_storage_constructor(dht::dht_default_storage_constructor) +#endif + {} + + session_params::session_params(settings_pack&& sp + , std::vector> exts) + : settings(std::move(sp)) + , extensions(std::move(exts)) +#ifndef TORRENT_DISABLE_DHT + , dht_storage_constructor(dht::dht_default_storage_constructor) +#endif + {} + + session_params::session_params(settings_pack const& sp + , std::vector> exts) + : settings(sp) + , extensions(std::move(exts)) +#ifndef TORRENT_DISABLE_DHT + , dht_storage_constructor(dht::dht_default_storage_constructor) +#endif + {} +} diff --git a/src/session_call.cpp b/src/session_call.cpp new file mode 100644 index 0000000..bb9fad0 --- /dev/null +++ b/src/session_call.cpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2014-2018, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/session_call.hpp" + +namespace libtorrent { namespace aux { + +#ifdef TORRENT_PROFILE_CALLS +static std::mutex g_calls_mutex; +static std::unordered_map g_blocking_calls; +#endif + +void blocking_call() +{ +#ifdef TORRENT_PROFILE_CALLS + char stack[2048]; + print_backtrace(stack, sizeof(stack), 20); + std::unique_lock l(g_calls_mutex); + g_blocking_calls[stack] += 1; +#endif +} + +void dump_call_profile() +{ +#ifdef TORRENT_PROFILE_CALLS + FILE* out = fopen("blocking_calls.txt", "w+"); + + std::map profile; + + std::unique_lock l(g_calls_mutex); + for (auto const& c : g_blocking_calls) + { + profile[c.second] = c.first; + } + for (std::map::const_reverse_iterator i = profile.rbegin() + , end(profile.rend()); i != end; ++i) + { + std::fprintf(out, "\n\n%d\n%s\n", i->first, i->second.c_str()); + } + fclose(out); +#endif +} + +void torrent_wait(bool& done, aux::session_impl& ses) +{ + blocking_call(); + std::unique_lock l(ses.mut); + while (!done) { ses.cond.wait(l); } +} + +} } // namespace aux namespace libtorrent diff --git a/src/session_handle.cpp b/src/session_handle.cpp new file mode 100644 index 0000000..478ac5e --- /dev/null +++ b/src/session_handle.cpp @@ -0,0 +1,1277 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session_handle.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/aux_/scope_end.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/lazy_entry.hpp" +#endif + +using libtorrent::aux::session_impl; + +namespace libtorrent { + + constexpr peer_class_t session_handle::global_peer_class_id; + constexpr peer_class_t session_handle::tcp_peer_class_id; + constexpr peer_class_t session_handle::local_peer_class_id; + + constexpr save_state_flags_t session_handle::save_settings; + constexpr save_state_flags_t session_handle::save_dht_settings; + constexpr save_state_flags_t session_handle::save_dht_state; +#if TORRENT_ABI_VERSION == 1 + constexpr save_state_flags_t session_handle::save_encryption_settings; + constexpr save_state_flags_t session_handle::save_as_map TORRENT_DEPRECATED_ENUM; + constexpr save_state_flags_t session_handle::save_proxy TORRENT_DEPRECATED_ENUM; + constexpr save_state_flags_t session_handle::save_i2p_proxy TORRENT_DEPRECATED_ENUM; + constexpr save_state_flags_t session_handle::save_dht_proxy TORRENT_DEPRECATED_ENUM; + constexpr save_state_flags_t session_handle::save_peer_proxy TORRENT_DEPRECATED_ENUM; + constexpr save_state_flags_t session_handle::save_web_proxy TORRENT_DEPRECATED_ENUM; + constexpr save_state_flags_t session_handle::save_tracker_proxy TORRENT_DEPRECATED_ENUM; +#endif + + constexpr session_flags_t session_handle::add_default_plugins; +#if TORRENT_ABI_VERSION == 1 + constexpr session_flags_t session_handle::start_default_features; +#endif + + constexpr remove_flags_t session_handle::delete_files; + constexpr remove_flags_t session_handle::delete_partfile; + + constexpr reopen_network_flags_t session_handle::reopen_map_ports; + + template + void session_handle::async_call(Fun f, Args&&... a) const + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + s->get_io_service().dispatch([=]() mutable + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (s.get()->*f)(std::forward(a)...); +#ifndef BOOST_NO_EXCEPTIONS + } catch (system_error const& e) { + s->alerts().emplace_alert(e.code(), e.what()); + } catch (std::exception const& e) { + s->alerts().emplace_alert(error_code(), e.what()); + } catch (...) { + s->alerts().emplace_alert(error_code(), "unknown error"); + } +#endif + }); + } + + template + void session_handle::sync_call(Fun f, Args&&... a) const + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + + // this is the flag to indicate the call has completed + // capture them by pointer to allow everything to be captured by value + // and simplify the capture expression + bool done = false; + + std::exception_ptr ex; + s->get_io_service().dispatch([=, &done, &ex]() mutable + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (s.get()->*f)(std::forward(a)...); +#ifndef BOOST_NO_EXCEPTIONS + } catch (...) { + ex = std::current_exception(); + } +#endif + std::unique_lock l(s->mut); + done = true; + s->cond.notify_all(); + }); + + aux::torrent_wait(done, *s); + if (ex) std::rethrow_exception(ex); + } + + template + Ret session_handle::sync_call_ret(Fun f, Args&&... a) const + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + + // this is the flag to indicate the call has completed + // capture them by pointer to allow everything to be captured by value + // and simplify the capture expression + bool done = false; + Ret r; + std::exception_ptr ex; + s->get_io_service().dispatch([=, &r, &done, &ex]() mutable + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + r = (s.get()->*f)(std::forward(a)...); +#ifndef BOOST_NO_EXCEPTIONS + } catch (...) { + ex = std::current_exception(); + } +#endif + std::unique_lock l(s->mut); + done = true; + s->cond.notify_all(); + }); + + aux::torrent_wait(done, *s); + if (ex) std::rethrow_exception(ex); + return r; + } + + void session_handle::save_state(entry& e, save_state_flags_t const flags) const + { + entry* ep = &e; + sync_call(&session_impl::save_state, ep, flags); + } + + void session_handle::load_state(bdecode_node const& e + , save_state_flags_t const flags) + { + // this needs to be synchronized since the lifespan + // of e is tied to the caller + sync_call(&session_impl::load_state, &e, flags); + } + + std::vector session_handle::get_torrent_status( + std::function const& pred + , status_flags_t const flags) const + { + std::vector ret; + sync_call(&session_impl::get_torrent_status, &ret, pred, flags); + return ret; + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t const flags) const + { + sync_call(&session_impl::get_torrent_status, ret, pred, flags); + } +#endif + + void session_handle::refresh_torrent_status(std::vector* ret + , status_flags_t const flags) const + { + sync_call(&session_impl::refresh_torrent_status, ret, flags); + } + + void session_handle::post_torrent_updates(status_flags_t const flags) + { + async_call(&session_impl::post_torrent_updates, flags); + } + + void session_handle::post_session_stats() + { + async_call(&session_impl::post_session_stats); + } + + void session_handle::post_dht_stats() + { + async_call(&session_impl::post_dht_stats); + } + + io_service& session_handle::get_io_service() + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + return s->get_io_service(); + } + + torrent_handle session_handle::find_torrent(sha1_hash const& info_hash) const + { + return sync_call_ret(&session_impl::find_torrent_handle, info_hash); + } + + std::vector session_handle::get_torrents() const + { + return sync_call_ret>(&session_impl::get_torrents); + } + +#if TORRENT_ABI_VERSION == 1 +namespace { + + void handle_backwards_compatible_resume_data(add_torrent_params& atp) + { + // if there's no resume data set, there's nothing to do. It's either + // using the previous API without resume data, or the resume data has + // already been parsed out into the add_torrent_params struct. + if (atp.resume_data.empty()) return; + + error_code ec; + add_torrent_params resume_data + = read_resume_data(atp.resume_data, ec); + + resume_data.internal_resume_data_error = ec; + if (ec) return; + + // now, merge resume_data into atp according to the merge flags + if ((atp.flags & add_torrent_params::flag_use_resume_save_path) + && !resume_data.save_path.empty()) + { + atp.save_path = std::move(resume_data.save_path); + } + + if (!atp.ti) + { + atp.ti = std::move(resume_data.ti); + } + + if (!resume_data.trackers.empty()) + { + atp.tracker_tiers.resize(atp.trackers.size(), 0); + atp.trackers.insert(atp.trackers.end() + , resume_data.trackers.begin() + , resume_data.trackers.end()); + atp.tracker_tiers.insert(atp.tracker_tiers.end() + , resume_data.tracker_tiers.begin() + , resume_data.tracker_tiers.end()); + if (!(resume_data.flags & add_torrent_params::flag_merge_resume_trackers)) + atp.flags |= add_torrent_params::flag_override_trackers; + } + + if (!resume_data.url_seeds.empty()) + { + if (!(atp.flags & add_torrent_params::flag_merge_resume_http_seeds)) + atp.url_seeds.clear(); + + atp.url_seeds.insert(atp.url_seeds.end() + , resume_data.url_seeds.begin() + , resume_data.url_seeds.end()); + if (!(atp.flags & add_torrent_params::flag_merge_resume_http_seeds)) + atp.flags |= add_torrent_params::flag_override_web_seeds; + } + + if (!resume_data.http_seeds.empty()) + { + if (!(atp.flags & add_torrent_params::flag_merge_resume_http_seeds)) + atp.http_seeds.clear(); + + atp.http_seeds.insert(atp.http_seeds.end() + , resume_data.http_seeds.begin() + , resume_data.http_seeds.end()); + if (!(atp.flags & add_torrent_params::flag_merge_resume_http_seeds)) + atp.flags |= add_torrent_params::flag_override_web_seeds; + } + + atp.total_uploaded = resume_data.total_uploaded; + atp.total_downloaded = resume_data.total_downloaded; + atp.num_complete = resume_data.num_complete; + atp.num_incomplete = resume_data.num_incomplete; + atp.num_downloaded = resume_data.num_downloaded; + atp.active_time = resume_data.active_time; + atp.finished_time = resume_data.finished_time; + atp.seeding_time = resume_data.seeding_time; + + atp.last_seen_complete = resume_data.last_seen_complete; + atp.last_upload = resume_data.last_upload; + atp.last_download = resume_data.last_download; + atp.url = resume_data.url; + atp.uuid = resume_data.uuid; + + atp.added_time = resume_data.added_time; + atp.completed_time = resume_data.completed_time; + + atp.peers.swap(resume_data.peers); + atp.banned_peers.swap(resume_data.banned_peers); + + atp.unfinished_pieces.swap(resume_data.unfinished_pieces); + atp.have_pieces.swap(resume_data.have_pieces); + atp.verified_pieces.swap(resume_data.verified_pieces); + atp.piece_priorities.swap(resume_data.piece_priorities); + + atp.merkle_tree = std::move(resume_data.merkle_tree); + + atp.renamed_files = std::move(resume_data.renamed_files); + + if (!(atp.flags & add_torrent_params::flag_override_resume_data)) + { + atp.download_limit = resume_data.download_limit; + atp.upload_limit = resume_data.upload_limit; + atp.max_connections = resume_data.max_connections; + atp.max_uploads = resume_data.max_uploads; + atp.trackerid = resume_data.trackerid; + if (!resume_data.file_priorities.empty()) + atp.file_priorities = resume_data.file_priorities; + + torrent_flags_t const mask = + add_torrent_params::flag_seed_mode + | add_torrent_params::flag_super_seeding + | add_torrent_params::flag_auto_managed + | add_torrent_params::flag_sequential_download + | add_torrent_params::flag_paused; + + atp.flags &= ~mask; + atp.flags |= resume_data.flags & mask; + } + else + { + if (atp.file_priorities.empty()) + atp.file_priorities = resume_data.file_priorities; + } + } + +} // anonymous namespace + +#endif // TORRENT_ABI_VERSION + +#ifndef BOOST_NO_EXCEPTIONS + torrent_handle session_handle::add_torrent(add_torrent_params&& params) + { + TORRENT_ASSERT_PRECOND(!params.save_path.empty()); + +#if TORRENT_ABI_VERSION == 1 + handle_backwards_compatible_resume_data(params); +#endif + error_code ec; + auto ecr = std::ref(ec); + torrent_handle r = sync_call_ret(&session_impl::add_torrent, std::move(params), ecr); + if (ec) aux::throw_ex(ec); + return r; + } + + torrent_handle session_handle::add_torrent(add_torrent_params const& params) + { + return add_torrent(add_torrent_params(params)); + } +#endif + + torrent_handle session_handle::add_torrent(add_torrent_params&& params, error_code& ec) + { + TORRENT_ASSERT_PRECOND(!params.save_path.empty()); + + ec.clear(); +#if TORRENT_ABI_VERSION == 1 + handle_backwards_compatible_resume_data(params); +#endif + auto ecr = std::ref(ec); + return sync_call_ret(&session_impl::add_torrent, std::move(params), ecr); + } + + torrent_handle session_handle::add_torrent(add_torrent_params const& params, error_code& ec) + { + return add_torrent(add_torrent_params(params), ec); + } + + void session_handle::async_add_torrent(add_torrent_params const& params) + { + async_add_torrent(add_torrent_params(params)); + } + + void session_handle::async_add_torrent(add_torrent_params&& params) + { + TORRENT_ASSERT_PRECOND(!params.save_path.empty()); + + // we cannot capture a unique_ptr into a lambda in c++11, so we use a raw + // pointer for now. async_call uses a lambda expression to post the call + // to the main thread + // TODO: in C++14, use unique_ptr and move it into the lambda + auto* p = new add_torrent_params(std::move(params)); + auto guard = aux::scope_end([p]{ delete p; }); + p->save_path = complete(p->save_path); + +#if TORRENT_ABI_VERSION == 1 + handle_backwards_compatible_resume_data(*p); +#endif + + async_call(&session_impl::async_add_torrent, p); + guard.disarm(); + } + +#ifndef BOOST_NO_EXCEPTIONS +#if TORRENT_ABI_VERSION == 1 + // if the torrent already exists, this will throw duplicate_torrent + torrent_handle session_handle::add_torrent( + torrent_info const& ti + , std::string const& save_path + , entry const& resume_data + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc) + { + add_torrent_params p(std::move(sc)); + p.ti = std::make_shared(ti); + p.save_path = save_path; + if (resume_data.type() != entry::undefined_t) + { + bencode(std::back_inserter(p.resume_data), resume_data); + } + p.storage_mode = storage_mode; + if (paused) p.flags |= add_torrent_params::flag_paused; + else p.flags &= ~add_torrent_params::flag_paused; + return add_torrent(p); + } + + torrent_handle session_handle::add_torrent( + char const* tracker_url + , sha1_hash const& info_hash + , char const* name + , std::string const& save_path + , entry const& resume_data + , storage_mode_t storage_mode + , bool paused + , storage_constructor_type sc + , void* userdata) + { + TORRENT_ASSERT_PRECOND(!save_path.empty()); + + add_torrent_params p(std::move(sc)); + p.trackers.push_back(tracker_url); + p.info_hash = info_hash; + p.save_path = save_path; + p.storage_mode = storage_mode; + + if (paused) p.flags |= add_torrent_params::flag_paused; + else p.flags &= ~add_torrent_params::flag_paused; + + p.userdata = userdata; + p.name = name; + if (resume_data.type() != entry::undefined_t) + { + bencode(std::back_inserter(p.resume_data), resume_data); + } + return add_torrent(p); + } +#endif // TORRENT_ABI_VERSION +#endif // BOOST_NO_EXCEPTIONS + + void session_handle::pause() + { + async_call(&session_impl::pause); + } + + void session_handle::resume() + { + async_call(&session_impl::resume); + } + + bool session_handle::is_paused() const + { + return sync_call_ret(&session_impl::is_paused); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::set_load_function(user_load_function_t fun) + { + async_call(&session_impl::set_load_function, fun); + } + + session_status session_handle::status() const + { + return sync_call_ret(&session_impl::status); + } + + void session_handle::get_cache_info(sha1_hash const& ih + , std::vector& ret) const + { + cache_status st; + get_cache_info(&st, find_torrent(ih)); + ret.swap(st.pieces); + } + + cache_status session_handle::get_cache_status() const + { + cache_status st; + get_cache_info(&st); + return st; + } +#endif + + void session_handle::get_cache_info(cache_status* ret + , torrent_handle h, int flags) const + { + sync_call(&session_impl::get_cache_info, h, ret, flags); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::start_dht() + { + settings_pack p; + p.set_bool(settings_pack::enable_dht, true); + apply_settings(std::move(p)); + } + + void session_handle::stop_dht() + { + settings_pack p; + p.set_bool(settings_pack::enable_dht, false); + apply_settings(std::move(p)); + } +#endif // TORRENT_ABI_VERSION + + void session_handle::set_dht_settings(dht::dht_settings const& settings) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::set_dht_settings, settings); +#else + TORRENT_UNUSED(settings); +#endif + } + + dht::dht_settings session_handle::get_dht_settings() const + { +#ifndef TORRENT_DISABLE_DHT + return sync_call_ret(&session_impl::get_dht_settings); +#else + return dht::dht_settings(); +#endif + } + + bool session_handle::is_dht_running() const + { +#ifndef TORRENT_DISABLE_DHT + return sync_call_ret(&session_impl::is_dht_running); +#else + return false; +#endif + } + + void session_handle::set_dht_storage(dht::dht_storage_constructor_type sc) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::set_dht_storage, sc); +#else + TORRENT_UNUSED(sc); +#endif + } + + void session_handle::add_dht_node(std::pair const& node) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::add_dht_node_name, node); +#else + TORRENT_UNUSED(node); +#endif + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::add_dht_router(std::pair const& node) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::add_dht_router, node); +#else + TORRENT_UNUSED(node); +#endif + } +#endif // TORRENT_ABI_VERSION + + void session_handle::dht_get_item(sha1_hash const& target) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_get_immutable_item, target); +#else + TORRENT_UNUSED(target); +#endif + } + + void session_handle::dht_get_item(std::array key + , std::string salt) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_get_mutable_item, key, salt); +#else + TORRENT_UNUSED(key); + TORRENT_UNUSED(salt); +#endif + } + + // TODO: 3 expose the sequence_number, public_key, secret_key and signature + // types to the client + sha1_hash session_handle::dht_put_item(entry data) + { + std::vector buf; + bencode(std::back_inserter(buf), data); + sha1_hash const ret = hasher(buf).final(); + +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_put_immutable_item, data, ret); +#endif + return ret; + } + + void session_handle::dht_put_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_put_mutable_item, key, cb, salt); +#else + TORRENT_UNUSED(key); + TORRENT_UNUSED(cb); + TORRENT_UNUSED(salt); +#endif + } + + void session_handle::dht_get_peers(sha1_hash const& info_hash) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_get_peers, info_hash); +#else + TORRENT_UNUSED(info_hash); +#endif + } + + void session_handle::dht_announce(sha1_hash const& info_hash, int port + , dht::announce_flags_t const flags) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_announce, info_hash, port, flags); +#else + TORRENT_UNUSED(info_hash); + TORRENT_UNUSED(port); + TORRENT_UNUSED(flags); +#endif + } + + void session_handle::dht_live_nodes(sha1_hash const& nid) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_live_nodes, nid); +#else + TORRENT_UNUSED(nid); +#endif + } + + void session_handle::dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::dht_sample_infohashes, ep, target); +#else + TORRENT_UNUSED(ep); + TORRENT_UNUSED(target); +#endif + } + + void session_handle::dht_direct_request(udp::endpoint const& ep, entry const& e, void* userdata) + { +#ifndef TORRENT_DISABLE_DHT + entry copy = e; + async_call(&session_impl::dht_direct_request, ep, copy, userdata); +#else + TORRENT_UNUSED(ep); + TORRENT_UNUSED(e); + TORRENT_UNUSED(userdata); +#endif + } + +#if TORRENT_ABI_VERSION == 1 + entry session_handle::dht_state() const + { +#ifndef TORRENT_DISABLE_DHT + return sync_call_ret(&session_impl::dht_state); +#else + return entry(); +#endif + } + + void session_handle::start_dht(entry const& startup_state) + { +#ifndef TORRENT_DISABLE_DHT + async_call(&session_impl::start_dht_deprecated, startup_state); +#else + TORRENT_UNUSED(startup_state); +#endif + } +#endif // TORRENT_ABI_VERSION + + void session_handle::add_extension(std::function(torrent_handle const&, void*)> ext) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + async_call(&session_impl::add_extension, ext); +#else + TORRENT_UNUSED(ext); +#endif + } + + void session_handle::add_extension(std::shared_ptr ext) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + async_call(&session_impl::add_ses_extension, ext); +#else + TORRENT_UNUSED(ext); +#endif + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::load_asnum_db(char const*) {} + void session_handle::load_country_db(char const*) {} + + int session_handle::as_for_ip(address const&) + { return 0; } + + void session_handle::load_asnum_db(wchar_t const*) {} + void session_handle::load_country_db(wchar_t const*) {} + + void session_handle::load_state(entry const& ses_state + , save_state_flags_t const flags) + { + if (ses_state.type() == entry::undefined_t) return; + std::vector buf; + bencode(std::back_inserter(buf), ses_state); + bdecode_node e; + error_code ec; +#if TORRENT_USE_ASSERTS || !defined BOOST_NO_EXCEPTIONS + int ret = +#endif + bdecode(&buf[0], &buf[0] + buf.size(), e, ec); + + TORRENT_ASSERT(ret == 0); +#ifndef BOOST_NO_EXCEPTIONS + if (ret != 0) aux::throw_ex(ec); +#endif + sync_call(&session_impl::load_state, &e, flags); + } + + entry session_handle::state() const + { + entry ret; + auto retp = &ret; + sync_call(&session_impl::save_state, retp, save_state_flags_t::all()); + return ret; + } + + void session_handle::load_state(lazy_entry const& ses_state + , save_state_flags_t const flags) + { + if (ses_state.type() == lazy_entry::none_t) return; + std::pair buf = ses_state.data_section(); + bdecode_node e; + error_code ec; +#if TORRENT_USE_ASSERTS || !defined BOOST_NO_EXCEPTIONS + int ret = +#endif + bdecode(buf.first, buf.first + buf.second, e, ec); + + TORRENT_ASSERT(ret == 0); +#ifndef BOOST_NO_EXCEPTIONS + if (ret != 0) aux::throw_ex(ec); +#endif + sync_call(&session_impl::load_state, &e, flags); + } +#endif // TORRENT_ABI_VERSION + + void session_handle::set_ip_filter(ip_filter const& f) + { + std::shared_ptr copy = std::make_shared(f); + async_call(&session_impl::set_ip_filter, copy); + } + + ip_filter session_handle::get_ip_filter() const + { + return sync_call_ret(&session_impl::get_ip_filter); + } + + void session_handle::set_port_filter(port_filter const& f) + { + async_call(&session_impl::set_port_filter, f); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::set_peer_id(peer_id const& id) + { + settings_pack p; + p.set_str(settings_pack::peer_fingerprint, id.to_string()); + apply_settings(std::move(p)); + } + + void session_handle::set_key(std::uint32_t) + { + // this is just a dummy function now, as we generate the key automatically + // per listen interface + } + + peer_id session_handle::id() const + { + return sync_call_ret(&session_impl::deprecated_get_peer_id); + } +#endif + + unsigned short session_handle::listen_port() const + { + return sync_call_ret + (&session_impl::listen_port); + } + + unsigned short session_handle::ssl_listen_port() const + { + return sync_call_ret + (&session_impl::ssl_listen_port); + } + + bool session_handle::is_listening() const + { + return sync_call_ret(&session_impl::is_listening); + } + + void session_handle::set_peer_class_filter(ip_filter const& f) + { + async_call(&session_impl::set_peer_class_filter, f); + } + + ip_filter session_handle::get_peer_class_filter() const + { + return sync_call_ret(&session_impl::get_peer_class_filter); + } + + void session_handle::set_peer_class_type_filter(peer_class_type_filter const& f) + { + async_call(&session_impl::set_peer_class_type_filter, f); + } + + peer_class_type_filter session_handle::get_peer_class_type_filter() const + { + return sync_call_ret(&session_impl::get_peer_class_type_filter); + } + + peer_class_t session_handle::create_peer_class(char const* name) + { + return sync_call_ret(&session_impl::create_peer_class, name); + } + + void session_handle::delete_peer_class(peer_class_t cid) + { + async_call(&session_impl::delete_peer_class, cid); + } + + peer_class_info session_handle::get_peer_class(peer_class_t cid) const + { + return sync_call_ret(&session_impl::get_peer_class, cid); + } + + void session_handle::set_peer_class(peer_class_t cid, peer_class_info const& pci) + { + async_call(&session_impl::set_peer_class, cid, pci); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::use_interfaces(char const* interfaces) + { + settings_pack p; + p.set_str(settings_pack::outgoing_interfaces, interfaces); + apply_settings(std::move(p)); + } + + void session_handle::listen_on( + std::pair const& port_range + , error_code& ec + , const char* net_interface, int flags) + { + settings_pack p; + std::string interfaces_str; + if (net_interface == nullptr || strlen(net_interface) == 0) + net_interface = "0.0.0.0"; + + interfaces_str = print_endpoint(tcp::endpoint(make_address(net_interface, ec), std::uint16_t(port_range.first))); + if (ec) return; + + p.set_str(settings_pack::listen_interfaces, interfaces_str); + p.set_int(settings_pack::max_retry_port_bind, port_range.second - port_range.first); + p.set_bool(settings_pack::listen_system_port_fallback, (flags & session::listen_no_system_port) == 0); + apply_settings(std::move(p)); + } +#endif + + void session_handle::remove_torrent(const torrent_handle& h, remove_flags_t const options) + { + if (!h.is_valid()) +#ifdef BOOST_NO_EXCEPTIONS + return; +#else + throw_invalid_handle(); +#endif + async_call(&session_impl::remove_torrent, h, options); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::set_pe_settings(pe_settings const& r) + { + settings_pack p; + p.set_bool(settings_pack::prefer_rc4, r.prefer_rc4); + p.set_int(settings_pack::out_enc_policy, r.out_enc_policy); + p.set_int(settings_pack::in_enc_policy, r.in_enc_policy); + p.set_int(settings_pack::allowed_enc_level, r.allowed_enc_level); + + apply_settings(std::move(p)); + } + + pe_settings session_handle::get_pe_settings() const + { + settings_pack sett = get_settings(); + + pe_settings r; + r.prefer_rc4 = sett.get_bool(settings_pack::prefer_rc4); + r.out_enc_policy = std::uint8_t(sett.get_int(settings_pack::out_enc_policy)); + r.in_enc_policy = std::uint8_t(sett.get_int(settings_pack::in_enc_policy)); + r.allowed_enc_level = std::uint8_t(sett.get_int(settings_pack::allowed_enc_level)); + return r; + } +#endif + + void session_handle::apply_settings(settings_pack const& s) + { + TORRENT_ASSERT_PRECOND(!s.has_val(settings_pack::out_enc_policy) + || s.get_int(settings_pack::out_enc_policy) + <= settings_pack::pe_disabled); + TORRENT_ASSERT_PRECOND(!s.has_val(settings_pack::in_enc_policy) + || s.get_int(settings_pack::in_enc_policy) + <= settings_pack::pe_disabled); + TORRENT_ASSERT_PRECOND(!s.has_val(settings_pack::allowed_enc_level) + || s.get_int(settings_pack::allowed_enc_level) + <= settings_pack::pe_both); + + auto copy = std::make_shared(s); + async_call(&session_impl::apply_settings_pack, copy); + } + + void session_handle::apply_settings(settings_pack&& s) + { + TORRENT_ASSERT_PRECOND(!s.has_val(settings_pack::out_enc_policy) + || s.get_int(settings_pack::out_enc_policy) + <= settings_pack::pe_disabled); + TORRENT_ASSERT_PRECOND(!s.has_val(settings_pack::in_enc_policy) + || s.get_int(settings_pack::in_enc_policy) + <= settings_pack::pe_disabled); + TORRENT_ASSERT_PRECOND(!s.has_val(settings_pack::allowed_enc_level) + || s.get_int(settings_pack::allowed_enc_level) + <= settings_pack::pe_both); + + auto copy = std::make_shared(std::move(s)); + async_call(&session_impl::apply_settings_pack, copy); + } + + settings_pack session_handle::get_settings() const + { + return sync_call_ret(&session_impl::get_settings); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::set_i2p_proxy(proxy_settings const& s) + { + settings_pack pack; + pack.set_str(settings_pack::i2p_hostname, s.hostname); + pack.set_int(settings_pack::i2p_port, s.port); + + apply_settings(pack); + } + + proxy_settings session_handle::i2p_proxy() const + { + proxy_settings ret; + settings_pack sett = get_settings(); + ret.hostname = sett.get_str(settings_pack::i2p_hostname); + ret.port = std::uint16_t(sett.get_int(settings_pack::i2p_port)); + return ret; + } + + void session_handle::set_proxy(proxy_settings const& s) + { + settings_pack p; + p.set_str(settings_pack::proxy_hostname, s.hostname); + p.set_str(settings_pack::proxy_username, s.username); + p.set_str(settings_pack::proxy_password, s.password); + p.set_int(settings_pack::proxy_type, s.type); + p.set_int(settings_pack::proxy_port, s.port); + p.set_bool(settings_pack::proxy_hostnames,s.proxy_hostnames); + p.set_bool(settings_pack::proxy_peer_connections, s.proxy_peer_connections); + + apply_settings(std::move(p)); + } + + proxy_settings session_handle::proxy() const + { + settings_pack sett = get_settings(); + return proxy_settings(sett); + } + + int session_handle::num_uploads() const + { + return sync_call_ret(&session_impl::num_uploads); + } + + int session_handle::num_connections() const + { + return sync_call_ret(&session_impl::num_connections); + } + + void session_handle::set_peer_proxy(proxy_settings const& s) + { + set_proxy(s); + } + + void session_handle::set_web_seed_proxy(proxy_settings const&) + { + // NO-OP + } + + void session_handle::set_tracker_proxy(proxy_settings const& s) + { + // if the tracker proxy is enabled, set the "proxy_tracker_connections" + // setting + settings_pack pack; + pack.set_bool(settings_pack::proxy_tracker_connections + , s.type != settings_pack::none); + apply_settings(pack); + } + + proxy_settings session_handle::peer_proxy() const + { + return proxy(); + } + + proxy_settings session_handle::web_seed_proxy() const + { + return proxy(); + } + + proxy_settings session_handle::tracker_proxy() const + { + settings_pack const sett = get_settings(); + return sett.get_bool(settings_pack::proxy_tracker_connections) + ? proxy_settings(sett) : proxy_settings(); + } + + void session_handle::set_dht_proxy(proxy_settings const&) + { + // NO-OP + } + + proxy_settings session_handle::dht_proxy() const + { + return proxy(); + } + + int session_handle::upload_rate_limit() const + { + return sync_call_ret(&session_impl::upload_rate_limit_depr); + } + + int session_handle::download_rate_limit() const + { + return sync_call_ret(&session_impl::download_rate_limit_depr); + } + + int session_handle::local_upload_rate_limit() const + { + return sync_call_ret(&session_impl::local_upload_rate_limit); + } + + int session_handle::local_download_rate_limit() const + { + return sync_call_ret(&session_impl::local_download_rate_limit); + } + + int session_handle::max_half_open_connections() const { return 8; } + + void session_handle::set_local_upload_rate_limit(int bytes_per_second) + { + async_call(&session_impl::set_local_upload_rate_limit, bytes_per_second); + } + + void session_handle::set_local_download_rate_limit(int bytes_per_second) + { + async_call(&session_impl::set_local_download_rate_limit, bytes_per_second); + } + + void session_handle::set_upload_rate_limit(int bytes_per_second) + { + async_call(&session_impl::set_upload_rate_limit_depr, bytes_per_second); + } + + void session_handle::set_download_rate_limit(int bytes_per_second) + { + async_call(&session_impl::set_download_rate_limit_depr, bytes_per_second); + } + + void session_handle::set_max_connections(int limit) + { + async_call(&session_impl::set_max_connections, limit); + } + + void session_handle::set_max_uploads(int limit) + { + async_call(&session_impl::set_max_uploads, limit); + } + + void session_handle::set_max_half_open_connections(int) {} + + int session_handle::max_uploads() const + { + return sync_call_ret(&session_impl::max_uploads); + } + + int session_handle::max_connections() const + { + return sync_call_ret(&session_impl::max_connections); + } + +#endif // TORRENT_ABI_VERSION + + // the alerts are const, they may not be deleted by the client + void session_handle::pop_alerts(std::vector* alerts) + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + s->pop_alerts(alerts); + } + + alert* session_handle::wait_for_alert(time_duration max_wait) + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + return s->wait_for_alert(max_wait); + } + + void session_handle::set_alert_notify(std::function const& fun) + { + std::shared_ptr s = m_impl.lock(); + if (!s) aux::throw_ex(errors::invalid_session_handle); + s->alerts().set_notify_function(fun); + } + +#if TORRENT_ABI_VERSION == 1 + void session_handle::set_severity_level(alert::severity_t s) + { + alert_category_t m = {}; + switch (s) + { + case alert::debug: m = alert_category::all; break; + case alert::info: m = alert_category::all & ~(alert::debug_notification + | alert::progress_notification | alert_category::dht); break; + case alert::warning: m = alert_category::all & ~(alert::debug_notification + | alert_category::status | alert::progress_notification + | alert_category::dht); break; + case alert::critical: m = alert_category::error | alert_category::storage; break; + case alert::fatal: m = alert_category::error; break; + case alert::none: m = {}; break; + } + + settings_pack p; + p.set_int(settings_pack::alert_mask, m); + apply_settings(std::move(p)); + } + + size_t session_handle::set_alert_queue_size_limit(size_t queue_size_limit_) + { + return sync_call_ret(&session_impl::set_alert_queue_size_limit, queue_size_limit_); + } + + void session_handle::set_alert_mask(std::uint32_t m) + { + settings_pack p; + p.set_int(settings_pack::alert_mask, int(m)); + apply_settings(std::move(p)); + } + + std::uint32_t session_handle::get_alert_mask() const + { + return std::uint32_t(get_settings().get_int(settings_pack::alert_mask)); + } + + void session_handle::start_lsd() + { + settings_pack p; + p.set_bool(settings_pack::enable_lsd, true); + apply_settings(std::move(p)); + } + + void session_handle::stop_lsd() + { + settings_pack p; + p.set_bool(settings_pack::enable_lsd, false); + apply_settings(std::move(p)); + } + + void session_handle::start_upnp() + { + settings_pack p; + p.set_bool(settings_pack::enable_upnp, true); + apply_settings(std::move(p)); + } + + void session_handle::stop_upnp() + { + settings_pack p; + p.set_bool(settings_pack::enable_upnp, false); + apply_settings(std::move(p)); + } + + void session_handle::start_natpmp() + { + settings_pack p; + p.set_bool(settings_pack::enable_natpmp, true); + apply_settings(std::move(p)); + } + + void session_handle::stop_natpmp() + { + settings_pack p; + p.set_bool(settings_pack::enable_natpmp, false); + apply_settings(std::move(p)); + } +#endif // TORRENT_ABI_VERSION + + std::vector session_handle::add_port_mapping(portmap_protocol const t + , int external_port, int local_port) + { + return sync_call_ret>(&session_impl::add_port_mapping, t, external_port, local_port); + } + + void session_handle::delete_port_mapping(port_mapping_t handle) + { + async_call(&session_impl::delete_port_mapping, handle); + } + + void session_handle::reopen_network_sockets(reopen_network_flags_t const options) + { + async_call(&session_impl::reopen_network_sockets, options); + } + +} // namespace libtorrent diff --git a/src/session_impl.cpp b/src/session_impl.cpp new file mode 100644 index 0000000..487354f --- /dev/null +++ b/src/session_impl.cpp @@ -0,0 +1,7276 @@ +/* + +Copyright (c) 2006-2018, Arvid Norberg, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include +#include +#include +#include // for snprintf +#include // for PRId64 et.al. +#include +#include +#include // for accumulate + +#if TORRENT_USE_INVARIANT_CHECKS +#include +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/aux_/openssl.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/fingerprint.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#ifndef TORRENT_DISABLE_DHT +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/kademlia/types.hpp" +#include "libtorrent/kademlia/node_entry.hpp" +#endif +#include "libtorrent/enum_net.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/natpmp.hpp" +#include "libtorrent/lsd.hpp" +#include "libtorrent/aux_/instantiate_connection.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/choker.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/platform_util.hpp" +#include "libtorrent/aux_/bind_to_device.hpp" +#include "libtorrent/hex.hpp" // to_hex, from_hex +#include "libtorrent/aux_/scope_end.hpp" +#include "libtorrent/aux_/set_socket_buffer.hpp" +#include "libtorrent/aux_/generate_peer_id.hpp" +#include "libtorrent/aux_/ffs.hpp" + +#ifndef TORRENT_DISABLE_LOGGING + +#include "libtorrent/socket_io.hpp" + +// for logging stat layout +#include "libtorrent/stat.hpp" + +#include // for va_list + +// for logging the size of DHT structures +#ifndef TORRENT_DISABLE_DHT +#include +#include +#include +#include +#include +#endif // TORRENT_DISABLE_DHT + +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/udp_tracker_connection.hpp" + +#endif // TORRENT_DISABLE_LOGGING + +#ifdef TORRENT_USE_LIBGCRYPT + +#if GCRYPT_VERSION_NUMBER < 0x010600 +extern "C" { +GCRY_THREAD_OPTION_PTHREAD_IMPL; +} +#endif + +namespace { + + // libgcrypt requires this to initialize the library + struct gcrypt_setup + { + gcrypt_setup() + { + gcry_check_version(nullptr); +#if GCRYPT_VERSION_NUMBER < 0x010600 + gcry_error_t e = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); + if (e != 0) std::fprintf(stderr, "libcrypt ERROR: %s\n", gcry_strerror(e)); + e = gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + if (e != 0) std::fprintf(stderr, "initialization finished error: %s\n", gcry_strerror(e)); +#endif + } + } gcrypt_global_constructor; +} + +#endif // TORRENT_USE_LIBGCRYPT + +#ifdef TORRENT_USE_OPENSSL + +#include +#include + +// by openssl changelog at https://www.openssl.org/news/changelog.html +// Changes between 1.0.2h and 1.1.0 [25 Aug 2016] +// - Most global cleanup functions are no longer required because they are handled +// via auto-deinit. Affected function CRYPTO_cleanup_all_ex_data() +#if !defined(OPENSSL_API_COMPAT) || OPENSSL_API_COMPAT < 0x10100000L +namespace { + + // openssl requires this to clean up internal + // structures it allocates + struct openssl_cleanup + { +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + ~openssl_cleanup() { CRYPTO_cleanup_all_ex_data(); } +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif + } openssl_global_destructor; +} +#endif + +#endif // TORRENT_USE_OPENSSL + +#ifdef TORRENT_WINDOWS +// for ERROR_SEM_TIMEOUT +#include +#endif + +using namespace std::placeholders; + +#ifdef BOOST_NO_EXCEPTIONS +namespace boost { + + void throw_exception(std::exception const& e) { std::abort(); } +} +#endif + +namespace libtorrent { + +#if defined TORRENT_ASIO_DEBUGGING + std::map _async_ops; + std::deque _wakeups; + int _async_ops_nthreads = 0; + std::mutex _async_ops_mutex; +#endif + +namespace aux { + + constexpr listen_socket_flags_t listen_socket_t::accept_incoming; + constexpr listen_socket_flags_t listen_socket_t::local_network; + constexpr listen_socket_flags_t listen_socket_t::was_expanded; + constexpr listen_socket_flags_t listen_socket_t::proxy; + + constexpr ip_source_t session_interface::source_dht; + constexpr ip_source_t session_interface::source_peer; + constexpr ip_source_t session_interface::source_tracker; + constexpr ip_source_t session_interface::source_router; + + std::vector>::iterator partition_listen_sockets( + std::vector& eps + , std::vector>& sockets) + { + return std::partition(sockets.begin(), sockets.end() + , [&eps](std::shared_ptr const& sock) + { + auto match = std::find_if(eps.begin(), eps.end() + , [&sock](listen_endpoint_t const& ep) + { + return ep.ssl == sock->ssl + && ep.port == sock->original_port + && ep.device == sock->device + && ep.flags == sock->flags + && ep.addr == sock->local_endpoint.address(); + }); + + if (match != eps.end()) + { + // remove the matched endpoint so that another socket can't match it + // this also signals to the caller that it doesn't need to create a + // socket for the endpoint + eps.erase(match); + return true; + } + else + { + return false; + } + }); + } + + // To comply with BEP 45 multi homed clients must run separate DHT nodes + // on each interface they use to talk to the DHT. This is enforced + // by prohibiting creating a listen socket on [::] and 0.0.0.0. Instead the list of + // interfaces is enumerated and sockets are created for each of them. + void expand_unspecified_address(span const ifs + , span const routes + , std::vector& eps) + { + auto unspecified_begin = std::partition(eps.begin(), eps.end() + , [](listen_endpoint_t const& ep) { return !ep.addr.is_unspecified(); }); + std::vector unspecified_eps(unspecified_begin, eps.end()); + eps.erase(unspecified_begin, eps.end()); + for (auto const& uep : unspecified_eps) + { + bool const v4 = uep.addr.is_v4(); + for (auto const& ipface : ifs) + { + if (!ipface.preferred) + continue; + if (ipface.interface_address.is_v4() != v4) + continue; + if (!uep.device.empty() && uep.device != ipface.name) + continue; + if (std::any_of(eps.begin(), eps.end(), [&](listen_endpoint_t const& e) + { + // ignore device name because we don't want to create + // duplicates if the user explicitly configured an address + // without a device name + return e.addr == ipface.interface_address + && e.port == uep.port + && e.ssl == uep.ssl; + })) + { + continue; + } + + // ignore interfaces that are down + if (ipface.state != if_state::up && ipface.state != if_state::unknown) + continue; + if (!(ipface.flags & if_flags::up)) + continue; + + // we assume this listen_socket_t is local-network under some + // conditions, meaning we won't announce it to internet trackers + bool const local + = ipface.interface_address.is_loopback() + || is_link_local(ipface.interface_address) + || (ipface.flags & if_flags::loopback) + || (!is_global(ipface.interface_address) + && !(ipface.flags & if_flags::pointopoint) + && !has_internet_route(ipface.name, family(ipface.interface_address), routes)); + + eps.emplace_back(ipface.interface_address, uep.port, uep.device + , uep.ssl, uep.flags | listen_socket_t::was_expanded + | (local ? listen_socket_t::local_network : listen_socket_flags_t{})); + } + } + } + + void expand_devices(span const ifs + , std::vector& eps) + { + for (auto& ep : eps) + { + auto const iface = ep.device.empty() + ? std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& ipface) + { + return match_addr_mask(ipface.interface_address, ep.addr, ipface.netmask); + }) + : std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& ipface) + { + return ipface.name == ep.device + && match_addr_mask(ipface.interface_address, ep.addr, ipface.netmask); + }); + + if (iface == ifs.end()) + { + // we can't find which device this is for, just assume we can't + // reach anything on it + ep.netmask = build_netmask(0, ep.addr.is_v4() ? AF_INET : AF_INET6); + continue; + } + + ep.netmask = iface->netmask; + ep.device = iface->name; + } + } + + bool listen_socket_t::can_route(address const& addr) const + { + // if this is a proxy, we assume it can reach everything + if (flags & proxy) return true; + + if (is_v4(local_endpoint) != addr.is_v4()) return false; + + if (local_endpoint.address().is_v6() + && local_endpoint.address().to_v6().scope_id() != addr.to_v6().scope_id()) + return false; + + if (local_endpoint.address() == addr) return true; + if (local_endpoint.address().is_unspecified()) return true; + if (match_addr_mask(addr, local_endpoint.address(), netmask)) return true; + return !(flags & local_network); + } + + void session_impl::init_peer_class_filter(bool unlimited_local) + { + // set the default peer_class_filter to use the local peer class + // for peers on local networks + std::uint32_t lfilter = 1 << static_cast(m_local_peer_class); + std::uint32_t gfilter = 1 << static_cast(m_global_class); + + struct class_mapping + { + char const* first; + char const* last; + std::uint32_t filter; + }; + + static const class_mapping v4_classes[] = + { + // everything + {"0.0.0.0", "255.255.255.255", gfilter}, + // local networks + {"10.0.0.0", "10.255.255.255", lfilter}, + {"172.16.0.0", "172.31.255.255", lfilter}, + {"192.168.0.0", "192.168.255.255", lfilter}, + // link-local + {"169.254.0.0", "169.254.255.255", lfilter}, + // loop-back + {"127.0.0.0", "127.255.255.255", lfilter}, + }; + + static const class_mapping v6_classes[] = + { + // everything + {"::0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", gfilter}, + // local networks + {"fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", lfilter}, + // link-local + {"fe80::", "febf::ffff:ffff:ffff:ffff:ffff:ffff:ffff", lfilter}, + // loop-back + {"::1", "::1", lfilter}, + }; + + class_mapping const* p = v4_classes; + int len = sizeof(v4_classes) / sizeof(v4_classes[0]); + if (!unlimited_local) len = 1; + for (int i = 0; i < len; ++i) + { + error_code ec; + address_v4 begin = make_address_v4(p[i].first, ec); + address_v4 end = make_address_v4(p[i].last, ec); + if (ec) continue; + m_peer_class_filter.add_rule(begin, end, p[i].filter); + } + p = v6_classes; + len = sizeof(v6_classes) / sizeof(v6_classes[0]); + if (!unlimited_local) len = 1; + for (int i = 0; i < len; ++i) + { + error_code ec; + address_v6 begin = make_address_v6(p[i].first, ec); + address_v6 end = make_address_v6(p[i].last, ec); + if (ec) continue; + m_peer_class_filter.add_rule(begin, end, p[i].filter); + } + } + +#if defined TORRENT_USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x90812f +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + namespace { + // when running bittorrent over SSL, the SNI (server name indication) + // extension is used to know which torrent the incoming connection is + // trying to connect to. The 40 first bytes in the name is expected to + // be the hex encoded info-hash + int servername_callback(SSL* s, int*, void* arg) + { + auto* ses = reinterpret_cast(arg); + const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); + + if (!servername || std::strlen(servername) < 40) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + sha1_hash info_hash; + bool valid = aux::from_hex({servername, 40}, info_hash.data()); + + // the server name is not a valid hex-encoded info-hash + if (!valid) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + // see if there is a torrent with this info-hash + std::shared_ptr t = ses->find_torrent(info_hash).lock(); + + // if there isn't, fail + if (!t) return SSL_TLSEXT_ERR_ALERT_FATAL; + + // if the torrent we found isn't an SSL torrent, also fail. + if (!t->is_ssl_torrent()) return SSL_TLSEXT_ERR_ALERT_FATAL; + + // if the torrent doesn't have an SSL context and should not allow + // incoming SSL connections + if (!t->ssl_ctx()) return SSL_TLSEXT_ERR_ALERT_FATAL; + + // use this torrent's certificate + SSL_CTX *torrent_context = t->ssl_ctx()->native_handle(); + + SSL_set_SSL_CTX(s, torrent_context); + SSL_set_verify(s, SSL_CTX_get_verify_mode(torrent_context) + , SSL_CTX_get_verify_callback(torrent_context)); + + return SSL_TLSEXT_ERR_OK; + } + } // anonymous namespace +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif +#endif + + session_impl::session_impl(io_service& ios, settings_pack const& pack) + : m_settings(pack) + , m_io_service(ios) +#ifdef TORRENT_USE_OPENSSL +#if BOOST_VERSION >= 106400 + , m_ssl_ctx(ssl::context::tls_client) + , m_peer_ssl_ctx(ssl::context::tls) +#else + , m_ssl_ctx(ssl::context::tlsv12_client) + , m_peer_ssl_ctx(ssl::context::tlsv12) +#endif +#endif + , m_alerts(m_settings.get_int(settings_pack::alert_queue_size) + , alert_category_t{static_cast(m_settings.get_int(settings_pack::alert_mask))}) + , m_disk_thread(m_io_service, m_settings, m_stats_counters) + , m_download_rate(peer_connection::download_channel) + , m_upload_rate(peer_connection::upload_channel) + , m_host_resolver(m_io_service) + , m_tracker_manager( + std::bind(&session_impl::send_udp_packet_listen, this, _1, _2, _3, _4, _5) + , std::bind(&session_impl::send_udp_packet_hostname_listen, this, _1, _2, _3, _4, _5, _6) + , m_stats_counters + , m_host_resolver + , m_settings +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , *this +#endif + ) + , m_work(new io_service::work(m_io_service)) +#if TORRENT_USE_I2P + , m_i2p_conn(m_io_service) +#endif + , m_created(clock_type::now()) + , m_last_tick(m_created) + , m_last_second_tick(m_created - milliseconds(900)) + , m_last_choke(m_created) + , m_last_auto_manage(m_created) +#ifndef TORRENT_DISABLE_DHT + , m_dht_announce_timer(m_io_service) +#endif + , m_utp_socket_manager( + std::bind(&session_impl::send_udp_packet, this, _1, _2, _3, _4, _5) + , std::bind(&session_impl::incoming_connection, this, _1) + , m_io_service + , m_settings, m_stats_counters, nullptr) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_utp_socket_manager( + std::bind(&session_impl::send_udp_packet, this, _1, _2, _3, _4, _5) + , std::bind(&session_impl::on_incoming_utp_ssl, this, _1) + , m_io_service + , m_settings, m_stats_counters + , &m_peer_ssl_ctx) +#endif + , m_timer(m_io_service) + , m_lsd_announce_timer(m_io_service) + , m_close_file_timer(m_io_service) + { + } + + template + void session_impl::wrap(Fun f, Args&&... a) +#ifndef BOOST_NO_EXCEPTIONS + try +#endif + { + (this->*f)(std::forward(a)...); + } +#ifndef BOOST_NO_EXCEPTIONS + catch (system_error const& e) { + alerts().emplace_alert(e.code(), e.what()); + pause(); + } catch (std::exception const& e) { + alerts().emplace_alert(error_code(), e.what()); + pause(); + } catch (...) { + alerts().emplace_alert(error_code(), "unknown error"); + pause(); + } +#endif + + // This function is called by the creating thread, not in the message loop's + // io_service thread. + // TODO: 2 is there a reason not to move all of this into init()? and just + // post it to the io_service? + void session_impl::start_session() + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("start session"); +#endif + +#ifdef TORRENT_USE_OPENSSL + error_code ec; + m_ssl_ctx.set_verify_mode(boost::asio::ssl::context::verify_none, ec); + m_ssl_ctx.set_default_verify_paths(ec); + m_peer_ssl_ctx.set_verify_mode(boost::asio::ssl::context::verify_none, ec); +#if OPENSSL_VERSION_NUMBER >= 0x90812f + aux::openssl_set_tlsext_servername_callback(m_peer_ssl_ctx.native_handle() + , servername_callback); + aux::openssl_set_tlsext_servername_arg(m_peer_ssl_ctx.native_handle(), this); +#endif // OPENSSL_VERSION_NUMBER +#endif + +#ifndef TORRENT_DISABLE_DHT + m_next_dht_torrent = m_torrents.begin(); +#endif + m_next_lsd_torrent = m_torrents.begin(); + + m_global_class = m_classes.new_peer_class("global"); + m_tcp_peer_class = m_classes.new_peer_class("tcp"); + m_local_peer_class = m_classes.new_peer_class("local"); + // local peers are always unchoked + m_classes.at(m_local_peer_class)->ignore_unchoke_slots = true; + // local peers are allowed to exceed the normal connection + // limit by 50% + m_classes.at(m_local_peer_class)->connection_limit_factor = 150; + + TORRENT_ASSERT(m_global_class == session::global_peer_class_id); + TORRENT_ASSERT(m_tcp_peer_class == session::tcp_peer_class_id); + TORRENT_ASSERT(m_local_peer_class == session::local_peer_class_id); + + init_peer_class_filter(true); + + // TCP, SSL/TCP and I2P connections should be assigned the TCP peer class + m_peer_class_type_filter.add(peer_class_type_filter::tcp_socket, m_tcp_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::ssl_tcp_socket, m_tcp_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::i2p_socket, m_tcp_peer_class); + +#ifndef TORRENT_DISABLE_LOGGING + + session_log("version: %s revision: %s" + , LIBTORRENT_VERSION, LIBTORRENT_REVISION); + +#endif // TORRENT_DISABLE_LOGGING + + // ---- auto-cap max connections ---- + int const max_files = max_open_files(); + // deduct some margin for epoll/kqueue, log files, + // futexes, shared objects etc. + // 80% of the available file descriptors should go to connections + m_settings.set_int(settings_pack::connections_limit, std::min( + m_settings.get_int(settings_pack::connections_limit) + , std::max(5, (max_files - 20) * 8 / 10))); + // 20% goes towards regular files (see disk_io_thread) +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("max-connections: %d max-files: %d" + , m_settings.get_int(settings_pack::connections_limit) + , max_files); + } +#endif + + m_io_service.post([this] { this->wrap(&session_impl::init); }); + } + + void session_impl::init() + { + // this is a debug facility + // see single_threaded in debug.hpp + thread_started(); + + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" *** session thread init"); +#endif + + // this is where we should set up all async operations. This + // is called from within the network thread as opposed to the + // constructor which is called from the main thread + +#if defined TORRENT_ASIO_DEBUGGING + async_inc_threads(); + add_outstanding_async("session_impl::on_tick"); +#endif + m_io_service.post([this]{ this->wrap(&session_impl::on_tick, error_code()); }); + + int const lsd_announce_interval + = m_settings.get_int(settings_pack::local_service_announce_interval); + int const delay = std::max(lsd_announce_interval + / std::max(static_cast(m_torrents.size()), 1), 1); + error_code ec; + m_lsd_announce_timer.expires_from_now(seconds(delay), ec); + ADD_OUTSTANDING_ASYNC("session_impl::on_lsd_announce"); + m_lsd_announce_timer.async_wait([this](error_code const& e) { + this->wrap(&session_impl::on_lsd_announce, e); } ); + TORRENT_ASSERT(!ec); + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" done starting session"); +#endif + + // this applies unchoke settings from m_settings + recalculate_unchoke_slots(); + + // apply all m_settings to this session + run_all_updates(*this); + reopen_listen_sockets(false); + +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } + + // TODO: 2 the ip filter should probably be saved here too + void session_impl::save_state(entry* eptr, save_state_flags_t const flags) const + { + TORRENT_ASSERT(is_single_thread()); + + entry& e = *eptr; + // make it a dict + e.dict(); + + if (flags & session::save_settings) + { + entry::dictionary_type& sett = e["settings"].dict(); + save_settings_to_dict(m_settings, sett); + } + +#ifndef TORRENT_DISABLE_DHT + if (flags & session::save_dht_settings) + { + e["dht"] = dht::save_dht_settings(m_dht_settings); + } + + if (m_dht && (flags & session::save_dht_state)) + { + e["dht state"] = dht::save_dht_state(m_dht->state()); + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& ext : m_ses_extensions[plugins_all_idx]) + { + ext->save_state(*eptr); + } +#endif + } + + proxy_settings session_impl::proxy() const + { + return proxy_settings(m_settings); + } + + void session_impl::load_state(bdecode_node const* e + , save_state_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + + bdecode_node settings; + if (e->type() != bdecode_node::dict_t) return; + +#ifndef TORRENT_DISABLE_DHT + bool need_update_dht = false; + if (flags & session_handle::save_dht_settings) + { + settings = e->dict_find_dict("dht"); + if (settings) + { + static_cast(m_dht_settings) = dht::read_dht_settings(settings); + } + } + + if (flags & session_handle::save_dht_state) + { + settings = e->dict_find_dict("dht state"); + if (settings) + { + m_dht_state = dht::read_dht_state(settings); + need_update_dht = true; + } + } +#endif + +#if TORRENT_ABI_VERSION == 1 + bool need_update_proxy = false; + if (flags & session_handle::save_proxy) + { + settings = e->dict_find_dict("proxy"); + if (settings) + { + m_settings.bulk_set([&settings](session_settings_single_thread& s) + { + bdecode_node val; + val = settings.dict_find_int("port"); + if (val) s.set_int(settings_pack::proxy_port, int(val.int_value())); + val = settings.dict_find_int("type"); + if (val) s.set_int(settings_pack::proxy_type, int(val.int_value())); + val = settings.dict_find_int("proxy_hostnames"); + if (val) s.set_bool(settings_pack::proxy_hostnames, val.int_value() != 0); + val = settings.dict_find_int("proxy_peer_connections"); + if (val) s.set_bool(settings_pack::proxy_peer_connections, val.int_value() != 0); + val = settings.dict_find_string("hostname"); + if (val) s.set_str(settings_pack::proxy_hostname, val.string_value().to_string()); + val = settings.dict_find_string("password"); + if (val) s.set_str(settings_pack::proxy_password, val.string_value().to_string()); + val = settings.dict_find_string("username"); + if (val) s.set_str(settings_pack::proxy_username, val.string_value().to_string()); + }); + need_update_proxy = true; + } + } + + settings = e->dict_find_dict("encryption"); + if (settings) + { + m_settings.bulk_set([&settings](session_settings_single_thread& s) + { + bdecode_node val; + val = settings.dict_find_int("prefer_rc4"); + if (val) s.set_bool(settings_pack::prefer_rc4, val.int_value() != 0); + val = settings.dict_find_int("out_enc_policy"); + if (val) s.set_int(settings_pack::out_enc_policy, int(val.int_value())); + val = settings.dict_find_int("in_enc_policy"); + if (val) s.set_int(settings_pack::in_enc_policy, int(val.int_value())); + val = settings.dict_find_int("allowed_enc_level"); + if (val) s.set_int(settings_pack::allowed_enc_level, int(val.int_value())); + }); + } +#endif + + if (flags & session_handle::save_settings) + { + settings = e->dict_find_dict("settings"); + if (settings) + { + // apply_settings_pack will update dht and proxy + settings_pack pack = load_pack_from_dict(settings); + + // these settings are not loaded from state + // they are set by the client software, not configured by users + pack.clear(settings_pack::user_agent); + pack.clear(settings_pack::peer_fingerprint); + + apply_settings_pack_impl(pack); +#ifndef TORRENT_DISABLE_DHT + need_update_dht = false; +#endif +#if TORRENT_ABI_VERSION == 1 + need_update_proxy = false; +#endif + } + } + +#ifndef TORRENT_DISABLE_DHT + if (need_update_dht) start_dht(); +#endif +#if TORRENT_ABI_VERSION == 1 + if (need_update_proxy) update_proxy(); +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_ses_extensions[plugins_all_idx]) + { + ext->load_state(*e); + } +#endif + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + + void session_impl::add_extension(ext_function_t ext) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(ext); + + add_ses_extension(std::make_shared(ext)); + } + + void session_impl::add_ses_extension(std::shared_ptr ext) + { + // this is called during startup of the session, from the thread creating + // it, not its own thread +// TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT_VAL(ext, ext); + + feature_flags_t const features = ext->implemented_features(); + + m_ses_extensions[plugins_all_idx].push_back(ext); + + if (features & plugin::optimistic_unchoke_feature) + m_ses_extensions[plugins_optimistic_unchoke_idx].push_back(ext); + if (features & plugin::tick_feature) + m_ses_extensions[plugins_tick_idx].push_back(ext); + if (features & plugin::dht_request_feature) + m_ses_extensions[plugins_dht_request_idx].push_back(ext); + if (features & plugin::alert_feature) + m_alerts.add_extension(ext); + session_handle h(shared_from_this()); + ext->added(h); + } + +#endif // TORRENT_DISABLE_EXTENSIONS + + void session_impl::pause() + { + TORRENT_ASSERT(is_single_thread()); + + if (m_paused) return; +#ifndef TORRENT_DISABLE_LOGGING + session_log(" *** session paused ***"); +#endif + // this will abort all tracker announces other than event=stopped + m_tracker_manager.abort_all_requests(); + + m_paused = true; + for (auto& te : m_torrents) + { + te.second->set_session_paused(true); + } + } + + void session_impl::resume() + { + TORRENT_ASSERT(is_single_thread()); + + if (!m_paused) return; + m_paused = false; + + for (auto& te : m_torrents) + { + te.second->set_session_paused(false); + } + } + + void session_impl::abort() noexcept + { + TORRENT_ASSERT(is_single_thread()); + + if (m_abort) return; +#ifndef TORRENT_DISABLE_LOGGING + session_log(" *** ABORT CALLED ***"); +#endif + + // at this point we cannot call the notify function anymore, since the + // session will become invalid. + m_alerts.set_notify_function({}); + + // this will cancel requests that are not critical for shutting down + // cleanly. i.e. essentially tracker hostname lookups that we're not + // about to send event=stopped to + m_host_resolver.abort(); + + m_close_file_timer.cancel(); + + // abort the main thread + m_abort = true; + error_code ec; + +#if TORRENT_USE_I2P + m_i2p_conn.close(ec); +#endif + stop_ip_notifier(); + stop_lsd(); + stop_upnp(); + stop_natpmp(); +#ifndef TORRENT_DISABLE_DHT + stop_dht(); + m_dht_announce_timer.cancel(ec); +#endif + m_lsd_announce_timer.cancel(ec); + + for (auto const& s : m_incoming_sockets) + { + s->close(ec); + TORRENT_ASSERT(!ec); + } + m_incoming_sockets.clear(); + +#if TORRENT_USE_I2P + if (m_i2p_listen_socket && m_i2p_listen_socket->is_open()) + { + m_i2p_listen_socket->close(ec); + TORRENT_ASSERT(!ec); + } + m_i2p_listen_socket.reset(); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" aborting all torrents (%d)", int(m_torrents.size())); +#endif + // abort all torrents + for (auto const& te : m_torrents) + { + te.second->abort(); + } + m_torrents.clear(); + m_stats_counters.set_value(counters::num_peers_up_unchoked_all, 0); + m_stats_counters.set_value(counters::num_peers_up_unchoked, 0); + m_stats_counters.set_value(counters::num_peers_up_unchoked_optimistic, 0); + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" aborting all tracker requests"); +#endif + m_tracker_manager.abort_all_requests(); + +#ifndef TORRENT_DISABLE_LOGGING + session_log(" aborting all connections (%d)", int(m_connections.size())); +#endif + // abort all connections + for (auto i = m_connections.begin(); i != m_connections.end();) + { + peer_connection* p = (*i).get(); + ++i; + p->disconnect(errors::stopping_torrent, operation_t::bittorrent); + } + + // close the listen sockets + for (auto const& l : m_listen_sockets) + { + if (l->sock) + { + l->sock->close(ec); + TORRENT_ASSERT(!ec); + } + + // TODO: 3 closing the udp sockets here means that + // the uTP connections cannot be closed gracefully + if (l->udp_sock) + { + l->udp_sock->sock.close(); + } + } + + // we need to give all the sockets an opportunity to actually have their handlers + // called and cancelled before we continue the shutdown. This is a bit + // complicated, if there are no "undead" peers, it's safe to resume the + // shutdown, but if there are, we have to wait for them to be cleared out + // first. In session_impl::on_tick() we check them periodically. If we're + // shutting down and we remove the last one, we'll initiate + // shutdown_stage2 from there. + if (m_undead_peers.empty()) + { + m_io_service.post(make_handler([this] { abort_stage2(); } + , m_abort_handler_storage, *this)); + } + } + + void session_impl::abort_stage2() noexcept + { + m_download_rate.close(); + m_upload_rate.close(); + + // it's OK to detach the threads here. The disk_io_thread + // has an internal counter and won't release the network + // thread until they're all dead (via m_work). + m_disk_thread.abort(false); + + // now it's OK for the network thread to exit + m_work.reset(); + } + + bool session_impl::has_connection(peer_connection* p) const + { + return m_connections.find(p->self()) != m_connections.end(); + } + + void session_impl::insert_peer(std::shared_ptr const& c) + { + TORRENT_ASSERT(!c->m_in_constructor); + + // removing a peer may not throw an exception, so prepare for this + // connection to be added to the undead peers now. + m_undead_peers.reserve(m_undead_peers.size() + m_connections.size() + 1); + m_connections.insert(c); + + TORRENT_ASSERT_VAL(m_undead_peers.capacity() >= m_connections.size() + , m_undead_peers.capacity()); + } + + void session_impl::set_port_filter(port_filter const& f) + { + m_port_filter = f; + if (m_settings.get_bool(settings_pack::no_connect_privileged_ports)) + m_port_filter.add_rule(0, 1024, port_filter::blocked); + // Close connections whose endpoint is filtered + // by the new ip-filter + for (auto const& t : m_torrents) + t.second->port_filter_updated(); + } + + void session_impl::set_ip_filter(std::shared_ptr const& f) + { + INVARIANT_CHECK; + + m_ip_filter = f; + + // Close connections whose endpoint is filtered + // by the new ip-filter + for (auto& i : m_torrents) + i.second->set_ip_filter(m_ip_filter); + } + + void session_impl::ban_ip(address addr) + { + TORRENT_ASSERT(is_single_thread()); + if (!m_ip_filter) m_ip_filter = std::make_shared(); + m_ip_filter->add_rule(addr, addr, ip_filter::blocked); + for (auto& i : m_torrents) + i.second->set_ip_filter(m_ip_filter); + } + + ip_filter const& session_impl::get_ip_filter() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_ip_filter) m_ip_filter = std::make_shared(); + return *m_ip_filter; + } + + port_filter const& session_impl::get_port_filter() const + { + TORRENT_ASSERT(is_single_thread()); + return m_port_filter; + } + + peer_class_t session_impl::create_peer_class(char const* name) + { + TORRENT_ASSERT(is_single_thread()); + return m_classes.new_peer_class(name); + } + + void session_impl::delete_peer_class(peer_class_t const cid) + { + TORRENT_ASSERT(is_single_thread()); + // if you hit this assert, you're deleting a non-existent peer class + TORRENT_ASSERT_PRECOND(m_classes.at(cid)); + if (m_classes.at(cid) == nullptr) return; + m_classes.decref(cid); + } + + peer_class_info session_impl::get_peer_class(peer_class_t const cid) const + { + peer_class_info ret{}; + peer_class const* pc = m_classes.at(cid); + // if you hit this assert, you're passing in an invalid cid + TORRENT_ASSERT_PRECOND(pc); + if (pc == nullptr) + { +#if TORRENT_USE_INVARIANT_CHECKS + // make it obvious that the return value is undefined + ret.upload_limit = 0xf0f0f0f; + ret.download_limit = 0xf0f0f0f; + ret.label.resize(20); + url_random(span(ret.label)); + ret.ignore_unchoke_slots = false; + ret.connection_limit_factor = 0xf0f0f0f; + ret.upload_priority = 0xf0f0f0f; + ret.download_priority = 0xf0f0f0f; +#endif + return ret; + } + + pc->get_info(&ret); + return ret; + } + +namespace { + + std::uint16_t make_announce_port(std::uint16_t const p) + { return p == 0 ? 1 : p; } +} + + void session_impl::queue_tracker_request(tracker_request&& req + , std::weak_ptr c) + { + req.listen_port = 0; +#if TORRENT_USE_I2P + if (!m_settings.get_str(settings_pack::i2p_hostname).empty()) + { + req.i2pconn = &m_i2p_conn; + } +#endif + +#ifdef TORRENT_USE_OPENSSL + bool const use_ssl = req.ssl_ctx != nullptr && req.ssl_ctx != &m_ssl_ctx; + if (!use_ssl) req.ssl_ctx = &m_ssl_ctx; +#endif + + if (req.outgoing_socket) + { + auto ls = req.outgoing_socket.get(); + + req.listen_port = +#if TORRENT_USE_I2P + (req.kind == tracker_request::i2p) ? 1 : +#endif +#ifdef TORRENT_USE_OPENSSL + // SSL torrents use the SSL listen port + use_ssl ? make_announce_port(ssl_listen_port(ls)) : +#endif + make_announce_port(listen_port(ls)); + m_tracker_manager.queue_request(get_io_service(), std::move(req) + , m_settings, c); + } + else + { + for (auto& ls : m_listen_sockets) + { + if (!(ls->flags & listen_socket_t::accept_incoming)) continue; +#ifdef TORRENT_USE_OPENSSL + if ((ls->ssl == transport::ssl) != use_ssl) continue; +#endif + tracker_request socket_req(req); + socket_req.listen_port = +#if TORRENT_USE_I2P + (req.kind == tracker_request::i2p) ? 1 : +#endif +#ifdef TORRENT_USE_OPENSSL + // SSL torrents use the SSL listen port + use_ssl ? make_announce_port(ssl_listen_port(ls.get())) : +#endif + make_announce_port(listen_port(ls.get())); + + socket_req.outgoing_socket = ls; + m_tracker_manager.queue_request(get_io_service() + , std::move(socket_req), m_settings, c); + } + } + } + + void session_impl::set_peer_class(peer_class_t const cid, peer_class_info const& pci) + { + peer_class* pc = m_classes.at(cid); + // if you hit this assert, you're passing in an invalid cid + TORRENT_ASSERT_PRECOND(pc); + if (pc == nullptr) return; + + pc->set_info(&pci); + } + + void session_impl::set_peer_class_filter(ip_filter const& f) + { + INVARIANT_CHECK; + m_peer_class_filter = f; + } + + ip_filter const& session_impl::get_peer_class_filter() const + { + return m_peer_class_filter; + } + + void session_impl::set_peer_class_type_filter(peer_class_type_filter f) + { + m_peer_class_type_filter = f; + } + + peer_class_type_filter session_impl::get_peer_class_type_filter() + { + return m_peer_class_type_filter; + } + + void session_impl::set_peer_classes(peer_class_set* s, address const& a, int const st) + { + std::uint32_t peer_class_mask = m_peer_class_filter.access(a); + + using sock_t = peer_class_type_filter::socket_type_t; + // assign peer class based on socket type + static const sock_t mapping[] = { + sock_t::tcp_socket, sock_t::tcp_socket + , sock_t::tcp_socket, sock_t::tcp_socket + , sock_t::utp_socket, sock_t::i2p_socket + , sock_t::ssl_tcp_socket, sock_t::ssl_tcp_socket + , sock_t::ssl_tcp_socket, sock_t::ssl_utp_socket + }; + sock_t const socket_type = mapping[st]; + // filter peer classes based on type + peer_class_mask = m_peer_class_type_filter.apply(socket_type, peer_class_mask); + + for (peer_class_t i{0}; peer_class_mask; peer_class_mask >>= 1, ++i) + { + if ((peer_class_mask & 1) == 0) continue; + + // if you hit this assert, your peer class filter contains + // a bitmask referencing a non-existent peer class + TORRENT_ASSERT_PRECOND(m_classes.at(i)); + + if (m_classes.at(i) == nullptr) continue; + s->add_class(m_classes, i); + } + } + + bool session_impl::ignore_unchoke_slots_set(peer_class_set const& set) const + { + int num = set.num_classes(); + for (int i = 0; i < num; ++i) + { + peer_class const* pc = m_classes.at(set.class_at(i)); + if (pc == nullptr) continue; + if (pc->ignore_unchoke_slots) return true; + } + return false; + } + + bandwidth_manager* session_impl::get_bandwidth_manager(int channel) + { + return (channel == peer_connection::download_channel) + ? &m_download_rate : &m_upload_rate; + } + + void session_impl::deferred_submit_jobs() + { + if (m_deferred_submit_disk_jobs) return; + m_deferred_submit_disk_jobs = true; + m_io_service.post([this] { this->wrap(&session_impl::submit_disk_jobs); } ); + } + + void session_impl::submit_disk_jobs() + { + TORRENT_ASSERT(m_deferred_submit_disk_jobs); + m_deferred_submit_disk_jobs = false; + m_disk_thread.submit_jobs(); + } + + // copies pointers to bandwidth channels from the peer classes + // into the array. Only bandwidth channels with a bandwidth limit + // is considered pertinent and copied + // returns the number of pointers copied + // channel is upload_channel or download_channel + int session_impl::copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int const max) + { + int num_channels = set.num_classes(); + int num_copied = 0; + for (int i = 0; i < num_channels; ++i) + { + peer_class* pc = m_classes.at(set.class_at(i)); + TORRENT_ASSERT(pc); + if (pc == nullptr) continue; + bandwidth_channel* chan = &pc->channel[channel]; + // no need to include channels that don't have any bandwidth limits + if (chan->throttle() == 0) continue; + dst[num_copied] = chan; + ++num_copied; + if (num_copied == max) break; + } + return num_copied; + } + + bool session_impl::use_quota_overhead(bandwidth_channel* ch, int amount) + { + ch->use_quota(amount); + return (ch->throttle() > 0 && ch->throttle() < amount); + } + + int session_impl::use_quota_overhead(peer_class_set& set, int const amount_down, int const amount_up) + { + int ret = 0; + int const num = set.num_classes(); + for (int i = 0; i < num; ++i) + { + peer_class* p = m_classes.at(set.class_at(i)); + if (p == nullptr) continue; + + bandwidth_channel* ch = &p->channel[peer_connection::download_channel]; + if (use_quota_overhead(ch, amount_down)) + ret |= 1 << peer_connection::download_channel; + ch = &p->channel[peer_connection::upload_channel]; + if (use_quota_overhead(ch, amount_up)) + ret |= 1 << peer_connection::upload_channel; + } + return ret; + } + + // session_impl is responsible for deleting 'pack' + void session_impl::apply_settings_pack(std::shared_ptr pack) + { + INVARIANT_CHECK; + apply_settings_pack_impl(*pack); + } + + settings_pack session_impl::get_settings() const + { + settings_pack ret; + // TODO: it would be nice to reserve() these vectors up front + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) + { + ret.set_str(i, m_settings.get_str(i)); + } + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + ret.set_int(i, m_settings.get_int(i)); + } + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + ret.set_bool(i, m_settings.get_bool(i)); + } + return ret; + } + + void session_impl::apply_settings_pack_impl(settings_pack const& pack) + { + bool const reopen_listen_port = +#if TORRENT_ABI_VERSION == 1 + (pack.has_val(settings_pack::ssl_listen) + && pack.get_int(settings_pack::ssl_listen) + != m_settings.get_int(settings_pack::ssl_listen)) + || +#endif + (pack.has_val(settings_pack::listen_interfaces) + && pack.get_str(settings_pack::listen_interfaces) + != m_settings.get_str(settings_pack::listen_interfaces)) + || (pack.has_val(settings_pack::proxy_type) + && pack.get_int(settings_pack::proxy_type) + != m_settings.get_int(settings_pack::proxy_type)) + || (pack.has_val(settings_pack::proxy_peer_connections) + && pack.get_bool(settings_pack::proxy_peer_connections) + != m_settings.get_bool(settings_pack::proxy_peer_connections)) + ; + +#ifndef TORRENT_DISABLE_LOGGING + session_log("applying settings pack, reopen_listen_port=%s" + , reopen_listen_port ? "true" : "false"); +#endif + + apply_pack(&pack, m_settings, this); + m_disk_thread.settings_updated(); + + if (!reopen_listen_port) + { + // no need to call this if reopen_listen_port is true + // since the apply_pack will do it + update_listen_interfaces(); + } + + if (reopen_listen_port) + { + reopen_listen_sockets(); + } + } + + std::shared_ptr session_impl::setup_listener( + listen_endpoint_t const& lep, error_code& ec) + { + int retries = m_settings.get_int(settings_pack::max_retry_port_bind); + tcp::endpoint bind_ep(lep.addr, std::uint16_t(lep.port)); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("attempting to open listen socket to: %s on device: %s %s%s%s%s%s" + , print_endpoint(bind_ep).c_str(), lep.device.c_str() + , (lep.ssl == transport::ssl) ? "ssl " : "" + , (lep.flags & listen_socket_t::local_network) ? "local-network " : "" + , (lep.flags & listen_socket_t::accept_incoming) ? "accept-incoming " : "no-incoming " + , (lep.flags & listen_socket_t::was_expanded) ? "expanded-ip " : "" + , (lep.flags & listen_socket_t::proxy) ? "proxy " : ""); + } +#endif + + auto ret = std::make_shared(); + ret->ssl = lep.ssl; + ret->original_port = bind_ep.port(); + ret->flags = lep.flags; + ret->netmask = lep.netmask; + operation_t last_op = operation_t::unknown; + socket_type_t const sock_type + = (lep.ssl == transport::ssl) + ? socket_type_t::tcp_ssl + : socket_type_t::tcp; + + // if we're in force-proxy mode, don't open TCP listen sockets. We cannot + // accept connections on our local machine in this case. + // TODO: 3 the logic in this if-block should be factored out into a + // separate function. At least most of it + if (ret->flags & listen_socket_t::accept_incoming) + { + ret->sock = std::make_shared(m_io_service); + ret->sock->open(bind_ep.protocol(), ec); + last_op = operation_t::sock_open; + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("failed to open socket: %s" + , ec.message().c_str()); + } +#endif + + if (m_alerts.should_post()) + m_alerts.emplace_alert(lep.device, bind_ep, last_op + , ec, sock_type); + return ret; + } + +#ifdef TORRENT_WINDOWS + { + // this is best-effort. ignore errors + error_code err; + ret->sock->set_option(exclusive_address_use(true), err); +#ifndef TORRENT_DISABLE_LOGGING + if (err && should_log()) + { + session_log("failed enable exclusive address use on listen socket: %s" + , err.message().c_str()); + } +#endif // TORRENT_DISABLE_LOGGING + } +#else + + { + // this is best-effort. ignore errors + error_code err; + ret->sock->set_option(tcp::acceptor::reuse_address(true), err); +#ifndef TORRENT_DISABLE_LOGGING + if (err && should_log()) + { + session_log("failed enable reuse-address on listen socket: %s" + , err.message().c_str()); + } +#endif // TORRENT_DISABLE_LOGGING + } +#endif // TORRENT_WINDOWS + + if (is_v6(bind_ep)) + { + error_code err; // ignore errors here + ret->sock->set_option(boost::asio::ip::v6_only(true), err); +#ifndef TORRENT_DISABLE_LOGGING + if (err && should_log()) + { + session_log("failed enable v6 only on listen socket: %s" + , err.message().c_str()); + } +#endif // LOGGING + +#ifdef TORRENT_WINDOWS + // enable Teredo on windows + ret->sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err); +#ifndef TORRENT_DISABLE_LOGGING + if (err && should_log()) + { + session_log("failed enable IPv6 unrestricted protection level on " + "listen socket: %s", err.message().c_str()); + } +#endif // TORRENT_DISABLE_LOGGING +#endif // TORRENT_WINDOWS + } + + if (!lep.device.empty()) + { + // we have an actual device we're interested in listening on, if we + // have SO_BINDTODEVICE functionality, use it now. +#if TORRENT_HAS_BINDTODEVICE + bind_device(*ret->sock, lep.device.c_str(), ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + session_log("bind to device failed (device: %s): %s" + , lep.device.c_str(), ec.message().c_str()); + } +#endif // TORRENT_DISABLE_LOGGING + ec.clear(); +#endif // TORRENT_HAS_BINDTODEVICE + } + + ret->sock->bind(bind_ep, ec); + last_op = operation_t::sock_bind; + + while (ec == error_code(error::address_in_use) && retries > 0) + { + TORRENT_ASSERT_VAL(ec, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("failed to bind listen socket to: %s on device: %s :" + " [%s] (%d) %s (retries: %d)" + , print_endpoint(bind_ep).c_str() + , lep.device.c_str() + , ec.category().name(), ec.value(), ec.message().c_str() + , retries); + } +#endif + ec.clear(); + --retries; + bind_ep.port(bind_ep.port() + 1); + ret->sock->bind(bind_ep, ec); + } + + if (ec == error_code(error::address_in_use) + && m_settings.get_bool(settings_pack::listen_system_port_fallback) + && bind_ep.port() != 0) + { + // instead of giving up, try let the OS pick a port + bind_ep.port(0); + ec.clear(); + ret->sock->bind(bind_ep, ec); + last_op = operation_t::sock_bind; + } + + if (ec) + { + // not even that worked, give up + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("failed to bind listen socket to: %s on device: %s :" + " [%s] (%d) %s (giving up)" + , print_endpoint(bind_ep).c_str() + , lep.device.c_str() + , ec.category().name(), ec.value(), ec.message().c_str()); + } +#endif + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(lep.device, bind_ep + , last_op, ec, sock_type); + } + ret->sock.reset(); + return ret; + } + ret->local_endpoint = ret->sock->local_endpoint(ec); + last_op = operation_t::getname; + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("get_sockname failed on listen socket: %s" + , ec.message().c_str()); + } +#endif + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(lep.device, bind_ep + , last_op, ec, sock_type); + } + return ret; + } + + TORRENT_ASSERT(ret->local_endpoint.port() == bind_ep.port() + || bind_ep.port() == 0); + + if (bind_ep.port() == 0) bind_ep = ret->local_endpoint; + + ret->sock->listen(m_settings.get_int(settings_pack::listen_queue_size), ec); + last_op = operation_t::sock_listen; + + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("cannot listen on interface \"%s\": %s" + , lep.device.c_str(), ec.message().c_str()); + } +#endif + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(lep.device, bind_ep + , last_op, ec, sock_type); + } + return ret; + } + } // accept incoming + + socket_type_t const udp_sock_type + = (lep.ssl == transport::ssl) + ? socket_type_t::utp_ssl + : socket_type_t::udp; + udp::endpoint udp_bind_ep(bind_ep.address(), bind_ep.port()); + + ret->udp_sock = std::make_shared(m_io_service, ret); + ret->udp_sock->sock.open(udp_bind_ep.protocol(), ec); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("failed to open UDP socket: %s: %s" + , lep.device.c_str(), ec.message().c_str()); + } +#endif + + last_op = operation_t::sock_open; + if (m_alerts.should_post()) + m_alerts.emplace_alert(lep.device + , bind_ep, last_op, ec, udp_sock_type); + + return ret; + } + +#if TORRENT_HAS_BINDTODEVICE + if (!lep.device.empty()) + { + bind_device(ret->udp_sock->sock, lep.device.c_str(), ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + session_log("bind to device failed (device: %s): %s" + , lep.device.c_str(), ec.message().c_str()); + } +#endif // TORRENT_DISABLE_LOGGING + ec.clear(); + } +#endif + ret->udp_sock->sock.bind(udp_bind_ep, ec); + + while (ec == error_code(error::address_in_use) && retries > 0) + { + TORRENT_ASSERT_VAL(ec, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("failed to bind udp socket to: %s on device: %s :" + " [%s] (%d) %s (retries: %d)" + , print_endpoint(bind_ep).c_str() + , lep.device.c_str() + , ec.category().name(), ec.value(), ec.message().c_str() + , retries); + } +#endif + ec.clear(); + --retries; + udp_bind_ep.port(udp_bind_ep.port() + 1); + ret->udp_sock->sock.bind(udp_bind_ep, ec); + } + + if (ec == error_code(error::address_in_use) + && m_settings.get_bool(settings_pack::listen_system_port_fallback) + && udp_bind_ep.port() != 0) + { + // instead of giving up, try let the OS pick a port + udp_bind_ep.port(0); + ec.clear(); + ret->udp_sock->sock.bind(udp_bind_ep, ec); + } + + last_op = operation_t::sock_bind; + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("failed to bind UDP socket: %s: %s" + , lep.device.c_str(), ec.message().c_str()); + } +#endif + + if (m_alerts.should_post()) + m_alerts.emplace_alert(lep.device + , bind_ep, last_op, ec, udp_sock_type); + + return ret; + } + + // if we did not open a TCP listen socket, ret->local_endpoint was never + // initialized, so do that now, based on the UDP socket + if (!(ret->flags & listen_socket_t::accept_incoming)) + { + auto const udp_ep = ret->udp_sock->local_endpoint(); + ret->local_endpoint = tcp::endpoint(udp_ep.address(), udp_ep.port()); + } + + ret->device = lep.device; + + error_code err; + set_socket_buffer_size(ret->udp_sock->sock, m_settings, err); + if (err) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(ret->udp_sock->sock.local_endpoint(ec) + , operation_t::alloc_recvbuf, err); + } + + // this call is necessary here because, unless the settings actually + // change after the session is up and listening, at no other point + // set_proxy_settings is called with the correct proxy configuration, + // internally, this method handle the SOCKS5's connection logic + ret->udp_sock->sock.set_proxy_settings(proxy(), m_alerts); + + ADD_OUTSTANDING_ASYNC("session_impl::on_udp_packet"); + ret->udp_sock->sock.async_read(aux::make_handler(std::bind(&session_impl::on_udp_packet + , this, ret->udp_sock, ret, ret->ssl, _1) + , ret->udp_handler_storage, *this)); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log(" listening on: %s TCP port: %d UDP port: %d" + , bind_ep.address().to_string().c_str() + , ret->tcp_external_port(), ret->udp_external_port()); + } +#endif + return ret; + } + + void session_impl::on_exception(std::exception const& e) + { + TORRENT_UNUSED(e); +#ifndef TORRENT_DISABLE_LOGGING + session_log("FATAL SESSION ERROR [%s]", e.what()); +#endif + this->abort(); + } + + void session_impl::on_error(error_code const& ec) + { + TORRENT_UNUSED(ec); +#ifndef TORRENT_DISABLE_LOGGING + session_log("FATAL SESSION ERROR (%s : %d) [%s]" + , ec.category().name(), ec.value(), ec.message().c_str()); +#endif + this->abort(); + } + + void session_impl::on_ip_change(error_code const& ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (!ec) + session_log("received ip change from internal ip_notifier"); + else + session_log("received error on_ip_change: %d, %s", ec.value(), ec.message().c_str()); +#endif + if (ec || m_abort || !m_ip_notifier) return; + m_ip_notifier->async_wait([this] (error_code const& e) + { this->wrap(&session_impl::on_ip_change, e); }); + reopen_network_sockets({}); + } + + // TODO: could this function be merged with expand_unspecified_addresses? + // right now both listen_endpoint_t and listen_interface_t are almost + // identical, maybe the latter could be removed too + void interface_to_endpoints(listen_interface_t const& iface + , listen_socket_flags_t flags + , span const ifs + , std::vector& eps) + { + flags |= iface.local ? listen_socket_t::local_network : listen_socket_flags_t{}; + transport const ssl = iface.ssl ? transport::ssl : transport::plaintext; + + // First, check to see if it's an IP address + error_code err; + address const adr = make_address(iface.device.c_str(), err); + if (!err) + { + eps.emplace_back(adr, iface.port, std::string{}, ssl, flags); + } + else + { + flags |= listen_socket_t::was_expanded; + + // this is the case where device names a network device. We need to + // enumerate all IPs associated with this device + for (auto const& ipface : ifs) + { + // we're looking for a specific interface, and its address + // (which must be of the same family as the address we're + // connecting to) + if (iface.device != ipface.name) continue; + + bool const local = iface.local + || ipface.interface_address.is_loopback() + || is_link_local(ipface.interface_address); + + eps.emplace_back(ipface.interface_address, iface.port, iface.device + , ssl, flags | (local ? listen_socket_t::local_network : listen_socket_flags_t{})); + } + } + } + + void session_impl::reopen_listen_sockets(bool const map_ports) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("reopen listen sockets"); +#endif + + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(!m_abort); + + error_code ec; + + if (m_abort) return; + + // first build a list of endpoints we should be listening on + // we need to remove any unneeded sockets first to avoid the possibility + // of a new socket failing to bind due to a conflict with a stale socket + std::vector eps; + + // if we don't proxy peer connections, don't apply the special logic for + // proxies + if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none + && m_settings.get_bool(settings_pack::proxy_peer_connections)) + { + // we will be able to accept incoming connections over UDP. so use + // one of the ports the user specified to use a consistent port + // across sessions. If the user did not specify any ports, pick one + // at random + int const port = m_listen_interfaces.empty() + ? int(random(63000) + 2000) + : m_listen_interfaces.front().port; + listen_endpoint_t ep(address_v4::any(), port, {} + , transport::plaintext, listen_socket_t::proxy); + eps.emplace_back(ep); + } + else + { + std::vector const ifs = enum_net_interfaces(m_io_service, ec); + if (ec && m_alerts.should_post()) + { + m_alerts.emplace_alert("" + , operation_t::enum_if, ec, socket_type_t::tcp); + } + auto const routes = enum_routes(m_io_service, ec); + if (ec && m_alerts.should_post()) + { + m_alerts.emplace_alert("" + , operation_t::enum_route, ec, socket_type_t::tcp); + } + + // expand device names and populate eps + for (auto const& iface : m_listen_interfaces) + { +#ifndef TORRENT_USE_OPENSSL + if (iface.ssl) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("attempted to listen ssl with no library support on device: \"%s\"" + , iface.device.c_str()); +#endif + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(iface.device + , operation_t::sock_open + , boost::asio::error::operation_not_supported + , socket_type_t::tcp_ssl); + } + continue; + } +#endif + + // now we have a device to bind to. This device may actually just be an + // IP address or a device name. In case it's a device name, we want to + // (potentially) end up binding a socket for each IP address associated + // with that device. + interface_to_endpoints(iface, listen_socket_t::accept_incoming, ifs, eps); + } + + if (eps.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("no listen sockets"); +#endif + } + + expand_unspecified_address(ifs, routes, eps); + expand_devices(ifs, eps); + } + + auto remove_iter = partition_listen_sockets(eps, m_listen_sockets); + + while (remove_iter != m_listen_sockets.end()) + { +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + m_dht->delete_socket(*remove_iter); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("closing listen socket for %s on device \"%s\"" + , print_endpoint((*remove_iter)->local_endpoint).c_str() + , (*remove_iter)->device.c_str()); + } +#endif + if ((*remove_iter)->sock) (*remove_iter)->sock->close(ec); + if ((*remove_iter)->udp_sock) (*remove_iter)->udp_sock->sock.close(); + if ((*remove_iter)->natpmp_mapper) (*remove_iter)->natpmp_mapper->close(); + if ((*remove_iter)->upnp_mapper) (*remove_iter)->upnp_mapper->close(); + if ((*remove_iter)->lsd) (*remove_iter)->lsd->close(); + remove_iter = m_listen_sockets.erase(remove_iter); + } + + // all sockets in there stayed the same. Only sockets after this point are + // new and should post alerts + int const existing_sockets = int(m_listen_sockets.size()); + + m_stats_counters.set_value(counters::has_incoming_connections + , std::any_of(m_listen_sockets.begin(), m_listen_sockets.end() + , [](std::shared_ptr const& l) + { return l->incoming_connection; })); + + // open new sockets on any endpoints that didn't match with + // an existing socket + for (auto const& ep : eps) + { +#ifndef BOOST_NO_EXCEPTIONS + try +#endif + { + std::shared_ptr s = setup_listener(ep, ec); + + if (!ec && (s->sock || s->udp_sock)) + { + m_listen_sockets.emplace_back(s); + +#ifndef TORRENT_DISABLE_DHT + if (m_dht + && s->ssl != transport::ssl + && !(s->flags & listen_socket_t::local_network)) + { + m_dht->new_socket(m_listen_sockets.back()); + } +#endif + + TORRENT_ASSERT(bool(s->flags & listen_socket_t::accept_incoming) == bool(s->sock)); + if (s->sock) async_accept(s->sock, s->ssl); + } + } +#ifndef BOOST_NO_EXCEPTIONS + catch (std::exception const& e) + { + TORRENT_UNUSED(e); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("setup_listener(%s) device: %s failed: %s" + , print_endpoint(ep.addr, ep.port).c_str() + , ep.device.c_str() + , e.what()); + } +#endif // TORRENT_DISABLE_LOGGING + } +#endif // BOOST_NO_EXCEPTIONS + } + + if (m_listen_sockets.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("giving up on binding listen sockets"); +#endif + return; + } + + auto const new_sockets = span>( + m_listen_sockets).subspan(existing_sockets); + + // now, send out listen_succeeded_alert for the listen sockets we are + // listening on + if (m_alerts.should_post()) + { + for (auto const& l : new_sockets) + { + error_code err; + if (l->sock) + { + tcp::endpoint const tcp_ep = l->sock->local_endpoint(err); + if (!err) + { + socket_type_t const socket_type + = l->ssl == transport::ssl + ? socket_type_t::tcp_ssl + : socket_type_t::tcp; + + m_alerts.emplace_alert( + tcp_ep, socket_type); + } + } + + if (l->udp_sock) + { + udp::endpoint const udp_ep = l->udp_sock->sock.local_endpoint(err); + if (!err && l->udp_sock->sock.is_open()) + { + socket_type_t const socket_type + = l->ssl == transport::ssl + ? socket_type_t::utp_ssl + : socket_type_t::udp; + + m_alerts.emplace_alert( + udp_ep, socket_type); + } + } + } + } + + if (m_settings.get_int(settings_pack::peer_tos) != 0) + { + update_peer_tos(); + } + + ec.clear(); + + if (m_settings.get_bool(settings_pack::enable_natpmp)) + { + for (auto const& s : new_sockets) + start_natpmp(*s); + } + + if (m_settings.get_bool(settings_pack::enable_upnp)) + { + for (auto const& s : new_sockets) + start_upnp(*s); + } + + if (map_ports) + { + for (auto const& s : m_listen_sockets) + remap_ports(remap_natpmp_and_upnp, *s); + } + else + { + // new sockets need to map ports even if the caller did not request + // re-mapping + for (auto const& s : new_sockets) + remap_ports(remap_natpmp_and_upnp, *s); + } + + update_lsd(); + +#if TORRENT_USE_I2P + open_new_incoming_i2p_connection(); +#endif + + // trackers that were not reachable, may have become reachable now. + // so clear the "disabled" flags to let them be tried one more time + // TODO: it would probably be better to do this by having a + // listen-socket "version" number that gets bumped. And instead of + // setting a bool to disable a tracker, we set the version number that + // it was disabled at. This change would affect the ABI in 1.2, so + // should be done in 2.0 or later + for (auto& t : m_torrents) + t.second->enable_all_trackers(); + } + + void session_impl::reopen_network_sockets(reopen_network_flags_t const options) + { + reopen_listen_sockets(bool(options & session_handle::reopen_map_ports)); + } + + namespace { + template + void map_port(MapProtocol& m, ProtoType protocol, EndpointType const& ep + , port_mapping_t& map_handle) + { + if (map_handle != port_mapping_t{-1}) m.delete_mapping(map_handle); + map_handle = port_mapping_t{-1}; + + address const addr = ep.address(); + // with IPv4 the interface might be behind NAT so we can't skip them + // based on the scope of the local address + if (addr.is_v6() && is_local(addr)) + return; + + // only update this mapping if we actually have a socket listening + if (ep != EndpointType()) + map_handle = m.add_mapping(protocol, ep.port(), ep); + } + } + + void session_impl::remap_ports(remap_port_mask_t const mask + , listen_socket_t& s) + { + tcp::endpoint const tcp_ep = s.sock ? s.sock->local_endpoint() : tcp::endpoint(); + udp::endpoint const udp_ep = s.udp_sock ? s.udp_sock->sock.local_endpoint() : udp::endpoint(); + + if ((mask & remap_natpmp) && s.natpmp_mapper) + { + map_port(*s.natpmp_mapper, portmap_protocol::tcp, tcp_ep + , s.tcp_port_mapping[portmap_transport::natpmp].mapping); + map_port(*s.natpmp_mapper, portmap_protocol::udp, make_tcp(udp_ep) + , s.udp_port_mapping[portmap_transport::natpmp].mapping); + } + if ((mask & remap_upnp) && s.upnp_mapper) + { + map_port(*s.upnp_mapper, portmap_protocol::tcp, tcp_ep + , s.tcp_port_mapping[portmap_transport::upnp].mapping); + map_port(*s.upnp_mapper, portmap_protocol::udp, make_tcp(udp_ep) + , s.udp_port_mapping[portmap_transport::upnp].mapping); + } + } + + void session_impl::update_i2p_bridge() + { + // we need this socket to be open before we + // can make name lookups for trackers for instance. + // pause the session now and resume it once we've + // established the i2p SAM connection +#if TORRENT_USE_I2P + if (m_settings.get_str(settings_pack::i2p_hostname).empty()) + { + error_code ec; + m_i2p_conn.close(ec); + return; + } + m_i2p_conn.open(m_settings.get_str(settings_pack::i2p_hostname) + , m_settings.get_int(settings_pack::i2p_port) + , std::bind(&session_impl::on_i2p_open, this, _1)); +#endif + } + +#ifndef TORRENT_DISABLE_DHT + int session_impl::external_udp_port(address const& local_address) const + { + auto ls = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , [&](std::shared_ptr const& e) + { + return e->local_endpoint.address() == local_address; + }); + + if (ls != m_listen_sockets.end()) + return (*ls)->udp_external_port(); + else + return -1; + } +#endif + +#if TORRENT_USE_I2P + + proxy_settings session_impl::i2p_proxy() const + { + proxy_settings ret; + + ret.hostname = m_settings.get_str(settings_pack::i2p_hostname); + ret.type = settings_pack::i2p_proxy; + ret.port = std::uint16_t(m_settings.get_int(settings_pack::i2p_port)); + return ret; + } + + void session_impl::on_i2p_open(error_code const& ec) + { + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(ec); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + session_log("i2p open failed (%d) %s", ec.value(), ec.message().c_str()); +#endif + } + // now that we have our i2p connection established + // it's OK to start torrents and use this socket to + // do i2p name lookups + + open_new_incoming_i2p_connection(); + } + + void session_impl::open_new_incoming_i2p_connection() + { + if (!m_i2p_conn.is_open()) return; + + if (m_i2p_listen_socket) return; + + m_i2p_listen_socket = std::make_shared(m_io_service); + bool ret = instantiate_connection(m_io_service, m_i2p_conn.proxy() + , *m_i2p_listen_socket, nullptr, nullptr, true, false); + TORRENT_ASSERT_VAL(ret, ret); + TORRENT_UNUSED(ret); + + ADD_OUTSTANDING_ASYNC("session_impl::on_i2p_accept"); + i2p_stream& s = *m_i2p_listen_socket->get(); + s.set_command(i2p_stream::cmd_accept); + s.set_session_id(m_i2p_conn.session_id()); + + s.async_connect(tcp::endpoint() + , std::bind(&session_impl::on_i2p_accept, this, m_i2p_listen_socket, _1)); + } + + void session_impl::on_i2p_accept(std::shared_ptr const& s + , error_code const& e) + { + COMPLETE_ASYNC("session_impl::on_i2p_accept"); + m_i2p_listen_socket.reset(); + if (e == boost::asio::error::operation_aborted) return; + if (e) + { + if (m_alerts.should_post()) + { + m_alerts.emplace_alert("i2p" + , operation_t::sock_accept + , e, socket_type_t::i2p); + } +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + session_log("i2p SAM connection failure: %s", e.message().c_str()); +#endif + return; + } + open_new_incoming_i2p_connection(); + incoming_connection(s); + } +#endif + + void session_impl::send_udp_packet_hostname(std::weak_ptr sock + , char const* hostname + , int const port + , span p + , error_code& ec + , udp_send_flags_t const flags) + { + auto si = sock.lock(); + if (!si) + { + ec = boost::asio::error::bad_descriptor; + return; + } + + auto s = std::static_pointer_cast(si)->udp_sock; + + s->sock.send_hostname(hostname, port, p, ec, flags); + + if ((ec == error::would_block || ec == error::try_again) + && !s->write_blocked) + { + s->write_blocked = true; + ADD_OUTSTANDING_ASYNC("session_impl::on_udp_writeable"); + s->sock.async_write(std::bind(&session_impl::on_udp_writeable + , this, s, _1)); + } + } + + void session_impl::send_udp_packet(std::weak_ptr sock + , udp::endpoint const& ep + , span p + , error_code& ec + , udp_send_flags_t const flags) + { + auto si = sock.lock(); + if (!si) + { + ec = boost::asio::error::bad_descriptor; + return; + } + + auto s = std::static_pointer_cast(si)->udp_sock; + + TORRENT_ASSERT(s->sock.is_closed() || s->sock.local_endpoint().protocol() == ep.protocol()); + + s->sock.send(ep, p, ec, flags); + + if ((ec == error::would_block || ec == error::try_again) && !s->write_blocked) + { + s->write_blocked = true; + ADD_OUTSTANDING_ASYNC("session_impl::on_udp_writeable"); + s->sock.async_write(std::bind(&session_impl::on_udp_writeable + , this, s, _1)); + } + } + + void session_impl::on_udp_writeable(std::weak_ptr sock, error_code const& ec) + { + COMPLETE_ASYNC("session_impl::on_udp_writeable"); + if (ec) return; + + auto s = sock.lock(); + if (!s) return; + + s->write_blocked = false; + +#ifdef TORRENT_USE_OPENSSL + auto i = std::find_if( + m_listen_sockets.begin(), m_listen_sockets.end() + , [&s] (std::shared_ptr const& ls) { return ls->udp_sock == s; }); +#endif + + // notify the utp socket manager it can start sending on the socket again + struct utp_socket_manager& mgr = +#ifdef TORRENT_USE_OPENSSL + (i != m_listen_sockets.end() && (*i)->ssl == transport::ssl) ? m_ssl_utp_socket_manager : +#endif + m_utp_socket_manager; + + mgr.writable(); + } + + + void session_impl::on_udp_packet(std::weak_ptr socket + , std::weak_ptr ls, transport const ssl, error_code const& ec) + { + COMPLETE_ASYNC("session_impl::on_udp_packet"); + if (ec) + { + std::shared_ptr s = socket.lock(); + udp::endpoint ep; + if (s) ep = s->local_endpoint(); + + // don't bubble up operation aborted errors to the user + if (ec != boost::asio::error::operation_aborted + && ec != boost::asio::error::bad_descriptor + && m_alerts.should_post()) + { + m_alerts.emplace_alert(ep + , operation_t::sock_read, ec); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("UDP error: %s (%d) %s" + , print_endpoint(ep).c_str(), ec.value(), ec.message().c_str()); + } +#endif + return; + } + + m_stats_counters.inc_stats_counter(counters::on_udp_counter); + + std::shared_ptr s = socket.lock(); + if (!s) return; + + struct utp_socket_manager& mgr = +#ifdef TORRENT_USE_OPENSSL + ssl == transport::ssl ? m_ssl_utp_socket_manager : +#endif + m_utp_socket_manager; + + auto listen_socket = ls.lock(); + if (listen_socket) + listen_socket->incoming_connection = true; + + for (;;) + { + aux::array p; + error_code err; + int const num_packets = s->sock.read(p, err); + + for (int i = 0; i < num_packets; ++i) + { + udp_socket::packet& packet = p[i]; + + if (packet.error) + { + // TODO: 3 it would be neat if the utp socket manager would + // handle ICMP errors too + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + m_dht->incoming_error(packet.error, packet.from); +#endif + + m_tracker_manager.incoming_error(packet.error, packet.from); + continue; + } + + span const buf = packet.data; + + // give the uTP socket manager first dibs on the packet. Presumably + // the majority of packets are uTP packets. + if (!mgr.incoming_packet(ls, packet.from, buf)) + { + // if it wasn't a uTP packet, try the other users of the UDP + // socket + bool handled = false; +#ifndef TORRENT_DISABLE_DHT + if (m_dht && buf.size() > 20 + && buf.front() == 'd' + && buf.back() == 'e' + && listen_socket) + { + handled = m_dht->incoming_packet(listen_socket, packet.from, buf); + } +#endif + + if (!handled) + { + m_tracker_manager.incoming_packet(packet.from, buf); + } + } + } + + if (err == error::would_block || err == error::try_again) + { + // there are no more packets on the socket + break; + } + + if (err) + { + udp::endpoint const ep = s->local_endpoint(); + + if (err != boost::asio::error::operation_aborted + && m_alerts.should_post()) + m_alerts.emplace_alert(ep + , operation_t::sock_read, err); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("UDP error: %s (%d) %s" + , print_endpoint(ep).c_str(), ec.value(), ec.message().c_str()); + } +#endif + + // any error other than these ones are considered fatal errors, and + // we won't read from the socket again + if (err != boost::asio::error::host_unreachable + && err != boost::asio::error::fault + && err != boost::asio::error::connection_reset + && err != boost::asio::error::connection_refused + && err != boost::asio::error::connection_aborted + && err != boost::asio::error::operation_aborted + && err != boost::asio::error::network_reset + && err != boost::asio::error::network_unreachable +#ifdef _WIN32 + // ERROR_MORE_DATA means the same thing as EMSGSIZE + && err != error_code(ERROR_MORE_DATA, system_category()) + && err != error_code(ERROR_HOST_UNREACHABLE, system_category()) + && err != error_code(ERROR_PORT_UNREACHABLE, system_category()) + && err != error_code(ERROR_RETRY, system_category()) + && err != error_code(ERROR_NETWORK_UNREACHABLE, system_category()) + && err != error_code(ERROR_CONNECTION_REFUSED, system_category()) + && err != error_code(ERROR_CONNECTION_ABORTED, system_category()) +#endif + && err != boost::asio::error::message_size) + { + // fatal errors. Don't try to read from this socket again + mgr.socket_drained(); + return; + } + // non-fatal UDP errors get here, we should re-issue the read. + continue; + } + } + + mgr.socket_drained(); + + ADD_OUTSTANDING_ASYNC("session_impl::on_udp_packet"); + s->sock.async_read(make_handler(std::bind(&session_impl::on_udp_packet + , this, std::move(socket), std::move(ls), ssl, _1), s->udp_handler_storage + , *this)); + } + + void session_impl::async_accept(std::shared_ptr const& listener + , transport const ssl) + { + TORRENT_ASSERT(!m_abort); + std::shared_ptr c = std::make_shared(m_io_service); + tcp::socket* str = nullptr; + +#ifdef TORRENT_USE_OPENSSL + if (ssl == transport::ssl) + { + // accept connections initializing the SSL connection to use the peer + // ssl context. Since it has the servername callback set on it, we will + // switch away from this context into a specific torrent once we start + // handshaking + c->instantiate>(m_io_service, &m_peer_ssl_ctx); + str = &c->get>()->next_layer(); + } + else +#endif + { + c->instantiate(m_io_service); + str = c->get(); + } + + ADD_OUTSTANDING_ASYNC("session_impl::on_accept_connection"); + +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASSERT((ssl == transport::ssl) == is_ssl(*c)); +#endif + + std::weak_ptr ls(listener); + m_stats_counters.inc_stats_counter(counters::num_outstanding_accept); + listener->async_accept(*str, [this, c, ls, ssl] (error_code const& ec) + { return this->wrap(&session_impl::on_accept_connection, c, ls, ec, ssl); }); + } + + void session_impl::on_accept_connection(std::shared_ptr const& s + , std::weak_ptr listen_socket, error_code const& e + , transport const ssl) + { + COMPLETE_ASYNC("session_impl::on_accept_connection"); + m_stats_counters.inc_stats_counter(counters::on_accept_counter); + m_stats_counters.inc_stats_counter(counters::num_outstanding_accept, -1); + + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr listener = listen_socket.lock(); + if (!listener) return; + + if (e == boost::asio::error::operation_aborted) return; + + if (m_abort) return; + + error_code ec; + if (e) + { + tcp::endpoint const ep = listener->local_endpoint(ec); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("error accepting connection on '%s': %s" + , print_endpoint(ep).c_str(), e.message().c_str()); + } +#endif +#ifdef TORRENT_WINDOWS + // Windows sometimes generates this error. It seems to be + // non-fatal and we have to do another async_accept. + if (e.value() == ERROR_SEM_TIMEOUT) + { + async_accept(listener, ssl); + return; + } +#endif +#ifdef TORRENT_BSD + // Leopard sometimes generates an "invalid argument" error. It seems to be + // non-fatal and we have to do another async_accept. + if (e.value() == EINVAL) + { + async_accept(listener, ssl); + return; + } +#endif + if (e == boost::system::errc::too_many_files_open) + { + // if we failed to accept an incoming connection + // because we have too many files open, try again + // and lower the number of file descriptors used + // elsewhere. + if (m_settings.get_int(settings_pack::connections_limit) > 10) + { + // now, disconnect a random peer + auto const i = std::max_element(m_torrents.begin(), m_torrents.end() + , [](torrent_map::value_type const& lhs, torrent_map::value_type const& rhs) + { return lhs.second->num_peers() < rhs.second->num_peers(); }); + + if (m_alerts.should_post()) + m_alerts.emplace_alert( + torrent_handle(), performance_alert::too_few_file_descriptors); + + if (i != m_torrents.end()) + { + i->second->disconnect_peers(1, e); + } + + m_settings.set_int(settings_pack::connections_limit + , std::max(10, int(m_connections.size()))); + } + // try again, but still alert the user of the problem + async_accept(listener, ssl); + } + if (m_alerts.should_post()) + { + error_code err; + m_alerts.emplace_alert(ep.address().to_string(err) + , ep, operation_t::sock_accept, e + , ssl == transport::ssl ? socket_type_t::tcp_ssl : socket_type_t::tcp); + } + return; + } + async_accept(listener, ssl); + + // don't accept any connections from our local sockets if we're using a + // proxy + if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none) + return; + + auto listen = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , [&listener](std::shared_ptr const& l) + { return l->sock == listener; }); + if (listen != m_listen_sockets.end()) + (*listen)->incoming_connection = true; + +#ifdef TORRENT_USE_OPENSSL + if (ssl == transport::ssl) + { + TORRENT_ASSERT(is_ssl(*s)); + + // for SSL connections, incoming_connection() is called + // after the handshake is done + ADD_OUTSTANDING_ASYNC("session_impl::ssl_handshake"); + s->get>()->async_accept_handshake( + std::bind(&session_impl::ssl_handshake, this, _1, s)); + m_incoming_sockets.insert(s); + } + else +#endif + { + incoming_connection(s); + } + } + +#ifdef TORRENT_USE_OPENSSL + + void session_impl::on_incoming_utp_ssl(std::shared_ptr const& s) + { + TORRENT_ASSERT(is_ssl(*s)); + + // for SSL connections, incoming_connection() is called + // after the handshake is done + ADD_OUTSTANDING_ASYNC("session_impl::ssl_handshake"); + s->get>()->async_accept_handshake( + std::bind(&session_impl::ssl_handshake, this, _1, s)); + m_incoming_sockets.insert(s); + } + + // to test SSL connections, one can use this openssl command template: + // + // openssl s_client -cert .pem -key .pem + // -CAfile .pem -debug -connect 127.0.0.1:4433 -tls1 + // -servername + + void session_impl::ssl_handshake(error_code const& ec, std::shared_ptr s) + { + COMPLETE_ASYNC("session_impl::ssl_handshake"); + TORRENT_ASSERT(is_ssl(*s)); + + m_incoming_sockets.erase(s); + + error_code e; + tcp::endpoint endp = s->remote_endpoint(e); + if (e) return; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log(" *** peer SSL handshake done [ ip: %s ec: %s socket: %s ]" + , print_endpoint(endp).c_str(), ec.message().c_str(), s->type_name()); + } +#endif + + if (ec) + { + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle(), endp + , peer_id(), operation_t::ssl_handshake, ec); + } + return; + } + + incoming_connection(s); + } + +#endif // TORRENT_USE_OPENSSL + + void session_impl::incoming_connection(std::shared_ptr const& s) + { + TORRENT_ASSERT(is_single_thread()); + + if (m_paused) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log(" <== INCOMING CONNECTION [ ignored, paused ]"); +#endif + return; + } + + error_code ec; + // we got a connection request! + tcp::endpoint endp = s->remote_endpoint(ec); + + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log(" <== INCOMING CONNECTION [ rejected, could " + "not retrieve remote endpoint: %s ]" + , print_error(ec).c_str()); + } +#endif + return; + } + + if (!m_settings.get_bool(settings_pack::enable_incoming_utp) + && is_utp(*s)) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("<== INCOMING CONNECTION [ rejected uTP connection ]"); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp, peer_blocked_alert::utp_disabled); + return; + } + + if (!m_settings.get_bool(settings_pack::enable_incoming_tcp) + && s->get()) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("<== INCOMING CONNECTION [ rejected TCP connection ]"); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp, peer_blocked_alert::tcp_disabled); + return; + } + + // if there are outgoing interfaces specified, verify this + // peer is correctly bound to one of them + if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty()) + { + tcp::endpoint local = s->local_endpoint(ec); + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("<== INCOMING CONNECTION [ rejected connection: %s ]" + , print_error(ec).c_str()); + } +#endif + return; + } + + if (!verify_incoming_interface(local.address())) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + error_code err; + session_log("<== INCOMING CONNECTION [ rejected, local interface has incoming connections disabled: %s ]" + , local.address().to_string(err).c_str()); + } +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp, peer_blocked_alert::invalid_local_interface); + return; + } + if (!verify_bound_address(local.address(), is_utp(*s), ec)) + { + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("<== INCOMING CONNECTION [ rejected, not allowed local interface: %s ]" + , print_error(ec).c_str()); + } +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + error_code err; + session_log("<== INCOMING CONNECTION [ rejected, not allowed local interface: %s ]" + , local.address().to_string(err).c_str()); + } +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp, peer_blocked_alert::invalid_local_interface); + return; + } + } + + // local addresses do not count, since it's likely + // coming from our own client through local service discovery + // and it does not reflect whether or not a router is open + // for incoming connections or not. + if (!is_local(endp.address())) + m_stats_counters.set_value(counters::has_incoming_connections, 1); + + // this filter is ignored if a single torrent + // is set to ignore the filter, since this peer might be + // for that torrent + if (m_stats_counters[counters::non_filter_torrents] == 0 + && m_ip_filter + && (m_ip_filter->access(endp.address()) & ip_filter::blocked)) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("<== INCOMING CONNECTION [ filtered blocked ip ]"); +#endif + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , endp, peer_blocked_alert::ip_filter); + return; + } + + // check if we have any active torrents + // if we don't reject the connection + if (m_torrents.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("<== INCOMING CONNECTION [ rejected, there are no torrents ]"); +#endif + return; + } + + // figure out which peer classes this is connections has, + // to get connection_limit_factor + peer_class_set pcs; + set_peer_classes(&pcs, endp.address(), s->type()); + int connection_limit_factor = 0; + for (int i = 0; i < pcs.num_classes(); ++i) + { + peer_class_t pc = pcs.class_at(i); + if (m_classes.at(pc) == nullptr) continue; + int f = m_classes.at(pc)->connection_limit_factor; + if (connection_limit_factor < f) connection_limit_factor = f; + } + if (connection_limit_factor == 0) connection_limit_factor = 100; + + std::int64_t limit = m_settings.get_int(settings_pack::connections_limit); + limit = limit * 100 / connection_limit_factor; + + // don't allow more connections than the max setting + // weighed by the peer class' setting + bool reject = num_connections() >= limit + m_settings.get_int(settings_pack::connections_slack); + + if (reject) + { + if (m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle(), endp, peer_id() + , operation_t::bittorrent, s->type() + , error_code(errors::too_many_connections) + , close_reason_t::none); + } +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("<== INCOMING CONNECTION [ connections limit exceeded, conns: %d, limit: %d, slack: %d ]" + , num_connections(), m_settings.get_int(settings_pack::connections_limit) + , m_settings.get_int(settings_pack::connections_slack)); + } +#endif + return; + } + + // if we don't have any active torrents, there's no + // point in accepting this connection. If, however, + // the setting to start up queued torrents when they + // get an incoming connection is enabled, we cannot + // perform this check. + if (!m_settings.get_bool(settings_pack::incoming_starts_queued_torrents)) + { + bool has_active_torrent = std::any_of(m_torrents.begin(), m_torrents.end() + , [](std::pair> const& i) + { return !i.second->is_torrent_paused(); }); + if (!has_active_torrent) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("<== INCOMING CONNECTION [ rejected, no active torrents ]"); +#endif + return; + } + } + + m_stats_counters.inc_stats_counter(counters::incoming_connections); + + if (m_alerts.should_post()) + m_alerts.emplace_alert(s->type(), endp); + + peer_connection_args pack{ + this + , &m_settings + , &m_stats_counters + , &m_disk_thread + , &m_io_service + , std::weak_ptr() + , s + , endp + , nullptr + , aux::generate_peer_id(m_settings) + }; + + std::shared_ptr c + = std::make_shared(std::move(pack)); + + if (!c->is_disconnecting()) + { + // in case we've exceeded the limit, let this peer know that + // as soon as it's received the handshake, it needs to either + // disconnect or pick another peer to disconnect + if (num_connections() >= limit) + c->peer_exceeds_limit(); + + TORRENT_ASSERT(!c->m_in_constructor); + // removing a peer may not throw an exception, so prepare for this + // connection to be added to the undead peers now. + m_undead_peers.reserve(m_undead_peers.size() + m_connections.size() + 1); + m_connections.insert(c); + c->start(); + } + } + + void session_impl::close_connection(peer_connection* p) noexcept + { + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr sp(p->self()); + + TORRENT_ASSERT(p->is_disconnecting()); + + auto const i = m_connections.find(sp); + // make sure the next disk peer round-robin cursor stays valid + if (i != m_connections.end()) + { + m_connections.erase(i); + + TORRENT_ASSERT(std::find(m_undead_peers.begin() + , m_undead_peers.end(), sp) == m_undead_peers.end()); + + // someone else is holding a reference, it's important that + // it's destructed from the network thread. Make sure the + // last reference is held by the network thread. + TORRENT_ASSERT_VAL(m_undead_peers.capacity() > m_undead_peers.size() + , m_undead_peers.capacity()); + if (sp.use_count() > 2) + m_undead_peers.push_back(sp); + } + } + +#if TORRENT_ABI_VERSION == 1 + peer_id session_impl::deprecated_get_peer_id() const + { + return aux::generate_peer_id(m_settings); + } +#endif + + int session_impl::next_port() const + { + int start = m_settings.get_int(settings_pack::outgoing_port); + int num = m_settings.get_int(settings_pack::num_outgoing_ports); + std::pair out_ports(start, start + num); + if (m_next_port < out_ports.first || m_next_port > out_ports.second) + m_next_port = out_ports.first; + + int port = m_next_port; + ++m_next_port; + if (m_next_port > out_ports.second) m_next_port = out_ports.first; +#ifndef TORRENT_DISABLE_LOGGING + session_log(" *** BINDING OUTGOING CONNECTION [ port: %d ]", port); +#endif + return port; + } + + int session_impl::rate_limit(peer_class_t c, int channel) const + { + TORRENT_ASSERT(channel >= 0 && channel <= 1); + if (channel < 0 || channel > 1) return 0; + + peer_class const* pc = m_classes.at(c); + if (pc == nullptr) return 0; + return pc->channel[channel].throttle(); + } + + int session_impl::upload_rate_limit(peer_class_t c) const + { + return rate_limit(c, peer_connection::upload_channel); + } + + int session_impl::download_rate_limit(peer_class_t c) const + { + return rate_limit(c, peer_connection::download_channel); + } + + void session_impl::set_rate_limit(peer_class_t c, int channel, int limit) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + TORRENT_ASSERT(channel >= 0 && channel <= 1); + + if (channel < 0 || channel > 1) return; + + peer_class* pc = m_classes.at(c); + if (pc == nullptr) return; + if (limit <= 0) limit = 0; + else limit = std::min(limit, std::numeric_limits::max() - 1); + pc->channel[channel].throttle(limit); + } + + void session_impl::set_upload_rate_limit(peer_class_t c, int limit) + { + set_rate_limit(c, peer_connection::upload_channel, limit); + } + + void session_impl::set_download_rate_limit(peer_class_t c, int limit) + { + set_rate_limit(c, peer_connection::download_channel, limit); + } + +#if TORRENT_USE_ASSERTS + bool session_impl::has_peer(peer_connection const* p) const + { + TORRENT_ASSERT(is_single_thread()); + return std::any_of(m_connections.begin(), m_connections.end() + , [p] (std::shared_ptr const& pr) + { return pr.get() == p; }); + } + + bool session_impl::any_torrent_has_peer(peer_connection const* p) const + { + for (auto& pe : m_torrents) + if (pe.second->has_peer(p)) return true; + return false; + } + + bool session_impl::verify_queue_position(torrent const* t, queue_position_t const pos) + { + return m_download_queue.end_index() > pos && m_download_queue[pos] == t; + } +#endif + + void session_impl::sent_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + m_stats_counters.inc_stats_counter(counters::sent_bytes + , bytes_payload + bytes_protocol); + m_stats_counters.inc_stats_counter(counters::sent_payload_bytes + , bytes_payload); + + m_stat.sent_bytes(bytes_payload, bytes_protocol); + } + + void session_impl::received_bytes(int bytes_payload, int bytes_protocol) + { + TORRENT_ASSERT(bytes_payload >= 0); + TORRENT_ASSERT(bytes_protocol >= 0); + m_stats_counters.inc_stats_counter(counters::recv_bytes + , bytes_payload + bytes_protocol); + m_stats_counters.inc_stats_counter(counters::recv_payload_bytes + , bytes_payload); + + m_stat.received_bytes(bytes_payload, bytes_protocol); + } + + void session_impl::trancieve_ip_packet(int bytes, bool ipv6) + { + TORRENT_ASSERT(bytes >= 0); + // one TCP/IP packet header for the packet + // sent or received, and one for the ACK + // The IPv4 header is 20 bytes + // and IPv6 header is 40 bytes + int const header = (ipv6 ? 40 : 20) + 20; + int const mtu = 1500; + int const packet_size = mtu - header; + int const overhead = std::max(1, (bytes + packet_size - 1) / packet_size) * header; + m_stats_counters.inc_stats_counter(counters::sent_ip_overhead_bytes + , overhead); + m_stats_counters.inc_stats_counter(counters::recv_ip_overhead_bytes + , overhead); + + m_stat.trancieve_ip_packet(bytes, ipv6); + } + + void session_impl::sent_syn(bool ipv6) + { + int const overhead = ipv6 ? 60 : 40; + m_stats_counters.inc_stats_counter(counters::sent_ip_overhead_bytes + , overhead); + + m_stat.sent_syn(ipv6); + } + + void session_impl::received_synack(bool ipv6) + { + int const overhead = ipv6 ? 60 : 40; + m_stats_counters.inc_stats_counter(counters::sent_ip_overhead_bytes + , overhead); + m_stats_counters.inc_stats_counter(counters::recv_ip_overhead_bytes + , overhead); + + m_stat.received_synack(ipv6); + } + + void session_impl::on_tick(error_code const& e) + { + COMPLETE_ASYNC("session_impl::on_tick"); + m_stats_counters.inc_stats_counter(counters::on_tick_counter); + + TORRENT_ASSERT(is_single_thread()); + + // submit all disk jobs when we leave this function + deferred_submit_jobs(); + + time_point const now = aux::time_now(); + + // remove undead peers that only have this list as their reference keeping them alive + if (!m_undead_peers.empty()) + { + auto const remove_it = std::remove_if(m_undead_peers.begin(), m_undead_peers.end() + , [](std::shared_ptr& ptr) { return ptr.use_count() == 1; }); + m_undead_peers.erase(remove_it, m_undead_peers.end()); + if (m_undead_peers.empty()) + { + // we just removed our last "undead" peer (i.e. a peer connection + // that had some external reference to it). It's now safe to + // shut-down + if (m_abort) + { + m_io_service.post(std::bind(&session_impl::abort_stage2, this)); + } + } + } + +// too expensive +// INVARIANT_CHECK; + + // we have to keep ticking the utp socket manager + // until they're all closed + // we also have to keep updating the aux time while + // there are outstanding announces + if (m_abort) + { + if (m_utp_socket_manager.num_sockets() == 0 +#ifdef TORRENT_USE_OPENSSL + && m_ssl_utp_socket_manager.num_sockets() == 0 +#endif + && m_undead_peers.empty() + && m_tracker_manager.empty()) + { + return; + } +#if defined TORRENT_ASIO_DEBUGGING + std::fprintf(stderr, "uTP sockets: %d ssl-uTP sockets: %d undead-peers left: %d\n" + , m_utp_socket_manager.num_sockets() +#ifdef TORRENT_USE_OPENSSL + , m_ssl_utp_socket_manager.num_sockets() +#else + , 0 +#endif + , int(m_undead_peers.size())); +#endif + } + + if (e == boost::asio::error::operation_aborted) return; + + if (e) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + session_log("*** TICK TIMER FAILED %s", e.message().c_str()); +#endif + std::abort(); + } + + ADD_OUTSTANDING_ASYNC("session_impl::on_tick"); + error_code ec; + m_timer.expires_at(now + milliseconds(m_settings.get_int(settings_pack::tick_interval)), ec); + m_timer.async_wait(aux::make_handler([this](error_code const& err) + { this->wrap(&session_impl::on_tick, err); }, m_tick_handler_storage, *this)); + + m_download_rate.update_quotas(now - m_last_tick); + m_upload_rate.update_quotas(now - m_last_tick); + + m_last_tick = now; + + m_utp_socket_manager.tick(now); +#ifdef TORRENT_USE_OPENSSL + m_ssl_utp_socket_manager.tick(now); +#endif + + // only tick the following once per second + if (now - m_last_second_tick < seconds(1)) return; + +#ifndef TORRENT_DISABLE_DHT + if (m_dht + && m_dht_interval_update_torrents < 40 + && m_dht_interval_update_torrents != int(m_torrents.size())) + update_dht_announce_interval(); +#endif + + m_utp_socket_manager.decay(); +#ifdef TORRENT_USE_OPENSSL + m_ssl_utp_socket_manager.decay(); +#endif + + int const tick_interval_ms = aux::numeric_cast(total_milliseconds(now - m_last_second_tick)); + m_last_second_tick = now; + + std::int32_t const stime = session_time(); + if (stime > 65000) + { + // we're getting close to the point where our timestamps + // in torrent_peer are wrapping. We need to step all counters back + // four hours. This means that any timestamp that refers to a time + // more than 18.2 - 4 = 14.2 hours ago, will be incremented to refer to + // 14.2 hours ago. + + m_created += hours(4); + + constexpr int four_hours = 60 * 60 * 4; + for (auto& i : m_torrents) + { + i.second->step_session_time(four_hours); + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_ses_extensions[plugins_tick_idx]) + { + ext->on_tick(); + } +#endif + + // don't do any of the following while we're shutting down + if (m_abort) return; + + switch (m_settings.get_int(settings_pack::mixed_mode_algorithm)) + { + case settings_pack::prefer_tcp: + set_upload_rate_limit(m_tcp_peer_class, 0); + set_download_rate_limit(m_tcp_peer_class, 0); + break; + case settings_pack::peer_proportional: + { + int num_peers[2][2] = {{0, 0}, {0, 0}}; + for (auto const& i : m_connections) + { + peer_connection& p = *i; + if (p.in_handshake()) continue; + int protocol = 0; + if (is_utp(*p.get_socket())) protocol = 1; + + if (p.download_queue().size() + p.request_queue().size() > 0) + ++num_peers[protocol][peer_connection::download_channel]; + if (!p.upload_queue().empty()) + ++num_peers[protocol][peer_connection::upload_channel]; + } + + peer_class* pc = m_classes.at(m_tcp_peer_class); + bandwidth_channel* tcp_channel = pc->channel; + int stat_rate[] = {m_stat.upload_rate(), m_stat.download_rate() }; + // never throttle below this + int lower_limit[] = {5000, 30000}; + + for (int i = 0; i < 2; ++i) + { + // if there are no uploading uTP peers, don't throttle TCP up + if (num_peers[1][i] == 0) + { + tcp_channel[i].throttle(0); + } + else + { + if (num_peers[0][i] == 0) num_peers[0][i] = 1; + int total_peers = num_peers[0][i] + num_peers[1][i]; + // this are 64 bits since it's multiplied by the number + // of peers, which otherwise might overflow an int + std::int64_t rate = stat_rate[i]; + tcp_channel[i].throttle(std::max(int(rate * num_peers[0][i] / total_peers), lower_limit[i])); + } + } + } + break; + } + + // -------------------------------------------------------------- + // auto managed torrent + // -------------------------------------------------------------- + if (!m_paused) m_auto_manage_time_scaler--; + if (m_auto_manage_time_scaler < 0) + { + m_auto_manage_time_scaler = settings().get_int(settings_pack::auto_manage_interval); + recalculate_auto_managed_torrents(); + } + + // -------------------------------------------------------------- + // check for incoming connections that might have timed out + // -------------------------------------------------------------- + + for (auto i = m_connections.begin(); i != m_connections.end();) + { + peer_connection* p = (*i).get(); + ++i; + // ignore connections that already have a torrent, since they + // are ticked through the torrents' second_tick + if (!p->associated_torrent().expired()) continue; + + // TODO: have a separate list for these connections, instead of having to loop through all of them + int timeout = m_settings.get_int(settings_pack::handshake_timeout); +#if TORRENT_USE_I2P + timeout *= is_i2p(*p->get_socket()) ? 4 : 1; +#endif + if (m_last_tick - p->connected_time () > seconds(timeout)) + p->disconnect(errors::timed_out, operation_t::bittorrent); + } + + // -------------------------------------------------------------- + // second_tick every torrent (that wants it) + // -------------------------------------------------------------- + +#if TORRENT_DEBUG_STREAMING > 0 + std::printf("\033[2J\033[0;0H"); +#endif + + aux::vector& want_tick = m_torrent_lists[torrent_want_tick]; + for (int i = 0; i < int(want_tick.size()); ++i) + { + torrent& t = *want_tick[i]; + TORRENT_ASSERT(t.want_tick()); + TORRENT_ASSERT(!t.is_aborted()); + + t.second_tick(tick_interval_ms); + + // if the call to second_tick caused the torrent + // to no longer want to be ticked (i.e. it was + // removed from the list) we need to back up the counter + // to not miss the torrent after it + if (!t.want_tick()) --i; + } + + // TODO: this should apply to all bandwidth channels + if (m_settings.get_bool(settings_pack::rate_limit_ip_overhead)) + { + int const up_limit = upload_rate_limit(m_global_class); + int const down_limit = download_rate_limit(m_global_class); + + if (down_limit > 0 + && m_stat.download_ip_overhead() >= down_limit + && m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle() + , performance_alert::download_limit_too_low); + } + + if (up_limit > 0 + && m_stat.upload_ip_overhead() >= up_limit + && m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle() + , performance_alert::upload_limit_too_low); + } + } + +#if TORRENT_ABI_VERSION == 1 + m_peak_up_rate = std::max(m_stat.upload_rate(), m_peak_up_rate); +#endif + + m_stat.second_tick(tick_interval_ms); + + // -------------------------------------------------------------- + // scrape paused torrents that are auto managed + // (unless the session is paused) + // -------------------------------------------------------------- + if (!m_paused) + { + INVARIANT_CHECK; + --m_auto_scrape_time_scaler; + if (m_auto_scrape_time_scaler <= 0) + { + aux::vector& want_scrape = m_torrent_lists[torrent_want_scrape]; + m_auto_scrape_time_scaler = m_settings.get_int(settings_pack::auto_scrape_interval) + / std::max(1, int(want_scrape.size())); + if (m_auto_scrape_time_scaler < m_settings.get_int(settings_pack::auto_scrape_min_interval)) + m_auto_scrape_time_scaler = m_settings.get_int(settings_pack::auto_scrape_min_interval); + + if (!want_scrape.empty() && !m_abort) + { + if (m_next_scrape_torrent >= int(want_scrape.size())) + m_next_scrape_torrent = 0; + + torrent& t = *want_scrape[m_next_scrape_torrent]; + TORRENT_ASSERT(t.is_paused() && t.is_auto_managed()); + + // false means it's not triggered by the user, but automatically + // by libtorrent + t.scrape_tracker(-1, false); + + ++m_next_scrape_torrent; + if (m_next_scrape_torrent >= int(want_scrape.size())) + m_next_scrape_torrent = 0; + + } + } + } + + // -------------------------------------------------------------- + // connect new peers + // -------------------------------------------------------------- + + try_connect_more_peers(); + + // -------------------------------------------------------------- + // unchoke set calculations + // -------------------------------------------------------------- + m_unchoke_time_scaler--; + if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) + { + m_unchoke_time_scaler = settings().get_int(settings_pack::unchoke_interval); + recalculate_unchoke_slots(); + } + + // -------------------------------------------------------------- + // optimistic unchoke calculation + // -------------------------------------------------------------- + m_optimistic_unchoke_time_scaler--; + if (m_optimistic_unchoke_time_scaler <= 0) + { + m_optimistic_unchoke_time_scaler + = settings().get_int(settings_pack::optimistic_unchoke_interval); + recalculate_optimistic_unchoke_slots(); + } + + // -------------------------------------------------------------- + // disconnect peers when we have too many + // -------------------------------------------------------------- + --m_disconnect_time_scaler; + if (m_disconnect_time_scaler <= 0) + { + m_disconnect_time_scaler = m_settings.get_int(settings_pack::peer_turnover_interval); + + // if the connections_limit is too low, the disconnect + // logic is disabled, since it is too disruptive + if (m_settings.get_int(settings_pack::connections_limit) > 5) + { + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit) + * m_settings.get_int(settings_pack::peer_turnover_cutoff) / 100 + && !m_torrents.empty()) + { + // every 90 seconds, disconnect the worst peers + // if we have reached the connection limit + auto const i = std::max_element(m_torrents.begin(), m_torrents.end() + , [] (torrent_map::value_type const& lhs, torrent_map::value_type const& rhs) + { return lhs.second->num_peers() < rhs.second->num_peers(); }); + + TORRENT_ASSERT(i != m_torrents.end()); + int const peers_to_disconnect = std::min(std::max( + i->second->num_peers() * m_settings.get_int(settings_pack::peer_turnover) / 100, 1) + , i->second->num_connect_candidates()); + i->second->disconnect_peers(peers_to_disconnect + , error_code(errors::optimistic_disconnect)); + } + else + { + // if we haven't reached the global max. see if any torrent + // has reached its local limit + for (auto const& pt : m_torrents) + { + std::shared_ptr t = pt.second; + + // ths disconnect logic is disabled for torrents with + // too low connection limit + if (t->num_peers() < t->max_connections() + * m_settings.get_int(settings_pack::peer_turnover_cutoff) / 100 + || t->max_connections() < 6) + continue; + + int const peers_to_disconnect = std::min(std::max(t->num_peers() + * m_settings.get_int(settings_pack::peer_turnover) / 100, 1) + , t->num_connect_candidates()); + t->disconnect_peers(peers_to_disconnect, errors::optimistic_disconnect); + } + } + } + } + } + + void session_impl::received_buffer(int s) + { + int index = std::min(aux::log2p1(std::uint32_t(s >> 3)), 17); + m_stats_counters.inc_stats_counter(counters::socket_recv_size3 + index); + } + + void session_impl::sent_buffer(int s) + { + int index = std::min(aux::log2p1(std::uint32_t(s >> 3)), 17); + m_stats_counters.inc_stats_counter(counters::socket_send_size3 + index); + } + + void session_impl::prioritize_connections(std::weak_ptr t) + { + m_prio_torrents.emplace_back(t, 10); + } + +#ifndef TORRENT_DISABLE_DHT + + void session_impl::add_dht_node(udp::endpoint const& n) + { + TORRENT_ASSERT(is_single_thread()); + if (m_dht) m_dht->add_node(n); + else m_dht_nodes.push_back(n); + } + + bool session_impl::has_dht() const + { + return m_dht.get() != nullptr; + } + + void session_impl::prioritize_dht(std::weak_ptr t) + { + TORRENT_ASSERT(!m_abort); + if (m_abort) return; + + TORRENT_ASSERT(m_dht); + m_dht_torrents.push_back(t); +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr tor = t.lock(); + if (tor && should_log()) + session_log("prioritizing DHT announce: \"%s\"", tor->name().c_str()); +#endif + // trigger a DHT announce right away if we just added a new torrent and + // there's no back-log. in the timer handler, as long as there are more + // high priority torrents to be announced to the DHT, it will keep the + // timer interval short until all torrents have been announced. + if (m_dht_torrents.size() == 1) + { + ADD_OUTSTANDING_ASYNC("session_impl::on_dht_announce"); + error_code ec; + m_dht_announce_timer.expires_from_now(seconds(0), ec); + m_dht_announce_timer.async_wait([this](error_code const& err) { + this->wrap(&session_impl::on_dht_announce, err); }); + } + } + + void session_impl::on_dht_announce(error_code const& e) + { + COMPLETE_ASYNC("session_impl::on_dht_announce"); + TORRENT_ASSERT(is_single_thread()); + if (e) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("aborting DHT announce timer (%d): %s" + , e.value(), e.message().c_str()); + } +#endif + return; + } + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("aborting DHT announce timer: m_abort set"); +#endif + return; + } + + if (!m_dht) + { + m_dht_torrents.clear(); + return; + } + + TORRENT_ASSERT(m_dht); + + // announce to DHT every 15 minutes + int delay = std::max(m_settings.get_int(settings_pack::dht_announce_interval) + / std::max(int(m_torrents.size()), 1), 1); + + if (!m_dht_torrents.empty()) + { + // we have prioritized torrents that need + // an initial DHT announce. Don't wait too long + // until we announce those. + delay = std::min(4, delay); + } + + ADD_OUTSTANDING_ASYNC("session_impl::on_dht_announce"); + error_code ec; + m_dht_announce_timer.expires_from_now(seconds(delay), ec); + m_dht_announce_timer.async_wait([this](error_code const& err) + { this->wrap(&session_impl::on_dht_announce, err); }); + + if (!m_dht_torrents.empty()) + { + std::shared_ptr t; + do + { + t = m_dht_torrents.front().lock(); + m_dht_torrents.pop_front(); + } while (!t && !m_dht_torrents.empty()); + + if (t) + { + t->dht_announce(); + return; + } + } + if (m_torrents.empty()) return; + + if (m_next_dht_torrent == m_torrents.end()) + m_next_dht_torrent = m_torrents.begin(); + m_next_dht_torrent->second->dht_announce(); + // TODO: 2 make a list for torrents that want to be announced on the DHT so we + // don't have to loop over all torrents, just to find the ones that want to announce + ++m_next_dht_torrent; + if (m_next_dht_torrent == m_torrents.end()) + m_next_dht_torrent = m_torrents.begin(); + } +#endif + + void session_impl::on_lsd_announce(error_code const& e) + { + COMPLETE_ASYNC("session_impl::on_lsd_announce"); + m_stats_counters.inc_stats_counter(counters::on_lsd_counter); + TORRENT_ASSERT(is_single_thread()); + if (e) return; + + if (m_abort) return; + + ADD_OUTSTANDING_ASYNC("session_impl::on_lsd_announce"); + // announce on local network every 5 minutes + int const delay = std::max(m_settings.get_int(settings_pack::local_service_announce_interval) + / std::max(int(m_torrents.size()), 1), 1); + error_code ec; + m_lsd_announce_timer.expires_from_now(seconds(delay), ec); + m_lsd_announce_timer.async_wait([this](error_code const& err) { + this->wrap(&session_impl::on_lsd_announce, err); }); + + if (m_torrents.empty()) return; + + if (m_next_lsd_torrent == m_torrents.end()) + m_next_lsd_torrent = m_torrents.begin(); + m_next_lsd_torrent->second->lsd_announce(); + ++m_next_lsd_torrent; + if (m_next_lsd_torrent == m_torrents.end()) + m_next_lsd_torrent = m_torrents.begin(); + } + + void session_impl::auto_manage_checking_torrents(std::vector& list + , int& limit) + { + for (auto& t : list) + { + TORRENT_ASSERT(t->state() == torrent_status::checking_files); + TORRENT_ASSERT(t->is_auto_managed()); + if (limit <= 0) + { + t->pause(); + } + else + { + t->resume(); + if (!t->should_check_files()) continue; + t->start_checking(); + --limit; + } + } + } + + void session_impl::auto_manage_torrents(std::vector& list + , int& dht_limit, int& tracker_limit + , int& lsd_limit, int& hard_limit, int type_limit) + { + for (auto& t : list) + { + TORRENT_ASSERT(t->state() != torrent_status::checking_files); + + // inactive torrents don't count (and if you configured them to do so, + // the torrent won't say it's inactive) + if (hard_limit > 0 && t->is_inactive()) + { + t->set_announce_to_dht(--dht_limit >= 0); + t->set_announce_to_trackers(--tracker_limit >= 0); + t->set_announce_to_lsd(--lsd_limit >= 0); + + --hard_limit; +#ifndef TORRENT_DISABLE_LOGGING + if (t->is_torrent_paused()) + t->log_to_all_peers("auto manager starting (inactive) torrent"); +#endif + t->set_paused(false); + continue; + } + + if (type_limit > 0 && hard_limit > 0) + { + t->set_announce_to_dht(--dht_limit >= 0); + t->set_announce_to_trackers(--tracker_limit >= 0); + t->set_announce_to_lsd(--lsd_limit >= 0); + + --hard_limit; + --type_limit; +#ifndef TORRENT_DISABLE_LOGGING + if (t->is_torrent_paused()) + t->log_to_all_peers("auto manager starting torrent"); +#endif + t->set_paused(false); + continue; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (!t->is_torrent_paused()) + t->log_to_all_peers("auto manager pausing torrent"); +#endif + // use graceful pause for auto-managed torrents + t->set_paused(true, torrent_handle::graceful_pause + | torrent_handle::clear_disk_cache); + t->set_announce_to_dht(false); + t->set_announce_to_trackers(false); + t->set_announce_to_lsd(false); + } + } + + int session_impl::get_int_setting(int n) const + { + int const v = settings().get_int(n); + if (v < 0) return std::numeric_limits::max(); + return v; + } + + void session_impl::recalculate_auto_managed_torrents() + { + INVARIANT_CHECK; + + m_last_auto_manage = time_now(); + m_need_auto_manage = false; + + if (m_paused) return; + + // make copies of the lists of torrents that we want to consider for auto + // management. We need copies because they will be sorted. + std::vector checking + = torrent_list(session_interface::torrent_checking_auto_managed); + std::vector downloaders + = torrent_list(session_interface::torrent_downloading_auto_managed); + std::vector seeds + = torrent_list(session_interface::torrent_seeding_auto_managed); + + // these counters are set to the number of torrents + // of each kind we're allowed to have active + int downloading_limit = get_int_setting(settings_pack::active_downloads); + int seeding_limit = get_int_setting(settings_pack::active_seeds); + int checking_limit = get_int_setting(settings_pack::active_checking); + int dht_limit = get_int_setting(settings_pack::active_dht_limit); + int tracker_limit = get_int_setting(settings_pack::active_tracker_limit); + int lsd_limit = get_int_setting(settings_pack::active_lsd_limit); + int hard_limit = get_int_setting(settings_pack::active_limit); + + // if hard_limit is <= 0, all torrents in these lists should be paused. + // The order is not relevant + if (hard_limit > 0) + { + // we only need to sort the first n torrents here, where n is the number + // of checking torrents we allow. The rest of the list is still used to + // make sure the remaining torrents are paused, but their order is not + // relevant + std::partial_sort(checking.begin(), checking.begin() + + std::min(checking_limit, int(checking.size())), checking.end() + , [](torrent const* lhs, torrent const* rhs) + { return lhs->sequence_number() < rhs->sequence_number(); }); + + std::partial_sort(downloaders.begin(), downloaders.begin() + + std::min(hard_limit, int(downloaders.size())), downloaders.end() + , [](torrent const* lhs, torrent const* rhs) + { return lhs->sequence_number() < rhs->sequence_number(); }); + + std::partial_sort(seeds.begin(), seeds.begin() + + std::min(hard_limit, int(seeds.size())), seeds.end() + , [this](torrent const* lhs, torrent const* rhs) + { return lhs->seed_rank(m_settings) > rhs->seed_rank(m_settings); }); + } + + auto_manage_checking_torrents(checking, checking_limit); + + if (settings().get_bool(settings_pack::auto_manage_prefer_seeds)) + { + auto_manage_torrents(seeds, dht_limit, tracker_limit, lsd_limit + , hard_limit, seeding_limit); + auto_manage_torrents(downloaders, dht_limit, tracker_limit, lsd_limit + , hard_limit, downloading_limit); + } + else + { + auto_manage_torrents(downloaders, dht_limit, tracker_limit, lsd_limit + , hard_limit, downloading_limit); + auto_manage_torrents(seeds, dht_limit, tracker_limit, lsd_limit + , hard_limit, seeding_limit); + } + } + + namespace { +#ifndef TORRENT_DISABLE_EXTENSIONS + uint64_t const priority_undetermined = std::numeric_limits::max() - 1; +#endif + + struct opt_unchoke_candidate + { + explicit opt_unchoke_candidate(std::shared_ptr const* tp) + : peer(tp) + {} + + std::shared_ptr const* peer; +#ifndef TORRENT_DISABLE_EXTENSIONS + // this is mutable because comparison functors passed to std::partial_sort + // are not supposed to modify the elements they are sorting. Here the mutation + // being applied is idempotent so it should not pose a problem. + mutable uint64_t ext_priority = priority_undetermined; +#endif + }; + + struct last_optimistic_unchoke_cmp + { +#ifndef TORRENT_DISABLE_EXTENSIONS + explicit last_optimistic_unchoke_cmp(std::vector>& ps) + : plugins(ps) + {} + + std::vector>& plugins; +#endif + + uint64_t get_ext_priority(opt_unchoke_candidate const& peer) const + { +#ifndef TORRENT_DISABLE_EXTENSIONS + if (peer.ext_priority == priority_undetermined) + { + peer.ext_priority = std::numeric_limits::max(); + for (auto& e : plugins) + { + uint64_t const priority = e->get_unchoke_priority(peer_connection_handle(*peer.peer)); + peer.ext_priority = std::min(priority, peer.ext_priority); + } + } + return peer.ext_priority; +#else + TORRENT_UNUSED(peer); + return std::numeric_limits::max(); +#endif + } + + bool operator()(opt_unchoke_candidate const& l + , opt_unchoke_candidate const& r) const + { + torrent_peer const* pil = (*l.peer)->peer_info_struct(); + torrent_peer const* pir = (*r.peer)->peer_info_struct(); + if (pil->last_optimistically_unchoked + != pir->last_optimistically_unchoked) + { + return pil->last_optimistically_unchoked + < pir->last_optimistically_unchoked; + } + else + { + return get_ext_priority(l) < get_ext_priority(r); + } + } + }; + } + + void session_impl::recalculate_optimistic_unchoke_slots() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(is_single_thread()); + if (m_stats_counters[counters::num_unchoke_slots] == 0) return; + + // if we unchoke everyone, skip this logic + if (settings().get_int(settings_pack::choking_algorithm) == settings_pack::fixed_slots_choker + && settings().get_int(settings_pack::unchoke_slots_limit) < 0) + return; + + std::vector opt_unchoke; + + // collect the currently optimistically unchoked peers here, so we can + // choke them when we've found new optimistic unchoke candidates. + std::vector prev_opt_unchoke; + + // TODO: 3 it would probably make sense to have a separate list of peers + // that are eligible for optimistic unchoke, similar to the torrents + // perhaps this could even iterate over the pool allocators of + // torrent_peer objects. It could probably be done in a single pass and + // collect the n best candidates. maybe just a queue of peers would make + // even more sense, just pick the next peer in the queue for unchoking. It + // would be O(1). + for (auto& i : m_connections) + { + peer_connection* p = i.get(); + TORRENT_ASSERT(p); + torrent_peer* pi = p->peer_info_struct(); + if (!pi) continue; + if (pi->web_seed) continue; + + if (pi->optimistically_unchoked) + { + prev_opt_unchoke.push_back(pi); + } + + torrent const* t = p->associated_torrent().lock().get(); + if (!t) continue; + + // TODO: 3 peers should know whether their torrent is paused or not, + // instead of having to ask it over and over again + if (t->is_paused()) continue; + + if (!p->is_connecting() + && !p->is_disconnecting() + && p->is_peer_interested() + && t->free_upload_slots() + && (p->is_choked() || pi->optimistically_unchoked) + && !p->ignore_unchoke_slots() + && t->valid_metadata()) + { + opt_unchoke.emplace_back(&i); + } + } + + // find the peers that has been waiting the longest to be optimistically + // unchoked + + int num_opt_unchoke = m_settings.get_int(settings_pack::num_optimistic_unchoke_slots); + int const allowed_unchoke_slots = int(m_stats_counters[counters::num_unchoke_slots]); + if (num_opt_unchoke == 0) num_opt_unchoke = std::max(1, allowed_unchoke_slots / 5); + if (num_opt_unchoke > int(opt_unchoke.size())) num_opt_unchoke = + int(opt_unchoke.size()); + + // find the n best optimistic unchoke candidates + std::partial_sort(opt_unchoke.begin() + , opt_unchoke.begin() + num_opt_unchoke + , opt_unchoke.end() +#ifndef TORRENT_DISABLE_EXTENSIONS + , last_optimistic_unchoke_cmp(m_ses_extensions[plugins_optimistic_unchoke_idx]) +#else + , last_optimistic_unchoke_cmp() +#endif + ); + + // unchoke the first num_opt_unchoke peers in the candidate set + // and make sure that the others are choked + auto opt_unchoke_end = opt_unchoke.begin() + + num_opt_unchoke; + + for (auto i = opt_unchoke.begin(); i != opt_unchoke_end; ++i) + { + torrent_peer* pi = (*i->peer)->peer_info_struct(); + peer_connection* p = static_cast(pi->connection); + if (pi->optimistically_unchoked) + { +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "OPTIMISTIC UNCHOKE" + , "already unchoked | session-time: %d" + , pi->last_optimistically_unchoked); +#endif + TORRENT_ASSERT(!pi->connection->is_choked()); + // remove this peer from prev_opt_unchoke, to prevent us from + // choking it later. This peer gets another round of optimistic + // unchoke + auto const existing = + std::find(prev_opt_unchoke.begin(), prev_opt_unchoke.end(), pi); + TORRENT_ASSERT(existing != prev_opt_unchoke.end()); + prev_opt_unchoke.erase(existing); + } + else + { + TORRENT_ASSERT(p->is_choked()); + std::shared_ptr t = p->associated_torrent().lock(); + bool ret = t->unchoke_peer(*p, true); + TORRENT_ASSERT(ret); + if (ret) + { + pi->optimistically_unchoked = true; + m_stats_counters.inc_stats_counter(counters::num_peers_up_unchoked_optimistic); + pi->last_optimistically_unchoked = std::uint16_t(session_time()); +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "OPTIMISTIC UNCHOKE" + , "session-time: %d", pi->last_optimistically_unchoked); +#endif + } + } + } + + // now, choke all the previous optimistically unchoked peers + for (torrent_peer* pi : prev_opt_unchoke) + { + TORRENT_ASSERT(pi->optimistically_unchoked); + auto* p = static_cast(pi->connection); + std::shared_ptr t = p->associated_torrent().lock(); + pi->optimistically_unchoked = false; + m_stats_counters.inc_stats_counter(counters::num_peers_up_unchoked_optimistic, -1); + t->choke_peer(*p); + } + + // if we have too many unchoked peers now, we need to trigger the regular + // choking logic to choke some + if (m_stats_counters[counters::num_unchoke_slots] + < m_stats_counters[counters::num_peers_up_unchoked_all]) + { + m_unchoke_time_scaler = 0; + } + } + + void session_impl::try_connect_more_peers() + { + if (m_abort) return; + + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit)) + return; + + // this is the maximum number of connections we will + // attempt this tick + int max_connections = m_settings.get_int(settings_pack::connection_speed); + + // this loop will "hand out" connection_speed to the torrents, in a round + // robin fashion, so that every torrent is equally likely to connect to a + // peer + + // boost connections are connections made by torrent connection + // boost, which are done immediately on a tracker response. These + // connections needs to be deducted from the regular connection attempt + // quota for this tick + if (m_boost_connections > 0) + { + if (m_boost_connections > max_connections) + { + m_boost_connections -= max_connections; + max_connections = 0; + } + else + { + max_connections -= m_boost_connections; + m_boost_connections = 0; + } + } + + // zero connections speeds are allowed, we just won't make any connections + if (max_connections <= 0) return; + + // TODO: use a lower limit than m_settings.connections_limit + // to allocate the to 10% or so of connection slots for incoming + // connections + // cap this at max - 1, since we may add one below + int const limit = std::min(m_settings.get_int(settings_pack::connections_limit) + - num_connections(), std::numeric_limits::max() - 1); + + // this logic is here to smooth out the number of new connection + // attempts over time, to prevent connecting a large number of + // sockets, wait 10 seconds, and then try again + if (m_settings.get_bool(settings_pack::smooth_connects) && max_connections > (limit+1) / 2) + max_connections = (limit + 1) / 2; + + aux::vector& want_peers_download = m_torrent_lists[torrent_want_peers_download]; + aux::vector& want_peers_finished = m_torrent_lists[torrent_want_peers_finished]; + + // if no torrent want any peers, just return + if (want_peers_download.empty() && want_peers_finished.empty()) return; + + // if we don't have any connection attempt quota, return + if (max_connections <= 0) return; + + int steps_since_last_connect = 0; + int const num_torrents = int(want_peers_finished.size() + want_peers_download.size()); + for (;;) + { + if (m_next_downloading_connect_torrent >= int(want_peers_download.size())) + m_next_downloading_connect_torrent = 0; + + if (m_next_finished_connect_torrent >= int(want_peers_finished.size())) + m_next_finished_connect_torrent = 0; + + torrent* t = nullptr; + // there are prioritized torrents. Pick one of those + while (!m_prio_torrents.empty()) + { + t = m_prio_torrents.front().first.lock().get(); + --m_prio_torrents.front().second; + if (m_prio_torrents.front().second > 0 + && t != nullptr + && t->want_peers()) break; + m_prio_torrents.pop_front(); + t = nullptr; + } + + if (t == nullptr) + { + if ((m_download_connect_attempts >= m_settings.get_int( + settings_pack::connect_seed_every_n_download) + && !want_peers_finished.empty()) + || want_peers_download.empty()) + { + // pick a finished torrent to give a peer to + t = want_peers_finished[m_next_finished_connect_torrent]; + TORRENT_ASSERT(t->want_peers_finished()); + m_download_connect_attempts = 0; + ++m_next_finished_connect_torrent; + } + else + { + // pick a downloading torrent to give a peer to + t = want_peers_download[m_next_downloading_connect_torrent]; + TORRENT_ASSERT(t->want_peers_download()); + ++m_download_connect_attempts; + ++m_next_downloading_connect_torrent; + } + } + + TORRENT_ASSERT(t->want_peers()); + TORRENT_ASSERT(!t->is_torrent_paused()); + + if (t->try_connect_peer()) + { + --max_connections; + steps_since_last_connect = 0; + m_stats_counters.inc_stats_counter(counters::connection_attempts); + } + + ++steps_since_last_connect; + + // if there are no more free connection slots, abort + if (max_connections == 0) return; + // there are no more torrents that want peers + if (want_peers_download.empty() && want_peers_finished.empty()) break; + // if we have gone a whole loop without + // handing out a single connection, break + if (steps_since_last_connect > num_torrents + 1) break; + // maintain the global limit on number of connections + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit)) break; + } + } + + void session_impl::recalculate_unchoke_slots() + { + TORRENT_ASSERT(is_single_thread()); + + time_point const now = aux::time_now(); + time_duration const unchoke_interval = now - m_last_choke; + m_last_choke = now; + + // if we unchoke everyone, skip this logic + if (settings().get_int(settings_pack::choking_algorithm) == settings_pack::fixed_slots_choker + && settings().get_int(settings_pack::unchoke_slots_limit) < 0) + { + m_stats_counters.set_value(counters::num_unchoke_slots, std::numeric_limits::max()); + return; + } + + // build list of all peers that are + // unchokable. + // TODO: 3 there should be a pre-calculated list of all peers eligible for + // unchoking + std::vector peers; + for (auto i = m_connections.begin(); i != m_connections.end();) + { + std::shared_ptr p = *i; + TORRENT_ASSERT(p); + ++i; + torrent* const t = p->associated_torrent().lock().get(); + torrent_peer* const pi = p->peer_info_struct(); + + if (p->ignore_unchoke_slots() || t == nullptr || pi == nullptr + || pi->web_seed || t->is_paused()) + { + p->reset_choke_counters(); + continue; + } + + if (!p->is_peer_interested() + || p->is_disconnecting() + || p->is_connecting()) + { + // this peer is not unchokable. So, if it's unchoked + // already, make sure to choke it. + if (p->is_choked()) + { + p->reset_choke_counters(); + continue; + } + if (pi && pi->optimistically_unchoked) + { + m_stats_counters.inc_stats_counter(counters::num_peers_up_unchoked_optimistic, -1); + pi->optimistically_unchoked = false; + // force a new optimistic unchoke + m_optimistic_unchoke_time_scaler = 0; + // TODO: post a message to have this happen + // immediately instead of waiting for the next tick + } + t->choke_peer(*p); + p->reset_choke_counters(); + continue; + } + + peers.push_back(p.get()); + } + +#if TORRENT_ABI_VERSION == 1 + // the unchoker wants an estimate of our upload rate capacity + // (used by bittyrant) + int max_upload_rate = upload_rate_limit(m_global_class); + if (m_settings.get_int(settings_pack::choking_algorithm) + == settings_pack::bittyrant_choker + && max_upload_rate == 0) + { + // we don't know at what rate we can upload. If we have a + // measurement of the peak, use that + 10kB/s, otherwise + // assume 20 kB/s + max_upload_rate = std::max(20000, m_peak_up_rate + 10000); + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , performance_alert::bittyrant_with_no_uplimit); + } +#else + int const max_upload_rate = 0; +#endif + + int const allowed_upload_slots = unchoke_sort(peers, max_upload_rate + , unchoke_interval, m_settings); + + m_stats_counters.set_value(counters::num_unchoke_slots + , allowed_upload_slots); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("RECALCULATE UNCHOKE SLOTS: [ peers: %d " + "eligible-peers: %d" + " allowed-slots: %d ]" + , int(m_connections.size()) + , int(peers.size()) + , allowed_upload_slots); + } +#endif + + int const unchoked_counter_optimistic + = int(m_stats_counters[counters::num_peers_up_unchoked_optimistic]); + int const num_opt_unchoke = (unchoked_counter_optimistic == 0) + ? std::max(1, allowed_upload_slots / 5) : unchoked_counter_optimistic; + + int unchoke_set_size = allowed_upload_slots - num_opt_unchoke; + + // go through all the peers and unchoke the first ones and choke + // all the other ones. + for (auto p : peers) + { + TORRENT_ASSERT(p != nullptr); + TORRENT_ASSERT(!p->ignore_unchoke_slots()); + + // this will update the m_uploaded_at_last_unchoke + p->reset_choke_counters(); + + torrent* t = p->associated_torrent().lock().get(); + TORRENT_ASSERT(t); + + if (unchoke_set_size > 0) + { + // yes, this peer should be unchoked + if (p->is_choked()) + { + if (!t->unchoke_peer(*p)) + continue; + } + + --unchoke_set_size; + + TORRENT_ASSERT(p->peer_info_struct()); + if (p->peer_info_struct()->optimistically_unchoked) + { + // force a new optimistic unchoke + // since this one just got promoted into the + // proper unchoke set + m_optimistic_unchoke_time_scaler = 0; + p->peer_info_struct()->optimistically_unchoked = false; + m_stats_counters.inc_stats_counter(counters::num_peers_up_unchoked_optimistic, -1); + } + } + else + { + // no, this peer should be choked + TORRENT_ASSERT(p->peer_info_struct()); + if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) + t->choke_peer(*p); + } + } + } + + std::shared_ptr session_impl::delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& e : m_ses_extensions[plugins_all_idx]) + { + add_torrent_params p; + if (e->on_unknown_torrent(info_hash, peer_connection_handle(pc->self()), p)) + { + error_code ec; + torrent_handle handle = add_torrent(std::move(p), ec); + + return handle.native_handle(); + } + } +#else + TORRENT_UNUSED(pc); + TORRENT_UNUSED(info_hash); +#endif + return std::shared_ptr(); + } + + // the return value from this function is valid only as long as the + // session is locked! + std::weak_ptr session_impl::find_torrent(sha1_hash const& info_hash) const + { + TORRENT_ASSERT(is_single_thread()); + + auto const i = m_torrents.find(info_hash); +#if TORRENT_USE_INVARIANT_CHECKS + for (auto const& te : m_torrents) + { + TORRENT_ASSERT(te.second); + } +#endif + if (i != m_torrents.end()) return i->second; + return std::weak_ptr(); + } + + void session_impl::insert_torrent(sha1_hash const& ih, std::shared_ptr const& t +#if TORRENT_ABI_VERSION == 1 + , std::string const uuid +#endif + ) + { + sha1_hash const next_lsd = m_next_lsd_torrent != m_torrents.end() + ? m_next_lsd_torrent->first : sha1_hash(); +#ifndef TORRENT_DISABLE_DHT + sha1_hash const next_dht = m_next_dht_torrent != m_torrents.end() + ? m_next_dht_torrent->first : sha1_hash(); +#endif + + float const load_factor = m_torrents.load_factor(); + + m_torrents.emplace(ih, t); + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = {'r', 'e', 'q', '2'}; + hasher h(req2); + h.update(ih); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + m_obfuscated_torrents.emplace(h.final(), t); +#endif + + // if this insert made the hash grow, the iterators became invalid + // we need to reset them + if (m_torrents.load_factor() < load_factor) + { + // this indicates the hash table re-hashed + if (!next_lsd.is_all_zeros()) + m_next_lsd_torrent = m_torrents.find(next_lsd); +#ifndef TORRENT_DISABLE_DHT + if (!next_dht.is_all_zeros()) + m_next_dht_torrent = m_torrents.find(next_dht); +#endif + } + +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + if (!uuid.empty()) m_uuids.insert(std::make_pair(uuid, t)); +#endif + + t->added(); + } + + void session_impl::set_queue_position(torrent* me, queue_position_t p) + { + queue_position_t const current_pos = me->queue_position(); + if (current_pos == p) return; + + if (p >= queue_position_t{0} && current_pos == no_pos) + { + // we're inserting the torrent into the download queue + queue_position_t const last = m_download_queue.end_index(); + if (p >= last) + { + m_download_queue.push_back(me); + me->set_queue_position_impl(last); + } + else + { + m_download_queue.insert(m_download_queue.begin() + static_cast(p), me); + for (queue_position_t i = p; i < m_download_queue.end_index(); ++i) + { + m_download_queue[i]->set_queue_position_impl(i); + } + } + } + else if (p < queue_position_t{}) + { + // we're removing the torrent from the download queue + TORRENT_ASSERT(current_pos >= queue_position_t{0}); + TORRENT_ASSERT(p == no_pos); + TORRENT_ASSERT(m_download_queue[current_pos] == me); + m_download_queue.erase(m_download_queue.begin() + static_cast(current_pos)); + me->set_queue_position_impl(no_pos); + for (queue_position_t i = current_pos; i < m_download_queue.end_index(); ++i) + { + m_download_queue[i]->set_queue_position_impl(i); + } + } + else if (p < current_pos) + { + // we're moving the torrent up the queue + torrent* tmp = me; + for (queue_position_t i = p; i <= current_pos; ++i) + { + std::swap(m_download_queue[i], tmp); + m_download_queue[i]->set_queue_position_impl(i); + } + TORRENT_ASSERT(tmp == me); + } + else if (p > current_pos) + { + // we're moving the torrent down the queue + p = std::min(p, prev(m_download_queue.end_index())); + for (queue_position_t i = current_pos; i < p; ++i) + { + m_download_queue[i] = m_download_queue[next(i)]; + m_download_queue[i]->set_queue_position_impl(i); + } + m_download_queue[p] = me; + me->set_queue_position_impl(p); + } + + trigger_auto_manage(); + } + +#if !defined TORRENT_DISABLE_ENCRYPTION + torrent const* session_impl::find_encrypted_torrent(sha1_hash const& info_hash + , sha1_hash const& xor_mask) + { + sha1_hash obfuscated = info_hash; + obfuscated ^= xor_mask; + + auto const i = m_obfuscated_torrents.find(obfuscated); + if (i == m_obfuscated_torrents.end()) return nullptr; + return i->second.get(); + } +#endif + +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + std::weak_ptr session_impl::find_torrent(std::string const& uuid) const + { + TORRENT_ASSERT(is_single_thread()); + + auto const i = m_uuids.find(uuid); + if (i != m_uuids.end()) return i->second; + return std::weak_ptr(); + } +#endif + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + std::vector> session_impl::find_collection( + std::string const& collection) const + { + std::vector> ret; + for (auto const& tp : m_torrents) + { + std::shared_ptr t = tp.second; + if (!t) continue; + std::vector const& c = t->torrent_file().collections(); + if (std::find(c.begin(), c.end(), collection) == c.end()) continue; + ret.push_back(t); + } + return ret; + } +#endif //TORRENT_DISABLE_MUTABLE_TORRENTS + + namespace { + + // returns true if lhs is a better disconnect candidate than rhs + bool compare_disconnect_torrent(session_impl::torrent_map::value_type const& lhs + , session_impl::torrent_map::value_type const& rhs) + { + // a torrent with 0 peers is never a good disconnect candidate + // since there's nothing to disconnect + if ((lhs.second->num_peers() == 0) != (rhs.second->num_peers() == 0)) + return lhs.second->num_peers() != 0; + + // other than that, always prefer to disconnect peers from seeding torrents + // in order to not harm downloading ones + if (lhs.second->is_seed() != rhs.second->is_seed()) + return lhs.second->is_seed(); + + return lhs.second->num_peers() > rhs.second->num_peers(); + } + + } // anonymous namespace + + std::weak_ptr session_impl::find_disconnect_candidate_torrent() const + { + auto const i = std::min_element(m_torrents.begin(), m_torrents.end() + , &compare_disconnect_torrent); + + TORRENT_ASSERT(i != m_torrents.end()); + if (i == m_torrents.end()) return std::shared_ptr(); + + return i->second; + } + +#ifndef TORRENT_DISABLE_LOGGING + bool session_impl::should_log() const + { + return m_alerts.should_post(); + } + + TORRENT_FORMAT(2,3) + void session_impl::session_log(char const* fmt, ...) const noexcept try + { + if (!m_alerts.should_post()) return; + + va_list v; + va_start(v, fmt); + m_alerts.emplace_alert(fmt, v); + va_end(v); + } + catch (std::exception const&) {} +#endif + + void session_impl::get_torrent_status(std::vector* ret + , std::function const& pred + , status_flags_t const flags) const + { + for (auto const& t : m_torrents) + { + if (t.second->is_aborted()) continue; + torrent_status st; + t.second->status(&st, flags); + if (!pred(st)) continue; + ret->push_back(std::move(st)); + } + } + + void session_impl::refresh_torrent_status(std::vector* ret + , status_flags_t const flags) const + { + for (auto& st : *ret) + { + auto t = st.handle.m_torrent.lock(); + if (!t) continue; + t->status(&st, flags); + } + } + + void session_impl::post_torrent_updates(status_flags_t const flags) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(is_single_thread()); + + std::vector& state_updates + = m_torrent_lists[aux::session_impl::torrent_state_updates]; + +#if TORRENT_USE_ASSERTS + m_posting_torrent_updates = true; +#endif + + std::vector status; + status.reserve(state_updates.size()); + + // TODO: it might be a nice feature here to limit the number of torrents + // to send in a single update. By just posting the first n torrents, they + // would nicely be round-robined because the torrent lists are always + // pushed back. Perhaps the status_update_alert could even have a fixed + // array of n entries rather than a vector, to further improve memory + // locality. + for (auto& t : state_updates) + { + TORRENT_ASSERT(t->m_links[aux::session_impl::torrent_state_updates].in_list()); + status.emplace_back(); + // querying accurate download counters may require + // the torrent to be loaded. Loading a torrent, and evicting another + // one will lead to calling state_updated(), which screws with + // this list while we're working on it, and break things + t->status(&status.back(), flags); + t->clear_in_state_update(); + } + state_updates.clear(); + +#if TORRENT_USE_ASSERTS + m_posting_torrent_updates = false; +#endif + + m_alerts.emplace_alert(std::move(status)); + } + + void session_impl::post_session_stats() + { + if (!m_posted_stats_header) + { + m_posted_stats_header = true; + m_alerts.emplace_alert(); + } + m_disk_thread.update_stats_counters(m_stats_counters); + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + m_dht->update_stats_counters(m_stats_counters); +#endif + + m_stats_counters.set_value(counters::limiter_up_queue + , m_upload_rate.queue_size()); + m_stats_counters.set_value(counters::limiter_down_queue + , m_download_rate.queue_size()); + + m_stats_counters.set_value(counters::limiter_up_bytes + , m_upload_rate.queued_bytes()); + m_stats_counters.set_value(counters::limiter_down_bytes + , m_download_rate.queued_bytes()); + + m_alerts.emplace_alert(m_stats_counters); + } + + void session_impl::post_dht_stats() + { + std::vector requests; + std::vector table; + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + m_dht->dht_status(table, requests); +#endif + + m_alerts.emplace_alert(std::move(table), std::move(requests)); + } + + std::vector session_impl::get_torrents() const + { + std::vector ret; + + for (auto const& i : m_torrents) + { + if (i.second->is_aborted()) continue; + ret.push_back(torrent_handle(i.second)); + } + return ret; + } + + torrent_handle session_impl::find_torrent_handle(sha1_hash const& info_hash) + { + return torrent_handle(find_torrent(info_hash)); + } + + void session_impl::async_add_torrent(add_torrent_params* params) + { + std::unique_ptr holder(params); + +#if TORRENT_ABI_VERSION == 1 + if (!params->ti && string_begins_no_case("file://", params->url.c_str())) + { + if (!m_torrent_load_thread) + m_torrent_load_thread.reset(new work_thread_t()); + + m_torrent_load_thread->ios.post([params, this] + { + std::string const torrent_file_path = resolve_file_url(params->url); + params->url.clear(); + + std::unique_ptr holder2(params); + error_code ec; + params->ti = std::make_shared(torrent_file_path, ec); + this->m_io_service.post(std::bind(&session_impl::on_async_load_torrent + , this, params, ec)); + holder2.release(); + }); + holder.release(); + return; + } +#endif + + error_code ec; + add_torrent(std::move(*params), ec); + } + +#if TORRENT_ABI_VERSION == 1 + void session_impl::on_async_load_torrent(add_torrent_params* params, error_code ec) + { + std::unique_ptr holder(params); + + if (ec) + { + m_alerts.emplace_alert(torrent_handle() + , *params, ec); + return; + } + TORRENT_ASSERT(params->ti->is_valid()); + TORRENT_ASSERT(params->ti->num_files() > 0); + params->url.clear(); + add_torrent(std::move(*params), ec); + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + void session_impl::add_extensions_to_torrent( + std::shared_ptr const& torrent_ptr, void* userdata) + { + for (auto& e : m_ses_extensions[plugins_all_idx]) + { + std::shared_ptr tp(e->new_torrent( + torrent_ptr->get_handle(), userdata)); + if (tp) torrent_ptr->add_extension(std::move(tp)); + } + } +#endif + + torrent_handle session_impl::add_torrent(add_torrent_params&& params + , error_code& ec) + { + // params is updated by add_torrent_impl() + std::shared_ptr torrent_ptr; + + // in case there's an error, make sure to abort the torrent before leaving + // the scope + auto abort_torrent = aux::scope_end([&]{ if (torrent_ptr) torrent_ptr->abort(); }); + + bool added; + // TODO: 3 perhaps params could be moved into the torrent object, instead + // of it being copied by the torrent constructor + std::tie(torrent_ptr, added) = add_torrent_impl(params, ec); + + torrent_handle const handle(torrent_ptr); + m_alerts.emplace_alert(handle, params, ec); + + if (!torrent_ptr) return handle; + + // params.info_hash should have been initialized by add_torrent_impl() + TORRENT_ASSERT(params.info_hash != sha1_hash(nullptr)); + +#ifndef TORRENT_DISABLE_DHT + if (params.ti) + { + for (auto const& n : params.ti->nodes()) + add_dht_node_name(n); + } +#endif + +#if TORRENT_ABI_VERSION == 1 + if (m_alerts.should_post()) + m_alerts.emplace_alert(handle); +#endif + + // if this was an existing torrent, we can't start it again, or add + // another set of plugins etc. we're done + if (!added) + { + abort_torrent.disarm(); + return handle; + } + + torrent_ptr->set_ip_filter(m_ip_filter); + torrent_ptr->start(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : params.extensions) + { + std::shared_ptr tp(ext(handle, params.userdata)); + if (tp) torrent_ptr->add_extension(std::move(tp)); + } + + add_extensions_to_torrent(torrent_ptr, params.userdata); +#endif + + insert_torrent(params.info_hash, torrent_ptr +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + , params.uuid.empty() + ? params.url.empty() ? std::string() + : params.url + : params.uuid +#endif + ); + + // once we successfully add the torrent, we can disarm the abort action + abort_torrent.disarm(); + + // recalculate auto-managed torrents sooner (or put it off) + // if another torrent will be added within one second from now + // we want to put it off again anyway. So that while we're adding + // a boat load of torrents, we postpone the recalculation until + // we're done adding them all (since it's kind of an expensive operation) + if (params.flags & torrent_flags::auto_managed) + { + const int max_downloading = settings().get_int(settings_pack::active_downloads); + const int max_seeds = settings().get_int(settings_pack::active_seeds); + const int max_active = settings().get_int(settings_pack::active_limit); + + const int num_downloading + = int(torrent_list(session_interface::torrent_downloading_auto_managed).size()); + const int num_seeds + = int(torrent_list(session_interface::torrent_seeding_auto_managed).size()); + const int num_active = num_downloading + num_seeds; + + // there's no point in triggering the auto manage logic early if we + // don't have a reason to believe anything will change. It's kind of + // expensive. + if ((num_downloading < max_downloading + || num_seeds < max_seeds) + && num_active < max_active) + { + trigger_auto_manage(); + } + } + + return handle; + } + + std::pair, bool> + session_impl::add_torrent_impl(add_torrent_params& params, error_code& ec) + { + TORRENT_ASSERT(!params.save_path.empty()); + + using ptr_t = std::shared_ptr; + +#if TORRENT_ABI_VERSION == 1 + if (string_begins_no_case("magnet:", params.url.c_str())) + { + parse_magnet_uri(params.url, params, ec); + if (ec) return std::make_pair(ptr_t(), false); + params.url.clear(); + } + + if (!params.ti && string_begins_no_case("file://", params.url.c_str())) + { + std::string const torrent_file_path = resolve_file_url(params.url); + params.url.clear(); + auto t = std::make_shared(torrent_file_path, std::ref(ec), 0); + if (ec) return std::make_pair(ptr_t(), false); + params.ti = t; + } +#endif + + if (params.ti && !params.ti->is_valid()) + { + ec = errors::no_metadata; + return std::make_pair(ptr_t(), false); + } + + if (params.ti && params.ti->is_valid() && params.ti->num_files() == 0) + { + ec = errors::no_files_in_torrent; + return std::make_pair(ptr_t(), false); + } + + if (params.ti + && !params.info_hash.is_all_zeros() + && params.info_hash != params.ti->info_hash()) + { + ec = errors::mismatching_info_hash; + return std::make_pair(ptr_t(), false); + } + +#ifndef TORRENT_DISABLE_DHT + // add params.dht_nodes to the DHT, if enabled + for (auto const& n : params.dht_nodes) + add_dht_node_name(n); +#endif + + INVARIANT_CHECK; + + if (is_aborted()) + { + ec = errors::session_is_closing; + return std::make_pair(ptr_t(), false); + } + + // figure out the info hash of the torrent and make sure params.info_hash + // is set correctly + if (params.ti) params.info_hash = params.ti->info_hash(); +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + else if (!params.url.empty()) + { + // in order to avoid info-hash collisions, for + // torrents where we don't have an info-hash, but + // just a URL, set the temporary info-hash to the + // hash of the URL. This will be changed once we + // have the actual .torrent file + params.info_hash = hasher(¶ms.url[0], int(params.url.size())).final(); + } +#endif + + if (params.info_hash.is_all_zeros()) + { + ec = errors::missing_info_hash_in_uri; + return std::make_pair(ptr_t(), false); + } + + // is the torrent already active? + std::shared_ptr torrent_ptr = find_torrent(params.info_hash).lock(); +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + if (!torrent_ptr && !params.uuid.empty()) torrent_ptr = find_torrent(params.uuid).lock(); + // if we still can't find the torrent, look for it by url + if (!torrent_ptr && !params.url.empty()) + { + auto const i = std::find_if(m_torrents.begin(), m_torrents.end() + , [¶ms](torrent_map::value_type const& te) + { return te.second->url() == params.url; }); + if (i != m_torrents.end()) + torrent_ptr = i->second; + } +#endif + + if (torrent_ptr) + { + if (!(params.flags & torrent_flags::duplicate_is_error)) + { +#if TORRENT_ABI_VERSION == 1 + //deprecated in 1.2 + if (!params.uuid.empty() && torrent_ptr->uuid().empty()) + torrent_ptr->set_uuid(params.uuid); + if (!params.url.empty() && torrent_ptr->url().empty()) + torrent_ptr->set_url(params.url); +#endif + return std::make_pair(torrent_ptr, false); + } + + ec = errors::duplicate_torrent; + return std::make_pair(ptr_t(), false); + } + + // make sure we have enough memory in the torrent lists up-front, + // since when torrents changes states, we cannot allocate memory that + // might fail. + size_t const num_torrents = m_torrents.size(); + for (auto& l : m_torrent_lists) + { + l.reserve(num_torrents + 1); + } + + torrent_ptr = std::make_shared(*this, m_paused, params); + torrent_ptr->set_queue_position(m_download_queue.end_index()); + + return std::make_pair(torrent_ptr, true); + } + + void session_impl::update_outgoing_interfaces() + { + std::string const net_interfaces = m_settings.get_str(settings_pack::outgoing_interfaces); + + // declared in string_util.hpp + parse_comma_separated_string(net_interfaces, m_outgoing_interfaces); + +#ifndef TORRENT_DISABLE_LOGGING + if (!net_interfaces.empty() && m_outgoing_interfaces.empty()) + { + session_log("ERROR: failed to parse outgoing interface list: %s" + , net_interfaces.c_str()); + } +#endif + } + + tcp::endpoint session_impl::bind_outgoing_socket(socket_type& s + , address const& remote_address, error_code& ec) const + { + tcp::endpoint bind_ep(address_v4(), 0); + if (m_settings.get_int(settings_pack::outgoing_port) > 0) + { +#ifdef TORRENT_WINDOWS + s.set_option(exclusive_address_use(true), ec); +#else + s.set_option(tcp::acceptor::reuse_address(true), ec); +#endif + // ignore errors because the underlying socket may not + // be opened yet. This happens when we're routing through + // a proxy. In that case, we don't yet know the address of + // the proxy server, and more importantly, we don't know + // the address family of its address. This means we can't + // open the socket yet. The socks abstraction layer defers + // opening it. + ec.clear(); + bind_ep.port(std::uint16_t(next_port())); + } + + if (is_utp(s)) + { + // TODO: factor out this logic into a separate function for unit + // testing + + utp_socket_impl* impl = nullptr; + transport ssl = transport::plaintext; +#ifdef TORRENT_USE_OPENSSL + if (s.get>() != nullptr) + { + impl = s.get>()->next_layer().get_impl(); + ssl = transport::ssl; + } + else +#endif + impl = s.get()->get_impl(); + + std::vector> with_gateways; + std::shared_ptr match; + for (auto& ls : m_listen_sockets) + { + if (is_v4(ls->local_endpoint) != remote_address.is_v4()) continue; + if (ls->ssl != ssl) continue; + if (!(ls->flags & listen_socket_t::local_network)) + with_gateways.push_back(ls); + + if (match_addr_mask(ls->local_endpoint.address(), remote_address, ls->netmask)) + { + // is this better than the previous match? + match = ls; + } + } + if (!match && !with_gateways.empty()) + match = with_gateways[random(std::uint32_t(with_gateways.size() - 1))]; + + if (match) + { + utp_init_socket(impl, match); + return match->local_endpoint; + } + ec.assign(boost::system::errc::not_supported, generic_category()); + return {}; + } + + if (!m_outgoing_interfaces.empty()) + { + if (m_interface_index >= m_outgoing_interfaces.size()) m_interface_index = 0; + std::string const& ifname = m_outgoing_interfaces[m_interface_index++]; + + bind_ep.address(bind_socket_to_device(m_io_service, s + , remote_address.is_v4() ? tcp::v4() : tcp::v6() + , ifname.c_str(), bind_ep.port(), ec)); + return bind_ep; + } + + // if we're not binding to a specific interface, bind + // to the same protocol family as the target endpoint + if (is_any(bind_ep.address())) + { + if (remote_address.is_v6()) + bind_ep.address(address_v6::any()); + else + bind_ep.address(address_v4::any()); + } + + s.bind(bind_ep, ec); + return bind_ep; + } + + // verify that ``addr``s interface allows incoming connections + bool session_impl::verify_incoming_interface(address const& addr) + { + auto const iter = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , [&addr](std::shared_ptr const& s) + { return s->local_endpoint.address() == addr; }); + return iter == m_listen_sockets.end() + ? false + : bool((*iter)->flags & listen_socket_t::accept_incoming); + } + + // verify that the given local address satisfies the requirements of + // the outgoing interfaces. i.e. that one of the allowed outgoing + // interfaces has this address. For uTP sockets, which are all backed + // by an unconnected udp socket, we won't be able to tell what local + // address is used for this peer's packets, in that case, just make + // sure one of the allowed interfaces exists and maybe that it's the + // default route. For systems that have SO_BINDTODEVICE, it should be + // enough to just know that one of the devices exist + bool session_impl::verify_bound_address(address const& addr, bool utp + , error_code& ec) + { + TORRENT_UNUSED(utp); + + // we have specific outgoing interfaces specified. Make sure the + // local endpoint for this socket is bound to one of the allowed + // interfaces. the list can be a mixture of interfaces and IP + // addresses. + for (auto const& s : m_outgoing_interfaces) + { + error_code err; + address const ip = make_address(s.c_str(), err); + if (err) continue; + if (ip == addr) return true; + } + + // we didn't find the address as an IP in the interface list. Now, + // resolve which device (if any) has this IP address. + std::string const device = device_for_address(addr, m_io_service, ec); + if (ec) return false; + + // if no device was found to have this address, we fail + if (device.empty()) return false; + + return std::any_of(m_outgoing_interfaces.begin(), m_outgoing_interfaces.end() + , [&device](std::string const& s) { return s == device; }); + } + + bool session_impl::has_lsd() const + { + return std::any_of(m_listen_sockets.begin(), m_listen_sockets.end() + , [](std::shared_ptr const& s) { return bool(s->lsd); }); + } + + void session_impl::remove_torrent(const torrent_handle& h + , remove_flags_t const options) + { + INVARIANT_CHECK; + + std::shared_ptr tptr = h.m_torrent.lock(); + if (!tptr) return; + + m_alerts.emplace_alert(tptr->get_handle() + , tptr->info_hash()); + + remove_torrent_impl(tptr, options); + + tptr->abort(); + } + + void session_impl::remove_torrent_impl(std::shared_ptr tptr + , remove_flags_t const options) + { +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // remove from uuid list + if (!tptr->uuid().empty()) + { + auto const j = m_uuids.find(tptr->uuid()); + if (j != m_uuids.end()) m_uuids.erase(j); + } +#endif + + auto i = m_torrents.find(tptr->torrent_file().info_hash()); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // this torrent might be filed under the URL-hash + if (i == m_torrents.end() && !tptr->url().empty()) + { + i = m_torrents.find(hasher(tptr->url()).final()); + } +#endif + + if (i == m_torrents.end()) return; + + torrent& t = *i->second; + if (options) + { + if (!t.delete_files(options)) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(t.get_handle() + , error_code(), t.torrent_file().info_hash()); + } + } + + tptr->update_gauge(); + +#if TORRENT_USE_ASSERTS + sha1_hash i_hash = t.torrent_file().info_hash(); +#endif +#ifndef TORRENT_DISABLE_DHT + if (i == m_next_dht_torrent) + ++m_next_dht_torrent; +#endif + if (i == m_next_lsd_torrent) + ++m_next_lsd_torrent; + + m_torrents.erase(i); + tptr->removed(); + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = {'r', 'e', 'q', '2'}; + hasher h(req2); + h.update(tptr->info_hash()); + m_obfuscated_torrents.erase(h.final()); +#endif + +#ifndef TORRENT_DISABLE_DHT + if (m_next_dht_torrent == m_torrents.end()) + m_next_dht_torrent = m_torrents.begin(); +#endif + if (m_next_lsd_torrent == m_torrents.end()) + m_next_lsd_torrent = m_torrents.begin(); + + // this torrent may open up a slot for a queued torrent + trigger_auto_manage(); + + TORRENT_ASSERT(m_torrents.find(i_hash) == m_torrents.end()); + } + +#if TORRENT_ABI_VERSION == 1 + + void session_impl::update_ssl_listen() + { + INVARIANT_CHECK; + + // this function maps the previous functionality of just setting the ssl + // listen port in order to enable the ssl listen sockets, to the new + // mechanism where SSL sockets are specified in listen_interfaces. + std::vector ignore; + auto current_ifaces = parse_listen_interfaces( + m_settings.get_str(settings_pack::listen_interfaces), ignore); + // these are the current interfaces we have, first remove all the SSL + // interfaces + current_ifaces.erase(std::remove_if(current_ifaces.begin(), current_ifaces.end() + , std::bind(&listen_interface_t::ssl, _1)), current_ifaces.end()); + + int const ssl_listen_port = m_settings.get_int(settings_pack::ssl_listen); + + // setting a port of 0 means to disable listening on SSL, so just update + // the interface list with the new list, and we're done + if (ssl_listen_port == 0) + { + m_settings.set_str(settings_pack::listen_interfaces + , print_listen_interfaces(current_ifaces)); + return; + } + + std::vector new_ifaces; + std::transform(current_ifaces.begin(), current_ifaces.end() + , std::back_inserter(new_ifaces), [](listen_interface_t in) + { in.ssl = true; return in; }); + + current_ifaces.insert(current_ifaces.end(), new_ifaces.begin(), new_ifaces.end()); + + m_settings.set_str(settings_pack::listen_interfaces + , print_listen_interfaces(current_ifaces)); + } +#endif // TORRENT_ABI_VERSION + + void session_impl::update_listen_interfaces() + { + INVARIANT_CHECK; + + std::string const net_interfaces = m_settings.get_str(settings_pack::listen_interfaces); + std::vector err; + m_listen_interfaces = parse_listen_interfaces(net_interfaces, err); + + for (auto const& e : err) + { + m_alerts.emplace_alert(e, lt::address{}, 0 + , operation_t::parse_address, errors::invalid_port, lt::socket_type_t::tcp); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("update listen interfaces: %s", net_interfaces.c_str()); + session_log("parsed listen interfaces count: %d, ifaces: %s" + , int(m_listen_interfaces.size()) + , print_listen_interfaces(m_listen_interfaces).c_str()); + } +#endif + } + + void session_impl::update_privileged_ports() + { + if (m_settings.get_bool(settings_pack::no_connect_privileged_ports)) + { + m_port_filter.add_rule(0, 1024, port_filter::blocked); + + // Close connections whose endpoint is filtered + // by the new ip-filter + for (auto const& t : m_torrents) + t.second->port_filter_updated(); + } + else + { + m_port_filter.add_rule(0, 1024, 0); + } + } + + void session_impl::update_auto_sequential() + { + for (auto& i : m_torrents) + i.second->update_auto_sequential(); + } + + void session_impl::update_max_failcount() + { + for (auto& i : m_torrents) + i.second->update_max_failcount(); + } + + void session_impl::update_resolver_cache_timeout() + { + int const timeout = m_settings.get_int(settings_pack::resolver_cache_timeout); + m_host_resolver.set_cache_timeout(seconds(timeout)); + } + + void session_impl::update_proxy() + { + for (auto& i : m_listen_sockets) + i->udp_sock->sock.set_proxy_settings(proxy(), m_alerts); + } + + void session_impl::update_ip_notifier() + { + if (m_settings.get_bool(settings_pack::enable_ip_notifier)) + start_ip_notifier(); + else + stop_ip_notifier(); + } + + void session_impl::update_upnp() + { + if (m_settings.get_bool(settings_pack::enable_upnp)) + start_upnp(); + else + stop_upnp(); + } + + void session_impl::update_natpmp() + { + if (m_settings.get_bool(settings_pack::enable_natpmp)) + start_natpmp(); + else + stop_natpmp(); + } + + void session_impl::update_lsd() + { + if (m_settings.get_bool(settings_pack::enable_lsd)) + start_lsd(); + else + stop_lsd(); + } + + void session_impl::update_dht() + { +#ifndef TORRENT_DISABLE_DHT + if (m_settings.get_bool(settings_pack::enable_dht)) + { + if (!m_settings.get_str(settings_pack::dht_bootstrap_nodes).empty() + && m_dht_router_nodes.empty()) + { + // if we have bootstrap nodes configured, make sure we initiate host + // name lookups. once these complete, the DHT will be started. + // they are tracked by m_outstanding_router_lookups + update_dht_bootstrap_nodes(); + } + else + { + start_dht(); + } + } + else + stop_dht(); +#endif + } + + void session_impl::update_dht_bootstrap_nodes() + { +#ifndef TORRENT_DISABLE_DHT + if (!m_settings.get_bool(settings_pack::enable_dht)) return; + + std::string const& node_list = m_settings.get_str(settings_pack::dht_bootstrap_nodes); + std::vector> nodes; + parse_comma_separated_string_port(node_list, nodes); + +#ifndef TORRENT_DISABLE_LOGGING + if (!node_list.empty() && nodes.empty()) + { + session_log("ERROR: failed to parse DHT bootstrap list: %s", node_list.c_str()); + } +#endif + for (auto const& n : nodes) + add_dht_router(n); +#endif + } + + void session_impl::update_dht_settings() + { +#ifndef TORRENT_DISABLE_DHT + bool const prefer_verified_nodes = m_settings.get_bool( + settings_pack::dht_prefer_verified_node_ids); + + m_dht_settings.prefer_verified_node_ids = prefer_verified_nodes; +#endif + } + + void session_impl::update_count_slow() + { + error_code ec; + for (auto const& tp : m_torrents) + { + tp.second->on_inactivity_tick(ec); + } + } + + // TODO: 2 this function should be removed and users need to deal with the + // more generic case of having multiple listen ports + std::uint16_t session_impl::listen_port() const + { + return listen_port(nullptr); + } + + std::uint16_t session_impl::listen_port(listen_socket_t* sock) const + { + if (m_listen_sockets.empty()) return 0; + if (sock) + { + // if we're using a proxy, we won't be able to accept any TCP + // connections. Not even uTP connections via the port we know about. + // The DHT may use the implied port to make it work, but the port we + // announce here has no relevance for that. + if (sock->flags & listen_socket_t::proxy) + return 0; + + if (!(sock->flags & listen_socket_t::accept_incoming)) + return 0; + + return std::uint16_t(sock->tcp_external_port()); + } + +#ifdef TORRENT_USE_OPENSSL + for (auto const& s : m_listen_sockets) + { + if (!(s->flags & listen_socket_t::accept_incoming)) continue; + if (s->ssl == transport::plaintext) + return std::uint16_t(s->tcp_external_port()); + } + return 0; +#else + sock = m_listen_sockets.front().get(); + if (!(sock->flags & listen_socket_t::accept_incoming)) return 0; + return std::uint16_t(sock->tcp_external_port()); +#endif + } + + // TODO: 2 this function should be removed and users need to deal with the + // more generic case of having multiple ssl ports + std::uint16_t session_impl::ssl_listen_port() const + { + return ssl_listen_port(nullptr); + } + + std::uint16_t session_impl::ssl_listen_port(listen_socket_t* sock) const + { +#ifdef TORRENT_USE_OPENSSL + if (sock) + { + if (!(sock->flags & listen_socket_t::accept_incoming)) return 0; + return std::uint16_t(sock->tcp_external_port()); + } + + if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none) + return 0; + + for (auto const& s : m_listen_sockets) + { + if (!(s->flags & listen_socket_t::accept_incoming)) continue; + if (s->ssl == transport::ssl) + return std::uint16_t(s->tcp_external_port()); + } +#else + TORRENT_UNUSED(sock); +#endif + return 0; + } + + int session_impl::get_listen_port(transport const ssl, aux::listen_socket_handle const& s) + { + auto socket = s.get(); + if (socket->ssl != ssl) + { + auto alt_socket = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , [&](std::shared_ptr const& e) + { + return e->ssl == ssl + && e->external_address.external_address() + == socket->external_address.external_address(); + }); + if (alt_socket != m_listen_sockets.end()) + socket = alt_socket->get(); + } + return socket->udp_external_port(); + } + + int session_impl::listen_port(transport const ssl, address const& local_addr) + { + auto socket = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , [&](std::shared_ptr const& e) + { + if (!(e->flags & listen_socket_t::accept_incoming)) return false; + auto const& listen_addr = e->external_address.external_address(); + return e->ssl == ssl + && (listen_addr == local_addr + || (listen_addr.is_v4() == local_addr.is_v4() && listen_addr.is_unspecified())); + }); + if (socket != m_listen_sockets.end()) + return (*socket)->tcp_external_port(); + return 0; + } + + void session_impl::announce_lsd(sha1_hash const& ih, int port) + { + // use internal listen port for local peers + for (auto const& s : m_listen_sockets) + { + if (s->lsd) s->lsd->announce(ih, port); + } + } + + void session_impl::on_lsd_peer(tcp::endpoint const& peer, sha1_hash const& ih) + { + m_stats_counters.inc_stats_counter(counters::on_lsd_peer_counter); + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + std::shared_ptr t = find_torrent(ih).lock(); + if (!t) return; + // don't add peers from lsd to private torrents + if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + && !m_settings.get_bool(settings_pack::allow_i2p_mixed))) return; + + t->add_peer(peer, peer_info::lsd); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + error_code ec; + t->debug_log("lsd add_peer() [ %s ]" + , peer.address().to_string(ec).c_str()); + } +#endif + + t->do_connect_boost(); + + if (m_alerts.should_post()) + m_alerts.emplace_alert(t->get_handle(), peer); + } + + void session_impl::start_natpmp(aux::listen_socket_t& s) + { + // don't create mappings for local IPv6 addresses + // they can't be reached from outside of the local network anyways + if (is_v6(s.local_endpoint) && is_local(s.local_endpoint.address())) + return; + + if (!s.natpmp_mapper + && !(s.flags & listen_socket_t::local_network) + && !(s.flags & listen_socket_t::proxy)) + { + // the natpmp constructor may fail and call the callbacks + // into the session_impl. + s.natpmp_mapper = std::make_shared(m_io_service, *this); + ip_interface ip; + ip.interface_address = s.local_endpoint.address(); + ip.netmask = s.netmask; + std::strncpy(ip.name, s.device.c_str(), sizeof(ip.name) - 1); + ip.name[sizeof(ip.name) - 1] = '\0'; + s.natpmp_mapper->start(ip); + } + } + + namespace { + bool find_tcp_port_mapping(portmap_transport const transport + , port_mapping_t mapping, std::shared_ptr const& ls) + { + return ls->tcp_port_mapping[transport].mapping == mapping; + } + + bool find_udp_port_mapping(portmap_transport const transport + , port_mapping_t mapping, std::shared_ptr const& ls) + { + return ls->udp_port_mapping[transport].mapping == mapping; + } + } + + void session_impl::on_port_mapping(port_mapping_t const mapping + , address const& ip, int port + , portmap_protocol const proto, error_code const& ec + , portmap_transport const transport) + { + TORRENT_ASSERT(is_single_thread()); + + // NOTE: don't assume that if ec != 0, the rest of the logic + // is not necessary, the ports still need to be set, in other + // words, don't early return without careful review of the + // remaining logic + if (ec && m_alerts.should_post()) + { + m_alerts.emplace_alert(mapping + , transport, ec); + } + + // look through our listen sockets to see if this mapping is for one of + // them (it could also be a user mapping) + + auto ls + = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , std::bind(find_tcp_port_mapping, transport, mapping, _1)); + + bool tcp = true; + if (ls == m_listen_sockets.end()) + { + ls = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , std::bind(find_udp_port_mapping, transport, mapping, _1)); + tcp = false; + } + + if (ls != m_listen_sockets.end()) + { + if (!ec && ip != address()) + { + // TODO: 1 report the proper address of the router as the source IP of + // this vote of our external address, instead of the empty address + (*ls)->external_address.cast_vote(ip, source_router, address()); + } + + if (tcp) (*ls)->tcp_port_mapping[transport].port = port; + else (*ls)->udp_port_mapping[transport].port = port; + } + + if (!ec && m_alerts.should_post()) + { + m_alerts.emplace_alert(mapping, port + , transport, proto); + } + } + +#if TORRENT_ABI_VERSION == 1 + session_status session_impl::status() const + { +// INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + + session_status s; + + s.optimistic_unchoke_counter = m_optimistic_unchoke_time_scaler; + s.unchoke_counter = m_unchoke_time_scaler; + s.num_dead_peers = int(m_undead_peers.size()); + + s.num_peers = int(m_stats_counters[counters::num_peers_connected]); + s.num_unchoked = int(m_stats_counters[counters::num_peers_up_unchoked_all]); + s.allowed_upload_slots = int(m_stats_counters[counters::num_unchoke_slots]); + + s.num_torrents + = int(m_stats_counters[counters::num_checking_torrents] + + m_stats_counters[counters::num_stopped_torrents] + + m_stats_counters[counters::num_queued_seeding_torrents] + + m_stats_counters[counters::num_queued_download_torrents] + + m_stats_counters[counters::num_upload_only_torrents] + + m_stats_counters[counters::num_downloading_torrents] + + m_stats_counters[counters::num_seeding_torrents] + + m_stats_counters[counters::num_error_torrents]); + + s.num_paused_torrents + = int(m_stats_counters[counters::num_stopped_torrents] + + m_stats_counters[counters::num_error_torrents] + + m_stats_counters[counters::num_queued_seeding_torrents] + + m_stats_counters[counters::num_queued_download_torrents]); + + s.total_redundant_bytes = m_stats_counters[counters::recv_redundant_bytes]; + s.total_failed_bytes = m_stats_counters[counters::recv_failed_bytes]; + + s.up_bandwidth_queue = int(m_stats_counters[counters::limiter_up_queue]); + s.down_bandwidth_queue = int(m_stats_counters[counters::limiter_down_queue]); + + s.up_bandwidth_bytes_queue = int(m_stats_counters[counters::limiter_up_bytes]); + s.down_bandwidth_bytes_queue = int(m_stats_counters[counters::limiter_down_bytes]); + + s.disk_write_queue = int(m_stats_counters[counters::num_peers_down_disk]); + s.disk_read_queue = int(m_stats_counters[counters::num_peers_up_disk]); + + s.has_incoming_connections = m_stats_counters[counters::has_incoming_connections] != 0; + + // total + s.download_rate = m_stat.download_rate(); + s.total_upload = m_stat.total_upload(); + s.upload_rate = m_stat.upload_rate(); + s.total_download = m_stat.total_download(); + + // payload + s.payload_download_rate = m_stat.transfer_rate(stat::download_payload); + s.total_payload_download = m_stat.total_transfer(stat::download_payload); + s.payload_upload_rate = m_stat.transfer_rate(stat::upload_payload); + s.total_payload_upload = m_stat.total_transfer(stat::upload_payload); + + // IP-overhead + s.ip_overhead_download_rate = m_stat.transfer_rate(stat::download_ip_protocol); + s.total_ip_overhead_download = m_stats_counters[counters::recv_ip_overhead_bytes]; + s.ip_overhead_upload_rate = m_stat.transfer_rate(stat::upload_ip_protocol); + s.total_ip_overhead_upload = m_stats_counters[counters::sent_ip_overhead_bytes]; + + // tracker + s.total_tracker_download = m_stats_counters[counters::recv_tracker_bytes]; + s.total_tracker_upload = m_stats_counters[counters::sent_tracker_bytes]; + + // dht + s.total_dht_download = m_stats_counters[counters::dht_bytes_in]; + s.total_dht_upload = m_stats_counters[counters::dht_bytes_out]; + + // deprecated + s.tracker_download_rate = 0; + s.tracker_upload_rate = 0; + s.dht_download_rate = 0; + s.dht_upload_rate = 0; + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) + { + m_dht->dht_status(s); + } + else +#endif + { + s.dht_nodes = 0; + s.dht_node_cache = 0; + s.dht_torrents = 0; + s.dht_global_nodes = 0; + s.dht_total_allocations = 0; + } + + s.utp_stats.packet_loss = std::uint64_t(m_stats_counters[counters::utp_packet_loss]); + s.utp_stats.timeout = std::uint64_t(m_stats_counters[counters::utp_timeout]); + s.utp_stats.packets_in = std::uint64_t(m_stats_counters[counters::utp_packets_in]); + s.utp_stats.packets_out = std::uint64_t(m_stats_counters[counters::utp_packets_out]); + s.utp_stats.fast_retransmit = std::uint64_t(m_stats_counters[counters::utp_fast_retransmit]); + s.utp_stats.packet_resend = std::uint64_t(m_stats_counters[counters::utp_packet_resend]); + s.utp_stats.samples_above_target = std::uint64_t(m_stats_counters[counters::utp_samples_above_target]); + s.utp_stats.samples_below_target = std::uint64_t(m_stats_counters[counters::utp_samples_below_target]); + s.utp_stats.payload_pkts_in = std::uint64_t(m_stats_counters[counters::utp_payload_pkts_in]); + s.utp_stats.payload_pkts_out = std::uint64_t(m_stats_counters[counters::utp_payload_pkts_out]); + s.utp_stats.invalid_pkts_in = std::uint64_t(m_stats_counters[counters::utp_invalid_pkts_in]); + s.utp_stats.redundant_pkts_in = std::uint64_t(m_stats_counters[counters::utp_redundant_pkts_in]); + + s.utp_stats.num_idle = int(m_stats_counters[counters::num_utp_idle]); + s.utp_stats.num_syn_sent = int(m_stats_counters[counters::num_utp_syn_sent]); + s.utp_stats.num_connected = int(m_stats_counters[counters::num_utp_connected]); + s.utp_stats.num_fin_sent = int(m_stats_counters[counters::num_utp_fin_sent]); + s.utp_stats.num_close_wait = int(m_stats_counters[counters::num_utp_close_wait]); + + // this loop is potentially expensive. It could be optimized by + // simply keeping a global counter + s.peerlist_size = std::accumulate(m_torrents.begin(), m_torrents.end(), 0 + , [](int const acc, std::pair> const& t) + { return acc + t.second->num_known_peers(); }); + + return s; + } +#endif // TORRENT_ABI_VERSION + + void session_impl::get_cache_info(torrent_handle h, cache_status* ret, int flags) const + { + storage_index_t st{0}; + bool whole_session = true; + std::shared_ptr t = h.m_torrent.lock(); + if (t) + { + if (t->has_storage()) + { + st = t->storage(); + whole_session = false; + } + else + flags = session::disk_cache_no_pieces; + } + m_disk_thread.get_cache_info(ret, st + , flags & session::disk_cache_no_pieces, whole_session); + } + +#ifndef TORRENT_DISABLE_DHT + + void session_impl::start_dht() + { + INVARIANT_CHECK; + + stop_dht(); + + if (!m_settings.get_bool(settings_pack::enable_dht)) return; + + // postpone starting the DHT if we're still resolving the DHT router + if (m_outstanding_router_lookups > 0) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("not starting DHT, outstanding router lookups: %d" + , m_outstanding_router_lookups); +#endif + return; + } + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("not starting DHT, aborting"); +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + session_log("starting DHT, running: %s, router lookups: %d" + , m_dht ? "true" : "false", m_outstanding_router_lookups); +#endif + + // TODO: refactor, move the storage to dht_tracker + m_dht_storage = m_dht_storage_constructor(m_dht_settings); + m_dht = std::make_shared( + static_cast(this) + , m_io_service + , [=](aux::listen_socket_handle const& sock + , udp::endpoint const& ep + , span p + , error_code& ec + , udp_send_flags_t const flags) + { send_udp_packet_listen(sock, ep, p, ec, flags); } + , m_dht_settings + , m_stats_counters + , *m_dht_storage + , std::move(m_dht_state)); + + for (auto& s : m_listen_sockets) + { + if (s->ssl != transport::ssl + && !(s->flags & listen_socket_t::local_network)) + { + m_dht->new_socket(s); + } + } + + for (auto const& n : m_dht_router_nodes) + { + m_dht->add_router_node(n); + } + + for (auto const& n : m_dht_nodes) + { + m_dht->add_node(n); + } + m_dht_nodes.clear(); + m_dht_nodes.shrink_to_fit(); + + auto cb = [this]( + std::vector> const&) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(); + }; + + m_dht->start(cb); + } + + void session_impl::stop_dht() + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("about to stop DHT, running: %s", m_dht ? "true" : "false"); +#endif + + if (m_dht) + { + m_dht->stop(); + m_dht.reset(); + } + + m_dht_storage.reset(); + } + + void session_impl::set_dht_settings(dht::dht_settings const& settings) + { + static_cast(m_dht_settings) = settings; + if (m_dht_settings.upload_rate_limit > std::numeric_limits::max() / 3) + m_dht_settings.upload_rate_limit = std::numeric_limits::max() / 3; + m_settings.set_int(settings_pack::dht_upload_rate_limit, m_dht_settings.upload_rate_limit); + } + + void session_impl::set_dht_state(dht::dht_state&& state) + { + m_dht_state = std::move(state); + } + + void session_impl::set_dht_storage(dht::dht_storage_constructor_type sc) + { + m_dht_storage_constructor = std::move(sc); + } + +#if TORRENT_ABI_VERSION == 1 + entry session_impl::dht_state() const + { + return m_dht ? dht::save_dht_state(m_dht->state()) : entry(); + } + + void session_impl::start_dht_deprecated(entry const& startup_state) + { + m_settings.set_bool(settings_pack::enable_dht, true); + std::vector tmp; + bencode(std::back_inserter(tmp), startup_state); + + bdecode_node e; + error_code ec; + if (tmp.empty() || bdecode(&tmp[0], &tmp[0] + tmp.size(), e, ec) != 0) + return; + m_dht_state = dht::read_dht_state(e); + start_dht(); + } +#endif + + void session_impl::add_dht_node_name(std::pair const& node) + { + ADD_OUTSTANDING_ASYNC("session_impl::on_dht_name_lookup"); + m_host_resolver.async_resolve(node.first, resolver::abort_on_shutdown + , std::bind(&session_impl::on_dht_name_lookup + , this, _1, _2, node.second)); + } + + void session_impl::on_dht_name_lookup(error_code const& e + , std::vector
    const& addresses, int port) + { + COMPLETE_ASYNC("session_impl::on_dht_name_lookup"); + + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert( + operation_t::hostname_lookup, e); + return; + } + + for (auto const& addr : addresses) + { + udp::endpoint ep(addr, std::uint16_t(port)); + add_dht_node(ep); + } + } + + void session_impl::add_dht_router(std::pair const& node) + { + ADD_OUTSTANDING_ASYNC("session_impl::on_dht_router_name_lookup"); + ++m_outstanding_router_lookups; + m_host_resolver.async_resolve(node.first, resolver::abort_on_shutdown + , std::bind(&session_impl::on_dht_router_name_lookup + , this, _1, _2, node.second)); + } + + void session_impl::on_dht_router_name_lookup(error_code const& e + , std::vector
    const& addresses, int port) + { + COMPLETE_ASYNC("session_impl::on_dht_router_name_lookup"); + --m_outstanding_router_lookups; + + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert( + operation_t::hostname_lookup, e); + + if (m_outstanding_router_lookups == 0) start_dht(); + return; + } + + + for (auto const& addr : addresses) + { + // router nodes should be added before the DHT is started (and bootstrapped) + udp::endpoint ep(addr, std::uint16_t(port)); + if (m_dht) m_dht->add_router_node(ep); + m_dht_router_nodes.push_back(ep); + } + + if (m_outstanding_router_lookups == 0) start_dht(); + } + + // callback for dht_immutable_get + void session_impl::get_immutable_callback(sha1_hash target + , dht::item const& i) + { + TORRENT_ASSERT(!i.is_mutable()); + m_alerts.emplace_alert(target, i.value()); + } + + void session_impl::dht_get_immutable_item(sha1_hash const& target) + { + if (!m_dht) return; + m_dht->get_item(target, std::bind(&session_impl::get_immutable_callback + , this, target, _1)); + } + + // callback for dht_mutable_get + void session_impl::get_mutable_callback(dht::item const& i + , bool const authoritative) + { + TORRENT_ASSERT(i.is_mutable()); + m_alerts.emplace_alert(i.pk().bytes + , i.sig().bytes, i.seq().value + , i.salt(), i.value(), authoritative); + } + + // key is a 32-byte binary string, the public key to look up. + // the salt is optional + // TODO: 3 use public_key here instead of std::array + void session_impl::dht_get_mutable_item(std::array key + , std::string salt) + { + if (!m_dht) return; + m_dht->get_item(dht::public_key(key.data()), std::bind(&session_impl::get_mutable_callback + , this, _1, _2), std::move(salt)); + } + + namespace { + + void on_dht_put_immutable_item(alert_manager& alerts, sha1_hash target, int num) + { + if (alerts.should_post()) + alerts.emplace_alert(target, num); + } + + void on_dht_put_mutable_item(alert_manager& alerts, dht::item const& i, int num) + { + if (alerts.should_post()) + { + dht::signature const sig = i.sig(); + dht::public_key const pk = i.pk(); + dht::sequence_number const seq = i.seq(); + std::string salt = i.salt(); + alerts.emplace_alert(pk.bytes, sig.bytes + , std::move(salt), seq.value, num); + } + } + + void put_mutable_callback(dht::item& i + , std::function& + , std::int64_t&, std::string const&)> cb) + { + entry value = i.value(); + dht::signature sig = i.sig(); + dht::public_key pk = i.pk(); + dht::sequence_number seq = i.seq(); + std::string salt = i.salt(); + cb(value, sig.bytes, seq.value, salt); + i.assign(std::move(value), salt, seq, pk, sig); + } + + void on_dht_get_peers(alert_manager& alerts, sha1_hash info_hash, std::vector const& peers) + { + if (alerts.should_post()) + alerts.emplace_alert(info_hash, peers); + } + + void on_direct_response(alert_manager& alerts, void* userdata, dht::msg const& msg) + { + if (msg.message.type() == bdecode_node::none_t) + alerts.emplace_alert(userdata, msg.addr); + else + alerts.emplace_alert(userdata, msg.addr, msg.message); + } + + } // anonymous namespace + + void session_impl::dht_put_immutable_item(entry const& data, sha1_hash target) + { + if (!m_dht) return; + m_dht->put_item(data, std::bind(&on_dht_put_immutable_item, std::ref(m_alerts) + , target, _1)); + } + + void session_impl::dht_put_mutable_item(std::array key + , std::function& + , std::int64_t&, std::string const&)> cb + , std::string salt) + { + if (!m_dht) return; + m_dht->put_item(dht::public_key(key.data()) + , std::bind(&on_dht_put_mutable_item, std::ref(m_alerts), _1, _2) + , std::bind(&put_mutable_callback, _1, std::move(cb)), salt); + } + + void session_impl::dht_get_peers(sha1_hash const& info_hash) + { + if (!m_dht) return; + m_dht->get_peers(info_hash, std::bind(&on_dht_get_peers, std::ref(m_alerts), info_hash, _1)); + } + + void session_impl::dht_announce(sha1_hash const& info_hash, int port, dht::announce_flags_t const flags) + { + if (!m_dht) return; + m_dht->announce(info_hash, port, flags, std::bind(&on_dht_get_peers, std::ref(m_alerts), info_hash, _1)); + } + + void session_impl::dht_live_nodes(sha1_hash const& nid) + { + if (!m_dht) return; + auto nodes = m_dht->live_nodes(nid); + m_alerts.emplace_alert(nid, nodes); + } + + void session_impl::dht_sample_infohashes(udp::endpoint const& ep, sha1_hash const& target) + { + if (!m_dht) return; + m_dht->sample_infohashes(ep, target, [this, ep](time_duration const interval + , int const num, std::vector samples + , std::vector> nodes) + { + m_alerts.emplace_alert(ep + , interval, num, std::move(samples), std::move(nodes)); + }); + } + + void session_impl::dht_direct_request(udp::endpoint const& ep, entry& e, void* userdata) + { + if (!m_dht) return; + m_dht->direct_request(ep, e, std::bind(&on_direct_response, std::ref(m_alerts), userdata, _1)); + } + +#endif + +#if !defined TORRENT_DISABLE_ENCRYPTION + void session_impl::add_obfuscated_hash(sha1_hash const& obfuscated + , std::weak_ptr const& t) + { + m_obfuscated_torrents.insert(std::make_pair(obfuscated, t.lock())); + } +#endif // TORRENT_DISABLE_ENCRYPTION + + bool session_impl::is_listening() const + { + return !m_listen_sockets.empty(); + } + + session_impl::~session_impl() + { + // since we're destructing the session, no more alerts will make it out to + // the user. So stop posting them now + m_alerts.set_alert_mask({}); + + // this is not allowed to be the network thread! +// TORRENT_ASSERT(is_not_thread()); +// TODO: asserts that no outstanding async operations are still in flight + + // this can happen if we end the io_service run loop with an exception + m_connections.clear(); + for (auto& t : m_torrents) + { + t.second->panic(); + t.second->abort(); + } + m_torrents.clear(); +#if !defined TORRENT_DISABLE_ENCRYPTION + m_obfuscated_torrents.clear(); +#endif +#if TORRENT_ABI_VERSION == 1 + m_uuids.clear(); +#endif + +#if defined TORRENT_ASIO_DEBUGGING + FILE* f = fopen("wakeups.log", "w+"); + if (f != nullptr) + { + time_point m = min_time(); + if (!_wakeups.empty()) m = _wakeups[0].timestamp; + time_point prev = m; + std::uint64_t prev_csw = 0; + if (!_wakeups.empty()) prev_csw = _wakeups[0].context_switches; + std::fprintf(f, "abs. time\trel. time\tctx switch\tidle-wakeup\toperation\n"); + for (wakeup_t const& w : _wakeups) + { + bool const idle_wakeup = w.context_switches > prev_csw; + std::fprintf(f, "%" PRId64 "\t%" PRId64 "\t%" PRId64 "\t%c\t%s\n" + , total_microseconds(w.timestamp - m) + , total_microseconds(w.timestamp - prev) + , w.context_switches + , idle_wakeup ? '*' : '.' + , w.operation); + prev = w.timestamp; + prev_csw = w.context_switches; + } + fclose(f); + } +#endif + } + +#if TORRENT_ABI_VERSION == 1 + int session_impl::max_connections() const + { + return m_settings.get_int(settings_pack::connections_limit); + } + + int session_impl::max_uploads() const + { + return m_settings.get_int(settings_pack::unchoke_slots_limit); + } + + void session_impl::set_local_download_rate_limit(int bytes_per_second) + { + INVARIANT_CHECK; + settings_pack p; + p.set_int(settings_pack::local_download_rate_limit, bytes_per_second); + apply_settings_pack_impl(p); + } + + void session_impl::set_local_upload_rate_limit(int bytes_per_second) + { + INVARIANT_CHECK; + settings_pack p; + p.set_int(settings_pack::local_upload_rate_limit, bytes_per_second); + apply_settings_pack_impl(p); + } + + void session_impl::set_download_rate_limit_depr(int bytes_per_second) + { + INVARIANT_CHECK; + settings_pack p; + p.set_int(settings_pack::download_rate_limit, bytes_per_second); + apply_settings_pack_impl(p); + } + + void session_impl::set_upload_rate_limit_depr(int bytes_per_second) + { + INVARIANT_CHECK; + settings_pack p; + p.set_int(settings_pack::upload_rate_limit, bytes_per_second); + apply_settings_pack_impl(p); + } + + void session_impl::set_max_connections(int limit) + { + INVARIANT_CHECK; + settings_pack p; + p.set_int(settings_pack::connections_limit, limit); + apply_settings_pack_impl(p); + } + + void session_impl::set_max_uploads(int limit) + { + INVARIANT_CHECK; + settings_pack p; + p.set_int(settings_pack::unchoke_slots_limit, limit); + apply_settings_pack_impl(p); + } + + int session_impl::local_upload_rate_limit() const + { + return upload_rate_limit(m_local_peer_class); + } + + int session_impl::local_download_rate_limit() const + { + return download_rate_limit(m_local_peer_class); + } + + int session_impl::upload_rate_limit_depr() const + { + return upload_rate_limit(m_global_class); + } + + int session_impl::download_rate_limit_depr() const + { + return download_rate_limit(m_global_class); + } +#endif // DEPRECATE + + + namespace { + template + void set_tos(Socket& s, int v, error_code& ec) + { +#if defined IPV6_TCLASS + if (is_v6(s.local_endpoint(ec))) + s.set_option(traffic_class(char(v)), ec); + else if (!ec) +#endif + s.set_option(type_of_service(char(v)), ec); + } + } + + // TODO: 2 this should be factored into the udp socket, so we only have the + // code once + void session_impl::update_peer_tos() + { + int const tos = m_settings.get_int(settings_pack::peer_tos); + for (auto const& l : m_listen_sockets) + { + if (l->sock) + { + error_code ec; + set_tos(*l->sock, tos, ec); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log(">>> SET_TOS [ tcp (%s %d) tos: %x e: %s ]" + , l->sock->local_endpoint().address().to_string().c_str() + , l->sock->local_endpoint().port(), tos, ec.message().c_str()); + } +#endif + } + + if (l->udp_sock) + { + error_code ec; + set_tos(l->udp_sock->sock, tos, ec); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log(">>> SET_TOS [ udp (%s %d) tos: %x e: %s ]" + , l->udp_sock->sock.local_endpoint().address().to_string().c_str() + , l->udp_sock->sock.local_port() + , tos, ec.message().c_str()); + } +#endif + } + } + } + + void session_impl::update_user_agent() + { + // replace all occurrences of '\n' with ' '. + std::string agent = m_settings.get_str(settings_pack::user_agent); + std::string::iterator i = agent.begin(); + while ((i = std::find(i, agent.end(), '\n')) + != agent.end()) + *i = ' '; + m_settings.set_str(settings_pack::user_agent, agent); + } + + void session_impl::update_unchoke_limit() + { + int const allowed_upload_slots = get_int_setting(settings_pack::unchoke_slots_limit); + + m_stats_counters.set_value(counters::num_unchoke_slots + , allowed_upload_slots); + + if (m_settings.get_int(settings_pack::num_optimistic_unchoke_slots) + >= allowed_upload_slots / 2) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(torrent_handle() + , performance_alert::too_many_optimistic_unchoke_slots); + } + + if (settings().get_int(settings_pack::choking_algorithm) != settings_pack::fixed_slots_choker) + return; + + if (allowed_upload_slots == std::numeric_limits::max()) + { + // this means we're not aplpying upload slot limits, unchoke + // everyone + for (auto const& p : m_connections) + { + if (p->is_disconnecting() + || p->is_connecting() + || !p->is_choked() + || p->in_handshake() + || p->ignore_unchoke_slots() + ) + continue; + + auto const t = p->associated_torrent().lock(); + t->unchoke_peer(*p); + } + } + else + { + // trigger recalculating unchoke slots + m_unchoke_time_scaler = 0; + } + } + + void session_impl::update_connection_speed() + { + if (m_settings.get_int(settings_pack::connection_speed) < 0) + m_settings.set_int(settings_pack::connection_speed, 200); + } + + void session_impl::update_queued_disk_bytes() + { + int const cache_size = m_settings.get_int(settings_pack::cache_size); + if (m_settings.get_int(settings_pack::max_queued_disk_bytes) / 16 / 1024 + > cache_size / 2 + && cache_size > 5 + && m_alerts.should_post()) + { + m_alerts.emplace_alert(torrent_handle() + , performance_alert::too_high_disk_queue_limit); + } + } + + void session_impl::update_alert_queue_size() + { + m_alerts.set_alert_queue_size_limit(m_settings.get_int(settings_pack::alert_queue_size)); + } + + bool session_impl::preemptive_unchoke() const + { + if (settings().get_int(settings_pack::choking_algorithm) != settings_pack::fixed_slots_choker) return false; + return m_stats_counters[counters::num_peers_up_unchoked] + < m_stats_counters[counters::num_unchoke_slots] + || m_settings.get_int(settings_pack::unchoke_slots_limit) < 0; + } + + void session_impl::update_dht_upload_rate_limit() + { +#ifndef TORRENT_DISABLE_DHT + m_dht_settings.upload_rate_limit = m_settings.get_int(settings_pack::dht_upload_rate_limit); + if (m_dht_settings.upload_rate_limit > std::numeric_limits::max() / 3) + { + m_settings.set_int(settings_pack::dht_upload_rate_limit, std::numeric_limits::max() / 3); + m_dht_settings.upload_rate_limit = std::numeric_limits::max() / 3; + } +#endif + } + + void session_impl::update_disk_threads() + { + if (m_settings.get_int(settings_pack::aio_threads) < 0) + m_settings.set_int(settings_pack::aio_threads, 0); + +#if !TORRENT_USE_PREAD && !TORRENT_USE_PREADV + // if we don't have pread() nor preadv() there's no way + // to perform concurrent file operations on the same file + // handle, so we must limit the disk thread to a single one + + if (m_settings.get_int(settings_pack::aio_threads) > 1) + m_settings.set_int(settings_pack::aio_threads, 1); +#endif + } + + void session_impl::update_report_web_seed_downloads() + { + // if this flag changed, update all web seed connections + bool report = m_settings.get_bool(settings_pack::report_web_seed_downloads); + for (auto const& c : m_connections) + { + connection_type const type = c->type(); + if (type == connection_type::url_seed + || type == connection_type::http_seed) + c->ignore_stats(!report); + } + } + + void session_impl::trigger_auto_manage() + { + if (m_pending_auto_manage || m_abort) return; + + // we recalculated auto-managed torrents less than a second ago, + // put it off one second. + if (time_now() - m_last_auto_manage < seconds(1)) + { + m_auto_manage_time_scaler = 0; + return; + } + m_pending_auto_manage = true; + m_need_auto_manage = true; + + m_io_service.post([this]{ this->wrap(&session_impl::on_trigger_auto_manage); }); + } + + void session_impl::on_trigger_auto_manage() + { + TORRENT_ASSERT(m_pending_auto_manage); + if (!m_need_auto_manage || m_abort) + { + m_pending_auto_manage = false; + return; + } + // don't clear m_pending_auto_manage until after we've + // recalculated the auto managed torrents. The auto-managed + // logic may trigger another auto-managed event otherwise + recalculate_auto_managed_torrents(); + m_pending_auto_manage = false; + } + + void session_impl::update_socket_buffer_size() + { + for (auto const& l : m_listen_sockets) + { + error_code ec; + set_socket_buffer_size(l->udp_sock->sock, m_settings, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + error_code err; + session_log("listen socket buffer size [ udp %s:%d ] %s" + , l->udp_sock->sock.local_endpoint().address().to_string(err).c_str() + , l->udp_sock->sock.local_port(), print_error(ec).c_str()); + } +#endif + ec.clear(); + set_socket_buffer_size(*l->sock, m_settings, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + error_code err; + session_log("listen socket buffer size [ tcp %s:%d] %s" + , l->sock->local_endpoint().address().to_string(err).c_str() + , l->sock->local_endpoint().port(), print_error(ec).c_str()); + } +#endif + } + } + + void session_impl::update_dht_announce_interval() + { +#ifndef TORRENT_DISABLE_DHT + if (!m_dht) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("not starting DHT announce timer: m_dht == nullptr"); +#endif + return; + } + + m_dht_interval_update_torrents = int(m_torrents.size()); + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + session_log("not starting DHT announce timer: m_abort set"); +#endif + return; + } + + ADD_OUTSTANDING_ASYNC("session_impl::on_dht_announce"); + error_code ec; + int delay = std::max(m_settings.get_int(settings_pack::dht_announce_interval) + / std::max(int(m_torrents.size()), 1), 1); + m_dht_announce_timer.expires_from_now(seconds(delay), ec); + m_dht_announce_timer.async_wait([this](error_code const& e) { + this->wrap(&session_impl::on_dht_announce, e); }); +#endif + } + +#if TORRENT_ABI_VERSION == 1 + void session_impl::update_local_download_rate() + { + if (m_settings.get_int(settings_pack::local_download_rate_limit) < 0) + m_settings.set_int(settings_pack::local_download_rate_limit, 0); + set_download_rate_limit(m_local_peer_class + , m_settings.get_int(settings_pack::local_download_rate_limit)); + } + + void session_impl::update_local_upload_rate() + { + if (m_settings.get_int(settings_pack::local_upload_rate_limit) < 0) + m_settings.set_int(settings_pack::local_upload_rate_limit, 0); + set_upload_rate_limit(m_local_peer_class + , m_settings.get_int(settings_pack::local_upload_rate_limit)); + } +#endif + + void session_impl::update_download_rate() + { + if (m_settings.get_int(settings_pack::download_rate_limit) < 0) + m_settings.set_int(settings_pack::download_rate_limit, 0); + set_download_rate_limit(m_global_class + , m_settings.get_int(settings_pack::download_rate_limit)); + } + + void session_impl::update_upload_rate() + { + if (m_settings.get_int(settings_pack::upload_rate_limit) < 0) + m_settings.set_int(settings_pack::upload_rate_limit, 0); + set_upload_rate_limit(m_global_class + , m_settings.get_int(settings_pack::upload_rate_limit)); + } + + void session_impl::update_connections_limit() + { + int limit = m_settings.get_int(settings_pack::connections_limit); + + if (limit <= 0) limit = max_open_files(); + + m_settings.set_int(settings_pack::connections_limit, limit); + + if (num_connections() > m_settings.get_int(settings_pack::connections_limit) + && !m_torrents.empty()) + { + // if we have more connections that we're allowed, disconnect + // peers from the torrents so that they are all as even as possible + + int to_disconnect = num_connections() - m_settings.get_int(settings_pack::connections_limit); + + int last_average = 0; + int average = m_settings.get_int(settings_pack::connections_limit) / int(m_torrents.size()); + + // the number of slots that are unused by torrents + int extra = m_settings.get_int(settings_pack::connections_limit) % int(m_torrents.size()); + + // run 3 iterations of this, then we're probably close enough + for (int iter = 0; iter < 4; ++iter) + { + // the number of torrents that are above average + int num_above = 0; + for (auto const& t : m_torrents) + { + int const num = t.second->num_peers(); + if (num <= last_average) continue; + if (num > average) ++num_above; + if (num < average) extra += average - num; + } + + // distribute extra among the torrents that are above average + if (num_above == 0) num_above = 1; + last_average = average; + average += extra / num_above; + if (extra == 0) break; + // save the remainder for the next iteration + extra = extra % num_above; + } + + for (auto const& t : m_torrents) + { + int const num = t.second->num_peers(); + if (num <= average) continue; + + // distribute the remainder + int my_average = average; + if (extra > 0) + { + ++my_average; + --extra; + } + + int const disconnect = std::min(to_disconnect, num - my_average); + to_disconnect -= disconnect; + t.second->disconnect_peers(disconnect, errors::too_many_connections); + } + } + } + + void session_impl::update_alert_mask() + { + m_alerts.set_alert_mask(alert_category_t( + static_cast(m_settings.get_int(settings_pack::alert_mask)))); + } + + void session_impl::update_validate_https() + { +#ifdef TORRENT_USE_OPENSSL + using boost::asio::ssl::context; + auto const flags = m_settings.get_bool(settings_pack::validate_https_trackers) + ? context::verify_peer + | context::verify_fail_if_no_peer_cert + | context::verify_client_once + : context::verify_none; + error_code ec; + m_ssl_ctx.set_verify_mode(flags, ec); +#endif + } + + void session_impl::pop_alerts(std::vector* alerts) + { + m_alerts.get_all(*alerts); + } + +#if TORRENT_ABI_VERSION == 1 + void session_impl::update_rate_limit_utp() + { + if (m_settings.get_bool(settings_pack::rate_limit_utp)) + { + // allow the global or local peer class to limit uTP peers + m_peer_class_type_filter.allow(peer_class_type_filter::utp_socket + , m_global_class); + m_peer_class_type_filter.allow(peer_class_type_filter::ssl_utp_socket + , m_global_class); + } + else + { + // don't add the global or local peer class to limit uTP peers + m_peer_class_type_filter.disallow(peer_class_type_filter::utp_socket + , m_global_class); + m_peer_class_type_filter.disallow(peer_class_type_filter::ssl_utp_socket + , m_global_class); + } + } + + void session_impl::update_ignore_rate_limits_on_local_network() + { + init_peer_class_filter( + m_settings.get_bool(settings_pack::ignore_limits_on_local_network)); + } + + // this function is called on the user's thread + // not the network thread + void session_impl::pop_alerts() + { + // if we don't have any alerts in our local cache, we have to ask + // the alert_manager for more. It will swap our vector with its and + // destruct eny left-over alerts in there. + if (m_alert_pointer_pos >= int(m_alert_pointers.size())) + { + pop_alerts(&m_alert_pointers); + m_alert_pointer_pos = 0; + } + } + + alert const* session_impl::pop_alert() + { + if (m_alert_pointer_pos >= int(m_alert_pointers.size())) + { + pop_alerts(); + if (m_alert_pointers.empty()) + return nullptr; + } + + if (m_alert_pointers.empty()) return nullptr; + + // clone here to be backwards compatible, to make the client delete the + // alert object + return m_alert_pointers[m_alert_pointer_pos++]; + } + +#endif + + alert* session_impl::wait_for_alert(time_duration max_wait) + { + return m_alerts.wait_for_alert(max_wait); + } + +#if TORRENT_ABI_VERSION == 1 + std::size_t session_impl::set_alert_queue_size_limit(std::size_t queue_size_limit_) + { + m_settings.set_int(settings_pack::alert_queue_size, int(queue_size_limit_)); + return std::size_t(m_alerts.set_alert_queue_size_limit(int(queue_size_limit_))); + } +#endif + + void session_impl::start_ip_notifier() + { + INVARIANT_CHECK; + + if (m_ip_notifier) return; + + m_ip_notifier = create_ip_notifier(m_io_service); + m_ip_notifier->async_wait([this](error_code const& e) + { this->wrap(&session_impl::on_ip_change, e); }); + } + + void session_impl::start_lsd() + { + INVARIANT_CHECK; + + for (auto& s : m_listen_sockets) + { + // we're not looking for local peers when we're using a proxy. We + // want all traffic to go through the proxy + if (s->flags & listen_socket_t::proxy) continue; + if (s->lsd) continue; + s->lsd = std::make_shared(m_io_service, *this, s->local_endpoint.address() + , s->netmask); + error_code ec; + s->lsd->start(ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(ec); + s->lsd.reset(); + } + } + } + + void session_impl::start_natpmp() + { + INVARIANT_CHECK; + for (auto& s : m_listen_sockets) + { + start_natpmp(*s); + remap_ports(remap_natpmp, *s); + } + } + + void session_impl::start_upnp() + { + INVARIANT_CHECK; + for (auto& s : m_listen_sockets) + { + start_upnp(*s); + remap_ports(remap_upnp, *s); + } + } + + void session_impl::start_upnp(aux::listen_socket_t& s) + { + // until we support SSDP over an IPv6 network ( + // https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol ) + // there's no point in starting upnp on one. + if (is_v6(s.local_endpoint)) + return; + + // there's no point in starting the UPnP mapper for a network that isn't + // connected to the internet. The whole point is to forward ports through + // the gateway + if ((s.flags & listen_socket_t::local_network) + || (s.flags & listen_socket_t::proxy)) + return; + + if (!s.upnp_mapper) + { + // the upnp constructor may fail and call the callbacks + // into the session_impl. + s.upnp_mapper = std::make_shared(m_io_service, m_settings + , *this, s.local_endpoint.address().to_v4(), s.netmask.to_v4(), s.device); + s.upnp_mapper->start(); + } + } + + std::vector session_impl::add_port_mapping(portmap_protocol const t + , int const external_port + , int const local_port) + { + std::vector ret; + for (auto& s : m_listen_sockets) + { + if (s->upnp_mapper) ret.push_back(s->upnp_mapper->add_mapping(t, external_port + , tcp::endpoint(s->local_endpoint.address(), static_cast(local_port)))); + if (s->natpmp_mapper) ret.push_back(s->natpmp_mapper->add_mapping(t, external_port + , tcp::endpoint(s->local_endpoint.address(), static_cast(local_port)))); + } + return ret; + } + + void session_impl::delete_port_mapping(port_mapping_t handle) + { + for (auto& s : m_listen_sockets) + { + if (s->upnp_mapper) s->upnp_mapper->delete_mapping(handle); + if (s->natpmp_mapper) s->natpmp_mapper->delete_mapping(handle); + } + } + + void session_impl::stop_ip_notifier() + { + if (!m_ip_notifier) return; + + m_ip_notifier->cancel(); + m_ip_notifier.reset(); + } + + void session_impl::stop_lsd() + { + for (auto& s : m_listen_sockets) + { + if (!s->lsd) continue; + s->lsd->close(); + s->lsd.reset(); + } + } + + void session_impl::stop_natpmp() + { + for (auto& s : m_listen_sockets) + { + s->tcp_port_mapping[portmap_transport::natpmp] = listen_port_mapping(); + s->udp_port_mapping[portmap_transport::natpmp] = listen_port_mapping(); + if (!s->natpmp_mapper) continue; + s->natpmp_mapper->close(); + s->natpmp_mapper.reset(); + } + } + + void session_impl::stop_upnp() + { + for (auto& s : m_listen_sockets) + { + if (!s->upnp_mapper) continue; + s->tcp_port_mapping[portmap_transport::upnp] = listen_port_mapping(); + s->udp_port_mapping[portmap_transport::upnp] = listen_port_mapping(); + s->upnp_mapper->close(); + s->upnp_mapper.reset(); + } + } + + external_ip session_impl::external_address() const + { + address ips[2][2]; + + // take the first IP we find which matches each category + for (auto const& i : m_listen_sockets) + { + address external_addr = i->external_address.external_address(); + if (ips[0][external_addr.is_v6()] == address()) + ips[0][external_addr.is_v6()] = external_addr; + address local_addr = i->local_endpoint.address(); + if (ips[is_local(local_addr)][local_addr.is_v6()] == address()) + ips[is_local(local_addr)][local_addr.is_v6()] = local_addr; + } + + return {ips[1][0], ips[0][0], ips[1][1], ips[0][1]}; + } + + // this is the DHT observer version. DHT is the implied source + void session_impl::set_external_address(aux::listen_socket_handle const& iface + , address const& ip, address const& source) + { + auto i = iface.m_sock.lock(); + TORRENT_ASSERT(i); + if (!i) return; + set_external_address(i, ip, source_dht, source); + } + + void session_impl::get_peers(sha1_hash const& ih) + { + if (!m_alerts.should_post()) return; + m_alerts.emplace_alert(ih); + } + + void session_impl::announce(sha1_hash const& ih, address const& addr + , int port) + { + if (!m_alerts.should_post()) return; + m_alerts.emplace_alert(addr, port, ih); + } + + void session_impl::outgoing_get_peers(sha1_hash const& target + , sha1_hash const& sent_target, udp::endpoint const& ep) + { + if (!m_alerts.should_post()) return; + m_alerts.emplace_alert(target, sent_target, ep); + } + +#ifndef TORRENT_DISABLE_LOGGING + bool session_impl::should_log(module_t) const + { + return m_alerts.should_post(); + } + + TORRENT_FORMAT(3,4) + void session_impl::log(module_t m, char const* fmt, ...) + { + if (!m_alerts.should_post()) return; + + va_list v; + va_start(v, fmt); + m_alerts.emplace_alert( + static_cast(m), fmt, v); + va_end(v); + } + + void session_impl::log_packet(message_direction_t dir, span pkt + , udp::endpoint const& node) + { + if (!m_alerts.should_post()) return; + + dht_pkt_alert::direction_t d = dir == dht::dht_logger::incoming_message + ? dht_pkt_alert::incoming : dht_pkt_alert::outgoing; + + m_alerts.emplace_alert(pkt, d, node); + } + + bool session_impl::should_log_portmap(portmap_transport) const + { + return m_alerts.should_post(); + } + + void session_impl::log_portmap(portmap_transport transport, char const* msg) const + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(transport, msg); + } + + bool session_impl::should_log_lsd() const + { + return m_alerts.should_post(); + } + + void session_impl::log_lsd(char const* msg) const + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(msg); + } +#endif + + bool session_impl::on_dht_request(string_view query + , dht::msg const& request, entry& response) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& ext : m_ses_extensions[plugins_dht_request_idx]) + { + if (ext->on_dht_request(query + , request.addr, request.message, response)) + return true; + } +#else + TORRENT_UNUSED(query); + TORRENT_UNUSED(request); + TORRENT_UNUSED(response); +#endif + return false; + } + + void session_impl::set_external_address( + tcp::endpoint const& local_endpoint, address const& ip + , ip_source_t const source_type, address const& source) + { + auto sock = std::find_if(m_listen_sockets.begin(), m_listen_sockets.end() + , [&](std::shared_ptr const& v) + { return v->local_endpoint.address() == local_endpoint.address(); }); + + if (sock != m_listen_sockets.end()) + set_external_address(*sock, ip, source_type, source); + } + + void session_impl::set_external_address(std::shared_ptr const& sock + , address const& ip, ip_source_t const source_type, address const& source) + { + if (!sock->external_address.cast_vote(ip, source_type, source)) return; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + session_log("external address updated for %s [ new-ip: %s type: %d last-voter: %s ]" + , sock->device.empty() ? print_endpoint(sock->local_endpoint).c_str() : sock->device.c_str() + , print_address(ip).c_str() + , static_cast(source_type) + , print_address(source).c_str()); + } +#endif + + if (m_alerts.should_post()) + m_alerts.emplace_alert(ip); + + for (auto const& t : m_torrents) + { + t.second->new_external_ip(); + } + + // since we have a new external IP now, we need to + // restart the DHT with a new node ID + +#ifndef TORRENT_DISABLE_DHT + if (m_dht) m_dht->update_node_id(sock); +#endif + } + +#if TORRENT_USE_INVARIANT_CHECKS + void session_impl::check_invariant() const + { + TORRENT_ASSERT(is_single_thread()); + + if (m_settings.get_int(settings_pack::unchoke_slots_limit) < 0 + && m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::fixed_slots_choker) + TORRENT_ASSERT(m_stats_counters[counters::num_unchoke_slots] == std::numeric_limits::max()); + + for (torrent_list_index_t l{}; l != m_torrent_lists.end_index(); ++l) + { + std::vector const& list = m_torrent_lists[l]; + for (auto const& i : list) + { + TORRENT_ASSERT(i->m_links[l].in_list()); + } + + queue_position_t idx{}; + for (auto t : m_download_queue) + { + TORRENT_ASSERT(t->queue_position() == idx); + ++idx; + } + } + + int const num_gauges = counters::num_error_torrents - counters::num_checking_torrents + 1; + aux::array torrent_state_gauges; + torrent_state_gauges.fill(0); + +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + std::unordered_set unique; +#endif + + int num_active_downloading = 0; + int num_active_finished = 0; + int total_downloaders = 0; + for (auto const& tor : m_torrents) + { + std::shared_ptr const& t = tor.second; + if (t->want_peers_download()) ++num_active_downloading; + if (t->want_peers_finished()) ++num_active_finished; + TORRENT_ASSERT(!(t->want_peers_download() && t->want_peers_finished())); + + int const state = t->current_stats_state() - counters::num_checking_torrents; + if (state != torrent::no_gauge_state) + { + ++torrent_state_gauges[state]; + } + + queue_position_t const pos = t->queue_position(); + if (pos < queue_position_t{}) + { + TORRENT_ASSERT(pos == no_pos); + continue; + } + ++total_downloaders; + +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + unique.insert(t->queue_position()); +#endif + } + + for (int i = 0, j = counters::num_checking_torrents; + j < counters::num_error_torrents + 1; ++i, ++j) + { + TORRENT_ASSERT(torrent_state_gauges[i] == m_stats_counters[j]); + } + +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(int(unique.size()) == total_downloaders); +#endif + TORRENT_ASSERT(num_active_downloading == int(m_torrent_lists[torrent_want_peers_download].size())); + TORRENT_ASSERT(num_active_finished == int(m_torrent_lists[torrent_want_peers_finished].size())); + + std::unordered_set unique_peers; + + int unchokes = 0; + int unchokes_all = 0; + int num_optimistic = 0; + int disk_queue[2] = {0, 0}; + for (auto const& p : m_connections) + { + TORRENT_ASSERT(p); + if (p->is_disconnecting()) continue; + + std::shared_ptr t = p->associated_torrent().lock(); + TORRENT_ASSERT(unique_peers.find(p.get()) == unique_peers.end()); + unique_peers.insert(p.get()); + + if (p->m_channel_state[0] & peer_info::bw_disk) ++disk_queue[0]; + if (p->m_channel_state[1] & peer_info::bw_disk) ++disk_queue[1]; + + if (p->ignore_unchoke_slots()) + { + if (!p->is_choked()) ++unchokes_all; + continue; + } + if (!p->is_choked()) + { + ++unchokes; + ++unchokes_all; + } + + if (p->peer_info_struct() + && p->peer_info_struct()->optimistically_unchoked) + { + ++num_optimistic; + TORRENT_ASSERT(!p->is_choked()); + } + } + + for (auto const& p : m_undead_peers) + { + if (p->ignore_unchoke_slots()) + { + if (!p->is_choked()) ++unchokes_all; + continue; + } + if (!p->is_choked()) + { + ++unchokes_all; + ++unchokes; + } + + if (p->peer_info_struct() + && p->peer_info_struct()->optimistically_unchoked) + { + ++num_optimistic; + TORRENT_ASSERT(!p->is_choked()); + } + } + + TORRENT_ASSERT(disk_queue[peer_connection::download_channel] + == m_stats_counters[counters::num_peers_down_disk]); + TORRENT_ASSERT(disk_queue[peer_connection::upload_channel] + == m_stats_counters[counters::num_peers_up_disk]); + + if (m_settings.get_int(settings_pack::num_optimistic_unchoke_slots)) + { + TORRENT_ASSERT(num_optimistic <= m_settings.get_int( + settings_pack::num_optimistic_unchoke_slots)); + } + + int const unchoked_counter_all = int(m_stats_counters[counters::num_peers_up_unchoked_all]); + int const unchoked_counter = int(m_stats_counters[counters::num_peers_up_unchoked]); + int const unchoked_counter_optimistic + = int(m_stats_counters[counters::num_peers_up_unchoked_optimistic]); + + TORRENT_ASSERT_VAL(unchoked_counter_all == unchokes_all, unchokes_all); + TORRENT_ASSERT_VAL(unchoked_counter == unchokes, unchokes); + TORRENT_ASSERT_VAL(unchoked_counter_optimistic == num_optimistic, num_optimistic); + + for (auto const& te : m_torrents) + { + TORRENT_ASSERT(te.second); + } + } +#endif // TORRENT_USE_INVARIANT_CHECKS + +#ifndef TORRENT_DISABLE_LOGGING + tracker_logger::tracker_logger(session_interface& ses): m_ses(ses) {} + void tracker_logger::tracker_warning(tracker_request const& + , std::string const& str) + { + debug_log("*** tracker warning: %s", str.c_str()); + } + + void tracker_logger::tracker_response(tracker_request const& + , libtorrent::address const& tracker_ip + , std::list
    const& tracker_ips + , struct tracker_response const& resp) + { + TORRENT_UNUSED(tracker_ips); + debug_log("TRACKER RESPONSE\n" + "interval: %d\n" + "external ip: %s\n" + "we connected to: %s\n" + "peers:" + , resp.interval.count() + , print_address(resp.external_ip).c_str() + , print_address(tracker_ip).c_str()); + + for (auto const& p : resp.peers) + { + debug_log(" %16s %5d %s", p.hostname.c_str(), p.port + , p.pid.is_all_zeros() ? "" : to_hex(p.pid).c_str()); + } + for (auto const& p : resp.peers4) + { + debug_log(" %s:%d", print_address(address_v4(p.ip)).c_str(), p.port); + } + for (auto const& p : resp.peers6) + { + debug_log(" [%s]:%d", print_address(address_v6(p.ip)).c_str(), p.port); + } + } + + void tracker_logger::tracker_request_error(tracker_request const& + , error_code const& ec, std::string const& str + , seconds32 const retry_interval) + { + TORRENT_UNUSED(retry_interval); + debug_log("*** tracker error: %s %s" + , ec.message().c_str(), str.c_str()); + } + + bool tracker_logger::should_log() const + { + return m_ses.alerts().should_post(); + } + + void tracker_logger::debug_log(const char* fmt, ...) const noexcept try + { + if (!m_ses.alerts().should_post()) return; + + va_list v; + va_start(v, fmt); + m_ses.alerts().emplace_alert(fmt, v); + va_end(v); + } + catch (std::exception const&) {} +#endif // TORRENT_DISABLE_LOGGING +}} diff --git a/src/session_settings.cpp b/src/session_settings.cpp new file mode 100644 index 0000000..6a2412b --- /dev/null +++ b/src/session_settings.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/settings_pack.hpp" + +#include + +namespace libtorrent { namespace aux { + + session_settings::session_settings() = default; + + session_settings::session_settings(settings_pack const& p) + { + apply_pack_impl(&p, m_store); + } + + void session_settings::bulk_set(std::function f) + { + std::unique_lock l(m_mutex); + f(m_store); + } + + void session_settings::bulk_get(std::function f) const + { + std::unique_lock l(m_mutex); + f(m_store); + } + + session_settings_single_thread::session_settings_single_thread() + { + initialize_default_settings(*this); + } + +} } + diff --git a/src/session_stats.cpp b/src/session_stats.cpp new file mode 100644 index 0000000..7a96fa9 --- /dev/null +++ b/src/session_stats.cpp @@ -0,0 +1,600 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session_stats.hpp" // for stats_metric +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/performance_counters.hpp" // for counters + +#include +#include + +namespace libtorrent { + +#if TORRENT_ABI_VERSION == 1 + constexpr metric_type_t stats_metric::type_counter; + constexpr metric_type_t stats_metric::type_gauge; +#endif + +namespace { + + struct stats_metric_impl + { + char const* name; + int value_index; + }; + +#define METRIC(category, name) { #category "." #name, counters:: name }, + aux::array const metrics + ({{ + // ``error_peers`` is the total number of peer disconnects + // caused by an error (not initiated by this client) and + // disconnected initiated by this client (``disconnected_peers``). + METRIC(peer, error_peers) + METRIC(peer, disconnected_peers) + + // these counters break down the peer errors into more specific + // categories. These errors are what the underlying transport + // reported (i.e. TCP or uTP) + METRIC(peer, eof_peers) + METRIC(peer, connreset_peers) + METRIC(peer, connrefused_peers) + METRIC(peer, connaborted_peers) + METRIC(peer, notconnected_peers) + METRIC(peer, perm_peers) + METRIC(peer, buffer_peers) + METRIC(peer, unreachable_peers) + METRIC(peer, broken_pipe_peers) + METRIC(peer, addrinuse_peers) + METRIC(peer, no_access_peers) + METRIC(peer, invalid_arg_peers) + METRIC(peer, aborted_peers) + + // the total number of incoming piece requests we've received followed + // by the number of rejected piece requests for various reasons. + // max_piece_requests mean we already had too many outstanding requests + // from this peer, so we rejected it. cancelled_piece_requests are ones + // where the other end explicitly asked for the piece to be rejected. + METRIC(peer, piece_requests) + METRIC(peer, max_piece_requests) + METRIC(peer, invalid_piece_requests) + METRIC(peer, choked_piece_requests) + METRIC(peer, cancelled_piece_requests) + METRIC(peer, piece_rejects) + + // these counters break down the peer errors into + // whether they happen on incoming or outgoing peers. + METRIC(peer, error_incoming_peers) + METRIC(peer, error_outgoing_peers) + + // these counters break down the peer errors into + // whether they happen on encrypted peers (just + // encrypted handshake) and rc4 peers (full stream + // encryption). These can indicate whether encrypted + // peers are more or less likely to fail + METRIC(peer, error_rc4_peers) + METRIC(peer, error_encrypted_peers) + + // these counters break down the peer errors into + // whether they happen on uTP peers or TCP peers. + // these may indicate whether one protocol is + // more error prone + METRIC(peer, error_tcp_peers) + METRIC(peer, error_utp_peers) + + // these counters break down the reasons to + // disconnect peers. + METRIC(peer, connect_timeouts) + METRIC(peer, uninteresting_peers) + METRIC(peer, timeout_peers) + METRIC(peer, no_memory_peers) + METRIC(peer, too_many_peers) + METRIC(peer, transport_timeout_peers) + METRIC(peer, num_banned_peers) + METRIC(peer, banned_for_hash_failure) + + METRIC(peer, connection_attempts) + METRIC(peer, connection_attempt_loops) + METRIC(peer, boost_connection_attempts) + METRIC(peer, missed_connection_attempts) + METRIC(peer, no_peer_connection_attempts) + METRIC(peer, incoming_connections) + + // the number of peer connections for each kind of socket. + // ``num_peers_half_open`` counts half-open (connecting) peers, no other + // count includes those peers. + // ``num_peers_up_unchoked_all`` is the total number of unchoked peers, + // whereas ``num_peers_up_unchoked`` only are unchoked peers that count + // against the limit (i.e. excluding peers that are unchoked because the + // limit doesn't apply to them). ``num_peers_up_unchoked_optimistic`` is + // the number of optimistically unchoked peers. + METRIC(peer, num_tcp_peers) + METRIC(peer, num_socks5_peers) + METRIC(peer, num_http_proxy_peers) + METRIC(peer, num_utp_peers) + METRIC(peer, num_i2p_peers) + METRIC(peer, num_ssl_peers) + METRIC(peer, num_ssl_socks5_peers) + METRIC(peer, num_ssl_http_proxy_peers) + METRIC(peer, num_ssl_utp_peers) + + METRIC(peer, num_peers_half_open) + METRIC(peer, num_peers_connected) + METRIC(peer, num_peers_up_interested) + METRIC(peer, num_peers_down_interested) + METRIC(peer, num_peers_up_unchoked_all) + METRIC(peer, num_peers_up_unchoked_optimistic) + METRIC(peer, num_peers_up_unchoked) + METRIC(peer, num_peers_down_unchoked) + METRIC(peer, num_peers_up_requests) + METRIC(peer, num_peers_down_requests) + METRIC(peer, num_peers_end_game) + METRIC(peer, num_peers_up_disk) + METRIC(peer, num_peers_down_disk) + + // These counters count the number of times the + // network thread wakes up for each respective + // reason. If these counters are very large, it + // may indicate a performance issue, causing the + // network thread to wake up too ofte, wasting CPU. + // mitigate it by increasing buffers and limits + // for the specific trigger that wakes up the + // thread. + METRIC(net, on_read_counter) + METRIC(net, on_write_counter) + METRIC(net, on_tick_counter) + METRIC(net, on_lsd_counter) + METRIC(net, on_lsd_peer_counter) + METRIC(net, on_udp_counter) + METRIC(net, on_accept_counter) + METRIC(net, on_disk_queue_counter) + METRIC(net, on_disk_counter) + + // total number of bytes sent and received by the session + METRIC(net, sent_payload_bytes) + METRIC(net, sent_bytes) + METRIC(net, sent_ip_overhead_bytes) + METRIC(net, sent_tracker_bytes) + METRIC(net, recv_payload_bytes) + METRIC(net, recv_bytes) + METRIC(net, recv_ip_overhead_bytes) + METRIC(net, recv_tracker_bytes) + + // the number of sockets currently waiting for upload and download + // bandwidth from the rate limiter. + METRIC(net, limiter_up_queue) + METRIC(net, limiter_down_queue) + + // the number of upload and download bytes waiting to be handed out from + // the rate limiter. + METRIC(net, limiter_up_bytes) + METRIC(net, limiter_down_bytes) + + // the number of bytes downloaded that had to be discarded because they + // failed the hash check + METRIC(net, recv_failed_bytes) + + // the number of downloaded bytes that were discarded because they + // were downloaded multiple times (from different peers) + METRIC(net, recv_redundant_bytes) + + // is false by default and set to true when + // the first incoming connection is established + // this is used to know if the client is behind + // NAT or not. + METRIC(net, has_incoming_connections) + + // these gauges count the number of torrents in + // different states. Each torrent only belongs to + // one of these states. For torrents that could + // belong to multiple of these, the most prominent + // in picked. For instance, a torrent with an error + // counts as an error-torrent, regardless of its other + // state. + METRIC(ses, num_checking_torrents) + METRIC(ses, num_stopped_torrents) + METRIC(ses, num_upload_only_torrents) + METRIC(ses, num_downloading_torrents) + METRIC(ses, num_seeding_torrents) + METRIC(ses, num_queued_seeding_torrents) + METRIC(ses, num_queued_download_torrents) + METRIC(ses, num_error_torrents) + + // the number of torrents that don't have the + // IP filter applied to them. + METRIC(ses, non_filter_torrents) + + // these count the number of times a piece has passed the + // hash check, the number of times a piece was successfully + // written to disk and the number of total possible pieces + // added by adding torrents. e.g. when adding a torrent with + // 1000 piece, num_total_pieces_added is incremented by 1000. + METRIC(ses, num_piece_passed) + METRIC(ses, num_piece_failed) + + METRIC(ses, num_have_pieces) + METRIC(ses, num_total_pieces_added) + +#if TORRENT_ABI_VERSION == 1 + // this counts the number of times a torrent has been + // evicted (only applies when dynamic-loading-of-torrent-files + // is enabled, which is deprecated). + METRIC(ses, torrent_evicted_counter) +#endif + + // the number of allowed unchoked peers + METRIC(ses, num_unchoke_slots) + + // the number of listen sockets that are currently accepting incoming + // connections + METRIC(ses, num_outstanding_accept) + + // bittorrent message counters. These counters are incremented + // every time a message of the corresponding type is received from + // or sent to a bittorrent peer. + METRIC(ses, num_incoming_choke) + METRIC(ses, num_incoming_unchoke) + METRIC(ses, num_incoming_interested) + METRIC(ses, num_incoming_not_interested) + METRIC(ses, num_incoming_have) + METRIC(ses, num_incoming_bitfield) + METRIC(ses, num_incoming_request) + METRIC(ses, num_incoming_piece) + METRIC(ses, num_incoming_cancel) + METRIC(ses, num_incoming_dht_port) + METRIC(ses, num_incoming_suggest) + METRIC(ses, num_incoming_have_all) + METRIC(ses, num_incoming_have_none) + METRIC(ses, num_incoming_reject) + METRIC(ses, num_incoming_allowed_fast) + METRIC(ses, num_incoming_ext_handshake) + METRIC(ses, num_incoming_pex) + METRIC(ses, num_incoming_metadata) + METRIC(ses, num_incoming_extended) + + METRIC(ses, num_outgoing_choke) + METRIC(ses, num_outgoing_unchoke) + METRIC(ses, num_outgoing_interested) + METRIC(ses, num_outgoing_not_interested) + METRIC(ses, num_outgoing_have) + METRIC(ses, num_outgoing_bitfield) + METRIC(ses, num_outgoing_request) + METRIC(ses, num_outgoing_piece) + METRIC(ses, num_outgoing_cancel) + METRIC(ses, num_outgoing_dht_port) + METRIC(ses, num_outgoing_suggest) + METRIC(ses, num_outgoing_have_all) + METRIC(ses, num_outgoing_have_none) + METRIC(ses, num_outgoing_reject) + METRIC(ses, num_outgoing_allowed_fast) + METRIC(ses, num_outgoing_ext_handshake) + METRIC(ses, num_outgoing_pex) + METRIC(ses, num_outgoing_metadata) + METRIC(ses, num_outgoing_extended) + + // the number of wasted downloaded bytes by reason of the bytes being + // wasted. + METRIC(ses, waste_piece_timed_out) + METRIC(ses, waste_piece_cancelled) + METRIC(ses, waste_piece_unknown) + METRIC(ses, waste_piece_seed) + METRIC(ses, waste_piece_end_game) + METRIC(ses, waste_piece_closing) + + // the number of pieces considered while picking pieces + METRIC(picker, piece_picker_partial_loops) + METRIC(picker, piece_picker_suggest_loops) + METRIC(picker, piece_picker_sequential_loops) + METRIC(picker, piece_picker_reverse_rare_loops) + METRIC(picker, piece_picker_rare_loops) + METRIC(picker, piece_picker_rand_start_loops) + METRIC(picker, piece_picker_rand_loops) + METRIC(picker, piece_picker_busy_loops) + + // This breaks down the piece picks into the event that + // triggered it + METRIC(picker, reject_piece_picks) + METRIC(picker, unchoke_piece_picks) + METRIC(picker, incoming_redundant_piece_picks) + METRIC(picker, incoming_piece_picks) + METRIC(picker, end_game_piece_picks) + METRIC(picker, snubbed_piece_picks) + METRIC(picker, interesting_piece_picks) + METRIC(picker, hash_fail_piece_picks) + + // These gauges indicate how many blocks are currently in use as dirty + // disk blocks (``write_cache_blocks``) and read cache blocks, + // respectively. deprecates ``cache_status::read_cache_size``. + // The sum of these gauges deprecates ``cache_status::cache_size``. + METRIC(disk, write_cache_blocks) + METRIC(disk, read_cache_blocks) + + // the number of microseconds it takes from receiving a request from a + // peer until we're sending the response back on the socket. + METRIC(disk, request_latency) + + // ``disk_blocks_in_use`` indicates how many disk blocks are currently in + // use, either as dirty blocks waiting to be written or blocks kept around + // in the hope that a peer will request it or in a peer send buffer. This + // gauge deprecates ``cache_status::total_used_buffers``. + METRIC(disk, pinned_blocks) + METRIC(disk, disk_blocks_in_use) + + // ``queued_disk_jobs`` is the number of disk jobs currently queued, + // waiting to be executed by a disk thread. Deprecates + // ``cache_status::job_queue_length``. + METRIC(disk, queued_disk_jobs) + METRIC(disk, num_running_disk_jobs) + METRIC(disk, num_read_jobs) + METRIC(disk, num_write_jobs) + METRIC(disk, num_jobs) + METRIC(disk, blocked_disk_jobs) + + METRIC(disk, num_writing_threads) + METRIC(disk, num_running_threads) + + // the number of bytes we have sent to the disk I/O + // thread for writing. Every time we hear back from + // the disk I/O thread with a completed write job, this + // is updated to the number of bytes the disk I/O thread + // is actually waiting for to be written (as opposed to + // bytes just hanging out in the cache) + METRIC(disk, queued_write_bytes) + METRIC(disk, arc_mru_size) + METRIC(disk, arc_mru_ghost_size) + METRIC(disk, arc_mfu_size) + METRIC(disk, arc_mfu_ghost_size) + METRIC(disk, arc_write_size) + METRIC(disk, arc_volatile_size) + + // the number of blocks written and read from disk in total. A block is 16 + // kiB. ``num_blocks_written`` and ``num_blocks_read`` deprecates + // ``cache_status::blocks_written`` and ``cache_status::blocks_read`` respectively. + METRIC(disk, num_blocks_written) + METRIC(disk, num_blocks_read) + + // the total number of blocks run through SHA-1 hashing + METRIC(disk, num_blocks_hashed) + + // the number of blocks read from the disk cache + // Deprecates ``cache_info::blocks_read_hit``. + METRIC(disk, num_blocks_cache_hits) + + // the number of disk I/O operation for reads and writes. One disk + // operation may transfer more then one block. + // These counters deprecates ``cache_status::writes`` and + // ``cache_status::reads``. + METRIC(disk, num_write_ops) + METRIC(disk, num_read_ops) + + // the number of blocks that had to be read back from disk in order to + // hash a piece (when verifying against the piece hash) + METRIC(disk, num_read_back) + + // cumulative time spent in various disk jobs, as well + // as total for all disk jobs. Measured in microseconds + METRIC(disk, disk_read_time) + METRIC(disk, disk_write_time) + METRIC(disk, disk_hash_time) + METRIC(disk, disk_job_time) + + // for each kind of disk job, a counter of how many jobs of that kind + // are currently blocked by a disk fence + METRIC(disk, num_fenced_read) + METRIC(disk, num_fenced_write) + METRIC(disk, num_fenced_hash) + METRIC(disk, num_fenced_move_storage) + METRIC(disk, num_fenced_release_files) + METRIC(disk, num_fenced_delete_files) + METRIC(disk, num_fenced_check_fastresume) + METRIC(disk, num_fenced_save_resume_data) + METRIC(disk, num_fenced_rename_file) + METRIC(disk, num_fenced_stop_torrent) + METRIC(disk, num_fenced_flush_piece) + METRIC(disk, num_fenced_flush_hashed) + METRIC(disk, num_fenced_flush_storage) + METRIC(disk, num_fenced_trim_cache) + METRIC(disk, num_fenced_file_priority) + METRIC(disk, num_fenced_load_torrent) + METRIC(disk, num_fenced_clear_piece) + METRIC(disk, num_fenced_tick_storage) + + // The number of nodes in the DHT routing table + METRIC(dht, dht_nodes) + + // The number of replacement nodes in the DHT routing table + METRIC(dht, dht_node_cache) + + // the number of torrents currently tracked by our DHT node + METRIC(dht, dht_torrents) + + // the number of peers currently tracked by our DHT node + METRIC(dht, dht_peers) + + // the number of immutable data items tracked by our DHT node + METRIC(dht, dht_immutable_data) + + // the number of mutable data items tracked by our DHT node + METRIC(dht, dht_mutable_data) + + // the number of RPC observers currently allocated + METRIC(dht, dht_allocated_observers) + + // the total number of DHT messages sent and received + METRIC(dht, dht_messages_in) + METRIC(dht, dht_messages_out) + + // the number of incoming DHT requests that were dropped. There are a few + // different reasons why incoming DHT packets may be dropped: + // + // 1. there wasn't enough send quota to respond to them. + // 2. the Denial of service logic kicked in, blocking the peer + // 3. ignore_dark_internet is enabled, and the packet came from a + // non-public IP address + // 4. the bencoding of the message was invalid + METRIC(dht, dht_messages_in_dropped) + + // the number of outgoing messages that failed to be + // sent + METRIC(dht, dht_messages_out_dropped) + + // the total number of bytes sent and received by the DHT + METRIC(dht, dht_bytes_in) + METRIC(dht, dht_bytes_out) + + // the number of DHT messages we've sent and received + // by kind. + METRIC(dht, dht_ping_in) + METRIC(dht, dht_ping_out) + METRIC(dht, dht_find_node_in) + METRIC(dht, dht_find_node_out) + METRIC(dht, dht_get_peers_in) + METRIC(dht, dht_get_peers_out) + METRIC(dht, dht_announce_peer_in) + METRIC(dht, dht_announce_peer_out) + METRIC(dht, dht_get_in) + METRIC(dht, dht_get_out) + METRIC(dht, dht_put_in) + METRIC(dht, dht_put_out) + METRIC(dht, dht_sample_infohashes_in) + METRIC(dht, dht_sample_infohashes_out) + + // the number of failed incoming DHT requests by kind of request + METRIC(dht, dht_invalid_announce) + METRIC(dht, dht_invalid_get_peers) + METRIC(dht, dht_invalid_find_node) + METRIC(dht, dht_invalid_put) + METRIC(dht, dht_invalid_get) + METRIC(dht, dht_invalid_sample_infohashes) + + // uTP counters. Each counter represents the number of time each event + // has occurred. + METRIC(utp, utp_packet_loss) + METRIC(utp, utp_timeout) + METRIC(utp, utp_packets_in) + METRIC(utp, utp_packets_out) + METRIC(utp, utp_fast_retransmit) + METRIC(utp, utp_packet_resend) + METRIC(utp, utp_samples_above_target) + METRIC(utp, utp_samples_below_target) + METRIC(utp, utp_payload_pkts_in) + METRIC(utp, utp_payload_pkts_out) + METRIC(utp, utp_invalid_pkts_in) + METRIC(utp, utp_redundant_pkts_in) + + // the number of uTP sockets in each respective state + METRIC(utp, num_utp_idle) + METRIC(utp, num_utp_syn_sent) + METRIC(utp, num_utp_connected) + METRIC(utp, num_utp_fin_sent) + METRIC(utp, num_utp_close_wait) + METRIC(utp, num_utp_deleted) + + // the buffer sizes accepted by + // socket send and receive calls respectively. + // The larger the buffers are, the more efficient, + // because it reqire fewer system calls per byte. + // The size is 1 << n, where n is the number + // at the end of the counter name. i.e. + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + // bytes + METRIC(sock_bufs, socket_send_size3) + METRIC(sock_bufs, socket_send_size4) + METRIC(sock_bufs, socket_send_size5) + METRIC(sock_bufs, socket_send_size6) + METRIC(sock_bufs, socket_send_size7) + METRIC(sock_bufs, socket_send_size8) + METRIC(sock_bufs, socket_send_size9) + METRIC(sock_bufs, socket_send_size10) + METRIC(sock_bufs, socket_send_size11) + METRIC(sock_bufs, socket_send_size12) + METRIC(sock_bufs, socket_send_size13) + METRIC(sock_bufs, socket_send_size14) + METRIC(sock_bufs, socket_send_size15) + METRIC(sock_bufs, socket_send_size16) + METRIC(sock_bufs, socket_send_size17) + METRIC(sock_bufs, socket_send_size18) + METRIC(sock_bufs, socket_send_size19) + METRIC(sock_bufs, socket_send_size20) + METRIC(sock_bufs, socket_recv_size3) + METRIC(sock_bufs, socket_recv_size4) + METRIC(sock_bufs, socket_recv_size5) + METRIC(sock_bufs, socket_recv_size6) + METRIC(sock_bufs, socket_recv_size7) + METRIC(sock_bufs, socket_recv_size8) + METRIC(sock_bufs, socket_recv_size9) + METRIC(sock_bufs, socket_recv_size10) + METRIC(sock_bufs, socket_recv_size11) + METRIC(sock_bufs, socket_recv_size12) + METRIC(sock_bufs, socket_recv_size13) + METRIC(sock_bufs, socket_recv_size14) + METRIC(sock_bufs, socket_recv_size15) + METRIC(sock_bufs, socket_recv_size16) + METRIC(sock_bufs, socket_recv_size17) + METRIC(sock_bufs, socket_recv_size18) + METRIC(sock_bufs, socket_recv_size19) + METRIC(sock_bufs, socket_recv_size20) + + // if the outstanding tracker announce limit is reached, tracker + // announces are queued, to be issued when an announce slot opens up. + // this measure the number of tracker announces currently in the + // queue + METRIC(tracker, num_queued_tracker_announces) + // ... more + }}); +#undef METRIC + } // anonymous namespace + + std::vector session_stats_metrics() + { + aux::vector stats; + stats.resize(metrics.size()); + for (int i = 0; i < metrics.end_index(); ++i) + { + stats[i].name = metrics[i].name; + stats[i].value_index = metrics[i].value_index; + stats[i].type = metrics[i].value_index >= counters::num_stats_counters + ? metric_type_t::gauge : metric_type_t::counter; + } + return std::move(stats); + } + + int find_metric_idx(string_view name) + { + auto const i = std::find_if(std::begin(metrics), std::end(metrics) + , [name](stats_metric_impl const& metr) + { return metr.name == name; }); + + if (i == std::end(metrics)) return -1; + return i->value_index; + } +} diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp new file mode 100644 index 0000000..66ab40f --- /dev/null +++ b/src/settings_pack.cpp @@ -0,0 +1,793 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +#include + +namespace { + + template + bool compare_first(std::pair const& lhs + , std::pair const& rhs) + { + return lhs.first < rhs.first; + } + + template + void insort_replace(std::vector>& c, std::pair v) + { + auto i = std::lower_bound(c.begin(), c.end(), v, &compare_first); + if (i != c.end() && i->first == v.first) i->second = std::move(v.second); + else c.emplace(i, std::move(v)); + } + + // return the string, unless it's null, in which case the empty string is + // returned + char const* ensure_string(char const* str) + { return str == nullptr ? "" : str; } +} + +namespace libtorrent { + + struct str_setting_entry_t + { + // the name of this setting. used for serialization and deserialization + char const* name; + // if present, this function is called when the setting is changed + void (aux::session_impl::*fun)(); + char const *default_value; + }; + + struct int_setting_entry_t + { + // the name of this setting. used for serialization and deserialization + char const* name; + // if present, this function is called when the setting is changed + void (aux::session_impl::*fun)(); + int default_value; + }; + + struct bool_setting_entry_t + { + // the name of this setting. used for serialization and deserialization + char const* name; + // if present, this function is called when the setting is changed + void (aux::session_impl::*fun)(); + bool default_value; + }; + + +#define SET(name, default_value, fun) { #name, fun, default_value } + +#if TORRENT_ABI_VERSION == 1 +#define DEPRECATED_SET(name, default_value, fun) { #name, fun, default_value } +#define DEPRECATED_SET_STR(name, default_value, fun) { #name, fun, default_value } +#else +#define DEPRECATED_SET(name, default_value, fun) { "", nullptr, 0 } +#define DEPRECATED_SET_STR(name, default_value, fun) { "", nullptr, nullptr } +#endif + +#ifdef TORRENT_WINDOWS +constexpr int CLOSE_FILE_INTERVAL = 120; +#else +constexpr int CLOSE_FILE_INTERVAL = 0; +#endif + + namespace { + + using aux::session_impl; + + aux::array const str_settings + ({{ + SET(user_agent, "libtorrent/" LIBTORRENT_VERSION, &session_impl::update_user_agent), + SET(announce_ip, nullptr, nullptr), + DEPRECATED_SET_STR(mmap_cache, nullptr, nullptr), + SET(handshake_client_version, nullptr, nullptr), + SET(outgoing_interfaces, "", &session_impl::update_outgoing_interfaces), + SET(listen_interfaces, "0.0.0.0:6881,[::]:6881", &session_impl::update_listen_interfaces), + SET(proxy_hostname, "", &session_impl::update_proxy), + SET(proxy_username, "", &session_impl::update_proxy), + SET(proxy_password, "", &session_impl::update_proxy), + SET(i2p_hostname, "", &session_impl::update_i2p_bridge), + SET(peer_fingerprint, "-LT1290-", nullptr), + SET(dht_bootstrap_nodes, "dht.libtorrent.org:25401", &session_impl::update_dht_bootstrap_nodes) + }}); + + aux::array const bool_settings + ({{ + SET(allow_multiple_connections_per_ip, false, nullptr), + DEPRECATED_SET(ignore_limits_on_local_network, true, &session_impl::update_ignore_rate_limits_on_local_network), + SET(send_redundant_have, true, nullptr), + DEPRECATED_SET(lazy_bitfields, false, nullptr), + SET(use_dht_as_fallback, false, nullptr), + SET(upnp_ignore_nonrouters, false, nullptr), + SET(use_parole_mode, true, nullptr), + SET(use_read_cache, true, nullptr), + DEPRECATED_SET(use_write_cache, true, nullptr), + DEPRECATED_SET(dont_flush_write_cache, false, nullptr), +#ifdef TORRENT_WINDOWS + // the emulation of preadv/pwritev uses overlapped reads/writes to be able + // to issue them all back to back. However, it appears windows fail to + // merge them. At least for people reporting performance issues in + // qBittorrent + SET(coalesce_reads, true, nullptr), + SET(coalesce_writes, true, nullptr), +#else + SET(coalesce_reads, false, nullptr), + SET(coalesce_writes, false, nullptr), +#endif + SET(auto_manage_prefer_seeds, false, nullptr), + SET(dont_count_slow_torrents, true, &session_impl::update_count_slow), + SET(close_redundant_connections, true, nullptr), + SET(prioritize_partial_pieces, false, nullptr), + SET(rate_limit_ip_overhead, true, nullptr), + SET(announce_to_all_trackers, false, nullptr), + SET(announce_to_all_tiers, false, nullptr), + SET(prefer_udp_trackers, true, nullptr), + DEPRECATED_SET(strict_super_seeding, false, nullptr), + DEPRECATED_SET(lock_disk_cache, false, nullptr), + SET(disable_hash_checks, false, nullptr), + SET(allow_i2p_mixed, false, nullptr), + DEPRECATED_SET(low_prio_disk, true, nullptr), + SET(volatile_read_cache, false, nullptr), + DEPRECATED_SET(guided_read_cache, false, nullptr), + SET(no_atime_storage, true, nullptr), + SET(incoming_starts_queued_torrents, false, nullptr), + SET(report_true_downloaded, false, nullptr), + SET(strict_end_game_mode, true, nullptr), + DEPRECATED_SET(broadcast_lsd, true, nullptr), + SET(enable_outgoing_utp, true, nullptr), + SET(enable_incoming_utp, true, nullptr), + SET(enable_outgoing_tcp, true, nullptr), + SET(enable_incoming_tcp, true, nullptr), + SET(ignore_resume_timestamps, false, nullptr), + SET(no_recheck_incomplete_resume, false, nullptr), + SET(anonymous_mode, false, nullptr), + SET(report_web_seed_downloads, true, &session_impl::update_report_web_seed_downloads), + DEPRECATED_SET(rate_limit_utp, true, &session_impl::update_rate_limit_utp), + DEPRECATED_SET(announce_double_nat, false, nullptr), + SET(seeding_outgoing_connections, true, nullptr), + SET(no_connect_privileged_ports, false, &session_impl::update_privileged_ports), + SET(smooth_connects, true, nullptr), + SET(always_send_user_agent, false, nullptr), + SET(apply_ip_filter_to_trackers, true, nullptr), + DEPRECATED_SET(use_disk_read_ahead, true, nullptr), + DEPRECATED_SET(lock_files, false, nullptr), + DEPRECATED_SET(contiguous_recv_buffer, true, nullptr), + SET(ban_web_seeds, true, nullptr), + SET(allow_partial_disk_writes, true, nullptr), + DEPRECATED_SET(force_proxy, false, nullptr), + SET(support_share_mode, true, nullptr), + SET(support_merkle_torrents, true, nullptr), + SET(report_redundant_bytes, true, nullptr), + SET(listen_system_port_fallback, true, nullptr), + DEPRECATED_SET(use_disk_cache_pool, false, nullptr), + SET(announce_crypto_support, true, nullptr), + SET(enable_upnp, true, &session_impl::update_upnp), + SET(enable_natpmp, true, &session_impl::update_natpmp), + SET(enable_lsd, true, &session_impl::update_lsd), + SET(enable_dht, true, &session_impl::update_dht), + SET(prefer_rc4, false, nullptr), + SET(proxy_hostnames, true, nullptr), + SET(proxy_peer_connections, true, nullptr), + SET(auto_sequential, true, &session_impl::update_auto_sequential), + SET(proxy_tracker_connections, true, nullptr), + SET(enable_ip_notifier, true, &session_impl::update_ip_notifier), + SET(dht_prefer_verified_node_ids, true, &session_impl::update_dht_settings), + SET(piece_extent_affinity, false, nullptr), + SET(validate_https_trackers, false, &session_impl::update_validate_https), + }}); + + aux::array const int_settings + ({{ + SET(tracker_completion_timeout, 30, nullptr), + SET(tracker_receive_timeout, 10, nullptr), + SET(stop_tracker_timeout, 5, nullptr), + SET(tracker_maximum_response_length, 1024*1024, nullptr), + SET(piece_timeout, 20, nullptr), + SET(request_timeout, 60, nullptr), + SET(request_queue_time, 3, nullptr), + SET(max_allowed_in_request_queue, 500, nullptr), + SET(max_out_request_queue, 500, nullptr), + SET(whole_pieces_threshold, 20, nullptr), + SET(peer_timeout, 120, nullptr), + SET(urlseed_timeout, 20, nullptr), + SET(urlseed_pipeline_size, 5, nullptr), + SET(urlseed_wait_retry, 30, nullptr), + SET(file_pool_size, 40, nullptr), + SET(max_failcount, 3, &session_impl::update_max_failcount), + SET(min_reconnect_time, 60, nullptr), + SET(peer_connect_timeout, 15, nullptr), + SET(connection_speed, 30, &session_impl::update_connection_speed), + SET(inactivity_timeout, 600, nullptr), + SET(unchoke_interval, 15, nullptr), + SET(optimistic_unchoke_interval, 30, nullptr), + SET(num_want, 200, nullptr), + SET(initial_picker_threshold, 4, nullptr), + SET(allowed_fast_set_size, 5, nullptr), + SET(suggest_mode, settings_pack::no_piece_suggestions, nullptr), + SET(max_queued_disk_bytes, 1024 * 1024, &session_impl::update_queued_disk_bytes), + SET(handshake_timeout, 10, nullptr), + SET(send_buffer_low_watermark, 10 * 1024, nullptr), + SET(send_buffer_watermark, 500 * 1024, nullptr), + SET(send_buffer_watermark_factor, 50, nullptr), + SET(choking_algorithm, settings_pack::fixed_slots_choker, nullptr), + SET(seed_choking_algorithm, settings_pack::round_robin, nullptr), + SET(cache_size, 2048, nullptr), + DEPRECATED_SET(cache_buffer_chunk_size, 0, nullptr), + SET(cache_expiry, 300, nullptr), + SET(disk_io_write_mode, settings_pack::enable_os_cache, nullptr), + SET(disk_io_read_mode, settings_pack::enable_os_cache, nullptr), + SET(outgoing_port, 0, nullptr), + SET(num_outgoing_ports, 0, nullptr), + SET(peer_tos, 0x20, &session_impl::update_peer_tos), + SET(active_downloads, 3, &session_impl::trigger_auto_manage), + SET(active_seeds, 5, &session_impl::trigger_auto_manage), + SET(active_checking, 1, &session_impl::trigger_auto_manage), + SET(active_dht_limit, 88, nullptr), + SET(active_tracker_limit, 1600, nullptr), + SET(active_lsd_limit, 60, nullptr), + SET(active_limit, 500, &session_impl::trigger_auto_manage), + DEPRECATED_SET(active_loaded_limit, 0, &session_impl::trigger_auto_manage), + SET(auto_manage_interval, 30, nullptr), + SET(seed_time_limit, 24 * 60 * 60, nullptr), + SET(auto_scrape_interval, 1800, nullptr), + SET(auto_scrape_min_interval, 300, nullptr), + SET(max_peerlist_size, 3000, nullptr), + SET(max_paused_peerlist_size, 1000, nullptr), + SET(min_announce_interval, 5 * 60, nullptr), + SET(auto_manage_startup, 60, nullptr), + SET(seeding_piece_quota, 20, nullptr), + // TODO: deprecate this + SET(max_rejects, 50, nullptr), + SET(recv_socket_buffer_size, 0, &session_impl::update_socket_buffer_size), + SET(send_socket_buffer_size, 0, &session_impl::update_socket_buffer_size), + SET(max_peer_recv_buffer_size, 2 * 1024 * 1024, nullptr), + DEPRECATED_SET(file_checks_delay_per_block, 0, nullptr), + SET(read_cache_line_size, 32, nullptr), + SET(write_cache_line_size, 16, nullptr), + SET(optimistic_disk_retry, 10 * 60, nullptr), + SET(max_suggest_pieces, 16, nullptr), + SET(local_service_announce_interval, 5 * 60, nullptr), + SET(dht_announce_interval, 15 * 60, &session_impl::update_dht_announce_interval), + SET(udp_tracker_token_expiry, 60, nullptr), + DEPRECATED_SET(default_cache_min_age, 1, nullptr), + SET(num_optimistic_unchoke_slots, 0, nullptr), + SET(default_est_reciprocation_rate, 16000, nullptr), + SET(increase_est_reciprocation_rate, 20, nullptr), + SET(decrease_est_reciprocation_rate, 3, nullptr), + SET(max_pex_peers, 50, nullptr), + SET(tick_interval, 500, nullptr), + SET(share_mode_target, 3, nullptr), + SET(upload_rate_limit, 0, &session_impl::update_upload_rate), + SET(download_rate_limit, 0, &session_impl::update_download_rate), + DEPRECATED_SET(local_upload_rate_limit, 0, &session_impl::update_local_upload_rate), + DEPRECATED_SET(local_download_rate_limit, 0, &session_impl::update_local_download_rate), + SET(dht_upload_rate_limit, 8000, &session_impl::update_dht_upload_rate_limit), + SET(unchoke_slots_limit, 8, &session_impl::update_unchoke_limit), + DEPRECATED_SET(half_open_limit, 0, nullptr), + SET(connections_limit, 200, &session_impl::update_connections_limit), + SET(connections_slack, 10, nullptr), + SET(utp_target_delay, 100, nullptr), + SET(utp_gain_factor, 3000, nullptr), + SET(utp_min_timeout, 500, nullptr), + SET(utp_syn_resends, 2, nullptr), + SET(utp_fin_resends, 2, nullptr), + SET(utp_num_resends, 3, nullptr), + SET(utp_connect_timeout, 3000, nullptr), + SET(utp_delayed_ack, 0, nullptr), + SET(utp_loss_multiplier, 50, nullptr), + SET(mixed_mode_algorithm, settings_pack::peer_proportional, nullptr), + SET(listen_queue_size, 5, nullptr), + SET(torrent_connect_boost, 30, nullptr), + SET(alert_queue_size, 1000, &session_impl::update_alert_queue_size), + SET(max_metadata_size, 3 * 1024 * 10240, nullptr), + DEPRECATED_SET(hashing_threads, 1, nullptr), + SET(checking_mem_usage, 1024, nullptr), + SET(predictive_piece_announce, 0, nullptr), + SET(aio_threads, 4, &session_impl::update_disk_threads), + DEPRECATED_SET(aio_max, 300, nullptr), + DEPRECATED_SET(network_threads, 0, nullptr), + DEPRECATED_SET(ssl_listen, 0, &session_impl::update_ssl_listen), + SET(tracker_backoff, 250, nullptr), + SET(share_ratio_limit, 200, nullptr), + SET(seed_time_ratio_limit, 700, nullptr), + SET(peer_turnover, 4, nullptr), + SET(peer_turnover_cutoff, 90, nullptr), + SET(peer_turnover_interval, 300, nullptr), + SET(connect_seed_every_n_download, 10, nullptr), + SET(max_http_recv_buffer_size, 4*1024*204, nullptr), + SET(max_retry_port_bind, 10, nullptr), + SET(alert_mask, int(static_cast(alert_category::error)), &session_impl::update_alert_mask), + SET(out_enc_policy, settings_pack::pe_enabled, nullptr), + SET(in_enc_policy, settings_pack::pe_enabled, nullptr), + SET(allowed_enc_level, settings_pack::pe_both, nullptr), + SET(inactive_down_rate, 2048, nullptr), + SET(inactive_up_rate, 2048, nullptr), + SET(proxy_type, settings_pack::none, &session_impl::update_proxy), + SET(proxy_port, 0, &session_impl::update_proxy), + SET(i2p_port, 0, &session_impl::update_i2p_bridge), + SET(cache_size_volatile, 256, nullptr), + SET(urlseed_max_request_bytes, 16 * 1024 * 1024, nullptr), + SET(web_seed_name_lookup_retry, 1800, nullptr), + SET(close_file_interval, CLOSE_FILE_INTERVAL, nullptr), + SET(utp_cwnd_reduce_timer, 100, nullptr), + SET(max_web_seed_connections, 3, nullptr), + SET(resolver_cache_timeout, 1200, &session_impl::update_resolver_cache_timeout), + SET(send_not_sent_low_watermark, 16384, nullptr), + SET(rate_choker_initial_threshold, 1024, nullptr), + SET(upnp_lease_duration, 3600, nullptr), + SET(max_concurrent_http_announces, 50, nullptr), + }}); + +#undef SET +#undef DEPRECATED_SET + + } // anonymous namespace + + int setting_by_name(string_view const key) + { + for (int k = 0; k < str_settings.end_index(); ++k) + { + if (key != str_settings[k].name) continue; + return settings_pack::string_type_base + k; + } + for (int k = 0; k < int_settings.end_index(); ++k) + { + if (key != int_settings[k].name) continue; + return settings_pack::int_type_base + k; + } + for (int k = 0; k < bool_settings.end_index(); ++k) + { + if (key != bool_settings[k].name) continue; + return settings_pack::bool_type_base + k; + } + return -1; + } + + char const* name_for_setting(int s) + { + switch (s & settings_pack::type_mask) + { + case settings_pack::string_type_base: + return str_settings[s - settings_pack::string_type_base].name; + case settings_pack::int_type_base: + return int_settings[s - settings_pack::int_type_base].name; + case settings_pack::bool_type_base: + return bool_settings[s - settings_pack::bool_type_base].name; + } + return ""; + } + + settings_pack load_pack_from_dict(bdecode_node const& settings) + { + settings_pack pack; + + for (int i = 0; i < settings.dict_size(); ++i) + { + string_view key; + bdecode_node val; + std::tie(key, val) = settings.dict_at(i); + switch (val.type()) + { + case bdecode_node::dict_t: + case bdecode_node::list_t: + continue; + case bdecode_node::int_t: + { + bool found = false; + for (int k = 0; k < int_settings.end_index(); ++k) + { + if (key != int_settings[k].name) continue; + pack.set_int(settings_pack::int_type_base | k, int(val.int_value())); + found = true; + break; + } + if (found) continue; + for (int k = 0; k < bool_settings.end_index(); ++k) + { + if (key != bool_settings[k].name) continue; + pack.set_bool(settings_pack::bool_type_base | k, val.int_value() != 0); + break; + } + } + break; + case bdecode_node::string_t: + for (int k = 0; k < str_settings.end_index(); ++k) + { + if (key != str_settings[k].name) continue; + pack.set_str(settings_pack::string_type_base + k, val.string_value().to_string()); + break; + } + break; + case bdecode_node::none_t: + break; + } + } + return pack; + } + + void save_settings_to_dict(aux::session_settings const& sett, entry::dictionary_type& out) + { + sett.bulk_get([&out](aux::session_settings_single_thread const& s) + { + // loop over all settings that differ from default + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (ensure_string(str_settings[i].default_value) == s.get_str(i | settings_pack::string_type_base)) continue; + out[str_settings[i].name] = s.get_str(i | settings_pack::string_type_base); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].default_value == s.get_int(i | settings_pack::int_type_base)) continue; + out[int_settings[i].name] = s.get_int(i | settings_pack::int_type_base); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].default_value == s.get_bool(i | settings_pack::bool_type_base)) continue; + out[bool_settings[i].name] = s.get_bool(i | settings_pack::bool_type_base); + } + }); + } + + void run_all_updates(aux::session_impl& ses) + { + using fun_t = void (aux::session_impl::*)(); + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + fun_t const& f = str_settings[i].fun; + if (f) (ses.*f)(); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + fun_t const& f = int_settings[i].fun; + if (f) (ses.*f)(); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + fun_t const& f = bool_settings[i].fun; + if (f) (ses.*f)(); + } + } + + void initialize_default_settings(aux::session_settings_single_thread& s) + { + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].default_value == nullptr) continue; + s.set_str(settings_pack::string_type_base | i, str_settings[i].default_value); + TORRENT_ASSERT(s.get_str(settings_pack::string_type_base + i) == str_settings[i].default_value); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + s.set_int(settings_pack::int_type_base | i, int_settings[i].default_value); + TORRENT_ASSERT(s.get_int(settings_pack::int_type_base + i) == int_settings[i].default_value); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + s.set_bool(settings_pack::bool_type_base | i, bool_settings[i].default_value); + TORRENT_ASSERT(s.get_bool(settings_pack::bool_type_base + i) == bool_settings[i].default_value); + } + } + + settings_pack default_settings() + { + settings_pack ret; + // TODO: it would be nice to reserve() these vectors up front + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].default_value == nullptr) continue; + ret.set_str(settings_pack::string_type_base + i, str_settings[i].default_value); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + ret.set_int(settings_pack::int_type_base + i, int_settings[i].default_value); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + ret.set_bool(settings_pack::bool_type_base + i, bool_settings[i].default_value); + } + return ret; + } + + int default_int_value(int const name) + { + TORRENT_ASSERT((name & settings_pack::type_mask) == settings_pack::int_type_base); + return int_settings[name - settings_pack::int_type_base].default_value; + } + + void apply_pack(settings_pack const* pack, aux::session_settings& sett + , aux::session_impl* ses) + { + using fun_t = void (aux::session_impl::*)(); + std::vector callbacks; + + sett.bulk_set([&](aux::session_settings_single_thread& s) + { + apply_pack_impl(pack, s, ses ? &callbacks : nullptr); + }); + + // call the callbacks once all the settings have been applied, and + // only once per callback + for (auto const& f : callbacks) + { + (ses->*f)(); + } + } + + void apply_pack_impl(settings_pack const* pack, aux::session_settings_single_thread& sett + , std::vector* callbacks) + { + for (auto const& p : pack->m_strings) + { + // disregard setting indices that are not string types + if ((p.first & settings_pack::type_mask) != settings_pack::string_type_base) + continue; + + // ignore settings that are out of bounds + int const index = p.first & settings_pack::index_mask; + TORRENT_ASSERT_PRECOND(index >= 0 && index < settings_pack::num_string_settings); + if (index < 0 || index >= settings_pack::num_string_settings) + continue; + + // if the value did not change, don't call the update callback + if (sett.get_str(p.first) == p.second) continue; + + sett.set_str(p.first, p.second); + str_setting_entry_t const& sa = str_settings[index]; + + if (sa.fun && callbacks + && std::find(callbacks->begin(), callbacks->end(), sa.fun) == callbacks->end()) + callbacks->push_back(sa.fun); + } + + for (auto const& p : pack->m_ints) + { + // disregard setting indices that are not int types + if ((p.first & settings_pack::type_mask) != settings_pack::int_type_base) + continue; + + // ignore settings that are out of bounds + int const index = p.first & settings_pack::index_mask; + TORRENT_ASSERT_PRECOND(index >= 0 && index < settings_pack::num_int_settings); + if (index < 0 || index >= settings_pack::num_int_settings) + continue; + + // if the value did not change, don't call the update callback + if (sett.get_int(p.first) == p.second) continue; + + sett.set_int(p.first, p.second); + int_setting_entry_t const& sa = int_settings[index]; + if (sa.fun && callbacks + && std::find(callbacks->begin(), callbacks->end(), sa.fun) == callbacks->end()) + callbacks->push_back(sa.fun); + } + + for (auto const& p : pack->m_bools) + { + // disregard setting indices that are not bool types + if ((p.first & settings_pack::type_mask) != settings_pack::bool_type_base) + continue; + + // ignore settings that are out of bounds + int const index = p.first & settings_pack::index_mask; + TORRENT_ASSERT_PRECOND(index >= 0 && index < settings_pack::num_bool_settings); + if (index < 0 || index >= settings_pack::num_bool_settings) + continue; + + // if the value did not change, don't call the update callback + if (sett.get_bool(p.first) == p.second) continue; + + sett.set_bool(p.first, p.second); + bool_setting_entry_t const& sa = bool_settings[index]; + if (sa.fun && callbacks + && std::find(callbacks->begin(), callbacks->end(), sa.fun) == callbacks->end()) + callbacks->push_back(sa.fun); + } + } + + void settings_pack::set_str(int const name, std::string val) + { + TORRENT_ASSERT((name & type_mask) == string_type_base); + if ((name & type_mask) != string_type_base) return; + std::pair v(aux::numeric_cast(name), std::move(val)); + insort_replace(m_strings, std::move(v)); + } + + void settings_pack::set_int(int const name, int const val) + { + TORRENT_ASSERT((name & type_mask) == int_type_base); + if ((name & type_mask) != int_type_base) return; + std::pair v(aux::numeric_cast(name), val); + insort_replace(m_ints, v); + } + + void settings_pack::set_bool(int const name, bool const val) + { + TORRENT_ASSERT((name & type_mask) == bool_type_base); + if ((name & type_mask) != bool_type_base) return; + std::pair v(aux::numeric_cast(name), val); + insort_replace(m_bools, v); + } + + bool settings_pack::has_val(int const name) const + { + switch (name & type_mask) + { + case string_type_base: + { + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_strings.size() == settings_pack::num_string_settings) + return true; + std::pair v(aux::numeric_cast(name), std::string()); + auto i = std::lower_bound(m_strings.begin(), m_strings.end(), v + , &compare_first); + return i != m_strings.end() && i->first == name; + } + case int_type_base: + { + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_ints.size() == settings_pack::num_int_settings) + return true; + std::pair v(aux::numeric_cast(name), 0); + auto i = std::lower_bound(m_ints.begin(), m_ints.end(), v + , &compare_first); + return i != m_ints.end() && i->first == name; + } + case bool_type_base: + { + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_bools.size() == settings_pack::num_bool_settings) + return true; + std::pair v(aux::numeric_cast(name), false); + auto i = std::lower_bound(m_bools.begin(), m_bools.end(), v + , &compare_first); + return i != m_bools.end() && i->first == name; + } + } + TORRENT_ASSERT_FAIL(); + return false; + } + + std::string const& settings_pack::get_str(int name) const + { + static std::string const empty; + TORRENT_ASSERT((name & type_mask) == string_type_base); + if ((name & type_mask) != string_type_base) return empty; + + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_strings.size() == settings_pack::num_string_settings) + { + TORRENT_ASSERT(m_strings[name & index_mask].first == name); + return m_strings[name & index_mask].second; + } + std::pair v(aux::numeric_cast(name), std::string()); + auto i = std::lower_bound(m_strings.begin(), m_strings.end(), v + , &compare_first); + if (i != m_strings.end() && i->first == name) return i->second; + return empty; + } + + int settings_pack::get_int(int name) const + { + TORRENT_ASSERT((name & type_mask) == int_type_base); + if ((name & type_mask) != int_type_base) return 0; + + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_ints.size() == settings_pack::num_int_settings) + { + TORRENT_ASSERT(m_ints[name & index_mask].first == name); + return m_ints[name & index_mask].second; + } + std::pair v(aux::numeric_cast(name), 0); + auto i = std::lower_bound(m_ints.begin(), m_ints.end(), v + , &compare_first); + if (i != m_ints.end() && i->first == name) return i->second; + return 0; + } + + bool settings_pack::get_bool(int name) const + { + TORRENT_ASSERT((name & type_mask) == bool_type_base); + if ((name & type_mask) != bool_type_base) return false; + + // this is an optimization. If the settings pack is complete, + // i.e. has every key, we don't need to search, it's just a lookup + if (m_bools.size() == settings_pack::num_bool_settings) + { + TORRENT_ASSERT(m_bools[name & index_mask].first == name); + return m_bools[name & index_mask].second; + } + std::pair v(aux::numeric_cast(name), false); + auto i = std::lower_bound(m_bools.begin(), m_bools.end(), v + , &compare_first); + if (i != m_bools.end() && i->first == name) return i->second; + return false; + } + + void settings_pack::clear() + { + m_strings.clear(); + m_ints.clear(); + m_bools.clear(); + } + + void settings_pack::clear(int const name) + { + switch (name & type_mask) + { + case string_type_base: + { + std::pair v(aux::numeric_cast(name), std::string()); + auto const i = std::lower_bound(m_strings.begin(), m_strings.end() + , v, &compare_first); + if (i != m_strings.end() && i->first == name) m_strings.erase(i); + break; + } + case int_type_base: + { + std::pair v(aux::numeric_cast(name), 0); + auto const i = std::lower_bound(m_ints.begin(), m_ints.end() + , v, &compare_first); + if (i != m_ints.end() && i->first == name) m_ints.erase(i); + break; + } + case bool_type_base: + { + std::pair v(aux::numeric_cast(name), false); + auto const i = std::lower_bound(m_bools.begin(), m_bools.end() + , v, &compare_first); + if (i != m_bools.end() && i->first == name) m_bools.erase(i); + break; + } + } + } +} diff --git a/src/sha1.cpp b/src/sha1.cpp new file mode 100644 index 0000000..6b2825a --- /dev/null +++ b/src/sha1.cpp @@ -0,0 +1,328 @@ +/* +SHA-1 C++ conversion + +original version: + +SHA-1 in C +By Steve Reid +100% Public Domain + +changelog at the end of the file. +*/ + +#include "libtorrent/sha1.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CRYPTOAPI \ + && !defined TORRENT_USE_LIBCRYPTO + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +namespace libtorrent { + +namespace { + +using u32 = std::uint32_t; +using u8 = std::uint8_t; + + union CHAR64LONG16 + { + u8 c[64]; + u32 l[16]; + }; + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-member-function" +#endif + +// blk0() and blk() perform the initial expand. +// I got the idea of expanding during the round function from SSLeay + struct little_endian_blk0 + { + static u32 apply(CHAR64LONG16* block, int i) + { + return block->l[i] = (rol(block->l[i],24)&0xFF00FF00) + | (rol(block->l[i],8)&0x00FF00FF); + } + }; + + struct big_endian_blk0 + { + static u32 apply(CHAR64LONG16* block, int i) + { + return block->l[i]; + } + }; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#define blk(i) (block->l[(i)&15] = rol(block->l[((i)+13)&15]^block->l[((i)+8)&15] \ + ^block->l[((i)+2)&15]^block->l[(i)&15],1)) + +// (R0+R1), R2, R3, R4 are the different operations used in SHA1 +#define R0(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+BlkFun::apply(block, i)+0x5A827999+rol(v,5);(w)=rol(w,30) +#define R1(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk(i)+0x5A827999+rol(v,5);(w)=rol(w,30) +#define R2(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0x6ED9EBA1+rol(v,5);(w)=rol(w,30) +#define R3(v,w,x,y,z,i) z+=((((w)|(x))&(y))|((w)&(x)))+blk(i)+0x8F1BBCDC+rol(v,5);(w)=rol(w,30) +#define R4(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0xCA62C1D6+rol(v,5);(w)=rol(w,30) + + // Hash a single 512-bit block. This is the core of the algorithm. + template + void SHA1transform(u32 state[5], u8 const buffer[64]) + { + using namespace std; + u32 a, b, c, d, e; + + CHAR64LONG16 workspace; + CHAR64LONG16* block = &workspace; + memcpy(block, buffer, 64); + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + } + +#ifdef VERBOSE + void SHAPrintContext(sha1_ctx *context, char *msg) + { + using namespace std; + std::printf("%s (%u,%u) %x %x %x %x %x\n" + , msg, (unsigned int)context->count[0] + , (unsigned int)context->count[1] + , (unsigned int)context->state[0] + , (unsigned int)context->state[1] + , (unsigned int)context->state[2] + , (unsigned int)context->state[3] + , (unsigned int)context->state[4]); + } +#endif + + template + void internal_update(sha1_ctx* context, u8 const* data, size_t len) + { + using namespace std; + size_t i, j; // JHB + +#ifdef VERBOSE + SHAPrintContext(context, "before"); +#endif + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + { + SHA1transform(context->state, &data[i]); + } + j = 0; + } + else + { + i = 0; + } + memcpy(&context->buffer[j], &data[i], len - i); +#ifdef VERBOSE + SHAPrintContext(context, "after "); +#endif + } + +#if !BOOST_ENDIAN_BIG_BYTE && !BOOST_ENDIAN_LITTLE_BYTE + bool is_big_endian() + { + u32 test = 1; + return *reinterpret_cast(&test) == 0; + } +#endif +} + +// SHA1Init - Initialize new context + +void SHA1_init(sha1_ctx* context) +{ + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +// Run your data through this. + +void SHA1_update(sha1_ctx* context, u8 const* data, size_t len) +{ + // GCC standard defines for endianness + // test with: cpp -dM /dev/null +#if BOOST_ENDIAN_BIG_BYTE + internal_update(context, data, len); +#elif BOOST_ENDIAN_LITTLE_BYTE + internal_update(context, data, len); +#else + // select different functions depending on endianess + // and figure out the endianess runtime + if (is_big_endian()) + internal_update(context, data, len); + else + internal_update(context, data, len); +#endif +} + + +// Add padding and return the message digest. + +void SHA1_final(u8* digest, sha1_ctx* context) +{ + u8 finalcount[8]; + + for (u32 i = 0; i < 8; ++i) + { + // Endian independent + finalcount[i] = static_cast( + (context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); + } + + SHA1_update(context, reinterpret_cast("\200"), 1); + while ((context->count[0] & 504) != 448) + SHA1_update(context, reinterpret_cast("\0"), 1); + SHA1_update(context, finalcount, 8); // Should cause a SHA1transform() + + for (u32 i = 0; i < 20; ++i) + { + digest[i] = static_cast( + (context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} + +} // libtorrent namespace + +#endif + +/************************************************************ + +----------------- +Modified 7/98 +By James H. Brown +Still 100% Public Domain + +Corrected a problem which generated improper hash values on 16 bit machines +Routine SHA1Update changed from + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int +len) +to + void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned +long len) + +The 'len' parameter was declared an int which works fine on 32 bit machines. +However, on 16 bit machines an int is too small for the shifts being done +against +it. This caused the hash function to generate incorrect values if len was +greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update(). + +Since the file IO in main() reads 16K at a time, any file 8K or larger would +be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million +"a"s). + +I also changed the declaration of variables i & j in SHA1Update to +unsigned long from unsigned int for the same reason. + +These changes should make no difference to any 32 bit implementations since +an +int and a long are the same size in those environments. + +-- +I also corrected a few compiler warnings generated by Borland C. +1. Added #include for exit() prototype +2. Removed unused variable 'j' in SHA1Final +3. Changed exit(0) to return(0) at end of main. + +ALL changes I made can be located by searching for comments containing 'JHB' +----------------- +Modified 8/98 +By Steve Reid +Still 100% public domain + +1- Removed #include and used return() instead of exit() +2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall) +3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net + +----------------- +Modified 4/01 +By Saul Kravitz +Still 100% PD +Modified to run on Compaq Alpha hardware. + +----------------- +Converted to C++ 6/04 +By Arvid Norberg +1- made the input buffer const, and made the + previous SHA1HANDSOFF implicit +2- uses C99 types with size guarantees + from boost +3- if none of BOOST_BIG_ENDIAN or BOOST_LITTLE_ENDIAN + are defined, endianess is determined + at runtime. templates are used to duplicate + the transform function for each endianess +4- using anonymous namespace to avoid external + linkage on internal functions +5- using standard C++ includes +6- made API compatible with openssl + +still 100% PD +*/ + +/* +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ diff --git a/src/sha1_hash.cpp b/src/sha1_hash.cpp new file mode 100644 index 0000000..47e029f --- /dev/null +++ b/src/sha1_hash.cpp @@ -0,0 +1,147 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/hex.hpp" // to_hex, from_hex + +#if TORRENT_USE_IOSTREAM +#include +#include +#endif // TORRENT_USE_IOSTREAM + +namespace libtorrent { + +#if TORRENT_USE_IOSTREAM + + // print a sha1_hash object to an ostream as 40 hexadecimal digits + std::ostream& operator<<(std::ostream& os, sha1_hash const& peer) + { + return os << aux::to_hex(peer); + } + + // read 40 hexadecimal digits from an istream into a sha1_hash + std::istream& operator>>(std::istream& is, sha1_hash& peer) + { + char hex[sha1_hash::size() * 2]; + is.read(hex, sha1_hash::size() * 2); + if (!aux::from_hex(hex, peer.data())) + is.setstate(std::ios_base::failbit); + return is; + } + +#endif // TORRENT_USE_IOSTREAM + +namespace aux { + + void bits_shift_left(span const number, int n) + { + TORRENT_ASSERT(n >= 0); + int const num_words = n / 32; + int const number_size = int(number.size()); + if (num_words >= number_size) + { + std::memset(number.data(), 0, std::size_t(number.size() * 4)); + return; + } + + if (num_words > 0) + { + std::memmove(number.data(), number.data() + num_words + , std::size_t(number_size - num_words) * sizeof(std::uint32_t)); + std::memset(number.data() + (number_size - num_words) + , 0, std::size_t(num_words) * sizeof(std::uint32_t)); + n -= num_words * 32; + } + if (n > 0) + { + // keep in mind that the uint32_t are stored in network + // byte order, so they have to be byteswapped before + // applying the shift operations, and then byteswapped + // back again. + number[0] = aux::network_to_host(number[0]); + for (int i = 0; i < number_size - 1; ++i) + { + number[i] <<= n; + number[i + 1] = aux::network_to_host(number[i + 1]); + number[i] |= number[i + 1] >> (32 - n); + number[i] = aux::host_to_network(number[i]); + } + number[number_size - 1] <<= n; + number[number_size - 1] = aux::host_to_network(number[number_size - 1]); + } + } + + void bits_shift_right(span const number, int n) + { + TORRENT_ASSERT(n >= 0); + int const num_words = n / 32; + int const number_size = int(number.size()); + if (num_words >= number_size) + { + std::memset(number.data(), 0, std::size_t(number.size() * 4)); + return; + } + if (num_words > 0) + { + std::memmove(number.data() + num_words + , number.data(), std::size_t(number_size - num_words) * sizeof(std::uint32_t)); + std::memset(number.data(), 0, std::size_t(num_words) * sizeof(std::uint32_t)); + n -= num_words * 32; + } + if (n > 0) + { + // keep in mind that the uint32_t are stored in network + // byte order, so they have to be byteswapped before + // applying the shift operations, and then byteswapped + // back again. + number[number_size - 1] = aux::network_to_host(number[number_size - 1]); + + for (int i = number_size - 1; i > 0; --i) + { + number[i] >>= n; + number[i - 1] = aux::network_to_host(number[i - 1]); + number[i] |= (number[i - 1] << (32 - n)) & 0xffffffff; + number[i] = aux::host_to_network(number[i]); + } + number[0] >>= n; + number[0] = aux::host_to_network(number[0]); + } + } +} + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + static_assert(std::is_nothrow_move_assignable::value + , "should be nothrow move assignable"); + static_assert(std::is_nothrow_default_constructible::value + , "should be nothrow default constructible"); +} diff --git a/src/sha512.cpp b/src/sha512.cpp new file mode 100644 index 0000000..63dadd9 --- /dev/null +++ b/src/sha512.cpp @@ -0,0 +1,289 @@ +#include +#include + +#include "libtorrent/sha512.hpp" + +#if !defined TORRENT_USE_LIBGCRYPT \ + && !TORRENT_USE_COMMONCRYPTO \ + && !TORRENT_USE_CRYPTOAPI_SHA_512 \ + && !defined TORRENT_USE_LIBCRYPTO + +// ignore warnings in this file +#include "libtorrent/aux_/disable_warnings_push.hpp" + +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + */ + +using u64 = std::uint64_t; +using i64 = std::int64_t; +using i32 = std::int32_t; + +#ifndef UINT64_C +#define UINT64_C(x) x ## LL +#endif + +namespace libtorrent { + +/* the K array */ +static const u64 K[80] = { + UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), + UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc), + UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), + UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118), + UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), + UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2), + UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), + UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694), + UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), + UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65), + UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), + UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5), + UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), + UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4), + UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), + UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70), + UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), + UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df), + UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), + UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b), + UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), + UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30), + UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), + UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8), + UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), + UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8), + UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), + UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3), + UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), + UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec), + UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), + UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b), + UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), + UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178), + UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), + UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b), + UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), + UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c), + UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), + UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817) +}; + +/* Various logical functions */ + +#define ROR64c(x, y) \ + ( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((u64)(y)&UINT64_C(63))) | \ + ((x)<<((u64)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF)) + +#define STORE64H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } + +#define LOAD64H(x, y) \ + { x = (((u64)((y)[0] & 255))<<56)|(((u64)((y)[1] & 255))<<48) | \ + (((u64)((y)[2] & 255))<<40)|(((u64)((y)[3] & 255))<<32) | \ + (((u64)((y)[4] & 255))<<24)|(((u64)((y)[5] & 255))<<16) | \ + (((u64)((y)[6] & 255))<<8)|(((u64)((y)[7] & 255))); } + + +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) ROR64c(x, n) +#define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((u64)n)) +#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) +#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) +#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) +#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6)) +#ifndef MIN + #define MIN(x, y) ( ((x)<(y))?(x):(y) ) +#endif + +/* compress 1024-bits */ +static int sha512_compress(sha512_ctx *md, unsigned char *buf) +{ + u64 S[8], W[80], t0, t1; + int i; + + /* copy state into S */ + for (i = 0; i < 8; i++) { + S[i] = md->state[i]; + } + + /* copy the state into 1024-bits into W[0..15] */ + for (i = 0; i < 16; i++) { + LOAD64H(W[i], buf + (8*i)); + } + + /* fill W[16..79] */ + for (i = 16; i < 80; i++) { + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } + +/* Compress */ + #define RND(a,b,c,d,e,f,g,h,i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c);\ + d += t0; \ + h = t0 + t1; + + for (i = 0; i < 80; i += 8) { + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); + } + + #undef RND + + + + /* feedback */ + for (i = 0; i < 8; i++) { + md->state[i] = md->state[i] + S[i]; + } + + return 0; +} + + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return 0 if successful +*/ +int SHA512_init(sha512_ctx* md) { + if (md == nullptr) return 1; + + md->curlen = 0; + md->length = 0; + md->state[0] = UINT64_C(0x6a09e667f3bcc908); + md->state[1] = UINT64_C(0xbb67ae8584caa73b); + md->state[2] = UINT64_C(0x3c6ef372fe94f82b); + md->state[3] = UINT64_C(0xa54ff53a5f1d36f1); + md->state[4] = UINT64_C(0x510e527fade682d1); + md->state[5] = UINT64_C(0x9b05688c2b3e6c1f); + md->state[6] = UINT64_C(0x1f83d9abfb41bd6b); + md->state[7] = UINT64_C(0x5be0cd19137e2179); + + return 0; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return 0 if successful +*/ +int SHA512_update(sha512_ctx* md, std::uint8_t const* in, std::size_t inlen) +{ + std::size_t n; + std::size_t i; + int err; + if (md == nullptr) return 1; + if (in == nullptr) return 1; + if (md->curlen > sizeof(md->buf)) { + return 1; + } + while (inlen > 0) { + if (md->curlen == 0 && inlen >= 128) { + if ((err = sha512_compress (md, (unsigned char *)in)) != 0) { + return err; + } + md->length += 128 * 8; + in += 128; + inlen -= 128; + } else { + n = MIN(inlen, (128 - md->curlen)); + + for (i = 0; i < n; i++) { + md->buf[i + md->curlen] = in[i]; + } + + + md->curlen += n; + in += n; + inlen -= n; + if (md->curlen == 128) { + if ((err = sha512_compress (md, md->buf)) != 0) { + return err; + } + md->length += 8*128; + md->curlen = 0; + } + } + } + return 0; +} + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (64 bytes) + @return 0 if successful +*/ +int SHA512_final(std::uint8_t* out, sha512_ctx* md) +{ + int i; + + if (md == nullptr) return 1; + if (out == nullptr) return 1; + + if (md->curlen >= sizeof(md->buf)) { + return 1; + } + + /* increase the length of the message */ + md->length += md->curlen * UINT64_C(8); + + /* append the '1' bit */ + md->buf[md->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 112 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if (md->curlen > 112) { + while (md->curlen < 128) { + md->buf[md->curlen++] = (unsigned char)0; + } + sha512_compress(md, md->buf); + md->curlen = 0; + } + + /* pad upto 120 bytes of zeroes + * note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash + * > 2^64 bits of data... :-) + */ + while (md->curlen < 120) { + md->buf[md->curlen++] = (unsigned char)0; + } + + /* store length */ + STORE64H(md->length, md->buf+120); + sha512_compress(md, md->buf); + + /* copy output */ + for (i = 0; i < 8; i++) { + STORE64H(md->state[i], out+(8*i)); + } + + return 0; +} + +} // libtorrent namespace + +#endif diff --git a/src/smart_ban.cpp b/src/smart_ban.cpp new file mode 100644 index 0000000..73e67a2 --- /dev/null +++ b/src/smart_ban.cpp @@ -0,0 +1,336 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/hasher.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/smart_ban.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/operations.hpp" // for operation_t enum + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" +#include "libtorrent/hex.hpp" // to_hex +#endif + +using namespace std::placeholders; + +namespace libtorrent { + +class torrent; + +namespace { + + + struct smart_ban_plugin final + : torrent_plugin + , std::enable_shared_from_this + { + explicit smart_ban_plugin(torrent& t) + : m_torrent(t) + , m_salt(random(0xffffffff)) + {} + + void on_piece_pass(piece_index_t const p) override + { + // has this piece failed earlier? If it has, go through the + // CRCs from the time it failed and ban the peers that + // sent bad blocks + auto i = m_block_hashes.lower_bound(piece_block(p, 0)); + if (i == m_block_hashes.end() || i->first.piece_index != p) return; + +#ifndef TORRENT_DISABLE_LOGGING + if (m_torrent.should_log()) + m_torrent.debug_log("PIECE PASS [ p: %d | block_hash_size: %d ]" + , static_cast(p), int(m_block_hashes.size())); +#endif + + int size = m_torrent.torrent_file().piece_size(p); + peer_request r = {p, 0, std::min(16 * 1024, size)}; + piece_block pb(p, 0); + while (size > 0) + { + if (i->first.block_index == pb.block_index) + { + m_torrent.session().disk_thread().async_read(m_torrent.storage() + , r, std::bind(&smart_ban_plugin::on_read_ok_block + , shared_from_this(), *i, i->second.peer->address(), _1, r.length, _2, _3)); + i = m_block_hashes.erase(i); + } + else + { + TORRENT_ASSERT(i->first.block_index > pb.block_index); + } + + if (i == m_block_hashes.end() || i->first.piece_index != p) + break; + + r.start += 16 * 1024; + size -= 16 * 1024; + r.length = std::min(16 * 1024, size); + ++pb.block_index; + } + +#ifndef NDEBUG + // make sure we actually removed all the entries for piece 'p' + i = m_block_hashes.lower_bound(piece_block(p, 0)); + TORRENT_ASSERT(i == m_block_hashes.end() || i->first.piece_index != p); +#endif + + if (m_torrent.is_seed()) + { + std::map().swap(m_block_hashes); + return; + } + } + + void on_piece_failed(piece_index_t const p) override + { + // The piece failed the hash check. Record + // the CRC and origin peer of every block + + // if the torrent is aborted, no point in starting + // a bunch of read operations on it + if (m_torrent.is_aborted()) return; + + std::vector downloaders; + m_torrent.picker().get_downloaders(downloaders, p); + + int size = m_torrent.torrent_file().piece_size(p); + peer_request r = {p, 0, std::min(16*1024, size)}; + piece_block pb(p, 0); + for (auto const& i : downloaders) + { + if (i != nullptr) + { + // for very sad and involved reasons, this read need to force a copy out of the cache + // since the piece has failed, this block is very likely to be replaced with a newly + // downloaded one very soon, and to get a block by reference would fail, since the + // block read will have been deleted by the time it gets back to the network thread + m_torrent.session().disk_thread().async_read(m_torrent.storage(), r + , std::bind(&smart_ban_plugin::on_read_failed_block + , shared_from_this(), pb, i->address(), _1, r.length, _2, _3) + , disk_interface::force_copy); + } + + r.start += 16*1024; + size -= 16*1024; + r.length = std::min(16*1024, size); + ++pb.block_index; + } + TORRENT_ASSERT(size <= 0); + } + + private: + + // this entry ties a specific block CRC to + // a peer. + struct block_entry + { + torrent_peer* peer; + sha1_hash digest; + }; + + void on_read_failed_block(piece_block const b, address const a + , disk_buffer_holder buffer, int const block_size, disk_job_flags_t + , storage_error const& error) + { + TORRENT_ASSERT(m_torrent.session().is_single_thread()); + + // ignore read errors + if (error) return; + + hasher h; + h.update({buffer.get(), block_size}); + h.update(reinterpret_cast(&m_salt), sizeof(m_salt)); + + auto const range = m_torrent.find_peers(a); + + // there is no peer with this address anymore + if (range.first == range.second) return; + + torrent_peer* p = (*range.first); + block_entry e = {p, h.final()}; + + auto i = m_block_hashes.lower_bound(b); + + if (i != m_block_hashes.end() && i->first == b && i->second.peer == p) + { + // this peer has sent us this block before + // if the peer is already banned, it doesn't matter if it sent + // good or bad data. Nothings going to change it + if (!p->banned && i->second.digest != e.digest) + { + // this time the digest of the block is different + // from the first time it sent it + // at least one of them must be bad +#ifndef TORRENT_DISABLE_LOGGING + if (m_torrent.should_log()) + { + char const* client = "-"; + peer_info info; + if (p->connection) + { + p->connection->get_peer_info(info); + client = info.client.c_str(); + } + m_torrent.debug_log("BANNING PEER [ p: %d | b: %d | c: %s" + " | hash1: %s | hash2: %s | ip: %s ]" + , static_cast(b.piece_index), b.block_index, client + , aux::to_hex(i->second.digest).c_str() + , aux::to_hex(e.digest).c_str() + , print_endpoint(p->ip()).c_str()); + } +#endif + m_torrent.ban_peer(p); + if (p->connection) p->connection->disconnect( + errors::peer_banned, operation_t::bittorrent); + } + // we already have this exact entry in the map + // we don't have to insert it + return; + } + + m_block_hashes.insert(i, std::pair(b, e)); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_torrent.should_log()) + { + char const* client = "-"; + peer_info info; + if (p->connection) + { + p->connection->get_peer_info(info); + client = info.client.c_str(); + } + m_torrent.debug_log("STORE BLOCK CRC [ p: %d | b: %d | c: %s" + " | digest: %s | ip: %s ]" + , static_cast(b.piece_index), b.block_index, client + , aux::to_hex(e.digest).c_str() + , print_address(p->ip().address()).c_str()); + } +#endif + } + + void on_read_ok_block(std::pair const b + , address const& a, disk_buffer_holder buffer, int const block_size + , disk_job_flags_t, storage_error const& error) + { + TORRENT_ASSERT(m_torrent.session().is_single_thread()); + + // ignore read errors + if (error) return; + + hasher h; + h.update({buffer.get(), block_size}); + h.update(reinterpret_cast(&m_salt), sizeof(m_salt)); + sha1_hash const ok_digest = h.final(); + + if (b.second.digest == ok_digest) return; + + // find the peer + auto range = m_torrent.find_peers(a); + if (range.first == range.second) return; + torrent_peer* p = nullptr; + for (; range.first != range.second; ++range.first) + { + if (b.second.peer != *range.first) continue; + p = *range.first; + } + if (p == nullptr) return; + +#ifndef TORRENT_DISABLE_LOGGING + if (m_torrent.should_log()) + { + char const* client = "-"; + peer_info info; + if (p->connection) + { + p->connection->get_peer_info(info); + client = info.client.c_str(); + } + m_torrent.debug_log("BANNING PEER [ p: %d | b: %d | c: %s" + " | ok_digest: %s | bad_digest: %s | ip: %s ]" + , static_cast(b.first.piece_index), b.first.block_index, client + , aux::to_hex(ok_digest).c_str() + , aux::to_hex(b.second.digest).c_str() + , print_address(p->ip().address()).c_str()); + } +#endif + m_torrent.ban_peer(p); + if (p->connection) p->connection->disconnect( + errors::peer_banned, operation_t::bittorrent); + } + + torrent& m_torrent; + + // This table maps a piece_block (piece and block index + // pair) to a peer and the block CRC. The CRC is calculated + // from the data in the block + the salt + std::map m_block_hashes; + + // This salt is a random value used to calculate the block CRCs + // Since the CRC function that is used is not a one way function + // the salt is required to avoid attacks where bad data is sent + // that is forged to match the CRC of the good data. + std::uint32_t const m_salt; + + // explicitly disallow assignment, to silence msvc warning + smart_ban_plugin& operator=(smart_ban_plugin const&) = delete; + }; + +} } + +namespace libtorrent { + + std::shared_ptr create_smart_ban_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + return std::make_shared(*t); + } +} + +#endif diff --git a/src/socket_io.cpp b/src/socket_io.cpp new file mode 100644 index 0000000..14715fb --- /dev/null +++ b/src/socket_io.cpp @@ -0,0 +1,168 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io.hpp" // for write_uint16 +#include "libtorrent/hasher.hpp" // for hasher +#include "libtorrent/aux_/escape_string.hpp" // for trim + +namespace libtorrent { + + std::string print_address(address const& addr) + { + error_code ec; + return addr.to_string(ec); + } + + std::string address_to_bytes(address const& a) + { + std::string ret; + std::back_insert_iterator out(ret); + detail::write_address(a, out); + return ret; + } + + std::string endpoint_to_bytes(udp::endpoint const& ep) + { + std::string ret; + std::back_insert_iterator out(ret); + detail::write_endpoint(ep, out); + return ret; + } + + std::string print_endpoint(address const& addr, int port) + { + error_code ec; + char buf[200]; + if (addr.is_v6()) + std::snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string(ec).c_str(), port); + else + std::snprintf(buf, sizeof(buf), "%s:%d", addr.to_string(ec).c_str(), port); + return buf; + } + + std::string print_endpoint(tcp::endpoint const& ep) + { + return print_endpoint(ep.address(), ep.port()); + } + + std::string print_endpoint(udp::endpoint const& ep) + { + return print_endpoint(ep.address(), ep.port()); + } + + tcp::endpoint parse_endpoint(string_view str, error_code& ec) + { + tcp::endpoint ret; + + str = trim(str); + + string_view addr; + string_view port; + + if (str.empty()) + { + ec = errors::invalid_port; + return ret; + } + + // this is for IPv6 addresses + if (str.front() == '[') + { + auto const close_bracket = str.find_first_of(']'); + if (close_bracket == string_view::npos) + { + ec = errors::expected_close_bracket_in_address; + return ret; + } + addr = str.substr(1, close_bracket - 1); + port = str.substr(close_bracket + 1); + if (port.empty() || port.front() != ':') + { + ec = errors::invalid_port; + return ret; + } + // shave off the ':' + port = port.substr(1); + ret.address(make_address_v6(addr.to_string(), ec)); + if (ec) return ret; + } + else + { + auto const port_pos = str.find_first_of(':'); + if (port_pos == string_view::npos) + { + ec = errors::invalid_port; + return ret; + } + addr = str.substr(0, port_pos); + port = str.substr(port_pos + 1); + ret.address(make_address_v4(addr.to_string(), ec)); + if (ec) return ret; + } + + if (port.empty()) + { + ec = errors::invalid_port; + return ret; + } + + int const port_num = std::atoi(port.to_string().c_str()); + if (port_num <= 0 || port_num > std::numeric_limits::max()) + { + ec = errors::invalid_port; + return ret; + } + ret.port(static_cast(port_num)); + return ret; + } + + sha1_hash hash_address(address const& ip) + { + if (ip.is_v6()) + { + address_v6::bytes_type b = ip.to_v6().to_bytes(); + return hasher(reinterpret_cast(b.data()), int(b.size())).final(); + } + else + { + address_v4::bytes_type b = ip.to_v4().to_bytes(); + return hasher(reinterpret_cast(b.data()), int(b.size())).final(); + } + } + +} diff --git a/src/socket_type.cpp b/src/socket_type.cpp new file mode 100644 index 0000000..bc8b047 --- /dev/null +++ b/src/socket_type.cpp @@ -0,0 +1,427 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/socket_type.hpp" +#include "libtorrent/aux_/openssl.hpp" +#include "libtorrent/deadline_timer.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include +#if BOOST_VERSION >= 107300 +#include +using boost::asio::ssl::host_name_verification; +#else +#include +using host_name_verification = boost::asio::ssl::rfc2818_verification; +#endif + +#endif + +#include "libtorrent/debug.hpp" + +namespace libtorrent { +namespace aux { + + bool is_ssl(socket_type const& s) + { +#ifdef TORRENT_USE_OPENSSL +#define CASE(t) case socket_type_int_impl>::value: + switch (s.type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + return true; + default: return false; + } +#undef CASE +#else + TORRENT_UNUSED(s); + return false; +#endif + } + + bool is_utp(socket_type const& s) + { + return s.get() != nullptr +#ifdef TORRENT_USE_OPENSSL + || s.get>() != nullptr +#endif + ; + } + +#if TORRENT_USE_I2P + bool is_i2p(socket_type const& s) + { + return s.get() != nullptr +#ifdef TORRENT_USE_OPENSSL + || s.get>() != nullptr +#endif + ; + } +#endif + + void setup_ssl_hostname(socket_type& s, std::string const& hostname, error_code& ec) + { +#if defined TORRENT_USE_OPENSSL +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + // for SSL connections, make sure to authenticate the hostname + // of the certificate +#define CASE(t) case socket_type_int_impl>::value: \ + s.get>()->set_verify_callback( \ + host_name_verification(hostname), ec); \ + ssl = s.get>()->native_handle(); \ + ctx = SSL_get_SSL_CTX(ssl); \ + break; + + SSL* ssl = nullptr; + SSL_CTX* ctx = nullptr; + + switch(s.type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + } +#undef CASE + +#if OPENSSL_VERSION_NUMBER >= 0x90812f + if (ctx) + { + aux::openssl_set_tlsext_servername_callback(ctx, nullptr); + aux::openssl_set_tlsext_servername_arg(ctx, nullptr); + } +#endif // OPENSSL_VERSION_NUMBER + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (ssl) + { + aux::openssl_set_tlsext_hostname(ssl, hostname.c_str()); + } +#endif + +#else + TORRENT_UNUSED(ec); + TORRENT_UNUSED(hostname); + TORRENT_UNUSED(s); +#endif +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif + } + +#ifdef TORRENT_USE_OPENSSL + + struct socket_closer + { + socket_closer(io_service& e + , std::shared_ptr holder + , socket_type* s) + : h(std::move(holder)) + , t(std::make_shared(e)) + , sock(s) + { + t->expires_from_now(seconds(3)); + t->async_wait(*this); + } + + void operator()(error_code const&) + { + COMPLETE_ASYNC("on_close_socket"); + error_code ec; + sock->close(ec); + t->cancel(); + } + + std::shared_ptr h; + std::shared_ptr t; + socket_type* sock; + }; +#endif + + // the second argument is a shared pointer to an object that + // will keep the socket (s) alive for the duration of the async operation + void async_shutdown(socket_type& s, std::shared_ptr holder) + { + error_code e; + +#ifdef TORRENT_USE_OPENSSL + // for SSL connections, first do an async_shutdown, before closing the socket +#if defined TORRENT_ASIO_DEBUGGING +#define MAYBE_ASIO_DEBUGGING add_outstanding_async("on_close_socket"); +#else +#define MAYBE_ASIO_DEBUGGING +#endif + +// we call ASIO_DEBUGGING twice, because the socket_closer callback will be +// called twice +#define CASE(t) case socket_type_int_impl>::value: \ + MAYBE_ASIO_DEBUGGING \ + MAYBE_ASIO_DEBUGGING \ + s.get>()->async_shutdown( \ + socket_closer(s.get_io_service(), std::move(holder), &s)); \ + break; + + switch (s.type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + default: s.close(e); break; + } +#undef CASE +#else + TORRENT_UNUSED(holder); + s.close(e); +#endif // TORRENT_USE_OPENSSL + } + + void socket_type::destruct() + { + using tcp_socket = tcp::socket; + switch (m_type) + { + case 0: break; + case socket_type_int_impl::value: + get()->~tcp_socket(); + break; + case socket_type_int_impl::value: + get()->~socks5_stream(); + break; + case socket_type_int_impl::value: + get()->~http_stream(); + break; + case socket_type_int_impl::value: + get()->~utp_stream(); + break; +#if TORRENT_USE_I2P + case socket_type_int_impl::value: + get()->~i2p_stream(); + break; +#endif +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl>::value: + get>()->~ssl_stream(); + break; + case socket_type_int_impl>::value: + get>()->~ssl_stream(); + break; + case socket_type_int_impl>::value: + get>()->~ssl_stream(); + break; + case socket_type_int_impl>::value: + get>()->~ssl_stream(); + break; +#endif + default: TORRENT_ASSERT_FAIL(); + } + m_type = 0; + } + + void socket_type::construct(int type, void* userdata) + { +#ifndef TORRENT_USE_OPENSSL + TORRENT_UNUSED(userdata); +#endif + + destruct(); + switch (type) + { + case 0: break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) tcp::socket(m_io_service); + break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) socks5_stream(m_io_service); + break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) http_stream(m_io_service); + break; + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) utp_stream(m_io_service); + break; +#if TORRENT_USE_I2P + case socket_type_int_impl::value: + new (reinterpret_cast(&m_data)) i2p_stream(m_io_service); + break; +#endif +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl>::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; + case socket_type_int_impl>::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; + case socket_type_int_impl>::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; + case socket_type_int_impl>::value: + TORRENT_ASSERT(userdata); + new (reinterpret_cast*>(&m_data)) ssl_stream(m_io_service + , *static_cast(userdata)); + break; +#endif + default: TORRENT_ASSERT_FAIL(); + } + + m_type = type; + } + + char const* socket_type::type_name() const + { + static char const* const names[] = + { + "uninitialized", + "TCP", + "Socks5", + "HTTP", + "uTP", +#if TORRENT_USE_I2P + "I2P", +#else + "", +#endif +#ifdef TORRENT_USE_OPENSSL + "SSL/TCP", + "SSL/Socks5", + "SSL/HTTP", + "SSL/uTP" +#else + "","","","" +#endif + }; + return names[m_type]; + } + + io_service& socket_type::get_io_service() const + { return m_io_service; } + + socket_type::~socket_type() + { destruct(); } + + bool socket_type::is_open() const + { + if (m_type == 0) return false; + TORRENT_SOCKTYPE_FORWARD_RET(is_open(), false) + } + + void socket_type::open(protocol_type const& p, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(open(p, ec)) } + + void socket_type::close(error_code& ec) + { + if (m_type == 0) return; + TORRENT_SOCKTYPE_FORWARD(close(ec)) + } + + void socket_type::set_close_reason(close_reason_t code) + { + switch (m_type) + { + case socket_type_int_impl::value: + get()->set_close_reason(code); + break; +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl>::value: + get>()->lowest_layer().set_close_reason(code); + break; +#endif + default: break; + } + } + + close_reason_t socket_type::get_close_reason() + { + switch (m_type) + { + case socket_type_int_impl::value: + return get()->get_close_reason(); +#ifdef TORRENT_USE_OPENSSL + case socket_type_int_impl>::value: + return get>()->lowest_layer().get_close_reason(); +#endif + default: return close_reason_t::none; + } + } + + socket_type::endpoint_type socket_type::local_endpoint(error_code& ec) const + { TORRENT_SOCKTYPE_FORWARD_RET(local_endpoint(ec), socket_type::endpoint_type()) } + + socket_type::endpoint_type socket_type::remote_endpoint(error_code& ec) const + { TORRENT_SOCKTYPE_FORWARD_RET(remote_endpoint(ec), socket_type::endpoint_type()) } + + void socket_type::bind(endpoint_type const& endpoint, error_code& ec) + { TORRENT_SOCKTYPE_FORWARD(bind(endpoint, ec)) } + + std::size_t socket_type::available(error_code& ec) const + { TORRENT_SOCKTYPE_FORWARD_RET(available(ec), 0) } + + int socket_type::type() const { return m_type; } + +#ifndef BOOST_NO_EXCEPTIONS + void socket_type::open(protocol_type const& p) + { TORRENT_SOCKTYPE_FORWARD(open(p)) } + + void socket_type::close() + { + if (m_type == 0) return; + TORRENT_SOCKTYPE_FORWARD(close()) + } + + socket_type::endpoint_type socket_type::local_endpoint() const + { TORRENT_SOCKTYPE_FORWARD_RET(local_endpoint(), socket_type::endpoint_type()) } + + socket_type::endpoint_type socket_type::remote_endpoint() const + { TORRENT_SOCKTYPE_FORWARD_RET(remote_endpoint(), socket_type::endpoint_type()) } + + void socket_type::bind(endpoint_type const& endpoint) + { TORRENT_SOCKTYPE_FORWARD(bind(endpoint)) } + + std::size_t socket_type::available() const + { TORRENT_SOCKTYPE_FORWARD_RET(available(), 0) } +#endif + +} +} diff --git a/src/socks5_stream.cpp b/src/socks5_stream.cpp new file mode 100644 index 0000000..fb850e4 --- /dev/null +++ b/src/socks5_stream.cpp @@ -0,0 +1,419 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/socks5_stream.hpp" +#include "libtorrent/socket_io.hpp" + +using namespace std::placeholders; + +namespace libtorrent { + + namespace socks_error + { + boost::system::error_code make_error_code(socks_error_code e) + { return {e, socks_category()}; } + } + + struct socks_error_category final : boost::system::error_category + { + const char* name() const BOOST_SYSTEM_NOEXCEPT override + { return "socks"; } + std::string message(int ev) const override + { + static char const* messages[] = + { + "SOCKS no error", + "SOCKS unsupported version", + "SOCKS unsupported authentication method", + "SOCKS unsupported authentication version", + "SOCKS authentication error", + "SOCKS username required", + "SOCKS general failure", + "SOCKS command not supported", + "SOCKS no identd running", + "SOCKS identd could not identify username" + }; + + if (ev < 0 || ev >= socks_error::num_errors) return "unknown error"; + return messages[ev]; + } + boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT override + { return {ev, *this}; } + }; + + boost::system::error_category& socks_category() + { + static socks_error_category cat; + return cat; + } + + void socks5_stream::name_lookup(error_code const& e, tcp::resolver::iterator i + , handler_type h) + { + COMPLETE_ASYNC("socks5_stream::name_lookup"); + if (handle_error(e, h)) return; + + error_code ec; + if (!m_sock.is_open()) + { + m_sock.open(i->endpoint().protocol(), ec); + if (handle_error(ec, h)) return; + } + + // TODO: we could bind the socket here, since we know what the + // target endpoint is of the proxy + ADD_OUTSTANDING_ASYNC("socks5_stream::connected"); + m_sock.async_connect(i->endpoint(), std::bind( + &socks5_stream::connected, this, _1, std::move(h))); + } + + void socks5_stream::connected(error_code const& e, handler_type h) + { + COMPLETE_ASYNC("socks5_stream::connected"); + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + if (m_version == 5) + { + // send SOCKS5 authentication methods + m_buffer.resize(m_user.empty()?3:4); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + if (m_user.empty()) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake1"); + async_write(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&socks5_stream::handshake1, this, _1, std::move(h))); + } + else if (m_version == 4) + { + socks_connect(std::move(h)); + } + else + { + h(socks_error::unsupported_version); + } + } + + void socks5_stream::handshake1(error_code const& e, handler_type h) + { + COMPLETE_ASYNC("socks5_stream::handshake1"); + if (handle_error(e, h)) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake2"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&socks5_stream::handshake2, this, _1, std::move(h))); + } + + void socks5_stream::handshake2(error_code const& e, handler_type h) + { + COMPLETE_ASYNC("socks5_stream::handshake2"); + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int method = read_uint8(p); + + if (version < m_version) + { + h(socks_error::unsupported_version); + return; + } + + if (method == 0) + { + socks_connect(std::move(h)); + } + else if (method == 2) + { + if (m_user.empty()) + { + h(socks_error::username_required); + return; + } + + // start sub-negotiation + m_buffer.resize(m_user.size() + m_password.size() + 3); + p = &m_buffer[0]; + write_uint8(1, p); + TORRENT_ASSERT(m_user.size() < 0x100); + write_uint8(uint8_t(m_user.size()), p); + write_string(m_user, p); + TORRENT_ASSERT(m_password.size() < 0x100); + write_uint8(uint8_t(m_password.size()), p); + write_string(m_password, p); + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake3"); + async_write(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&socks5_stream::handshake3, this, _1, std::move(h))); + } + else + { + h(socks_error::unsupported_authentication_method); + return; + } + } + + void socks5_stream::handshake3(error_code const& e + , handler_type h) + { + COMPLETE_ASYNC("socks5_stream::handshake3"); + if (handle_error(e, h)) return; + + ADD_OUTSTANDING_ASYNC("socks5_stream::handshake4"); + m_buffer.resize(2); + async_read(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&socks5_stream::handshake4, this, _1, std::move(h))); + } + + void socks5_stream::handshake4(error_code const& e + , handler_type h) + { + COMPLETE_ASYNC("socks5_stream::handshake4"); + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + char* p = &m_buffer[0]; + int version = read_uint8(p); + int status = read_uint8(p); + + if (version != 1) + { + h(socks_error::unsupported_authentication_version); + return; + } + + if (status != 0) + { + h(socks_error::authentication_error); + return; + } + + std::vector().swap(m_buffer); + socks_connect(std::move(h)); + } + + void socks5_stream::socks_connect(handler_type h) + { + using namespace libtorrent::detail; + + if (m_version == 5) + { + // send SOCKS5 connect command + m_buffer.resize(6 + (!m_dst_name.empty() + ? m_dst_name.size() + 1 + :(is_v4(m_remote_endpoint) ? 4 : 16))); + char* p = &m_buffer[0]; + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(std::uint8_t(m_command), p); // CONNECT command + write_uint8(0, p); // reserved + if (!m_dst_name.empty()) + { + write_uint8(3, p); // address type + TORRENT_ASSERT(m_dst_name.size() < 0x100); + write_uint8(uint8_t(m_dst_name.size()), p); + std::copy(m_dst_name.begin(), m_dst_name.end(), p); + p += m_dst_name.size(); + } + else + { + // we either need a hostname or a valid endpoint + TORRENT_ASSERT(m_remote_endpoint.address() != address()); + + write_uint8(is_v4(m_remote_endpoint) ? 1 : 4, p); // address type + write_address(m_remote_endpoint.address(), p); + } + write_uint16(m_remote_endpoint.port(), p); + } + else if (m_version == 4) + { + // SOCKS4 only supports IPv4 + if (!is_v4(m_remote_endpoint)) + { + h(boost::asio::error::address_family_not_supported); + return; + } + m_buffer.resize(m_user.size() + 9); + char* p = &m_buffer[0]; + write_uint8(4, p); // SOCKS VERSION 4 + write_uint8(std::uint8_t(m_command), p); // CONNECT command + write_uint16(m_remote_endpoint.port(), p); + write_uint32(m_remote_endpoint.address().to_v4().to_ulong(), p); + std::copy(m_user.begin(), m_user.end(), p); + p += m_user.size(); + write_uint8(0, p); // 0-terminator + } + else + { + h(socks_error::unsupported_version); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + async_write(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&socks5_stream::connect1, this, _1, std::move(h))); + } + + void socks5_stream::connect1(error_code const& e, handler_type h) + { + COMPLETE_ASYNC("socks5_stream::connect1"); + if (handle_error(e, h)) return; + + if (m_version == 5) + m_buffer.resize(6 + 4); // assume an IPv4 address + else if (m_version == 4) + m_buffer.resize(8); + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect2"); + async_read(m_sock, boost::asio::buffer(m_buffer) + , std::bind(&socks5_stream::connect2, this, _1, std::move(h))); + } + + void socks5_stream::connect2(error_code const& e, handler_type h) + { + COMPLETE_ASYNC("socks5_stream::connect2"); + if (handle_error(e, h)) return; + + using namespace libtorrent::detail; + + char const* p = &m_buffer[0]; + int const version = read_uint8(p); + int const response = read_uint8(p); + + if (m_version == 5) + { + if (version < m_version) + { + h(socks_error::unsupported_version); + return; + } + if (response != 0) + { + error_code ec(socks_error::general_failure); + switch (response) + { + case 2: ec = boost::asio::error::no_permission; break; + case 3: ec = boost::asio::error::network_unreachable; break; + case 4: ec = boost::asio::error::host_unreachable; break; + case 5: ec = boost::asio::error::connection_refused; break; + case 6: ec = boost::asio::error::timed_out; break; + case 7: ec = socks_error::command_not_supported; break; + case 8: ec = boost::asio::error::address_family_not_supported; break; + } + h(ec); + return; + } + p += 1; // reserved + int const atyp = read_uint8(p); + // read the proxy IP it was bound to (this is variable length depending + // on address type) + if (atyp == 1) + { + std::vector().swap(m_buffer); + h(e); + return; + } + std::size_t extra_bytes = 0; + if (atyp == 4) + { + // IPv6 + extra_bytes = 12; + } + else if (atyp == 3) + { + // hostname with length prefix + extra_bytes = read_uint8(p) - 3; + } + else + { + h(boost::asio::error::address_family_not_supported); + return; + } + m_buffer.resize(m_buffer.size() + extra_bytes); + + ADD_OUTSTANDING_ASYNC("socks5_stream::connect3"); + TORRENT_ASSERT(extra_bytes > 0); + async_read(m_sock, boost::asio::buffer(&m_buffer[m_buffer.size() - extra_bytes], extra_bytes) + , std::bind(&socks5_stream::connect3, this, _1, std::move(h))); + } + else if (m_version == 4) + { + if (version != 0) + { + h(socks_error::general_failure); + return; + } + + // access granted + if (response == 90) + { + std::vector().swap(m_buffer); + h(e); + return; + } + + error_code ec(socks_error::general_failure); + switch (response) + { + case 91: ec = boost::asio::error::connection_refused; break; + case 92: ec = socks_error::no_identd; break; + case 93: ec = socks_error::identd_error; break; + } + h(ec); + } + } + + void socks5_stream::connect3(error_code const& e, handler_type h) + { + COMPLETE_ASYNC("socks5_stream::connect3"); + using namespace libtorrent::detail; + + if (handle_error(e, h)) return; + + std::vector().swap(m_buffer); + h(e); + } +} diff --git a/src/stack_allocator.cpp b/src/stack_allocator.cpp new file mode 100644 index 0000000..51ed07d --- /dev/null +++ b/src/stack_allocator.cpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2015-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/stack_allocator.hpp" +#include // for va_list, va_copy, va_end + +namespace libtorrent { +namespace aux { + + allocation_slot stack_allocator::copy_string(string_view str) + { + int const ret = int(m_storage.size()); + m_storage.resize(ret + numeric_cast(str.size()) + 1); + std::memcpy(&m_storage[ret], str.data(), str.size()); + m_storage[ret + int(str.length())] = '\0'; + return allocation_slot(ret); + } + + allocation_slot stack_allocator::copy_string(char const* str) + { + int const ret = int(m_storage.size()); + int const len = int(std::strlen(str)); + m_storage.resize(ret + len + 1); + std::memcpy(&m_storage[ret], str, numeric_cast(len)); + m_storage[ret + len] = '\0'; + return allocation_slot(ret); + } + + allocation_slot stack_allocator::format_string(char const* fmt, va_list v) + { + int const pos = int(m_storage.size()); + int len = 512; + + for(;;) + { + m_storage.resize(pos + len + 1); + + va_list args; + va_copy(args, v); + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + int const ret = std::vsnprintf(m_storage.data() + pos, static_cast(len) + 1, fmt, args); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + va_end(args); + + if (ret < 0) + { + m_storage.resize(pos); + return copy_string("(format error)"); + } + if (ret > len) + { + // try again + len = ret; + continue; + } + break; + } + + // +1 is to include the 0-terminator + m_storage.resize(pos + len + 1); + return allocation_slot(pos); + } + + allocation_slot stack_allocator::copy_buffer(span buf) + { + int const ret = int(m_storage.size()); + int const size = int(buf.size()); + if (size < 1) return {}; + m_storage.resize(ret + size); + std::memcpy(&m_storage[ret], buf.data(), numeric_cast(size)); + return allocation_slot(ret); + } + + allocation_slot stack_allocator::allocate(int const bytes) + { + if (bytes < 1) return {}; + int const ret = m_storage.end_index(); + m_storage.resize(ret + bytes); + return allocation_slot(ret); + } + + char* stack_allocator::ptr(allocation_slot const idx) + { + if(idx.val() < 0) return nullptr; + TORRENT_ASSERT(idx.val() < int(m_storage.size())); + return &m_storage[idx.val()]; + } + + char const* stack_allocator::ptr(allocation_slot const idx) const + { + if(idx.val() < 0) return nullptr; + TORRENT_ASSERT(idx.val() < int(m_storage.size())); + return &m_storage[idx.val()]; + } + + void stack_allocator::swap(stack_allocator& rhs) + { + m_storage.swap(rhs.m_storage); + } + + void stack_allocator::reset() + { + m_storage.clear(); + } +} +} + diff --git a/src/stat.cpp b/src/stat.cpp new file mode 100644 index 0000000..9d49218 --- /dev/null +++ b/src/stat.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/stat.hpp" + +namespace libtorrent { + +void stat_channel::second_tick(int tick_interval_ms) +{ + std::int64_t sample = std::int64_t(m_counter) * 1000 / tick_interval_ms; + TORRENT_ASSERT(sample >= 0); + m_5_sec_average = std::int32_t(std::int64_t(m_5_sec_average) * 4 / 5 + sample / 5); + m_counter = 0; +} + +} diff --git a/src/stat_cache.cpp b/src/stat_cache.cpp new file mode 100644 index 0000000..cbdccb1 --- /dev/null +++ b/src/stat_cache.cpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/path.hpp" + +namespace libtorrent { + + stat_cache::stat_cache() = default; + stat_cache::~stat_cache() = default; + + void stat_cache::set_cache(file_index_t const i, std::int64_t const size) + { + std::lock_guard l(m_mutex); + set_cache_impl(i, size); + } + + void stat_cache::set_cache_impl(file_index_t const i, std::int64_t const size) + { + if (i >= m_stat_cache.end_index()) + m_stat_cache.resize(static_cast(i) + 1, stat_cache_t{not_in_cache}); + m_stat_cache[i].file_size = size; + } + + void stat_cache::set_error(file_index_t const i, error_code const& ec) + { + std::lock_guard l(m_mutex); + set_error_impl(i, ec); + } + + void stat_cache::set_error_impl(file_index_t const i, error_code const& ec) + { + if (i >= m_stat_cache.end_index()) + m_stat_cache.resize(static_cast(i) + 1, stat_cache_t{not_in_cache}); + + int const error_index = add_error(ec); + m_stat_cache[i].file_size = file_error - error_index; + } + + void stat_cache::set_dirty(file_index_t const i) + { + std::lock_guard l(m_mutex); + if (i >= m_stat_cache.end_index()) return; + m_stat_cache[i].file_size = not_in_cache; + } + + std::int64_t stat_cache::get_filesize(file_index_t const i, file_storage const& fs + , std::string const& save_path, error_code& ec) + { + // always pretend symlinks don't exist, to trigger special logic for + // creating and possibly validating them. There's a risk we'll and up in a + // cycle of references here otherwise. + // Should stat_file() be changed to use lstat()? + if (fs.file_flags(i) & file_storage::flag_symlink) + { + ec.assign(boost::system::errc::no_such_file_or_directory, boost::system::system_category()); + return 0; + } + + std::lock_guard l(m_mutex); + TORRENT_ASSERT(i < fs.end_file()); + if (i >= m_stat_cache.end_index()) m_stat_cache.resize(static_cast(i) + 1 + , stat_cache_t{not_in_cache}); + std::int64_t sz = m_stat_cache[i].file_size; + if (sz < not_in_cache) + { + ec = m_errors[std::size_t(-sz + file_error)]; + return file_error; + } + else if (sz == not_in_cache) + { + // query the filesystem + file_status s; + std::string const file_path = fs.file_path(i, save_path); + stat_file(file_path, &s, ec); + if (ec) + { + set_error_impl(i, ec); + sz = file_error; + } + else + { + set_cache_impl(i, s.file_size); + sz = s.file_size; + } + } + return sz; + } + + void stat_cache::reserve(int num_files) + { + std::lock_guard l(m_mutex); + m_stat_cache.resize(num_files, stat_cache_t{not_in_cache}); + } + + void stat_cache::clear() + { + std::lock_guard l(m_mutex); + m_stat_cache.clear(); + m_stat_cache.shrink_to_fit(); + m_errors.clear(); + m_errors.shrink_to_fit(); + } + + int stat_cache::add_error(error_code const& ec) + { + auto const i = std::find(m_errors.begin(), m_errors.end(), ec); + if (i != m_errors.end()) return int(i - m_errors.begin()); + m_errors.push_back(ec); + return int(m_errors.size()) - 1; + } +} diff --git a/src/storage.cpp b/src/storage.cpp new file mode 100644 index 0000000..c733993 --- /dev/null +++ b/src/storage.cpp @@ -0,0 +1,879 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/storage_utils.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#if defined(__APPLE__) +// for getattrlist() +#include +#include +// for statfs() +#include +#include +#endif + +#if defined(__linux__) +#include +#endif + +#if defined(__FreeBSD__) +// for statfs() +#include +#include +#endif + +#if TORRENT_HAS_SYMLINK +#include // for symlink() +#endif + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "libtorrent/storage.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/disk_buffer_holder.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/hex.hpp" // to_hex +//#include "libtorrent/aux_/escape_string.hpp" + +namespace libtorrent { + + default_storage::default_storage(storage_params const& params + , file_pool& pool) + : storage_interface(params.files) + , m_file_priority(params.priorities) + , m_pool(pool) + , m_allocate_files(params.mode == storage_mode_allocate) + { + if (params.mapped_files) m_mapped_files.reset(new file_storage(*params.mapped_files)); + + TORRENT_ASSERT(files().num_files() > 0); + m_save_path = complete(params.path); + m_part_file_name = "." + aux::to_hex(params.info_hash) + ".parts"; + } + + default_storage::~default_storage() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + // this may be called from a different + // thread than the disk thread + m_pool.release(storage_index()); + } + + void default_storage::need_partfile() + { + if (m_part_file) return; + + m_part_file.reset(new part_file( + m_save_path, m_part_file_name + , files().num_pieces(), files().piece_length())); + } + + void default_storage::set_file_priority( + aux::vector& prio + , storage_error& ec) + { + // extend our file priorities in case it's truncated + // the default assumed priority is 4 (the default) + if (prio.size() > m_file_priority.size()) + m_file_priority.resize(prio.size(), default_priority); + + file_storage const& fs = files(); + for (file_index_t i(0); i < prio.end_index(); ++i) + { + // pad files always have priority 0. + if (fs.pad_file_at(i)) continue; + + download_priority_t const old_prio = m_file_priority[i]; + download_priority_t new_prio = prio[i]; + if (old_prio == dont_download && new_prio != dont_download) + { + // move stuff out of the part file + file_handle f = open_file(i, open_mode::read_write, ec); + if (ec) + { + prio = m_file_priority; + return; + } + + if (m_part_file && use_partfile(i)) + { + m_part_file->export_file([&f, &ec](std::int64_t file_offset, span buf) + { + iovec_t const v = {buf.data(), buf.size()}; + std::int64_t const ret = f->writev(file_offset, v, ec.ec); + TORRENT_UNUSED(ret); + TORRENT_ASSERT(ec || ret == std::int64_t(v.size())); + }, fs.file_offset(i), fs.file_size(i), ec.ec); + + if (ec) + { + ec.file(i); + ec.operation = operation_t::partfile_write; + prio = m_file_priority; + return; + } + } + } + else if (old_prio != dont_download && new_prio == dont_download) + { + // move stuff into the part file + // this is not implemented yet. + // so we just don't use a partfile for this file + + std::string const fp = fs.file_path(i, m_save_path); + if (exists(fp)) use_partfile(i, false); +/* + file_handle f = open_file(i, open_mode::read_only, ec); + if (ec.ec != boost::system::errc::no_such_file_or_directory) + { + if (ec) + { + prio = m_file_priority; + return; + } + + need_partfile(); + + m_part_file->import_file(*f, fs.file_offset(i), fs.file_size(i), ec.ec); + if (ec) + { + ec.file(i); + ec.operation = operation_t::partfile_read; + prio = m_file_priority; + return; + } + // remove the file + std::string p = fs.file_path(i, m_save_path); + delete_one_file(p, ec.ec); + if (ec) + { + ec.file(i); + ec.operation = operation_t::file_remove; + prio = m_file_priority; + return; + } + } +*/ + } + ec.ec.clear(); + m_file_priority[i] = new_prio; + + if (m_file_priority[i] == dont_download && use_partfile(i)) + { + need_partfile(); + } + } + if (m_part_file) m_part_file->flush_metadata(ec.ec); + if (ec) + { + ec.file(torrent_status::error_file_partfile); + ec.operation = operation_t::partfile_write; + } + } + + bool default_storage::use_partfile(file_index_t const index) const + { + TORRENT_ASSERT_VAL(index >= file_index_t{}, index); + if (index >= m_use_partfile.end_index()) return true; + return m_use_partfile[index]; + } + + void default_storage::use_partfile(file_index_t const index, bool const b) + { + if (index >= m_use_partfile.end_index()) m_use_partfile.resize(static_cast(index) + 1, true); + m_use_partfile[index] = b; + } + + void default_storage::initialize(storage_error& ec) + { + m_stat_cache.reserve(files().num_files()); + +#ifdef TORRENT_WINDOWS + // don't do full file allocations on network drives + auto const file_name = convert_to_native_path_string(m_save_path); + int const drive_type = GetDriveTypeW(file_name.c_str()); + + if (drive_type == DRIVE_REMOTE) + m_allocate_files = false; +#endif + + { + std::unique_lock l(m_file_created_mutex); + m_file_created.resize(files().num_files(), false); + } + + file_storage const& fs = files(); + // if some files have priority 0, we need to check if they exist on the + // filesystem, in which case we won't use a partfile for them. + // this is to be backwards compatible with previous versions of + // libtorrent, when part files were not supported. + for (file_index_t i(0); i < m_file_priority.end_index(); ++i) + { + if (m_file_priority[i] != dont_download || fs.pad_file_at(i)) + continue; + + file_status s; + std::string const file_path = fs.file_path(i, m_save_path); + error_code err; + stat_file(file_path, &s, err); + if (!err) + { + use_partfile(i, false); + } + else + { + need_partfile(); + } + } + + // first, create all missing directories + std::string last_path; + for (auto const file_index : fs.file_range()) + { + // ignore files that have priority 0 + if (m_file_priority.end_index() > file_index + && m_file_priority[file_index] == dont_download) + { + continue; + } + + // ignore pad files + if (fs.pad_file_at(file_index)) continue; + + // this is just to see if the file exists + error_code err; + m_stat_cache.get_filesize(file_index, fs, m_save_path, err); + + if (err && err != boost::system::errc::no_such_file_or_directory) + { + ec.file(file_index); + ec.operation = operation_t::file_stat; + ec.ec = err; + break; + } + + // if the file is empty and doesn't already exist, create it + // deliberately don't truncate files that already exist + // if a file is supposed to have size 0, but already exists, we will + // never truncate it to 0. + if (fs.file_size(file_index) == 0 + && err == boost::system::errc::no_such_file_or_directory) + { + std::string dir = parent_path(fs.file_path(file_index, m_save_path)); + + if (dir != last_path) + { + last_path = dir; + + create_directories(last_path, ec.ec); + if (ec.ec) + { + ec.file(file_index); + ec.operation = operation_t::mkdir; + break; + } + } + ec.ec.clear(); + +#if TORRENT_HAS_SYMLINK + // create symlinks + if (fs.file_flags(file_index) & file_storage::flag_symlink) + { + // we make the symlink target relative to the link itself + std::string const target = lexically_relative( + parent_path(fs.file_path(file_index)), fs.symlink(file_index)); + std::string const link = fs.file_path(file_index, m_save_path); + if (::symlink(target.c_str(), link.c_str()) != 0) + { + int const error = errno; + if (error == EEXIST) + { + // if the file exist, it may be a symlink already. if so, + // just verify the link target is what it's supposed to be + // note that readlink() does not null terminate the buffer + char buffer[512]; + auto const ret = ::readlink(link.c_str(), buffer, sizeof(buffer)); + if (ret <= 0 || target != string_view(buffer, std::size_t(ret))) + { + ec.ec = error_code(error, generic_category()); + ec.file(file_index); + ec.operation = operation_t::symlink; + return; + } + } + else + { + ec.ec = error_code(error, generic_category()); + ec.file(file_index); + ec.operation = operation_t::symlink; + return; + } + } + } + else +#endif + { + // just creating the file is enough to make it zero-sized. If + // there's a race here and some other process truncates the file, + // it's not a problem, we won't access empty files ever again + file_handle f = open_file(file_index, open_mode::read_write + | open_mode::random_access, ec); + if (ec) return; + } + } + ec.ec.clear(); + } + + // close files that were opened in write mode + m_pool.release(storage_index()); + } + + bool default_storage::has_any_file(storage_error& ec) + { + m_stat_cache.reserve(files().num_files()); + + if (aux::has_any_file(files(), m_save_path, m_stat_cache, ec)) + return true; + + if (ec) return false; + + file_status s; + stat_file(combine_path(m_save_path, m_part_file_name), &s, ec.ec); + if (!ec) return true; + + // the part file not existing is expected + if (ec && ec.ec == boost::system::errc::no_such_file_or_directory) + ec.ec.clear(); + + if (ec) + { + ec.file(torrent_status::error_file_partfile); + ec.operation = operation_t::file_stat; + return false; + } + return false; + } + + void default_storage::rename_file(file_index_t const index, std::string const& new_filename + , storage_error& ec) + { + if (index < file_index_t(0) || index >= files().end_file()) return; + std::string old_name = files().file_path(index, m_save_path); + m_pool.release(storage_index(), index); + + // if the old file doesn't exist, just succeed and change the filename + // that will be created. This shortcut is important because the + // destination directory may not exist yet, which would cause a failure + // even though we're not moving a file (yet). It's better for it to + // fail later when we try to write to the file the first time, because + // the user then will have had a chance to make the destination directory + // valid. + if (exists(old_name, ec.ec)) + { + std::string new_path; + if (is_complete(new_filename)) new_path = new_filename; + else new_path = combine_path(m_save_path, new_filename); + std::string new_dir = parent_path(new_path); + + // create any missing directories that the new filename + // lands in + create_directories(new_dir, ec.ec); + if (ec.ec) + { + ec.file(index); + ec.operation = operation_t::file_rename; + return; + } + + rename(old_name, new_path, ec.ec); + + // if old_name doesn't exist, that's not an error + // here. Once we start writing to the file, it will + // be written to the new filename + if (ec.ec == boost::system::errc::no_such_file_or_directory) + ec.ec.clear(); + + if (ec) + { + ec.ec.clear(); + copy_file(old_name, new_path, ec.ec); + + if (ec) + { + ec.file(index); + ec.operation = operation_t::file_rename; + return; + } + + error_code ignore; + remove(old_name, ignore); + } + } + else if (ec.ec) + { + // if exists fails, report that error + ec.file(index); + ec.operation = operation_t::file_rename; + return; + } + + // if old path doesn't exist, just rename the file + // in our file_storage, so that when it is created + // it will get the new name + if (!m_mapped_files) + { m_mapped_files.reset(new file_storage(files())); } + m_mapped_files->rename_file(index, new_filename); + } + + void default_storage::release_files(storage_error&) + { + if (m_part_file) + { + error_code ignore; + m_part_file->flush_metadata(ignore); + } + + // make sure we don't have the files open + m_pool.release(storage_index()); + + // make sure we can pick up new files added to the download directory when + // we start the torrent again + m_stat_cache.clear(); + } + + void default_storage::delete_files(remove_flags_t const options, storage_error& ec) + { + // make sure we don't have the files open + m_pool.release(storage_index()); + + // if there's a part file open, make sure to destruct it to have it + // release the underlying part file. Otherwise we may not be able to + // delete it + if (m_part_file) m_part_file.reset(); + + aux::delete_files(files(), m_save_path, m_part_file_name, options, ec); + } + + bool default_storage::verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , storage_error& ec) + { + return aux::verify_resume_data(rd, links, files() + , m_file_priority, m_stat_cache, m_save_path, ec); + } + + status_t default_storage::move_storage(std::string const& sp + , move_flags_t const flags, storage_error& ec) + { + m_pool.release(storage_index()); + + status_t ret; + std::tie(ret, m_save_path) = aux::move_storage(files(), m_save_path, sp + , m_part_file.get(), flags, ec); + + // clear the stat cache in case the new location has new files + m_stat_cache.clear(); + + return ret; + } + + int default_storage::readv(span bufs + , piece_index_t const piece, int const offset + , open_mode_t const flags, storage_error& error) + { +#ifdef TORRENT_SIMULATE_SLOW_READ + std::this_thread::sleep_for(seconds(1)); +#endif + return readwritev(files(), bufs, piece, offset, error + , [this, flags](file_index_t const file_index + , std::int64_t const file_offset + , span vec, storage_error& ec) + { + if (files().pad_file_at(file_index)) + { + // reading from a pad file yields zeroes + aux::clear_bufs(vec); + return bufs_size(vec); + } + + if (file_index < m_file_priority.end_index() + && m_file_priority[file_index] == dont_download + && use_partfile(file_index)) + { + TORRENT_ASSERT(m_part_file); + + error_code e; + peer_request map = files().map_file(file_index + , file_offset, 0); + int const ret = m_part_file->readv(vec + , map.piece, map.start, e); + + if (e) + { + ec.ec = e; + ec.file(file_index); + ec.operation = operation_t::partfile_read; + return -1; + } + return ret; + } + + file_handle handle = open_file(file_index + , open_mode::read_only | flags, ec); + if (ec) return -1; + + error_code e; + int const ret = int(handle->readv(file_offset + , vec, e, flags)); + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = operation_t::file_read; + + // we either get an error or 0 or more bytes read + TORRENT_ASSERT(e || ret >= 0); + TORRENT_ASSERT(ret <= bufs_size(vec)); + + if (e) + { + ec.ec = e; + ec.file(file_index); + return -1; + } + + return ret; + }); + } + + int default_storage::writev(span bufs + , piece_index_t const piece, int const offset + , open_mode_t const flags, storage_error& error) + { + return readwritev(files(), bufs, piece, offset, error + , [this, flags](file_index_t const file_index + , std::int64_t const file_offset + , span vec, storage_error& ec) + { + if (files().pad_file_at(file_index)) + { + // writing to a pad-file is a no-op + return bufs_size(vec); + } + + if (file_index < m_file_priority.end_index() + && m_file_priority[file_index] == dont_download + && use_partfile(file_index)) + { + TORRENT_ASSERT(m_part_file); + + error_code e; + peer_request map = files().map_file(file_index + , file_offset, 0); + int const ret = m_part_file->writev(vec + , map.piece, map.start, e); + + if (e) + { + ec.ec = e; + ec.file(file_index); + ec.operation = operation_t::partfile_write; + return -1; + } + return ret; + } + + // invalidate our stat cache for this file, since + // we're writing to it + m_stat_cache.set_dirty(file_index); + + file_handle handle = open_file(file_index + , open_mode::read_write, ec); + if (ec) return -1; + + error_code e; + int const ret = int(handle->writev(file_offset + , vec, e, flags)); + + // set this unconditionally in case the upper layer would like to treat + // short reads as errors + ec.operation = operation_t::file_write; + + // we either get an error or 0 or more bytes read + TORRENT_ASSERT(e || ret >= 0); + TORRENT_ASSERT(ret <= bufs_size(vec)); + + if (e) + { + ec.ec = e; + ec.file(file_index); + return -1; + } + + return ret; + }); + } + + file_handle default_storage::open_file(file_index_t const file + , open_mode_t mode, storage_error& ec) const + { + file_handle h = open_file_impl(file, mode, ec.ec); + if (((mode & open_mode::rw_mask) != open_mode::read_only) + && ec.ec == boost::system::errc::no_such_file_or_directory) + { + // this means the directory the file is in doesn't exist. + // so create it + ec.ec.clear(); + std::string path = files().file_path(file, m_save_path); + create_directories(parent_path(path), ec.ec); + + if (ec.ec) + { + ec.file(file); + ec.operation = operation_t::mkdir; + return file_handle(); + } + + // if the directory creation failed, don't try to open the file again + // but actually just fail + h = open_file_impl(file, mode, ec.ec); + } + if (ec.ec) + { + ec.file(file); + ec.operation = operation_t::file_open; + return file_handle(); + } + TORRENT_ASSERT(h); + + if ((mode & open_mode::rw_mask) != open_mode::read_only) + { + std::unique_lock l(m_file_created_mutex); + if (m_file_created.size() != files().num_files()) + m_file_created.resize(files().num_files(), false); + + TORRENT_ASSERT(int(m_file_created.size()) == files().num_files()); + TORRENT_ASSERT(file < m_file_created.end_index()); + // if this is the first time we open this file for writing, + // and we have m_allocate_files enabled, set the final size of + // the file right away, to allocate it on the filesystem. + if (m_file_created[file] == false) + { + m_file_created.set_bit(file); + l.unlock(); + + // if we're allocating files or if the file exists and is greater + // than what it's supposed to be, truncate it to its correct size + std::int64_t const size = files().file_size(file); + error_code e; + bool const need_truncate = h->get_size(e) > size; + if (e) + { + ec.ec = e; + ec.file(file); + ec.operation = operation_t::file_stat; + return h; + } + + if (m_allocate_files || need_truncate) + { + h->set_size(size, e); + if (e) + { + ec.ec = e; + ec.file(file); + ec.operation = operation_t::file_fallocate; + return h; + } + m_stat_cache.set_dirty(file); + } + } + } + return h; + } + + file_handle default_storage::open_file_impl(file_index_t file, open_mode_t mode + , error_code& ec) const + { + if (!m_allocate_files) mode |= open_mode::sparse; + + // files with priority 0 should always be sparse + if (m_file_priority.end_index() > file + && m_file_priority[file] == dont_download) + { + mode |= open_mode::sparse; + } + + if (m_settings && settings().get_bool(settings_pack::no_atime_storage)) mode |= open_mode::no_atime; + + // if we have a cache already, don't store the data twice by leaving it in the OS cache as well + if (m_settings + && settings().get_int(settings_pack::disk_io_write_mode) + == settings_pack::disable_os_cache) + { + mode |= open_mode::no_cache; + } + + file_handle ret = m_pool.open_file(storage_index(), m_save_path, file + , files(), mode, ec); + return ret; + } + + bool default_storage::tick() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + return false; + } + + storage_interface* default_storage_constructor(storage_params const& params + , file_pool& pool) + { + return new default_storage(params, pool); + } + + // -- disabled_storage -------------------------------------------------- + +namespace { + + // this storage implementation does not write anything to disk + // and it pretends to read, and just leaves garbage in the buffers + // this is useful when simulating many clients on the same machine + // or when running stress tests and want to take the cost of the + // disk I/O out of the picture. This cannot be used for any kind + // of normal bittorrent operation, since it will just send garbage + // to peers and throw away all the data it downloads. It would end + // up being banned immediately + class disabled_storage final : public storage_interface + { + public: + explicit disabled_storage(file_storage const& fs) : storage_interface(fs) {} + + bool has_any_file(storage_error&) override { return false; } + void set_file_priority(aux::vector& + , storage_error&) override {} + void rename_file(file_index_t, std::string const&, storage_error&) override {} + void release_files(storage_error&) override {} + void delete_files(remove_flags_t, storage_error&) override {} + void initialize(storage_error&) override {} + status_t move_storage(std::string const&, move_flags_t, storage_error&) override { return status_t::no_error; } + + int readv(span bufs + , piece_index_t, int, open_mode_t, storage_error&) override + { + return bufs_size(bufs); + } + int writev(span bufs + , piece_index_t, int, open_mode_t, storage_error&) override + { + return bufs_size(bufs); + } + + bool verify_resume_data(add_torrent_params const& + , aux::vector const& + , storage_error&) override { return false; } + }; + } + + storage_interface* disabled_storage_constructor(storage_params const& params, file_pool&) + { + return new disabled_storage(params.files); + } + + // -- zero_storage ------------------------------------------------------ + +namespace { + + // this storage implementation always reads zeroes, and always discards + // anything written to it + struct zero_storage final : storage_interface + { + explicit zero_storage(file_storage const& fs) : storage_interface(fs) {} + void initialize(storage_error&) override {} + + int readv(span bufs + , piece_index_t, int, open_mode_t, storage_error&) override + { + int ret = 0; + for (auto const& b : bufs) + { + std::memset(b.data(), 0, std::size_t(b.size())); + ret += int(b.size()); + } + return ret; + } + int writev(span bufs + , piece_index_t, int, open_mode_t, storage_error&) override + { + return std::accumulate(bufs.begin(), bufs.end(), 0 + , [](int const acc, iovec_t const& b) { return acc + int(b.size()); }); + } + + bool has_any_file(storage_error&) override { return false; } + void set_file_priority(aux::vector& /* prio */ + , storage_error&) override {} + status_t move_storage(std::string const& /* save_path */ + , move_flags_t, storage_error&) override { return status_t::no_error; } + bool verify_resume_data(add_torrent_params const& /* rd */ + , aux::vector const& /* links */ + , storage_error&) override + { return false; } + void release_files(storage_error&) override {} + void rename_file(file_index_t + , std::string const& /* new_filename */, storage_error&) override {} + void delete_files(remove_flags_t, storage_error&) override {} + }; + } + + storage_interface* zero_storage_constructor(storage_params const& params, file_pool&) + { + return new zero_storage(params.files); + } + +} // namespace libtorrent diff --git a/src/storage_piece_set.cpp b/src/storage_piece_set.cpp new file mode 100644 index 0000000..6d74f07 --- /dev/null +++ b/src/storage_piece_set.cpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg, Daniel Wallin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/storage_piece_set.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/storage.hpp" // for storage_interface + +namespace libtorrent { namespace aux { + + void storage_piece_set::add_piece(cached_piece_entry* p) + { + TORRENT_ASSERT(p->in_storage == false); + TORRENT_ASSERT(p->storage.get() == this); + m_cached_pieces.push_back(*p); + ++m_num_pieces; +#if TORRENT_USE_ASSERTS + p->in_storage = true; +#endif + } + + void storage_piece_set::remove_piece(cached_piece_entry* p) + { + TORRENT_ASSERT(p->in_storage == true); + p->unlink(); + --m_num_pieces; +#if TORRENT_USE_ASSERTS + p->in_storage = false; +#endif + } + +}} diff --git a/src/storage_utils.cpp b/src/storage_utils.cpp new file mode 100644 index 0000000..c907391 --- /dev/null +++ b/src/storage_utils.cpp @@ -0,0 +1,631 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/aux_/path.hpp" // for count_bufs +#include "libtorrent/part_file.hpp" +#include "libtorrent/session.hpp" // for session::delete_files +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_status.hpp" + +#include + +namespace libtorrent { namespace aux { + + int copy_bufs(span bufs, int bytes + , span target) + { + TORRENT_ASSERT(bytes >= 0); + auto dst = target.begin(); + int ret = 0; + if (bytes == 0) return ret; + for (iovec_t const& src : bufs) + { + auto const to_copy = std::min(src.size(), std::ptrdiff_t(bytes)); + *dst = src.first(to_copy); + bytes -= int(to_copy); + ++ret; + ++dst; + if (bytes <= 0) return ret; + } + return ret; + } + + span advance_bufs(span bufs, int const bytes) + { + TORRENT_ASSERT(bytes >= 0); + std::ptrdiff_t size = 0; + for (;;) + { + size += bufs.front().size(); + if (size >= bytes) + { + bufs.front() = bufs.front().last(size - bytes); + return bufs; + } + bufs = bufs.subspan(1); + } + } + + void clear_bufs(span bufs) + { + for (auto buf : bufs) + std::fill(buf.begin(), buf.end(), char(0)); + } + +#if TORRENT_USE_ASSERTS + namespace { + + int count_bufs(span bufs, int bytes) + { + std::ptrdiff_t size = 0; + int count = 0; + if (bytes == 0) return count; + for (auto b : bufs) + { + ++count; + size += b.size(); + if (size >= bytes) return count; + } + return count; + } + + } +#endif + + // much of what needs to be done when reading and writing is buffer + // management and piece to file mapping. Most of that is the same for reading + // and writing. This function is a template, and the fileop decides what to + // do with the file and the buffers. + int readwritev(file_storage const& files, span const bufs + , piece_index_t const piece, const int offset + , storage_error& ec, fileop op) + { + TORRENT_ASSERT(piece >= piece_index_t(0)); + TORRENT_ASSERT(piece < files.end_piece()); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(bufs.size() > 0); + + const int size = bufs_size(bufs); + TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(static_cast(piece) * static_cast(files.piece_length()) + + offset + size <= files.total_size()); + + // find the file iterator and file offset + std::int64_t const torrent_offset = static_cast(piece) * std::int64_t(files.piece_length()) + offset; + file_index_t file_index = files.file_index_at_offset(torrent_offset); + TORRENT_ASSERT(torrent_offset >= files.file_offset(file_index)); + TORRENT_ASSERT(torrent_offset < files.file_offset(file_index) + files.file_size(file_index)); + std::int64_t file_offset = torrent_offset - files.file_offset(file_index); + + // the number of bytes left before this read or write operation is + // completely satisfied. + int bytes_left = size; + + TORRENT_ASSERT(bytes_left >= 0); + + // copy the iovec array so we can use it to keep track of our current + // location by updating the head base pointer and size. (see + // advance_bufs()) + TORRENT_ALLOCA(current_buf, iovec_t, bufs.size()); + copy_bufs(bufs, size, current_buf); + TORRENT_ASSERT(count_bufs(current_buf, size) == int(bufs.size())); + + TORRENT_ALLOCA(tmp_buf, iovec_t, bufs.size()); + + while (bytes_left > 0) + { + // the number of bytes left to read in the current file (specified by + // file_index). This is the minimum of (file_size - file_offset) and + // bytes_left. + int file_bytes_left = bytes_left; + if (file_offset + file_bytes_left > files.file_size(file_index)) + file_bytes_left = std::max(static_cast(files.file_size(file_index) - file_offset), 0); + + // there are no bytes left in this file, move to the next one + // this loop skips over empty files + while (file_bytes_left == 0) + { + ++file_index; + file_offset = 0; + TORRENT_ASSERT(file_index < files.end_file()); + + // this should not happen. bytes_left should be clamped by the total + // size of the torrent, so we should never run off the end of it + if (file_index >= files.end_file()) return size; + + file_bytes_left = bytes_left; + if (file_offset + file_bytes_left > files.file_size(file_index)) + file_bytes_left = std::max(static_cast(files.file_size(file_index) - file_offset), 0); + } + + // make a copy of the iovec array that _just_ covers the next + // file_bytes_left bytes, i.e. just this one operation + int const tmp_bufs_used = copy_bufs(current_buf, file_bytes_left, tmp_buf); + + int const bytes_transferred = op(file_index, file_offset + , tmp_buf.first(tmp_bufs_used), ec); + if (ec) return -1; + + // advance our position in the iovec array and the file offset. + current_buf = advance_bufs(current_buf, bytes_transferred); + bytes_left -= bytes_transferred; + file_offset += bytes_transferred; + + TORRENT_ASSERT(count_bufs(current_buf, bytes_left) <= int(bufs.size())); + + // if the file operation returned 0, we've hit end-of-file. We're done + if (bytes_transferred == 0) + { + if (file_bytes_left > 0 ) + { + // fill in this information in case the caller wants to treat + // a short-read as an error + ec.file(file_index); + } + return size - bytes_left; + } + } + return size; + } + + std::pair move_storage(file_storage const& f + , std::string const& save_path + , std::string const& destination_save_path + , part_file* pf + , move_flags_t const flags, storage_error& ec) + { + status_t ret = status_t::no_error; + std::string const new_save_path = complete(destination_save_path); + + // check to see if any of the files exist + if (flags == move_flags_t::fail_if_exist) + { + file_status s; + error_code err; + stat_file(new_save_path, &s, err); + if (err != boost::system::errc::no_such_file_or_directory) + { + // the directory exists, check all the files + for (auto const i : f.file_range()) + { + // files moved out to absolute paths are ignored + if (f.file_absolute_path(i)) continue; + + stat_file(f.file_path(i, new_save_path), &s, err); + if (err != boost::system::errc::no_such_file_or_directory) + { + ec.ec = err; + ec.file(i); + ec.operation = operation_t::file_stat; + return { status_t::file_exist, save_path }; + } + } + } + } + + { + file_status s; + error_code err; + stat_file(new_save_path, &s, err); + if (err == boost::system::errc::no_such_file_or_directory) + { + err.clear(); + create_directories(new_save_path, err); + if (err) + { + ec.ec = err; + ec.file(file_index_t(-1)); + ec.operation = operation_t::mkdir; + return { status_t::fatal_disk_error, save_path }; + } + } + else if (err) + { + ec.ec = err; + ec.file(file_index_t(-1)); + ec.operation = operation_t::file_stat; + return { status_t::fatal_disk_error, save_path }; + } + } + + // indices of all files we ended up copying. These need to be deleted + // later + aux::vector copied_files(std::size_t(f.num_files()), false); + + // track how far we got in case of an error + file_index_t file_index{}; + error_code e; + for (auto const i : f.file_range()) + { + // files moved out to absolute paths are not moved + if (f.file_absolute_path(i)) continue; + + std::string const old_path = combine_path(save_path, f.file_path(i)); + std::string const new_path = combine_path(new_save_path, f.file_path(i)); + + if (flags == move_flags_t::dont_replace && exists(new_path)) + { + if (ret == status_t::no_error) ret = status_t::need_full_check; + continue; + } + + // TODO: ideally, if we end up copying files because of a move across + // volumes, the source should not be deleted until they've all been + // copied. That would let us rollback with higher confidence. + move_file(old_path, new_path, e); + + // if the source file doesn't exist. That's not a problem + // we just ignore that file + if (e == boost::system::errc::no_such_file_or_directory) + e.clear(); + else if (e + && e != boost::system::errc::invalid_argument + && e != boost::system::errc::permission_denied) + { + // moving the file failed + // on OSX, the error when trying to rename a file across different + // volumes is EXDEV, which will make it fall back to copying. + e.clear(); + copy_file(old_path, new_path, e); + if (!e) copied_files[i] = true; + } + + if (e) + { + ec.ec = e; + ec.file(i); + ec.operation = operation_t::file_rename; + file_index = i; + break; + } + } + + if (!e && pf) + { + pf->move_partfile(new_save_path, e); + if (e) + { + ec.ec = e; + ec.file(torrent_status::error_file_partfile); + ec.operation = operation_t::partfile_move; + } + } + + if (e) + { + // rollback + while (--file_index >= file_index_t(0)) + { + // files moved out to absolute paths are not moved + if (f.file_absolute_path(file_index)) continue; + + // if we ended up copying the file, don't do anything during + // roll-back + if (copied_files[file_index]) continue; + + std::string const old_path = combine_path(save_path, f.file_path(file_index)); + std::string const new_path = combine_path(new_save_path, f.file_path(file_index)); + + // ignore errors when rolling back + error_code ignore; + move_file(new_path, old_path, ignore); + } + + return { status_t::fatal_disk_error, save_path }; + } + + // TODO: 2 technically, this is where the transaction of moving the files + // is completed. This is where the new save_path should be committed. If + // there is an error in the code below, that should not prevent the new + // save path to be set. Maybe it would make sense to make the save_path + // an in-out parameter + + std::set subdirs; + for (auto const i : f.file_range()) + { + // files moved out to absolute paths are not moved + if (f.file_absolute_path(i)) continue; + + if (has_parent_path(f.file_path(i))) + subdirs.insert(parent_path(f.file_path(i))); + + // if we ended up renaming the file instead of moving it, there's no + // need to delete the source. + if (copied_files[i] == false) continue; + + std::string const old_path = combine_path(save_path, f.file_path(i)); + + // we may still have some files in old save_path + // eg. if (flags == dont_replace && exists(new_path)) + // ignore errors when removing + error_code ignore; + remove(old_path, ignore); + } + + for (std::string const& s : subdirs) + { + error_code err; + std::string subdir = combine_path(save_path, s); + + while (!compare_path(subdir, save_path) && !err) + { + remove(subdir, err); + subdir = parent_path(subdir); + } + } + + return { ret, new_save_path }; + } + + namespace { + + void delete_one_file(std::string const& p, error_code& ec) + { + remove(p, ec); + + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + } + + } + + void delete_files(file_storage const& fs, std::string const& save_path + , std::string const& part_file_name, remove_flags_t const options, storage_error& ec) + { + if (options == session::delete_files) + { + // delete the files from disk + std::set directories; + using iter_t = std::set::iterator; + for (auto const i : fs.file_range()) + { + std::string const fp = fs.file_path(i); + bool const complete = fs.file_absolute_path(i); + std::string const p = complete ? fp : combine_path(save_path, fp); + if (!complete) + { + std::string bp = parent_path(fp); + std::pair ret; + ret.second = true; + while (ret.second && !bp.empty()) + { + ret = directories.insert(combine_path(save_path, bp)); + bp = parent_path(bp); + } + } + delete_one_file(p, ec.ec); + if (ec) { ec.file(i); ec.operation = operation_t::file_remove; } + } + + // remove the directories. Reverse order to delete + // subdirectories first + + for (auto i = directories.rbegin() + , end(directories.rend()); i != end; ++i) + { + error_code error; + delete_one_file(*i, error); + if (error && !ec) + { + ec.file(file_index_t(-1)); + ec.ec = error; + ec.operation = operation_t::file_remove; + } + } + } + + if (options == session::delete_files + || options == session::delete_partfile) + { + error_code error; + remove(combine_path(save_path, part_file_name), error); + if (error && error != boost::system::errc::no_such_file_or_directory) + { + ec.file(file_index_t(-1)); + ec.ec = error; + ec.operation = operation_t::file_remove; + } + } + } + +namespace { + +std::int64_t get_filesize(stat_cache& stat, file_index_t const file_index + , file_storage const& fs, std::string const& save_path, storage_error& ec) +{ + error_code error; + std::int64_t const size = stat.get_filesize(file_index, fs, save_path, error); + + if (size >= 0) return size; + if (error != boost::system::errc::no_such_file_or_directory) + { + ec.ec = error; + ec.file(file_index); + ec.operation = operation_t::file_stat; + } + else + { + ec.ec = errors::mismatching_file_size; + ec.file(file_index); + ec.operation = operation_t::file_stat; + } + return -1; +} + +} + + bool verify_resume_data(add_torrent_params const& rd + , aux::vector const& links + , file_storage const& fs + , aux::vector const& file_priority + , stat_cache& stat + , std::string const& save_path + , storage_error& ec) + { +#ifdef TORRENT_DISABLE_MUTABLE_TORRENTS + TORRENT_UNUSED(links); +#else + // TODO: this should probably be moved to default_storage::initialize + if (!links.empty()) + { + TORRENT_ASSERT(int(links.size()) == fs.num_files()); + // if this is a mutable torrent, and we need to pick up some files + // from other torrents, do that now. Note that there is an inherent + // race condition here. We checked if the files existed on a different + // thread a while ago. These files may no longer exist or may have been + // moved. If so, we just fail. The user is responsible to not touch + // other torrents until a new mutable torrent has been completely + // added. + for (auto const idx : fs.file_range()) + { + std::string const& s = links[idx]; + if (s.empty()) continue; + + error_code err; + std::string file_path = fs.file_path(idx, save_path); + hard_link(s, file_path, err); + + // if the file already exists, that's not an error + // TODO: 2 is this risky? The upper layer will assume we have the + // whole file. Perhaps we should verify that at least the size + // of the file is correct + if (!err || err == boost::system::errc::file_exists) + continue; + + ec.ec = err; + ec.file(idx); + ec.operation = operation_t::file_hard_link; + return false; + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + bool const seed = (rd.have_pieces.size() >= fs.num_pieces() + && rd.have_pieces.all_set()) + || (rd.flags & torrent_flags::seed_mode); + + if (seed) + { + for (file_index_t const file_index : fs.file_range()) + { + if (fs.pad_file_at(file_index)) continue; + + // files with priority zero may not have been saved to disk at their + // expected location, but is likely to be in a partfile. Just exempt it + // from checking + if (file_index < file_priority.end_index() + && file_priority[file_index] == dont_download + && !(rd.flags & torrent_flags::seed_mode)) + continue; + + std::int64_t const size = get_filesize(stat, file_index, fs + , save_path, ec); + if (size < 0) return false; + + if (size < fs.file_size(file_index)) + { + ec.ec = errors::mismatching_file_size; + ec.file(file_index); + ec.operation = operation_t::check_resume; + return false; + } + } + return true; + } + + // parse have bitmask. Verify that the files we expect to have + // actually do exist + for (piece_index_t i(0); i < rd.have_pieces.end_index(); ++i) + { + if (rd.have_pieces.get_bit(i) == false) continue; + + std::vector f = fs.map_block(i, 0, 1); + TORRENT_ASSERT(!f.empty()); + + file_index_t const file_index = f[0].file_index; + + // files with priority zero may not have been saved to disk at their + // expected location, but is likely to be in a partfile. Just exempt it + // from checking + if (file_index < file_priority.end_index() + && file_priority[file_index] == dont_download) + continue; + + if (fs.pad_file_at(file_index)) continue; + + if (get_filesize(stat, file_index, fs, save_path, ec) < 0) + return false; + + // OK, this file existed, good. Now, skip all remaining pieces in + // this file. We're just sanity-checking whether the files exist + // or not. + peer_request const pr = fs.map_file(file_index + , fs.file_size(file_index) + 1, 0); + i = std::max(next(i), pr.piece); + } + return true; + } + + bool has_any_file( + file_storage const& fs + , std::string const& save_path + , stat_cache& cache + , storage_error& ec) + { + for (auto const i : fs.file_range()) + { + std::int64_t const sz = cache.get_filesize( + i, fs, save_path, ec.ec); + + if (sz < 0) + { + if (ec && ec.ec != boost::system::errc::no_such_file_or_directory) + { + ec.file(i); + ec.operation = operation_t::file_stat; + cache.clear(); + return false; + } + // some files not existing is expected and not an error + ec.ec.clear(); + } + + if (sz > 0) return true; + } + return false; + } + +}} diff --git a/src/string_util.cpp b/src/string_util.cpp new file mode 100644 index 0000000..6d29e10 --- /dev/null +++ b/src/string_util.cpp @@ -0,0 +1,396 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" + +#include // for malloc +#include // for strlen +#include // for search + +namespace libtorrent { + + // We need well defined results that don't depend on locale + std::array::digits10> + to_string(std::int64_t const n) + { + std::array::digits10> ret; + char *p = &ret.back(); + *p = '\0'; + // we want "un" to be the absolute value + // since the absolute of INT64_MIN cannot be represented by a signed + // int64, we calculate the abs in unsigned space + std::uint64_t un = n < 0 + ? std::numeric_limits::max() - std::uint64_t(n) + 1 + : std::uint64_t(n); + do { + *--p = '0' + un % 10; + un /= 10; + } while (un); + if (n < 0) *--p = '-'; + std::memmove(ret.data(), p, std::size_t(&ret.back() - p + 1)); + return ret; + } + + bool is_alpha(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + bool is_print(char c) + { + return c >= 32 && c < 127; + } + + bool is_space(char c) + { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; + } + + char to_lower(char c) + { + return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; + } + + bool string_begins_no_case(char const* s1, char const* s2) + { + TORRENT_ASSERT(s1 != nullptr); + TORRENT_ASSERT(s2 != nullptr); + + while (*s1 != 0) + { + if (to_lower(*s1) != to_lower(*s2)) return false; + ++s1; + ++s2; + } + return true; + } + + bool string_equal_no_case(string_view s1, string_view s2) + { + if (s1.size() != s2.size()) return false; + return std::equal(s1.begin(), s1.end(), s2.begin() + , [] (char const c1, char const c2) + { return to_lower(c1) == to_lower(c2); }); + } + + // generate a url-safe random string + void url_random(span dest) + { + // http-accepted characters: + // excluding ', since some buggy trackers don't support that + static char const printable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz-_.!~*()"; + + // the random number + std::generate(dest.begin(), dest.end() + , []{ return printable[random(sizeof(printable) - 2)]; }); + } + + bool string_ends_with(string_view s1, string_view s2) + { + return s1.size() >= s2.size() && std::equal(s2.rbegin(), s2.rend(), s1.rbegin()); + } + + int search(span src, span target) + { + TORRENT_ASSERT(!src.empty()); + TORRENT_ASSERT(!target.empty()); + TORRENT_ASSERT(target.size() >= src.size()); + TORRENT_ASSERT(target.size() < std::numeric_limits::max()); + + auto const it = std::search(target.begin(), target.end(), src.begin(), src.end()); + + // no complete sync + if (it == target.end()) return -1; + return static_cast(it - target.begin()); + } + + char* allocate_string_copy(string_view str) + { + if (str.empty()) return nullptr; + auto* tmp = new char[str.size() + 1]; + std::copy(str.data(), str.data() + str.size(), tmp); + tmp[str.size()] = '\0'; + return tmp; + } + +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING + std::string print_listen_interfaces(std::vector const& in) + { + std::string ret; + for (auto const& i : in) + { + if (!ret.empty()) ret += ','; + + error_code ec; + make_address_v6(i.device, ec); + if (!ec) + { + // IPv6 addresses must be wrapped in square brackets + ret += '['; + ret += i.device; + ret += ']'; + } + else + { + ret += i.device; + } + ret += ':'; + ret += to_string(i.port).data(); + if (i.ssl) ret += 's'; + if (i.local) ret += 'l'; + } + + return ret; + } +#endif + + string_view strip_string(string_view in) + { + while (!in.empty() && is_space(in.front())) + in.remove_prefix(1); + + while (!in.empty() && is_space(in.back())) + in.remove_suffix(1); + return in; + } + + // this parses the string that's used as the listen_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + std::vector parse_listen_interfaces(std::string const& in + , std::vector& err) + { + std::vector out; + + string_view rest = in; + while (!rest.empty()) + { + string_view element; + std::tie(element, rest) = split_string(rest, ','); + + element = strip_string(element); + if (element.size() > 1 && element.front() == '"' && element.back() == '"') + element = element.substr(1, element.size() - 2); + if (element.empty()) continue; + + listen_interface_t iface; + iface.ssl = false; + iface.local = false; + + string_view port; + if (element.front() == '[') + { + auto const pos = find_first_of(element, ']', 0); + if (pos == string_view::npos + || pos+1 >= element.size() + || element[pos+1] != ':') + { + err.emplace_back(element); + continue; + } + + iface.device = strip_string(element.substr(1, pos - 1)).to_string(); + + port = strip_string(element.substr(pos + 2)); + } + else + { + // consume device name + auto const pos = find_first_of(element, ':', 0); + iface.device = strip_string(element.substr(0, pos)).to_string(); + if (pos == string_view::npos) + { + err.emplace_back(element); + continue; + } + port = strip_string(element.substr(pos + 1)); + } + + // consume port + std::string port_str; + for (std::size_t i = 0; i < port.size() && is_digit(port[i]); ++i) + port_str += port[i]; + + if (port_str.empty() || port_str.size() > 5) + { + err.emplace_back(element); + continue; + } + + iface.port = std::atoi(port_str.c_str()); + if (iface.port < 0 || iface.port > 65535) + { + err.emplace_back(element); + continue; + } + + port.remove_prefix(port_str.size()); + port = strip_string(port); + + // consume potential SSL 's' + for (auto const c : port) + { + switch (c) + { + case 's': iface.ssl = true; break; + case 'l': iface.local = true; break; + } + } + + TORRENT_ASSERT(iface.port >= 0); + out.emplace_back(iface); + } + + return out; + } + + // this parses the string that's used as the dht_bootstrap setting. + // it is a comma-separated list of IP or hostnames with ports. For + // example: "router.bittorrent.com:6881,router.utorrent.com:6881" or "127.0.0.1:6881" + void parse_comma_separated_string_port(std::string const& in + , std::vector>& out) + { + out.clear(); + + std::string::size_type start = 0; + std::string::size_type end = 0; + + while (start < in.size()) + { + // skip leading spaces + while (start < in.size() + && is_space(in[start])) + ++start; + + end = in.find_first_of(',', start); + if (end == std::string::npos) end = in.size(); + + std::string::size_type colon = in.find_last_of(':', end); + + if (colon != std::string::npos && colon > start) + { + int port = std::atoi(in.substr(colon + 1, end - colon - 1).c_str()); + + // skip trailing spaces + std::string::size_type soft_end = colon; + while (soft_end > start + && is_space(in[soft_end-1])) + --soft_end; + + // in case this is an IPv6 address, strip off the square brackets + // to make it more easily parseable into an ip::address + if (in[start] == '[') ++start; + if (soft_end > start && in[soft_end-1] == ']') --soft_end; + + out.emplace_back(in.substr(start, soft_end - start), port); + } + + start = end + 1; + } + } + + void parse_comma_separated_string(std::string const& in, std::vector& out) + { + out.clear(); + + std::string::size_type start = 0; + std::string::size_type end = 0; + + while (start < in.size()) + { + // skip leading spaces + while (start < in.size() + && is_space(in[start])) + ++start; + + end = in.find_first_of(',', start); + if (end == std::string::npos) end = in.size(); + + // skip trailing spaces + std::string::size_type soft_end = end; + while (soft_end > start + && is_space(in[soft_end - 1])) + --soft_end; + + out.push_back(in.substr(start, soft_end - start)); + start = end + 1; + } + } + + std::pair split_string(string_view last, char const sep) + { + if (last.empty()) return {{}, {}}; + + std::size_t pos = 0; + if (last[0] == '"' && sep != '"') + { + for (auto const c : last.substr(1)) + { + ++pos; + if (c == '"') break; + } + } + std::size_t found_sep = 0; + for (char const c : last.substr(pos)) + { + if (c == sep) + { + found_sep = 1; + break; + } + ++pos; + } + return {last.substr(0, pos), last.substr(pos + found_sep)}; + } + +#if TORRENT_USE_I2P + + bool is_i2p_url(std::string const& url) + { + using std::ignore; + std::string hostname; + error_code ec; + std::tie(ignore, ignore, hostname, ignore, ignore) + = parse_url_components(url, ec); + return string_ends_with(hostname, ".i2p"); + } + +#endif +} diff --git a/src/time.cpp b/src/time.cpp new file mode 100644 index 0000000..06efeed --- /dev/null +++ b/src/time.cpp @@ -0,0 +1,40 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent { namespace aux { + + time_point time_now() { return clock_type::now(); } + time_point32 time_now32() { return time_point_cast(clock_type::now()); } + +} } diff --git a/src/timestamp_history.cpp b/src/timestamp_history.cpp new file mode 100644 index 0000000..609ef55 --- /dev/null +++ b/src/timestamp_history.cpp @@ -0,0 +1,105 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#include "libtorrent/timestamp_history.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + +namespace libtorrent { + +constexpr std::uint32_t TIME_MASK = 0xffffffff; + +// defined in utp_stream.cpp +bool compare_less_wrap(std::uint32_t lhs, std::uint32_t rhs + , std::uint32_t mask); + +std::uint32_t timestamp_history::add_sample(std::uint32_t sample, bool step) +{ + if (!initialized()) + { + m_history.fill(sample); + m_base = sample; + m_num_samples = 0; + } + + // don't let the counter wrap + if (m_num_samples < 0xfffe) ++m_num_samples; + + // if sample is less than base, update the base + // and update the history entry (because it will + // be less than that too) + if (compare_less_wrap(sample, m_base, TIME_MASK)) + { + m_base = sample; + m_history[m_index] = sample; + } + // if sample is less than our history entry, update it + else if (compare_less_wrap(sample, m_history[m_index], TIME_MASK)) + { + m_history[m_index] = sample; + } + + std::uint32_t ret = sample - m_base; + + // don't step base delay history unless we have at least 120 + // samples. Anything less would suggest that the connection is + // essentially idle and the samples are probably not very reliable + if (step && m_num_samples > 120) + { + m_num_samples = 0; + m_index = (m_index + 1) % history_size; + + m_history[m_index] = sample; + // update m_base + m_base = sample; + for (auto& h : m_history) + { + if (compare_less_wrap(h, m_base, TIME_MASK)) + m_base = h; + } + } + return ret; +} + +void timestamp_history::adjust_base(int change) +{ + TORRENT_ASSERT(initialized()); + m_base += aux::numeric_cast(change); + // make sure this adjustment sticks by updating all history slots + for (auto& h : m_history) + { + if (compare_less_wrap(h, m_base, TIME_MASK)) + h = m_base; + } +} + +} diff --git a/src/torrent.cpp b/src/torrent.cpp new file mode 100644 index 0000000..7446e2d --- /dev/null +++ b/src/torrent.cpp @@ -0,0 +1,11224 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include // for va_list +#include +#include +#include +#include +#include +#include +#include +#include // for numeric_limits +#include // for snprintf +#include + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/ssl_stream.hpp" +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif // TORRENT_USE_OPENSSL + +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/http_seed_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/identify_client.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/instantiate_connection.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/http_connection.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/peer_class.hpp" // for peer_class +#include "libtorrent/socket_io.hpp" // for read_*_endpoint +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/aux_/has_block.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_ip_address +#include "libtorrent/download_priority.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/range.hpp" +// TODO: factor out cache_status to its own header +#include "libtorrent/disk_io_thread.hpp" // for cache_status +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/generate_peer_id.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/aux_/session_impl.hpp" // for tracker_logger +#endif + +#include "libtorrent/aux_/torrent_impl.hpp" + +using namespace std::placeholders; + +namespace libtorrent { +namespace { + +bool is_downloading_state(int const st) +{ + switch (st) + { + case torrent_status::checking_files: + case torrent_status::allocating: + case torrent_status::checking_resume_data: + return false; + case torrent_status::downloading_metadata: + case torrent_status::downloading: + case torrent_status::finished: + case torrent_status::seeding: + return true; + default: + // unexpected state + TORRENT_ASSERT_FAIL_VAL(st); + return false; + } +} +} // anonymous namespace + + constexpr web_seed_flag_t torrent::ephemeral; + + web_seed_t::web_seed_t(web_seed_entry const& wse) + : web_seed_entry(wse) + { + peer_info.web_seed = true; + } + + web_seed_t::web_seed_t(std::string const& url_, web_seed_entry::type_t type_ + , std::string const& auth_ + , web_seed_entry::headers_t const& extra_headers_) + : web_seed_entry(url_, type_, auth_, extra_headers_) + { + peer_info.web_seed = true; + } + + torrent_hot_members::torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, bool const session_paused) + : m_ses(ses) + , m_complete(0xffffff) + , m_upload_mode(p.flags & torrent_flags::upload_mode) + , m_connections_initialized(false) + , m_abort(false) + , m_paused(p.flags & torrent_flags::paused) + , m_session_paused(session_paused) +#ifndef TORRENT_DISABLE_SHARE_MODE + , m_share_mode(p.flags & torrent_flags::share_mode) +#endif + , m_have_all(false) + , m_graceful_pause_mode(false) + , m_state_subscription(p.flags & torrent_flags::update_subscribe) + , m_max_connections(0xffffff) + , m_state(torrent_status::checking_resume_data) + {} + + torrent::torrent( + aux::session_interface& ses + , bool const session_paused + , add_torrent_params const& p) + : torrent_hot_members(ses, p, session_paused) + , m_total_uploaded(p.total_uploaded) + , m_total_downloaded(p.total_downloaded) + , m_tracker_timer(ses.get_io_service()) + , m_inactivity_timer(ses.get_io_service()) + , m_trackerid(p.trackerid) + , m_save_path(complete(p.save_path)) +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + , m_url(p.url) + , m_uuid(p.uuid) +#endif + , m_stats_counters(ses.stats_counters()) + , m_storage_constructor(p.storage) + , m_added_time(p.added_time ? p.added_time : std::time(nullptr)) + , m_completed_time(p.completed_time) + , m_info_hash(p.info_hash) + , m_error_file(torrent_status::error_file_none) + , m_sequence_number(-1) + , m_peer_id(aux::generate_peer_id(settings())) + , m_announce_to_trackers(!(p.flags & torrent_flags::paused)) + , m_announce_to_lsd(!(p.flags & torrent_flags::paused)) + , m_has_incoming(false) + , m_files_checked(false) + , m_storage_mode(p.storage_mode) + , m_announcing(false) + , m_added(false) + , m_sequential_download(p.flags & torrent_flags::sequential_download) + , m_auto_sequential(false) + , m_seed_mode(false) +#ifndef TORRENT_DISABLE_SUPERSEEDING + , m_super_seeding(p.flags & torrent_flags::super_seeding) +#endif + , m_stop_when_ready(p.flags & torrent_flags::stop_when_ready) + , m_need_save_resume_data(p.flags & torrent_flags::need_save_resume) + , m_enable_dht(!bool(p.flags & torrent_flags::disable_dht)) + , m_enable_lsd(!bool(p.flags & torrent_flags::disable_lsd)) + , m_max_uploads((1 << 24) - 1) + , m_save_resume_flags() + , m_num_uploads(0) + , m_enable_pex(!bool(p.flags & torrent_flags::disable_pex)) + , m_magnet_link(false) + , m_apply_ip_filter(p.flags & torrent_flags::apply_ip_filter) + , m_pending_active_change(false) + , m_connect_boost_counter(static_cast(settings().get_int(settings_pack::torrent_connect_boost))) + , m_incomplete(0xffffff) + , m_announce_to_dht(!(p.flags & torrent_flags::paused)) + , m_ssl_torrent(false) + , m_deleted(false) + , m_last_download(seconds32(p.last_download)) + , m_last_upload(seconds32(p.last_upload)) + , m_auto_managed(p.flags & torrent_flags::auto_managed) + , m_current_gauge_state(static_cast(no_gauge_state)) + , m_moving_storage(false) + , m_inactive(false) + , m_downloaded(0xffffff) + , m_progress_ppm(0) + , m_torrent_initialized(false) + , m_outstanding_file_priority(false) + , m_complete_sent(false) + { + // we cannot log in the constructor, because it relies on shared_from_this + // being initialized, which happens after the constructor returns. + + // TODO: 3 we could probably get away with just saving a few fields here + // TODO: 2 p should probably be moved in here + m_add_torrent_params.reset(new add_torrent_params(p)); + +#if TORRENT_USE_UNC_PATHS + m_save_path = canonicalize_path(m_save_path); +#endif + + if (!m_apply_ip_filter) + { + inc_stats_counter(counters::non_filter_torrents); + } + + if (!p.ti || !p.ti->is_valid()) + { + // we don't have metadata for this torrent. We'll download + // it either through the URL passed in, or through a metadata + // extension. Make sure that when we save resume data for this + // torrent, we also save the metadata + m_magnet_link = true; + } + + if (!m_torrent_file) + m_torrent_file = (p.ti ? p.ti : std::make_shared(m_info_hash)); + + // in case we added the torrent via magnet link, make sure to preserve any + // DHT nodes passed in on the URI in the torrent file itself + if (!m_torrent_file->is_valid()) + { + for (auto const& n : p.dht_nodes) + m_torrent_file->add_node(n); + } + + // --- WEB SEEDS --- + + // if override web seed flag is set, don't load any web seeds from the + // torrent file. + std::vector ws; + if (!(p.flags & torrent_flags::override_web_seeds)) + { + for (auto const& e : m_torrent_file->web_seeds()) + ws.emplace_back(e); + } + + // add web seeds from add_torrent_params + bool const multi_file = m_torrent_file->is_valid() + && m_torrent_file->num_files() > 1; + + for (auto const& u : p.url_seeds) + { + ws.emplace_back(web_seed_t(u, web_seed_entry::url_seed)); + + // correct URLs to end with a "/" for multi-file torrents + if (multi_file) + ensure_trailing_slash(ws.back().url); + if (!m_torrent_file->is_valid()) + m_torrent_file->add_url_seed(ws.back().url); + } + + for (auto const& e : p.http_seeds) + { + ws.emplace_back(e, web_seed_entry::http_seed); + if (!m_torrent_file->is_valid()) + m_torrent_file->add_http_seed(e); + } + + aux::random_shuffle(ws); + for (auto& w : ws) m_web_seeds.emplace_back(std::move(w)); + + // --- TRACKERS --- + + // if override trackers flag is set, don't load trackers from torrent file + if (!(p.flags & torrent_flags::override_trackers)) + { + auto const& trackers = m_torrent_file->trackers(); + m_trackers = {trackers.begin(), trackers.end()}; + } + + int tier = 0; + auto tier_iter = p.tracker_tiers.begin(); + for (auto const& url : p.trackers) + { + announce_entry e(url); + if (tier_iter != p.tracker_tiers.end()) + tier = *tier_iter++; + + e.fail_limit = 0; + e.source = announce_entry::source_magnet_link; + e.tier = std::uint8_t(tier); + if (!find_tracker(e.url)) + { + if (e.url.empty()) continue; + m_trackers.push_back(e); + // add the tracker to the m_torrent_file here so that the trackers + // will be preserved via create_torrent() when passing in just the + // torrent_info object. + if (!m_torrent_file->is_valid()) + m_torrent_file->add_tracker(e.url, e.tier, announce_entry::tracker_source(e.source)); + } + } + + std::sort(m_trackers.begin(), m_trackers.end() + , [] (announce_entry const& lhs, announce_entry const& rhs) + { return lhs.tier < rhs.tier; }); + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + + // --- MERKLE TREE --- + + if (m_torrent_file->is_valid() + && m_torrent_file->is_merkle_torrent()) + { + if (p.merkle_tree.size() == m_torrent_file->merkle_tree().size()) + { + // TODO: 2 set_merkle_tree should probably take the vector as && + std::vector tree(p.merkle_tree); + m_torrent_file->set_merkle_tree(tree); + } + else + { + // TODO: 0 if this is a merkle torrent and we can't + // restore the tree, we need to wipe all the + // bits in the have array, but not necessarily + // we might want to do a full check to see if we have + // all the pieces. This is low priority since almost + // no one uses merkle torrents + TORRENT_ASSERT_FAIL(); + } + } + + if (m_torrent_file->is_valid()) + { + // setting file- or piece priorities for seed mode makes no sense. If a + // torrent ends up in seed mode by accident, it can be very confusing, + // so assume the seed mode flag is not intended and don't enable it in + // that case. Also, if the resume data says we're missing a piece, we + // can't be in seed-mode. + m_seed_mode = (p.flags & torrent_flags::seed_mode) + && std::find(p.file_priorities.begin(), p.file_priorities.end(), dont_download) == p.file_priorities.end() + && std::find(p.piece_priorities.begin(), p.piece_priorities.end(), dont_download) == p.piece_priorities.end() + && std::find(p.have_pieces.begin(), p.have_pieces.end(), false) == p.have_pieces.end(); + + m_connections_initialized = true; + } + else + { + if (!p.name.empty()) m_name.reset(new std::string(p.name)); + } + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + if (!m_url.empty() && m_uuid.empty()) m_uuid = m_url; +#endif + + TORRENT_ASSERT(is_single_thread()); + m_file_priority.assign(p.file_priorities.begin(), p.file_priorities.end()); + + if (m_seed_mode) + { + m_verified.resize(m_torrent_file->num_pieces(), false); + m_verifying.resize(m_torrent_file->num_pieces(), false); + } + + m_total_uploaded = p.total_uploaded; + m_total_downloaded = p.total_downloaded; + + // the number of seconds this torrent has spent in started, finished and + // seeding state so far, respectively. + m_active_time = seconds(p.active_time); + m_finished_time = seconds(p.finished_time); + m_seeding_time = seconds(p.seeding_time); + + if (m_completed_time != 0 && m_completed_time < m_added_time) + m_completed_time = m_added_time; + +#if TORRENT_ABI_VERSION == 1 + if (!m_name && !m_url.empty()) m_name.reset(new std::string(m_url)); +#endif + + if (valid_metadata()) + { + inc_stats_counter(counters::num_total_pieces_added + , m_torrent_file->num_pieces()); + } + } + + void torrent::inc_stats_counter(int c, int value) + { m_ses.stats_counters().inc_stats_counter(c, value); } + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + void torrent::on_torrent_download(error_code const& ec + , http_parser const& parser, span data) try + { + if (m_abort) return; + + if (ec && ec != boost::asio::error::eof) + { + set_error(ec, torrent_status::error_file_url); + pause(); + return; + } + + if (parser.status_code() != 200) + { + set_error(error_code(parser.status_code(), http_category()), torrent_status::error_file_url); + pause(); + return; + } + + error_code e; + auto tf = std::make_shared(data, std::ref(e), from_span); + if (e) + { + set_error(e, torrent_status::error_file_url); + pause(); + return; + } + + // update our torrent_info object and move the + // torrent from the old info-hash to the new one + // as we replace the torrent_info object + // we're about to erase the session's reference to this + // torrent, create another reference + auto me = shared_from_this(); + + m_ses.remove_torrent_impl(me, {}); + + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), info_hash(), tf->info_hash()); + + m_torrent_file = tf; + m_info_hash = tf->info_hash(); + + // now, we might already have this torrent in the session. + std::shared_ptr t = m_ses.find_torrent(m_torrent_file->info_hash()).lock(); + if (t) + { + if (!m_uuid.empty() && t->uuid().empty()) + t->set_uuid(m_uuid); + if (!m_url.empty() && t->url().empty()) + t->set_url(m_url); + + // insert this torrent in the uuid index + if (!m_uuid.empty() || !m_url.empty()) + { + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, t); + } + + // TODO: if the existing torrent doesn't have metadata, insert + // the metadata we just downloaded into it. + + set_error(errors::duplicate_torrent, torrent_status::error_file_url); + abort(); + return; + } + + m_ses.insert_torrent(m_torrent_file->info_hash(), me, m_uuid); + + // if the user added any trackers while downloading the + // .torrent file, merge them into the new tracker list + std::vector new_trackers = m_torrent_file->trackers(); + for (auto const& tr : m_trackers) + { + // if we already have this tracker, ignore it + if (std::any_of(new_trackers.begin(), new_trackers.end() + , [&tr] (announce_entry const& ae) { return ae.url == tr.url; })) + continue; + + // insert the tracker ordered by tier + new_trackers.insert(std::find_if(new_trackers.begin(), new_trackers.end() + , [&tr] (announce_entry const& ae) { return ae.tier >= tr.tier; }), tr); + } + m_trackers.swap(new_trackers); + + // add the web seeds from the .torrent file + std::vector const& web_seeds = m_torrent_file->web_seeds(); + std::vector ws(web_seeds.begin(), web_seeds.end()); + aux::random_shuffle(ws); + for (auto& w : ws) m_web_seeds.push_back(std::move(w)); + +#if !defined TORRENT_DISABLE_ENCRYPTION + static char const req2[4] = {'r', 'e', 'q', '2'}; + hasher h(req2); + h.update(m_torrent_file->info_hash()); + m_ses.add_obfuscated_hash(h.final(), shared_from_this()); +#endif + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle()); + } + + state_updated(); + + set_state(torrent_status::downloading); + + init(); + } + catch (...) { handle_exception(); } + +#endif // TORRENT_ABI_VERSION + + int torrent::current_stats_state() const + { + if (m_abort || !m_added) + return counters::num_checking_torrents + no_gauge_state; + + if (has_error()) return counters::num_error_torrents; + if (m_paused || m_graceful_pause_mode) + { + if (!is_auto_managed()) return counters::num_stopped_torrents; + if (is_seed()) return counters::num_queued_seeding_torrents; + return counters::num_queued_download_torrents; + } + if (state() == torrent_status::checking_files +#if TORRENT_ABI_VERSION == 1 + || state() == torrent_status::queued_for_checking +#endif + ) + return counters::num_checking_torrents; + else if (is_seed()) return counters::num_seeding_torrents; + else if (is_upload_only()) return counters::num_upload_only_torrents; + return counters::num_downloading_torrents; + } + + void torrent::update_gauge() + { + int const new_gauge_state = current_stats_state() - counters::num_checking_torrents; + TORRENT_ASSERT(new_gauge_state >= 0); + TORRENT_ASSERT(new_gauge_state <= no_gauge_state); + + if (new_gauge_state == int(m_current_gauge_state)) return; + + if (m_current_gauge_state != no_gauge_state) + inc_stats_counter(m_current_gauge_state + counters::num_checking_torrents, -1); + if (new_gauge_state != no_gauge_state) + inc_stats_counter(new_gauge_state + counters::num_checking_torrents, 1); + + TORRENT_ASSERT(new_gauge_state >= 0); + TORRENT_ASSERT(new_gauge_state <= no_gauge_state); + m_current_gauge_state = static_cast(new_gauge_state); + } + + void torrent::leave_seed_mode(seed_mode_t const checking) + { + if (!m_seed_mode) return; + + if (checking == seed_mode_t::check_files) + { + // this means the user promised we had all the + // files, but it turned out we didn't. This is + // an error. + + // TODO: 2 post alert + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** FAILED SEED MODE, rechecking"); +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** LEAVING SEED MODE (%s)" + , checking == seed_mode_t::skip_checking ? "as seed" : "as non-seed"); +#endif + m_seed_mode = false; + // seed is false if we turned out not + // to be a seed after all + if (checking == seed_mode_t::check_files + && state() != torrent_status::checking_resume_data) + { + m_have_all = false; + set_state(torrent_status::downloading); + force_recheck(); + } + m_num_verified = 0; + m_verified.clear(); + m_verifying.clear(); + + set_need_save_resume(); + } + + void torrent::verified(piece_index_t const piece) + { + TORRENT_ASSERT(!m_verified.get_bit(piece)); + ++m_num_verified; + m_verified.set_bit(piece); + } + + void torrent::start() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_was_started == false); +#if TORRENT_USE_ASSERTS + m_was_started = true; +#endif + + // Some of these calls may log to the torrent debug log, which requires a + // call to get_handle(), which requires the torrent object to be fully + // constructed, as it relies on get_shared_from_this() + if (m_add_torrent_params) + { +#if TORRENT_ABI_VERSION == 1 + if (m_add_torrent_params->internal_resume_data_error + && m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , m_add_torrent_params->internal_resume_data_error, "" + , operation_t::unknown); + } +#endif + + add_torrent_params const& p = *m_add_torrent_params; + + set_max_uploads(p.max_uploads, false); + set_max_connections(p.max_connections, false); + set_limit_impl(p.upload_limit, peer_connection::upload_channel, false); + set_limit_impl(p.download_limit, peer_connection::download_channel, false); + + for (auto const& peer : p.peers) + { + add_peer(peer, peer_info::resume_data); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && !p.peers.empty()) + { + std::string str; + for (auto const& peer : p.peers) + { + error_code ec; + str += peer.address().to_string(ec); + str += ' '; + } + debug_log("add_torrent add_peer() [ %s] connect-candidates: %d" + , str.c_str(), m_peer_list + ? m_peer_list->num_connect_candidates() : -1); + } +#endif + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("creating torrent: %s max-uploads: %d max-connections: %d " + "upload-limit: %d download-limit: %d flags: %s%s%s%s%s%s%s%s%s%s%s " + "save-path: %s" + , torrent_file().name().c_str() + , int(m_max_uploads) + , int(m_max_connections) + , upload_limit() + , download_limit() + , m_seed_mode ? "seed-mode " : "" + , m_upload_mode ? "upload-mode " : "" +#ifndef TORRENT_DISABLE_SHARE_MODE + , m_share_mode ? "share-mode " : "" +#else + , "" +#endif + , m_apply_ip_filter ? "apply-ip-filter " : "" + , m_paused ? "paused " : "" + , m_auto_managed ? "auto-managed " : "" + , m_state_subscription ? "update-subscribe " : "" +#ifndef TORRENT_DISABLE_SUPERSEEDING + , m_super_seeding ? "super-seeding " : "" +#else + , "" +#endif + , m_sequential_download ? "sequential-download " : "" + , (m_add_torrent_params && m_add_torrent_params->flags & torrent_flags::override_trackers) + ? "override-trackers " : "" + , (m_add_torrent_params && m_add_torrent_params->flags & torrent_flags::override_web_seeds) + ? "override-web-seeds " : "" + , m_save_path.c_str() + ); + } +#endif + + update_gauge(); + + update_want_peers(); + update_want_scrape(); + update_want_tick(); + update_state_list(); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + if (!m_torrent_file->is_valid() && !m_url.empty()) + { + // we need to download the .torrent file from m_url + start_download_url(); + } + else +#endif + if (m_torrent_file->is_valid()) + { + init(); + } + else + { + // we need to start announcing since we don't have any + // metadata. To receive peers to ask for it. + set_state(torrent_status::downloading_metadata); + start_announcing(); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_invariant(); +#endif + } + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + void torrent::start_download_url() + { + TORRENT_ASSERT(!m_url.empty()); + TORRENT_ASSERT(!m_torrent_file->is_valid()); + std::shared_ptr conn( + new http_connection(m_ses.get_io_service() + , m_ses.get_resolver() + , std::bind(&torrent::on_torrent_download, shared_from_this() + , _1, _2, _3) + , true // bottled + //bottled buffer size + , settings().get_int(settings_pack::max_http_recv_buffer_size) + , http_connect_handler() + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx.get() +#endif + )); + aux::proxy_settings ps = m_ses.proxy(); + conn->get(m_url, seconds(30), 0, &ps + , 5 + , settings().get_bool(settings_pack::anonymous_mode) + ? "" : settings().get_str(settings_pack::user_agent)); + set_state(torrent_status::downloading_metadata); + } +#endif + + void torrent::set_apply_ip_filter(bool b) + { + if (b == m_apply_ip_filter) return; + if (b) + { + inc_stats_counter(counters::non_filter_torrents, -1); + } + else + { + inc_stats_counter(counters::non_filter_torrents); + } + m_apply_ip_filter = b; + ip_filter_updated(); + state_updated(); + } + + void torrent::set_ip_filter(std::shared_ptr ipf) + { + m_ip_filter = std::move(ipf); + if (!m_apply_ip_filter) return; + ip_filter_updated(); + } + +#ifndef TORRENT_DISABLE_DHT + bool torrent::should_announce_dht() const + { + TORRENT_ASSERT(is_single_thread()); + if (!m_enable_dht) return false; + if (!m_ses.announce_dht()) return false; + + if (!m_ses.dht()) return false; + if (m_torrent_file->is_valid() && !m_files_checked) return false; + if (!m_announce_to_dht) return false; + if (m_paused) return false; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // if we don't have the metadata, and we're waiting + // for a web server to serve it to us, no need to announce + // because the info-hash is just the URL hash + if (!m_torrent_file->is_valid() && !m_url.empty()) return false; +#endif + + // don't announce private torrents + if (m_torrent_file->is_valid() && m_torrent_file->priv()) return false; + if (m_trackers.empty()) return true; + if (!settings().get_bool(settings_pack::use_dht_as_fallback)) return true; + + return std::none_of(m_trackers.begin(), m_trackers.end() + , [](announce_entry const& tr) { return bool(tr.verified); }); + } + +#endif + + torrent::~torrent() + { + // TODO: 3 assert there are no outstanding async operations on this + // torrent + +#if TORRENT_USE_ASSERTS + for (torrent_list_index_t i{}; i != m_links.end_index(); ++i) + { + if (!m_links[i].in_list()) continue; + m_links[i].unlink(m_ses.torrent_list(i), i); + } +#endif + + // The invariant can't be maintained here, since the torrent + // is being destructed, all weak references to it have been + // reset, which means that all its peers already have an + // invalidated torrent pointer (so it cannot be verified to be correct) + + // i.e. the invariant can only be maintained if all connections have + // been closed by the time the torrent is destructed. And they are + // supposed to be closed. So we can still do the invariant check. + + // however, the torrent object may be destructed from the main + // thread when shutting down, if the disk cache has references to it. + // this means that the invariant check that this is called from the + // network thread cannot be maintained + + TORRENT_ASSERT(m_peer_class == peer_class_t{0}); + TORRENT_ASSERT(m_connections.empty()); + // just in case, make sure the session accounting is kept right + for (auto p : m_connections) + m_ses.close_connection(p); + } + + void torrent::read_piece(piece_index_t const piece) + { + error_code ec; + if (m_abort || m_deleted) + { + ec.assign(boost::system::errc::operation_canceled, generic_category()); + } + else if (!valid_metadata()) + { + ec.assign(errors::no_metadata, libtorrent_category()); + } + else if (piece < piece_index_t{0} || piece >= m_torrent_file->end_piece()) + { + ec.assign(errors::invalid_piece_index, libtorrent_category()); + } + + if (ec) + { + m_ses.alerts().emplace_alert(get_handle(), piece, ec); + return; + } + + const int piece_size = m_torrent_file->piece_size(piece); + const int blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + + TORRENT_ASSERT(blocks_in_piece > 0); + TORRENT_ASSERT(piece_size > 0); + + if (blocks_in_piece == 0) + { + // this shouldn't actually happen + boost::shared_array buf; + m_ses.alerts().emplace_alert( + get_handle(), piece, buf, 0); + return; + } + + std::shared_ptr rp = std::make_shared(); + rp->piece_data.reset(new (std::nothrow) char[std::size_t(piece_size)]); + if (!rp->piece_data) + { + m_ses.alerts().emplace_alert( + get_handle(), piece, error_code(boost::system::errc::not_enough_memory, generic_category())); + return; + } + rp->blocks_left = blocks_in_piece; + rp->fail = false; + + peer_request r; + r.piece = piece; + r.start = 0; + for (int i = 0; i < blocks_in_piece; ++i, r.start += block_size()) + { + r.length = std::min(piece_size - r.start, block_size()); + m_ses.disk_thread().async_read(m_storage, r + , std::bind(&torrent::on_disk_read_complete + , shared_from_this(), _1, _2, _3, r, rp)); + } + m_ses.disk_thread().submit_jobs(); + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void torrent::send_share_mode() + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const pc : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + if (pc->type() != connection_type::bittorrent) continue; + auto* p = static_cast(pc); + p->write_share_mode(); + } +#endif + } +#endif // TORRENT_DISABLE_SHARE_MODE + + void torrent::send_upload_only() + { +#ifndef TORRENT_DISABLE_EXTENSIONS + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (share_mode()) return; +#endif +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (super_seeding()) return; +#endif + + // if we send upload-only, the other end is very likely to disconnect + // us, at least if it's a seed. If we don't want to close redundant + // connections, don't sent upload-only + if (!settings().get_bool(settings_pack::close_redundant_connections)) return; + + // if we're super seeding, we don't want to make peers + // think that we only have a single piece and is upload + // only, since they might disconnect immediately when + // they have downloaded a single piece, although we'll + // make another piece available + bool const upload_only_enabled = is_upload_only() +#ifndef TORRENT_DISABLE_SUPERSEEDING + && !super_seeding() +#endif + ; + + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + + p->send_not_interested(); + p->send_upload_only(upload_only_enabled); + } +#endif // TORRENT_DISABLE_EXTENSIONS + } + + torrent_flags_t torrent::flags() const + { + torrent_flags_t ret = torrent_flags_t{}; + if (m_seed_mode) + ret |= torrent_flags::seed_mode; + if (m_upload_mode) + ret |= torrent_flags::upload_mode; +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_share_mode) + ret |= torrent_flags::share_mode; +#endif + if (m_apply_ip_filter) + ret |= torrent_flags::apply_ip_filter; + if (is_torrent_paused()) + ret |= torrent_flags::paused; + if (m_auto_managed) + ret |= torrent_flags::auto_managed; +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (m_super_seeding) + ret |= torrent_flags::super_seeding; +#endif + if (m_sequential_download) + ret |= torrent_flags::sequential_download; + if (m_stop_when_ready) + ret |= torrent_flags::stop_when_ready; + if (!m_enable_dht) + ret |= torrent_flags::disable_dht; + if (!m_enable_lsd) + ret |= torrent_flags::disable_lsd; + if (!m_enable_pex) + ret |= torrent_flags::disable_pex; + return ret; + } + + void torrent::set_flags(torrent_flags_t const flags + , torrent_flags_t const mask) + { + if ((mask & torrent_flags::seed_mode) + && !(flags & torrent_flags::seed_mode)) + { + leave_seed_mode(seed_mode_t::check_files); + } + if (mask & torrent_flags::upload_mode) + set_upload_mode(bool(flags & torrent_flags::upload_mode)); +#ifndef TORRENT_DISABLE_SHARE_MODE + if (mask & torrent_flags::share_mode) + set_share_mode(bool(flags & torrent_flags::share_mode)); +#endif + if (mask & torrent_flags::apply_ip_filter) + set_apply_ip_filter(bool(flags & torrent_flags::apply_ip_filter)); + if (mask & torrent_flags::paused) + { + if (flags & torrent_flags::paused) + pause(torrent_handle::graceful_pause); + else + resume(); + } + if (mask & torrent_flags::auto_managed) + auto_managed(bool(flags & torrent_flags::auto_managed)); +#ifndef TORRENT_DISABLE_SUPERSEEDING + if (mask & torrent_flags::super_seeding) + set_super_seeding(bool(flags & torrent_flags::super_seeding)); +#endif + if (mask & torrent_flags::sequential_download) + set_sequential_download(bool(flags & torrent_flags::sequential_download)); + if (mask & torrent_flags::stop_when_ready) + stop_when_ready(bool(flags & torrent_flags::stop_when_ready)); + if (mask & torrent_flags::disable_dht) + m_enable_dht = !bool(flags & torrent_flags::disable_dht); + if (mask & torrent_flags::disable_lsd) + m_enable_lsd = !bool(flags & torrent_flags::disable_lsd); + if (mask & torrent_flags::disable_pex) + m_enable_pex = !bool(flags & torrent_flags::disable_pex); + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void torrent::set_share_mode(bool s) + { + if (s == m_share_mode) return; + + m_share_mode = s; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-share-mode: %d", s); +#endif + if (m_share_mode) + { + std::size_t const num_files = valid_metadata() + ? std::size_t(m_torrent_file->num_files()) + : m_file_priority.size(); + // in share mode, all pieces have their priorities initialized to + // dont_download + prioritize_files(aux::vector(num_files, dont_download)); + } + } +#endif // TORRENT_DISABLE_SHARE_MODE + + void torrent::set_upload_mode(bool b) + { + if (b == m_upload_mode) return; + + m_upload_mode = b; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-upload-mode: %d", b); +#endif + + update_gauge(); + state_updated(); + send_upload_only(); + + if (m_upload_mode) + { + // clear request queues of all peers + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + // we may want to disconnect other upload-only peers + if (p->upload_only()) + p->update_interest(); + p->cancel_all_requests(); + } + // this is used to try leaving upload only mode periodically + m_upload_mode_time = aux::time_now32(); + } + else if (m_peer_list) + { + // reset last_connected, to force fast reconnect after leaving upload mode + for (auto pe : *m_peer_list) + { + pe->last_connected = 0; + } + + // send_block_requests on all peers + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + // we may be interested now, or no longer interested + p->update_interest(); + p->send_block_requests(); + } + } + } + + void torrent::need_peer_list() + { + if (m_peer_list) return; + m_peer_list.reset(new peer_list(m_ses.get_peer_allocator())); + } + + void torrent::handle_exception() + { + try + { + throw; + } + catch (system_error const& err) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("torrent exception: (%d) %s: %s" + , err.code().value(), err.code().message().c_str() + , err.what()); + } +#endif + set_error(err.code(), torrent_status::error_file_exception); + } + catch (std::exception const& err) + { + TORRENT_UNUSED(err); + set_error(error_code(), torrent_status::error_file_exception); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("torrent exception: %s", err.what()); + } +#endif + } + catch (...) + { + set_error(error_code(), torrent_status::error_file_exception); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("torrent exception: unknown"); + } +#endif + } + } + + void torrent::handle_disk_error(string_view job_name + , storage_error const& error + , peer_connection* c + , disk_class rw) + { + TORRENT_UNUSED(job_name); + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(error); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("disk error: (%d) %s [%*s : %s] in file: %s" + , error.ec.value(), error.ec.message().c_str() + , int(job_name.size()), job_name.data() + , operation_name(error.operation) + , resolve_filename(error.file()).c_str()); + } +#endif + + if (error.ec == boost::system::errc::not_enough_memory) + { + if (alerts().should_post()) + alerts().emplace_alert(error.ec + , resolve_filename(error.file()), error.operation, get_handle()); + if (c) c->disconnect(errors::no_memory, error.operation); + return; + } + + if (error.ec == boost::asio::error::operation_aborted) return; + + // notify the user of the error + if (alerts().should_post()) + alerts().emplace_alert(error.ec + , resolve_filename(error.file()), error.operation, get_handle()); + + // if a write operation failed, and future writes are likely to + // fail, while reads may succeed, just set the torrent to upload mode + // if we make an incorrect assumption here, it's not the end of the + // world, if we ever issue a read request and it fails as well, we + // won't get in here and we'll actually end up pausing the torrent + if (rw == disk_class::write + && (error.ec == boost::system::errc::read_only_file_system + || error.ec == boost::system::errc::permission_denied + || error.ec == boost::system::errc::operation_not_permitted + || error.ec == boost::system::errc::no_space_on_device + || error.ec == boost::system::errc::file_too_large)) + { + // if we failed to write, stop downloading and just + // keep seeding. + // TODO: 1 make this depend on the error and on the filesystem the + // files are being downloaded to. If the error is no_space_left_on_device + // and the filesystem doesn't support sparse files, only zero the priorities + // of the pieces that are at the tails of all files, leaving everything + // up to the highest written piece in each file + set_upload_mode(true); + return; + } + + // put the torrent in an error-state + set_error(error.ec, error.file()); + + // if the error appears to be more serious than a full disk, just pause the torrent + pause(); + } + + void torrent::on_piece_fail_sync(piece_index_t, piece_block) try + { + if (m_abort) return; + + update_gauge(); + // some peers that previously was no longer interesting may + // now have become interesting, since we lack this one piece now. + for (auto i = begin(); i != end();) + { + peer_connection* p = *i; + // update_interest may disconnect the peer and + // invalidate the iterator + ++i; + // no need to do anything with peers that + // already are interested. Gaining a piece may + // only make uninteresting peers interesting again. + if (p->is_interesting()) continue; + p->update_interest(); + if (!m_abort) + { + if (request_a_block(*this, *p)) + inc_stats_counter(counters::hash_fail_piece_picks); + p->send_block_requests(); + } + } + } + catch (...) { handle_exception(); } + + void torrent::on_disk_read_complete(disk_buffer_holder buffer + , disk_job_flags_t, storage_error const& se + , peer_request const& r, std::shared_ptr rp) try + { + // hold a reference until this function returns + TORRENT_ASSERT(is_single_thread()); + + --rp->blocks_left; + if (se) + { + rp->fail = true; + rp->error = se.ec; + handle_disk_error("read", se); + } + else + { + std::memcpy(rp->piece_data.get() + r.start, buffer.get(), aux::numeric_cast(r.length)); + } + + if (rp->blocks_left == 0) + { + int size = m_torrent_file->piece_size(r.piece); + if (rp->fail) + { + m_ses.alerts().emplace_alert( + get_handle(), r.piece, rp->error); + } + else + { + m_ses.alerts().emplace_alert( + get_handle(), r.piece, rp->piece_data, size); + } + } + } + catch (...) { handle_exception(); } + + storage_mode_t torrent::storage_mode() const + { return storage_mode_t(m_storage_mode); } + + void torrent::clear_peers() + { + disconnect_all(error_code(), operation_t::unknown); + if (m_peer_list) m_peer_list->clear(); + } + + storage_interface* torrent::get_storage_impl() const + { + return m_ses.disk_thread().get_torrent(m_storage); + } + + void torrent::need_picker() + { + if (m_picker) return; + + TORRENT_ASSERT(valid_metadata()); + TORRENT_ASSERT(m_connections_initialized); + + INVARIANT_CHECK; + + // if we have all pieces we should not have a picker + // unless we're in suggest mode + TORRENT_ASSERT(!m_have_all + || settings().get_int(settings_pack::suggest_mode) + == settings_pack::suggest_read_cache); + + int const blocks_per_piece + = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); + int const blocks_in_last_piece + = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) + + block_size() - 1) / block_size(); + + std::unique_ptr pp(new piece_picker(blocks_per_piece + , blocks_in_last_piece + , m_torrent_file->num_pieces())); + + if (m_have_all) pp->we_have_all(); + + // initialize the file progress too + if (m_file_progress.empty()) + m_file_progress.init(*pp, m_torrent_file->files()); + + m_picker = std::move(pp); + + update_gauge(); + + for (auto const p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + if (p->is_disconnecting()) continue; + peer_has(p->get_bitfield(), p); + } + } + + struct piece_refcount + { + piece_refcount(piece_picker& p, piece_index_t piece) + : m_picker(p) + , m_piece(piece) + { + m_picker.inc_refcount(m_piece, nullptr); + } + + piece_refcount(piece_refcount const&) = delete; + piece_refcount& operator=(piece_refcount const&) = delete; + + ~piece_refcount() + { + m_picker.dec_refcount(m_piece, nullptr); + } + + private: + piece_picker& m_picker; + piece_index_t m_piece; + }; + + // TODO: 3 there's some duplication between this function and + // peer_connection::incoming_piece(). is there a way to merge something? + void torrent::add_piece(piece_index_t const piece, char const* data + , add_piece_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + int const piece_size = m_torrent_file->piece_size(piece); + int const blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + + if (m_deleted) return; + + // avoid crash trying to access the picker when there is none + if (m_have_all && !has_picker()) return; + + // we don't support clobbering the piece picker while checking the + // files. We may end up having the same piece multiple times + TORRENT_ASSERT_PRECOND(state() != torrent_status::checking_files + && state() != torrent_status::checking_resume_data); + if (state() == torrent_status::checking_files + || state() == torrent_status::checking_resume_data) + return; + + need_picker(); + + if (picker().have_piece(piece) + && !(flags & torrent_handle::overwrite_existing)) + return; + + peer_request p; + p.piece = piece; + p.start = 0; + piece_refcount refcount{picker(), piece}; + for (int i = 0; i < blocks_in_piece; ++i, p.start += block_size()) + { + piece_block const block(piece, i); + if (!(flags & torrent_handle::overwrite_existing) + && picker().is_finished(block)) + continue; + + p.length = std::min(piece_size - p.start, block_size()); + + m_stats_counters.inc_stats_counter(counters::queued_write_bytes, p.length); + m_ses.disk_thread().async_write(m_storage, p, data + p.start, nullptr + , std::bind(&torrent::on_disk_write_complete + , shared_from_this(), _1, p)); + + bool const was_finished = picker().is_piece_finished(p.piece); + bool const multi = picker().num_peers(block) > 1; + + picker().mark_as_downloading(block, nullptr); + picker().mark_as_writing(block, nullptr); + + if (multi) cancel_block(block); + + // did we just finish the piece? + // this means all blocks are either written + // to disk or are in the disk write cache + if (picker().is_piece_finished(p.piece) && !was_finished) + { + verify_piece(p.piece); + } + } + } + + void torrent::on_disk_write_complete(storage_error const& error + , peer_request const& p) try + { + TORRENT_ASSERT(is_single_thread()); + + m_stats_counters.inc_stats_counter(counters::queued_write_bytes, -p.length); + +// std::fprintf(stderr, "torrent::on_disk_write_complete ret:%d piece:%d block:%d\n" +// , j->ret, j->piece, j->offset/0x4000); + + INVARIANT_CHECK; + if (m_abort) return; + piece_block const block_finished(p.piece, p.start / block_size()); + + if (error) + { + handle_disk_error("write", error); + return; + } + + if (!has_picker()) return; + + // if we already have this block, just ignore it. + // this can happen if the same block is passed in through + // add_piece() multiple times + if (picker().is_finished(block_finished)) return; + + picker().mark_as_finished(block_finished, nullptr); + maybe_done_flushing(); + + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle(), + tcp::endpoint(), peer_id(), block_finished.block_index + , block_finished.piece_index); + } + } + catch (...) { handle_exception(); } + + bool torrent::add_merkle_nodes(std::map const& nodes + , piece_index_t const piece) + { + return m_torrent_file->add_merkle_nodes(nodes, piece); + } + + peer_request torrent::to_req(piece_block const& p) const + { + int block_offset = p.block_index * block_size(); + int block = std::min(torrent_file().piece_size( + p.piece_index) - block_offset, block_size()); + TORRENT_ASSERT(block > 0); + TORRENT_ASSERT(block <= block_size()); + + peer_request r; + r.piece = p.piece_index; + r.start = block_offset; + r.length = block; + return r; + } + + std::string torrent::name() const + { + if (valid_metadata()) return m_torrent_file->name(); + if (m_name) return *m_name; + return ""; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + + void torrent::add_extension(std::shared_ptr ext) + { + m_extensions.push_back(ext); + } + + void torrent::remove_extension(std::shared_ptr ext) + { + auto const i = std::find(m_extensions.begin(), m_extensions.end(), ext); + if (i == m_extensions.end()) return; + m_extensions.erase(i); + } + + void torrent::add_extension_fun(std::function(torrent_handle const&, void*)> const& ext + , void* userdata) + { + std::shared_ptr tp(ext(get_handle(), userdata)); + if (!tp) return; + + add_extension(tp); + + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + std::shared_ptr pp(tp->new_connection(peer_connection_handle(p->self()))); + if (pp) p->add_extension(std::move(pp)); + } + + // if files are checked for this torrent, call the extension + // to let it initialize itself + if (m_connections_initialized) + tp->on_files_checked(); + } + +#endif + +#ifdef TORRENT_USE_OPENSSL +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + + bool torrent::verify_peer_cert(bool const preverified, boost::asio::ssl::verify_context& ctx) + { + // if the cert wasn't signed by the correct CA, fail the verification + if (!preverified) return false; + + // we're only interested in checking the certificate at the end of the chain. + // TODO: is verify_peer_cert called once per certificate in the chain, and + // this function just tells us which depth we're at right now? If so, the comment + // makes sense. + // any certificate that isn't the leaf (i.e. the one presented by the peer) + // should be accepted automatically, given preverified is true. The leaf certificate + // need to be verified to make sure its DN matches the info-hash + int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle()); + if (depth > 0) return true; + + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + + // Go through the alternate names in the certificate looking for matching DNS entries + auto* gens = static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)); + +#ifndef TORRENT_DISABLE_LOGGING + std::string names; + bool match = false; +#endif + for (int i = 0; i < aux::openssl_num_general_names(gens); ++i) + { + GENERAL_NAME* gen = aux::openssl_general_name_value(gens, i); + if (gen->type != GEN_DNS) continue; + ASN1_IA5STRING* domain = gen->d.dNSName; + if (domain->type != V_ASN1_IA5STRING || !domain->data || !domain->length) continue; + auto const* torrent_name = reinterpret_cast(domain->data); + std::size_t const name_length = aux::numeric_cast(domain->length); + +#ifndef TORRENT_DISABLE_LOGGING + if (i > 1) names += " | n: "; + names.append(torrent_name, name_length); +#endif + if (std::strncmp(torrent_name, "*", name_length) == 0 + || std::strncmp(torrent_name, m_torrent_file->name().c_str(), name_length) == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + match = true; + // if we're logging, keep looping over all names, + // for completeness of the log + continue; +#else + return true; +#endif + } + } + + // no match in the alternate names, so try the common names. We should only + // use the "most specific" common name, which is the last one in the list. + X509_NAME* name = X509_get_subject_name(cert); + int i = -1; + ASN1_STRING* common_name = nullptr; + while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) + { + X509_NAME_ENTRY* name_entry = X509_NAME_get_entry(name, i); + common_name = X509_NAME_ENTRY_get_data(name_entry); + } + if (common_name && common_name->data && common_name->length) + { + auto const* torrent_name = reinterpret_cast(common_name->data); + std::size_t const name_length = aux::numeric_cast(common_name->length); + +#ifndef TORRENT_DISABLE_LOGGING + if (!names.empty()) names += " | n: "; + names.append(torrent_name, name_length); +#endif + + if (std::strncmp(torrent_name, "*", name_length) == 0 + || std::strncmp(torrent_name, m_torrent_file->name().c_str(), name_length) == 0) + { +#ifdef TORRENT_DISABLE_LOGGING + return true; +#else + match = true; +#endif + } + } + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("<== incoming SSL CONNECTION [ n: %s | match: %s ]" + , names.c_str(), match?"yes":"no"); + return match; +#else + return false; +#endif + } + + void torrent::init_ssl(string_view cert) + { + using boost::asio::ssl::context; + + // this is needed for openssl < 1.0 to decrypt keys created by openssl 1.0+ +#if !defined(OPENSSL_API_COMPAT) || (OPENSSL_API_COMPAT < 0x10100000L) + OpenSSL_add_all_algorithms(); +#else + OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS, nullptr); +#endif + + // create the SSL context for this torrent. We need to + // inject the root certificate, and no other, to + // verify other peers against +#if BOOST_VERSION >= 106400 + std::unique_ptr ctx(new context(context::tls)); +#else + std::unique_ptr ctx(new context(context::tlsv12)); +#endif + + if (!ctx) + { + error_code ec(int(::ERR_get_error()), + boost::asio::error::get_ssl_category()); + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + ctx->set_options(context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::single_dh_use); + + error_code ec; + ctx->set_verify_mode(context::verify_peer + | context::verify_fail_if_no_peer_cert + | context::verify_client_once, ec); + if (ec) + { + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + // the verification function verifies the distinguished name + // of a peer certificate to make sure it matches the info-hash + // of the torrent, or that it's a "star-cert" + ctx->set_verify_callback(std::bind(&torrent::verify_peer_cert, this, _1, _2), ec); + if (ec) + { + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + SSL_CTX* ssl_ctx = ctx->native_handle(); + // create a new x.509 certificate store + X509_STORE* cert_store = X509_STORE_new(); + if (!cert_store) + { + ec.assign(int(::ERR_get_error()), + boost::asio::error::get_ssl_category()); + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + // wrap the PEM certificate in a BIO, for openssl to read + BIO* bp = BIO_new_mem_buf( + const_cast(static_cast(cert.data())) + , int(cert.size())); + + // parse the certificate into OpenSSL's internal + // representation + X509* certificate = PEM_read_bio_X509_AUX(bp, nullptr, nullptr, nullptr); + + BIO_free(bp); + + if (!certificate) + { + ec.assign(int(::ERR_get_error()), + boost::asio::error::get_ssl_category()); + X509_STORE_free(cert_store); + set_error(ec, torrent_status::error_file_ssl_ctx); + pause(); + return; + } + + // add cert to cert_store + X509_STORE_add_cert(cert_store, certificate); + + X509_free(certificate); + + // and lastly, replace the default cert store with ours + SSL_CTX_set_cert_store(ssl_ctx, cert_store); +#if 0 + char filename[100]; + std::snprintf(filename, sizeof(filename), "/tmp/%u.pem", random()); + FILE* f = fopen(filename, "w+"); + fwrite(cert.c_str(), cert.size(), 1, f); + fclose(f); + ctx->load_verify_file(filename); +#endif + // if all went well, set the torrent ssl context to this one + m_ssl_ctx = std::move(ctx); + // tell the client we need a cert for this torrent + alerts().emplace_alert(get_handle()); + } +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif +#endif // TORRENT_OPENSSL + + void torrent::construct_storage() + { + storage_params params{ + m_torrent_file->orig_files(), + &m_torrent_file->orig_files() != &m_torrent_file->files() + ? &m_torrent_file->files() : nullptr, + m_save_path, + static_cast(m_storage_mode), + m_file_priority, + m_info_hash + }; + + TORRENT_ASSERT(m_storage_constructor); + + m_storage = m_ses.disk_thread().new_torrent(m_storage_constructor + , params, shared_from_this()); + } + + peer_connection* torrent::find_lowest_ranking_peer() const + { + auto lowest_rank = end(); + for (auto i = begin(); i != end(); ++i) + { + // disconnecting peers don't count + if ((*i)->is_disconnecting()) continue; + if (lowest_rank == end() || (*lowest_rank)->peer_rank() > (*i)->peer_rank()) + lowest_rank = i; + } + + if (lowest_rank == end()) return nullptr; + return *lowest_rank; + } + + // this may not be called from a constructor because of the call to + // shared_from_this(). It's either called when we start() the torrent, or at a + // later time if it's a magnet link, once the metadata is downloaded + void torrent::init() + { + INVARIANT_CHECK; + + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("init torrent: %s", torrent_file().name().c_str()); +#endif + + TORRENT_ASSERT(valid_metadata()); + TORRENT_ASSERT(m_torrent_file->num_files() > 0); + TORRENT_ASSERT(m_torrent_file->total_size() >= 0); + + if (int(m_file_priority.size()) > m_torrent_file->num_files()) + m_file_priority.resize(m_torrent_file->num_files()); + + auto cert = m_torrent_file->ssl_cert(); + if (!cert.empty()) + { + m_ssl_torrent = true; +#ifdef TORRENT_USE_OPENSSL + init_ssl(cert); +#endif + } + + if (m_torrent_file->num_pieces() > piece_picker::max_pieces) + { + set_error(errors::too_many_pieces_in_torrent, torrent_status::error_file_none); + pause(); + return; + } + + if (m_torrent_file->num_pieces() == 0) + { + set_error(errors::torrent_invalid_length, torrent_status::error_file_none); + pause(); + return; + } + + int const blocks_per_piece + = (m_torrent_file->piece_length() + default_block_size - 1) / default_block_size; + if (blocks_per_piece > piece_picker::max_blocks_per_piece) + { + set_error(errors::invalid_piece_size, torrent_status::error_file_none); + pause(); + return; + } + + // --- MAPPED FILES --- + file_storage const& fs = m_torrent_file->files(); + if (m_add_torrent_params) + { + for (auto const& f : m_add_torrent_params->renamed_files) + { + if (f.first < file_index_t(0) || f.first >= fs.end_file()) continue; + m_torrent_file->rename_file(file_index_t(f.first), f.second); + } + } + + construct_storage(); + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_share_mode && valid_metadata()) + { + // in share mode, all pieces have their priorities initialized to 0 + m_file_priority.clear(); + m_file_priority.resize(m_torrent_file->num_files(), dont_download); + } +#endif + + // it's important to initialize the peers early, because this is what will + // fix up their have-bitmasks to have the correct size + // TODO: 2 add a unit test where we don't have metadata, connect to a peer + // that sends a bitfield that's too large, then we get the metadata + if (!m_connections_initialized) + { + m_connections_initialized = true; + // all peer connections have to initialize themselves now that the metadata + // is available + // copy the peer list since peers may disconnect and invalidate + // m_connections as we initialize them + for (auto c : m_connections) + { + auto pc = c->self(); + if (pc->is_disconnecting()) continue; + pc->on_metadata_impl(); + if (pc->is_disconnecting()) continue; + pc->init(); + } + } + + // in case file priorities were passed in via the add_torrent_params + // and also in the case of share mode, we need to update the priorities + // this has to be applied before piece priority + if (!m_file_priority.empty()) update_piece_priorities(m_file_priority); + + if (m_add_torrent_params) + { + piece_index_t idx(0); + if (m_add_torrent_params->piece_priorities.size() > std::size_t(m_torrent_file->num_pieces())) + m_add_torrent_params->piece_priorities.resize(std::size_t(m_torrent_file->num_pieces())); + + for (auto prio : m_add_torrent_params->piece_priorities) + { + if (has_picker() || prio != default_priority) + { + need_picker(); + m_picker->set_piece_priority(idx, prio); + } + ++idx; + } + update_gauge(); + } + + if (m_seed_mode) + { + m_have_all = true; + update_gauge(); + update_state_list(); + update_want_tick(); + } + else + { + need_picker(); + + TORRENT_ASSERT(block_size() > 0); + + for (auto const i : fs.file_range()) + { + if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; + + peer_request pr = m_torrent_file->map_file(i, 0, int(fs.file_size(i))); + int off = pr.start & (block_size() - 1); + if (off != 0) { pr.length -= block_size() - off; pr.start += block_size() - off; } + TORRENT_ASSERT((pr.start & (block_size() - 1)) == 0); + + int block = block_size(); + piece_block pb(pr.piece, pr.start / block); + for (; pr.length >= block; pr.length -= block, ++pb.block_index) + { + if (pb.block_index == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } + m_picker->mark_as_pad(pb); + ++m_padding_blocks; + } + // ugly edge case where padfiles are not used they way they're + // supposed to be. i.e. added back-to back or at the end + if (pb.block_index == blocks_per_piece) { pb.block_index = 0; ++pb.piece_index; } + if (pr.length > 0 && ((next(i) != fs.end_file() && fs.pad_file_at(next(i))) + || next(i) == fs.end_file())) + { + m_picker->mark_as_finished(pb, nullptr); + } + } + + if (m_padding_blocks > 0) + { + // if we marked an entire piece as finished, we actually + // need to consider it finished + + std::vector dq + = m_picker->get_download_queue(); + + std::vector have_pieces; + + for (auto const& p : dq) + { + int const num_blocks = m_picker->blocks_in_piece(p.index); + if (p.finished < num_blocks) continue; + have_pieces.push_back(p.index); + } + + for (auto i : have_pieces) + { + picker().piece_passed(i); + TORRENT_ASSERT(picker().have_piece(i)); + we_have(i); + } + } + } + + set_state(torrent_status::checking_resume_data); + + aux::vector links; +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + if (!m_torrent_file->similar_torrents().empty() + || !m_torrent_file->collections().empty()) + { + resolve_links res(m_torrent_file); + + for (auto const& ih : m_torrent_file->similar_torrents()) + { + std::shared_ptr t = m_ses.find_torrent(ih).lock(); + if (!t) continue; + + // Only attempt to reuse files from torrents that are seeding. + // TODO: this could be optimized by looking up which files are + // complete and just look at those + if (!t->is_seed()) continue; + + res.match(t->get_torrent_copy(), t->save_path()); + } + for (auto const& c : m_torrent_file->collections()) + { + std::vector> ts = m_ses.find_collection(c); + + for (auto const& t : ts) + { + // Only attempt to reuse files from torrents that are seeding. + // TODO: this could be optimized by looking up which files are + // complete and just look at those + if (!t->is_seed()) continue; + + res.match(t->get_torrent_copy(), t->save_path()); + } + } + + std::vector const& l = res.get_links(); + if (!l.empty()) + { + for (auto const& i : l) + { + if (!i.ti) continue; + links.push_back(combine_path(i.save_path + , i.ti->files().file_path(i.file_idx))); + } + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_check_files == false); + m_outstanding_check_files = true; +#endif + m_ses.disk_thread().async_check_files( + m_storage, m_add_torrent_params ? m_add_torrent_params.get() : nullptr + , links, std::bind(&torrent::on_resume_data_checked + , shared_from_this(), _1, _2)); + // async_check_files will gut links +#ifndef TORRENT_DISABLE_LOGGING + debug_log("init, async_check_files"); +#endif + + update_want_peers(); + update_want_tick(); + + // this will remove the piece picker, if we're done with it + maybe_done_flushing(); + + m_torrent_initialized = true; + } + + bt_peer_connection* torrent::find_introducer(tcp::endpoint const& ep) const + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto pe : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + if (pe->type() != connection_type::bittorrent) continue; + auto* p = static_cast(pe); + if (!p->supports_holepunch()) continue; + if (p->was_introduced_by(ep)) return p; + } +#else + TORRENT_UNUSED(ep); +#endif + return nullptr; + } + + bt_peer_connection* torrent::find_peer(tcp::endpoint const& ep) const + { + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + if (p->type() != connection_type::bittorrent) continue; + if (p->remote() == ep) return static_cast(p); + } + return nullptr; + } + + peer_connection* torrent::find_peer(peer_id const& pid) + { + for (auto p : m_connections) + { + if (p->pid() == pid) return p; + } + return nullptr; + } + + bool torrent::is_self_connection(peer_id const& pid) const + { + return m_outgoing_pids.count(pid) > 0; + } + + void torrent::on_resume_data_checked(status_t const status + , storage_error const& error) try + { +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_check_files); + m_outstanding_check_files = false; +#endif + + // when applying some of the resume data to the torrent, we will + // trigger calls that set m_need_save_resume_data, even though we're + // just applying the state of the resume data we loaded with. We don't + // want anything in this function to affect the state of + // m_need_save_resume_data, so we save it in a local variable and reset + // it at the end of the function. + bool const need_save_resume_data = m_need_save_resume_data; + + TORRENT_ASSERT(is_single_thread()); + + if (m_abort) return; + + if (status == status_t::fatal_disk_error) + { + TORRENT_ASSERT(m_outstanding_check_files == false); + m_add_torrent_params.reset(); + handle_disk_error("check_resume_data", error); + auto_managed(false); + pause(); + set_state(torrent_status::checking_files); + if (should_check_files()) start_checking(); + return; + } + + state_updated(); + + if (m_add_torrent_params) + { + // --- PEERS --- + + for (auto const& p : m_add_torrent_params->peers) + { + add_peer(p , peer_info::resume_data); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && !m_add_torrent_params->peers.empty()) + { + error_code ec; + std::string str; + for (auto const& peer : m_add_torrent_params->peers) + { + str += peer.address().to_string(ec); + str += ' '; + } + debug_log("resume-checked add_peer() [ %s] connect-candidates: %d" + , str.c_str(), m_peer_list + ? m_peer_list->num_connect_candidates() : -1); + } +#endif + + for (auto const& p : m_add_torrent_params->banned_peers) + { + torrent_peer* peer = add_peer(p, peer_info::resume_data); + if (peer) ban_peer(peer); + } + + if (!m_add_torrent_params->peers.empty() + || !m_add_torrent_params->banned_peers.empty()) + { + update_want_peers(); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (m_peer_list && m_peer_list->num_peers() > 0) + debug_log("resume added peers (total peers: %d)" + , m_peer_list->num_peers()); +#endif + } + + // only report this error if the user actually provided resume data + // (i.e. m_add_torrent_params->have_pieces) + if ((error || status != status_t::no_error) + && m_add_torrent_params + && aux::contains_resume_data(*m_add_torrent_params) + && m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , error.ec + , resolve_filename(error.file()) + , error.operation); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + if (status != status_t::no_error || error) + { + debug_log("fastresume data rejected: ret: %d (%d) op: %s file: %d %s" + , static_cast(status), error.ec.value() + , operation_name(error.operation) + , static_cast(error.file()) + , error.ec.message().c_str()); + } + else + { + debug_log("fastresume data accepted"); + } + } +#endif + + bool should_start_full_check = (status != status_t::no_error); + + // if we got a partial pieces bitfield, it means we were in the middle of + // checking this torrent. pick it up where we left off + if (status == status_t::no_error + && m_add_torrent_params + && !m_add_torrent_params->have_pieces.empty() + && m_add_torrent_params->have_pieces.size() < m_torrent_file->num_pieces()) + { + m_checking_piece = m_num_checked_pieces + = m_add_torrent_params->have_pieces.end_index(); + should_start_full_check = true; + } + + // if ret != 0, it means we need a full check. We don't necessarily need + // that when the resume data check fails. For instance, if the resume data + // is incorrect, but we don't have any files, we skip the check and initialize + // the storage to not have anything. + if (status == status_t::no_error) + { + // there are either no files for this torrent + // or the resume_data was accepted + + if (m_seed_mode) + { + m_have_all = true; + update_gauge(); + update_state_list(); + + if (!error && m_add_torrent_params) + { + int const num_pieces2 = std::min(m_add_torrent_params->verified_pieces.size() + , torrent_file().num_pieces()); + for (piece_index_t i = piece_index_t(0); + i < piece_index_t(num_pieces2); ++i) + { + if (!m_add_torrent_params->verified_pieces[i]) continue; + m_verified.set_bit(i); + } + } + } + else if (!error && m_add_torrent_params) + { + // --- PIECES --- + + int const num_pieces = std::min(m_add_torrent_params->have_pieces.size() + , torrent_file().num_pieces()); + for (piece_index_t i = piece_index_t(0); i < piece_index_t(num_pieces); ++i) + { + if (!m_add_torrent_params->have_pieces[i]) continue; + need_picker(); + m_picker->we_have(i); + inc_stats_counter(counters::num_piece_passed); + update_gauge(); + we_have(i); + } + + // --- UNFINISHED PIECES --- + + int const num_blocks_per_piece = torrent_file().piece_length() / block_size(); + + for (auto const& p : m_add_torrent_params->unfinished_pieces) + { + piece_index_t const piece = p.first; + bitfield const& blocks = p.second; + + if (piece < piece_index_t(0) || piece >= torrent_file().end_piece()) + { + continue; + } + + // being in seed mode and missing a piece is not compatible. + // Leave seed mode if that happens + if (m_seed_mode) leave_seed_mode(seed_mode_t::skip_checking); + + if (has_picker() && m_picker->have_piece(piece)) + { + m_picker->we_dont_have(piece); + update_gauge(); + } + + need_picker(); + + const int num_bits = std::min(num_blocks_per_piece, int(blocks.size())); + for (int k = 0; k < num_bits; ++k) + { + if (blocks.get_bit(k)) + { + m_picker->mark_as_finished(piece_block(piece, k), nullptr); + } + } + if (m_picker->is_piece_finished(piece)) + { + verify_piece(piece); + } + } + } + } + else + { + m_seed_mode = false; + // either the fastresume data was rejected or there are + // some files + m_have_all = false; + update_gauge(); + update_state_list(); + } + + if (should_start_full_check) + { + set_state(torrent_status::checking_files); + if (should_check_files()) start_checking(); + + // start the checking right away (potentially) + m_ses.trigger_auto_manage(); + } + else + { + files_checked(); + } + + // this will remove the piece picker, if we're done with it + maybe_done_flushing(); + TORRENT_ASSERT(m_outstanding_check_files == false); + m_add_torrent_params.reset(); + + // restore m_need_save_resume_data to its state when we entered this + // function. + m_need_save_resume_data = need_save_resume_data; + } + catch (...) { handle_exception(); } + + void torrent::force_recheck() + { + INVARIANT_CHECK; + + if (!valid_metadata()) return; + + // if the torrent is already queued to check its files + // don't do anything + if (should_check_files() + || m_state == torrent_status::checking_resume_data) + return; + + clear_error(); + + disconnect_all(errors::stopping_torrent, operation_t::bittorrent); + stop_announcing(); + + // we're checking everything anyway, no point in assuming we are a seed + // now. + leave_seed_mode(seed_mode_t::skip_checking); + + m_ses.disk_thread().async_release_files(m_storage); + + // forget that we have any pieces + m_have_all = false; + +// removing the piece picker will clear the user priorities +// instead, just clear which pieces we have + if (m_picker) + { + int const blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); + int const blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) + + block_size() - 1) / block_size(); + m_picker->resize(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); + + m_file_progress.clear(); + m_file_progress.init(picker(), m_torrent_file->files()); + } + + // assume that we don't have anything + m_files_checked = false; + + update_gauge(); + update_want_tick(); + set_state(torrent_status::checking_resume_data); + + set_queue_position(last_pos); + + TORRENT_ASSERT(m_outstanding_check_files == false); + m_add_torrent_params.reset(); + + // this will clear the stat cache, to make us actually query the + // filesystem for files again + m_ses.disk_thread().async_release_files(m_storage); + + aux::vector links; + m_ses.disk_thread().async_check_files(m_storage, nullptr + , links, std::bind(&torrent::on_force_recheck + , shared_from_this(), _1, _2)); + } + + void torrent::on_force_recheck(status_t const status, storage_error const& error) try + { + TORRENT_ASSERT(is_single_thread()); + + // hold a reference until this function returns + state_updated(); + + if (m_abort) return; + + if (error) + { + handle_disk_error("force_recheck", error); + return; + } + if (status == status_t::no_error) + { + // if there are no files, just start + files_checked(); + } + else + { + m_progress_ppm = 0; + m_checking_piece = piece_index_t(0); + m_num_checked_pieces = piece_index_t(0); + + set_state(torrent_status::checking_files); + if (m_auto_managed) pause(torrent_handle::graceful_pause); + if (should_check_files()) start_checking(); + else m_ses.trigger_auto_manage(); + } + } + catch (...) { handle_exception(); } + + void torrent::start_checking() + { + TORRENT_ASSERT(should_check_files()); + + int num_outstanding = settings().get_int(settings_pack::checking_mem_usage) * block_size() + / m_torrent_file->piece_length(); + // if we only keep a single read operation in-flight at a time, we suffer + // significant performance degradation. Always keep at least 4 jobs + // outstanding per hasher thread + int const min_outstanding = 4 + * std::max(1, settings().get_int(settings_pack::aio_threads) + / disk_io_thread::hasher_thread_divisor); + if (num_outstanding < min_outstanding) num_outstanding = min_outstanding; + + // we might already have some outstanding jobs, if we were paused and + // resumed quickly, before the outstanding jobs completed + if (m_checking_piece >= m_torrent_file->end_piece()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_checking, checking_piece >= num_pieces. %d >= %d" + , static_cast(m_checking_piece), m_torrent_file->num_pieces()); +#endif + return; + } + + // subtract the number of pieces we already have outstanding + num_outstanding -= (static_cast(m_checking_piece) + - static_cast(m_num_checked_pieces)); + if (num_outstanding < 0) num_outstanding = 0; + + for (int i = 0; i < num_outstanding; ++i) + { + m_ses.disk_thread().async_hash(m_storage, m_checking_piece + , disk_interface::sequential_access | disk_interface::volatile_read + , std::bind(&torrent::on_piece_hashed + , shared_from_this(), _1, _2, _3)); + ++m_checking_piece; + if (m_checking_piece >= m_torrent_file->end_piece()) break; + } +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_checking, m_checking_piece: %d" + , static_cast(m_checking_piece)); +#endif + } + + // This is only used for checking of torrents. i.e. force-recheck or initial checking + // of existing files + void torrent::on_piece_hashed(piece_index_t const piece + , sha1_hash const& piece_hash, storage_error const& error) try + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_abort) return; + if (m_deleted) return; + + state_updated(); + + ++m_num_checked_pieces; + + if (error) + { + if (error.ec == boost::system::errc::no_such_file_or_directory + || error.ec == boost::asio::error::eof +#ifdef TORRENT_WINDOWS + || error.ec == error_code(ERROR_HANDLE_EOF, system_category()) +#endif + ) + { + TORRENT_ASSERT(error.file() >= file_index_t(0)); + + // skip this file by updating m_checking_piece to the first piece following it + file_storage const& st = m_torrent_file->files(); + std::int64_t file_size = st.file_size(error.file()); + piece_index_t last = st.map_file(error.file(), file_size, 0).piece; + if (m_checking_piece < last) + { + int diff = static_cast(last) - static_cast(m_checking_piece); + m_num_checked_pieces = piece_index_t(static_cast(m_num_checked_pieces) + diff); + m_checking_piece = last; + } + } + else + { + m_checking_piece = piece_index_t{0}; + m_num_checked_pieces = piece_index_t{0}; + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(error.ec, + resolve_filename(error.file()), error.operation, get_handle()); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("on_piece_hashed, fatal disk error: (%d) %s", error.ec.value() + , error.ec.message().c_str()); + } +#endif + auto_managed(false); + pause(); + set_error(error.ec, error.file()); + + // recalculate auto-managed torrents sooner + // in order to start checking the next torrent + m_ses.trigger_auto_manage(); + return; + } + } + + m_progress_ppm = std::uint32_t(std::int64_t(static_cast(m_num_checked_pieces)) + * 1000000 / torrent_file().num_pieces()); + + if (settings().get_bool(settings_pack::disable_hash_checks) + || piece_hash == m_torrent_file->hash_for_piece(piece)) + { + if (has_picker() || !m_have_all) + { + need_picker(); + m_picker->we_have(piece); + update_gauge(); + } + we_have(piece); + } + else + { + // if the hash failed, remove it from the cache + if (m_storage) + m_ses.disk_thread().clear_piece(m_storage, piece); + } + + if (m_num_checked_pieces < m_torrent_file->end_piece()) + { + // we're not done yet, issue another job + if (m_checking_piece >= m_torrent_file->end_piece()) + { + // actually, we already have outstanding jobs for + // the remaining pieces. We just need to wait for them + // to finish + return; + } + + // we paused the checking + if (!should_check_files()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, checking paused"); +#endif + if (m_checking_piece == m_num_checked_pieces) + { + // we are paused, and we just completed the last outstanding job. + // now we can be considered paused + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + return; + } + + m_ses.disk_thread().async_hash(m_storage, m_checking_piece + , disk_interface::sequential_access | disk_interface::volatile_read + , std::bind(&torrent::on_piece_hashed + , shared_from_this(), _1, _2, _3)); + ++m_checking_piece; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, m_checking_piece: %d" + , static_cast(m_checking_piece)); +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("on_piece_hashed, completed"); +#endif + if (m_auto_managed) + { + // if we're auto managed. assume we need to be paused until the auto + // managed logic runs again (which is triggered further down) + // setting flags to 0 prevents the disk cache from being evicted as a + // result of this + set_paused(true, {}); + } + + // we're done checking! (this should cause a call to trigger_auto_manage) + files_checked(); + + // reset the checking state + m_checking_piece = piece_index_t(0); + m_num_checked_pieces = piece_index_t(0); + } + catch (...) { handle_exception(); } + +#if TORRENT_ABI_VERSION == 1 + void torrent::use_interface(std::string net_interfaces) + { + std::shared_ptr p = std::make_shared(); + p->set_str(settings_pack::outgoing_interfaces, std::move(net_interfaces)); + m_ses.apply_settings_pack(p); + } +#endif + + void torrent::on_tracker_announce(error_code const& ec) try + { + COMPLETE_ASYNC("tracker::on_tracker_announce"); + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_waiting_tracker > 0); + --m_waiting_tracker; + if (ec) return; + if (m_abort) return; + announce_with_tracker(); + } + catch (...) { handle_exception(); } + + void torrent::lsd_announce() + { + if (m_abort) return; + if (!m_enable_lsd) return; + + // if the files haven't been checked yet, we're + // not ready for peers. Except, if we don't have metadata, + // we need peers to download from + if (!m_files_checked && valid_metadata()) return; + + if (!m_announce_to_lsd) return; + + // private torrents are never announced on LSD + if (m_torrent_file->is_valid() && m_torrent_file->priv()) return; + + // i2p torrents are also never announced on LSD + // unless we allow mixed swarms + if (m_torrent_file->is_valid() + && (torrent_file().is_i2p() && !settings().get_bool(settings_pack::allow_i2p_mixed))) + return; + + if (is_paused()) return; + + if (!m_ses.has_lsd()) return; + + // TODO: this pattern is repeated in a few places. Factor this into + // a function and generalize the concept of a torrent having a + // dedicated listen port +#ifdef TORRENT_USE_OPENSSL + int port = is_ssl_torrent() ? m_ses.ssl_listen_port() : m_ses.listen_port(); +#else + int port = m_ses.listen_port(); +#endif + + // announce with the local discovery service + m_ses.announce_lsd(m_torrent_file->info_hash(), port); + } + +#ifndef TORRENT_DISABLE_DHT + + void torrent::dht_announce() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_ses.dht()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("DHT: no dht initialized"); +#endif + return; + } + if (!should_announce_dht()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + if (!m_ses.announce_dht()) + debug_log("DHT: no listen sockets"); + + if (m_torrent_file->is_valid() && !m_files_checked) + debug_log("DHT: files not checked, skipping DHT announce"); + + if (!m_announce_to_dht) + debug_log("DHT: queueing disabled DHT announce"); + + if (m_paused) + debug_log("DHT: torrent paused, no DHT announce"); + + if (!m_enable_dht) + debug_log("DHT: torrent has DHT disabled flag"); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + if (!m_torrent_file->is_valid() && !m_url.empty()) + debug_log("DHT: no info-hash, waiting for \"%s\"", m_url.c_str()); +#endif + + if (m_torrent_file->is_valid() && m_torrent_file->priv()) + debug_log("DHT: private torrent, no DHT announce"); + + if (settings().get_bool(settings_pack::use_dht_as_fallback)) + { + int const verified_trackers = static_cast(std::count_if( + m_trackers.begin(), m_trackers.end() + , [](announce_entry const& t) { return t.verified; })); + + if (verified_trackers > 0) + debug_log("DHT: only using DHT as fallback, and there are %d working trackers", verified_trackers); + } + } +#endif + return; + } + + TORRENT_ASSERT(!m_paused); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("START DHT announce"); + m_dht_start_time = aux::time_now(); +#endif + + // if we're a seed, we tell the DHT for better scrape stats + dht::announce_flags_t flags = is_seed() ? dht::announce::seed : dht::announce_flags_t{}; + + // If this is an SSL torrent the announce needs to specify an SSL + // listen port. DHT nodes only operate on non-SSL ports so SSL + // torrents cannot use implied_port. + // if we allow incoming uTP connections, set the implied_port + // argument in the announce, this will make the DHT node use + // our source port in the packet as our listen port, which is + // likely more accurate when behind a NAT + if (is_ssl_torrent()) + { + flags |= dht::announce::ssl_torrent; + } + else if (settings().get_bool(settings_pack::enable_incoming_utp)) + { + flags |= dht::announce::implied_port; + } + + std::weak_ptr self(shared_from_this()); + m_ses.dht()->announce(m_torrent_file->info_hash(), 0, flags + , std::bind(&torrent::on_dht_announce_response_disp, self, _1)); + } + + void torrent::on_dht_announce_response_disp(std::weak_ptr t + , std::vector const& peers) + { + std::shared_ptr tor = t.lock(); + if (!tor) return; + tor->on_dht_announce_response(peers); + } + + void torrent::on_dht_announce_response(std::vector const& peers) try + { + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("END DHT announce (%d ms) (%d peers)" + , int(total_milliseconds(clock_type::now() - m_dht_start_time)) + , int(peers.size())); +#endif + + if (m_abort) return; + if (peers.empty()) return; + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle(), int(peers.size())); + } + + if (torrent_file().priv() || (torrent_file().is_i2p() + && !settings().get_bool(settings_pack::allow_i2p_mixed))) return; + + for (auto& p : peers) + add_peer(p, peer_info::dht); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && !peers.empty()) + { + error_code ec; + std::string str; + for (auto const& peer : peers) + { + str += peer.address().to_string(ec); + str += ' '; + } + debug_log("DHT add_peer() [ %s] connect-candidates: %d" + , str.c_str(), m_peer_list + ? m_peer_list->num_connect_candidates() : -1); + } +#endif + + do_connect_boost(); + + update_want_peers(); + } + catch (...) { handle_exception(); } + +#endif + + namespace + { + struct announce_state + { + explicit announce_state(aux::listen_socket_handle const& s) + : socket(s) {} + + aux::listen_socket_handle socket; + + // the tier is kept as INT_MAX until we find the first + // tracker that works, then it's set to that tracker's + // tier. + int tier = INT_MAX; + + // have we sent an announce in this tier yet? + bool sent_announce = false; + + // have we finished sending announces on this listen socket? + bool done = false; + }; + } + + void torrent::announce_with_tracker(std::uint8_t e) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(e == tracker_request::stopped || state() != torrent_status::checking_files); + INVARIANT_CHECK; + + if (m_trackers.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: no trackers"); +#endif + return; + } + + if (m_abort) e = tracker_request::stopped; + + // having stop_tracker_timeout <= 0 means that there is + // no need to send any request to trackers or trigger any + // related logic when the event is stopped + if (e == tracker_request::stopped + && settings().get_int(settings_pack::stop_tracker_timeout) <= 0) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: event == stopped && stop_tracker_timeout <= 0"); +#endif + return; + } + + // if we're not announcing to trackers, only allow + // stopping + if (e != tracker_request::stopped && !m_announce_to_trackers) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: event != stopped && !m_announce_to_trackers"); +#endif + return; + } + + // if we're not allowing peers, there's no point in announcing + if (e != tracker_request::stopped && m_paused) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** announce: event != stopped && m_paused"); +#endif + return; + } + + TORRENT_ASSERT(!m_paused || e == tracker_request::stopped); + + if (e == tracker_request::none && is_finished() && !is_seed()) + e = tracker_request::paused; + + tracker_request req; + if (settings().get_bool(settings_pack::apply_ip_filter_to_trackers) + && m_apply_ip_filter) + { + req.filter = m_ip_filter; + } + + req.private_torrent = m_torrent_file->priv(); + + req.info_hash = m_torrent_file->info_hash(); + req.pid = m_peer_id; + req.downloaded = m_stat.total_payload_download() - m_total_failed_bytes; + req.uploaded = m_stat.total_payload_upload(); + req.corrupt = m_total_failed_bytes; + req.left = value_or(bytes_left(), 16*1024); +#ifdef TORRENT_USE_OPENSSL + // if this torrent contains an SSL certificate, make sure + // any SSL tracker presents a certificate signed by it + req.ssl_ctx = m_ssl_ctx.get(); +#endif + + req.redundant = m_total_redundant_bytes; + // exclude redundant bytes if we should + if (!settings().get_bool(settings_pack::report_true_downloaded)) + { + req.downloaded -= m_total_redundant_bytes; + + // if the torrent is complete we know that all incoming pieces will be + // marked redundant so add them to the redundant count + // this is mainly needed to cover the case where a torrent has just completed + // but still has partially downloaded pieces + // if the incoming pieces are not accounted for it could cause the downloaded + // amount to exceed the total size of the torrent which upsets some trackers + if (is_seed()) + { + for (auto c : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + auto const pbp = c->downloading_piece_progress(); + if (pbp.bytes_downloaded > 0) + { + req.downloaded -= pbp.bytes_downloaded; + req.redundant += pbp.bytes_downloaded; + } + } + } + } + if (req.downloaded < 0) req.downloaded = 0; + + req.event = e; + + // since sending our IPv4/v6 address to the tracker may be sensitive. Only + // do that if we're not in anonymous mode and if it's a private torrent + if (!settings().get_bool(settings_pack::anonymous_mode) + && m_torrent_file + && m_torrent_file->priv()) + { + m_ses.for_each_listen_socket([&](aux::listen_socket_handle const& s) + { + if (s.is_ssl() != is_ssl_torrent()) return; + tcp::endpoint const ep = s.get_local_endpoint(); + if (is_any(ep.address())) return; + if (is_v6(ep)) + { + if (!is_local(ep.address()) && !is_loopback(ep.address())) + req.ipv6.push_back(ep.address().to_v6()); + } + else + { + if (!is_local(ep.address()) && !is_loopback(ep.address())) + req.ipv4.push_back(ep.address().to_v4()); + } + }); + } + + // if we are aborting. we don't want any new peers + req.num_want = (req.event == tracker_request::stopped) + ? 0 : settings().get_int(settings_pack::num_want); + + time_point32 const now = aux::time_now32(); + + // each listen socket gets it's own announce state + // so that each one should get at least one announce + std::vector listen_socket_states; + +#ifndef TORRENT_DISABLE_LOGGING + int idx = -1; + if (should_log()) + { + debug_log("*** announce: " + "[ announce_to_all_tiers: %d announce_to_all_trackers: %d num_trackers: %d ]" + , settings().get_bool(settings_pack::announce_to_all_tiers) + , settings().get_bool(settings_pack::announce_to_all_trackers) + , int(m_trackers.size())); + } +#endif + for (auto& ae : m_trackers) + { +#ifndef TORRENT_DISABLE_LOGGING + ++idx; +#endif + // update the endpoint list by adding entries for new listen sockets + // and removing entries for non-existent ones + std::size_t valid_endpoints = 0; + m_ses.for_each_listen_socket([&](aux::listen_socket_handle const& s) { + if (s.is_ssl() != is_ssl_torrent()) + return; + for (auto& aep : ae.endpoints) + { + if (aep.socket != s) continue; + std::swap(ae.endpoints[valid_endpoints], aep); + valid_endpoints++; + return; + } + + ae.endpoints.emplace_back(s, bool(m_complete_sent)); + std::swap(ae.endpoints[valid_endpoints], ae.endpoints.back()); + valid_endpoints++; + }); + + TORRENT_ASSERT(valid_endpoints <= ae.endpoints.size()); + ae.endpoints.erase(ae.endpoints.begin() + int(valid_endpoints), ae.endpoints.end()); + + // if trackerid is not specified for tracker use default one, probably set explicitly + req.trackerid = ae.trackerid.empty() ? m_trackerid : ae.trackerid; + req.url = ae.url; + + for (auto& aep : ae.endpoints) + { + // do not add code which continues to the next endpoint here! + // listen_socket_states needs to be populated even if none of the endpoints + // will be announcing for this tracker + // otherwise the early bail out when neither announce_to_all_trackers + // nor announce_to_all_tiers is set may be triggered prematurely + + auto aep_state_iter = std::find_if(listen_socket_states.begin(), listen_socket_states.end() + , [&](announce_state const& s) { return s.socket == aep.socket; }); + if (aep_state_iter == listen_socket_states.end()) + { + listen_socket_states.emplace_back(aep.socket); + aep_state_iter = listen_socket_states.end() - 1; + } + announce_state& state = *aep_state_iter; + + if (state.done) continue; + + // if we haven't sent an event=start to the tracker, there's no + // point in sending an event=stopped + if (!aep.enabled || (!aep.start_sent && req.event == tracker_request::stopped)) + continue; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** tracker: (%d) [ep: %s ] \"%s\" [ i->tier: %d tier: %d" + " working: %d limit: %d can: %d sent: %d ]" + , idx, print_endpoint(aep.local_endpoint).c_str() + , ae.url.c_str(), ae.tier, state.tier, aep.is_working() + , ae.fail_limit, aep.can_announce(now, is_seed(), ae.fail_limit), state.sent_announce); + } +#endif + + if (settings().get_bool(settings_pack::announce_to_all_tiers) + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && state.sent_announce + && ae.tier <= state.tier + && state.tier != INT_MAX) + continue; + + if (ae.tier > state.tier && state.sent_announce + && !settings().get_bool(settings_pack::announce_to_all_tiers)) continue; + if (aep.is_working()) { state.tier = ae.tier; state.sent_announce = false; } + if (!aep.can_announce(now, is_seed(), ae.fail_limit)) + { + // this counts + if (aep.is_working()) + { + state.sent_announce = true; + if (!settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) + { + state.done = true; + } + } + continue; + } + + req.event = e; + if (req.event == tracker_request::none) + { + if (!aep.start_sent) req.event = tracker_request::started; + else if (!m_complete_sent + && !aep.complete_sent + && is_seed()) + { + req.event = tracker_request::completed; + } + } + + req.triggered_manually = aep.triggered_manually; + aep.triggered_manually = false; + +#if TORRENT_ABI_VERSION == 1 + req.auth = tracker_login(); +#endif + req.key = tracker_key(); + +#if TORRENT_USE_I2P + if (is_i2p()) + { + req.kind |= tracker_request::i2p; + } +#endif + + req.outgoing_socket = aep.socket; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("==> TRACKER REQUEST \"%s\" event: %s abort: %d ssl: %p " + "port: %d ssl-port: %d fails: %d upd: %d ep: %s" + , req.url.c_str() + , (req.event == tracker_request::stopped ? "stopped" + : req.event == tracker_request::started ? "started" : "") + , m_abort +#ifdef TORRENT_USE_OPENSSL + , static_cast(req.ssl_ctx) +#else + , static_cast(nullptr) +#endif + , m_ses.listen_port() + , m_ses.ssl_listen_port() + , aep.fails + , aep.updating + , print_endpoint(aep.local_endpoint).c_str()); + } + + // if we're not logging session logs, don't bother creating an + // observer object just for logging + if (m_abort && m_ses.should_log()) + { + auto tl = std::make_shared(m_ses); + m_ses.queue_tracker_request(tracker_request(req), tl); + } + else +#endif + { + m_ses.queue_tracker_request(tracker_request(req), shared_from_this()); + } + + aep.updating = true; + aep.next_announce = now; + aep.min_announce = now; + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle(), aep.local_endpoint, req.url, req.event); + } + + state.sent_announce = true; + if (aep.is_working() + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) + { + state.done = true; + } + } + + if (std::all_of(listen_socket_states.begin(), listen_socket_states.end() + , [](announce_state const& s) { return s.done; })) + break; + } + update_tracker_timer(now); + } + + void torrent::scrape_tracker(int idx, bool const user_triggered) + { + TORRENT_ASSERT(is_single_thread()); +#if TORRENT_ABI_VERSION == 1 + m_last_scrape = aux::time_now32(); +#endif + + if (m_trackers.empty()) return; + + if (idx < 0 || idx >= int(m_trackers.size())) idx = m_last_working_tracker; + if (idx < 0) idx = 0; + + tracker_request req; + if (settings().get_bool(settings_pack::apply_ip_filter_to_trackers) + && m_apply_ip_filter) + req.filter = m_ip_filter; + + req.info_hash = m_torrent_file->info_hash(); + req.kind |= tracker_request::scrape_request; + req.url = m_trackers[idx].url; + req.private_torrent = m_torrent_file->priv(); +#if TORRENT_ABI_VERSION == 1 + req.auth = tracker_login(); +#endif + req.key = tracker_key(); + req.triggered_manually = user_triggered; + m_ses.queue_tracker_request(std::move(req), shared_from_this()); + } + + void torrent::tracker_warning(tracker_request const& req, std::string const& msg) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + announce_entry* ae = find_tracker(req.url); + tcp::endpoint local_endpoint; + if (ae) + { + for (auto& aep : ae->endpoints) + { + if (aep.socket != req.outgoing_socket) continue; + local_endpoint = aep.local_endpoint; + aep.message = msg; + break; + } + } + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , local_endpoint, req.url, msg); + } + + void torrent::tracker_scrape_response(tracker_request const& req + , int const complete, int const incomplete, int const downloaded, int /* downloaders */) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + TORRENT_ASSERT(0 != (req.kind & tracker_request::scrape_request)); + + announce_entry* ae = find_tracker(req.url); + tcp::endpoint local_endpoint; + if (ae) + { + announce_endpoint* aep = ae->find_endpoint(req.outgoing_socket); + if (aep) + { + local_endpoint = aep->local_endpoint; + if (incomplete >= 0) aep->scrape_incomplete = incomplete; + if (complete >= 0) aep->scrape_complete = complete; + if (downloaded >= 0) aep->scrape_downloaded = downloaded; + + update_scrape_state(); + } + } + + // if this was triggered manually we need to post this unconditionally, + // since the client expects a response from its action, regardless of + // whether all tracker events have been enabled by the alert mask + if (m_ses.alerts().should_post() + || req.triggered_manually) + { + m_ses.alerts().emplace_alert( + get_handle(), local_endpoint, incomplete, complete, req.url); + } + } + + void torrent::update_scrape_state() + { + // loop over all trackers and find the largest numbers for each scrape field + // then update the torrent-wide understanding of number of downloaders and seeds + int complete = -1; + int incomplete = -1; + int downloaded = -1; + for (auto const& t : m_trackers) + { + for (auto const& aep : t.endpoints) + { + complete = std::max(aep.scrape_complete, complete); + incomplete = std::max(aep.scrape_incomplete, incomplete); + downloaded = std::max(aep.scrape_downloaded, downloaded); + } + } + + if ((complete >= 0 && int(m_complete) != complete) + || (incomplete >= 0 && int(m_incomplete) != incomplete) + || (downloaded >= 0 && int(m_downloaded) != downloaded)) + state_updated(); + + if (int(m_complete) != complete + || int(m_incomplete) != incomplete + || int(m_downloaded) != downloaded) + { + m_complete = std::uint32_t(complete); + m_incomplete = std::uint32_t(incomplete); + m_downloaded = std::uint32_t(downloaded); + + update_auto_sequential(); + + // these numbers are cached in the resume data + set_need_save_resume(); + } + } + + void torrent::tracker_response( + tracker_request const& r + , address const& tracker_ip // this is the IP we connected to + , std::list
    const& tracker_ips // these are all the IPs it resolved to + , struct tracker_response const& resp) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + TORRENT_ASSERT(0 == (r.kind & tracker_request::scrape_request)); + + // if the tracker told us what our external IP address is, record it with + // out external IP counter (and pass along the IP of the tracker to know + // who to attribute this vote to) + if (resp.external_ip != address() && !is_any(tracker_ip) && r.outgoing_socket) + m_ses.set_external_address(r.outgoing_socket.get_local_endpoint() + , resp.external_ip + , aux::session_interface::source_tracker, tracker_ip); + + time_point32 const now = aux::time_now32(); + + auto const interval = std::max(resp.interval, seconds32( + settings().get_int(settings_pack::min_announce_interval))); + + announce_entry* ae = find_tracker(r.url); + tcp::endpoint local_endpoint; + if (ae) + { +#if TORRENT_ABI_VERSION == 1 + if (!ae->complete_sent && r.event == tracker_request::completed) + ae->complete_sent = true; +#endif + announce_endpoint* aep = ae->find_endpoint(r.outgoing_socket); + if (aep) + { + local_endpoint = aep->local_endpoint; + if (resp.incomplete >= 0) aep->scrape_incomplete = resp.incomplete; + if (resp.complete >= 0) aep->scrape_complete = resp.complete; + if (resp.downloaded >= 0) aep->scrape_downloaded = resp.downloaded; + if (!aep->start_sent && r.event == tracker_request::started) + aep->start_sent = true; + if (!aep->complete_sent && r.event == tracker_request::completed) + { + aep->complete_sent = true; + // we successfully reported event=completed to one tracker. Don't + // send it to any other ones from now on (there may be other + // announces outstanding right now though) + m_complete_sent = true; + } + ae->verified = true; + aep->next_announce = now + interval; + aep->min_announce = now + resp.min_interval; + aep->updating = false; + aep->fails = 0; + aep->last_error.clear(); + aep->message = !resp.warning_message.empty() ? resp.warning_message : std::string(); + int tracker_index = int(ae - m_trackers.data()); + m_last_working_tracker = std::int8_t(tracker_index); + + if ((!resp.trackerid.empty()) && (ae->trackerid != resp.trackerid)) + { + ae->trackerid = resp.trackerid; + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , aep->local_endpoint, r.url, resp.trackerid); + } + + update_scrape_state(); + } + } + update_tracker_timer(now); + +#if TORRENT_ABI_VERSION == 1 + if (resp.complete >= 0 && resp.incomplete >= 0) + m_last_scrape = aux::time_now32(); +#endif + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + std::string resolved_to; + for (auto const& i : tracker_ips) + { + resolved_to += i.to_string(); + resolved_to += ", "; + } + debug_log("TRACKER RESPONSE [ interval: %d | min-interval: %d | " + "external ip: %s | resolved to: %s | we connected to: %s ]" + , interval.count() + , resp.min_interval.count() + , print_address(resp.external_ip).c_str() + , resolved_to.c_str() + , print_address(tracker_ip).c_str()); + } +#else + TORRENT_UNUSED(tracker_ips); +#endif + + // for each of the peers we got from the tracker + for (auto const& i : resp.peers) + { + // don't make connections to ourself + if (i.pid == m_peer_id) + continue; + +#if TORRENT_USE_I2P + if (r.i2pconn && string_ends_with(i.hostname, ".i2p")) + { + // this is an i2p name, we need to use the SAM connection + // to do the name lookup + if (string_ends_with(i.hostname, ".b32.i2p")) + { + ADD_OUTSTANDING_ASYNC("torrent::on_i2p_resolve"); + r.i2pconn->async_name_lookup(i.hostname.c_str() + , std::bind(&torrent::on_i2p_resolve + , shared_from_this(), _1, _2)); + } + else + { + torrent_state st = get_peer_list_state(); + need_peer_list(); + if (m_peer_list->add_i2p_peer(i.hostname.c_str (), peer_info::tracker, {}, &st)) + state_updated(); + peers_erased(st.erased); + } + } + else +#endif + { + ADD_OUTSTANDING_ASYNC("torrent::on_peer_name_lookup"); + m_ses.get_resolver().async_resolve(i.hostname, resolver_interface::abort_on_shutdown + , std::bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i.port)); + } + } + + // there are 2 reasons to allow local IPs to be returned from a + // non-local tracker + // 1. retrackers are popular in russia, where an ISP runs a tracker within + // the AS (but not on the local network) giving out peers only from the + // local network + // 2. it might make sense to have a tracker extension in the future where + // trackers records a peer's internal and external IP, and match up + // peers on the same local network + + bool need_update = false; + for (auto const& i : resp.peers4) + { + tcp::endpoint const a(address_v4(i.ip), i.port); + need_update |= bool(add_peer(a, peer_info::tracker) != nullptr); + } + + for (auto const& i : resp.peers6) + { + tcp::endpoint const a(address_v6(i.ip), i.port); + need_update |= bool(add_peer(a, peer_info::tracker) != nullptr); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && (!resp.peers4.empty() || !resp.peers6.empty())) + { + error_code ec; + std::string str; + for (auto const& peer : resp.peers4) + { + str += address_v4(peer.ip).to_string(ec); + str += ' '; + } + for (auto const& peer : resp.peers6) + { + str += address_v6(peer.ip).to_string(ec); + str += ' '; + } + debug_log("tracker add_peer() [ %s] connect-candidates: %d" + , str.c_str(), m_peer_list + ? m_peer_list->num_connect_candidates() : -1); + } +#endif + if (need_update) state_updated(); + + update_want_peers(); + + // post unconditionally if the announce was triggered manually + if (m_ses.alerts().should_post() + || r.triggered_manually) + { + m_ses.alerts().emplace_alert( + get_handle(), local_endpoint, int(resp.peers.size() + resp.peers4.size()) + + int(resp.peers6.size()) + , r.url); + } + + do_connect_boost(); + + state_updated(); + } + + void torrent::update_auto_sequential() + { + if (!settings().get_bool(settings_pack::auto_sequential)) + { + m_auto_sequential = false; + return; + } + + if (num_peers() - m_num_connecting < 10) + { + // there are too few peers. Be conservative and don't assume it's + // well seeded until we can connect to more peers + m_auto_sequential = false; + return; + } + + // if there are at least 10 seeds, and there are 10 times more + // seeds than downloaders, enter sequential download mode + // (for performance) + int const downloaders = num_downloaders(); + int const seeds = num_seeds(); + m_auto_sequential = downloaders * 10 <= seeds + && seeds > 9; + } + + void torrent::do_connect_boost() + { + if (m_connect_boost_counter == 0) return; + + // this is the first tracker response for this torrent + // instead of waiting one second for session_impl::on_tick() + // to be called, connect to a few peers immediately + int conns = std::min(int(m_connect_boost_counter) + , settings().get_int(settings_pack::connections_limit) - m_ses.num_connections()); + + if (conns == 0) return; + + // if we don't know of any peers + if (!m_peer_list) return; + + while (want_peers() && conns > 0) + { + TORRENT_ASSERT(m_connect_boost_counter > 0); + --conns; + --m_connect_boost_counter; + torrent_state st = get_peer_list_state(); + torrent_peer* p = m_peer_list->connect_one_peer(m_ses.session_time(), &st); + peers_erased(st.erased); + inc_stats_counter(counters::connection_attempt_loops, st.loop_counter); + if (p == nullptr) + { + update_want_peers(); + continue; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + external_ip const& external = m_ses.external_address(); + debug_log(" *** FOUND CONNECTION CANDIDATE [" + " ip: %s rank: %u external: %s t: %d ]" + , print_endpoint(p->ip()).c_str() + , p->rank(external, m_ses.listen_port()) + , print_address(external.external_address(p->address())).c_str() + , int(m_ses.session_time() - p->last_connected)); + } +#endif + + if (!connect_to_peer(p)) + { + m_peer_list->inc_failcount(p); + update_want_peers(); + } + else + { + // increase m_ses.m_boost_connections for each connection + // attempt. This will be deducted from the connect speed + // the next time session_impl::on_tick() is triggered + m_ses.inc_boost_connections(); + update_want_peers(); + } + } + + if (want_peers()) m_ses.prioritize_connections(shared_from_this()); + } + + // this is the entry point for the client to force a re-announce. It's + // considered a client-initiated announce (as opposed to the regular ones, + // issued by libtorrent) + void torrent::force_tracker_request(time_point const t, int const tracker_idx + , reannounce_flags_t const flags) + { + TORRENT_ASSERT_PRECOND((tracker_idx >= 0 + && tracker_idx < int(m_trackers.size())) + || tracker_idx == -1); + + if (is_paused()) return; + if (tracker_idx == -1) + { + for (auto& e : m_trackers) + { + for (auto& aep : e.endpoints) + { + aep.next_announce = (flags & torrent_handle::ignore_min_interval) + ? time_point_cast(t) + seconds32(1) + : std::max(time_point_cast(t), aep.min_announce) + seconds32(1); + aep.min_announce = aep.next_announce; + aep.triggered_manually = true; + } + } + } + else + { + if (tracker_idx < 0 || tracker_idx >= int(m_trackers.size())) + return; + announce_entry& e = m_trackers[tracker_idx]; + for (auto& aep : e.endpoints) + { + aep.next_announce = (flags & torrent_handle::ignore_min_interval) + ? time_point_cast(t) + seconds32(1) + : std::max(time_point_cast(t), aep.min_announce) + seconds32(1); + aep.min_announce = aep.next_announce; + aep.triggered_manually = true; + } + } + update_tracker_timer(aux::time_now32()); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent::set_tracker_login(std::string const& name + , std::string const& pw) + { + m_username = name; + m_password = pw; + } +#endif + +#if TORRENT_USE_I2P + void torrent::on_i2p_resolve(error_code const& ec, char const* dest) try + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + COMPLETE_ASYNC("torrent::on_i2p_resolve"); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + debug_log("i2p_resolve error: %s", ec.message().c_str()); +#endif + if (ec || m_abort || m_ses.is_aborted()) return; + + need_peer_list(); + torrent_state st = get_peer_list_state(); + if (m_peer_list->add_i2p_peer(dest, peer_info::tracker, {}, &st)) + state_updated(); + peers_erased(st.erased); + } + catch (...) { handle_exception(); } +#endif + + void torrent::on_peer_name_lookup(error_code const& e + , std::vector
    const& host_list, int const port) try + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + COMPLETE_ASYNC("torrent::on_peer_name_lookup"); + +#ifndef TORRENT_DISABLE_LOGGING + if (e && should_log()) + debug_log("peer name lookup error: %s", e.message().c_str()); +#endif + + if (e || m_abort || host_list.empty() || m_ses.is_aborted()) return; + + // TODO: add one peer per IP the hostname resolves to + tcp::endpoint host(host_list.front(), std::uint16_t(port)); + + if (m_ip_filter && m_ip_filter->access(host.address()) & ip_filter::blocked) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + error_code ec; + debug_log("blocked ip from tracker: %s", host.address().to_string(ec).c_str()); + } +#endif + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , host, peer_blocked_alert::ip_filter); + return; + } + + if (add_peer(host, peer_info::tracker)) + { + state_updated(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + error_code ec; + debug_log("name-lookup add_peer() [ %s ] connect-candidates: %d" + , host.address().to_string(ec).c_str() + , m_peer_list ? m_peer_list->num_connect_candidates() : -1); + } +#endif + } + update_want_peers(); + } + catch (...) { handle_exception(); } + + boost::optional torrent::bytes_left() const + { + // if we don't have the metadata yet, we + // cannot tell how big the torrent is. + if (!valid_metadata()) return {}; + TORRENT_ASSERT(m_torrent_file->num_pieces() > 0); + if (m_seed_mode) return std::int64_t(0); + if (!has_picker()) return is_seed() ? std::int64_t(0) : m_torrent_file->total_size(); + + std::int64_t left + = m_torrent_file->total_size() + - std::int64_t(m_picker->num_passed()) * m_torrent_file->piece_length(); + + // if we have the last piece, we may have subtracted too much, as it can + // be smaller than the normal piece size. + // we have to correct it + piece_index_t const last_piece = prev(m_torrent_file->end_piece()); + if (m_picker->has_piece_passed(last_piece)) + { + left += m_torrent_file->piece_length() - m_torrent_file->piece_size(last_piece); + } + + return left; + } + + // we assume the last block is never a pad block. Should be a fairly + // safe assumption, and you just get a few kiB off if it is + std::int64_t calc_bytes(file_storage const& fs, piece_count const& pc) + { + // it's an impossible combination to have 0 pieces, but still have one of them be the last piece + TORRENT_ASSERT(!(pc.num_pieces == 0 && pc.last_piece == true)); + + // if we have 0 pieces, we can't have any pad blocks either + TORRENT_ASSERT(!(pc.num_pieces == 0 && pc.pad_blocks > 0)); + + // if we have all pieces, we must also have the last one + TORRENT_ASSERT(!(pc.num_pieces == fs.num_pieces() && pc.last_piece == false)); + int const block_size = std::min(default_block_size, fs.piece_length()); + + // every block should not be a pad block + TORRENT_ASSERT(pc.pad_blocks <= std::int64_t(pc.num_pieces) * fs.piece_length() / block_size); + + return std::int64_t(pc.num_pieces) * fs.piece_length() + - (pc.last_piece ? fs.piece_length() - fs.piece_size(fs.last_piece()) : 0) + - std::int64_t(pc.pad_blocks) * block_size; + } + + // fills in total_wanted, total_wanted_done and total_done +// TODO: 3 this could probably be pulled out into a free function + void torrent::bytes_done(torrent_status& st, status_flags_t const flags) const + { + INVARIANT_CHECK; + + st.total_done = 0; + st.total_wanted_done = 0; + st.total_wanted = m_torrent_file->total_size(); + + TORRENT_ASSERT(st.total_wanted >= m_padding_blocks * default_block_size); + TORRENT_ASSERT(st.total_wanted >= 0); + + TORRENT_ASSERT(!valid_metadata() || m_torrent_file->num_pieces() > 0); + if (!valid_metadata()) return; + + TORRENT_ASSERT(st.total_wanted >= std::int64_t(m_torrent_file->piece_length()) + * (m_torrent_file->num_pieces() - 1)); + + // if any piece hash fails, we'll be taken out of seed mode + // and m_seed_mode will be false + if (m_seed_mode || is_seed()) + { + st.total_done = m_torrent_file->total_size() + - m_padding_blocks * default_block_size; + st.total_wanted_done = st.total_done; + st.total_wanted = st.total_done; + return; + } + else if (!has_picker()) + { + st.total_done = 0; + st.total_wanted_done = 0; + st.total_wanted = m_torrent_file->total_size() + - m_padding_blocks * default_block_size; + return; + } + + TORRENT_ASSERT(has_picker()); + + file_storage const& files = m_torrent_file->files(); + + st.total_wanted = calc_bytes(files, m_picker->want()); + st.total_wanted_done = calc_bytes(files, m_picker->have_want()); + st.total_done = calc_bytes(files, m_picker->have()); + st.total = calc_bytes(files, m_picker->all_pieces()); + + TORRENT_ASSERT(st.total_done <= calc_bytes(files, m_picker->all_pieces())); + TORRENT_ASSERT(st.total_wanted <= calc_bytes(files, m_picker->all_pieces())); + + TORRENT_ASSERT(st.total_wanted_done >= 0); + TORRENT_ASSERT(st.total_wanted >= 0); + TORRENT_ASSERT(st.total_wanted >= st.total_wanted_done); + TORRENT_ASSERT(st.total_done >= 0); + TORRENT_ASSERT(st.total_done >= st.total_wanted_done); + + // this is expensive, we might not want to do it all the time + if (!(flags & torrent_handle::query_accurate_download_counters)) return; + + // to get higher accuracy of the download progress, include + // blocks from currently downloading pieces as well + std::vector const dl_queue + = m_picker->get_download_queue(); + + // look at all unfinished pieces and add the completed + // blocks to our 'done' counter + for (auto i = dl_queue.begin(); i != dl_queue.end(); ++i) + { + piece_index_t const index = i->index; + + // completed pieces are already accounted for + if (m_picker->have_piece(index)) continue; + + TORRENT_ASSERT(i->finished + i->writing <= m_picker->blocks_in_piece(index)); + TORRENT_ASSERT(i->finished + i->writing >= m_picker->pad_blocks_in_piece(index)); + + int const blocks = i->finished + i->writing - m_picker->pad_blocks_in_piece(index); + TORRENT_ASSERT(blocks >= 0); + + auto const additional_bytes = std::int64_t(blocks) * block_size(); + st.total_done += additional_bytes; + if (m_picker->piece_priority(index) > dont_download) + st.total_wanted_done += additional_bytes; + } + } + + void torrent::on_piece_verified(piece_index_t const piece + , sha1_hash const& piece_hash, storage_error const& error) try + { + TORRENT_ASSERT(is_single_thread()); + + if (m_abort) return; + if (m_deleted) return; + + bool const passed = settings().get_bool(settings_pack::disable_hash_checks) + || (!error && sha1_hash(piece_hash) == m_torrent_file->hash_for_piece(piece)); + + bool const disk_error = !passed && error; + + if (disk_error) handle_disk_error("piece_verified", error); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** PIECE_FINISHED [ p: %d | chk: %s | size: %d ]" + , static_cast(piece), passed ? "passed" : disk_error ? "disk failed" : "failed" + , m_torrent_file->piece_size(piece)); + } +#endif + TORRENT_ASSERT(valid_metadata()); + + // if we're a seed we don't have a picker + // and we also don't have to do anything because + // we already have this piece + if (!has_picker() && m_have_all) return; + + need_picker(); + + TORRENT_ASSERT(!m_picker->have_piece(piece)); + + state_updated(); + + // even though the piece passed the hash-check + // it might still have failed being written to disk + // if so, piece_picker::write_failed() has been + // called, and the piece is no longer finished. + // in this case, we have to ignore the fact that + // it passed the check + if (!m_picker->is_piece_finished(piece)) return; + + if (disk_error) + { + update_gauge(); + } + else if (passed) + { + // the following call may cause picker to become invalid + // in case we just became a seed + piece_passed(piece); + // if we're in seed mode, we just acquired this piece + // mark it as verified + if (m_seed_mode) verified(piece); + } + else + { + // piece_failed() will restore the piece + piece_failed(piece); + } + } + catch (...) { handle_exception(); } + + void torrent::add_suggest_piece(piece_index_t const index) + { + TORRENT_ASSERT(settings().get_int(settings_pack::suggest_mode) + == settings_pack::suggest_read_cache); + + // when we care about suggest mode, we keep the piece picker + // around to track piece availability + need_picker(); + int const peers = std::max(num_peers(), 1); + int const availability = m_picker->get_availability(index) * 100 / peers; + + m_suggest_pieces.add_piece(index, availability + , settings().get_int(settings_pack::max_suggest_pieces)); + } + + // this is called once we have completely downloaded piece + // 'index', its hash has been verified. It's also called + // during initial file check when we find a piece whose hash + // is correct + void torrent::we_have(piece_index_t const index) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!has_picker() || m_picker->has_piece_passed(index)); + + inc_stats_counter(counters::num_have_pieces); + + // at this point, we have the piece for sure. It has been + // successfully written to disk. We may announce it to peers + // (unless it has already been announced through predictive_piece_announce + // feature). + bool announce_piece = true; +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + auto const it = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (it != m_predictive_pieces.end() && *it == index) + { + // this means we've already announced the piece + announce_piece = false; + m_predictive_pieces.erase(it); + } +#endif + + // make a copy of the peer list since peers + // may disconnect while looping + for (auto c : m_connections) + { + auto p = c->self(); + + // received_piece will check to see if we're still interested + // in this peer, and if neither of us is interested in the other, + // disconnect it. + p->received_piece(index); + if (p->is_disconnecting()) continue; + + // if we're not announcing the piece, it means we + // already have, and that we might have received + // a request for it, and not sending it because + // we were waiting to receive the piece, now that + // we have received it, try to send stuff (fill_send_buffer) + if (announce_piece) p->announce_piece(index); + else p->fill_send_buffer(); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + ext->on_piece_pass(index); + } +#endif + + // since this piece just passed, we might have + // become uninterested in some peers where this + // was the last piece we were interested in + // update_interest may disconnect the peer and + // invalidate the iterator + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + // if we're not interested already, no need to check + if (!p->is_interesting()) continue; + // if the peer doesn't have the piece we just got, it + // shouldn't affect our interest + if (!p->has_piece(index)) continue; + p->update_interest(); + } + + set_need_save_resume(); + state_updated(); + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), index); + + // update m_file_progress (if we have one) + m_file_progress.update(m_torrent_file->files(), index + , [this](file_index_t const file_index) + { + if (m_ses.alerts().should_post()) + { + // this file just completed, post alert + m_ses.alerts().emplace_alert( + get_handle(), file_index); + } + }); + +#ifndef TORRENT_DISABLE_STREAMING + remove_time_critical_piece(index, true); +#endif + + if (is_downloading_state(m_state)) + { + if (m_state != torrent_status::finished + && m_state != torrent_status::seeding + && is_finished()) + { + // torrent finished + // i.e. all the pieces we're interested in have + // been downloaded. Release the files (they will open + // in read only mode if needed) + finished(); + // if we just became a seed, picker is now invalid, since it + // is deallocated by the torrent once it starts seeding + } + + m_last_download = aux::time_now32(); + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_share_mode) + recalc_share_mode(); +#endif + } + } + + // this is called when the piece hash is checked as correct. Note + // that the piece picker and the torrent won't necessarily consider + // us to have this piece yet, since it might not have been flushed + // to disk yet. Only if we have predictive_piece_announce on will + // we announce this piece to peers at this point. + void torrent::piece_passed(piece_index_t const index) + { +// INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!m_picker->has_piece_passed(index)); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("PIECE_PASSED (%d)", num_passed()); +#endif + +// std::fprintf(stderr, "torrent::piece_passed piece:%d\n", index); + + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_torrent_file->end_piece()); + + set_need_save_resume(); + + inc_stats_counter(counters::num_piece_passed); + +#ifndef TORRENT_DISABLE_STREAMING + remove_time_critical_piece(index, true); +#endif + + if (settings().get_int(settings_pack::suggest_mode) + == settings_pack::suggest_read_cache) + { + // we just got a new piece. Chances are that it's actually the + // rarest piece (since we're likely to download pieces rarest first) + // if it's rarer than any other piece that we currently suggest, insert + // it in the suggest set and pop the last one out + add_suggest_piece(index); + } + + std::vector downloaders; + m_picker->get_downloaders(downloaders, index); + + // increase the trust point of all peers that sent + // parts of this piece. + std::set peers; + + // these torrent_peer pointers are owned by m_peer_list and they may be + // invalidated if a peer disconnects. We cannot keep them across any + // significant operations, but we should use them right away + // ignore nullptrs + std::remove_copy(downloaders.begin(), downloaders.end() + , std::inserter(peers, peers.begin()), static_cast(nullptr)); + + for (auto p : peers) + { + TORRENT_ASSERT(p != nullptr); + if (p == nullptr) continue; + TORRENT_ASSERT(p->in_use); + p->on_parole = false; + int trust_points = p->trust_points; + ++trust_points; + if (trust_points > 8) trust_points = 8; + p->trust_points = trust_points; + if (p->connection) + { + auto* peer = static_cast(p->connection); + TORRENT_ASSERT(peer->m_in_use == 1337); + peer->received_valid_data(index); + } + } + // announcing a piece may invalidate the torrent_peer pointers + // so we can't use them anymore + + downloaders.clear(); + peers.clear(); + + // make the disk cache flush the piece to disk + if (m_storage) + m_ses.disk_thread().async_flush_piece(m_storage, index); + m_picker->piece_passed(index); + update_gauge(); + we_have(index); + update_want_tick(); + } + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + // we believe we will complete this piece very soon + // announce it to peers ahead of time to eliminate the + // round-trip times involved in announcing it, requesting it + // and sending it + // TODO: 2 use chrono type for time duration + void torrent::predicted_have_piece(piece_index_t const index, int const milliseconds) + { + auto const i = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (i != m_predictive_pieces.end() && *i == index) return; + + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::outgoing, "PREDICTIVE_HAVE", "piece: %d expected in %d ms" + , static_cast(index), milliseconds); +#else + TORRENT_UNUSED(milliseconds); +#endif + p->announce_piece(index); + } + + m_predictive_pieces.insert(i, index); + } +#endif + + void torrent::piece_failed(piece_index_t const index) + { + // if the last piece fails the peer connection will still + // think that it has received all of it until this function + // resets the download queue. So, we cannot do the + // invariant check here since it assumes: + // (total_done == m_torrent_file->total_size()) => is_seed() + INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(m_picker.get()); + TORRENT_ASSERT(index >= piece_index_t(0)); + TORRENT_ASSERT(index < m_torrent_file->end_piece()); + + inc_stats_counter(counters::num_piece_failed); + +#ifndef TORRENT_DISABLE_PREDICTIVE_PIECES + auto const it = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (it != m_predictive_pieces.end() && *it == index) + { + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + // send reject messages for + // potential outstanding requests to this piece + p->reject_piece(index); + // let peers that support the dont-have message + // know that we don't actually have this piece + p->write_dont_have(index); + } + m_predictive_pieces.erase(it); + } +#endif + // increase the total amount of failed bytes + add_failed_bytes(m_torrent_file->piece_size(index)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + ext->on_piece_failed(index); + } +#endif + + std::vector downloaders; + if (m_picker) + m_picker->get_downloaders(downloaders, index); + + // decrease the trust point of all peers that sent + // parts of this piece. + // first, build a set of all peers that participated + std::set peers; + std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); + +#if TORRENT_USE_ASSERTS + for (auto const& p : downloaders) + { + if (p && p->connection) + { + auto* peer = static_cast(p->connection); + peer->piece_failed = true; + } + } +#endif + + // did we receive this piece from a single peer? + bool const single_peer = peers.size() == 1; + + for (auto p : peers) + { + if (p == nullptr) continue; + TORRENT_ASSERT(p->in_use); + bool allow_disconnect = true; + if (p->connection) + { + auto* peer = static_cast(p->connection); + TORRENT_ASSERT(peer->m_in_use == 1337); + + // the peer implementation can ask not to be disconnected. + // this is used for web seeds for instance, to instead of + // disconnecting, mark the file as not being had. + allow_disconnect = peer->received_invalid_data(index, single_peer); + } + + if (settings().get_bool(settings_pack::use_parole_mode)) + p->on_parole = true; + + int hashfails = p->hashfails; + int trust_points = p->trust_points; + + // we decrease more than we increase, to keep the + // allowed failed/passed ratio low. + trust_points -= 2; + ++hashfails; + if (trust_points < -7) trust_points = -7; + p->trust_points = trust_points; + if (hashfails > 255) hashfails = 255; + p->hashfails = std::uint8_t(hashfails); + + // either, we have received too many failed hashes + // or this was the only peer that sent us this piece. + // if we have failed more than 3 pieces from this peer, + // don't trust it regardless. + if (p->trust_points <= -7 + || (single_peer && allow_disconnect)) + { + // we don't trust this peer anymore + // ban it. + if (m_ses.alerts().should_post()) + { + peer_id const pid = p->connection + ? p->connection->pid() : peer_id(); + m_ses.alerts().emplace_alert( + get_handle(), p->ip(), pid); + } + + // mark the peer as banned + ban_peer(p); + update_want_peers(); + inc_stats_counter(counters::banned_for_hash_failure); + + if (p->connection) + { + auto* peer = static_cast(p->connection); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** BANNING PEER: \"%s\" Too many corrupt pieces" + , print_endpoint(p->ip()).c_str()); + } + peer->peer_log(peer_log_alert::info, "BANNING_PEER", "Too many corrupt pieces"); +#endif + peer->disconnect(errors::too_many_corrupt_pieces, operation_t::bittorrent); + } + } + } + + // If m_storage isn't set here, it means we're shutting down + if (m_storage) + { + // it doesn't make much sense to fail to hash a piece + // without having a storage associated with the torrent. + // restoring the piece in the piece picker without calling + // clear piece on the disk thread will make them out of + // sync, and if we try to write more blocks to this piece + // the disk thread will barf, because it hasn't been cleared + TORRENT_ASSERT(m_storage); + + // don't allow picking any blocks from this piece + // until we're done synchronizing with the disk threads. + m_picker->lock_piece(index); + + // don't do this until after the plugins have had a chance + // to read back the blocks that failed, for blame purposes + // this way they have a chance to hit the cache + m_ses.disk_thread().async_clear_piece(m_storage, index + , std::bind(&torrent::on_piece_sync, shared_from_this(), _1)); + } + else + { + TORRENT_ASSERT(m_abort); + // it doesn't really matter what we do + // here, since we're about to destruct the + // torrent anyway. + on_piece_sync(index); + } + +#if TORRENT_USE_ASSERTS + for (auto const& p : downloaders) + { + if (p && p->connection) + { + auto* peer = static_cast(p->connection); + peer->piece_failed = false; + } + } +#endif + } + + void torrent::peer_is_interesting(peer_connection& c) + { + INVARIANT_CHECK; + + // no peer should be interesting if we're finished + TORRENT_ASSERT(!is_finished()); + + if (c.in_handshake()) return; + c.send_interested(); + if (c.has_peer_choked() + && c.allowed_fast().empty()) + return; + + if (request_a_block(*this, c)) + inc_stats_counter(counters::interesting_piece_picks); + c.send_block_requests(); + } + + void torrent::on_piece_sync(piece_index_t const piece) try + { + // the user may have called force_recheck, which clears + // the piece picker + if (!has_picker()) return; + + // unlock the piece and restore it, as if no block was + // ever downloaded for it. + m_picker->restore_piece(piece); + + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), piece); + + // we have to let the piece_picker know that + // this piece failed the check as it can restore it + // and mark it as being interesting for download + TORRENT_ASSERT(!m_picker->have_piece(piece)); + + // loop over all peers and re-request potential duplicate + // blocks to this piece + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + for (auto const& b : p->download_queue()) + { + if (b.timed_out || b.not_wanted) continue; + if (b.block.piece_index != piece) continue; + m_picker->mark_as_downloading(b.block, p->peer_info_struct() + , p->picker_options()); + } + for (auto const& b : p->request_queue()) + { + if (b.block.piece_index != piece) continue; + m_picker->mark_as_downloading(b.block, p->peer_info_struct() + , p->picker_options()); + } + } + } + catch (...) { handle_exception(); } + + void torrent::peer_has(piece_index_t const index, peer_connection const* peer) + { + if (has_picker()) + { + torrent_peer* pp = peer->peer_info_struct(); + m_picker->inc_refcount(index, pp); + } + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } + } + + // when we get a bitfield message, this is called for that piece + void torrent::peer_has(typed_bitfield const& bits + , peer_connection const* peer) + { + if (has_picker()) + { + TORRENT_ASSERT(bits.size() == torrent_file().num_pieces()); + torrent_peer* pp = peer->peer_info_struct(); + m_picker->inc_refcount(bits, pp); + } + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } + } + + void torrent::peer_has_all(peer_connection const* peer) + { + if (has_picker()) + { + torrent_peer* pp = peer->peer_info_struct(); + m_picker->inc_refcount_all(pp); + } + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } + } + + void torrent::peer_lost(typed_bitfield const& bits + , peer_connection const* peer) + { + if (has_picker()) + { + TORRENT_ASSERT(bits.size() == torrent_file().num_pieces()); + torrent_peer* pp = peer->peer_info_struct(); + m_picker->dec_refcount(bits, pp); + } + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } + } + + void torrent::peer_lost(piece_index_t const index, peer_connection const* peer) + { + if (m_picker) + { + torrent_peer* pp = peer->peer_info_struct(); + m_picker->dec_refcount(index, pp); + } + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } + } + + void torrent::abort() + { + TORRENT_ASSERT(is_single_thread()); + + if (m_abort) return; + + m_abort = true; + update_want_peers(); + update_want_tick(); + update_want_scrape(); + update_gauge(); + stop_announcing(); + + // remove from download queue + m_ses.set_queue_position(this, queue_position_t{-1}); + + if (m_peer_class > peer_class_t{0}) + { + remove_class(m_ses.peer_classes(), m_peer_class); + m_ses.peer_classes().decref(m_peer_class); + m_peer_class = peer_class_t{0}; + } + + error_code ec; + m_inactivity_timer.cancel(ec); + +#ifndef TORRENT_DISABLE_LOGGING + log_to_all_peers("aborting"); +#endif + + // disconnect all peers and close all + // files belonging to the torrents + disconnect_all(errors::torrent_aborted, operation_t::bittorrent); + + // make sure to destruct the peers immediately + on_remove_peers(); + TORRENT_ASSERT(m_connections.empty()); + + // post a message to the main thread to destruct + // the torrent object from there + if (m_storage) + { + try { + m_ses.disk_thread().async_stop_torrent(m_storage + , std::bind(&torrent::on_torrent_aborted, shared_from_this())); + } + catch (std::exception const& e) + { + TORRENT_UNUSED(e); + m_storage.reset(); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("Failed to flush disk cache: %s", e.what()); +#endif + // clients may rely on this alert to be posted, so it's probably a + // good idea to post it here, even though we failed + // TODO: 3 should this alert have an error code in it? + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + + // TODO: 2 abort lookups this torrent has made via the + // session host resolver interface + + if (!m_apply_ip_filter) + { + inc_stats_counter(counters::non_filter_torrents, -1); + m_apply_ip_filter = true; + } + + m_paused = false; + m_auto_managed = false; + update_state_list(); + for (torrent_list_index_t i{}; i != m_links.end_index(); ++i) + { + if (!m_links[i].in_list()) continue; + m_links[i].unlink(m_ses.torrent_list(i), i); + } + // don't re-add this torrent to the state-update list + m_state_subscription = false; + } + + // this is called when we're destructing non-gracefully. i.e. we're _just_ + // destructing everything. + void torrent::panic() + { + m_storage.reset(); + // if there are any other peers allocated still, we need to clear them + // now. They can't be cleared later because the allocator will already + // have been destructed + if (m_peer_list) m_peer_list->clear(); + m_connections.clear(); + m_outgoing_pids.clear(); + m_peers_to_disconnect.clear(); + m_num_uploads = 0; + m_num_connecting = 0; + m_num_connecting_seeds = 0; + } + +#ifndef TORRENT_DISABLE_SUPERSEEDING + void torrent::set_super_seeding(bool on) + { + if (on == m_super_seeding) return; + + m_super_seeding = on; + set_need_save_resume(); + state_updated(); + + if (m_super_seeding) return; + + // disable super seeding for all peers + for (auto pc : *this) + { + pc->superseed_piece(piece_index_t(-1), piece_index_t(-1)); + } + } + + // TODO: 3 this should return optional<>. piece index -1 should not be + // allowed + piece_index_t torrent::get_piece_to_super_seed(typed_bitfield const& bits) + { + // return a piece with low availability that is not in + // the bitfield and that is not currently being super + // seeded by any peer + TORRENT_ASSERT(m_super_seeding); + + // do a linear search from the first piece + int min_availability = 9999; + std::vector avail_vec; + for (auto const i : m_torrent_file->piece_range()) + { + if (bits[i]) continue; + + int availability = 0; + for (auto pc : *this) + { + if (pc->super_seeded_piece(i)) + { + // avoid super-seeding the same piece to more than one + // peer if we can avoid it. Do this by artificially + // increase the availability + availability = 999; + break; + } + if (pc->has_piece(i)) ++availability; + } + if (availability > min_availability) continue; + if (availability == min_availability) + { + avail_vec.push_back(i); + continue; + } + TORRENT_ASSERT(availability < min_availability); + min_availability = availability; + avail_vec.clear(); + avail_vec.push_back(i); + } + + if (avail_vec.empty()) return piece_index_t(-1); + return avail_vec[random(std::uint32_t(avail_vec.size() - 1))]; + } +#endif + + void torrent::on_files_deleted(storage_error const& error) try + { + TORRENT_ASSERT(is_single_thread()); + + if (error) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , error.ec, m_torrent_file->info_hash()); + } + else + { + alerts().emplace_alert(get_handle(), m_torrent_file->info_hash()); + } + } + catch (...) { handle_exception(); } + + void torrent::on_file_renamed(std::string const& filename + , file_index_t const file_idx + , storage_error const& error) try + { + TORRENT_ASSERT(is_single_thread()); + + if (error) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , file_idx, error.ec); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , filename, file_idx); + m_torrent_file->rename_file(file_idx, filename); + } + } + catch (...) { handle_exception(); } + + void torrent::on_torrent_paused() try + { + TORRENT_ASSERT(is_single_thread()); + + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + catch (...) { handle_exception(); } + +#if TORRENT_ABI_VERSION == 1 + std::string torrent::tracker_login() const + { + if (m_username.empty() && m_password.empty()) return ""; + return m_username + ":" + m_password; + } +#endif + + std::uint32_t torrent::tracker_key() const + { + uintptr_t const self = reinterpret_cast(this); + uintptr_t const ses = reinterpret_cast(&m_ses); + std::uint32_t const storage = m_storage + ? static_cast(static_cast(m_storage)) + : 0; + sha1_hash const h = hasher(reinterpret_cast(&self), sizeof(self)) + .update(reinterpret_cast(&storage), sizeof(storage)) + .update(reinterpret_cast(&ses), sizeof(ses)) + .final(); + unsigned char const* ptr = &h[0]; + return detail::read_uint32(ptr); + } + +#ifndef TORRENT_DISABLE_STREAMING + void torrent::cancel_non_critical() + { + std::set time_critical; + for (auto const& p : m_time_critical_pieces) + time_critical.insert(p.piece); + + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + // for each peer, go through its download and request queue + // and cancel everything, except pieces that are time critical + + // make a copy of the download queue since we may be cancelling entries + // from it from within the loop + std::vector dq = p->download_queue(); + for (auto const& k : dq) + { + if (time_critical.count(k.block.piece_index)) continue; + if (k.not_wanted || k.timed_out) continue; + p->cancel_request(k.block, true); + } + + // make a copy of the download queue since we may be cancelling entries + // from it from within the loop + std::vector rq = p->request_queue(); + for (auto const& k : rq) + { + if (time_critical.count(k.block.piece_index)) continue; + p->cancel_request(k.block, true); + } + } + } + + void torrent::set_piece_deadline(piece_index_t const piece, int const t + , deadline_flags_t const flags) + { + INVARIANT_CHECK; + + TORRENT_ASSERT_PRECOND(piece >= piece_index_t(0)); + TORRENT_ASSERT_PRECOND(valid_metadata()); + TORRENT_ASSERT_PRECOND(valid_metadata() && piece < m_torrent_file->end_piece()); + + if (m_abort || !valid_metadata() + || piece < piece_index_t(0) + || piece >= m_torrent_file->end_piece()) + { + // failed + if (flags & torrent_handle::alert_when_available) + { + m_ses.alerts().emplace_alert( + get_handle(), piece, error_code(boost::system::errc::operation_canceled, generic_category())); + } + return; + } + + time_point const deadline = aux::time_now() + milliseconds(t); + + // if we already have the piece, no need to set the deadline. + // however, if the user asked to get the piece data back, we still + // need to read it and post it back to the user + if (is_seed() || (has_picker() && m_picker->has_piece_passed(piece))) + { + if (flags & torrent_handle::alert_when_available) + read_piece(piece); + return; + } + + // if this is the first time critical piece we add. in order to make it + // react quickly, cancel all the currently outstanding requests + if (m_time_critical_pieces.empty()) + { + // defer this by posting it to the end of the message queue. + // this gives the client a chance to specify multiple time-critical + // pieces before libtorrent cancels requests + auto self = shared_from_this(); + m_ses.get_io_service().post([self] { self->wrap(&torrent::cancel_non_critical); }); + } + + for (auto i = m_time_critical_pieces.begin() + , end(m_time_critical_pieces.end()); i != end; ++i) + { + if (i->piece != piece) continue; + i->deadline = deadline; + i->flags = flags; + + // resort i since deadline might have changed + while (std::next(i) != m_time_critical_pieces.end() && i->deadline > std::next(i)->deadline) + { + std::iter_swap(i, std::next(i)); + ++i; + } + while (i != m_time_critical_pieces.begin() && i->deadline < std::prev(i)->deadline) + { + std::iter_swap(i, std::prev(i)); + --i; + } + // just in case this piece had priority 0 + download_priority_t prev_prio = m_picker->piece_priority(piece); + m_picker->set_piece_priority(piece, top_priority); + if (prev_prio == dont_download) update_gauge(); + return; + } + + need_picker(); + + time_critical_piece p; + p.first_requested = min_time(); + p.last_requested = min_time(); + p.flags = flags; + p.deadline = deadline; + p.peers = 0; + p.piece = piece; + auto const critical_piece_it = std::upper_bound(m_time_critical_pieces.begin() + , m_time_critical_pieces.end(), p); + m_time_critical_pieces.insert(critical_piece_it, p); + + // just in case this piece had priority 0 + download_priority_t prev_prio = m_picker->piece_priority(piece); + m_picker->set_piece_priority(piece, top_priority); + if (prev_prio == dont_download) update_gauge(); + + piece_picker::downloading_piece pi; + m_picker->piece_info(piece, pi); + if (pi.requested == 0) return; + // this means we have outstanding requests (or queued + // up requests that haven't been sent yet). Promote them + // to deadline pieces immediately + std::vector downloaders; + m_picker->get_downloaders(downloaders, piece); + + int block = 0; + for (auto i = downloaders.begin() + , end(downloaders.end()); i != end; ++i, ++block) + { + torrent_peer* tp = *i; + if (tp == nullptr || tp->connection == nullptr) continue; + auto* peer = static_cast(tp->connection); + peer->make_time_critical(piece_block(piece, block)); + } + } + + void torrent::reset_piece_deadline(piece_index_t piece) + { + remove_time_critical_piece(piece); + } + + void torrent::remove_time_critical_piece(piece_index_t const piece, bool const finished) + { + for (auto i = m_time_critical_pieces.begin(), end(m_time_critical_pieces.end()); + i != end; ++i) + { + if (i->piece != piece) continue; + if (finished) + { + if (i->flags & torrent_handle::alert_when_available) + { + read_piece(i->piece); + } + + // if first_requested is min_time(), it wasn't requested as a critical piece + // and we shouldn't adjust any average download times + if (i->first_requested != min_time()) + { + // update the average download time and average + // download time deviation + int const dl_time = aux::numeric_cast(total_milliseconds(aux::time_now() - i->first_requested)); + + if (m_average_piece_time == 0) + { + m_average_piece_time = dl_time; + } + else + { + int diff = std::abs(dl_time - m_average_piece_time); + if (m_piece_time_deviation == 0) m_piece_time_deviation = diff; + else m_piece_time_deviation = (m_piece_time_deviation * 9 + diff) / 10; + + m_average_piece_time = (m_average_piece_time * 9 + dl_time) / 10; + } + } + } + else if (i->flags & torrent_handle::alert_when_available) + { + // post an empty read_piece_alert to indicate it failed + alerts().emplace_alert( + get_handle(), piece, error_code(boost::system::errc::operation_canceled, generic_category())); + } + if (has_picker()) m_picker->set_piece_priority(piece, low_priority); + m_time_critical_pieces.erase(i); + return; + } + } + + void torrent::clear_time_critical() + { + for (auto i = m_time_critical_pieces.begin(); i != m_time_critical_pieces.end();) + { + if (i->flags & torrent_handle::alert_when_available) + { + // post an empty read_piece_alert to indicate it failed + m_ses.alerts().emplace_alert( + get_handle(), i->piece, error_code(boost::system::errc::operation_canceled, generic_category())); + } + if (has_picker()) m_picker->set_piece_priority(i->piece, low_priority); + i = m_time_critical_pieces.erase(i); + } + } + + // remove time critical pieces where priority is 0 + void torrent::remove_time_critical_pieces(aux::vector const& priority) + { + for (auto i = m_time_critical_pieces.begin(); i != m_time_critical_pieces.end();) + { + if (priority[i->piece] == dont_download) + { + if (i->flags & torrent_handle::alert_when_available) + { + // post an empty read_piece_alert to indicate it failed + alerts().emplace_alert( + get_handle(), i->piece, error_code(boost::system::errc::operation_canceled, generic_category())); + } + i = m_time_critical_pieces.erase(i); + continue; + } + ++i; + } + } +#endif // TORRENT_DISABLE_STREAMING + + void torrent::piece_availability(aux::vector& avail) const + { + INVARIANT_CHECK; + + TORRENT_ASSERT(valid_metadata()); + if (!has_picker()) + { + avail.clear(); + return; + } + + m_picker->get_availability(avail); + } + + void torrent::set_piece_priority(piece_index_t const index + , download_priority_t const priority) + { +// INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + if (!valid_metadata()) + { + debug_log("*** SET_PIECE_PRIORITY [ idx: %d prio: %d ignored. " + "no metadata yet ]", static_cast(index) + , static_cast(priority)); + } +#endif + if (!valid_metadata() || is_seed()) return; + + // this call is only valid on torrents with metadata + if (index < piece_index_t(0) || index >= m_torrent_file->end_piece()) + { + return; + } + + need_picker(); + + bool const was_finished = is_finished(); + bool const filter_updated = m_picker->set_piece_priority(index, priority); + + update_gauge(); + + if (filter_updated) + { + update_peer_interest(was_finished); +#ifndef TORRENT_DISABLE_STREAMING + if (priority == dont_download) remove_time_critical_piece(index); +#endif // TORRENT_DISABLE_STREAMING + } + + } + + download_priority_t torrent::piece_priority(piece_index_t const index) const + { +// INVARIANT_CHECK; + + if (!has_picker()) return default_priority; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (index < piece_index_t(0) || index >= m_torrent_file->end_piece()) + { + TORRENT_ASSERT_FAIL(); + return dont_download; + } + + return m_picker->piece_priority(index); + } + + void torrent::prioritize_piece_list(std::vector> const& pieces) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (is_seed()) return; + + need_picker(); + + bool filter_updated = false; + bool const was_finished = is_finished(); + for (auto const& p : pieces) + { + static_assert(std::is_unsigned::value + , "we need assert p.second >= dont_download"); + TORRENT_ASSERT(p.second <= top_priority); + TORRENT_ASSERT(p.first >= piece_index_t(0)); + TORRENT_ASSERT(p.first < m_torrent_file->end_piece()); + + if (p.first < piece_index_t(0) + || p.first >= m_torrent_file->end_piece() + || p.second > top_priority) + { + static_assert(std::is_unsigned::value + , "we need additional condition: p.second < dont_download"); + continue; + } + + filter_updated |= m_picker->set_piece_priority(p.first, p.second); + } + update_gauge(); + if (filter_updated) + { + // we need to save this new state + set_need_save_resume(); + + update_peer_interest(was_finished); + } + + state_updated(); + } + + void torrent::prioritize_pieces(aux::vector const& pieces) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (is_seed()) return; + + if (!valid_metadata()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** PRIORITIZE_PIECES [ ignored. no metadata yet ]"); +#endif + return; + } + + need_picker(); + + piece_index_t index(0); + bool filter_updated = false; + bool const was_finished = is_finished(); + for (auto prio : pieces) + { + static_assert(std::is_unsigned::value + , "we need assert prio >= dont_download"); + TORRENT_ASSERT(prio <= top_priority); + filter_updated |= m_picker->set_piece_priority(index, prio); + ++index; + } + update_gauge(); + update_want_tick(); + + if (filter_updated) + { + // we need to save this new state + set_need_save_resume(); + + update_peer_interest(was_finished); +#ifndef TORRENT_DISABLE_STREAMING + remove_time_critical_pieces(pieces); +#endif + } + + state_updated(); + update_state_list(); + } + + void torrent::piece_priorities(aux::vector* pieces) const + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + if (!valid_metadata()) + { + pieces->clear(); + return; + } + + if (!has_picker()) + { + pieces->clear(); + pieces->resize(m_torrent_file->num_pieces(), default_priority); + return; + } + + TORRENT_ASSERT(m_picker); + m_picker->piece_priorities(*pieces); + } + + namespace + { + aux::vector fix_priorities( + aux::vector input + , file_storage const* fs) + { + if (fs) input.resize(fs->num_files(), default_priority); + + for (file_index_t i : input.range()) + { + // initialize pad files to priority 0 + if (input[i] > dont_download && fs && fs->pad_file_at(i)) + input[i] = dont_download; + else if (input[i] > top_priority) + input[i] = top_priority; + } + + return input; + } + } + + void torrent::on_file_priority(storage_error const& err + , aux::vector prios) + { + m_outstanding_file_priority = false; + COMPLETE_ASYNC("file_priority"); + if (m_file_priority != prios) + { + m_file_priority = std::move(prios); +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_share_mode) + recalc_share_mode(); +#endif + } + + if (err) + { + // in this case, some file priorities failed to get set + if (alerts().should_post()) + alerts().emplace_alert(err.ec + , resolve_filename(err.file()), err.operation, get_handle()); + + set_error(err.ec, err.file()); + pause(); + } + else if (!m_deferred_file_priorities.empty() && !m_abort) + { + auto new_priority = m_file_priority; + // resize the vector if we have to. The last item in the map has the + // highest file index. + auto const max_idx = std::prev(m_deferred_file_priorities.end())->first; + if (new_priority.end_index() <= max_idx) + { + // any unallocated slot is assumed to have the default priority + new_priority.resize(static_cast(max_idx) + 1, default_priority); + } + for (auto const& p : m_deferred_file_priorities) + { + file_index_t const index = p.first; + download_priority_t const prio = p.second; + new_priority[index] = prio; + } + m_deferred_file_priorities.clear(); + prioritize_files(std::move(new_priority)); + } + } + + void torrent::prioritize_files(aux::vector files) + { + INVARIANT_CHECK; + + auto new_priority = fix_priorities(std::move(files) + , valid_metadata() ? &m_torrent_file->files() : nullptr); + + // storage may be NULL during shutdown + if (m_storage) + { + // the update of m_file_priority is deferred until the disk job comes + // back, but to preserve sanity and consistency, the piece priorities are + // updated immediately. If, on the off-chance, there's a disk failure, the + // piece priorities still stay the same, but the file priorities are + // possibly not fully updated. + update_piece_priorities(new_priority); + + m_outstanding_file_priority = true; + ADD_OUTSTANDING_ASYNC("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage + , std::move(new_priority), std::bind(&torrent::on_file_priority, shared_from_this(), _1, _2)); + } + else + { + m_file_priority = std::move(new_priority); + } + } + + void torrent::set_file_priority(file_index_t const index + , download_priority_t prio) + { + INVARIANT_CHECK; + + // setting file priority on a torrent that doesn't have metadata yet is + // similar to having passed in file priorities through add_torrent_params. + // we store the priorities in m_file_priority until we get the metadata + if (index < file_index_t(0) + || (valid_metadata() && index >= m_torrent_file->files().end_file())) + { + return; + } + + prio = aux::clamp(prio, dont_download, top_priority); + + if (m_outstanding_file_priority) + { + m_deferred_file_priorities[index] = prio; + return; + } + + auto new_priority = m_file_priority; + if (new_priority.end_index() <= index) + { + // any unallocated slot is assumed to have the default priority + new_priority.resize(static_cast(index) + 1, default_priority); + } + + new_priority[index] = prio; + + // storage may be nullptr during shutdown + if (m_storage) + { + // the update of m_file_priority is deferred until the disk job comes + // back, but to preserve sanity and consistency, the piece priorities are + // updated immediately. If, on the off-chance, there's a disk failure, the + // piece priorities still stay the same, but the file priorities are + // possibly not fully updated. + update_piece_priorities(new_priority); + m_outstanding_file_priority = true; + ADD_OUTSTANDING_ASYNC("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage + , std::move(new_priority), std::bind(&torrent::on_file_priority, shared_from_this(), _1, _2)); + } + else + { + m_file_priority = std::move(new_priority); + } + } + + download_priority_t torrent::file_priority(file_index_t const index) const + { + TORRENT_ASSERT_PRECOND(index >= file_index_t(0)); + if (index < file_index_t(0)) return dont_download; + + // if we have metadata, perform additional checks + if (valid_metadata()) + { + file_storage const& fs = m_torrent_file->files(); + TORRENT_ASSERT_PRECOND(index < fs.end_file()); + if (index >= fs.end_file()) return dont_download; + + // pad files always have priority 0 + if (fs.pad_file_at(index)) return dont_download; + } + + // any unallocated slot is assumed to have the default priority + if (m_file_priority.end_index() <= index) return default_priority; + + return m_file_priority[index]; + } + + void torrent::file_priorities(aux::vector* files) const + { + INVARIANT_CHECK; + + files->assign(m_file_priority.begin(), m_file_priority.end()); + + if (!valid_metadata()) + { + return; + } + + files->resize(m_torrent_file->num_files(), default_priority); + } + + void torrent::update_piece_priorities( + aux::vector const& file_prios) + { + INVARIANT_CHECK; + + if (m_torrent_file->num_pieces() == 0) return; + + bool need_update = false; + std::int64_t position = 0; + // initialize the piece priorities to 0, then only allow + // setting higher priorities + aux::vector pieces(aux::numeric_cast( + m_torrent_file->num_pieces()), dont_download); + file_storage const& fs = m_torrent_file->files(); + for (auto const i : fs.file_range()) + { + std::int64_t const size = m_torrent_file->files().file_size(i); + if (size == 0) continue; + position += size; + + // pad files always have priority 0 + download_priority_t const file_prio + = fs.pad_file_at(i) ? dont_download + : i >= file_prios.end_index() ? default_priority + : file_prios[i]; + + if (file_prio == dont_download) + { + // the pieces already start out as priority 0, no need to update + // the pieces vector in this case + need_update = true; + continue; + } + + // mark all pieces of the file with this file's priority + // but only if the priority is higher than the pieces + // already set (to avoid problems with overlapping pieces) + piece_index_t start; + piece_index_t end; + std::tie(start, end) = file_piece_range_inclusive(fs, i); + + // if one piece spans several files, we might + // come here several times with the same start_piece, end_piece + for (piece_index_t p = start; p < end; ++p) + pieces[p] = std::max(pieces[p], file_prio); + + need_update = true; + } + if (need_update) prioritize_pieces(pieces); + } + + // this is called when piece priorities have been updated + // updates the interested flag in peers + void torrent::update_peer_interest(bool const was_finished) + { + for (auto i = begin(); i != end();) + { + peer_connection* p = *i; + // update_interest may disconnect the peer and + // invalidate the iterator + ++i; + p->update_interest(); + } + + if (!is_downloading_state(m_state)) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** UPDATE_PEER_INTEREST [ skipping, state: %d ]" + , int(m_state)); +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** UPDATE_PEER_INTEREST [ finished: %d was_finished %d ]" + , is_finished(), was_finished); + } +#endif + + // the torrent just became finished + if (!was_finished && is_finished()) + { + finished(); + } + else if (was_finished && !is_finished()) + { + // if we used to be finished, but we aren't anymore + // we may need to connect to peers again + resume_download(); + } + } + + void torrent::replace_trackers(std::vector const& urls) + { + m_trackers.clear(); + std::remove_copy_if(urls.begin(), urls.end(), back_inserter(m_trackers) + , [](announce_entry const& e) { return e.url.empty(); }); + + m_last_working_tracker = -1; + for (auto& t : m_trackers) + { + t.endpoints.clear(); + if (t.source == 0) t.source = announce_entry::source_client; +#if TORRENT_ABI_VERSION == 1 + t.complete_sent = m_complete_sent; +#endif + for (auto& aep : t.endpoints) + aep.complete_sent = m_complete_sent; + } + + if (settings().get_bool(settings_pack::prefer_udp_trackers)) + prioritize_udp_trackers(); + + if (!m_trackers.empty()) announce_with_tracker(); + + set_need_save_resume(); + } + + void torrent::prioritize_udp_trackers() + { + // look for udp-trackers + for (auto i = m_trackers.begin(), end(m_trackers.end()); i != end; ++i) + { + if (i->url.substr(0, 6) != "udp://") continue; + // now, look for trackers with the same hostname + // that is has higher priority than this one + // if we find one, swap with the udp-tracker + error_code ec; + std::string udp_hostname; + using std::ignore; + std::tie(ignore, ignore, udp_hostname, ignore, ignore) + = parse_url_components(i->url, ec); + for (auto j = m_trackers.begin(); j != i; ++j) + { + std::string hostname; + std::tie(ignore, ignore, hostname, ignore, ignore) + = parse_url_components(j->url, ec); + if (hostname != udp_hostname) continue; + if (j->url.substr(0, 6) == "udp://") continue; + using std::swap; + using std::iter_swap; + swap(i->tier, j->tier); + iter_swap(i, j); + break; + } + } + } + + bool torrent::add_tracker(announce_entry const& url) + { + if (url.url.empty()) return false; + if(auto k = find_tracker(url.url)) + { + k->source |= url.source; + return false; + } + auto k = std::upper_bound(m_trackers.begin(), m_trackers.end(), url + , [] (announce_entry const& lhs, announce_entry const& rhs) + { return lhs.tier < rhs.tier; }); + if (k - m_trackers.begin() < m_last_working_tracker) ++m_last_working_tracker; + k = m_trackers.insert(k, url); + if (k->source == 0) k->source = announce_entry::source_client; + if (m_announcing && !m_trackers.empty()) announce_with_tracker(); + return true; + } + + bool torrent::choke_peer(peer_connection& c) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!c.is_choked()); + TORRENT_ASSERT(!c.ignore_unchoke_slots()); + TORRENT_ASSERT(m_num_uploads > 0); + if (!c.send_choke()) return false; + --m_num_uploads; + state_updated(); + return true; + } + + bool torrent::unchoke_peer(peer_connection& c, bool optimistic) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_graceful_pause_mode); + TORRENT_ASSERT(c.is_choked()); + TORRENT_ASSERT(!c.ignore_unchoke_slots()); + // when we're unchoking the optimistic slots, we might + // exceed the limit temporarily while we're iterating + // over the peers + if (m_num_uploads >= m_max_uploads && !optimistic) return false; + if (!c.send_unchoke()) return false; + ++m_num_uploads; + state_updated(); + return true; + } + + void torrent::trigger_unchoke() noexcept + { + m_ses.trigger_unchoke(); + } + + void torrent::trigger_optimistic_unchoke() noexcept + { + m_ses.trigger_optimistic_unchoke(); + } + + void torrent::cancel_block(piece_block block) + { + INVARIANT_CHECK; + + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + p->cancel_request(block); + } + } + +#ifdef TORRENT_USE_OPENSSL + namespace { + std::string password_callback(int length, boost::asio::ssl::context::password_purpose p + , std::string pw) + { + TORRENT_UNUSED(length); + + if (p != boost::asio::ssl::context::for_reading) return ""; + return pw; + } + } + + // certificate is a filename to a .pem file which is our + // certificate. The certificate must be signed by the root + // cert of the torrent file. any peer we connect to or that + // connect to use must present a valid certificate signed + // by the torrent root cert as well + void torrent::set_ssl_cert(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase) + { + if (!m_ssl_ctx) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , errors::not_an_ssl_torrent, ""); + return; + } + + using boost::asio::ssl::context; + error_code ec; + m_ssl_ctx->set_password_callback(std::bind(&password_callback, _1, _2, passphrase), ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, ""); + } + m_ssl_ctx->use_certificate_file(certificate, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, certificate); + } +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("*** use certificate file: %s", ec.message().c_str()); +#endif + m_ssl_ctx->use_private_key_file(private_key, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, private_key); + } +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("*** use private key file: %s", ec.message().c_str()); +#endif + m_ssl_ctx->use_tmp_dh_file(dh_params, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, dh_params); + } +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("*** use DH file: %s", ec.message().c_str()); +#endif + } + + void torrent::set_ssl_cert_buffer(std::string const& certificate + , std::string const& private_key + , std::string const& dh_params) + { + if (!m_ssl_ctx) return; + + boost::asio::const_buffer certificate_buf(certificate.c_str(), certificate.size()); + + using boost::asio::ssl::context; + error_code ec; + m_ssl_ctx->use_certificate(certificate_buf, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, "[certificate]"); + } + + boost::asio::const_buffer private_key_buf(private_key.c_str(), private_key.size()); + m_ssl_ctx->use_private_key(private_key_buf, context::pem, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, "[private key]"); + } + + boost::asio::const_buffer dh_params_buf(dh_params.c_str(), dh_params.size()); + m_ssl_ctx->use_tmp_dh(dh_params_buf, ec); + if (ec) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec, "[dh params]"); + } + } + +#endif + + void torrent::on_exception(std::exception const&) + { + set_error(errors::no_memory, torrent_status::error_file_none); + } + + void torrent::on_error(error_code const& ec) + { + set_error(ec, torrent_status::error_file_none); + } + + void torrent::remove_connection(peer_connection const* p) + { + TORRENT_ASSERT(m_iterating_connections == 0); + auto const i = sorted_find(m_connections, p); + if (i != m_connections.end()) + m_connections.erase(i); + } + + void torrent::remove_peer(std::shared_ptr p) noexcept + { + TORRENT_ASSERT(p); + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(std::count(m_peers_to_disconnect.begin() + , m_peers_to_disconnect.end(), p) == 0); + + auto it = m_outgoing_pids.find(p->our_pid()); + if (it != m_outgoing_pids.end()) + { + m_outgoing_pids.erase(it); + } + + // only schedule the peer for actual removal if in fact + // we can be sure peer_connection will be kept alive until + // the deferred function is called. If a peer_connection + // has not associated torrent, the session_impl object may + // remove it at any time, which may be while the non-owning + // pointer in m_peers_to_disconnect (if added to it) is + // waiting for the deferred function to be called. + // + // one example of this situation is if for example, this + // function is called from the attach_peer path and fail to + // do so because of too many connections. + bool const is_attached = p->associated_torrent().lock().get() == this; + if (is_attached) + { + std::weak_ptr weak_t = shared_from_this(); + TORRENT_ASSERT_VAL(m_peers_to_disconnect.capacity() > m_peers_to_disconnect.size() + , m_peers_to_disconnect.capacity()); + m_peers_to_disconnect.push_back(p); + m_deferred_disconnect.post(m_ses.get_io_service(), aux::make_handler([=]() + { + std::shared_ptr t = weak_t.lock(); + if (t) t->on_remove_peers(); + }, m_deferred_handler_storage, *this)); + } + else + { + // if the peer was inserted in m_connections but instructed to + // be removed from this torrent, just remove it from it, see + // attach_peer logic. + remove_connection(p.get()); + } + + torrent_peer* pp = p->peer_info_struct(); + if (ready_for_connections()) + { + TORRENT_ASSERT(p->associated_torrent().lock().get() == nullptr + || p->associated_torrent().lock().get() == this); + + if (has_picker()) + { + if (p->is_seed()) + { + m_picker->dec_refcount_all(pp); + } + else + { + auto const& pieces = p->get_bitfield(); + TORRENT_ASSERT(pieces.count() <= pieces.size()); + m_picker->dec_refcount(pieces, pp); + } + } + } + + if (!p->is_choked() && !p->ignore_unchoke_slots()) + { + --m_num_uploads; + trigger_unchoke(); + } + + if (pp) + { + if (pp->optimistically_unchoked) + { + pp->optimistically_unchoked = false; + m_stats_counters.inc_stats_counter( + counters::num_peers_up_unchoked_optimistic, -1); + trigger_optimistic_unchoke(); + } + + TORRENT_ASSERT(pp->prev_amount_upload == 0); + TORRENT_ASSERT(pp->prev_amount_download == 0); + pp->prev_amount_download += aux::numeric_cast(p->statistics().total_payload_download() >> 10); + pp->prev_amount_upload += aux::numeric_cast(p->statistics().total_payload_upload() >> 10); + + // only decrement the seed count if the peer completed attaching to the torrent + // otherwise the seed count did not get incremented for this peer + if (is_attached && pp->seed) + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + + if (pp->connection && m_peer_list) + { + torrent_state st = get_peer_list_state(); + m_peer_list->connection_closed(*p, m_ses.session_time(), &st); + peers_erased(st.erased); + } + } + + p->set_peer_info(nullptr); + + update_want_peers(); + update_want_tick(); + } + + void torrent::on_remove_peers() noexcept + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + +#if TORRENT_USE_ASSERTS + auto const num = m_peers_to_disconnect.size(); +#endif + for (auto const& p : m_peers_to_disconnect) + { + TORRENT_ASSERT(p); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + + remove_connection(p.get()); + m_ses.close_connection(p.get()); + } + TORRENT_ASSERT_VAL(m_peers_to_disconnect.size() == num, m_peers_to_disconnect.size() - num); + m_peers_to_disconnect.clear(); + + if (m_graceful_pause_mode && m_connections.empty()) + { + // we're in graceful pause mode and this was the last peer we + // disconnected. This will clear the graceful_pause_mode and post the + // torrent_paused_alert. + TORRENT_ASSERT(is_paused()); + + // this will post torrent_paused alert + set_paused(true); + } + + update_want_peers(); + update_want_tick(); + } + + void torrent::remove_web_seed_iter(std::list::iterator web) + { + if (web->resolving) + { + web->removed = true; + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("removing web seed: \"%s\"", web->url.c_str()); +#endif + + auto* peer = static_cast(web->peer_info.connection); + if (peer != nullptr) + { + // if we have a connection for this web seed, we also need to + // disconnect it and clear its reference to the peer_info object + // that's part of the web_seed_t we're about to remove + TORRENT_ASSERT(peer->m_in_use == 1337); + peer->disconnect(boost::asio::error::operation_aborted, operation_t::bittorrent); + peer->set_peer_info(nullptr); + } + if (has_picker()) picker().clear_peer(&web->peer_info); + + m_web_seeds.erase(web); + } + + update_want_tick(); + } + + void torrent::connect_to_url_seed(std::list::iterator web) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + TORRENT_ASSERT(!web->resolving); + if (web->resolving) return; + + if (num_peers() >= int(m_max_connections) + || m_ses.num_connections() >= settings().get_int(settings_pack::connections_limit)) + return; + + std::string protocol; + std::string auth; + std::string hostname; + int port; + std::string path; + error_code ec; + std::tie(protocol, auth, hostname, port, path) + = parse_url_components(web->url, ec); + if (port == -1) + { + port = protocol == "http" ? 80 : 443; + } + + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("failed to parse web seed url: %s", ec.message().c_str()); +#endif + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, ec); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + if (web->peer_info.banned) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("banned web seed: %s", web->url.c_str()); +#endif + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle(), web->url + , libtorrent::errors::peer_banned); + } + // never try it again + remove_web_seed_iter(web); + return; + } + +#ifdef TORRENT_USE_OPENSSL + if (protocol != "http" && protocol != "https") +#else + if (protocol != "http") +#endif + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle(), web->url, errors::unsupported_url_protocol); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + if (hostname.empty()) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle(), web->url + , errors::invalid_hostname); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + if (port == 0) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle(), web->url + , errors::invalid_port); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + if (m_ses.get_port_filter().access(std::uint16_t(port)) & port_filter::blocked) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, errors::port_blocked); + } + // never try it again + remove_web_seed_iter(web); + return; + } + + if (!web->endpoints.empty()) + { + connect_web_seed(web, web->endpoints.front()); + return; + } + + aux::proxy_settings const& ps = m_ses.proxy(); + if ((ps.type == settings_pack::http + || ps.type == settings_pack::http_pw) + && ps.proxy_peer_connections) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("resolving proxy for web seed: %s", web->url.c_str()); +#endif + + auto self = shared_from_this(); + std::uint16_t const proxy_port = ps.port; + + // use proxy + web->resolving = true; + m_ses.get_resolver().async_resolve(ps.hostname, resolver_interface::abort_on_shutdown + , [self, web, proxy_port](error_code const& e, std::vector
    const& addrs) + { + self->wrap(&torrent::on_proxy_name_lookup, e, addrs, web, proxy_port); + }); + } + else if (ps.proxy_hostnames + && (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw) + && ps.proxy_peer_connections) + { + connect_web_seed(web, {address(), std::uint16_t(port)}); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("resolving web seed: \"%s\" %s", hostname.c_str(), web->url.c_str()); +#endif + + auto self = shared_from_this(); + web->resolving = true; + + m_ses.get_resolver().async_resolve(hostname, resolver_interface::abort_on_shutdown + , [self, web, port](error_code const& e, std::vector
    const& addrs) + { + self->wrap(&torrent::on_name_lookup, e, addrs, port, web); + }); + } + } + + void torrent::on_proxy_name_lookup(error_code const& e + , std::vector
    const& addrs + , std::list::iterator web, int port) try + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + TORRENT_ASSERT(web->resolving); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("completed resolve proxy hostname for: %s", web->url.c_str()); + if (e && should_log()) + debug_log("proxy name lookup error: %s", e.message().c_str()); +#endif + web->resolving = false; + + if (web->removed) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("removed web seed"); +#endif + remove_web_seed_iter(web); + return; + } + + if (m_abort) return; + + if (e || addrs.empty()) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, e); + } + + // the name lookup failed for the http host. Don't try + // this host again + remove_web_seed_iter(web); + return; + } + + if (m_ses.is_aborted()) return; + + if (num_peers() >= int(m_max_connections) + || m_ses.num_connections() >= settings().get_int(settings_pack::connections_limit)) + return; + + tcp::endpoint a(addrs[0], std::uint16_t(port)); + + std::string hostname; + error_code ec; + std::string protocol; + std::tie(protocol, std::ignore, hostname, port, std::ignore) + = parse_url_components(web->url, ec); + if (port == -1) port = protocol == "http" ? 80 : 443; + + if (ec) + { + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , web->url, ec); + } + remove_web_seed_iter(web); + return; + } + + if (m_ip_filter && m_ip_filter->access(a.address()) & ip_filter::blocked) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , a, peer_blocked_alert::ip_filter); + return; + } + + auto self = shared_from_this(); + web->resolving = true; + m_ses.get_resolver().async_resolve(hostname, resolver_interface::abort_on_shutdown + , [self, web, port](error_code const& err, std::vector
    const& addr) + { + self->wrap(&torrent::on_name_lookup, err, addr, port, web); + }); + } + catch (...) { handle_exception(); } + + void torrent::on_name_lookup(error_code const& e + , std::vector
    const& addrs + , int const port + , std::list::iterator web) try + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + + TORRENT_ASSERT(web->resolving); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("completed resolve: %s", web->url.c_str()); +#endif + web->resolving = false; + if (web->removed) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("removed web seed"); +#endif + remove_web_seed_iter(web); + return; + } + + if (m_abort) return; + + if (e || addrs.empty()) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), web->url, e); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** HOSTNAME LOOKUP FAILED: %s: (%d) %s" + , web->url.c_str(), e.value(), e.message().c_str()); + } +#endif + + // unavailable, retry in `settings_pack::web_seed_name_lookup_retry` seconds + web->retry = aux::time_now32() + + seconds32(settings().get_int(settings_pack::web_seed_name_lookup_retry)); + return; + } + + for (auto const& addr : addrs) + { + // fill in the peer struct's address field + web->endpoints.emplace_back(addr, std::uint16_t(port)); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log(" -> %s", print_endpoint(tcp::endpoint(addr, std::uint16_t(port))).c_str()); +#endif + } + + if (num_peers() >= int(m_max_connections) + || m_ses.num_connections() >= settings().get_int(settings_pack::connections_limit)) + return; + + connect_web_seed(web, web->endpoints.front()); + } + catch (...) { handle_exception(); } + + void torrent::connect_web_seed(std::list::iterator web, tcp::endpoint a) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(is_single_thread()); + if (m_abort) return; + + if (m_ip_filter && m_ip_filter->access(a.address()) & ip_filter::blocked) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , a, peer_blocked_alert::ip_filter); + return; + } + + TORRENT_ASSERT(!web->resolving); + TORRENT_ASSERT(web->peer_info.connection == nullptr); + + if (is_v4(a)) + { + web->peer_info.addr = a.address().to_v4(); + web->peer_info.port = a.port(); + } + + if (is_paused()) return; + if (m_ses.is_aborted()) return; + if (is_upload_only()) return; + + // this web seed may have redirected all files to other URLs, leaving it + // having no file left, and there's no longer any point in connecting to + // it. + if (!web->have_files.empty() + && web->have_files.none_set()) return; + + std::shared_ptr s + = std::make_shared(m_ses.get_io_service()); + if (!s) return; + + void* userdata = nullptr; +#ifdef TORRENT_USE_OPENSSL + const bool ssl = string_begins_no_case("https://", web->url.c_str()); + if (ssl) + { + userdata = m_ssl_ctx.get(); + if (!userdata) userdata = m_ses.ssl_ctx(); + } +#endif + bool ret = instantiate_connection(m_ses.get_io_service(), m_ses.proxy() + , *s, userdata, nullptr, true, false); + (void)ret; + TORRENT_ASSERT(ret); + + if (s->get()) + { + // the web seed connection will talk immediately to + // the proxy, without requiring CONNECT support + s->get()->set_no_connect(true); + } + + std::string hostname; + error_code ec; + using std::ignore; + std::tie(ignore, ignore, hostname, ignore, ignore) + = parse_url_components(web->url, ec); + if (ec) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), web->url, ec); + return; + } + + bool const is_ip = is_ip_address(hostname); + if (is_ip) a.address(make_address(hostname, ec)); + bool const proxy_hostnames = settings().get_bool(settings_pack::proxy_hostnames) + && !is_ip; + + if (proxy_hostnames + && (s->get() +#ifdef TORRENT_USE_OPENSSL + || s->get>() +#endif + )) + { + // we're using a socks proxy and we're resolving + // hostnames through it + socks5_stream* str = +#ifdef TORRENT_USE_OPENSSL + ssl ? &s->get>()->next_layer() : +#endif + s->get(); + TORRENT_ASSERT_VAL(str, s->type_name()); + + str->set_dst_name(hostname); + } + + setup_ssl_hostname(*s, hostname, ec); + if (ec) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), web->url, ec); + return; + } + + peer_connection_args pack{ + &m_ses + , &settings() + , &m_ses.stats_counters() + , &m_ses.disk_thread() + , &m_ses.get_io_service() + , shared_from_this() + , s + , a + , &web->peer_info + , aux::generate_peer_id(settings()) + }; + + std::shared_ptr c; + if (web->type == web_seed_entry::url_seed) + { + c = std::make_shared(std::move(pack), *web); + } + else if (web->type == web_seed_entry::http_seed) + { + c = std::make_shared(std::move(pack), *web); + } + if (!c) return; + +#if TORRENT_USE_ASSERTS + c->m_in_constructor = false; +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& ext : m_extensions) + { + std::shared_ptr + pp(ext->new_connection(peer_connection_handle(c->self()))); + if (pp) c->add_extension(pp); + } +#endif + + TORRENT_ASSERT(!c->m_in_constructor); + // add the newly connected peer to this torrent's peer list + TORRENT_ASSERT(m_iterating_connections == 0); + + // we don't want to have to allocate memory to disconnect this peer, so + // make sure there's enough memory allocated in the deferred_disconnect + // list up-front + m_peers_to_disconnect.reserve(m_connections.size() + 1); + + sorted_insert(m_connections, c.get()); + update_want_peers(); + update_want_tick(); + m_ses.insert_peer(c); + + if (web->peer_info.seed) + { + TORRENT_ASSERT(m_num_seeds < 0xffff); + ++m_num_seeds; + } + + TORRENT_ASSERT(!web->peer_info.connection); + web->peer_info.connection = c.get(); +#if TORRENT_USE_ASSERTS + web->peer_info.in_use = true; +#endif + + c->add_stat(std::int64_t(web->peer_info.prev_amount_download) << 10 + , std::int64_t(web->peer_info.prev_amount_upload) << 10); + web->peer_info.prev_amount_download = 0; + web->peer_info.prev_amount_upload = 0; +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("web seed connection started: [%s] %s" + , print_endpoint(a).c_str(), web->url.c_str()); + } +#endif + + c->start(); + + if (c->is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("START queue peer [%p] (%d)", static_cast(c.get()) + , num_peers()); +#endif + } + + std::shared_ptr torrent::get_torrent_copy() + { + if (!m_torrent_file->is_valid()) return {}; + return m_torrent_file; + } + + void torrent::enable_all_trackers() + { + for (announce_entry& ae : m_trackers) + for (announce_endpoint& aep : ae.endpoints) + aep.enabled = true; + } + + void torrent::write_resume_data(add_torrent_params& ret) const + { + ret.version = LIBTORRENT_VERSION_NUM; + ret.storage_mode = storage_mode(); + ret.total_uploaded = m_total_uploaded; + ret.total_downloaded = m_total_downloaded; + + // cast to seconds in case that internal values doesn't have ratio<1> + ret.active_time = static_cast(total_seconds(active_time())); + ret.finished_time = static_cast(total_seconds(finished_time())); + ret.seeding_time = static_cast(total_seconds(seeding_time())); + ret.last_seen_complete = m_last_seen_complete; + ret.last_upload = std::time_t(total_seconds(m_last_upload.time_since_epoch())); + ret.last_download = std::time_t(total_seconds(m_last_download.time_since_epoch())); + + ret.num_complete = m_complete; + ret.num_incomplete = m_incomplete; + ret.num_downloaded = m_downloaded; + + ret.flags = flags(); + + ret.added_time = m_added_time; + ret.completed_time = m_completed_time; + + ret.save_path = m_save_path; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + ret.url = m_url; + ret.uuid = m_uuid; +#endif + + ret.info_hash = torrent_file().info_hash(); + + if (valid_metadata()) + { + if (m_magnet_link || (m_save_resume_flags & torrent_handle::save_info_dict)) + { + ret.ti = m_torrent_file; + } + } + + if (m_torrent_file->is_merkle_torrent()) + { + // we need to save the whole merkle hash tree + // in order to resume + ret.merkle_tree = m_torrent_file->merkle_tree(); + } + + // if this torrent is a seed, we won't have a piece picker + // if we don't have anything, we may also not have a picker + // in either case; there will be no half-finished pieces. + if (has_picker()) + { + int const num_blocks_per_piece = torrent_file().piece_length() / block_size(); + + std::vector const q + = m_picker->get_download_queue(); + + // info for each unfinished piece + for (auto const& dp : q) + { + if (dp.finished == 0) continue; + + bitfield bitmask; + bitmask.resize(num_blocks_per_piece, false); + + auto const info = m_picker->blocks_for_piece(dp); + for (int i = 0; i < int(info.size()); ++i) + { + if (info[i].state == piece_picker::block_info::state_finished) + bitmask.set_bit(i); + } + ret.unfinished_pieces.emplace(dp.index, std::move(bitmask)); + } + } + + // save trackers + for (auto const& tr : m_trackers) + { + ret.trackers.push_back(tr.url); + ret.tracker_tiers.push_back(tr.tier); + } + + // save web seeds + for (auto const& ws : m_web_seeds) + { + if (ws.removed || ws.ephemeral) continue; + if (ws.type == web_seed_entry::url_seed) + ret.url_seeds.push_back(ws.url); + else if (ws.type == web_seed_entry::http_seed) + ret.http_seeds.push_back(ws.url); + } + + // write have bitmask + // the pieces string has one byte per piece. Each + // byte is a bitmask representing different properties + // for the piece + // bit 0: set if we have the piece + // bit 1: set if we have verified the piece (in seed mode) + bool const is_checking = state() == torrent_status::checking_files; + + // if we are checking, only save the have_pieces bitfield up to the piece + // we have actually checked. This allows us to resume the checking when we + // load this torrent up again. If we have not completed checking nor is + // currently checking, don't save any pieces from the have_pieces + // bitfield. + piece_index_t const max_piece + = is_checking ? m_num_checked_pieces + : m_files_checked ? m_torrent_file->end_piece() + : piece_index_t(0); + + TORRENT_ASSERT(ret.have_pieces.empty()); + if (max_piece > piece_index_t(0)) + { + if (is_seed()) + { + ret.have_pieces.resize(static_cast(max_piece), true); + } + else if (has_picker()) + { + ret.have_pieces.resize(static_cast(max_piece), false); + for (auto const i : ret.have_pieces.range()) + if (m_picker->have_piece(i)) ret.have_pieces.set_bit(i); + } + + if (m_seed_mode) + ret.verified_pieces = m_verified; + } + + // write renamed files + if (&m_torrent_file->files() != &m_torrent_file->orig_files() + && m_torrent_file->files().num_files() == m_torrent_file->orig_files().num_files()) + { + file_storage const& fs = m_torrent_file->files(); + file_storage const& orig_fs = m_torrent_file->orig_files(); + for (auto const i : fs.file_range()) + { + if (fs.file_path(i) != orig_fs.file_path(i)) + ret.renamed_files[i] = fs.file_path(i); + } + } + + // write local peers + std::vector deferred_peers; + if (m_peer_list) + { + for (auto p : *m_peer_list) + { +#if TORRENT_USE_I2P + if (p->is_i2p_addr) continue; +#endif + if (p->banned) + { + ret.banned_peers.push_back(p->ip()); + continue; + } + + // we cannot save remote connection + // since we don't know their listen port + // unless they gave us their listen port + // through the extension handshake + // so, if the peer is not connectable (i.e. we + // don't know its listen port) or if it has + // been banned, don't save it. + if (!p->connectable) continue; + + // don't save peers that don't work + if (int(p->failcount) > 0) continue; + + // don't save peers that appear to send corrupt data + if (int(p->trust_points) < 0) continue; + + if (p->last_connected == 0) + { + // we haven't connected to this peer. It might still + // be useful to save it, but only save it if we + // don't have enough peers that we actually did connect to + if (int(deferred_peers.size()) < 100) + deferred_peers.push_back(p); + continue; + } + + ret.peers.push_back(p->ip()); + } + } + + // if we didn't save 100 peers, fill in with second choice peers + if (int(ret.peers.size()) < 100) + { + aux::random_shuffle(deferred_peers); + for (auto const p : deferred_peers) + { + ret.peers.push_back(p->ip()); + if (int(ret.peers.size()) >= 100) break; + } + } + + ret.upload_limit = upload_limit(); + ret.download_limit = download_limit(); + ret.max_connections = max_connections(); + ret.max_uploads = max_uploads(); + + // piece priorities and file priorities are mutually exclusive. If there + // are file priorities set, don't save piece priorities. + // when in seed mode (i.e. the client promises that we have all files) + // it does not make sense to save file priorities. + if (!m_file_priority.empty() && !m_seed_mode) + { + // write file priorities + ret.file_priorities = m_file_priority; + } + + if (has_picker()) + { + // write piece priorities + // but only if they are not set to the default + bool default_prio = true; + for (auto const i : m_torrent_file->piece_range()) + { + if (m_picker->piece_priority(i) == default_priority) continue; + default_prio = false; + break; + } + + if (!default_prio) + { + ret.piece_priorities.clear(); + ret.piece_priorities.reserve(static_cast(m_torrent_file->num_pieces())); + + for (auto const i : m_torrent_file->piece_range()) + ret.piece_priorities.push_back(m_picker->piece_priority(i)); + } + } + } + +#if TORRENT_ABI_VERSION == 1 + void torrent::get_full_peer_list(std::vector* v) const + { + v->clear(); + if (!m_peer_list) return; + + v->reserve(aux::numeric_cast(m_peer_list->num_peers())); + for (auto p : *m_peer_list) + { + peer_list_entry e; + e.ip = p->ip(); + e.flags = p->banned ? peer_list_entry::banned : 0; + e.failcount = p->failcount; + e.source = p->source; + v->push_back(e); + } + } +#endif + + void torrent::get_peer_info(std::vector* v) + { + v->clear(); + for (auto const peer : *this) + { + TORRENT_ASSERT(peer->m_in_use == 1337); + + // incoming peers that haven't finished the handshake should + // not be included in this list + if (peer->associated_torrent().expired()) continue; + + v->emplace_back(); + peer_info& p = v->back(); + + peer->get_peer_info(p); + } + } + + void torrent::get_download_queue(std::vector* queue) const + { + TORRENT_ASSERT(is_single_thread()); + queue->clear(); + std::vector& blk = m_ses.block_info_storage(); + blk.clear(); + + if (!valid_metadata() || !has_picker()) return; + piece_picker const& p = picker(); + std::vector q + = p.get_download_queue(); + if (q.empty()) return; + + const int blocks_per_piece = m_picker->blocks_in_piece(piece_index_t(0)); + blk.resize(q.size() * aux::numeric_cast(blocks_per_piece)); + + int counter = 0; + for (auto i = q.begin(); i != q.end(); ++i, ++counter) + { + partial_piece_info pi; + pi.blocks_in_piece = p.blocks_in_piece(i->index); + pi.finished = int(i->finished); + pi.writing = int(i->writing); + pi.requested = int(i->requested); +#if TORRENT_ABI_VERSION == 1 + pi.piece_state = partial_piece_info::none; +#endif + TORRENT_ASSERT(counter * blocks_per_piece + pi.blocks_in_piece <= int(blk.size())); + pi.blocks = &blk[std::size_t(counter * blocks_per_piece)]; + int const piece_size = torrent_file().piece_size(i->index); + int idx = -1; + for (auto const& info : m_picker->blocks_for_piece(*i)) + { + ++idx; + block_info& bi = pi.blocks[idx]; + bi.state = info.state; + bi.block_size = idx < pi.blocks_in_piece - 1 + ? aux::numeric_cast(block_size()) + : aux::numeric_cast(piece_size - (idx * block_size())); + bool const complete = bi.state == block_info::writing + || bi.state == block_info::finished; + if (info.peer == nullptr) + { + bi.set_peer(tcp::endpoint()); + bi.bytes_progress = complete ? bi.block_size : 0; + } + else + { + torrent_peer* tp = info.peer; + TORRENT_ASSERT(tp->in_use); + if (tp->connection) + { + auto* peer = static_cast(tp->connection); + TORRENT_ASSERT(peer->m_in_use); + bi.set_peer(peer->remote()); + if (bi.state == block_info::requested) + { + auto pbp = peer->downloading_piece_progress(); + if (pbp.piece_index == i->index && pbp.block_index == idx) + { + bi.bytes_progress = aux::numeric_cast(pbp.bytes_downloaded); + TORRENT_ASSERT(bi.bytes_progress <= bi.block_size); + } + else + { + bi.bytes_progress = 0; + } + } + else + { + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + else + { + bi.set_peer(tp->ip()); + bi.bytes_progress = complete ? bi.block_size : 0; + } + } + + pi.blocks[idx].num_peers = info.num_peers; + } + pi.piece_index = i->index; + queue->push_back(pi); + } + + } + + bool torrent::connect_to_peer(torrent_peer* peerinfo, bool const ignore_limit) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + TORRENT_UNUSED(ignore_limit); + + TORRENT_ASSERT(peerinfo); + TORRENT_ASSERT(peerinfo->connection == nullptr); + + if (m_abort) return false; + + peerinfo->last_connected = m_ses.session_time(); +#if TORRENT_USE_ASSERTS + if (!settings().get_bool(settings_pack::allow_multiple_connections_per_ip)) + { + // this asserts that we don't have duplicates in the peer_list's peer list + peer_iterator i_ = std::find_if(m_connections.begin(), m_connections.end() + , [peerinfo] (peer_connection const* p) + { return !p->is_disconnecting() && p->remote() == peerinfo->ip(); }); +#if TORRENT_USE_I2P + TORRENT_ASSERT(i_ == m_connections.end() + || (*i_)->type() != connection_type::bittorrent + || peerinfo->is_i2p_addr); +#else + TORRENT_ASSERT(i_ == m_connections.end() + || (*i_)->type() != connection_type::bittorrent); +#endif + } +#endif // TORRENT_USE_ASSERTS + + TORRENT_ASSERT(want_peers() || ignore_limit); + TORRENT_ASSERT(m_ses.num_connections() + < settings().get_int(settings_pack::connections_limit) || ignore_limit); + + tcp::endpoint a(peerinfo->ip()); + TORRENT_ASSERT(!m_apply_ip_filter + || !m_ip_filter + || (m_ip_filter->access(peerinfo->address()) & ip_filter::blocked) == 0); + + std::shared_ptr s = std::make_shared(m_ses.get_io_service()); + +#if TORRENT_USE_I2P + bool const i2p = peerinfo->is_i2p_addr; + if (i2p) + { + if (m_ses.i2p_proxy().hostname.empty()) + { + // we have an i2p torrent, but we're not connected to an i2p + // SAM proxy. + if (alerts().should_post()) + alerts().emplace_alert(errors::no_i2p_router); + return false; + } + + // It's not entirely obvious why this peer connection is not marked as + // one. The main feature of a peer connection is that whether or not we + // proxy it is configurable. When we use i2p, we want to always prox + // everything via i2p. + bool const ret = instantiate_connection(m_ses.get_io_service() + , m_ses.i2p_proxy(), *s, nullptr, nullptr, false, false); + (void)ret; + TORRENT_ASSERT(ret); + s->get()->set_destination(static_cast(peerinfo)->dest()); + s->get()->set_command(i2p_stream::cmd_connect); + s->get()->set_session_id(m_ses.i2p_session()); + } + else +#endif + { + // this is where we determine if we open a regular TCP connection + // or a uTP connection. If the utp_socket_manager pointer is not passed in + // we'll instantiate a TCP connection + utp_socket_manager* sm = nullptr; + + if (settings().get_bool(settings_pack::enable_outgoing_utp) + && (!settings().get_bool(settings_pack::enable_outgoing_tcp) + || peerinfo->supports_utp + || peerinfo->confirmed_supports_utp)) + { + sm = m_ses.utp_socket_manager(); + } + + // don't make a TCP connection if it's disabled + if (sm == nullptr && !settings().get_bool(settings_pack::enable_outgoing_tcp)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("discarding peer \"%s\": TCP connections disabled " + "[ supports-utp: %d ]", peerinfo->to_string().c_str() + , peerinfo->supports_utp); + } +#endif + return false; + } + + void* userdata = nullptr; +#ifdef TORRENT_USE_OPENSSL + if (is_ssl_torrent()) + { + userdata = m_ssl_ctx.get(); + // if we're creating a uTP socket, since this is SSL now, make sure + // to pass in the corresponding utp socket manager + if (sm) sm = m_ses.ssl_utp_socket_manager(); + } +#endif + + bool ret = instantiate_connection(m_ses.get_io_service() + , m_ses.proxy(), *s, userdata, sm, true, false); + (void)ret; + TORRENT_ASSERT(ret); + +#if defined TORRENT_USE_OPENSSL + if (is_ssl_torrent()) + { + // for ssl sockets, set the hostname + std::string host_name = aux::to_hex(m_torrent_file->info_hash()); + +#define CASE(t) case aux::socket_type_int_impl>::value: \ + s->get>()->set_host_name(host_name); break; + + switch (s->type()) + { + CASE(tcp::socket) + CASE(socks5_stream) + CASE(http_stream) + CASE(utp_stream) + default: break; + } + } +#undef CASE +#endif + } + + peer_id const our_pid = aux::generate_peer_id(settings()); + peer_connection_args pack{ + &m_ses + , &settings() + , &m_ses.stats_counters() + , &m_ses.disk_thread() + , &m_ses.get_io_service() + , shared_from_this() + , s + , a + , peerinfo + , our_pid + }; + + auto c = std::make_shared(std::move(pack)); + +#if TORRENT_USE_ASSERTS + c->m_in_constructor = false; +#endif + + c->add_stat(std::int64_t(peerinfo->prev_amount_download) << 10 + , std::int64_t(peerinfo->prev_amount_upload) << 10); + peerinfo->prev_amount_download = 0; + peerinfo->prev_amount_upload = 0; + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& ext : m_extensions) + { + std::shared_ptr pp(ext->new_connection( + peer_connection_handle(c->self()))); + if (pp) c->add_extension(pp); + } +#endif + + // add the newly connected peer to this torrent's peer list + TORRENT_ASSERT(m_iterating_connections == 0); + + // we don't want to have to allocate memory to disconnect this peer, so + // make sure there's enough memory allocated in the deferred_disconnect + // list up-front + m_peers_to_disconnect.reserve(m_connections.size() + 1); + + sorted_insert(m_connections, c.get()); + TORRENT_TRY + { + m_outgoing_pids.insert(our_pid); + m_ses.insert_peer(c); + need_peer_list(); + m_peer_list->set_connection(peerinfo, c.get()); + if (peerinfo->seed) + { + TORRENT_ASSERT(m_num_seeds < 0xffff); + ++m_num_seeds; + } + update_want_peers(); + update_want_tick(); + c->start(); + + if (c->is_disconnecting()) return false; + } + TORRENT_CATCH (std::exception const&) + { + TORRENT_ASSERT(m_iterating_connections == 0); + c->disconnect(errors::no_error, operation_t::bittorrent, peer_connection_interface::failure); + return false; + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_share_mode) + recalc_share_mode(); +#endif + + return peerinfo->connection != nullptr; + } + + bool torrent::set_metadata(span metadata_buf) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_torrent_file->is_valid()) return false; + + sha1_hash const info_hash = hasher(metadata_buf).final(); + if (info_hash != m_torrent_file->info_hash()) + { + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle() + , errors::mismatching_info_hash); + } + return false; + } + + bdecode_node metadata; + error_code ec; + int ret = bdecode(metadata_buf.begin(), metadata_buf.end(), metadata, ec); + if (ret != 0 || !m_torrent_file->parse_info_section(metadata, ec)) + { + update_gauge(); + // this means the metadata is correct, since we + // verified it against the info-hash, but we + // failed to parse it. Pause the torrent + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle(), ec); + } + set_error(errors::invalid_swarm_metadata, torrent_status::error_file_none); + pause(); + return false; + } + + update_gauge(); + update_want_tick(); + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle()); + } + + // we have to initialize the torrent before we start + // disconnecting redundant peers, otherwise we'll think + // we're a seed, because we have all 0 pieces + init(); + + inc_stats_counter(counters::num_total_pieces_added + , m_torrent_file->num_pieces()); + + // disconnect redundant peers + for (auto p : m_connections) + p->disconnect_if_redundant(); + + set_need_save_resume(); + + return true; + } + + namespace { + + bool connecting_time_compare(peer_connection const* lhs, peer_connection const* rhs) + { + bool const lhs_connecting = lhs->is_connecting() && !lhs->is_disconnecting(); + bool const rhs_connecting = rhs->is_connecting() && !rhs->is_disconnecting(); + if (lhs_connecting != rhs_connecting) return (int(lhs_connecting) < int(rhs_connecting)); + + // a lower value of connected_time means it's been waiting + // longer. This is a less-than comparison, so if lhs has + // waited longer than rhs, we should return false. + return lhs->connected_time() > rhs->connected_time(); + } + + } // anonymous namespace + + bool torrent::attach_peer(peer_connection* p) try + { +// INVARIANT_CHECK; + +#ifdef TORRENT_USE_OPENSSL +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + if (is_ssl_torrent()) + { + // if this is an SSL torrent, don't allow non SSL peers on it + std::shared_ptr s = p->get_socket(); + + // +#define SSL(t) aux::socket_type_int_impl>::value: \ + ssl_conn = s->get>()->native_handle(); \ + break; + + SSL* ssl_conn = nullptr; + + switch (s->type()) + { + case SSL(tcp::socket) + case SSL(socks5_stream) + case SSL(http_stream) + case SSL(utp_stream) + } + +#undef SSL + + if (ssl_conn == nullptr) + { + // don't allow non SSL peers on SSL torrents + p->disconnect(errors::requires_ssl_connection, operation_t::bittorrent); + return false; + } + + if (!m_ssl_ctx) + { + // we don't have a valid cert, don't accept any connection! + p->disconnect(errors::invalid_ssl_cert, operation_t::ssl_handshake); + return false; + } + + if (SSL_get_SSL_CTX(ssl_conn) != m_ssl_ctx->native_handle()) + { + // if the SSL_CTX associated with this connection is + // not the one belonging to this torrent, the SSL handshake + // connected to one torrent, and the BitTorrent protocol + // to a different one. This is probably an attempt to circumvent + // access control. Don't allow it. + p->disconnect(errors::invalid_ssl_cert, operation_t::bittorrent); + return false; + } + } +#ifdef TORRENT_MACOS_DEPRECATED_LIBCRYPTO +#pragma clang diagnostic pop +#endif +#else // TORRENT_USE_OPENSSL + if (is_ssl_torrent()) + { + // Don't accidentally allow seeding of SSL torrents, just + // because libtorrent wasn't built with SSL support + p->disconnect(errors::requires_ssl_connection, operation_t::ssl_handshake); + return false; + } +#endif // TORRENT_USE_OPENSSL + + TORRENT_ASSERT(p != nullptr); + TORRENT_ASSERT(!p->is_outgoing()); + + m_has_incoming = true; + + if (m_apply_ip_filter + && m_ip_filter + && m_ip_filter->access(p->remote().address()) & ip_filter::blocked) + { + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , p->remote(), peer_blocked_alert::ip_filter); + p->disconnect(errors::banned_by_ip_filter, operation_t::bittorrent); + return false; + } + + if (!is_downloading_state(m_state) && valid_metadata()) + { + p->disconnect(errors::torrent_not_ready, operation_t::bittorrent); + return false; + } + + if (!m_ses.has_connection(p)) + { + p->disconnect(errors::peer_not_constructed, operation_t::bittorrent); + return false; + } + + if (m_ses.is_aborted()) + { + p->disconnect(errors::session_closing, operation_t::bittorrent); + return false; + } + + int connection_limit_factor = 0; + for (int i = 0; i < p->num_classes(); ++i) + { + peer_class_t pc = p->class_at(i); + if (m_ses.peer_classes().at(pc) == nullptr) continue; + int f = m_ses.peer_classes().at(pc)->connection_limit_factor; + if (connection_limit_factor < f) connection_limit_factor = f; + } + if (connection_limit_factor == 0) connection_limit_factor = 100; + + std::int64_t const limit = std::int64_t(m_max_connections) * 100 / connection_limit_factor; + + bool maybe_replace_peer = false; + + if (m_connections.end_index() >= limit) + { + // if more than 10% of the connections are outgoing + // connection attempts that haven't completed yet, + // disconnect one of them and let this incoming + // connection through. + if (m_num_connecting > m_max_connections / 10) + { + // find one of the connecting peers and disconnect it + // find any peer that's connecting (i.e. a half-open TCP connection) + // that's also not disconnecting + // disconnect the peer that's been waiting to establish a connection + // the longest + auto i = std::max_element(begin(), end(), &connecting_time_compare); + + if (i == end() || !(*i)->is_connecting() || (*i)->is_disconnecting()) + { + // this seems odd, but we might as well handle it + p->disconnect(errors::too_many_connections, operation_t::bittorrent); + return false; + } + (*i)->disconnect(errors::too_many_connections, operation_t::bittorrent); + + // if this peer was let in via connections slack, + // it has done its duty of causing the disconnection + // of another peer + p->peer_disconnected_other(); + } + else + { + maybe_replace_peer = true; + } + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + std::shared_ptr pp(ext->new_connection( + peer_connection_handle(p->self()))); + if (pp) p->add_extension(pp); + } +#endif + torrent_state st = get_peer_list_state(); + need_peer_list(); + if (!m_peer_list->new_connection(*p, m_ses.session_time(), &st)) + { + peers_erased(st.erased); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("CLOSING CONNECTION \"%s\" peer list full " + "connections: %d limit: %d" + , print_endpoint(p->remote()).c_str() + , num_peers() + , m_max_connections); + } +#endif + p->disconnect(errors::too_many_connections, operation_t::bittorrent); + return false; + } + peers_erased(st.erased); + + m_peers_to_disconnect.reserve(m_connections.size() + 1); + m_connections.reserve(m_connections.size() + 1); + +#if TORRENT_USE_ASSERTS + error_code ec; + TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint(ec) || ec); +#endif + + TORRENT_ASSERT(p->peer_info_struct() != nullptr); + + // we need to do this after we've added the peer to the peer_list + // since that's when the peer is assigned its peer_info object, + // which holds the rank + if (maybe_replace_peer) + { + // now, find the lowest rank peer and disconnect that + // if it's lower rank than the incoming connection + peer_connection* peer = find_lowest_ranking_peer(); + + // TODO: 2 if peer is a really good peer, maybe we shouldn't disconnect it + // perhaps this logic should be disabled if we have too many idle peers + // (with some definition of idle) + if (peer != nullptr && peer->peer_rank() < p->peer_rank()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("CLOSING CONNECTION \"%s\" peer list full (low peer rank) " + "connections: %d limit: %d" + , print_endpoint(peer->remote()).c_str() + , num_peers() + , m_max_connections); + } +#endif + peer->disconnect(errors::too_many_connections, operation_t::bittorrent); + p->peer_disconnected_other(); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("CLOSING CONNECTION \"%s\" peer list full (low peer rank) " + "connections: %d limit: %d" + , print_endpoint(p->remote()).c_str() + , num_peers() + , m_max_connections); + } +#endif + p->disconnect(errors::too_many_connections, operation_t::bittorrent); + // we have to do this here because from the peer's point of view + // it wasn't really attached to the torrent, but we do need + // to let peer_list know we're removing it + remove_peer(p->self()); + return false; + } + } + +#if TORRENT_USE_INVARIANT_CHECKS + if (m_peer_list) m_peer_list->check_invariant(); +#endif + +#ifndef TORRENT_DISABLE_SHARE_MODE + if (m_share_mode) + recalc_share_mode(); +#endif + + // once we add the peer to our m_connections list, we can't throw an + // exception. That will end up violating an invariant between the session, + // torrent and peers + TORRENT_ASSERT(sorted_find(m_connections, p) == m_connections.end()); + TORRENT_ASSERT(m_iterating_connections == 0); + sorted_insert(m_connections, p); + update_want_peers(); + update_want_tick(); + + if (p->peer_info_struct() && p->peer_info_struct()->seed) + { + TORRENT_ASSERT(m_num_seeds < 0xffff); + ++m_num_seeds; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) try + { + debug_log("ATTACHED CONNECTION \"%s\" connections: %d limit: %d num-peers: %d" + , print_endpoint(p->remote()).c_str(), num_peers() + , m_max_connections + , num_peers()); + } + catch (std::exception const&) {} +#endif + + return true; + } + catch (...) + { + p->disconnect(errors::torrent_not_ready, operation_t::bittorrent); + // from the peer's point of view it was never really added to the torrent. + // So we need to clean it up here before propagating the error + remove_peer(p->self()); + return false; + } + + bool torrent::want_tick() const + { + if (m_abort) return false; + + if (!m_connections.empty()) return true; + + // we might want to connect web seeds + if (!is_finished() && !m_web_seeds.empty() && m_files_checked) + return true; + + if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) + return true; + + // if we don't get ticks we won't become inactive + if (!m_paused && !m_inactive) return true; + + return false; + } + + void torrent::update_want_tick() + { + update_list(aux::session_interface::torrent_want_tick, want_tick()); + } + + // this function adjusts which lists this torrent is part of (checking, + // seeding or downloading) + void torrent::update_state_list() + { + bool is_checking = false; + bool is_downloading = false; + bool is_seeding = false; + + if (is_auto_managed() && !has_error()) + { + if (m_state == torrent_status::checking_files + || m_state == torrent_status::allocating) + { + is_checking = true; + } + else if (m_state == torrent_status::downloading_metadata + || m_state == torrent_status::downloading + || m_state == torrent_status::finished + || m_state == torrent_status::seeding) + { + // torrents that are started (not paused) and + // inactive are not part of any list. They will not be touched because + // they are inactive + if (is_finished()) + is_seeding = true; + else + is_downloading = true; + } + } + + update_list(aux::session_interface::torrent_downloading_auto_managed + , is_downloading); + update_list(aux::session_interface::torrent_seeding_auto_managed + , is_seeding); + update_list(aux::session_interface::torrent_checking_auto_managed + , is_checking); + } + + // returns true if this torrent is interested in connecting to more peers + bool torrent::want_peers() const + { + // if all our connection slots are taken, we can't connect to more + if (num_peers() >= int(m_max_connections)) return false; + + // if we're paused, obviously we're not connecting to peers + if (is_paused() || m_abort || m_graceful_pause_mode) return false; + + if ((m_state == torrent_status::checking_files + || m_state == torrent_status::checking_resume_data) + && valid_metadata()) + return false; + + // if we don't know of any more potential peers to connect to, there's + // no point in trying + if (!m_peer_list || m_peer_list->num_connect_candidates() == 0) + return false; + + // if the user disabled outgoing connections for seeding torrents, + // don't make any + if (!settings().get_bool(settings_pack::seeding_outgoing_connections) + && (m_state == torrent_status::seeding + || m_state == torrent_status::finished)) + return false; + + return true; + } + + bool torrent::want_peers_download() const + { + return (m_state == torrent_status::downloading + || m_state == torrent_status::downloading_metadata) + && want_peers(); + } + + bool torrent::want_peers_finished() const + { + return (m_state == torrent_status::finished + || m_state == torrent_status::seeding) + && want_peers(); + } + + void torrent::update_want_peers() + { + update_list(aux::session_interface::torrent_want_peers_download, want_peers_download()); + update_list(aux::session_interface::torrent_want_peers_finished, want_peers_finished()); + } + + void torrent::update_want_scrape() + { + update_list(aux::session_interface::torrent_want_scrape + , m_paused && m_auto_managed && !m_abort); + } + + namespace { + +#ifndef TORRENT_DISABLE_LOGGING + char const* list_name(torrent_list_index_t const idx) + { +#define TORRENT_LIST_NAME(n) case static_cast(aux::session_interface:: n): return #n + switch (static_cast(idx)) + { + TORRENT_LIST_NAME(torrent_state_updates); + TORRENT_LIST_NAME(torrent_want_tick); + TORRENT_LIST_NAME(torrent_want_peers_download); + TORRENT_LIST_NAME(torrent_want_peers_finished); + TORRENT_LIST_NAME(torrent_want_scrape); + TORRENT_LIST_NAME(torrent_downloading_auto_managed); + TORRENT_LIST_NAME(torrent_seeding_auto_managed); + TORRENT_LIST_NAME(torrent_checking_auto_managed); + default: TORRENT_ASSERT_FAIL_VAL(idx); + } +#undef TORRENT_LIST_NAME + return ""; + } +#endif // TORRENT_DISABLE_LOGGING + + } // anonymous namespace + + void torrent::update_list(torrent_list_index_t const list, bool in) + { + link& l = m_links[list]; + aux::vector& v = m_ses.torrent_list(list); + + if (in) + { + if (l.in_list()) return; + l.insert(v, this); + } + else + { + if (!l.in_list()) return; + l.unlink(v, list); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + debug_log("*** UPDATE LIST [ %s : %d ]", list_name(list), int(in)); +#endif + } + + void torrent::disconnect_all(error_code const& ec, operation_t op) + { + TORRENT_ASSERT(m_iterating_connections == 0); + for (auto const& p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + p->disconnect(ec, op); + } + + update_want_peers(); + update_want_tick(); + } + + namespace { + + // this returns true if lhs is a better disconnect candidate than rhs + bool compare_disconnect_peer(peer_connection const* lhs, peer_connection const* rhs) + { + // prefer to disconnect peers that are already disconnecting + if (lhs->is_disconnecting() != rhs->is_disconnecting()) + return lhs->is_disconnecting(); + + // prefer to disconnect peers we're not interested in + if (lhs->is_interesting() != rhs->is_interesting()) + return rhs->is_interesting(); + + // prefer to disconnect peers that are not seeds + if (lhs->is_seed() != rhs->is_seed()) + return rhs->is_seed(); + + // prefer to disconnect peers that are on parole + if (lhs->on_parole() != rhs->on_parole()) + return lhs->on_parole(); + + // prefer to disconnect peers that send data at a lower rate + std::int64_t lhs_transferred = lhs->statistics().total_payload_download(); + std::int64_t rhs_transferred = rhs->statistics().total_payload_download(); + + time_point const now = aux::time_now(); + std::int64_t const lhs_time_connected = total_seconds(now - lhs->connected_time()); + std::int64_t const rhs_time_connected = total_seconds(now - rhs->connected_time()); + + lhs_transferred /= lhs_time_connected + 1; + rhs_transferred /= (rhs_time_connected + 1); + if (lhs_transferred != rhs_transferred) + return lhs_transferred < rhs_transferred; + + // prefer to disconnect peers that chokes us + if (lhs->is_choked() != rhs->is_choked()) + return lhs->is_choked(); + + return lhs->last_received() < rhs->last_received(); + } + + } // anonymous namespace + + int torrent::disconnect_peers(int const num, error_code const& ec) + { + INVARIANT_CHECK; + +#if TORRENT_USE_ASSERTS + // make sure we don't have any dangling pointers + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + TORRENT_ASSERT(m_ses.has_peer(p)); + } +#endif + aux::vector to_disconnect; + to_disconnect.resize(num); + auto end = std::partial_sort_copy(m_connections.begin(), m_connections.end() + , to_disconnect.begin(), to_disconnect.end(), compare_disconnect_peer); + for (auto p : range(to_disconnect.begin(), end)) + { + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + p->disconnect(ec, operation_t::bittorrent); + } + return static_cast(end - to_disconnect.begin()); + } + + // called when torrent is finished (all interesting + // pieces have been downloaded) + void torrent::finished() + { + update_state_list(); + + INVARIANT_CHECK; + + TORRENT_ASSERT(is_finished()); + + set_state(torrent_status::finished); + set_queue_position(no_pos); + + m_became_finished = aux::time_now32(); + + // we have to call completed() before we start + // disconnecting peers, since there's an assert + // to make sure we're cleared the piece picker + if (is_seed()) completed(); + + send_upload_only(); + state_updated(); + + if (m_completed_time == 0) + m_completed_time = time(nullptr); + + // disconnect all seeds + if (settings().get_bool(settings_pack::close_redundant_connections)) + { + // TODO: 1 should disconnect all peers that have the pieces we have + // not just seeds. It would be pretty expensive to check all pieces + // for all peers though + std::vector seeds; + for (auto const p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + if (p->upload_only()) + { +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "SEED", "CLOSING CONNECTION"); +#endif + seeds.push_back(p); + } + } + for (auto& p : seeds) + p->disconnect(errors::torrent_finished, operation_t::bittorrent + , peer_connection_interface::normal); + } + + if (m_abort) return; + + update_want_peers(); + + if (m_storage) + { + // we need to keep the object alive during this operation + m_ses.disk_thread().async_release_files(m_storage + , std::bind(&torrent::on_cache_flushed, shared_from_this(), false)); + } + + // this torrent just completed downloads, which means it will fall + // under a different limit with the auto-manager. Make sure we + // update auto-manage torrents in that case + if (m_auto_managed) + m_ses.trigger_auto_manage(); + } + + // this is called when we were finished, but some files were + // marked for downloading, and we are no longer finished + void torrent::resume_download() + { + // the invariant doesn't hold here, because it expects the torrent + // to be in downloading state (which it will be set to shortly) +// INVARIANT_CHECK; + + TORRENT_ASSERT(m_state != torrent_status::checking_resume_data + && m_state != torrent_status::checking_files + && m_state != torrent_status::allocating); + + // we're downloading now, which means we're no longer in seed mode + if (m_seed_mode) + leave_seed_mode(seed_mode_t::check_files); + + TORRENT_ASSERT(!is_finished()); + set_state(torrent_status::downloading); + set_queue_position(last_pos); + + m_completed_time = 0; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** RESUME_DOWNLOAD"); +#endif + send_upload_only(); + update_want_tick(); + update_state_list(); + } + + void torrent::maybe_done_flushing() + { + if (!has_picker()) return; + + if (m_picker->is_seeding()) + { + // no need for the piece picker anymore + // when we're suggesting read cache pieces, we + // still need the piece picker, to keep track + // of availability counts for pieces + if (settings().get_int(settings_pack::suggest_mode) + != settings_pack::suggest_read_cache) + { + m_picker.reset(); + m_file_progress.clear(); + } + m_have_all = true; + } + update_gauge(); + } + + // called when torrent is complete. i.e. all pieces downloaded + // not necessarily flushed to disk + void torrent::completed() + { + maybe_done_flushing(); + + set_state(torrent_status::seeding); + m_became_seed = aux::time_now32(); + + if (!m_announcing) return; + + time_point32 const now = aux::time_now32(); + for (auto& t : m_trackers) + { + for (auto& aep : t.endpoints) + { + if (aep.complete_sent || !aep.enabled) continue; + aep.next_announce = now; + aep.min_announce = now; + } + } + announce_with_tracker(); + } + + int torrent::deprioritize_tracker(int index) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_trackers.size())); + if (index >= int(m_trackers.size())) return -1; + + while (index < int(m_trackers.size()) - 1 && m_trackers[index].tier == m_trackers[index + 1].tier) + { + using std::swap; + swap(m_trackers[index], m_trackers[index + 1]); + if (m_last_working_tracker == index) ++m_last_working_tracker; + else if (m_last_working_tracker == index + 1) --m_last_working_tracker; + ++index; + } + return index; + } + + void torrent::files_checked() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(m_torrent_file->is_valid()); + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("files_checked(), paused"); +#endif + return; + } + + // calling pause will also trigger the auto managed + // recalculation + // if we just got here by downloading the metadata, + // just keep going, no need to disconnect all peers just + // to restart the torrent in a second + if (m_auto_managed) + { + // if this is an auto managed torrent, force a recalculation + // of which torrents to have active + m_ses.trigger_auto_manage(); + } + + if (!is_seed()) + { +#ifndef TORRENT_DISABLE_SUPERSEEDING + // turn off super seeding if we're not a seed + if (m_super_seeding) + { + m_super_seeding = false; + set_need_save_resume(); + state_updated(); + } +#endif + + if (m_state != torrent_status::finished && is_finished()) + finished(); + } + else + { + // we just added this torrent as a seed, or force-rechecked it, and we + // have all of it. Assume that we sent the event=completed when we + // finished downloading it, and don't send any more. + m_complete_sent = true; + for (auto& t : m_trackers) + { +#if TORRENT_ABI_VERSION == 1 + t.complete_sent = true; +#endif + for (auto& aep : t.endpoints) + aep.complete_sent = true; + } + + if (m_state != torrent_status::finished + && m_state != torrent_status::seeding) + finished(); + } + + // we might be finished already, in which case we should + // not switch to downloading mode. If all files are + // filtered, we're finished when we start. + if (m_state != torrent_status::finished + && m_state != torrent_status::seeding + && !m_seed_mode) + { + set_state(torrent_status::downloading); + } + + INVARIANT_CHECK; + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle()); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + ext->on_files_checked(); + } +#endif + + bool const notify_initialized = !m_connections_initialized; + m_connections_initialized = true; + m_files_checked = true; + + update_want_tick(); + + for (auto pc : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + // all peer connections have to initialize themselves now that the metadata + // is available + if (notify_initialized) + { + if (pc->is_disconnecting()) continue; + pc->on_metadata_impl(); + if (pc->is_disconnecting()) continue; + pc->init(); + } + +#ifndef TORRENT_DISABLE_LOGGING + pc->peer_log(peer_log_alert::info, "ON_FILES_CHECKED"); +#endif + if (pc->is_interesting() && !pc->has_peer_choked()) + { + if (request_a_block(*this, *pc)) + { + inc_stats_counter(counters::unchoke_piece_picks); + pc->send_block_requests(); + } + } + } + + start_announcing(); + + maybe_connect_web_seeds(); + } + + alert_manager& torrent::alerts() const + { + TORRENT_ASSERT(is_single_thread()); + return m_ses.alerts(); + } + + bool torrent::is_seed() const + { + if (!valid_metadata()) return false; + if (m_seed_mode) return true; + if (m_have_all) return true; + if (m_picker && m_picker->num_passed() == m_picker->num_pieces()) return true; + return m_state == torrent_status::seeding; + } + + bool torrent::is_finished() const + { + if (is_seed()) return true; + return valid_metadata() && has_picker() && m_picker->is_finished(); + } + + bool torrent::is_inactive() const + { + if (!settings().get_bool(settings_pack::dont_count_slow_torrents)) + return false; + return m_inactive; + } + + std::string torrent::save_path() const + { + return m_save_path; + } + + void torrent::rename_file(file_index_t const index, std::string name) + { + INVARIANT_CHECK; + + file_storage const& fs = m_torrent_file->files(); + TORRENT_ASSERT(index >= file_index_t(0)); + TORRENT_ASSERT(index < fs.end_file()); + TORRENT_UNUSED(fs); + + // storage may be nullptr during shutdown + if (!m_storage) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , index, errors::session_is_closing); + return; + } + + m_ses.disk_thread().async_rename_file(m_storage, index, std::move(name) + , std::bind(&torrent::on_file_renamed, shared_from_this(), _1, _2, _3)); + } + + void torrent::move_storage(std::string const& save_path, move_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_abort) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , boost::asio::error::operation_aborted + , "", operation_t::unknown); + return; + } + + // if we don't have metadata yet, we don't know anything about the file + // structure and we have to assume we don't have any file. + if (!valid_metadata()) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), save_path); +#if TORRENT_USE_UNC_PATHS + std::string path = canonicalize_path(save_path); +#else + std::string const& path = save_path; +#endif + m_save_path = complete(path); + return; + } + + // storage may be nullptr during shutdown + if (m_storage) + { +#if TORRENT_USE_UNC_PATHS + std::string path = canonicalize_path(save_path); +#else + std::string path = save_path; +#endif + m_ses.disk_thread().async_move_storage(m_storage, std::move(path), flags + , std::bind(&torrent::on_storage_moved, shared_from_this(), _1, _2, _3)); + m_moving_storage = true; + } + else + { +#if TORRENT_USE_UNC_PATHS + m_save_path = canonicalize_path(save_path); +#else + + m_save_path = save_path; +#endif + set_need_save_resume(); + + if (alerts().should_post()) + { + alerts().emplace_alert(get_handle(), m_save_path); + } + } + } + + void torrent::on_storage_moved(status_t const status, std::string const& path + , storage_error const& error) try + { + TORRENT_ASSERT(is_single_thread()); + + m_moving_storage = false; + if (status == status_t::no_error + || status == status_t::need_full_check) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), path); + m_save_path = path; + set_need_save_resume(); + if (status == status_t::need_full_check) + force_recheck(); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), error.ec + , resolve_filename(error.file()), error.operation); + } + } + catch (...) { handle_exception(); } + + torrent_handle torrent::get_handle() + { + TORRENT_ASSERT(is_single_thread()); + return torrent_handle(shared_from_this()); + } + + aux::session_settings const& torrent::settings() const + { + TORRENT_ASSERT(is_single_thread()); + return m_ses.settings(); + } + +#if TORRENT_USE_INVARIANT_CHECKS + void torrent::check_invariant() const + { + TORRENT_ASSERT(m_connections.size() >= m_outgoing_pids.size()); + + // the piece picker and the file progress states are supposed to be + // created in sync + TORRENT_ASSERT(has_picker() == !m_file_progress.empty()); + TORRENT_ASSERT(current_stats_state() == int(m_current_gauge_state + counters::num_checking_torrents) + || m_current_gauge_state == no_gauge_state); + + TORRENT_ASSERT(m_sequence_number == no_pos + || m_ses.verify_queue_position(this, m_sequence_number)); + +#ifndef TORRENT_DISABLE_STREAMING + for (auto const& i : m_time_critical_pieces) + { + TORRENT_ASSERT(!is_seed()); + TORRENT_ASSERT(!has_picker() || !m_picker->have_piece(i.piece)); + } +#endif + + switch (current_stats_state()) + { + case counters::num_error_torrents: TORRENT_ASSERT(has_error()); break; + case counters::num_checking_torrents: +#if TORRENT_ABI_VERSION == 1 + TORRENT_ASSERT(state() == torrent_status::checking_files + || state() == torrent_status::queued_for_checking); +#else + TORRENT_ASSERT(state() == torrent_status::checking_files); +#endif + break; + case counters::num_seeding_torrents: TORRENT_ASSERT(is_seed()); break; + case counters::num_upload_only_torrents: TORRENT_ASSERT(is_upload_only()); break; + case counters::num_stopped_torrents: TORRENT_ASSERT(!is_auto_managed() + && (m_paused || m_graceful_pause_mode)); + break; + case counters::num_queued_seeding_torrents: + TORRENT_ASSERT((m_paused || m_graceful_pause_mode) && is_seed()); break; + } + + if (m_torrent_file) + { + TORRENT_ASSERT(m_info_hash == m_torrent_file->info_hash()); + } + + for (torrent_list_index_t i{}; i != m_links.end_index(); ++i) + { + if (!m_links[i].in_list()) continue; + int const index = m_links[i].index; + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_ses.torrent_list(i).size())); + } + + TORRENT_ASSERT(want_peers_download() == m_links[aux::session_interface::torrent_want_peers_download].in_list()); + TORRENT_ASSERT(want_peers_finished() == m_links[aux::session_interface::torrent_want_peers_finished].in_list()); + TORRENT_ASSERT(want_tick() == m_links[aux::session_interface::torrent_want_tick].in_list()); + TORRENT_ASSERT((m_paused && m_auto_managed && !m_abort) == m_links[aux::session_interface::torrent_want_scrape].in_list()); + + bool is_checking = false; + bool is_downloading = false; + bool is_seeding = false; + + if (is_auto_managed() && !has_error()) + { + if (m_state == torrent_status::checking_files + || m_state == torrent_status::allocating) + { + is_checking = true; + } + else if (m_state == torrent_status::downloading_metadata + || m_state == torrent_status::downloading + || m_state == torrent_status::finished + || m_state == torrent_status::seeding) + { + if (is_finished()) + is_seeding = true; + else + is_downloading = true; + } + } + + TORRENT_ASSERT(m_links[aux::session_interface::torrent_checking_auto_managed].in_list() + == is_checking); + TORRENT_ASSERT(m_links[aux::session_interface::torrent_downloading_auto_managed].in_list() + == is_downloading); + TORRENT_ASSERT(m_links[aux::session_interface::torrent_seeding_auto_managed].in_list() + == is_seeding); + + if (m_seed_mode) + { + TORRENT_ASSERT(is_seed()); + } + + TORRENT_ASSERT(is_single_thread()); + // this fires during disconnecting peers + if (is_paused()) TORRENT_ASSERT(num_peers() == 0 || m_graceful_pause_mode); + + int seeds = 0; + int num_uploads = 0; + int num_connecting = 0; + int num_connecting_seeds = 0; + std::map num_requests; + for (peer_connection const* peer : *this) + { + peer_connection const& p = *peer; + + if (p.is_connecting()) ++num_connecting; + + if (p.is_connecting() && p.peer_info_struct()->seed) + ++num_connecting_seeds; + + if (p.peer_info_struct() && p.peer_info_struct()->seed) + ++seeds; + + for (auto const& j : p.request_queue()) + { + if (!j.not_wanted && !j.timed_out) ++num_requests[j.block]; + } + + for (auto const& j : p.download_queue()) + { + if (!j.not_wanted && !j.timed_out) ++num_requests[j.block]; + } + + if (!p.is_choked() && !p.ignore_unchoke_slots()) ++num_uploads; + torrent* associated_torrent = p.associated_torrent().lock().get(); + if (associated_torrent != this && associated_torrent != nullptr) + TORRENT_ASSERT_FAIL(); + } + TORRENT_ASSERT_VAL(num_uploads == int(m_num_uploads), int(m_num_uploads) - num_uploads); + TORRENT_ASSERT_VAL(seeds == int(m_num_seeds), int(m_num_seeds) - seeds); + TORRENT_ASSERT_VAL(num_connecting == int(m_num_connecting), int(m_num_connecting) - num_connecting); + TORRENT_ASSERT_VAL(num_connecting_seeds == int(m_num_connecting_seeds) + , int(m_num_connecting_seeds) - num_connecting_seeds); + TORRENT_ASSERT_VAL(int(m_num_uploads) <= num_peers(), m_num_uploads - num_peers()); + TORRENT_ASSERT_VAL(int(m_num_seeds) <= num_peers(), m_num_seeds - num_peers()); + TORRENT_ASSERT_VAL(int(m_num_connecting) <= num_peers(), int(m_num_connecting) - num_peers()); + TORRENT_ASSERT_VAL(int(m_num_connecting_seeds) <= num_peers(), int(m_num_connecting_seeds) - num_peers()); + TORRENT_ASSERT_VAL(int(m_num_connecting) + int(m_num_seeds) >= int(m_num_connecting_seeds) + , int(m_num_connecting_seeds) - (int(m_num_connecting) + int(m_num_seeds))); + TORRENT_ASSERT_VAL(int(m_num_connecting) + int(m_num_seeds) - int(m_num_connecting_seeds) <= num_peers() + , num_peers() - (int(m_num_connecting) + int(m_num_seeds) - int(m_num_connecting_seeds))); + + if (has_picker()) + { + for (std::map::iterator i = num_requests.begin() + , end(num_requests.end()); i != end; ++i) + { + piece_block b = i->first; + int count = i->second; + int picker_count = m_picker->num_peers(b); + // if we're no longer downloading the piece + // (for instance, it may be fully downloaded and waiting + // for the hash check to return), the piece picker always + // returns 0 requests, regardless of how many peers may still + // have the block in their queue + if (!m_picker->is_downloaded(b) && m_picker->is_downloading(b.piece_index)) + { + if (picker_count != count) + { + std::fprintf(stderr, "picker count discrepancy: " + "picker: %d != peerlist: %d\n", picker_count, count); + + for (const_peer_iterator j = this->begin(); j != this->end(); ++j) + { + peer_connection const& p = *(*j); + std::fprintf(stderr, "peer: %s\n", print_endpoint(p.remote()).c_str()); + for (auto const& k : p.request_queue()) + { + std::fprintf(stderr, " rq: (%d, %d) %s %s %s\n" + , static_cast(k.block.piece_index) + , k.block.block_index, k.not_wanted ? "not-wanted" : "" + , k.timed_out ? "timed-out" : "", k.busy ? "busy": ""); + } + for (auto const& k : p.download_queue()) + { + std::fprintf(stderr, " dq: (%d, %d) %s %s %s\n" + , static_cast(k.block.piece_index) + , k.block.block_index, k.not_wanted ? "not-wanted" : "" + , k.timed_out ? "timed-out" : "", k.busy ? "busy": ""); + } + } + TORRENT_ASSERT_FAIL(); + } + } + } + } + + if (valid_metadata()) + { + TORRENT_ASSERT(m_abort || m_error || !m_picker || m_picker->num_pieces() == m_torrent_file->num_pieces()); + } + else + { + TORRENT_ASSERT(m_abort || m_error || !m_picker || m_picker->num_pieces() == 0); + } + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + // make sure we haven't modified the peer object + // in a way that breaks the sort order + if (m_peer_list && m_peer_list->begin() != m_peer_list->end()) + { + auto i = m_peer_list->begin(); + auto p = i++; + auto end(m_peer_list->end()); + peer_address_compare cmp; + for (; i != end; ++i, ++p) + { + TORRENT_ASSERT(!cmp(*i, *p)); + } + } +#endif + +/* + if (m_picker && !m_abort) + { + // make sure that pieces that have completed the download + // of all their blocks are in the disk io thread's queue + // to be checked. + std::vector dl_queue + = m_picker->get_download_queue(); + for (std::vector::const_iterator i = + dl_queue.begin(); i != dl_queue.end(); ++i) + { + const int blocks_per_piece = m_picker->blocks_in_piece(i->index); + + bool complete = true; + for (int j = 0; j < blocks_per_piece; ++j) + { + if (i->info[j].state == piece_picker::block_info::state_finished) + continue; + complete = false; + break; + } + TORRENT_ASSERT(complete); + } + } +*/ + if (m_files_checked && valid_metadata()) + { + TORRENT_ASSERT(block_size() > 0); + } + } +#endif + + void torrent::set_sequential_download(bool const sd) + { + TORRENT_ASSERT(is_single_thread()); + if (m_sequential_download == sd) return; + m_sequential_download = sd; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-sequential-download: %d", sd); +#endif + + set_need_save_resume(); + + state_updated(); + } + + void torrent::queue_up() + { + // finished torrents may not change their queue positions, as it's set to + // -1 + if (m_abort || is_finished()) return; + + set_queue_position(queue_position() == queue_position_t{0} + ? queue_position() : prev(queue_position())); + } + + void torrent::queue_down() + { + set_queue_position(next(queue_position())); + } + + void torrent::set_queue_position(queue_position_t const p) + { + TORRENT_ASSERT(is_single_thread()); + + // finished torrents may not change their queue positions, as it's set to + // -1 + if ((m_abort || is_finished()) && p != no_pos) return; + + TORRENT_ASSERT((p == no_pos) == is_finished() + || (!m_auto_managed && p == no_pos) + || (m_abort && p == no_pos) + || (!m_added && p == no_pos)); + if (p == m_sequence_number) return; + + TORRENT_ASSERT(p >= no_pos); + + state_updated(); + + m_ses.set_queue_position(this, p); + } + + void torrent::set_max_uploads(int limit, bool const state_update) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + if (limit <= 0) limit = (1 << 24) - 1; + if (int(m_max_uploads)!= limit && state_update) state_updated(); + m_max_uploads = aux::numeric_cast(limit); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && state_update) + debug_log("*** set-max-uploads: %d", m_max_uploads); +#endif + + if (state_update) + set_need_save_resume(); + } + + void torrent::set_max_connections(int limit, bool const state_update) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + if (limit <= 0) limit = (1 << 24) - 1; + if (int(m_max_connections) != limit && state_update) state_updated(); + m_max_connections = aux::numeric_cast(limit); + update_want_peers(); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log() && state_update) + debug_log("*** set-max-connections: %d", m_max_connections); +#endif + + if (num_peers() > int(m_max_connections)) + { + disconnect_peers(num_peers() - m_max_connections + , errors::too_many_connections); + } + + if (state_update) + set_need_save_resume(); + } + + void torrent::set_upload_limit(int const limit) + { + set_limit_impl(limit, peer_connection::upload_channel); + set_need_save_resume(); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-upload-limit: %d", limit); +#endif + } + + void torrent::set_download_limit(int const limit) + { + set_limit_impl(limit, peer_connection::download_channel); + set_need_save_resume(); +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** set-download-limit: %d", limit); +#endif + } + + void torrent::set_limit_impl(int limit, int const channel, bool const state_update) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + if (limit <= 0) limit = 0; + + if (m_peer_class == peer_class_t{0}) + { + if (limit == 0) return; + setup_peer_class(); + } + + struct peer_class* tpc = m_ses.peer_classes().at(m_peer_class); + TORRENT_ASSERT(tpc); + if (tpc->channel[channel].throttle() != limit && state_update) + state_updated(); + tpc->channel[channel].throttle(limit); + } + + void torrent::setup_peer_class() + { + TORRENT_ASSERT(m_peer_class == peer_class_t{0}); + m_peer_class = m_ses.peer_classes().new_peer_class(name()); + add_class(m_ses.peer_classes(), m_peer_class); + } + + int torrent::limit_impl(int const channel) const + { + TORRENT_ASSERT(is_single_thread()); + + if (m_peer_class == peer_class_t{0}) return -1; + int limit = m_ses.peer_classes().at(m_peer_class)->channel[channel].throttle(); + if (limit == std::numeric_limits::max()) limit = -1; + return limit; + } + + int torrent::upload_limit() const + { + return limit_impl(peer_connection::upload_channel); + } + + int torrent::download_limit() const + { + return limit_impl(peer_connection::download_channel); + } + + bool torrent::delete_files(remove_flags_t const options) + { + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_LOGGING + log_to_all_peers("deleting files"); +#endif + + disconnect_all(errors::torrent_removed, operation_t::bittorrent); + stop_announcing(); + + // storage may be nullptr during shutdown + if (m_storage) + { + TORRENT_ASSERT(m_storage); + m_ses.disk_thread().async_delete_files(m_storage, options + , std::bind(&torrent::on_files_deleted, shared_from_this(), _1)); + m_deleted = true; + return true; + } + return false; + } + + void torrent::clear_error() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_error) return; + bool const checking_files = should_check_files(); + m_ses.trigger_auto_manage(); + m_error.clear(); + m_error_file = torrent_status::error_file_none; + + update_gauge(); + state_updated(); + update_want_peers(); + update_state_list(); + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + // if we haven't downloaded the metadata from m_url, try again + if (!m_url.empty() && !m_torrent_file->is_valid()) + { + start_download_url(); + return; + } +#endif + // if the error happened during initialization, try again now + if (!m_torrent_initialized && valid_metadata()) init(); + if (!checking_files && should_check_files()) + start_checking(); + } + std::string torrent::resolve_filename(file_index_t const file) const + { + if (file == torrent_status::error_file_none) return ""; + if (file == torrent_status::error_file_ssl_ctx) return "SSL Context"; + if (file == torrent_status::error_file_exception) return "exception"; + if (file == torrent_status::error_file_partfile) return "partfile"; +#if TORRENT_ABI_VERSION == 1 + if (file == torrent_status::error_file_url) return m_url; + if (file == torrent_status::error_file_metadata) return "metadata (from user load function)"; +#endif + + if (m_storage && file >= file_index_t(0)) + { + file_storage const& st = m_torrent_file->files(); + return st.file_path(file, m_save_path); + } + else + { + return m_save_path; + } + } + + void torrent::set_error(error_code const& ec, file_index_t const error_file) + { + TORRENT_ASSERT(is_single_thread()); + m_error = ec; + m_error_file = error_file; + + update_gauge(); + + if (alerts().should_post()) + alerts().emplace_alert(get_handle(), ec + , resolve_filename(error_file)); + +#ifndef TORRENT_DISABLE_LOGGING + if (ec) + { + char buf[1024]; + std::snprintf(buf, sizeof(buf), "error %s: %s", ec.message().c_str() + , resolve_filename(error_file).c_str()); + log_to_all_peers(buf); + } +#endif + + state_updated(); + update_state_list(); + } + + void torrent::auto_managed(bool a) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (m_auto_managed == a) return; + bool const checking_files = should_check_files(); + m_auto_managed = a; + update_gauge(); + update_want_scrape(); + update_state_list(); + + state_updated(); + + // we need to save this new state as well + set_need_save_resume(); + + // recalculate which torrents should be + // paused + m_ses.trigger_auto_manage(); + + if (!checking_files && should_check_files()) + { + start_checking(); + } + } + + namespace { + + std::uint16_t clamped_subtract_u16(int const a, int const b) + { + if (a < b) return 0; + return std::uint16_t(a - b); + } + + } // anonymous namespace + + // this is called every time the session timer takes a step back. Since the + // session time is meant to fit in 16 bits, it only covers a range of + // about 18 hours. This means every few hours the whole epoch of this + // clock is shifted forward. All timestamp in this clock must then be + // shifted backwards to remain the same. Anything that's shifted back + // beyond the new epoch is clamped to 0 (to represent the oldest timestamp + // currently representable by the session_time) + void torrent::step_session_time(int const seconds) + { + if (m_peer_list) + { + for (auto pe : *m_peer_list) + { + pe->last_optimistically_unchoked + = clamped_subtract_u16(pe->last_optimistically_unchoked, seconds); + pe->last_connected = clamped_subtract_u16(pe->last_connected, seconds); + } + } + } + + // the higher seed rank, the more important to seed + int torrent::seed_rank(aux::session_settings const& s) const + { + TORRENT_ASSERT(is_single_thread()); + enum flags + { + seed_ratio_not_met = 0x40000000, + no_seeds = 0x20000000, + recently_started = 0x10000000, + prio_mask = 0x0fffffff + }; + + if (!is_finished()) return 0; + + int scale = 1000; + if (!is_seed()) scale = 500; + + int ret = 0; + + seconds32 const act_time = active_time(); + seconds32 const fin_time = finished_time(); + seconds32 const download_time = act_time - fin_time; + + // if we haven't yet met the seed limits, set the seed_ratio_not_met + // flag. That will make this seed prioritized + // downloaded may be 0 if the torrent is 0-sized + std::int64_t const downloaded = std::max(m_total_downloaded, m_torrent_file->total_size()); + if (fin_time < seconds(s.get_int(settings_pack::seed_time_limit)) + && (download_time.count() > 1 + && fin_time * 100 / download_time < s.get_int(settings_pack::seed_time_ratio_limit)) + && downloaded > 0 + && m_total_uploaded * 100 / downloaded < s.get_int(settings_pack::share_ratio_limit)) + ret |= seed_ratio_not_met; + + // if this torrent is running, and it was started less + // than 30 minutes ago, give it priority, to avoid oscillation + if (!is_paused() && act_time < minutes(30)) + ret |= recently_started; + + // if we have any scrape data, use it to calculate + // seed rank + int seeds = 0; + int downloaders = 0; + + if (m_complete != 0xffffff) seeds = m_complete; + else seeds = m_peer_list ? m_peer_list->num_seeds() : 0; + + if (m_incomplete != 0xffffff) downloaders = m_incomplete; + else downloaders = m_peer_list ? m_peer_list->num_peers() - m_peer_list->num_seeds() : 0; + + if (seeds == 0) + { + ret |= no_seeds; + ret |= downloaders & prio_mask; + } + else + { + ret |= ((1 + downloaders) * scale / seeds) & prio_mask; + } + + return ret; + } + + // this is an async operation triggered by the client + // TODO: add a flag to ignore stats, and only care about resume data for + // content. For unchanged files, don't trigger a load of the metadata + // just to save an empty resume data file + void torrent::save_resume_data(resume_data_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (!valid_metadata()) + { + alerts().emplace_alert(get_handle() + , errors::no_metadata); + return; + } + + if ((flags & torrent_handle::only_if_modified) && !m_need_save_resume_data) + { + alerts().emplace_alert(get_handle() + , errors::resume_data_not_modified); + return; + } + + m_need_save_resume_data = false; + m_save_resume_flags = flags; + state_updated(); + + if ((flags & torrent_handle::flush_disk_cache) && m_storage) + m_ses.disk_thread().async_release_files(m_storage); + + state_updated(); + + add_torrent_params atp; + write_resume_data(atp); + alerts().emplace_alert(std::move(atp), get_handle()); + } + + bool torrent::should_check_files() const + { + TORRENT_ASSERT(is_single_thread()); + return m_state == torrent_status::checking_files + && !m_paused + && !has_error() + && !m_abort + && !m_session_paused; + } + + void torrent::flush_cache() + { + TORRENT_ASSERT(is_single_thread()); + + // storage may be nullptr during shutdown + if (!m_storage) + { + TORRENT_ASSERT(m_abort); + return; + } + m_ses.disk_thread().async_release_files(m_storage + , std::bind(&torrent::on_cache_flushed, shared_from_this(), true)); + } + + void torrent::on_cache_flushed(bool const manually_triggered) try + { + TORRENT_ASSERT(is_single_thread()); + + if (m_ses.is_aborted()) return; + + if (manually_triggered || alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + catch (...) { handle_exception(); } + + void torrent::on_torrent_aborted() + { + TORRENT_ASSERT(is_single_thread()); + + // there should be no more disk activity for this torrent now, we can + // release the disk io handle + m_storage.reset(); + } + + bool torrent::is_paused() const + { + return m_paused || m_session_paused; + } + + void torrent::pause(pause_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (!m_paused) + { + // we need to save this new state + set_need_save_resume(); + } + + set_paused(true, flags | torrent_handle::clear_disk_cache); + } + + void torrent::do_pause(pause_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + if (!is_paused()) return; + + // this torrent may be about to consider itself inactive. If so, we want + // to prevent it from doing so, since it's being paused unconditionally + // now. An illustrative example of this is a torrent that completes + // downloading when active_seeds = 0. It completes, it gets paused and it + // should not come back to life again. + if (m_pending_active_change) + { + m_inactivity_timer.cancel(); + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + if (ext->on_pause()) return; + } +#endif + + m_connect_boost_counter + = static_cast(settings().get_int(settings_pack::torrent_connect_boost)); + m_inactive = false; + + update_state_list(); + update_want_tick(); + + const time_point now = aux::time_now(); + + m_active_time += + duration_cast(now - m_started); + + if (is_seed()) m_seeding_time += + duration_cast(now - m_became_seed); + + if (is_finished()) m_finished_time += + duration_cast(now - m_became_finished); + + m_announce_to_dht = false; + m_announce_to_trackers = false; + m_announce_to_lsd = false; + + state_updated(); + update_want_peers(); + update_want_scrape(); + update_gauge(); + update_state_list(); + +#ifndef TORRENT_DISABLE_LOGGING + log_to_all_peers("pausing"); +#endif + + // when checking and being paused in graceful pause mode, we + // post the paused alert when the last outstanding disk job completes + if (m_state == torrent_status::checking_files) + { + if (m_checking_piece == m_num_checked_pieces) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + disconnect_all(errors::torrent_paused, operation_t::bittorrent); + return; + } + + if (!m_graceful_pause_mode) + { + // this will make the storage close all + // files and flush all cached data + if (m_storage && (flags & torrent_handle::clear_disk_cache)) + { + // the torrent_paused alert will be posted from on_torrent_paused + m_ses.disk_thread().async_stop_torrent(m_storage + , std::bind(&torrent::on_torrent_paused, shared_from_this())); + } + else + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + } + + disconnect_all(errors::torrent_paused, operation_t::bittorrent); + } + else + { + // disconnect all peers with no outstanding data to receive + // and choke all remaining peers to prevent responding to new + // requests + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + + if (p->is_disconnecting()) continue; + + if (p->outstanding_bytes() > 0) + { +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "CHOKING_PEER", "torrent graceful paused"); +#endif + // remove any un-sent requests from the queue + p->clear_request_queue(); + // don't accept new requests from the peer + p->choke_this_peer(); + continue; + } + + // since we're currently in graceful pause mode, the last peer to + // disconnect (assuming all peers end up begin disconnected here) + // will post the torrent_paused_alert +#ifndef TORRENT_DISABLE_LOGGING + p->peer_log(peer_log_alert::info, "CLOSING_CONNECTION", "torrent_paused"); +#endif + p->disconnect(errors::torrent_paused, operation_t::bittorrent); + } + } + + stop_announcing(); + } + +#ifndef TORRENT_DISABLE_LOGGING + void torrent::log_to_all_peers(char const* message) + { + TORRENT_ASSERT(is_single_thread()); + + bool const log_peers = !m_connections.empty() + && m_connections.front()->should_log(peer_log_alert::info); + + if (log_peers) + { + for (auto const p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + p->peer_log(peer_log_alert::info, "TORRENT", "%s", message); + } + } + + debug_log("%s", message); + } +#endif + + // add or remove a url that will be attempted for + // finding the file(s) in this torrent. + web_seed_t* torrent::add_web_seed(std::string const& url + , web_seed_entry::type_t const type + , std::string const& auth + , web_seed_entry::headers_t const& extra_headers + , web_seed_flag_t const flags) + { + web_seed_t ent(url, type, auth, extra_headers); + ent.ephemeral = bool(flags & ephemeral); + + // don't add duplicates + auto const it = std::find(m_web_seeds.begin(), m_web_seeds.end(), ent); + if (it != m_web_seeds.end()) return &*it; + m_web_seeds.push_back(ent); + set_need_save_resume(); + update_want_tick(); + return &m_web_seeds.back(); + } + + void torrent::set_session_paused(bool const b) + { + if (m_session_paused == b) return; + bool const paused_before = is_paused(); + m_session_paused = b; + + if (paused_before == is_paused()) return; + + if (b) do_pause(); + else do_resume(); + } + + void torrent::set_paused(bool const b, pause_flags_t flags) + { + TORRENT_ASSERT(is_single_thread()); + + // if there are no peers, there is no point in a graceful pause mode. In + // fact, the promise to post the torrent_paused_alert exactly once is + // maintained by the last peer to be disconnected in graceful pause mode, + // if there are no peers, we must not enter graceful pause mode, and post + // the torrent_paused_alert immediately instead. + if (num_peers() == 0) + flags &= ~torrent_handle::graceful_pause; + + if (m_paused == b) + { + // there is one special case here. If we are + // currently in graceful pause mode, and we just turned into regular + // paused mode, we need to actually pause the torrent properly + if (m_paused == true + && m_graceful_pause_mode == true + && !(flags & torrent_handle::graceful_pause)) + { + m_graceful_pause_mode = false; + update_gauge(); + do_pause(); + } + return; + } + + bool const paused_before = is_paused(); + + m_paused = b; + + // the session may still be paused, in which case + // the effective state of the torrent did not change + if (paused_before == is_paused()) return; + + m_graceful_pause_mode = bool(flags & torrent_handle::graceful_pause); + + if (b) do_pause(flags & torrent_handle::clear_disk_cache); + else do_resume(); + } + + void torrent::resume() + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + if (!m_paused + && m_announce_to_dht + && m_announce_to_trackers + && m_announce_to_lsd) return; + + m_announce_to_dht = true; + m_announce_to_trackers = true; + m_announce_to_lsd = true; + m_paused = false; + if (!m_session_paused) m_graceful_pause_mode = false; + + update_gauge(); + + // we need to save this new state + set_need_save_resume(); + + do_resume(); + } + + void torrent::do_resume() + { + TORRENT_ASSERT(is_single_thread()); + if (is_paused()) + { + update_want_tick(); + return; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + if (ext->on_resume()) return; + } +#endif + + if (alerts().should_post()) + alerts().emplace_alert(get_handle()); + + m_started = aux::time_now32(); + if (is_seed()) m_became_seed = m_started; + if (is_finished()) m_became_finished = m_started; + + clear_error(); + + if (m_state == torrent_status::checking_files) + { + if (m_auto_managed) m_ses.trigger_auto_manage(); + if (should_check_files()) start_checking(); + } + + state_updated(); + update_want_peers(); + update_want_tick(); + update_want_scrape(); + update_gauge(); + + if (should_check_files()) start_checking(); + + if (m_state == torrent_status::checking_files) return; + + start_announcing(); + + do_connect_boost(); + } + + namespace + { + struct timer_state + { + explicit timer_state(aux::listen_socket_handle const& s) + : socket(s) {} + + aux::listen_socket_handle socket; + + int tier = INT_MAX; + bool found_working = false; + bool done = false; + }; + } + + void torrent::update_tracker_timer(time_point32 const now) + { + TORRENT_ASSERT(is_single_thread()); + if (!m_announcing) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** update tracker timer: not announcing"); +#endif + return; + } + + time_point32 next_announce = time_point32::max(); + + std::vector listen_socket_states; + +#ifndef TORRENT_DISABLE_LOGGING + int idx = -1; + if (should_log()) + { + debug_log("*** update_tracker_timer: " + "[ announce_to_all_tiers: %d announce_to_all_trackers: %d num_trackers: %d ]" + , settings().get_bool(settings_pack::announce_to_all_tiers) + , settings().get_bool(settings_pack::announce_to_all_trackers) + , int(m_trackers.size())); + } +#endif + for (auto const& t : m_trackers) + { +#ifndef TORRENT_DISABLE_LOGGING + ++idx; +#endif + for (auto const& aep : t.endpoints) + { + auto aep_state_iter = std::find_if(listen_socket_states.begin(), listen_socket_states.end() + , [&](timer_state const& s) { return s.socket == aep.socket; }); + if (aep_state_iter == listen_socket_states.end()) + { + listen_socket_states.emplace_back(aep.socket); + aep_state_iter = listen_socket_states.end() - 1; + } + timer_state& state = *aep_state_iter; + + if (state.done) continue; + + if (settings().get_bool(settings_pack::announce_to_all_tiers) + && state.found_working + && t.tier <= state.tier + && state.tier != INT_MAX) + continue; + + if (t.tier > state.tier && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; + if (aep.is_working()) { state.tier = t.tier; state.found_working = false; } + if (aep.fails >= t.fail_limit && t.fail_limit != 0) continue; + if (!aep.enabled) continue; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** tracker: (%d) [ep: %s ] \"%s\" [ found: %d i->tier: %d tier: %d" + " working: %d fails: %d limit: %d upd: %d ]" + , idx, print_endpoint(aep.local_endpoint).c_str(), t.url.c_str() + , state.found_working, t.tier, state.tier, aep.is_working() + , aep.fails, t.fail_limit, aep.updating); + } +#endif + + if (aep.updating) + { + state.found_working = true; + } + else + { + time_point32 const next_tracker_announce = std::max(aep.next_announce, aep.min_announce); + if (next_tracker_announce < next_announce + && (!state.found_working || aep.is_working())) + next_announce = next_tracker_announce; + } + if (aep.is_working()) state.found_working = true; + if (state.found_working + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) + state.done = true; + } + + if (std::all_of(listen_socket_states.begin(), listen_socket_states.end() + , [](timer_state const& s) { return s.done; })) + break; + } + + if (next_announce <= now) next_announce = now; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** update tracker timer: next_announce < now %d" + " m_waiting_tracker: %d next_announce_in: %d" + , next_announce <= now, m_waiting_tracker + , int(total_seconds(next_announce - now))); +#endif + + // don't re-issue the timer if it's the same expiration time as last time + // if m_waiting_tracker is 0, expires_at() is undefined + if (m_waiting_tracker && m_tracker_timer.expires_at() == next_announce) return; + + error_code ec; + auto self = shared_from_this(); + + m_tracker_timer.expires_at(next_announce, ec); + ADD_OUTSTANDING_ASYNC("tracker::on_tracker_announce"); + ++m_waiting_tracker; + m_tracker_timer.async_wait([self](error_code const& e) + { self->wrap(&torrent::on_tracker_announce, e); }); + } + + void torrent::start_announcing() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(state() != torrent_status::checking_files); + if (is_paused()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_announcing(), paused"); +#endif + return; + } + // if we don't have metadata, we need to announce + // before checking files, to get peers to + // request the metadata from + if (!m_files_checked && valid_metadata()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_announcing(), files not checked (with valid metadata)"); +#endif + return; + } +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + if (!m_torrent_file->is_valid() && !m_url.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("start_announcing(), downloading URL"); +#endif + return; + } +#endif + if (m_announcing) return; + + m_announcing = true; + +#ifndef TORRENT_DISABLE_DHT + if ((!m_peer_list || m_peer_list->num_peers() < 50) && m_ses.dht()) + { + // we don't have any peers, prioritize + // announcing this torrent with the DHT + m_ses.prioritize_dht(shared_from_this()); + } +#endif + + if (!m_trackers.empty()) + { + // tell the tracker that we're back + for (auto& t : m_trackers) t.reset(); + } + + // reset the stats, since from the tracker's + // point of view, this is a new session + m_total_failed_bytes = 0; + m_total_redundant_bytes = 0; + m_stat.clear(); + + update_want_tick(); + + announce_with_tracker(); + + lsd_announce(); + } + + void torrent::stop_announcing() + { + TORRENT_ASSERT(is_single_thread()); + if (!m_announcing) return; + + error_code ec; + m_tracker_timer.cancel(ec); + + m_announcing = false; + + time_point32 const now = aux::time_now32(); + for (auto& t : m_trackers) + { + for (auto& aep : t.endpoints) + { + aep.next_announce = now; + aep.min_announce = now; + } + } + announce_with_tracker(tracker_request::stopped); + } + + seconds32 torrent::finished_time() const + { + if(!is_finished() || is_paused()) + return m_finished_time; + + return m_finished_time + duration_cast( + aux::time_now() - m_became_finished); + } + + seconds32 torrent::active_time() const + { + if(is_paused()) + return m_active_time; + + // m_active_time does not account for the current "session", just the + // time before we last started this torrent. To get the current time, we + // need to add the time since we started it + return m_active_time + duration_cast( + aux::time_now() - m_started); + } + + seconds32 torrent::seeding_time() const + { + if(!is_seed() || is_paused()) + return m_seeding_time; + // m_seeding_time does not account for the current "session", just the + // time before we last started this torrent. To get the current time, we + // need to add the time since we started it + return m_seeding_time + duration_cast( + aux::time_now() - m_became_seed); + } + + seconds32 torrent::upload_mode_time() const + { + if(!m_upload_mode) + return seconds32(0); + + return aux::time_now32() - m_upload_mode_time; + } + + void torrent::second_tick(int const tick_interval_ms) + { + TORRENT_ASSERT(want_tick()); + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + auto self = shared_from_this(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto const& ext : m_extensions) + { + ext->tick(); + } + + if (m_abort) return; +#endif + + // if we're in upload only mode and we're auto-managed + // leave upload mode every 10 minutes hoping that the error + // condition has been fixed + if (m_upload_mode && m_auto_managed && upload_mode_time() >= + seconds(settings().get_int(settings_pack::optimistic_disk_retry))) + { + set_upload_mode(false); + } + + if (is_paused() && !m_graceful_pause_mode) + { + // let the stats fade out to 0 + // check the rate before ticking the stats so that the last update is sent + // with the rate equal to zero + if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) + state_updated(); + m_stat.second_tick(tick_interval_ms); + + // the low pass transfer rate may just have dropped to 0 + update_want_tick(); + + return; + } + + if (settings().get_bool(settings_pack::rate_limit_ip_overhead)) + { + int const up_limit = upload_limit(); + int const down_limit = download_limit(); + + if (down_limit > 0 + && m_stat.download_ip_overhead() >= down_limit + && alerts().should_post()) + { + alerts().emplace_alert(get_handle() + , performance_alert::download_limit_too_low); + } + + if (up_limit > 0 + && m_stat.upload_ip_overhead() >= up_limit + && alerts().should_post()) + { + alerts().emplace_alert(get_handle() + , performance_alert::upload_limit_too_low); + } + } + +#ifndef TORRENT_DISABLE_STREAMING + // ---- TIME CRITICAL PIECES ---- + +#if TORRENT_DEBUG_STREAMING > 0 + std::vector queue; + get_download_queue(&queue); + + std::vector peer_list; + get_peer_info(peer_list); + + std::sort(queue.begin(), queue.end(), std::bind(&partial_piece_info::piece_index, _1) + < std::bind(&partial_piece_info::piece_index, _2)); + + std::printf("average piece download time: %.2f s (+/- %.2f s)\n" + , m_average_piece_time / 1000.f + , m_piece_time_deviation / 1000.f); + for (auto& i : queue) + { + extern void print_piece(libtorrent::partial_piece_info* pp + , std::vector const& peers + , std::vector const& time_critical); + + print_piece(&i, peer_list, m_time_critical_pieces); + } +#endif // TORRENT_DEBUG_STREAMING + + if (!m_time_critical_pieces.empty() && !upload_mode()) + { + request_time_critical_pieces(); + } +#endif // TORRENT_DISABLE_STREAMING + + // ---- WEB SEEDS ---- + + maybe_connect_web_seeds(); + + m_swarm_last_seen_complete = m_last_seen_complete; + for (auto p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + + // look for the peer that saw a seed most recently + m_swarm_last_seen_complete = std::max(p->last_seen_complete(), m_swarm_last_seen_complete); + + // updates the peer connection's ul/dl bandwidth + // resource requests + p->second_tick(tick_interval_ms); + } + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle(), tick_interval_ms, m_stat); + + m_total_uploaded += m_stat.last_payload_uploaded(); + m_total_downloaded += m_stat.last_payload_downloaded(); + m_stat.second_tick(tick_interval_ms); + + // these counters are saved in the resume data, since they updated + // we need to save the resume data too + m_need_save_resume_data = true; + + // if the rate is 0, there's no update because of network transfers + if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) + state_updated(); + + // this section determines whether the torrent is active or not. When it + // changes state, it may also trigger the auto-manage logic to reconsider + // which torrents should be queued and started. There is a low pass + // filter in order to avoid flapping (auto_manage_startup). + bool is_inactive = is_inactive_internal(); + + if (settings().get_bool(settings_pack::dont_count_slow_torrents)) + { + if (is_inactive != m_inactive && !m_pending_active_change) + { + int const delay = settings().get_int(settings_pack::auto_manage_startup); + m_inactivity_timer.expires_from_now(seconds(delay)); + m_inactivity_timer.async_wait([self](error_code const& ec) { + self->wrap(&torrent::on_inactivity_tick, ec); }); + m_pending_active_change = true; + } + else if (is_inactive == m_inactive + && m_pending_active_change) + { + m_inactivity_timer.cancel(); + } + } + + // want_tick depends on whether the low pass transfer rates are non-zero + // or not. They may just have turned zero in this last tick. + update_want_tick(); + } + + bool torrent::is_inactive_internal() const + { + if (is_finished()) + return m_stat.upload_payload_rate() + < settings().get_int(settings_pack::inactive_up_rate); + else + return m_stat.download_payload_rate() + < settings().get_int(settings_pack::inactive_down_rate); + } + + void torrent::on_inactivity_tick(error_code const& ec) try + { + m_pending_active_change = false; + + if (ec) return; + + bool const is_inactive = is_inactive_internal(); + if (is_inactive == m_inactive) return; + + m_inactive = is_inactive; + + update_state_list(); + update_want_tick(); + + if (settings().get_bool(settings_pack::dont_count_slow_torrents)) + m_ses.trigger_auto_manage(); + } + catch (...) { handle_exception(); } + + namespace { + int zero_or(int const val, int const def_val) + { return (val <= 0) ? def_val : val; } + } + + void torrent::maybe_connect_web_seeds() + { + if (m_abort) return; + + // if we have everything we want we don't need to connect to any web-seed + if (m_web_seeds.empty() + || is_finished() + || !m_files_checked + || num_peers() >= int(m_max_connections) + || m_ses.num_connections() >= settings().get_int(settings_pack::connections_limit)) + { + return; + } + + // when set to unlimited, use 100 as the limit + int limit = zero_or(settings().get_int(settings_pack::max_web_seed_connections) + , 100); + + auto const now = aux::time_now32(); + + // keep trying web-seeds if there are any + // first find out which web seeds we are connected to + for (auto i = m_web_seeds.begin(); i != m_web_seeds.end() && limit > 0;) + { + auto const w = i++; + if (w->removed || w->retry > now || !w->interesting) + continue; + + --limit; + if (w->peer_info.connection || w->resolving) + continue; + + connect_to_url_seed(w); + } + } + +#ifndef TORRENT_DISABLE_SHARE_MODE + void torrent::recalc_share_mode() + { + TORRENT_ASSERT(share_mode()); + if (is_seed()) return; + + int const pieces_in_torrent = m_torrent_file->num_pieces(); + int num_seeds = 0; + int num_peers = 0; + int num_downloaders = 0; + int missing_pieces = 0; + int num_interested = 0; + for (auto const p : m_connections) + { + TORRENT_INCREMENT(m_iterating_connections); + if (p->is_connecting()) continue; + if (p->is_disconnecting()) continue; + ++num_peers; + if (p->is_seed()) + { + ++num_seeds; + continue; + } + + if (p->share_mode()) continue; + if (p->upload_only()) continue; + + if (p->is_peer_interested()) ++num_interested; + + ++num_downloaders; + missing_pieces += pieces_in_torrent - p->num_have_pieces(); + } + + if (num_peers == 0) return; + + if (num_seeds * 100 / num_peers > 50 + && (num_peers * 100 / m_max_connections > 90 + || num_peers > 20)) + { + // we are connected to more than 50% seeds (and we're beyond + // 90% of the max number of connections). That will + // limit our ability to upload. We need more downloaders. + // disconnect some seeds so that we don't have more than 50% + int const to_disconnect = num_seeds - num_peers / 2; + aux::vector seeds; + seeds.reserve(num_seeds); + std::copy_if(m_connections.begin(), m_connections.end(), std::back_inserter(seeds) + , [](peer_connection const* p) { return p->is_seed(); }); + + aux::random_shuffle(seeds); + TORRENT_ASSERT(to_disconnect <= seeds.end_index()); + for (auto const& p : span(seeds).first(to_disconnect)) + p->disconnect(errors::upload_upload_connection, operation_t::bittorrent); + } + + if (num_downloaders == 0) return; + + // assume that the seeds are about as fast as us. During the time + // we can download one piece, and upload one piece, each seed + // can upload two pieces. + missing_pieces -= 2 * num_seeds; + + if (missing_pieces <= 0) return; + + // missing_pieces represents our opportunity to download pieces + // and share them more than once each + + // now, download at least one piece, otherwise download one more + // piece if our downloaded (and downloading) pieces is less than 50% + // of the uploaded bytes + int const num_downloaded_pieces = std::max(m_picker->have().num_pieces + , m_picker->want().num_pieces); + + if (std::int64_t(num_downloaded_pieces) * m_torrent_file->piece_length() + * settings().get_int(settings_pack::share_mode_target) > m_total_uploaded + && num_downloaded_pieces > 0) + return; + + // don't have more pieces downloading in parallel than 5% of the total + // number of pieces we have downloaded + if (m_picker->get_download_queue_size() > num_downloaded_pieces / 20) + return; + + // one more important property is that there are enough pieces + // that more than one peer wants to download + // make sure that there are enough downloaders for the rarest + // piece. Go through all pieces, figure out which one is the rarest + // and how many peers that has that piece + + aux::vector rarest_pieces; + + int const num_pieces = m_torrent_file->num_pieces(); + int rarest_rarity = INT_MAX; + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + piece_picker::piece_stats_t ps = m_picker->piece_stats(i); + if (ps.peer_count == 0) continue; + if (ps.priority == 0 && (ps.have || ps.downloading)) + { + m_picker->set_piece_priority(i, default_priority); + continue; + } + // don't count pieces we already have or are trying to download + if (ps.priority > 0 || ps.have) continue; + if (ps.peer_count > rarest_rarity) continue; + if (ps.peer_count == rarest_rarity) + { + rarest_pieces.push_back(i); + continue; + } + + rarest_pieces.clear(); + rarest_rarity = ps.peer_count; + rarest_pieces.push_back(i); + } + + update_gauge(); + update_want_peers(); + + // now, rarest_pieces is a list of all pieces that are the rarest ones. + // and rarest_rarity is the number of peers that have the rarest pieces + + // if there's only a single peer that doesn't have the rarest piece + // it's impossible for us to download one piece and upload it + // twice. i.e. we cannot get a positive share ratio + if (num_peers - rarest_rarity + < settings().get_int(settings_pack::share_mode_target)) + return; + + // now, pick one of the rarest pieces to download + int const pick = int(random(aux::numeric_cast(rarest_pieces.end_index() - 1))); + bool const was_finished = is_finished(); + m_picker->set_piece_priority(rarest_pieces[pick], default_priority); + update_gauge(); + update_peer_interest(was_finished); + update_want_peers(); + } +#endif // TORRENT_DISABLE_SHARE_MODE + + void torrent::sent_bytes(int const bytes_payload, int const bytes_protocol) + { + m_stat.sent_bytes(bytes_payload, bytes_protocol); + m_ses.sent_bytes(bytes_payload, bytes_protocol); + } + + void torrent::received_bytes(int const bytes_payload, int const bytes_protocol) + { + m_stat.received_bytes(bytes_payload, bytes_protocol); + m_ses.received_bytes(bytes_payload, bytes_protocol); + } + + void torrent::trancieve_ip_packet(int const bytes, bool const ipv6) + { + m_stat.trancieve_ip_packet(bytes, ipv6); + m_ses.trancieve_ip_packet(bytes, ipv6); + } + + void torrent::sent_syn(bool const ipv6) + { + m_stat.sent_syn(ipv6); + m_ses.sent_syn(ipv6); + } + + void torrent::received_synack(bool const ipv6) + { + m_stat.received_synack(ipv6); + m_ses.received_synack(ipv6); + } + +#ifndef TORRENT_DISABLE_STREAMING + +#if TORRENT_DEBUG_STREAMING > 0 + char const* esc(char const* code) + { + // this is a silly optimization + // to avoid copying of strings + int const num_strings = 200; + static char buf[num_strings][20]; + static int round_robin = 0; + char* ret = buf[round_robin]; + ++round_robin; + if (round_robin >= num_strings) round_robin = 0; + ret[0] = '\033'; + ret[1] = '['; + int i = 2; + int j = 0; + while (code[j]) ret[i++] = code[j++]; + ret[i++] = 'm'; + ret[i++] = 0; + return ret; + } + + int peer_index(libtorrent::tcp::endpoint addr + , std::vector const& peers) + { + std::vector::const_iterator i = std::find_if(peers.begin() + , peers.end(), std::bind(&peer_info::ip, _1) == addr); + if (i == peers.end()) return -1; + + return i - peers.begin(); + } + + void print_piece(libtorrent::partial_piece_info* pp + , std::vector const& peers + , std::vector const& time_critical) + { + time_point const now = clock_type::now(); + + float deadline = 0.f; + float last_request = 0.f; + int timed_out = -1; + + int piece = pp->piece_index; + std::vector::const_iterator i + = std::find_if(time_critical.begin(), time_critical.end() + , std::bind(&time_critical_piece::piece, _1) == piece); + if (i != time_critical.end()) + { + deadline = total_milliseconds(i->deadline - now) / 1000.f; + if (i->last_requested == min_time()) + last_request = -1; + else + last_request = total_milliseconds(now - i->last_requested) / 1000.f; + timed_out = i->timed_out; + } + + int num_blocks = pp->blocks_in_piece; + + std::printf("%5d: [", piece); + for (int j = 0; j < num_blocks; ++j) + { + int index = pp ? peer_index(pp->blocks[j].peer(), peers) % 36 : -1; + char chr = '+'; + if (index >= 0) + chr = (index < 10)?'0' + index:'A' + index - 10; + + char const* color = ""; + char const* multi_req = ""; + + if (pp->blocks[j].num_peers > 1) + multi_req = esc("1"); + + if (pp->blocks[j].bytes_progress > 0 + && pp->blocks[j].state == block_info::requested) + { + color = esc("33;7"); + chr = '0' + (pp->blocks[j].bytes_progress * 10 / pp->blocks[j].block_size); + } + else if (pp->blocks[j].state == block_info::finished) color = esc("32;7"); + else if (pp->blocks[j].state == block_info::writing) color = esc("36;7"); + else if (pp->blocks[j].state == block_info::requested) color = esc("0"); + else { color = esc("0"); chr = ' '; } + + std::printf("%s%s%c%s", color, multi_req, chr, esc("0")); + } + std::printf("%s]", esc("0")); + if (deadline != 0.f) + std::printf(" deadline: %f last-req: %f timed_out: %d\n" + , deadline, last_request, timed_out); + else + std::printf("\n"); + } +#endif // TORRENT_DEBUG_STREAMING + + namespace { + + struct busy_block_t + { + int peers; + int index; + bool operator<(busy_block_t const& rhs) const { return peers < rhs.peers; } + }; + + void pick_busy_blocks(piece_picker const* picker + , piece_index_t const piece + , int const blocks_in_piece + , int const timed_out + , std::vector& interesting_blocks + , piece_picker::downloading_piece const& pi) + { + // if there aren't any free blocks in the piece, and the piece is + // old enough, we may switch into busy mode for this piece. In this + // case busy_blocks and busy_count are set to contain the eligible + // busy blocks we may pick + // first, figure out which blocks are eligible for picking + // in "busy-mode" + TORRENT_ALLOCA(busy_blocks, busy_block_t, blocks_in_piece); + int busy_count = 0; + + // pick busy blocks from the piece + int idx = -1; + for (auto const& info : picker->blocks_for_piece(pi)) + { + ++idx; + // only consider blocks that have been requested + // and we're still waiting for them + if (info.state != piece_picker::block_info::state_requested) + continue; + + piece_block b(piece, idx); + + // only allow a single additional request per block, in order + // to spread it out evenly across all stalled blocks + if (int(info.num_peers) > timed_out) + continue; + + busy_blocks[busy_count].peers = info.num_peers; + busy_blocks[busy_count].index = idx; + ++busy_count; + +#if TORRENT_DEBUG_STREAMING > 1 + std::printf(" [%d (%d)]", b.block_index, info.num_peers); +#endif + } +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("\n"); +#endif + + busy_blocks = busy_blocks.first(busy_count); + + // then sort blocks by the number of peers with requests + // to the blocks (request the blocks with the fewest peers + // first) + std::sort(busy_blocks.begin(), busy_blocks.end()); + + // then insert them into the interesting_blocks vector + for (auto const& block : busy_blocks) + interesting_blocks.emplace_back(piece, block.index); + } + + void pick_time_critical_block(std::vector& peers + , std::vector& ignore_peers + , std::set& peers_with_requests + , piece_picker::downloading_piece const& pi + , time_critical_piece* i + , piece_picker const* picker + , int const blocks_in_piece + , int const timed_out) + { + std::vector interesting_blocks; + std::vector backup1; + std::vector backup2; + std::vector ignore; + + time_point const now = aux::time_now(); + + // loop until every block has been requested from this piece (i->piece) + do + { + // if this peer's download time exceeds 2 seconds, we're done. + // We don't want to build unreasonably long request queues + if (!peers.empty() && peers[0]->download_queue_time() > milliseconds(2000)) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("queue time: %d ms, done\n" + , int(total_milliseconds(peers[0]->download_queue_time()))); +#endif + break; + } + + // pick the peer with the lowest download_queue_time that has i->piece + auto p = std::find_if(peers.begin(), peers.end() + , std::bind(&peer_connection::has_piece, _1, i->piece)); + + // obviously we'll have to skip it if we don't have a peer that has + // this piece + if (p == peers.end()) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("out of peers, done\n"); +#endif + break; + } + peer_connection& c = **p; + + interesting_blocks.clear(); + backup1.clear(); + backup2.clear(); + + // specifically request blocks with no affinity towards fast or slow + // pieces. If we would, the picked block might end up in one of + // the backup lists + picker->add_blocks(i->piece, c.get_bitfield(), interesting_blocks + , backup1, backup2, blocks_in_piece, 0, c.peer_info_struct() + , ignore, {}); + + interesting_blocks.insert(interesting_blocks.end() + , backup1.begin(), backup1.end()); + interesting_blocks.insert(interesting_blocks.end() + , backup2.begin(), backup2.end()); + + bool busy_mode = false; + + if (interesting_blocks.empty()) + { + busy_mode = true; + +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("interesting_blocks.empty()\n"); +#endif + + // there aren't any free blocks to pick, and the piece isn't + // old enough to pick busy blocks yet. break to continue to + // the next piece. + if (timed_out == 0) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("not timed out, moving on to next piece\n"); +#endif + break; + } + +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("pick busy blocks\n"); +#endif + + pick_busy_blocks(picker, i->piece, blocks_in_piece, timed_out + , interesting_blocks, pi); + } + + // we can't pick anything from this piece, we're done with it. + // move on to the next one + if (interesting_blocks.empty()) break; + + piece_block const b = interesting_blocks.front(); + + // in busy mode we need to make sure we don't do silly + // things like requesting the same block twice from the + // same peer + std::vector const& dq = c.download_queue(); + + bool const already_requested = std::find_if(dq.begin(), dq.end() + , aux::has_block(b)) != dq.end(); + + if (already_requested) + { + // if the piece is stalled, we may end up picking a block + // that we've already requested from this peer. If so, we should + // simply disregard this peer from this piece, since this peer + // is likely to be causing the stall. We should request it + // from the next peer in the list + // the peer will be put back in the set for the next piece + ignore_peers.push_back(*p); + peers.erase(p); +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("piece already requested by peer, try next peer\n"); +#endif + // try next peer + continue; + } + + std::vector const& rq = c.request_queue(); + + bool const already_in_queue = std::find_if(rq.begin(), rq.end() + , aux::has_block(b)) != rq.end(); + + if (already_in_queue) + { + if (!c.make_time_critical(b)) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("piece already time-critical and in queue for peer, trying next peer\n"); +#endif + ignore_peers.push_back(*p); + peers.erase(p); + continue; + } + i->last_requested = now; + +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("piece already in queue for peer, making time-critical\n"); +#endif + + // we inserted a new block in the request queue, this + // makes us actually send it later + peers_with_requests.insert(peers_with_requests.begin(), &c); + } + else + { + if (!c.add_request(b, peer_connection::time_critical + | (busy_mode ? peer_connection::busy : request_flags_t{}))) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("failed to request block [%d, %d]\n" + , b.piece_index, b.block_index); +#endif + ignore_peers.push_back(*p); + peers.erase(p); + continue; + } + +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("requested block [%d, %d]\n" + , b.piece_index, b.block_index); +#endif + peers_with_requests.insert(peers_with_requests.begin(), &c); + } + + if (!busy_mode) i->last_requested = now; + + if (i->first_requested == min_time()) i->first_requested = now; + + if (!c.can_request_time_critical()) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("peer cannot pick time critical pieces\n"); +#endif + peers.erase(p); + // try next peer + continue; + } + + // resort p, since it will have a higher download_queue_time now + while (p != peers.end()-1 && (*p)->download_queue_time() + > (*(p+1))->download_queue_time()) + { + std::iter_swap(p, p+1); + ++p; + } + } while (!interesting_blocks.empty()); + } + + } // anonymous namespace + + void torrent::request_time_critical_pieces() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!upload_mode()); + + // build a list of peers and sort it by download_queue_time + // we use this sorted list to determine which peer we should + // request a block from. The earlier a peer is in the list, + // the sooner we will fully download the block we request. + aux::vector peers; + peers.reserve(num_peers()); + + // some peers are marked as not being able to request time critical + // blocks from. For instance, peers that have choked us, peers that are + // on parole (i.e. they are believed to have sent us bad data), peers + // that are being disconnected, in upload mode etc. + std::remove_copy_if(m_connections.begin(), m_connections.end() + , std::back_inserter(peers), [] (peer_connection* p) + { return !p->can_request_time_critical(); }); + + // sort by the time we believe it will take this peer to send us all + // blocks we've requested from it. The shorter time, the better candidate + // it is to request a time critical block from. + std::sort(peers.begin(), peers.end() + , [] (peer_connection const* lhs, peer_connection const* rhs) + { return lhs->download_queue_time(16*1024) < rhs->download_queue_time(16*1024); }); + + // remove the bottom 10% of peers from the candidate set. + // this is just to remove outliers that might stall downloads + int const new_size = (peers.end_index() * 9 + 9) / 10; + TORRENT_ASSERT(new_size <= peers.end_index()); + peers.resize(new_size); + + // remember all the peers we issued requests to, so we can commit them + // at the end of this function. Instead of sending the requests right + // away, we batch them up and send them in a single write to the TCP + // socket, increasing the chance that they will all be sent in the same + // packet. + std::set peers_with_requests; + + // peers that should be temporarily ignored for a specific piece + // in order to give priority to other peers. They should be used for + // subsequent pieces, so they are stored in this vector until the + // piece is done + std::vector ignore_peers; + + time_point const now = clock_type::now(); + + // now, iterate over all time critical pieces, in order of importance, and + // request them from the peers, in order of responsiveness. i.e. request + // the most time critical pieces from the fastest peers. + bool first_piece{true}; + for (auto& i : m_time_critical_pieces) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("considering %d\n", i->piece); +#endif + + if (peers.empty()) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("out of peers, done\n"); +#endif + break; + } + + // the +1000 is to compensate for the fact that we only call this + // function once per second, so if we need to request it 500 ms from + // now, we should request it right away + if (!first_piece && i.deadline > now + + milliseconds(m_average_piece_time + m_piece_time_deviation * 4 + 1000)) + { + // don't request pieces whose deadline is too far in the future + // this is one of the termination conditions. We don't want to + // send requests for all pieces in the torrent right away +#if TORRENT_DEBUG_STREAMING > 0 + std::printf("reached deadline horizon [%f + %f * 4 + 1]\n" + , m_average_piece_time / 1000.f + , m_piece_time_deviation / 1000.f); +#endif + break; + } + first_piece = false; + + piece_picker::downloading_piece pi; + m_picker->piece_info(i.piece, pi); + + // the number of "times" this piece has timed out. + int timed_out = 0; + + int const blocks_in_piece = m_picker->blocks_in_piece(i.piece); + +#if TORRENT_DEBUG_STREAMING > 0 + i.timed_out = timed_out; +#endif + int const free_to_request = blocks_in_piece + - pi.finished - pi.writing - pi.requested; + + if (free_to_request == 0) + { + if (i.last_requested == min_time()) + i.last_requested = now; + + // if it's been more than half of the typical download time + // of a piece since we requested the last block, allow + // one more request per block + if (m_average_piece_time > 0) + timed_out = int(total_milliseconds(now - i.last_requested) + / std::max(int(m_average_piece_time + m_piece_time_deviation / 2), 1)); + +#if TORRENT_DEBUG_STREAMING > 0 + i.timed_out = timed_out; +#endif + // every block in this piece is already requested + // there's no need to consider this piece, unless it + // appears to be stalled. + if (pi.requested == 0 || timed_out == 0) + { +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("skipping %d (full) [req: %d timed_out: %d ]\n" + , i.piece, pi.requested + , timed_out); +#endif + + // if requested is 0, it means all blocks have been received, and + // we're just waiting for it to flush them to disk. + // if last_requested is recent enough, we should give it some + // more time + // skip to the next piece + continue; + } + + // it's been too long since we requested the last block from + // this piece. Allow re-requesting blocks from this piece +#if TORRENT_DEBUG_STREAMING > 1 + std::printf("timed out [average-piece-time: %d ms ]\n" + , m_average_piece_time); +#endif + } + + // pick all blocks for this piece. the peers list is kept up to date + // and sorted. when we issue a request to a peer, its download queue + // time will increase and it may need to be bumped in the peers list, + // since it's ordered by download queue time + pick_time_critical_block(peers, ignore_peers + , peers_with_requests + , pi, &i, m_picker.get() + , blocks_in_piece, timed_out); + + // put back the peers we ignored into the peer list for the next piece + if (!ignore_peers.empty()) + { + peers.insert(peers.begin(), ignore_peers.begin(), ignore_peers.end()); + ignore_peers.clear(); + + // TODO: instead of resorting the whole list, insert the peers + // directly into the right place + std::sort(peers.begin(), peers.end() + , [] (peer_connection const* lhs, peer_connection const* rhs) + { return lhs->download_queue_time(16*1024) < rhs->download_queue_time(16*1024); }); + } + + // if this peer's download time exceeds 2 seconds, we're done. + // We don't want to build unreasonably long request queues + if (!peers.empty() && peers[0]->download_queue_time() > milliseconds(2000)) + break; + } + + // commit all the time critical requests + for (auto p : peers_with_requests) + { + p->send_block_requests(); + } + } +#endif // TORRENT_DISABLE_STREAMING + + std::set torrent::web_seeds(web_seed_entry::type_t const type) const + { + TORRENT_ASSERT(is_single_thread()); + std::set ret; + for (auto const& s : m_web_seeds) + { + if (s.peer_info.banned) continue; + if (s.removed) continue; + if (s.type != type) continue; + ret.insert(s.url); + } + return ret; + } + + void torrent::remove_web_seed(std::string const& url, web_seed_entry::type_t const type) + { + auto const i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() + , [&] (web_seed_t const& w) { return w.url == url && w.type == type; }); + + if (i != m_web_seeds.end()) + { + remove_web_seed_iter(i); + set_need_save_resume(); + } + } + + void torrent::disconnect_web_seed(peer_connection* p) + { + auto const i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() + , [p] (web_seed_t const& ws) { return ws.peer_info.connection == p; }); + + // this happens if the web server responded with a redirect + // or with something incorrect, so that we removed the web seed + // immediately, before we disconnected + if (i == m_web_seeds.end()) return; + + TORRENT_ASSERT(i->resolving == false); + + TORRENT_ASSERT(i->peer_info.connection); + i->peer_info.connection = nullptr; + } + + void torrent::remove_web_seed_conn(peer_connection* p, error_code const& ec + , operation_t const op, disconnect_severity_t const error) + { + auto const i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() + , [p] (web_seed_t const& ws) { return ws.peer_info.connection == p; }); + + TORRENT_ASSERT(i != m_web_seeds.end()); + if (i == m_web_seeds.end()) return; + + auto* peer = static_cast(i->peer_info.connection); + if (peer != nullptr) + { + // if we have a connection for this web seed, we also need to + // disconnect it and clear its reference to the peer_info object + // that's part of the web_seed_t we're about to remove + TORRENT_ASSERT(peer->m_in_use == 1337); + peer->disconnect(ec, op, error); + peer->set_peer_info(nullptr); + } + remove_web_seed_iter(i); + } + + void torrent::retry_web_seed(peer_connection* p, boost::optional const retry) + { + TORRENT_ASSERT(is_single_thread()); + auto const i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() + , [p] (web_seed_t const& ws) { return ws.peer_info.connection == p; }); + + TORRENT_ASSERT(i != m_web_seeds.end()); + if (i == m_web_seeds.end()) return; + if (i->removed) return; + i->retry = aux::time_now32() + value_or(retry, seconds32( + settings().get_int(settings_pack::urlseed_wait_retry))); + } + + torrent_state torrent::get_peer_list_state() + { + torrent_state ret; + ret.is_paused = is_paused(); + ret.is_finished = is_finished(); + ret.allow_multiple_connections_per_ip = settings().get_bool(settings_pack::allow_multiple_connections_per_ip); + ret.max_peerlist_size = is_paused() + ? settings().get_int(settings_pack::max_paused_peerlist_size) + : settings().get_int(settings_pack::max_peerlist_size); + ret.min_reconnect_time = settings().get_int(settings_pack::min_reconnect_time); + + ret.ip = m_ses.external_address(); + ret.port = m_ses.listen_port(); + ret.max_failcount = settings().get_int(settings_pack::max_failcount); + return ret; + } + + bool torrent::try_connect_peer() + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(want_peers()); + + torrent_state st = get_peer_list_state(); + need_peer_list(); + torrent_peer* p = m_peer_list->connect_one_peer(m_ses.session_time(), &st); + peers_erased(st.erased); + inc_stats_counter(counters::connection_attempt_loops, st.loop_counter); + + if (p == nullptr) + { + m_stats_counters.inc_stats_counter(counters::no_peer_connection_attempts); + update_want_peers(); + return false; + } + + if (!connect_to_peer(p)) + { + m_stats_counters.inc_stats_counter(counters::missed_connection_attempts); + m_peer_list->inc_failcount(p); + update_want_peers(); + return false; + } + update_want_peers(); + + return true; + } + + torrent_peer* torrent::add_peer(tcp::endpoint const& adr + , peer_source_flags_t const source, pex_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + +#ifndef TORRENT_DISABLE_DHT + if (source != peer_info::resume_data) + { + // try to send a DHT ping to this peer + // as well, to figure out if it supports + // DHT (uTorrent and BitComet don't + // advertise support) + session().add_dht_node({adr.address(), adr.port()}); + } +#endif + + if (m_apply_ip_filter + && m_ip_filter + && m_ip_filter->access(adr.address()) & ip_filter::blocked) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , adr, peer_blocked_alert::ip_filter); + +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return nullptr; + } + + if (m_ses.get_port_filter().access(adr.port()) & port_filter::blocked) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , adr, peer_blocked_alert::port_filter); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return nullptr; + } + +#if TORRENT_USE_I2P + // if this is an i2p torrent, and we don't allow mixed mode + // no regular peers should ever be added! + if (!settings().get_bool(settings_pack::allow_i2p_mixed) && is_i2p()) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , adr, peer_blocked_alert::i2p_mixed); + return nullptr; + } +#endif + + if (settings().get_bool(settings_pack::no_connect_privileged_ports) && adr.port() < 1024) + { + if (alerts().should_post()) + alerts().emplace_alert(get_handle() + , adr, peer_blocked_alert::privileged_ports); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return nullptr; + } + + need_peer_list(); + torrent_state st = get_peer_list_state(); + torrent_peer* p = m_peer_list->add_peer(adr, source, flags, &st); + peers_erased(st.erased); + + if (p) + { + state_updated(); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source + , st.first_time_seen + ? torrent_plugin::first_time + : add_peer_flags_t{}); +#endif + } + else + { +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + } + update_want_peers(); + state_updated(); + return p; + } + + bool torrent::ban_peer(torrent_peer* tp) + { + if (!settings().get_bool(settings_pack::ban_web_seeds) && tp->web_seed) + return false; + + need_peer_list(); + if (!m_peer_list->ban_peer(tp)) return false; + update_want_peers(); + + inc_stats_counter(counters::num_banned_peers); + return true; + } + + void torrent::set_seed(torrent_peer* p, bool const s) + { + if (p->seed != s) + { + if (s) + { + TORRENT_ASSERT(m_num_seeds < 0xffff); + ++m_num_seeds; + } + else + { + TORRENT_ASSERT(m_num_seeds > 0); + --m_num_seeds; + } + } + + need_peer_list(); + m_peer_list->set_seed(p, s); + update_auto_sequential(); + } + + void torrent::clear_failcount(torrent_peer* p) + { + need_peer_list(); + m_peer_list->set_failcount(p, 0); + update_want_peers(); + } + + std::pair torrent::find_peers(address const& a) + { + need_peer_list(); + return m_peer_list->find_peers(a); + } + + void torrent::update_peer_port(int const port, torrent_peer* p + , peer_source_flags_t const src) + { + need_peer_list(); + torrent_state st = get_peer_list_state(); + m_peer_list->update_peer_port(port, p, src, &st); + peers_erased(st.erased); + update_want_peers(); + } + + // verify piece is used when checking resume data or when the user + // adds a piece + void torrent::verify_piece(piece_index_t const piece) + { +// picker().mark_as_checking(piece); + + TORRENT_ASSERT(m_storage); + + m_ses.disk_thread().async_hash(m_storage, piece, {} + , std::bind(&torrent::on_piece_verified, shared_from_this(), _1, _2, _3)); + } + + announce_entry* torrent::find_tracker(std::string const& url) + { + auto i = std::find_if(m_trackers.begin(), m_trackers.end() + , [&url](announce_entry const& ae) { return ae.url == url; }); + if (i == m_trackers.end()) return nullptr; + return &*i; + } + + void torrent::ip_filter_updated() + { + if (!m_apply_ip_filter) return; + if (!m_peer_list) return; + if (!m_ip_filter) return; + + torrent_state st = get_peer_list_state(); + std::vector
    banned; + m_peer_list->apply_ip_filter(*m_ip_filter, &st, banned); + + if (alerts().should_post()) + { + for (auto const& addr : banned) + alerts().emplace_alert(get_handle() + , tcp::endpoint(addr, 0) + , peer_blocked_alert::ip_filter); + } + + peers_erased(st.erased); + } + + void torrent::port_filter_updated() + { + if (!m_apply_ip_filter) return; + if (!m_peer_list) return; + + torrent_state st = get_peer_list_state(); + std::vector
    banned; + m_peer_list->apply_port_filter(m_ses.get_port_filter(), &st, banned); + + if (alerts().should_post()) + { + for (auto const& addr : banned) + alerts().emplace_alert(get_handle() + , tcp::endpoint(addr, 0) + , peer_blocked_alert::port_filter); + } + + peers_erased(st.erased); + } + + // this is called when torrent_peers are removed from the peer_list + // (peer-list). It removes any references we may have to those torrent_peers, + // so we don't leave then dangling + void torrent::peers_erased(std::vector const& peers) + { + if (!has_picker()) return; + + for (auto const p : peers) + { + m_picker->clear_peer(p); + } +#if TORRENT_USE_INVARIANT_CHECKS + m_picker->check_peers(); +#endif + } + +#if TORRENT_ABI_VERSION == 1 +#if !TORRENT_NO_FPU + void torrent::file_progress_float(aux::vector& fp) + { + TORRENT_ASSERT(is_single_thread()); + if (!valid_metadata()) + { + fp.clear(); + return; + } + + fp.resize(m_torrent_file->num_files(), 1.f); + if (is_seed()) return; + + aux::vector progress; + file_progress(progress); + file_storage const& fs = m_torrent_file->files(); + for (auto const i : fs.file_range()) + { + std::int64_t file_size = m_torrent_file->files().file_size(i); + if (file_size == 0) fp[i] = 1.f; + else fp[i] = float(progress[i]) / file_size; + } + } +#endif +#endif // TORRENT_ABI_VERSION + + void torrent::file_progress(aux::vector& fp, int const flags) + { + TORRENT_ASSERT(is_single_thread()); + if (!valid_metadata()) + { + fp.clear(); + return; + } + + // if we're a seed, we don't have an m_file_progress anyway + // since we don't need one. We know we have all files + // just fill in the full file sizes as a shortcut + if (is_seed()) + { + fp.resize(m_torrent_file->num_files()); + file_storage const& fs = m_torrent_file->files(); + for (auto const i : fs.file_range()) + fp[i] = fs.file_size(i); + return; + } + + if (num_have() == 0 || m_file_progress.empty()) + { + // if we don't have any pieces, just return zeroes + fp.clear(); + fp.resize(m_torrent_file->num_files(), 0); + } + else + { + m_file_progress.export_progress(fp); + } + + if (flags & torrent_handle::piece_granularity) + return; + + TORRENT_ASSERT(has_picker()); + + std::vector q = m_picker->get_download_queue(); + + file_storage const& fs = m_torrent_file->files(); + for (auto const& dp : q) + { + std::int64_t offset = std::int64_t(static_cast(dp.index)) + * m_torrent_file->piece_length(); + file_index_t file = fs.file_index_at_offset(offset); + int idx = -1; + for (auto const& info : m_picker->blocks_for_piece(dp)) + { + ++idx; + TORRENT_ASSERT(file < fs.end_file()); + TORRENT_ASSERT(offset == std::int64_t(static_cast(dp.index)) + * m_torrent_file->piece_length() + + idx * block_size()); + TORRENT_ASSERT(offset < m_torrent_file->total_size()); + while (offset >= fs.file_offset(file) + fs.file_size(file)) + { + ++file; + } + TORRENT_ASSERT(file < fs.end_file()); + + std::int64_t block = block_size(); + + if (info.state == piece_picker::block_info::state_none) + { + offset += block; + continue; + } + + if (info.state == piece_picker::block_info::state_requested) + { + block = 0; + torrent_peer* p = info.peer; + if (p != nullptr && p->connection) + { + auto* peer = static_cast(p->connection); + auto pbp = peer->downloading_piece_progress(); + if (pbp.piece_index == dp.index && pbp.block_index == idx) + block = pbp.bytes_downloaded; + TORRENT_ASSERT(block <= block_size()); + } + + if (block == 0) + { + offset += block_size(); + continue; + } + } + + if (offset + block > fs.file_offset(file) + fs.file_size(file)) + { + std::int64_t left_over = block_size() - block; + // split the block on multiple files + while (block > 0) + { + TORRENT_ASSERT(offset <= fs.file_offset(file) + fs.file_size(file)); + std::int64_t const slice = std::min(fs.file_offset(file) + fs.file_size(file) - offset + , block); + fp[file] += slice; + offset += slice; + block -= slice; + TORRENT_ASSERT(offset <= fs.file_offset(file) + fs.file_size(file)); + if (offset == fs.file_offset(file) + fs.file_size(file)) + { + ++file; + if (file == fs.end_file()) + { + offset += block; + break; + } + } + } + offset += left_over; + TORRENT_ASSERT(offset == std::int64_t(static_cast(dp.index)) + * m_torrent_file->piece_length() + + (idx + 1) * block_size()); + } + else + { + fp[file] += block; + offset += block_size(); + } + TORRENT_ASSERT(file <= fs.end_file()); + } + } + } + + void torrent::new_external_ip() + { + if (m_peer_list) m_peer_list->clear_peer_prio(); + } + + void torrent::stop_when_ready(bool const b) + { + m_stop_when_ready = b; + + // to avoid race condition, if we're already in a downloading state, + // trigger the stop-when-ready logic immediately. + if (m_stop_when_ready && is_downloading_state(m_state)) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("stop_when_ready triggered"); +#endif + auto_managed(false); + pause(); + m_stop_when_ready = false; + } + } + + void torrent::set_state(torrent_status::state_t const s) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(s != 0); // this state isn't used anymore + +#if TORRENT_USE_ASSERTS + + if (s == torrent_status::seeding) + { + TORRENT_ASSERT(is_seed()); + TORRENT_ASSERT(is_finished()); + } + if (s == torrent_status::finished) + TORRENT_ASSERT(is_finished()); + if (s == torrent_status::downloading && m_state == torrent_status::finished) + TORRENT_ASSERT(!is_finished()); +#endif + + if (int(m_state) == s) return; + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert(get_handle() + , s, static_cast(m_state)); + } + + if (s == torrent_status::finished + && alerts().should_post()) + { + alerts().emplace_alert( + get_handle()); + } + + if (m_stop_when_ready + && !is_downloading_state(m_state) + && is_downloading_state(s)) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("stop_when_ready triggered"); +#endif + // stop_when_ready is set, and we're transitioning from a downloading + // state to a non-downloading state. pause the torrent. Note that + // "downloading" is defined broadly to include any state where we + // either upload or download (for the purpose of this flag). + auto_managed(false); + pause(); + m_stop_when_ready = false; + } + + m_state = s; + +#ifndef TORRENT_DISABLE_LOGGING + debug_log("set_state() %d", m_state); +#endif + + update_gauge(); + update_want_peers(); + update_want_tick(); + update_state_list(); + + state_updated(); + +#ifndef TORRENT_DISABLE_EXTENSIONS + for (auto& ext : m_extensions) + { + ext->on_state(state()); + } +#endif + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void torrent::notify_extension_add_peer(tcp::endpoint const& ip + , peer_source_flags_t const src, add_peer_flags_t const flags) + { + for (auto& ext : m_extensions) + { + ext->on_add_peer(ip, src, flags); + } + } +#endif + + void torrent::state_updated() + { + // if this fails, this function is probably called + // from within the torrent constructor, which it + // shouldn't be. Whichever function ends up calling + // this should probably be moved to torrent::start() + TORRENT_ASSERT(shared_from_this()); + + // we can't call state_updated() while the session + // is building the status update alert + TORRENT_ASSERT(!m_ses.is_posting_torrent_updates()); + + // we're not subscribing to this torrent, don't add it + if (!m_state_subscription) return; + + aux::vector& list = m_ses.torrent_list(aux::session_interface::torrent_state_updates); + + // if it has already been updated this round, no need to + // add it to the list twice + if (m_links[aux::session_interface::torrent_state_updates].in_list()) + { +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(find(list.begin(), list.end(), this) != list.end()); +#endif + return; + } + +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(find(list.begin(), list.end(), this) == list.end()); +#endif + + m_links[aux::session_interface::torrent_state_updates].insert(list, this); + } + + void torrent::status(torrent_status* st, status_flags_t const flags) + { + INVARIANT_CHECK; + + time_point32 const now = aux::time_now32(); + + st->handle = get_handle(); + st->info_hash = info_hash(); +#if TORRENT_ABI_VERSION == 1 + st->is_loaded = true; +#endif + + if (flags & torrent_handle::query_name) + st->name = name(); + + if (flags & torrent_handle::query_save_path) + st->save_path = save_path(); + + if (flags & torrent_handle::query_torrent_file) + st->torrent_file = m_torrent_file; + + st->has_incoming = m_has_incoming; + st->errc = m_error; + st->error_file = m_error_file; + +#if TORRENT_ABI_VERSION == 1 + if (m_error) st->error = convert_from_native(m_error.message()) + + ": " + resolve_filename(m_error_file); + st->seed_mode = m_seed_mode; +#endif + st->moving_storage = m_moving_storage; + + st->announcing_to_trackers = m_announce_to_trackers; + st->announcing_to_lsd = m_announce_to_lsd; + st->announcing_to_dht = m_announce_to_dht; +#if TORRENT_ABI_VERSION == 1 + st->stop_when_ready = m_stop_when_ready; +#endif + + st->added_time = m_added_time; + st->completed_time = m_completed_time; + +#if TORRENT_ABI_VERSION == 1 + st->last_scrape = static_cast(total_seconds(aux::time_now32() - m_last_scrape)); +#endif + +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_SHARE_MODE + st->share_mode = m_share_mode; +#else + st->share_mode = false; +#endif + st->upload_mode = m_upload_mode; +#endif + st->up_bandwidth_queue = 0; + st->down_bandwidth_queue = 0; +#if TORRENT_ABI_VERSION == 1 + st->priority = priority(); +#endif + + st->num_peers = num_peers() - m_num_connecting; + + st->list_peers = m_peer_list ? m_peer_list->num_peers() : 0; + st->list_seeds = m_peer_list ? m_peer_list->num_seeds() : 0; + st->connect_candidates = m_peer_list ? m_peer_list->num_connect_candidates() : 0; + TORRENT_ASSERT(st->connect_candidates >= 0); + st->seed_rank = seed_rank(settings()); + + st->all_time_upload = m_total_uploaded; + st->all_time_download = m_total_downloaded; + + // activity time +#if TORRENT_ABI_VERSION == 1 + st->finished_time = int(total_seconds(finished_time())); + st->active_time = int(total_seconds(active_time())); + st->seeding_time = int(total_seconds(seeding_time())); + + time_point32 const unset{seconds32(0)}; + + st->time_since_upload = m_last_upload == unset ? -1 + : static_cast(total_seconds(aux::time_now32() - m_last_upload)); + st->time_since_download = m_last_download == unset ? -1 + : static_cast(total_seconds(aux::time_now32() - m_last_download)); +#endif + + st->finished_duration = finished_time(); + st->active_duration = active_time(); + st->seeding_duration = seeding_time(); + + st->last_upload = m_last_upload; + st->last_download = m_last_download; + + st->storage_mode = static_cast(m_storage_mode); + + st->num_complete = (m_complete == 0xffffff) ? -1 : m_complete; + st->num_incomplete = (m_incomplete == 0xffffff) ? -1 : m_incomplete; +#if TORRENT_ABI_VERSION == 1 + st->paused = is_torrent_paused(); + st->auto_managed = m_auto_managed; + st->sequential_download = m_sequential_download; +#endif + st->is_seeding = is_seed(); + st->is_finished = is_finished(); +#if TORRENT_ABI_VERSION == 1 +#ifndef TORRENT_DISABLE_SUPERSEEDING + st->super_seeding = m_super_seeding; +#endif +#endif + st->has_metadata = valid_metadata(); + bytes_done(*st, flags); + TORRENT_ASSERT(st->total_wanted_done >= 0); + TORRENT_ASSERT(st->total_done >= st->total_wanted_done); + + // payload transfer + st->total_payload_download = m_stat.total_payload_download(); + st->total_payload_upload = m_stat.total_payload_upload(); + + // total transfer + st->total_download = m_stat.total_payload_download() + + m_stat.total_protocol_download(); + st->total_upload = m_stat.total_payload_upload() + + m_stat.total_protocol_upload(); + + // failed bytes + st->total_failed_bytes = m_total_failed_bytes; + st->total_redundant_bytes = m_total_redundant_bytes; + + // transfer rate + st->download_rate = m_stat.download_rate(); + st->upload_rate = m_stat.upload_rate(); + st->download_payload_rate = m_stat.download_payload_rate(); + st->upload_payload_rate = m_stat.upload_payload_rate(); + + if (is_paused() || m_tracker_timer.expires_at() < now) + st->next_announce = seconds(0); + else + st->next_announce = m_tracker_timer.expires_at() - now; + + if (st->next_announce.count() < 0) + st->next_announce = seconds(0); + +#if TORRENT_ABI_VERSION == 1 + st->announce_interval = seconds(0); +#endif + + st->current_tracker.clear(); + if (m_last_working_tracker >= 0) + { + TORRENT_ASSERT(m_last_working_tracker < m_trackers.end_index()); + const int i = m_last_working_tracker; + st->current_tracker = m_trackers[i].url; + } + else + { + for (auto const& t : m_trackers) + { + if (std::any_of(t.endpoints.begin(), t.endpoints.end() + , [](announce_endpoint const& aep) { return aep.updating; })) continue; + if (!t.verified) continue; + st->current_tracker = t.url; + break; + } + } + + if ((flags & torrent_handle::query_verified_pieces)) + { + st->verified_pieces = m_verified; + } + + st->num_uploads = m_num_uploads; + st->uploads_limit = m_max_uploads == (1 << 24) - 1 ? -1 : m_max_uploads; + st->num_connections = num_peers(); + st->connections_limit = m_max_connections == (1 << 24) - 1 ? -1 : m_max_connections; + // if we don't have any metadata, stop here + + st->queue_position = queue_position(); + st->need_save_resume = need_save_resume_data(); +#if TORRENT_ABI_VERSION == 1 + st->ip_filter_applies = m_apply_ip_filter; +#endif + + st->state = static_cast(m_state); + st->flags = this->flags(); + +#if TORRENT_USE_ASSERTS + if (st->state == torrent_status::finished + || st->state == torrent_status::seeding) + { + // it may be tempting to assume that st->is_finished == true here, but + // this assumption does not always hold. We transition to "finished" + // when we receive the last block of the last piece, which is before + // the hash check comes back. "is_finished" is set to true once all the + // pieces have been hash checked. So, there's a short window where it + // doesn't hold. + } +#endif + + if (!valid_metadata()) + { + st->state = torrent_status::downloading_metadata; + st->progress_ppm = m_progress_ppm; +#if !TORRENT_NO_FPU + st->progress = m_progress_ppm / 1000000.f; +#endif + st->block_size = 0; + return; + } + + st->block_size = block_size(); + + if (m_state == torrent_status::checking_files) + { + st->progress_ppm = m_progress_ppm; +#if !TORRENT_NO_FPU + st->progress = m_progress_ppm / 1000000.f; +#endif + } + else if (st->total_wanted == 0) + { + st->progress_ppm = 1000000; + st->progress = 1.f; + } + else + { + st->progress_ppm = int(st->total_wanted_done * 1000000 + / st->total_wanted); +#if !TORRENT_NO_FPU + st->progress = st->progress_ppm / 1000000.f; +#endif + } + + if (flags & torrent_handle::query_pieces) + { + int const num_pieces = m_torrent_file->num_pieces(); + if (has_picker()) + { + st->pieces.resize(num_pieces, false); + for (auto const i : st->pieces.range()) + if (m_picker->has_piece_passed(i)) st->pieces.set_bit(i); + } + else if (m_have_all) + { + st->pieces.resize(num_pieces, true); + } + else + { + st->pieces.resize(num_pieces, false); + } + } + st->num_pieces = num_have(); + st->num_seeds = num_seeds(); + if ((flags & torrent_handle::query_distributed_copies) && m_picker.get()) + { + std::tie(st->distributed_full_copies, st->distributed_fraction) = + m_picker->distributed_copies(); +#if TORRENT_NO_FPU + st->distributed_copies = -1.f; +#else + st->distributed_copies = st->distributed_full_copies + + float(st->distributed_fraction) / 1000; +#endif + } + else + { + st->distributed_full_copies = -1; + st->distributed_fraction = -1; + st->distributed_copies = -1.f; + } + + st->last_seen_complete = m_swarm_last_seen_complete; + } + + int torrent::priority() const + { + int priority = 0; + for (int i = 0; i < num_classes(); ++i) + { + int const* prio = m_ses.peer_classes().at(class_at(i))->priority; + priority = std::max(priority, prio[peer_connection::upload_channel]); + priority = std::max(priority, prio[peer_connection::download_channel]); + } + return priority; + } + +#if TORRENT_ABI_VERSION == 1 + void torrent::set_priority(int const prio) + { + // priority 1 is default + if (prio == 1 && m_peer_class == peer_class_t{}) return; + + if (m_peer_class == peer_class_t{}) + setup_peer_class(); + + struct peer_class* tpc = m_ses.peer_classes().at(m_peer_class); + TORRENT_ASSERT(tpc); + tpc->priority[peer_connection::download_channel] = prio; + tpc->priority[peer_connection::upload_channel] = prio; + + state_updated(); + } +#endif + + void torrent::add_redundant_bytes(int const b, waste_reason const reason) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(b > 0); + TORRENT_ASSERT(static_cast(reason) >= 0); + TORRENT_ASSERT(static_cast(reason) < static_cast(waste_reason::max)); + + if (m_total_redundant_bytes <= std::numeric_limits::max() - b) + m_total_redundant_bytes += b; + else + m_total_redundant_bytes = std::numeric_limits::max(); + + // the stats counters are 64 bits, so we don't check for overflow there + m_stats_counters.inc_stats_counter(counters::recv_redundant_bytes, b); + m_stats_counters.inc_stats_counter(counters::waste_piece_timed_out + static_cast(reason), b); + } + + void torrent::add_failed_bytes(int const b) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(b > 0); + if (m_total_failed_bytes <= std::numeric_limits::max() - b) + m_total_failed_bytes += b; + else + m_total_failed_bytes = std::numeric_limits::max(); + + // the stats counters are 64 bits, so we don't check for overflow there + m_stats_counters.inc_stats_counter(counters::recv_failed_bytes, b); + } + + // the number of connected peers that are seeds + int torrent::num_seeds() const + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + return int(m_num_seeds) - int(m_num_connecting_seeds); + } + + // the number of connected peers that are not seeds + int torrent::num_downloaders() const + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + int const ret = num_peers() + - m_num_seeds + - m_num_connecting + + m_num_connecting_seeds; + TORRENT_ASSERT(ret >= 0); + return ret; + } + + void torrent::tracker_request_error(tracker_request const& r + , error_code const& ec, std::string const& msg + , seconds32 const retry_interval) + { + TORRENT_ASSERT(is_single_thread()); + + INVARIANT_CHECK; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + debug_log("*** tracker error: (%d) %s %s", ec.value() + , ec.message().c_str(), msg.c_str()); + } +#endif + if (0 == (r.kind & tracker_request::scrape_request)) + { + // announce request + announce_entry* ae = find_tracker(r.url); + int fails = 0; + tcp::endpoint local_endpoint; + if (ae) + { + auto aep = std::find_if(ae->endpoints.begin(), ae->endpoints.end() + , [&](announce_endpoint const& e) { return e.socket == r.outgoing_socket; }); + + if (aep != ae->endpoints.end()) + { + local_endpoint = aep->local_endpoint; + aep->failed(settings().get_int(settings_pack::tracker_backoff) + , retry_interval); + aep->last_error = ec; + aep->message = msg; + fails = aep->fails; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** increment tracker fail count [ep: %s url: %s %d]" + , print_endpoint(aep->local_endpoint).c_str(), r.url.c_str(), aep->fails); +#endif + // don't try to announce from this endpoint again + if (ec == boost::system::errc::address_family_not_supported + || ec == boost::system::errc::host_unreachable) + { + aep->enabled = false; +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** disabling endpoint [ep: %s url: %s ]" + , print_endpoint(aep->local_endpoint).c_str(), r.url.c_str()); +#endif + } + } + else if (r.outgoing_socket) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("*** no matching endpoint for request [%s, %s]" + , r.url.c_str(), print_endpoint(r.outgoing_socket.get_local_endpoint()).c_str()); +#endif + } + + int const tracker_index = int(ae - m_trackers.data()); + + // never talk to this tracker again + if (ec == error_code(410, http_category())) ae->fail_limit = 1; + + // if all endpoints fail, then we de-prioritize the tracker and try + // the next one in the tier + if (std::all_of(ae->endpoints.begin(), ae->endpoints.end() + , [](announce_endpoint const& ep) { return ep.fails > 0; })) + { + deprioritize_tracker(tracker_index); + } + } + if (m_ses.alerts().should_post() + || r.triggered_manually) + { + m_ses.alerts().emplace_alert(get_handle() + , local_endpoint, fails, r.url, ec, msg); + } + } + else + { + announce_entry* ae = find_tracker(r.url); + + // scrape request + if (ec == error_code(410, http_category())) + { + // never talk to this tracker again + if (ae != nullptr) ae->fail_limit = 1; + } + + // if this was triggered manually we need to post this unconditionally, + // since the client expects a response from its action, regardless of + // whether all tracker events have been enabled by the alert mask + if (m_ses.alerts().should_post() + || r.triggered_manually) + { + tcp::endpoint local_endpoint; + if (ae != nullptr) + { + auto aep = ae->find_endpoint(r.outgoing_socket); + if (aep != nullptr) local_endpoint = aep->local_endpoint; + } + + m_ses.alerts().emplace_alert(get_handle(), local_endpoint, r.url, ec); + } + } + // announce to the next working tracker + // We may have changed state into checking by now, in which case we + // shouldn't keep trying to announce + if ((!m_abort && !is_paused() && state() != torrent_status::checking_files) + || r.event == tracker_request::stopped) + { + announce_with_tracker(r.event); + } + update_tracker_timer(aux::time_now32()); + } + +#ifndef TORRENT_DISABLE_LOGGING + bool torrent::should_log() const + { + return alerts().should_post(); + } + + TORRENT_FORMAT(2,3) + void torrent::debug_log(char const* fmt, ...) const noexcept try + { + if (!alerts().should_post()) return; + + va_list v; + va_start(v, fmt); + alerts().emplace_alert( + const_cast(this)->get_handle(), fmt, v); + va_end(v); + } + catch (std::exception const&) {} +#endif + +} diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp new file mode 100644 index 0000000..8c6591f --- /dev/null +++ b/src/torrent_handle.cpp @@ -0,0 +1,872 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_call.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/torrent_flags.hpp" +#include "libtorrent/pex_flags.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/peer_info.hpp" // for peer_list_entry +#endif + +#if TORRENT_ABI_VERSION == 1 && defined TORRENT_WINDOWS +#include "libtorrent/aux_/escape_string.hpp" +#endif + +using libtorrent::aux::session_impl; + +namespace libtorrent { + + constexpr resume_data_flags_t torrent_handle::flush_disk_cache; + constexpr resume_data_flags_t torrent_handle::save_info_dict; + constexpr resume_data_flags_t torrent_handle::only_if_modified; + constexpr add_piece_flags_t torrent_handle::overwrite_existing; + constexpr pause_flags_t torrent_handle::graceful_pause; + constexpr pause_flags_t torrent_handle::clear_disk_cache; + constexpr deadline_flags_t torrent_handle::alert_when_available; + constexpr reannounce_flags_t torrent_handle::ignore_min_interval; + + constexpr status_flags_t torrent_handle::query_distributed_copies; + constexpr status_flags_t torrent_handle::query_accurate_download_counters; + constexpr status_flags_t torrent_handle::query_last_seen_complete; + constexpr status_flags_t torrent_handle::query_pieces; + constexpr status_flags_t torrent_handle::query_verified_pieces; + constexpr status_flags_t torrent_handle::query_torrent_file; + constexpr status_flags_t torrent_handle::query_name; + constexpr status_flags_t torrent_handle::query_save_path; + +#ifndef BOOST_NO_EXCEPTIONS + [[noreturn]] void throw_invalid_handle() + { + throw system_error(errors::invalid_torrent_handle); + } +#endif + + template + void torrent_handle::async_call(Fun f, Args&&... a) const + { + std::shared_ptr t = m_torrent.lock(); + if (!t) aux::throw_ex(errors::invalid_torrent_handle); + auto& ses = static_cast(t->session()); + ses.get_io_service().dispatch([=,&ses] () + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (t.get()->*f)(a...); +#ifndef BOOST_NO_EXCEPTIONS + } catch (system_error const& e) { + ses.alerts().emplace_alert(torrent_handle(m_torrent) + , e.code(), e.what()); + } catch (std::exception const& e) { + ses.alerts().emplace_alert(torrent_handle(m_torrent) + , error_code(), e.what()); + } catch (...) { + ses.alerts().emplace_alert(torrent_handle(m_torrent) + , error_code(), "unknown error"); + } +#endif + } ); + } + + template + void torrent_handle::sync_call(Fun f, Args&&... a) const + { + std::shared_ptr t = m_torrent.lock(); + if (!t) aux::throw_ex(errors::invalid_torrent_handle); + auto& ses = static_cast(t->session()); + + // this is the flag to indicate the call has completed + bool done = false; + + std::exception_ptr ex; + ses.get_io_service().dispatch([=,&done,&ses,&ex] () + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + (t.get()->*f)(a...); +#ifndef BOOST_NO_EXCEPTIONS + } catch (...) { + ex = std::current_exception(); + } +#endif + std::unique_lock l(ses.mut); + done = true; + ses.cond.notify_all(); + } ); + + aux::torrent_wait(done, ses); + if (ex) std::rethrow_exception(ex); + } + + template + Ret torrent_handle::sync_call_ret(Ret def, Fun f, Args&&... a) const + { + std::shared_ptr t = m_torrent.lock(); + Ret r = def; +#ifndef BOOST_NO_EXCEPTIONS + if (!t) throw_invalid_handle(); +#else + if (!t) return r; +#endif + auto& ses = static_cast(t->session()); + + // this is the flag to indicate the call has completed + bool done = false; + + std::exception_ptr ex; + ses.get_io_service().dispatch([=,&r,&done,&ses,&ex] () + { +#ifndef BOOST_NO_EXCEPTIONS + try { +#endif + r = (t.get()->*f)(a...); +#ifndef BOOST_NO_EXCEPTIONS + } catch (...) { + ex = std::current_exception(); + } +#endif + std::unique_lock l(ses.mut); + done = true; + ses.cond.notify_all(); + } ); + + aux::torrent_wait(done, ses); + + if (ex) std::rethrow_exception(ex); + return r; + } + + sha1_hash torrent_handle::info_hash() const + { + std::shared_ptr t = m_torrent.lock(); + return t ? t->info_hash() : sha1_hash(); + } + + int torrent_handle::max_uploads() const + { + return sync_call_ret(0, &torrent::max_uploads); + } + + void torrent_handle::set_max_uploads(int max_uploads) const + { + TORRENT_ASSERT_PRECOND(max_uploads >= 2 || max_uploads == -1); + async_call(&torrent::set_max_uploads, max_uploads, true); + } + + int torrent_handle::max_connections() const + { + return sync_call_ret(0, &torrent::max_connections); + } + + void torrent_handle::set_max_connections(int max_connections) const + { + TORRENT_ASSERT_PRECOND(max_connections >= 2 || max_connections == -1); + async_call(&torrent::set_max_connections, max_connections, true); + } + + void torrent_handle::set_upload_limit(int limit) const + { + TORRENT_ASSERT_PRECOND(limit >= -1); + async_call(&torrent::set_upload_limit, limit); + } + + int torrent_handle::upload_limit() const + { + return sync_call_ret(0, &torrent::upload_limit); + } + + void torrent_handle::set_download_limit(int limit) const + { + TORRENT_ASSERT_PRECOND(limit >= -1); + async_call(&torrent::set_download_limit, limit); + } + + int torrent_handle::download_limit() const + { + return sync_call_ret(0, &torrent::download_limit); + } + + void torrent_handle::move_storage(std::string const& save_path, move_flags_t flags) const + { + async_call(&torrent::move_storage, save_path, flags); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent_handle::move_storage( + std::string const& save_path, int const flags) const + { + async_call(&torrent::move_storage, save_path, static_cast(flags)); + } + +#if defined TORRENT_WINDOWS + void torrent_handle::move_storage( + std::wstring const& save_path, int flags) const + { + async_call(&torrent::move_storage, convert_from_wstring(save_path), static_cast(flags)); + } + + void torrent_handle::rename_file(file_index_t index, std::wstring const& new_name) const + { + async_call(&torrent::rename_file, index, convert_from_wstring(new_name)); + } +#endif // TORRENT_WINDOWS +#endif // TORRENT_ABI_VERSION + + void torrent_handle::rename_file(file_index_t index, std::string const& new_name) const + { + async_call(&torrent::rename_file, index, new_name); + } + + void torrent_handle::add_extension( + std::function(torrent_handle const&, void*)> const& ext + , void* userdata) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + async_call(&torrent::add_extension_fun, ext, userdata); +#else + TORRENT_UNUSED(ext); + TORRENT_UNUSED(userdata); +#endif + } + + bool torrent_handle::set_metadata(span metadata) const + { + return sync_call_ret(false, &torrent::set_metadata, metadata); + } + + void torrent_handle::pause(pause_flags_t const flags) const + { + async_call(&torrent::pause, flags & graceful_pause); + } + + torrent_flags_t torrent_handle::flags() const + { + return sync_call_ret(torrent_flags_t{}, &torrent::flags); + } + + void torrent_handle::set_flags(torrent_flags_t const flags + , torrent_flags_t const mask) const + { + async_call(&torrent::set_flags, flags, mask); + } + + void torrent_handle::set_flags(torrent_flags_t const flags) const + { + async_call(&torrent::set_flags, torrent_flags::all, flags); + } + + void torrent_handle::unset_flags(torrent_flags_t const flags) const + { + async_call(&torrent::set_flags, torrent_flags_t{}, flags); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent_handle::stop_when_ready(bool b) const + { async_call(&torrent::stop_when_ready, b); } + + void torrent_handle::set_upload_mode(bool b) const + { async_call(&torrent::set_upload_mode, b); } + + void torrent_handle::set_share_mode(bool b) const + { + TORRENT_UNUSED(b); +#ifndef TORRENT_DISABLE_SHARE_MODE + async_call(&torrent::set_share_mode, b); +#endif + } + + void torrent_handle::apply_ip_filter(bool b) const + { async_call(&torrent::set_apply_ip_filter, b); } + + void torrent_handle::auto_managed(bool m) const + { async_call(&torrent::auto_managed, m); } + + void torrent_handle::set_pinned(bool) const {} + + void torrent_handle::set_sequential_download(bool sd) const + { async_call(&torrent::set_sequential_download, sd); } +#endif + + void torrent_handle::flush_cache() const + { + async_call(&torrent::flush_cache); + } + + void torrent_handle::set_ssl_certificate( + std::string const& certificate + , std::string const& private_key + , std::string const& dh_params + , std::string const& passphrase) + { +#ifdef TORRENT_USE_OPENSSL + async_call(&torrent::set_ssl_cert, certificate, private_key, dh_params, passphrase); +#else + TORRENT_UNUSED(certificate); + TORRENT_UNUSED(private_key); + TORRENT_UNUSED(dh_params); + TORRENT_UNUSED(passphrase); +#endif + } + + void torrent_handle::set_ssl_certificate_buffer( + std::string const& certificate + , std::string const& private_key + , std::string const& dh_params) + { +#ifdef TORRENT_USE_OPENSSL + async_call(&torrent::set_ssl_cert_buffer, certificate, private_key, dh_params); +#else + TORRENT_UNUSED(certificate); + TORRENT_UNUSED(private_key); + TORRENT_UNUSED(dh_params); +#endif + } + + void torrent_handle::save_resume_data(resume_data_flags_t f) const + { + async_call(&torrent::save_resume_data, f); + } + + bool torrent_handle::need_save_resume_data() const + { + return sync_call_ret(false, &torrent::need_save_resume_data); + } + + void torrent_handle::force_recheck() const + { + async_call(&torrent::force_recheck); + } + + void torrent_handle::resume() const + { + async_call(&torrent::resume); + } + + queue_position_t torrent_handle::queue_position() const + { + return sync_call_ret(no_pos + , &torrent::queue_position); + } + + void torrent_handle::queue_position_up() const + { + async_call(&torrent::queue_up); + } + + void torrent_handle::queue_position_down() const + { + async_call(&torrent::queue_down); + } + + void torrent_handle::queue_position_set(queue_position_t const p) const + { + TORRENT_ASSERT_PRECOND(p >= queue_position_t{}); + if (p < queue_position_t{}) return; + async_call(&torrent::set_queue_position, p); + } + + void torrent_handle::queue_position_top() const + { + async_call(&torrent::set_queue_position, queue_position_t{}); + } + + void torrent_handle::queue_position_bottom() const + { + async_call(&torrent::set_queue_position, last_pos); + } + + void torrent_handle::clear_error() const + { + async_call(&torrent::clear_error); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent_handle::set_priority(int const p) const + { + async_call(&torrent::set_priority, p); + } + + void torrent_handle::set_tracker_login(std::string const& name + , std::string const& password) const + { + async_call(&torrent::set_tracker_login, name, password); + } +#endif + + void torrent_handle::file_progress(std::vector& progress, int flags) const + { + auto& arg = static_cast&>(progress); + sync_call(&torrent::file_progress, std::ref(arg), flags); + } + + torrent_status torrent_handle::status(status_flags_t const flags) const + { + torrent_status st; + sync_call(&torrent::status, &st, flags); + return st; + } + + void torrent_handle::piece_availability(std::vector& avail) const + { + auto availr = std::ref(static_cast&>(avail)); + sync_call(&torrent::piece_availability, availr); + } + + void torrent_handle::piece_priority(piece_index_t index, download_priority_t priority) const + { + async_call(&torrent::set_piece_priority, index, priority); + } + + download_priority_t torrent_handle::piece_priority(piece_index_t index) const + { + return sync_call_ret(dont_download, &torrent::piece_priority, index); + } + + void torrent_handle::prioritize_pieces(std::vector const& pieces) const + { + async_call(&torrent::prioritize_pieces + , static_cast const&>(pieces)); + } + + void torrent_handle::prioritize_pieces(std::vector> const& pieces) const + { + async_call(&torrent::prioritize_piece_list, pieces); + } + + std::vector torrent_handle::get_piece_priorities() const + { + aux::vector ret; + auto retp = &ret; + sync_call(&torrent::piece_priorities, retp); + return std::move(ret); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent_handle::prioritize_pieces(std::vector const& pieces) const + { + aux::vector p; + p.reserve(pieces.size()); + for (auto const prio : pieces) { + p.push_back(download_priority_t(static_cast(prio))); + } + async_call(&torrent::prioritize_pieces, p); + } + + void torrent_handle::prioritize_pieces(std::vector> const& pieces) const + { + std::vector> p; + p.reserve(pieces.size()); + async_call(&torrent::prioritize_piece_list, std::move(p)); + } + + std::vector torrent_handle::piece_priorities() const + { + aux::vector prio; + auto retp = &prio; + sync_call(&torrent::piece_priorities, retp); + std::vector ret; + ret.reserve(prio.size()); + for (auto p : prio) + ret.push_back(int(static_cast(p))); + return ret; + } +#endif + + void torrent_handle::file_priority(file_index_t index, download_priority_t priority) const + { + async_call(&torrent::set_file_priority, index, priority); + } + + download_priority_t torrent_handle::file_priority(file_index_t index) const + { + return sync_call_ret(dont_download, &torrent::file_priority, index); + } + + // TODO: support moving files into this call + void torrent_handle::prioritize_files(std::vector const& files) const + { + async_call(&torrent::prioritize_files + , static_cast const&>(files)); + } + + std::vector torrent_handle::get_file_priorities() const + { + aux::vector ret; + auto retp = &ret; + sync_call(&torrent::file_priorities, retp); + return std::move(ret); + } + +#if TORRENT_ABI_VERSION == 1 + +// ============ start deprecation =============== + + void torrent_handle::prioritize_files(std::vector const& files) const + { + aux::vector file_prio; + file_prio.reserve(files.size()); + for (auto const p : files) { + file_prio.push_back(download_priority_t(static_cast(p))); + } + async_call(&torrent::prioritize_files, file_prio); + } + + std::vector torrent_handle::file_priorities() const + { + aux::vector prio; + auto retp = &prio; + sync_call(&torrent::file_priorities, retp); + std::vector ret; + ret.reserve(prio.size()); + for (auto p : prio) + ret.push_back(int(static_cast(p))); + return ret; + } + + + int torrent_handle::get_peer_upload_limit(tcp::endpoint) const { return -1; } + int torrent_handle::get_peer_download_limit(tcp::endpoint) const { return -1; } + void torrent_handle::set_peer_upload_limit(tcp::endpoint, int /* limit */) const {} + void torrent_handle::set_peer_download_limit(tcp::endpoint, int /* limit */) const {} + void torrent_handle::set_ratio(float) const {} + void torrent_handle::use_interface(const char* net_interface) const + { + async_call(&torrent::use_interface, std::string(net_interface)); + } + +#if !TORRENT_NO_FPU + void torrent_handle::file_progress(std::vector& progress) const + { + sync_call(&torrent::file_progress_float, std::ref(static_cast&>(progress))); + } +#endif + + bool torrent_handle::is_seed() const + { return sync_call_ret(false, &torrent::is_seed); } + + bool torrent_handle::is_finished() const + { return sync_call_ret(false, &torrent::is_finished); } + + bool torrent_handle::is_paused() const + { return sync_call_ret(false, &torrent::is_torrent_paused); } + + bool torrent_handle::is_sequential_download() const + { return sync_call_ret(false, &torrent::is_sequential_download); } + + bool torrent_handle::is_auto_managed() const + { return sync_call_ret(false, &torrent::is_auto_managed); } + + bool torrent_handle::has_metadata() const + { return sync_call_ret(false, &torrent::valid_metadata); } + + bool torrent_handle::super_seeding() const + { +#ifndef TORRENT_DISABLE_SUPERSEEDING + return sync_call_ret(false, &torrent::super_seeding); +#else + return false; +#endif + } + +// ============ end deprecation =============== +#endif + + std::vector torrent_handle::trackers() const + { + static const std::vector empty; + return sync_call_ret>(empty, &torrent::trackers); + } + + void torrent_handle::add_url_seed(std::string const& url) const + { + async_call(&torrent::add_web_seed, url, web_seed_entry::url_seed + , std::string(), web_seed_entry::headers_t(), web_seed_flag_t{}); + } + + void torrent_handle::remove_url_seed(std::string const& url) const + { + async_call(&torrent::remove_web_seed, url, web_seed_entry::url_seed); + } + + std::set torrent_handle::url_seeds() const + { + static const std::set empty; + return sync_call_ret>(empty, &torrent::web_seeds, web_seed_entry::url_seed); + } + + void torrent_handle::add_http_seed(std::string const& url) const + { + async_call(&torrent::add_web_seed, url, web_seed_entry::http_seed + , std::string(), web_seed_entry::headers_t(), web_seed_flag_t{}); + } + + void torrent_handle::remove_http_seed(std::string const& url) const + { + async_call(&torrent::remove_web_seed, url, web_seed_entry::http_seed); + } + + std::set torrent_handle::http_seeds() const + { + static const std::set empty; + return sync_call_ret>(empty, &torrent::web_seeds, web_seed_entry::http_seed); + } + + void torrent_handle::replace_trackers( + std::vector const& urls) const + { + async_call(&torrent::replace_trackers, urls); + } + + void torrent_handle::add_tracker(announce_entry const& url) const + { + async_call(&torrent::add_tracker, url); + } + + void torrent_handle::add_piece(piece_index_t piece, char const* data, add_piece_flags_t const flags) const + { + sync_call(&torrent::add_piece, piece, data, flags); + } + + void torrent_handle::read_piece(piece_index_t piece) const + { + async_call(&torrent::read_piece, piece); + } + + bool torrent_handle::have_piece(piece_index_t piece) const + { + return sync_call_ret(false, &torrent::user_have_piece, piece); + } + + storage_interface* torrent_handle::get_storage_impl() const + { + return sync_call_ret(nullptr, &torrent::get_storage_impl); + } + + bool torrent_handle::is_valid() const + { + return !m_torrent.expired(); + } + + std::shared_ptr torrent_handle::torrent_file() const + { + return sync_call_ret>( + std::shared_ptr(), &torrent::get_torrent_copy); + } + +#if TORRENT_ABI_VERSION == 1 + // this function should either be removed, or return + // reference counted handle to the torrent_info which + // forces the torrent to stay loaded while the client holds it + torrent_info const& torrent_handle::get_torrent_info() const + { + static aux::array, 4> holder; + static int cursor = 0; + static std::mutex holder_mutex; + + std::shared_ptr r = torrent_file(); + + std::lock_guard l(holder_mutex); + holder[cursor++] = r; + cursor = cursor % holder.end_index(); + return *r; + } + + entry torrent_handle::write_resume_data() const + { + add_torrent_params params; + auto retr = std::ref(params); + sync_call(&torrent::write_resume_data, retr); + return libtorrent::write_resume_data(params); + } + + std::string torrent_handle::save_path() const + { + return sync_call_ret("", &torrent::save_path); + } + + std::string torrent_handle::name() const + { + return sync_call_ret("", &torrent::name); + } + +#endif + + void torrent_handle::connect_peer(tcp::endpoint const& adr + , peer_source_flags_t const source, pex_flags_t const flags) const + { + async_call(&torrent::add_peer, adr, source, flags); + } + + void torrent_handle::clear_peers() + { + async_call(&torrent::clear_peers); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent_handle::force_reannounce( + boost::posix_time::time_duration duration) const + { + async_call(&torrent::force_tracker_request, aux::time_now() + + seconds(duration.total_seconds()), -1, reannounce_flags_t{}); + } + + void torrent_handle::file_status(std::vector& status) const + { + status.clear(); + + std::shared_ptr t = m_torrent.lock(); + if (!t || !t->has_storage()) return; + auto& ses = static_cast(t->session()); + status = ses.disk_thread().get_status(t->storage()); + } +#endif + + void torrent_handle::force_dht_announce() const + { +#ifndef TORRENT_DISABLE_DHT + async_call(&torrent::dht_announce); +#endif + } + + void torrent_handle::force_reannounce(int s, int idx, reannounce_flags_t const flags) const + { + async_call(&torrent::force_tracker_request, aux::time_now() + seconds(s), idx, flags); + } + + std::vector torrent_handle::file_status() const + { + std::shared_ptr t = m_torrent.lock(); + if (!t || !t->has_storage()) return {}; + auto& ses = static_cast(t->session()); + return ses.disk_thread().get_status(t->storage()); + } + + void torrent_handle::scrape_tracker(int idx) const + { + async_call(&torrent::scrape_tracker, idx, true); + } + +#if TORRENT_ABI_VERSION == 1 + void torrent_handle::super_seeding(bool on) const + { + TORRENT_UNUSED(on); +#ifndef TORRENT_DISABLE_SUPERSEEDING + async_call(&torrent::set_super_seeding, on); +#endif + } + + void torrent_handle::get_full_peer_list(std::vector& v) const + { + auto vp = &v; + sync_call(&torrent::get_full_peer_list, vp); + } +#endif + + void torrent_handle::get_peer_info(std::vector& v) const + { + auto vp = &v; + sync_call(&torrent::get_peer_info, vp); + } + + void torrent_handle::get_download_queue(std::vector& queue) const + { + auto queuep = &queue; + sync_call(&torrent::get_download_queue, queuep); + } + + void torrent_handle::set_piece_deadline(piece_index_t index, int deadline + , deadline_flags_t const flags) const + { +#ifndef TORRENT_DISABLE_STREAMING + async_call(&torrent::set_piece_deadline, index, deadline, flags); +#else + TORRENT_UNUSED(deadline); + if (flags & alert_when_available) + async_call(&torrent::read_piece, index); +#endif + } + + void torrent_handle::reset_piece_deadline(piece_index_t index) const + { +#ifndef TORRENT_DISABLE_STREAMING + async_call(&torrent::reset_piece_deadline, index); +#else + TORRENT_UNUSED(index); +#endif + } + + void torrent_handle::clear_piece_deadlines() const + { +#ifndef TORRENT_DISABLE_STREAMING + async_call(&torrent::clear_time_critical); +#endif + } + + std::shared_ptr torrent_handle::native_handle() const + { + return m_torrent.lock(); + } + + std::size_t hash_value(torrent_handle const& th) + { + // using the locked shared_ptr value as hash doesn't work + // for expired weak_ptrs. So, we're left with a hack + return std::size_t(*reinterpret_cast(&th.m_torrent)); + } + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + static_assert(std::is_nothrow_move_assignable::value + , "should be nothrow move assignable"); + static_assert(std::is_nothrow_default_constructible::value + , "should be nothrow default constructible"); +} diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp new file mode 100644 index 0000000..56683d6 --- /dev/null +++ b/src/torrent_info.cpp @@ -0,0 +1,1702 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/string_util.hpp" // is_space, is_i2p_url +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/utf8.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/escape_string.hpp" // maybe_url_encode +#include "libtorrent/aux_/merkle.hpp" // for merkle_* +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/numeric_cast.hpp" + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/lazy_entry.hpp" +#endif + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if TORRENT_ABI_VERSION == 1 && defined TORRENT_WINDOWS +#include "libtorrent/aux_/escape_string.hpp" +#endif + +namespace libtorrent { + + TORRENT_EXPORT from_span_t from_span; + + namespace { + + // this is an arbitrary limit to avoid malicious torrents causing + // unreasaonably large allocations. + // TODO: remove this limit and the overloads that imply it, in favour of + // using load_torrent_limits + constexpr int default_piece_limit = 0x200000; + + bool valid_path_character(std::int32_t const c) + { +#ifdef TORRENT_WINDOWS + static const char invalid_chars[] = "?<>\"|\b*:"; +#else + static const char invalid_chars[] = ""; +#endif + if (c < 32) return false; + if (c > 127) return true; + return std::strchr(invalid_chars, static_cast(c)) == nullptr; + } + + bool filter_path_character(std::int32_t const c) + { + // these unicode characters change the writing direction of the + // string and can be used for attacks: + // https://security.stackexchange.com/questions/158802/how-can-this-executable-have-an-avi-extension + static const std::array bad_cp = {{0x202a, 0x202b, 0x202c, 0x202d, 0x202e, 0x200e, 0x200f}}; + if (std::find(bad_cp.begin(), bad_cp.end(), c) != bad_cp.end()) return true; + + static const char invalid_chars[] = "/\\"; + if (c > 127) return false; + return std::strchr(invalid_chars, static_cast(c)) != nullptr; + } + + } // anonymous namespace + + // fixes invalid UTF-8 sequences + bool verify_encoding(std::string& target) + { + if (target.empty()) return true; + + std::string tmp_path; + tmp_path.reserve(target.size()+5); + bool valid_encoding = true; + + string_view ptr = target; + while (!ptr.empty()) + { + std::int32_t codepoint; + int len; + + // decode a single utf-8 character + std::tie(codepoint, len) = parse_utf8_codepoint(ptr); + + // this was the last character, and nothing was + // written to the destination buffer (i.e. the source character was + // truncated) + if (codepoint == -1) + { + codepoint = '_'; + valid_encoding = false; + } + + ptr = ptr.substr(std::size_t(len)); + + // encode codepoint into utf-8 + append_utf8_codepoint(tmp_path, codepoint); + } + + // the encoding was not valid utf-8 + // save the original encoding and replace the + // commonly used path with the correctly + // encoded string + if (!valid_encoding) target = tmp_path; + return valid_encoding; + } + + void sanitize_append_path_element(std::string& path, string_view element) + { + if (element.size() == 1 && element[0] == '.') return; + +#ifdef TORRENT_WINDOWS +#define TORRENT_SEPARATOR '\\' +#else +#define TORRENT_SEPARATOR '/' +#endif + path.reserve(path.size() + element.size() + 2); + int added_separator = 0; + if (!path.empty()) + { + path += TORRENT_SEPARATOR; + added_separator = 1; + } + + if (element.empty()) + { + path += "_"; + return; + } + +#if !TORRENT_USE_UNC_PATHS && defined TORRENT_WINDOWS +#pragma message ("building for windows without UNC paths is deprecated") + + // if we're not using UNC paths on windows, there + // are certain filenames we're not allowed to use + static const char const* reserved_names[] = + { + "con", "prn", "aux", "clock$", "nul", + "com0", "com1", "com2", "com3", "com4", + "com5", "com6", "com7", "com8", "com9", + "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", + "lpt5", "lpt6", "lpt7", "lpt8", "lpt9" + }; + int num_names = sizeof(reserved_names)/sizeof(reserved_names[0]); + + // this is not very efficient, but it only affects some specific + // windows builds for now anyway (not even the default windows build) + std::string pe = element.to_string(); + char const* file_end = strrchr(pe.c_str(), '.'); + std::string name = file_end + ? std::string(pe.data(), file_end) + : pe; + std::transform(name.begin(), name.end(), name.begin(), &to_lower); + char const** str = std::find(reserved_names, reserved_names + num_names, name); + if (str != reserved_names + num_names) + { + pe = "_" + pe; + element = string_view(); + } +#endif + // this counts the number of unicode characters + // we've added (which is different from the number + // of bytes) + int unicode_chars = 0; + + int added = 0; + // the number of dots we've added + char num_dots = 0; + bool found_extension = false; + + int seq_len = 0; + for (std::size_t i = 0; i < element.size(); i += std::size_t(seq_len)) + { + std::int32_t code_point; + std::tie(code_point, seq_len) = parse_utf8_codepoint(element.substr(i)); + + if (code_point >= 0 && filter_path_character(code_point)) + { + continue; + } + + if (code_point < 0 || !valid_path_character(code_point)) + { + // invalid utf8 sequence, replace with "_" + path += '_'; + ++added; + ++unicode_chars; + continue; + } + + // validation passed, add it to the output string + for (std::size_t k = i; k < i + std::size_t(seq_len); ++k) + { + TORRENT_ASSERT(element[k] != 0); + path.push_back(element[k]); + } + + if (code_point == '.') ++num_dots; + + added += seq_len; + ++unicode_chars; + + // any given path element should not + // be more than 255 characters + // if we exceed 240, pick up any potential + // file extension and add that too +#ifdef TORRENT_WINDOWS + if (unicode_chars >= 240 && !found_extension) +#else + if (added >= 240 && !found_extension) +#endif + { + int dot = -1; + for (int j = int(element.size()) - 1; + j > std::max(int(element.size()) - 10, int(i)); --j) + { + if (element[aux::numeric_cast(j)] != '.') continue; + dot = j; + break; + } + // there is no extension + if (dot == -1) break; + found_extension = true; + TORRENT_ASSERT(dot > 0); + i = std::size_t(dot - seq_len); + } + } + + if (added == num_dots && added <= 2) + { + // revert everything + path.erase(path.end() - added - added_separator, path.end()); + return; + } + +#ifdef TORRENT_WINDOWS + // remove trailing spaces and dots. These aren't allowed in filenames on windows + for (int i = int(path.size()) - 1; i >= 0; --i) + { + if (path[i] != ' ' && path[i] != '.') break; + path.resize(i); + --added; + TORRENT_ASSERT(added >= 0); + } + + if (added == 0 && added_separator) + { + // remove the separator added at the beginning + path.erase(path.end() - 1); + return; + } +#endif + + if (path.empty()) path = "_"; + } + +namespace { + + file_flags_t get_file_attributes(bdecode_node const& dict) + { + file_flags_t file_flags = {}; + bdecode_node const attr = dict.dict_find_string("attr"); + if (attr) + { + for (char const c : attr.string_value()) + { + switch (c) + { + case 'l': file_flags |= file_storage::flag_symlink; break; + case 'x': file_flags |= file_storage::flag_executable; break; + case 'h': file_flags |= file_storage::flag_hidden; break; + case 'p': file_flags |= file_storage::flag_pad_file; break; + } + } + } + return file_flags; + } + + // iterates an array of strings and returns the sum of the lengths of all + // strings + one additional character per entry (to account for the presumed + // forward- or backslash to separate directory entries) + int path_length(bdecode_node const& p, error_code& ec) + { + int ret = 0; + int const len = p.list_size(); + for (int i = 0; i < len; ++i) + { + bdecode_node const e = p.list_at(i); + if (e.type() != bdecode_node::string_t) + { + ec = errors::torrent_invalid_name; + return -1; + } + ret += e.string_length(); + } + return ret + len; + } + + // 'top_level' is extracting the file for a single-file torrent. The + // distinction is that the filename is found in "name" rather than + // "path" + // root_dir is the name of the torrent, unless this is a single file + // torrent, in which case it's empty. + bool extract_single_file(bdecode_node const& dict, file_storage& files + , std::string const& root_dir, std::ptrdiff_t const info_ptr_diff, bool top_level + , int& pad_file_cnt, error_code& ec) + { + if (dict.type() != bdecode_node::dict_t) return false; + + file_flags_t file_flags = get_file_attributes(dict); + + // symlinks have an implied "size" of zero. i.e. they use up 0 bytes of + // the torrent payload space + std::int64_t const file_size = (file_flags & file_storage::flag_symlink) + ? 0 + : dict.dict_find_int_value("length", -1); + if (file_size < 0 ) + { + ec = errors::torrent_invalid_length; + return false; + } + + std::time_t const mtime = std::time_t(dict.dict_find_int_value("mtime", 0)); + + std::string path = root_dir; + string_view filename; + + if (top_level) + { + // prefer the name.utf-8 because if it exists, it is more likely to be + // correctly encoded + bdecode_node p = dict.dict_find_string("name.utf-8"); + if (!p) p = dict.dict_find_string("name"); + if (!p || p.string_length() == 0) + { + ec = errors::torrent_missing_name; + return false; + } + + filename = { p.string_ptr() + info_ptr_diff + , static_cast(p.string_length())}; + + while (!filename.empty() && filename.front() == TORRENT_SEPARATOR) + filename.remove_prefix(1); + + sanitize_append_path_element(path, p.string_value()); + if (path.empty()) + { + ec = errors::torrent_missing_name; + return false; + } + } + else + { + bdecode_node p = dict.dict_find_list("path.utf-8"); + if (!p) p = dict.dict_find_list("path"); + + if (p && p.list_size() > 0) + { + std::size_t const preallocate = path.size() + std::size_t(path_length(p, ec)); + std::size_t const orig_path_len = path.size(); + if (ec) return false; + path.reserve(preallocate); + + for (int i = 0, end(p.list_size()); i < end; ++i) + { + bdecode_node const e = p.list_at(i); + if (i == end - 1) + { + filename = {e.string_ptr() + info_ptr_diff + , static_cast(e.string_length()) }; + while (!filename.empty() && filename.front() == TORRENT_SEPARATOR) + filename.remove_prefix(1); + } + sanitize_append_path_element(path, e.string_value()); + } + + // if all path elements were sanitized away, we need to use another + // name instead + if (path.size() == orig_path_len) + { + path += TORRENT_SEPARATOR; + path += "_"; + } + } + else if (file_flags & file_storage::flag_pad_file) + { + // pad files don't need a path element, we'll just store them + // under the .pad directory + char cnt[11]; + std::snprintf(cnt, sizeof(cnt), "%d", pad_file_cnt); + path = combine_path(".pad", cnt); + ++pad_file_cnt; + } + else + { + ec = errors::torrent_missing_name; + return false; + } + } + + // bitcomet pad file + if (path.find("_____padding_file_") != std::string::npos) + file_flags |= file_storage::flag_pad_file; + + bdecode_node const fh = dict.dict_find_string("sha1"); + char const* filehash = nullptr; + if (fh && fh.string_length() == 20) + filehash = fh.string_ptr() + info_ptr_diff; + + std::string symlink_path; + if (file_flags & file_storage::flag_symlink) + { + if (bdecode_node const s_p = dict.dict_find_list("symlink path")) + { + std::size_t const preallocate = std::size_t(path_length(s_p, ec)); + if (ec) return false; + symlink_path.reserve(preallocate); + for (int i = 0, end(s_p.list_size()); i < end; ++i) + { + auto pe = s_p.list_at(i).string_value(); + sanitize_append_path_element(symlink_path, pe); + } + } + else + { + // technically this is an invalid torrent. "symlink path" must exist + file_flags &= ~file_storage::flag_symlink; + } + // symlink targets are validated later, as it may point to a file or + // directory we haven't parsed yet + } + + if (filename.size() > path.length() + || path.substr(path.size() - filename.size()) != filename) + { + // if the filename was sanitized and differ, clear it to just use path + filename = {}; + } + + files.add_file_borrow(filename, path, file_size, file_flags, filehash + , mtime, symlink_path); + return true; + } + + // root_dir is the name of the torrent, unless this is a single file + // torrent, in which case it's empty. + bool extract_files(bdecode_node const& list, file_storage& target + , std::string const& root_dir, std::ptrdiff_t info_ptr_diff, error_code& ec) + { + if (list.type() != bdecode_node::list_t) + { + ec = errors::torrent_file_parse_failed; + return false; + } + target.reserve(list.list_size()); + + // this is the counter used to name pad files + int pad_file_cnt = 0; + for (int i = 0, end(list.list_size()); i < end; ++i) + { + if (!extract_single_file(list.list_at(i), target, root_dir + , info_ptr_diff, false, pad_file_cnt, ec)) + return false; + } + // this rewrites invalid symlinks to point to themselves + target.sanitize_symlinks(); + return true; + } + + int load_file(std::string const& filename, std::vector& v + , error_code& ec, int const max_buffer_size = 80000000) + { + ec.clear(); + file f; + if (!f.open(filename, open_mode::read_only, ec)) return -1; + std::int64_t const s = f.get_size(ec); + if (ec) return -1; + if (s > max_buffer_size) + { + ec = errors::metadata_too_large; + return -1; + } + v.resize(std::size_t(s)); + if (s == 0) return 0; + std::int64_t const read = f.readv(0, {v}, ec); + if (read != s) return -3; + if (ec) return -3; + return 0; + } + +} // anonymous namespace + + web_seed_entry::web_seed_entry(std::string const& url_, type_t type_ + , std::string const& auth_ + , headers_t const& extra_headers_) + : url(url_) + , auth(auth_) + , extra_headers(extra_headers_) + , type(std::uint8_t(type_)) + { + } + + torrent_info::torrent_info(torrent_info const& t) + : m_files(t.m_files) + , m_orig_files(t.m_orig_files) + , m_urls(t.m_urls) + , m_web_seeds(t.m_web_seeds) + , m_nodes(t.m_nodes) + , m_merkle_tree(t.m_merkle_tree) + , m_piece_hashes(t.m_piece_hashes) + , m_comment(t.m_comment) + , m_created_by(t.m_created_by) + , m_creation_date(t.m_creation_date) + , m_info_hash(t.m_info_hash) + , m_info_section_size(t.m_info_section_size) + , m_merkle_first_leaf(t.m_merkle_first_leaf) + , m_flags(t.m_flags) + { +#if TORRENT_USE_INVARIANT_CHECKS + t.check_invariant(); +#endif + if (m_info_section_size == 0) return; + TORRENT_ASSERT(m_piece_hashes); + + m_info_section.reset(new char[aux::numeric_cast(m_info_section_size)]); + std::memcpy(m_info_section.get(), t.m_info_section.get(), aux::numeric_cast(m_info_section_size)); + + std::ptrdiff_t const offset = m_info_section.get() - t.m_info_section.get(); + + m_files.apply_pointer_offset(offset); + if (m_orig_files) + const_cast(*m_orig_files).apply_pointer_offset(offset); + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + for (auto& c : m_collections) + c.first += offset; + + for (auto& st : m_similar_torrents) + st += offset; +#endif + + if (m_info_dict) + { + // make this decoded object point to our copy of the info section + // buffer + m_info_dict.switch_underlying_buffer(m_info_section.get()); + } + + m_piece_hashes += offset; + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + } + + void torrent_info::resolve_duplicate_filenames() + { + INVARIANT_CHECK; + + std::unordered_set files; + + std::string const empty_str; + + // insert all directories first, to make sure no files + // are allowed to collied with them + m_files.all_path_hashes(files); + for (auto const i : m_files.file_range()) + { + // as long as this file already exists + // increase the counter + std::uint32_t const h = m_files.file_path_hash(i, empty_str); + if (!files.insert(h).second) + { + // This filename appears to already exist! + // If this happens, just start over and do it the slow way, + // comparing full file names and come up with new names + resolve_duplicate_filenames_slow(); + return; + } + } + } + +namespace { + + template + void process_string_lowercase(CRC& crc, string_view str) + { + for (char const c : str) + crc.process_byte(to_lower(c) & 0xff); + } + + struct name_entry + { + file_index_t idx; + int length; + }; +} + + void torrent_info::resolve_duplicate_filenames_slow() + { + INVARIANT_CHECK; + + // maps filename hash to file index + // or, if the file_index is negative, maps into the paths vector + std::unordered_multimap files; + + std::vector const& paths = m_files.paths(); + files.reserve(paths.size() + aux::numeric_cast(m_files.num_files())); + + // insert all directories first, to make sure no files + // are allowed to collied with them + { + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + if (!m_files.name().empty()) + { + process_string_lowercase(crc, m_files.name()); + } + file_index_t path_index{-1}; + for (auto const& path : paths) + { + auto local_crc = crc; + if (!path.empty()) local_crc.process_byte(TORRENT_SEPARATOR); + int count = 0; + for (char const c : path) + { + if (c == TORRENT_SEPARATOR) + files.insert({local_crc.checksum(), {path_index, count}}); + local_crc.process_byte(to_lower(c) & 0xff); + ++count; + } + files.insert({local_crc.checksum(), {path_index, int(path.size())}}); + --path_index; + } + } + + // keep track of the total number of name collisions. If there are too + // many, it's probably a malicious torrent and we should just fail + int num_collisions = 0; + for (auto const i : m_files.file_range()) + { + // as long as this file already exists + // increase the counter + std::uint32_t const hash = m_files.file_path_hash(i, ""); + auto range = files.equal_range(hash); + auto const match = std::find_if(range.first, range.second, [&](std::pair const& o) + { + std::string const other_name = o.second.idx < file_index_t{} + ? combine_path(m_files.name(), paths[std::size_t(-static_cast(o.second.idx)-1)].substr(0, std::size_t(o.second.length))) + : m_files.file_path(o.second.idx); + return string_equal_no_case(other_name, m_files.file_path(i)); + }); + + if (match == range.second) + { + files.insert({hash, {i, 0}}); + continue; + } + + // pad files are allowed to collide with each-other, as long as they have + // the same size. + file_index_t const other_idx = match->second.idx; + if (other_idx >= file_index_t{} + && (m_files.file_flags(i) & file_storage::flag_pad_file) + && (m_files.file_flags(other_idx) & file_storage::flag_pad_file) + && m_files.file_size(i) == m_files.file_size(other_idx)) + continue; + + std::string filename = m_files.file_path(i); + std::string base = remove_extension(filename); + std::string ext = extension(filename); + int cnt = 0; + for (;;) + { + ++cnt; + char new_ext[50]; + std::snprintf(new_ext, sizeof(new_ext), ".%d%s", cnt, ext.c_str()); + filename = base + new_ext; + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + process_string_lowercase(crc, filename); + std::uint32_t const new_hash = crc.checksum(); + if (files.find(new_hash) == files.end()) + { + files.insert({new_hash, {i, 0}}); + break; + } + ++num_collisions; + if (num_collisions > 100) + { + // TODO: this should be considered a failure, and the .torrent file + // rejected + } + } + + copy_on_write(); + m_files.rename_file(i, filename); + } + } + + void torrent_info::remap_files(file_storage const& f) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(is_loaded()); + // the new specified file storage must have the exact + // same size as the current file storage + TORRENT_ASSERT(m_files.total_size() == f.total_size()); + + if (m_files.total_size() != f.total_size()) return; + copy_on_write(); + m_files = f; + m_files.set_num_pieces(m_orig_files->num_pieces()); + m_files.set_piece_length(m_orig_files->piece_length()); + } + +#if TORRENT_ABI_VERSION == 1 + torrent_info::torrent_info(lazy_entry const& torrent_file, error_code& ec) + { + std::pair buf = torrent_file.data_section(); + bdecode_node e; + if (bdecode(buf.first, buf.first + buf.second, e, ec) != 0) + return; + parse_torrent_file(e, ec); + } + + torrent_info::torrent_info(lazy_entry const& torrent_file) + { + std::pair buf = torrent_file.data_section(); + bdecode_node e; + error_code ec; + if (bdecode(buf.first, buf.first + buf.second, e, ec) != 0) + { + aux::throw_ex(ec); + } +#ifndef BOOST_NO_EXCEPTIONS + if (!parse_torrent_file(e, ec)) + aux::throw_ex(ec); +#else + parse_torrent_file(e, ec); +#endif + } + + // standard constructor that parses a torrent file + torrent_info::torrent_info(entry const& torrent_file) + { + std::vector tmp; + std::back_insert_iterator> out(tmp); + bencode(out, torrent_file); + + bdecode_node e; + error_code ec; + if (tmp.empty() || bdecode(&tmp[0], &tmp[0] + tmp.size(), e, ec) != 0) + { +#ifndef BOOST_NO_EXCEPTIONS + aux::throw_ex(ec); +#else + return; +#endif + } +#ifndef BOOST_NO_EXCEPTIONS + if (!parse_torrent_file(e, ec)) + aux::throw_ex(ec); +#else + parse_torrent_file(e, ec); +#endif + INVARIANT_CHECK; + } +#endif // TORRENT_ABI_VERSION + +#ifndef BOOST_NO_EXCEPTIONS + torrent_info::torrent_info(bdecode_node const& torrent_file) + { + error_code ec; + if (!parse_torrent_file(torrent_file, ec)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(span buffer, from_span_t) + { + error_code ec; + bdecode_node e = bdecode(buffer, ec); + if (ec) aux::throw_ex(ec); + + if (!parse_torrent_file(e, ec)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(std::string const& filename) + { + std::vector buf; + error_code ec; + int ret = load_file(filename, buf, ec); + if (ret < 0) aux::throw_ex(ec); + + bdecode_node e = bdecode(buf, ec); + if (ec) aux::throw_ex(ec); + + if (!parse_torrent_file(e, ec)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(bdecode_node const& torrent_file + , load_torrent_limits const& cfg) + { + error_code ec; + if (!parse_torrent_file(torrent_file, ec, cfg.max_pieces)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(span buffer + , load_torrent_limits const& cfg, from_span_t) + { + error_code ec; + bdecode_node e = bdecode(buffer, ec, nullptr + , cfg.max_decode_depth, cfg.max_decode_tokens); + if (ec) aux::throw_ex(ec); + + if (!parse_torrent_file(e, ec, cfg.max_pieces)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(std::string const& filename + , load_torrent_limits const& cfg) + { + std::vector buf; + error_code ec; + int ret = load_file(filename, buf, ec, cfg.max_buffer_size); + if (ret < 0) aux::throw_ex(ec); + + bdecode_node e = bdecode(buf, ec, nullptr, cfg.max_decode_depth + , cfg.max_decode_tokens); + if (ec) aux::throw_ex(ec); + + if (!parse_torrent_file(e, ec, cfg.max_pieces)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + +#if TORRENT_ABI_VERSION == 1 +#if defined TORRENT_WINDOWS + torrent_info::torrent_info(std::wstring const& filename) + { + std::vector buf; + error_code ec; + int ret = load_file(convert_from_wstring(filename), buf, ec); + if (ret < 0) aux::throw_ex(ec); + + bdecode_node e = bdecode(buf, ec); + if (ec) aux::throw_ex(ec); + + if (!parse_torrent_file(e, ec)) + aux::throw_ex(ec); + + INVARIANT_CHECK; + } + + void torrent_info::rename_file(file_index_t index, std::wstring const& new_filename) + { + TORRENT_ASSERT(is_loaded()); + copy_on_write(); + m_files.rename_file_deprecated(index, new_filename); + } +#endif // TORRENT_WINDOWS +#endif // TORRENT_ABI_VERSION +#endif + + torrent_info::torrent_info(bdecode_node const& torrent_file + , error_code& ec) + { + parse_torrent_file(torrent_file, ec); + INVARIANT_CHECK; + } + + torrent_info::torrent_info(span buffer + , error_code& ec, from_span_t) + { + bdecode_node e = bdecode(buffer, ec); + if (ec) return; + parse_torrent_file(e, ec); + + INVARIANT_CHECK; + } + + torrent_info::torrent_info(std::string const& filename, error_code& ec) + { + std::vector buf; + int ret = load_file(filename, buf, ec); + if (ret < 0) return; + + bdecode_node e = bdecode(buf, ec); + if (ec) return; + parse_torrent_file(e, ec); + + INVARIANT_CHECK; + } + +#if TORRENT_ABI_VERSION == 1 +#if defined TORRENT_WINDOWS + torrent_info::torrent_info(std::wstring const& filename + , error_code& ec) + { + std::vector buf; + int ret = load_file(convert_from_wstring(filename), buf, ec); + if (ret < 0) return; + + bdecode_node e = bdecode(buf, ec); + if (ec) return; + parse_torrent_file(e, ec); + + INVARIANT_CHECK; + } +#endif // TORRENT_WINDOWS +#endif // TORRENT_ABI_VERSION + + // constructor used for creating new torrents + // will not contain any hashes, comments, creation date + // just the necessary to use it with piece manager + // used for torrents with no metadata + torrent_info::torrent_info(sha1_hash const& info_hash) + : m_info_hash(info_hash) + {} + + torrent_info::~torrent_info() = default; + + sha1_hash torrent_info::hash_for_piece(piece_index_t const index) const + { return sha1_hash(hash_for_piece_ptr(index)); } + + void torrent_info::copy_on_write() + { + TORRENT_ASSERT(is_loaded()); + INVARIANT_CHECK; + + if (m_orig_files) return; + m_orig_files.reset(new file_storage(m_files)); + } + + void torrent_info::swap(torrent_info& ti) + { + INVARIANT_CHECK; + + using std::swap; + m_urls.swap(ti.m_urls); + m_web_seeds.swap(ti.m_web_seeds); + m_files.swap(ti.m_files); + m_orig_files.swap(ti.m_orig_files); + m_nodes.swap(ti.m_nodes); + m_similar_torrents.swap(ti.m_similar_torrents); + m_owned_similar_torrents.swap(ti.m_owned_similar_torrents); + m_collections.swap(ti.m_collections); + m_owned_collections.swap(ti.m_owned_collections); + swap(m_info_hash, ti.m_info_hash); + swap(m_creation_date, ti.m_creation_date); + m_comment.swap(ti.m_comment); + m_created_by.swap(ti.m_created_by); + swap(m_info_section, ti.m_info_section); + swap(m_piece_hashes, ti.m_piece_hashes); + m_info_dict.swap(ti.m_info_dict); + swap(m_merkle_tree, ti.m_merkle_tree); + swap(m_info_section_size, ti.m_info_section_size); + swap(m_merkle_first_leaf, ti.m_merkle_first_leaf); + swap(m_flags, ti.m_flags); + } + + string_view torrent_info::ssl_cert() const + { + if ((m_flags & ssl_torrent) == 0) return ""; + + // this is parsed lazily + if (!m_info_dict) + { + error_code ec; + bdecode(m_info_section.get(), m_info_section.get() + + m_info_section_size, m_info_dict, ec); + TORRENT_ASSERT(!ec); + if (ec) return ""; + } + TORRENT_ASSERT(m_info_dict.type() == bdecode_node::dict_t); + if (m_info_dict.type() != bdecode_node::dict_t) return ""; + return m_info_dict.dict_find_string_value("ssl-cert"); + } + + bool torrent_info::parse_info_section(bdecode_node const& e, error_code& ec) + { + return parse_info_section(e, ec, default_piece_limit); + } + + bool torrent_info::parse_info_section(bdecode_node const& info + , error_code& ec, int const max_pieces) + { + if (info.type() != bdecode_node::dict_t) + { + ec = errors::torrent_info_no_dict; + return false; + } + + // hash the info-field to calculate info-hash + auto section = info.data_section(); + m_info_hash = hasher(section).final(); + if (info.data_section().size() >= std::numeric_limits::max()) + { + ec = errors::metadata_too_large; + return false; + } + + // copy the info section + m_info_section_size = int(section.size()); + m_info_section.reset(new char[aux::numeric_cast(m_info_section_size)]); + std::memcpy(m_info_section.get(), section.data(), aux::numeric_cast(m_info_section_size)); + TORRENT_ASSERT(section[0] == 'd'); + TORRENT_ASSERT(section[m_info_section_size - 1] == 'e'); + + // when translating a pointer that points into the 'info' tree's + // backing buffer, into a pointer to our copy of the info section, + // this is the pointer offset to use. + std::ptrdiff_t const info_ptr_diff = m_info_section.get() - section.data(); + + // extract piece length + std::int64_t piece_length = info.dict_find_int_value("piece length", -1); + if (piece_length <= 0 || piece_length > std::numeric_limits::max()) + { + ec = errors::torrent_missing_piece_length; + return false; + } + file_storage files; + files.set_piece_length(static_cast(piece_length)); + + // extract file name (or the directory name if it's a multi file libtorrent) + bdecode_node name_ent = info.dict_find_string("name.utf-8"); + if (!name_ent) name_ent = info.dict_find_string("name"); + if (!name_ent) + { + ec = errors::torrent_missing_name; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + std::string name; + sanitize_append_path_element(name, name_ent.string_value()); + if (name.empty()) name = aux::to_hex(m_info_hash); + + // extract file list + bdecode_node const files_node = info.dict_find_list("files"); + if (!files_node) + { + // if there's no list of files, there has to be a length + // field. + // this is the counter used to name pad files + int pad_file_cnt = 0; + if (!extract_single_file(info, files, "", info_ptr_diff, true, pad_file_cnt, ec)) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + files.sanitize_symlinks(); + m_flags &= ~multifile; + } + else + { + if (!extract_files(files_node, files, name, info_ptr_diff, ec)) + { + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + m_flags |= multifile; + } + if (files.num_files() == 0) + { + ec = errors::no_files_in_torrent; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + if (files.name().empty()) + { + ec = errors::torrent_missing_name; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + // extract SHA-1 hashes for all pieces + // we want this division to round upwards, that's why we have the + // extra addition + + if (files.total_size() >= + static_cast(std::numeric_limits::max() + - files.piece_length()) * files.piece_length()) + { + ec = errors::too_many_pieces_in_torrent; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + files.set_num_pieces(int((files.total_size() + files.piece_length() - 1) + / files.piece_length())); + + bdecode_node const pieces = info.dict_find_string("pieces"); + bdecode_node const root_hash = info.dict_find_string("root hash"); + if (!pieces && !root_hash) + { + ec = errors::torrent_missing_pieces; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + // we expect the piece hashes to be < 2 GB in size + if (files.num_pieces() >= std::numeric_limits::max() / 20 + || files.num_pieces() > max_pieces) + { + ec = errors::too_many_pieces_in_torrent; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + if (pieces) + { + if (pieces.string_length() != files.num_pieces() * 20) + { + ec = errors::torrent_invalid_hashes; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + + m_piece_hashes = pieces.string_ptr() + info_ptr_diff; + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + } + else + { + TORRENT_ASSERT(root_hash); + if (root_hash.string_length() != 20) + { + ec = errors::torrent_invalid_hashes; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + if (files.num_pieces() <= 0) + { + ec = errors::no_files_in_torrent; + // mark the torrent as invalid + m_files.set_piece_length(0); + return false; + } + int const num_leafs = merkle_num_leafs(files.num_pieces()); + int const num_nodes = merkle_num_nodes(num_leafs); + m_merkle_first_leaf = num_nodes - num_leafs; + m_merkle_tree.resize(num_nodes); + m_merkle_tree[0].assign(root_hash.string_ptr()); + } + + m_flags |= (info.dict_find_int_value("private", 0) != 0) + ? private_torrent : 0; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + bdecode_node const similar = info.dict_find_list("similar"); + if (similar) + { + for (int i = 0; i < similar.list_size(); ++i) + { + if (similar.list_at(i).type() != bdecode_node::string_t) + continue; + + if (similar.list_at(i).string_length() != 20) + continue; + + m_similar_torrents.push_back(similar.list_at(i).string_ptr() + + info_ptr_diff); + } + } + + bdecode_node const collections = info.dict_find_list("collections"); + if (collections) + { + for (int i = 0; i < collections.list_size(); ++i) + { + bdecode_node const str = collections.list_at(i); + + if (str.type() != bdecode_node::string_t) continue; + + m_collections.emplace_back(str.string_ptr() + + info_ptr_diff, str.string_length()); + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + if (info.dict_find_string("ssl-cert")) + m_flags |= ssl_torrent; + + // now, commit the files structure we just parsed out + // into the torrent_info object. + m_files.swap(files); + return true; + } + + bdecode_node torrent_info::info(char const* key) const + { + if (m_info_dict.type() == bdecode_node::none_t) + { + error_code ec; + bdecode(m_info_section.get(), m_info_section.get() + + m_info_section_size, m_info_dict, ec); + if (ec) return bdecode_node(); + } + return m_info_dict.dict_find(key); + } + + + bool torrent_info::add_merkle_nodes(std::map const& subtree + , piece_index_t const piece) + { + INVARIANT_CHECK; + + int n = m_merkle_first_leaf + static_cast(piece); + auto const it = subtree.find(n); + if (it == subtree.end()) return false; + sha1_hash h = it->second; + + // if the verification passes, these are the + // nodes to add to our tree + std::map to_add; + + while (n > 0) + { + int const sibling = merkle_get_sibling(n); + int const parent = merkle_get_parent(n); + auto const sibling_hash = subtree.find(sibling); + if (sibling_hash == subtree.end()) + return false; + to_add[n] = h; + to_add[sibling] = sibling_hash->second; + hasher hs; + if (sibling < n) + { + hs.update(sibling_hash->second); + hs.update(h); + } + else + { + hs.update(h); + hs.update(sibling_hash->second); + } + h = hs.final(); + n = parent; + } + if (h != m_merkle_tree[0]) return false; + + // the nodes and piece hash matched the root-hash + // insert them into our tree + + for (auto const& i : to_add) + { + m_merkle_tree[i.first] = i.second; + } + return true; + } + + void torrent_info::internal_set_creator(string_view const c) + { m_created_by = std::string(c); } + + void torrent_info::internal_set_creation_date(std::time_t const t) + { m_creation_date = t; } + + void torrent_info::internal_set_comment(string_view const s) + { m_comment = std::string(s); } + + // builds a list of nodes that are required to verify + // the given piece + std::map + torrent_info::build_merkle_list(piece_index_t const piece) const + { + INVARIANT_CHECK; + + std::map ret; + int n = m_merkle_first_leaf + static_cast(piece); + ret[n] = m_merkle_tree[n]; + ret[0] = m_merkle_tree[0]; + while (n > 0) + { + int sibling = merkle_get_sibling(n); + int parent = merkle_get_parent(n); + ret[sibling] = m_merkle_tree[sibling]; + // we cannot build the tree path if one + // of the nodes in the tree is missing + TORRENT_ASSERT(!m_merkle_tree[sibling].is_all_zeros()); + n = parent; + } + return ret; + } + + bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file + , error_code& ec) + { + return parse_torrent_file(torrent_file, ec, default_piece_limit); + } + + bool torrent_info::parse_torrent_file(bdecode_node const& torrent_file + , error_code& ec, int const piece_limit) + { + if (torrent_file.type() != bdecode_node::dict_t) + { + ec = errors::torrent_is_no_dict; + return false; + } + + bdecode_node const info = torrent_file.dict_find_dict("info"); + if (!info) + { + bdecode_node const uri = torrent_file.dict_find_string("magnet-uri"); + if (uri) + { + auto const p = parse_magnet_uri(uri.string_value(), ec); + if (ec) return false; + + m_info_hash = p.info_hash; + m_urls.reserve(m_urls.size() + p.trackers.size()); + for (auto const& url : p.trackers) + m_urls.emplace_back(url); + + return true; + } + + ec = errors::torrent_missing_info; + return false; + } + if (!parse_info_section(info, ec, piece_limit)) return false; + resolve_duplicate_filenames(); + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + bdecode_node const similar = torrent_file.dict_find_list("similar"); + if (similar) + { + for (int i = 0; i < similar.list_size(); ++i) + { + if (similar.list_at(i).type() != bdecode_node::string_t) + continue; + + if (similar.list_at(i).string_length() != 20) + continue; + + m_owned_similar_torrents.emplace_back( + similar.list_at(i).string_ptr()); + } + } + + bdecode_node const collections = torrent_file.dict_find_list("collections"); + if (collections) + { + for (int i = 0; i < collections.list_size(); ++i) + { + bdecode_node const str = collections.list_at(i); + + if (str.type() != bdecode_node::string_t) continue; + + m_owned_collections.emplace_back(str.string_ptr() + , aux::numeric_cast(str.string_length())); + } + } +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + // extract the url of the tracker + bdecode_node const announce_node = torrent_file.dict_find_list("announce-list"); + if (announce_node) + { + m_urls.reserve(announce_node.list_size()); + for (int j = 0, end(announce_node.list_size()); j < end; ++j) + { + bdecode_node const tier = announce_node.list_at(j); + if (tier.type() != bdecode_node::list_t) continue; + for (int k = 0, end2(tier.list_size()); k < end2; ++k) + { + announce_entry e(tier.list_string_value_at(k).to_string()); + e.trim(); + if (e.url.empty()) continue; + e.tier = std::uint8_t(j); + e.fail_limit = 0; + e.source = announce_entry::source_torrent; +#if TORRENT_USE_I2P + if (is_i2p_url(e.url)) m_flags |= i2p; +#endif + m_urls.push_back(e); + } + } + + if (!m_urls.empty()) + { + // shuffle each tier + aux::random_shuffle(m_urls); + std::stable_sort(m_urls.begin(), m_urls.end() + , [](announce_entry const& lhs, announce_entry const& rhs) + { return lhs.tier < rhs.tier; }); + } + } + + if (m_urls.empty()) + { + announce_entry e(torrent_file.dict_find_string_value("announce")); + e.fail_limit = 0; + e.source = announce_entry::source_torrent; + e.trim(); +#if TORRENT_USE_I2P + if (is_i2p_url(e.url)) m_flags |= i2p; +#endif + if (!e.url.empty()) m_urls.push_back(e); + } + + bdecode_node const nodes = torrent_file.dict_find_list("nodes"); + if (nodes) + { + for (int i = 0, end(nodes.list_size()); i < end; ++i) + { + bdecode_node const n = nodes.list_at(i); + if (n.type() != bdecode_node::list_t + || n.list_size() < 2 + || n.list_at(0).type() != bdecode_node::string_t + || n.list_at(1).type() != bdecode_node::int_t) + continue; + m_nodes.emplace_back( + n.list_at(0).string_value().to_string() + , int(n.list_at(1).int_value())); + } + } + + // extract creation date + std::int64_t const cd = torrent_file.dict_find_int_value("creation date", -1); + if (cd >= 0) + { + m_creation_date = std::time_t(cd); + } + + // if there are any url-seeds, extract them + bdecode_node const url_seeds = torrent_file.dict_find("url-list"); + if (url_seeds && url_seeds.type() == bdecode_node::string_t + && url_seeds.string_length() > 0) + { + web_seed_entry ent(maybe_url_encode(url_seeds.string_value().to_string()) + , web_seed_entry::url_seed); + if ((m_flags & multifile) && num_files() > 1) + ensure_trailing_slash(ent.url); + m_web_seeds.push_back(ent); + } + else if (url_seeds && url_seeds.type() == bdecode_node::list_t) + { + // only add a URL once + std::set unique; + for (int i = 0, end(url_seeds.list_size()); i < end; ++i) + { + bdecode_node const url = url_seeds.list_at(i); + if (url.type() != bdecode_node::string_t) continue; + if (url.string_length() == 0) continue; + web_seed_entry ent(maybe_url_encode(url.string_value().to_string()) + , web_seed_entry::url_seed); + if ((m_flags & multifile) && num_files() > 1) + ensure_trailing_slash(ent.url); + if (!unique.insert(ent.url).second) continue; + m_web_seeds.push_back(ent); + } + } + + // if there are any http-seeds, extract them + bdecode_node const http_seeds = torrent_file.dict_find("httpseeds"); + if (http_seeds && http_seeds.type() == bdecode_node::string_t + && http_seeds.string_length() > 0) + { + m_web_seeds.emplace_back(maybe_url_encode(http_seeds.string_value().to_string()) + , web_seed_entry::http_seed); + } + else if (http_seeds && http_seeds.type() == bdecode_node::list_t) + { + // only add a URL once + std::set unique; + for (int i = 0, end(http_seeds.list_size()); i < end; ++i) + { + bdecode_node const url = http_seeds.list_at(i); + if (url.type() != bdecode_node::string_t || url.string_length() == 0) continue; + std::string const u = maybe_url_encode(url.string_value().to_string()); + if (!unique.insert(u).second) continue; + m_web_seeds.emplace_back(u, web_seed_entry::http_seed); + } + } + + m_comment = torrent_file.dict_find_string_value("comment.utf-8").to_string(); + if (m_comment.empty()) m_comment = torrent_file.dict_find_string_value("comment").to_string(); + verify_encoding(m_comment); + + m_created_by = torrent_file.dict_find_string_value("created by.utf-8").to_string(); + if (m_created_by.empty()) m_created_by = torrent_file.dict_find_string_value("created by").to_string(); + verify_encoding(m_created_by); + + return true; + } + + void torrent_info::add_tracker(std::string const& url, int const tier) + { + add_tracker(url, tier, announce_entry::source_client); + } + + void torrent_info::add_tracker(std::string const& url, int const tier + , announce_entry::tracker_source const source) + { + TORRENT_ASSERT_PRECOND(!url.empty()); + auto const i = std::find_if(m_urls.begin(), m_urls.end() + , [&url](announce_entry const& ae) { return ae.url == url; }); + if (i != m_urls.end()) return; + + announce_entry e(url); + e.tier = std::uint8_t(tier); + e.source = source; + m_urls.push_back(e); + + std::sort(m_urls.begin(), m_urls.end() + , [] (announce_entry const& lhs, announce_entry const& rhs) + { return lhs.tier < rhs.tier; }); + } + +#if TORRENT_ABI_VERSION == 1 +namespace { + + struct filter_web_seed_type + { + explicit filter_web_seed_type(web_seed_entry::type_t t_) : t(t_) {} + void operator() (web_seed_entry const& w) + { if (w.type == t) urls.push_back(w.url); } + std::vector urls; + web_seed_entry::type_t t; + }; + } + + std::vector torrent_info::url_seeds() const + { + return std::for_each(m_web_seeds.begin(), m_web_seeds.end() + , filter_web_seed_type(web_seed_entry::url_seed)).urls; + } + + std::vector torrent_info::http_seeds() const + { + return std::for_each(m_web_seeds.begin(), m_web_seeds.end() + , filter_web_seed_type(web_seed_entry::http_seed)).urls; + } + + bool torrent_info::parse_info_section(lazy_entry const& le, error_code& ec) + { + if (le.type() == lazy_entry::none_t) return false; + std::pair buf = le.data_section(); + bdecode_node e; + if (bdecode(buf.first, buf.first + buf.second, e, ec) != 0) + return false; + + return parse_info_section(e, ec); + } + +#endif // TORRENT_ABI_VERSION + + void torrent_info::add_url_seed(std::string const& url + , std::string const& ext_auth + , web_seed_entry::headers_t const& ext_headers) + { + m_web_seeds.emplace_back(url, web_seed_entry::url_seed + , ext_auth, ext_headers); + } + + void torrent_info::add_http_seed(std::string const& url + , std::string const& auth + , web_seed_entry::headers_t const& extra_headers) + { + m_web_seeds.emplace_back(url, web_seed_entry::http_seed + , auth, extra_headers); + } + + void torrent_info::set_web_seeds(std::vector seeds) + { + m_web_seeds = std::move(seeds); + } + + std::vector torrent_info::similar_torrents() const + { + std::vector ret; +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + ret.reserve(m_similar_torrents.size() + m_owned_similar_torrents.size()); + + for (auto const& st : m_similar_torrents) + ret.emplace_back(st); + + for (auto const& st : m_owned_similar_torrents) + ret.push_back(st); +#endif + + return ret; + } + + std::vector torrent_info::collections() const + { + std::vector ret; +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + ret.reserve(m_collections.size() + m_owned_collections.size()); + + for (auto const& c : m_collections) + ret.emplace_back(c.first, aux::numeric_cast(c.second)); + + for (auto const& c : m_owned_collections) + ret.push_back(c); +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS + + return ret; + } + +#if TORRENT_USE_INVARIANT_CHECKS + void torrent_info::check_invariant() const + { + for (auto const i : m_files.file_range()) + { + TORRENT_ASSERT(m_files.file_name_ptr(i) != nullptr); + if (m_files.file_name_len(i) != -1) + { + // name needs to point into the allocated info section buffer + TORRENT_ASSERT(m_files.file_name_ptr(i) >= m_info_section.get()); + TORRENT_ASSERT(m_files.file_name_ptr(i) < m_info_section.get() + m_info_section_size); + } + else + { + // name must be a valid string + TORRENT_ASSERT(strlen(m_files.file_name_ptr(i)) < 2048); + } + } + + if (m_piece_hashes != nullptr) + { + TORRENT_ASSERT(m_piece_hashes >= m_info_section.get()); + TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size); + } + } +#endif + +} diff --git a/src/torrent_peer.cpp b/src/torrent_peer.cpp new file mode 100644 index 0000000..b5e1749 --- /dev/null +++ b/src/torrent_peer.cpp @@ -0,0 +1,282 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/io.hpp" // for write_uint16 + +namespace libtorrent { + + namespace { + + void apply_mask(std::uint8_t* b, std::uint8_t const* mask, int size) + { + for (int i = 0; i < size; ++i) + { + *b &= *mask; + ++b; + ++mask; + } + } + } + + // 1. if the IP addresses are identical, hash the ports in 16 bit network-order + // binary representation, ordered lowest first. + // 2. if the IPs are in the same /24, hash the IPs ordered, lowest first. + // 3. if the IPs are in the ame /16, mask the IPs by 0xffffff55, hash them + // ordered, lowest first. + // 4. if IPs are not in the same /16, mask the IPs by 0xffff5555, hash them + // ordered, lowest first. + // + // * for IPv6 peers, just use the first 64 bits and widen the masks. + // like this: 0xffff5555 -> 0xffffffff55555555 + // the lower 64 bits are always unmasked + // + // * for IPv6 addresses, compare /32 and /48 instead of /16 and /24 + // + // * the two IP addresses that are used to calculate the rank must + // always be of the same address family + // + // * all IP addresses are in network byte order when hashed + std::uint32_t peer_priority(tcp::endpoint e1, tcp::endpoint e2) + { + TORRENT_ASSERT(is_v4(e1) == is_v4(e2)); + + using std::swap; + + std::uint32_t ret; + if (e1.address() == e2.address()) + { + if (e1.port() > e2.port()) + swap(e1, e2); + std::uint32_t p; + auto ptr = reinterpret_cast(&p); + detail::write_uint16(e1.port(), ptr); + detail::write_uint16(e2.port(), ptr); + ret = crc32c_32(p); + } + else if (is_v6(e1)) + { + static const std::uint8_t v6mask[][8] = { + { 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55 }, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55 }, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } + }; + + if (e1 > e2) swap(e1, e2); + address_v6::bytes_type b1 = e1.address().to_v6().to_bytes(); + address_v6::bytes_type b2 = e2.address().to_v6().to_bytes(); + int const mask = std::memcmp(b1.data(), b2.data(), 4) ? 0 + : std::memcmp(b1.data(), b2.data(), 6) ? 1 : 2; + apply_mask(b1.data(), v6mask[mask], 8); + apply_mask(b2.data(), v6mask[mask], 8); + std::uint64_t addrbuf[4]; + memcpy(&addrbuf[0], b1.data(), 16); + memcpy(&addrbuf[2], b2.data(), 16); + ret = crc32c(addrbuf, 4); + } + else + { + static const std::uint8_t v4mask[][4] = { + { 0xff, 0xff, 0x55, 0x55 }, + { 0xff, 0xff, 0xff, 0x55 }, + { 0xff, 0xff, 0xff, 0xff } + }; + + if (e1 > e2) swap(e1, e2); + address_v4::bytes_type b1 = e1.address().to_v4().to_bytes(); + address_v4::bytes_type b2 = e2.address().to_v4().to_bytes(); + int mask = memcmp(&b1[0], &b2[0], 2) ? 0 + : memcmp(&b1[0], &b2[0], 3) ? 1 : 2; + apply_mask(&b1[0], v4mask[mask], 4); + apply_mask(&b2[0], v4mask[mask], 4); + std::uint64_t addrbuf; + memcpy(&addrbuf, &b1[0], 4); + memcpy(reinterpret_cast(&addrbuf) + 4, &b2[0], 4); + ret = crc32c(&addrbuf, 1); + } + + return ret; + } + + torrent_peer::torrent_peer(std::uint16_t port_, bool conn + , peer_source_flags_t const src) + : prev_amount_upload(0) + , prev_amount_download(0) + , connection(nullptr) + , peer_rank(0) + , last_optimistically_unchoked(0) + , last_connected(0) + , port(port_) + , hashfails(0) + , failcount(0) + , connectable(conn) + , optimistically_unchoked(false) + , seed(false) + , fast_reconnects(0) + , trust_points(0) + , source(static_cast(src)) +#if !defined TORRENT_DISABLE_ENCRYPTION + // assume no support in order to + // prefer opening non-encrypted + // connections. If it fails, we'll + // retry with encryption + , pe_support(false) +#endif + , is_v6_addr(false) +#if TORRENT_USE_I2P + , is_i2p_addr(false) +#endif + , on_parole(false) + , banned(false) + , supports_utp(true) // assume peers support utp + , confirmed_supports_utp(false) + , supports_holepunch(false) + , web_seed(false) + {} + + std::uint32_t torrent_peer::rank(external_ip const& external, int external_port) const + { + TORRENT_ASSERT(in_use); +//TODO: how do we deal with our external address changing? + if (peer_rank == 0) + peer_rank = peer_priority( + tcp::endpoint(external.external_address(this->address()), std::uint16_t(external_port)) + , tcp::endpoint(this->address(), this->port)); + return peer_rank; + } + +#ifndef TORRENT_DISABLE_LOGGING + std::string torrent_peer::to_string() const + { + TORRENT_ASSERT(in_use); +#if TORRENT_USE_I2P + if (is_i2p_addr) return dest().to_string(); +#endif // TORRENT_USE_I2P + error_code ec; + return address().to_string(ec); + } +#endif + + std::int64_t torrent_peer::total_download() const + { + TORRENT_ASSERT(in_use); + if (connection != nullptr) + { + TORRENT_ASSERT(prev_amount_download == 0); + return connection->statistics().total_payload_download(); + } + else + { + return std::int64_t(prev_amount_download) << 10; + } + } + + std::int64_t torrent_peer::total_upload() const + { + TORRENT_ASSERT(in_use); + if (connection != nullptr) + { + TORRENT_ASSERT(prev_amount_upload == 0); + return connection->statistics().total_payload_upload(); + } + else + { + return std::int64_t(prev_amount_upload) << 10; + } + } + + ipv4_peer::ipv4_peer(tcp::endpoint const& ep, bool c + , peer_source_flags_t const src) + : torrent_peer(ep.port(), c, src) + , addr(ep.address().to_v4()) + { + is_v6_addr = false; +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif + } + + ipv4_peer::ipv4_peer(ipv4_peer const&) = default; + ipv4_peer& ipv4_peer::operator=(ipv4_peer const& p) = default; + +#if TORRENT_USE_I2P + i2p_peer::i2p_peer(string_view dest, bool connectable_ + , peer_source_flags_t const src) + : torrent_peer(0, connectable_, src) + , destination(dest) + { + is_v6_addr = false; + is_i2p_addr = true; + } +#endif // TORRENT_USE_I2P + + ipv6_peer::ipv6_peer(tcp::endpoint const& ep, bool c + , peer_source_flags_t const src) + : torrent_peer(ep.port(), c, src) + , addr(ep.address().to_v6().to_bytes()) + { + is_v6_addr = true; +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif + } + + ipv6_peer::ipv6_peer(ipv6_peer const&) = default; + +#if TORRENT_USE_I2P + string_view torrent_peer::dest() const + { + if (is_i2p_addr) + return *static_cast(this)->destination; + return ""; + } +#endif + + libtorrent::address torrent_peer::address() const + { + if (is_v6_addr) + return libtorrent::address_v6( + static_cast(this)->addr); + else +#if TORRENT_USE_I2P + if (is_i2p_addr) return libtorrent::address(); + else +#endif + return static_cast(this)->addr; + } + +} diff --git a/src/torrent_peer_allocator.cpp b/src/torrent_peer_allocator.cpp new file mode 100644 index 0000000..5917a28 --- /dev/null +++ b/src/torrent_peer_allocator.cpp @@ -0,0 +1,116 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" + +namespace libtorrent { + + torrent_peer* torrent_peer_allocator::allocate_peer_entry(int type) + { + TORRENT_ASSERT(m_in_use); + torrent_peer* p = nullptr; + switch(type) + { + case torrent_peer_allocator_interface::ipv4_peer_type: + p = static_cast(m_ipv4_peer_pool.malloc()); + if (p == nullptr) return nullptr; + m_ipv4_peer_pool.set_next_size(500); + m_total_bytes += sizeof(libtorrent::ipv4_peer); + m_live_bytes += sizeof(libtorrent::ipv4_peer); + ++m_live_allocations; + ++m_total_allocations; + break; + case torrent_peer_allocator_interface::ipv6_peer_type: + p = static_cast(m_ipv6_peer_pool.malloc()); + if (p == nullptr) return nullptr; + m_ipv6_peer_pool.set_next_size(500); + m_total_bytes += sizeof(libtorrent::ipv6_peer); + m_live_bytes += sizeof(libtorrent::ipv6_peer); + ++m_live_allocations; + ++m_total_allocations; + break; +#if TORRENT_USE_I2P + case torrent_peer_allocator_interface::i2p_peer_type: + p = static_cast(m_i2p_peer_pool.malloc()); + if (p == nullptr) return nullptr; + m_i2p_peer_pool.set_next_size(500); + m_total_bytes += sizeof(libtorrent::i2p_peer); + m_live_bytes += sizeof(libtorrent::i2p_peer); + ++m_live_allocations; + ++m_total_allocations; + break; +#endif + } + return p; + } + + void torrent_peer_allocator::free_peer_entry(torrent_peer* p) + { + TORRENT_ASSERT(m_in_use); + TORRENT_ASSERT(p->in_use); + if (p->is_v6_addr) + { + TORRENT_ASSERT(m_ipv6_peer_pool.is_from(static_cast(p))); + static_cast(p)->~ipv6_peer(); + m_ipv6_peer_pool.free(p); + TORRENT_ASSERT(m_live_bytes >= int(sizeof(ipv6_peer))); + m_live_bytes -= int(sizeof(ipv6_peer)); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + return; + } +#if TORRENT_USE_I2P + if (p->is_i2p_addr) + { + TORRENT_ASSERT(m_i2p_peer_pool.is_from(static_cast(p))); + static_cast(p)->~i2p_peer(); + m_i2p_peer_pool.free(p); + TORRENT_ASSERT(m_live_bytes >= int(sizeof(i2p_peer))); + m_live_bytes -= int(sizeof(i2p_peer)); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + return; + } +#endif + TORRENT_ASSERT(m_ipv4_peer_pool.is_from(static_cast(p))); + static_cast(p)->~ipv4_peer(); + m_ipv4_peer_pool.free(p); + TORRENT_ASSERT(m_live_bytes >= int(sizeof(ipv4_peer))); + m_live_bytes -= int(sizeof(ipv4_peer)); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + } + +} diff --git a/src/torrent_status.cpp b/src/torrent_status.cpp new file mode 100644 index 0000000..94c4af7 --- /dev/null +++ b/src/torrent_status.cpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/torrent_status.hpp" + +namespace libtorrent { + + file_index_t constexpr torrent_status::error_file_none; + file_index_t constexpr torrent_status::error_file_ssl_ctx; + file_index_t constexpr torrent_status::error_file_exception; + file_index_t constexpr torrent_status::error_file_partfile; + file_index_t constexpr torrent_status::error_file_metadata; + +#if TORRENT_ABI_VERSION == 1 + file_index_t constexpr torrent_status::error_file_url; +#endif + + torrent_status::torrent_status() noexcept {} + torrent_status::~torrent_status() = default; + torrent_status::torrent_status(torrent_status const&) = default; + torrent_status& torrent_status::operator=(torrent_status const&) = default; + torrent_status::torrent_status(torrent_status&&) noexcept = default; + torrent_status& torrent_status::operator=(torrent_status&&) = default; + + static_assert(std::is_nothrow_move_constructible::value + , "should be nothrow move constructible"); + static_assert(std::is_nothrow_default_constructible::value + , "should be nothrow default constructible"); +} diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp new file mode 100644 index 0000000..349b328 --- /dev/null +++ b/src/tracker_manager.cpp @@ -0,0 +1,498 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/tracker_manager.hpp" +#include "libtorrent/http_tracker_connection.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/aux_/io.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/socket_io.hpp" + +#ifdef TORRENT_USE_OPENSSL +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" +#endif + +using namespace std::placeholders; + +namespace libtorrent { + + timeout_handler::timeout_handler(io_service& ios) + : m_start_time(clock_type::now()) + , m_read_time(m_start_time) + , m_timeout(ios) + {} + + timeout_handler::~timeout_handler() + { + TORRENT_ASSERT(m_outstanding_timer_wait == 0); + } + + void timeout_handler::set_timeout(int completion_timeout, int read_timeout) + { + m_completion_timeout = completion_timeout; + m_read_timeout = read_timeout; + m_start_time = m_read_time = clock_type::now(); + + TORRENT_ASSERT(completion_timeout > 0 || read_timeout > 0); + + if (m_abort) return; + + int timeout = 0; + if (m_read_timeout > 0) timeout = m_read_timeout; + if (m_completion_timeout > 0) + { + timeout = timeout == 0 + ? m_completion_timeout + : std::min(m_completion_timeout, timeout); + } + + ADD_OUTSTANDING_ASYNC("timeout_handler::timeout_callback"); + error_code ec; + m_timeout.expires_at(m_read_time + seconds(timeout), ec); + m_timeout.async_wait(std::bind( + &timeout_handler::timeout_callback, shared_from_this(), _1)); +#if TORRENT_USE_ASSERTS + ++m_outstanding_timer_wait; +#endif + } + + void timeout_handler::restart_read_timeout() + { + m_read_time = clock_type::now(); + } + + void timeout_handler::cancel() + { + m_abort = true; + m_completion_timeout = 0; + error_code ec; + m_timeout.cancel(ec); + } + + void timeout_handler::timeout_callback(error_code const& error) + { + COMPLETE_ASYNC("timeout_handler::timeout_callback"); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_outstanding_timer_wait > 0); + --m_outstanding_timer_wait; +#endif + if (m_abort) return; + + time_point now = clock_type::now(); + time_duration receive_timeout = now - m_read_time; + time_duration completion_timeout = now - m_start_time; + + if ((m_read_timeout + && m_read_timeout <= total_seconds(receive_timeout)) + || (m_completion_timeout + && m_completion_timeout <= total_seconds(completion_timeout)) + || error) + { + on_timeout(error); + return; + } + + int timeout = 0; + if (m_read_timeout > 0) timeout = m_read_timeout; + if (m_completion_timeout > 0) + { + timeout = timeout == 0 + ? int(m_completion_timeout - total_seconds(m_read_time - m_start_time)) + : std::min(int(m_completion_timeout - total_seconds(m_read_time - m_start_time)), timeout); + } + ADD_OUTSTANDING_ASYNC("timeout_handler::timeout_callback"); + error_code ec; + m_timeout.expires_at(m_read_time + seconds(timeout), ec); + m_timeout.async_wait( + std::bind(&timeout_handler::timeout_callback, shared_from_this(), _1)); +#if TORRENT_USE_ASSERTS + ++m_outstanding_timer_wait; +#endif + } + + tracker_connection::tracker_connection( + tracker_manager& man + , tracker_request const& req + , io_service& ios + , std::weak_ptr r) + : timeout_handler(ios) + , m_req(req) + , m_requester(std::move(r)) + , m_man(man) + {} + + std::shared_ptr tracker_connection::requester() const + { + return m_requester.lock(); + } + + void tracker_connection::fail(error_code const& ec + , char const* msg, seconds32 const interval, seconds32 const min_interval) + { + // we need to post the error to avoid deadlock + get_io_service().post(std::bind(&tracker_connection::fail_impl + , shared_from_this(), ec, std::string(msg), interval, min_interval)); + } + + void tracker_connection::fail_impl(error_code const& ec + , std::string const msg, seconds32 const interval, seconds32 const min_interval) + { + std::shared_ptr cb = requester(); + if (cb) cb->tracker_request_error(m_req, ec, msg + , interval.count() == 0 ? min_interval : interval); + close(); + } + + address tracker_connection::bind_interface() const + { + return m_req.outgoing_socket.get_local_endpoint().address(); + } + + void tracker_connection::sent_bytes(int bytes) + { + m_man.sent_bytes(bytes); + } + + void tracker_connection::received_bytes(int bytes) + { + m_man.received_bytes(bytes); + } + + tracker_manager::tracker_manager(send_fun_t const& send_fun + , send_fun_hostname_t const& send_fun_hostname + , counters& stats_counters + , resolver_interface& resolver + , aux::session_settings const& sett +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , aux::session_logger& ses +#endif + ) + : m_send_fun(send_fun) + , m_send_fun_hostname(send_fun_hostname) + , m_host_resolver(resolver) + , m_settings(sett) + , m_stats_counters(stats_counters) +#if !defined TORRENT_DISABLE_LOGGING || TORRENT_USE_ASSERTS + , m_ses(ses) +#endif + {} + + tracker_manager::~tracker_manager() + { + abort_all_requests(true); + } + + void tracker_manager::sent_bytes(int bytes) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + m_stats_counters.inc_stats_counter(counters::sent_tracker_bytes, bytes); + } + + void tracker_manager::received_bytes(int bytes) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + m_stats_counters.inc_stats_counter(counters::recv_tracker_bytes, bytes); + } + + void tracker_manager::remove_request(http_tracker_connection const* c) + { + TORRENT_ASSERT(is_single_thread()); + auto const i = std::find_if(m_http_conns.begin(), m_http_conns.end() + , [c] (std::shared_ptr const& ptr) { return ptr.get() == c; }); + if (i != m_http_conns.end()) + { + m_http_conns.erase(i); + if (!m_queued.empty()) + { + auto conn = std::move(m_queued.front()); + m_queued.pop_front(); + m_http_conns.push_back(std::move(conn)); + m_http_conns.back()->start(); + m_stats_counters.set_value(counters::num_queued_tracker_announces, std::int64_t(m_queued.size())); + } + return; + } + + auto const j = std::find_if(m_queued.begin(), m_queued.end() + , [c] (std::shared_ptr const& ptr) { return ptr.get() == c; }); + if (j != m_queued.end()) + { + m_queued.erase(j); + m_stats_counters.set_value(counters::num_queued_tracker_announces, std::int64_t(m_queued.size())); + } + } + + void tracker_manager::remove_request(udp_tracker_connection const* c) + { + TORRENT_ASSERT(is_single_thread()); + m_udp_conns.erase(c->transaction_id()); + } + + void tracker_manager::update_transaction_id( + std::shared_ptr c + , std::uint32_t tid) + { + TORRENT_ASSERT(is_single_thread()); + m_udp_conns.erase(c->transaction_id()); + m_udp_conns[tid] = c; + } + + void tracker_manager::queue_request( + io_service& ios + , tracker_request&& req + , aux::session_settings const& sett + , std::weak_ptr c) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(req.num_want >= 0); + TORRENT_ASSERT(!m_abort || req.event == tracker_request::stopped); + if (m_abort && req.event != tracker_request::stopped) return; + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = c.lock(); + if (cb) cb->debug_log("*** QUEUE_TRACKER_REQUEST [ listen_port: %d ]" + , req.listen_port); +#endif + + std::string const protocol = req.url.substr(0, req.url.find(':')); + +#ifdef TORRENT_USE_OPENSSL + if (protocol == "http" || protocol == "https") +#else + if (protocol == "http") +#endif + { + auto con = std::make_shared(ios, *this, std::move(req), c); + if (m_http_conns.size() < std::size_t(sett.get_int(settings_pack::max_concurrent_http_announces))) + { + m_http_conns.push_back(std::move(con)); + m_http_conns.back()->start(); + } + else + { + m_queued.push_back(std::move(con)); + m_stats_counters.set_value(counters::num_queued_tracker_announces, std::int64_t(m_queued.size())); + } + return; + } + else if (protocol == "udp") + { + auto con = std::make_shared(ios, *this, std::move(req), c); + m_udp_conns[con->transaction_id()] = con; + con->start(); + return; + } + + // we need to post the error to avoid deadlock + if (auto r = c.lock()) + ios.post(std::bind(&request_callback::tracker_request_error, r, std::move(req) + , errors::unsupported_url_protocol + , "", seconds32(0))); + } + + bool tracker_manager::incoming_packet(udp::endpoint const& ep + , span const buf) + { + TORRENT_ASSERT(is_single_thread()); + // ignore packets smaller than 8 bytes + if (buf.size() < 8) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_ses.should_log()) + { + m_ses.session_log("incoming packet from %s, not a UDP tracker message " + "(%d Bytes)", print_endpoint(ep).c_str(), int(buf.size())); + } +#endif + return false; + } + + // the first word is the action, if it's not [0, 3] + // it's not a valid udp tracker response + span ptr = buf; + std::uint32_t const action = aux::read_uint32(ptr); + if (action > 3) return false; + + std::uint32_t const transaction = aux::read_uint32(ptr); + auto const i = m_udp_conns.find(transaction); + + if (i == m_udp_conns.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_ses.should_log()) + { + m_ses.session_log("incoming UDP tracker packet from %s has invalid " + "transaction ID (%x)", print_endpoint(ep).c_str() + , transaction); + } +#endif + return false; + } + + std::shared_ptr const p = i->second; + // on_receive() may remove the tracker connection from the list + return p->on_receive(ep, buf); + } + + void tracker_manager::incoming_error(error_code const& + , udp::endpoint const&) + { + TORRENT_ASSERT(is_single_thread()); + // TODO: 2 implement + } + + bool tracker_manager::incoming_packet(char const* hostname + , span const buf) + { + TORRENT_ASSERT(is_single_thread()); + // ignore packets smaller than 8 bytes + if (buf.size() < 16) return false; + + // the first word is the action, if it's not [0, 3] + // it's not a valid udp tracker response + span ptr = buf; + std::uint32_t const action = aux::read_uint32(ptr); + if (action > 3) return false; + + std::uint32_t const transaction = aux::read_uint32(ptr); + auto const i = m_udp_conns.find(transaction); + + if (i == m_udp_conns.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + // now, this may not have been meant to be a tracker response, + // but chances are pretty good, so it's probably worth logging + m_ses.session_log("incoming UDP tracker packet from %s has invalid " + "transaction ID (%x)", hostname, int(transaction)); +#endif + return false; + } + + std::shared_ptr const p = i->second; + // on_receive() may remove the tracker connection from the list + return p->on_receive_hostname(hostname, buf); + } + + void tracker_manager::send_hostname(aux::listen_socket_handle const& sock + , char const* hostname, int const port + , span p, error_code& ec, udp_send_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + m_send_fun_hostname(sock, hostname, port, p, ec, flags); + } + + void tracker_manager::send(aux::listen_socket_handle const& sock + , udp::endpoint const& ep + , span p + , error_code& ec, udp_send_flags_t const flags) + { + TORRENT_ASSERT(is_single_thread()); + m_send_fun(sock, ep, p, ec, flags); + } + + void tracker_manager::abort_all_requests(bool all) + { + // this is called from the destructor too, which is not subject to the + // single-thread requirement. + TORRENT_ASSERT(all || is_single_thread()); + // removes all connections except 'event=stopped'-requests + + m_abort = true; + std::vector> close_http_connections; + std::vector> close_udp_connections; + + for (auto const& c : m_queued) + { + tracker_request const& req = c->tracker_req(); + if (req.event == tracker_request::stopped && !all) + continue; + + close_http_connections.push_back(c); + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr rc = c->requester(); + if (rc) rc->debug_log("aborting: %s", req.url.c_str()); +#endif + } + for (auto const& c : m_http_conns) + { + tracker_request const& req = c->tracker_req(); + if (req.event == tracker_request::stopped && !all) + continue; + + close_http_connections.push_back(c); + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr rc = c->requester(); + if (rc) rc->debug_log("aborting: %s", req.url.c_str()); +#endif + } + for (auto const& p : m_udp_conns) + { + auto const& c = p.second; + tracker_request const& req = c->tracker_req(); + if (req.event == tracker_request::stopped && !all) + continue; + + close_udp_connections.push_back(c); + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr rc = c->requester(); + if (rc) rc->debug_log("aborting: %s", req.url.c_str()); +#endif + } + + for (auto const& c : close_http_connections) + c->close(); + + for (auto const& c : close_udp_connections) + c->close(); + } + + bool tracker_manager::empty() const + { + TORRENT_ASSERT(is_single_thread()); + return m_http_conns.empty() && m_udp_conns.empty(); + } + + int tracker_manager::num_requests() const + { + TORRENT_ASSERT(is_single_thread()); + return int(m_http_conns.size() + m_udp_conns.size()); + } +} diff --git a/src/udp_socket.cpp b/src/udp_socket.cpp new file mode 100644 index 0000000..b9dca97 --- /dev/null +++ b/src/udp_socket.cpp @@ -0,0 +1,981 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_v4 +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/socks5_stream.hpp" // for socks_error +#include "libtorrent/aux_/keepalive.hpp" + +#include +#include + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#ifdef _WIN32 +// for SIO_KEEPALIVE_VALS +#include +#endif + +namespace libtorrent { + +using namespace std::placeholders; + +// used to build SOCKS messages in +std::size_t const tmp_buffer_size = 270; + +// used for SOCKS5 UDP wrapper header +std::size_t const max_header_size = 255; + +// this class hold the state of the SOCKS5 connection to maintain the UDP +// ASSOCIATE tunnel. It's instantiated on the heap for two reasons: +// +// 1. since its asynchronous functions may refer to it after the udp_socket has +// been destructed, it needs to be held by a shared_ptr +// 2. since using a socks proxy is assumed to be a less common case, it makes +// the common case cheaper by not allocating this space unconditionally +struct socks5 : std::enable_shared_from_this +{ + explicit socks5(io_service& ios, aux::listen_socket_handle ls + , alert_manager& alerts) + : m_socks5_sock(ios) + , m_resolver(ios) + , m_timer(ios) + , m_retry_timer(ios) + , m_alerts(alerts) + , m_listen_socket(std::move(ls)) + {} + + void start(aux::proxy_settings const& ps); + void close(); + + bool active() const { return m_active; } + udp::endpoint target() const { return m_udp_proxy_addr; } + +private: + + std::shared_ptr self() { return shared_from_this(); } + + void on_name_lookup(error_code const& e, tcp::resolver::iterator i); + void on_connect_timeout(error_code const& e); + void on_connected(error_code const& e); + void handshake1(error_code const& e); + void handshake2(error_code const& e); + void handshake3(error_code const& e); + void handshake4(error_code const& e); + void socks_forward_udp(); + void connect1(error_code const& e); + void connect2(error_code const& e); + void hung_up(error_code const& e); + void on_retry_socks_connect(error_code const& e); + + void retry_connection(); + + tcp::socket m_socks5_sock; + tcp::resolver m_resolver; + deadline_timer m_timer; + deadline_timer m_retry_timer; + alert_manager& m_alerts; + aux::listen_socket_handle m_listen_socket; + std::array m_tmp_buf; + + aux::proxy_settings m_proxy_settings; + + // this is the endpoint the proxy server lives at. + // when performing a UDP associate, we get another + // endpoint (presumably on the same IP) where we're + // supposed to send UDP packets. + tcp::endpoint m_proxy_addr; + + // this is where UDP packets that are to be forwarded + // are sent. The result from UDP ASSOCIATE is stored + // in here. + udp::endpoint m_udp_proxy_addr; + + // count failures to increase the retry timer + int m_failures = 0; + + // set to true when we've been asked to shut down + bool m_abort = false; + + // set to true once the tunnel is established + bool m_active = false; +}; + +#ifdef TORRENT_HAS_DONT_FRAGMENT +struct set_dont_frag +{ + set_dont_frag(udp::socket& sock, bool const df) + : m_socket(sock) + , m_df(df) + { + if (!m_df) return; + error_code ignore_errors; + m_socket.set_option(libtorrent::dont_fragment(true), ignore_errors); + TORRENT_ASSERT_VAL(!ignore_errors, ignore_errors.message()); + } + + ~set_dont_frag() + { + if (!m_df) return; + error_code ignore_errors; + m_socket.set_option(libtorrent::dont_fragment(false), ignore_errors); + TORRENT_ASSERT_VAL(!ignore_errors, ignore_errors.message()); + } + +private: + udp::socket& m_socket; + bool const m_df; +}; +#else +struct set_dont_frag +{ set_dont_frag(udp::socket&, int) {} }; +#endif + +udp_socket::udp_socket(io_service& ios, aux::listen_socket_handle ls) + : m_socket(ios) + , m_buf(new receive_buffer()) + , m_listen_socket(std::move(ls)) + , m_bind_port(0) + , m_abort(true) +{} + +int udp_socket::read(span pkts, error_code& ec) +{ + auto const num = int(pkts.size()); + int ret = 0; + packet p; + + while (ret < num) + { + int const len = int(m_socket.receive_from(boost::asio::buffer(*m_buf) + , p.from, 0, ec)); + + if (ec == error::would_block + || ec == error::try_again + || ec == error::operation_aborted + || ec == error::bad_descriptor) + { + return ret; + } + + if (ec == error::interrupted) + { + continue; + } + + if (ec) + { + // SOCKS5 cannot wrap ICMP errors. And even if it could, they certainly + // would not arrive as unwrapped (regular) ICMP errors. If we're using + // a proxy we must ignore these + if (m_proxy_settings.type != settings_pack::none) continue; + + p.error = ec; + p.data = span(); + } + else + { + p.data = {m_buf->data(), len}; + + // support packets coming from the SOCKS5 proxy + if (active_socks5()) + { + // if the source IP doesn't match the proxy's, ignore the packet + if (p.from != m_socks5_connection->target()) continue; + // if we failed to unwrap, silently ignore the packet + if (!unwrap(p.from, p.data)) continue; + } + else + { + // if we don't proxy trackers or peers, we may be receiving unwrapped + // packets and we must let them through. + bool const proxy_only + = m_proxy_settings.proxy_peer_connections + && m_proxy_settings.proxy_tracker_connections + ; + + // if we proxy everything, block all packets that aren't coming from + // the proxy + if (m_proxy_settings.type != settings_pack::none && proxy_only) continue; + } + } + + pkts[ret] = p; + ++ret; + + // we only have a single buffer for now, so we can only return a + // single packet. In the future though, we could attempt to drain + // the socket here, or maybe even use recvmmsg() + break; + } + + return ret; +} + +bool udp_socket::active_socks5() const +{ + return (m_socks5_connection && m_socks5_connection->active()); +} + +void udp_socket::send_hostname(char const* hostname, int const port + , span p, error_code& ec, udp_send_flags_t const flags) +{ + TORRENT_ASSERT(is_single_thread()); + + // if the sockets are closed, the udp_socket is closing too + if (!is_open()) + { + ec = error_code(boost::system::errc::bad_file_descriptor, generic_category()); + return; + } + + bool const use_proxy + = ((flags & peer_connection) && m_proxy_settings.proxy_peer_connections) + || ((flags & tracker_connection) && m_proxy_settings.proxy_tracker_connections) + || !(flags & (tracker_connection | peer_connection)) + ; + + if (use_proxy && m_proxy_settings.type != settings_pack::none) + { + if (active_socks5()) + { + // send udp packets through SOCKS5 server + wrap(hostname, port, p, ec, flags); + } + else + { + ec = error_code(boost::system::errc::permission_denied, generic_category()); + } + return; + } + + // the overload that takes a hostname is really only supported when we're + // using a proxy + address const target = make_address(hostname, ec); + if (!ec) send(udp::endpoint(target, std::uint16_t(port)), p, ec, flags); +} + +void udp_socket::send(udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t const flags) +{ + TORRENT_ASSERT(is_single_thread()); + + // if the sockets are closed, the udp_socket is closing too + if (!is_open()) + { + ec = error_code(boost::system::errc::bad_file_descriptor, generic_category()); + return; + } + + bool const use_proxy + = ((flags & peer_connection) && m_proxy_settings.proxy_peer_connections) + || ((flags & tracker_connection) && m_proxy_settings.proxy_tracker_connections) + || !(flags & (tracker_connection | peer_connection)) + ; + + if (use_proxy && m_proxy_settings.type != settings_pack::none) + { + if (active_socks5()) + { + // send udp packets through SOCKS5 server + wrap(ep, p, ec, flags); + } + else + { + ec = error_code(boost::system::errc::permission_denied, generic_category()); + } + return; + } + + // set the DF flag for the socket and clear it again in the destructor + set_dont_frag df(m_socket, (flags & dont_fragment) + && is_v4(ep)); + + m_socket.send_to(boost::asio::buffer(p.data(), static_cast(p.size())), ep, 0, ec); +} + +void udp_socket::wrap(udp::endpoint const& ep, span p + , error_code& ec, udp_send_flags_t const flags) +{ + TORRENT_UNUSED(flags); + using namespace libtorrent::detail; + + std::array header; + char* h = header.data(); + + write_uint16(0, h); // reserved + write_uint8(0, h); // fragment + write_uint8(is_v4(ep) ? 1 : 4, h); // atyp + write_endpoint(ep, h); + + std::array iovec; + iovec[0] = boost::asio::const_buffer(header.data(), aux::numeric_cast(h - header.data())); + iovec[1] = boost::asio::const_buffer(p.data(), static_cast(p.size())); + + // set the DF flag for the socket and clear it again in the destructor + set_dont_frag df(m_socket, (flags & dont_fragment) && is_v4(ep)); + + m_socket.send_to(iovec, m_socks5_connection->target(), 0, ec); +} + +void udp_socket::wrap(char const* hostname, int const port, span p + , error_code& ec, udp_send_flags_t const flags) +{ + using namespace libtorrent::detail; + + std::array header; + char* h = header.data(); + + write_uint16(0, h); // reserved + write_uint8(0, h); // fragment + write_uint8(3, h); // atyp + std::size_t const hostlen = std::min(std::strlen(hostname), max_header_size - 7); + write_uint8(hostlen, h); // hostname len + std::memcpy(h, hostname, hostlen); + h += hostlen; + write_uint16(port, h); + + std::array iovec; + iovec[0] = boost::asio::const_buffer(header.data(), aux::numeric_cast(h - header.data())); + iovec[1] = boost::asio::const_buffer(p.data(), static_cast(p.size())); + + // set the DF flag for the socket and clear it again in the destructor + set_dont_frag df(m_socket, (flags & dont_fragment) + && is_v4(m_socket.local_endpoint(ec))); + + m_socket.send_to(iovec, m_socks5_connection->target(), 0, ec); +} + +// unwrap the UDP packet from the SOCKS5 header +// buf is an in-out parameter. It will be updated +// return false if the packet should be ignored. It's not a valid Socks5 UDP +// forwarded packet +bool udp_socket::unwrap(udp::endpoint& from, span& buf) +{ + using namespace libtorrent::detail; + + // the minimum socks5 header size + auto const size = aux::numeric_cast(buf.size()); + if (size <= 10) return false; + + char* p = buf.data(); + p += 2; // reserved + int const frag = read_uint8(p); + // fragmentation is not supported + if (frag != 0) return false; + + int const atyp = read_uint8(p); + if (atyp == 1) + { + // IPv4 + from = read_v4_endpoint(p); + } + else if (atyp == 4) + { + // IPv6 + from = read_v6_endpoint(p); + } + else + { + int const len = read_uint8(p); + if (len > buf.end() - p) return false; + std::string hostname(p, p + len); + error_code ec; + address addr = make_address(hostname, ec); + // we only support "hostnames" that are a dotted decimal IP + if (ec) return false; + p += len; + from = udp::endpoint(addr, read_uint16(p)); + } + + buf = {p, size - (p - buf.data())}; + return true; +} + +#if !defined BOOST_ASIO_ENABLE_CANCELIO && defined TORRENT_WINDOWS +#error BOOST_ASIO_ENABLE_CANCELIO needs to be defined when building libtorrent to enable cancel() in asio on windows +#endif + +void udp_socket::close() +{ + TORRENT_ASSERT(is_single_thread()); + + error_code ec; + m_socket.close(ec); + TORRENT_ASSERT_VAL(!ec || ec == error::bad_descriptor, ec); + if (m_socks5_connection) + { + m_socks5_connection->close(); + m_socks5_connection.reset(); + } + m_abort = true; +} + +void udp_socket::open(udp const& protocol, error_code& ec) +{ + TORRENT_ASSERT(is_single_thread()); + + m_abort = false; + + if (m_socket.is_open()) m_socket.close(ec); + ec.clear(); + + m_socket.open(protocol, ec); + if (ec) return; + if (protocol == udp::v6()) + { + error_code err; + m_socket.set_option(boost::asio::ip::v6_only(true), err); + +#ifdef TORRENT_WINDOWS + // enable Teredo on windows + m_socket.set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err); +#endif // TORRENT_WINDOWS + } + + // this is best-effort. ignore errors +#ifdef TORRENT_WINDOWS + error_code err; + m_socket.set_option(exclusive_address_use(true), err); +#endif +} + +void udp_socket::bind(udp::endpoint const& ep, error_code& ec) +{ + if (!m_socket.is_open()) open(ep.protocol(), ec); + if (ec) return; + m_socket.bind(ep, ec); + if (ec) return; + m_socket.non_blocking(true, ec); + if (ec) return; + + error_code err; + m_bind_port = m_socket.local_endpoint(err).port(); + if (err) m_bind_port = ep.port(); +} + +void udp_socket::set_proxy_settings(aux::proxy_settings const& ps + , alert_manager& alerts) +{ + TORRENT_ASSERT(is_single_thread()); + + if (m_socks5_connection) + { + m_socks5_connection->close(); + m_socks5_connection.reset(); + } + + m_proxy_settings = ps; + + if (m_abort) return; + + if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw) + { + // connect to socks5 server and open up the UDP tunnel + m_socks5_connection = std::make_shared(lt::get_io_service(m_socket) + , m_listen_socket, alerts); + m_socks5_connection->start(ps); + } +} + +// ===================== SOCKS 5 ========================= + +void socks5::start(aux::proxy_settings const& ps) +{ + m_proxy_settings = ps; + + // TODO: use the system resolver_interface here + tcp::resolver::query q(ps.hostname, to_string(ps.port).data()); + ADD_OUTSTANDING_ASYNC("socks5::on_name_lookup"); + m_resolver.async_resolve(q, std::bind( + &socks5::on_name_lookup, self(), _1, _2)); +} + +void socks5::on_name_lookup(error_code const& e, tcp::resolver::iterator i) +{ + COMPLETE_ASYNC("socks5::on_name_lookup"); + + if (m_abort) return; + + if (e == boost::asio::error::operation_aborted) return; + + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_listen_socket.get_local_endpoint() + , operation_t::hostname_lookup, e); + ++m_failures; + retry_connection(); + return; + } + + // only set up a SOCKS5 tunnel for sockets with the same address family + // as the proxy + // this is a hack to mitigate excessive SOCKS5 tunnels, until this can get + // fixed properly. + for (;;) + { + if (i == tcp::resolver::iterator{}) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_listen_socket.get_local_endpoint() + , operation_t::hostname_lookup + , error_code(boost::system::errc::host_unreachable, generic_category())); + ++m_failures; + retry_connection(); + return; + } + + // we found a match + if (m_listen_socket.can_route(i->endpoint().address())) + break; + ++i; + } + + m_proxy_addr = i->endpoint(); + + error_code ec; + m_socks5_sock.open(is_v4(m_proxy_addr) ? tcp::v4() : tcp::v6(), ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_open, ec); + return; + } + + // enable keepalives + m_socks5_sock.set_option(boost::asio::socket_base::keep_alive(true), ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_option, ec); + ec.clear(); + } + +#if defined _WIN32 && !defined TORRENT_BUILD_SIMULATOR + SOCKET sock = m_socks5_sock.native_handle(); + DWORD bytes = 0; + tcp_keepalive timeout{}; + timeout.onoff = TRUE; + timeout.keepalivetime = 30; + timeout.keepaliveinterval = 30; + auto const ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &timeout, sizeof(timeout) + , nullptr, 0, &bytes, nullptr, nullptr); + if (ret != 0) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_option + , error_code(WSAGetLastError(), system_category())); + } +#else +#if defined TORRENT_HAS_KEEPALIVE_IDLE + // set keepalive timeouts + m_socks5_sock.set_option(aux::tcp_keepalive_idle(30), ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_option, ec); + ec.clear(); + } +#endif +#ifdef TORRENT_HAS_KEEPALIVE_INTERVAL + m_socks5_sock.set_option(aux::tcp_keepalive_interval(1), ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_option, ec); + ec.clear(); + } +#endif +#endif + + tcp::endpoint const bind_ep(m_listen_socket.get_local_endpoint().address(), 0); + m_socks5_sock.bind(bind_ep, ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_bind, ec); + ++m_failures; + retry_connection(); + return; + } + + // TODO: perhaps an attempt should be made to bind m_socks5_sock to the + // device of m_listen_socket + + ADD_OUTSTANDING_ASYNC("socks5::on_connected"); + m_socks5_sock.async_connect(m_proxy_addr + , std::bind(&socks5::on_connected, self(), _1)); + + ADD_OUTSTANDING_ASYNC("socks5::on_connect_timeout"); + m_timer.expires_from_now(seconds(10)); + m_timer.async_wait(std::bind(&socks5::on_connect_timeout + , self(), _1)); +} + +void socks5::on_connect_timeout(error_code const& e) +{ + COMPLETE_ASYNC("socks5::on_connect_timeout"); + + if (e == boost::asio::error::operation_aborted) return; + + if (m_abort) return; + + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::connect, errors::timed_out); + + error_code ignore; + m_socks5_sock.close(ignore); + + ++m_failures; + retry_connection(); +} + +void socks5::on_connected(error_code const& e) +{ + COMPLETE_ASYNC("socks5::on_connected"); + + m_timer.cancel(); + + if (e == boost::asio::error::operation_aborted) return; + + if (m_abort) return; + + // we failed to connect to the proxy + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::connect, e); + ++m_failures; + retry_connection(); + return; + } + + using namespace libtorrent::detail; + + // send SOCKS5 authentication methods + char* p = m_tmp_buf.data(); + write_uint8(5, p); // SOCKS VERSION 5 + if (m_proxy_settings.username.empty() + || m_proxy_settings.type == settings_pack::socks5) + { + write_uint8(1, p); // 1 authentication method (no auth) + write_uint8(0, p); // no authentication + } + else + { + write_uint8(2, p); // 2 authentication methods + write_uint8(0, p); // no authentication + write_uint8(2, p); // username/password + } + TORRENT_ASSERT_VAL(p - m_tmp_buf.data() < int(m_tmp_buf.size()), (p - m_tmp_buf.data())); + ADD_OUTSTANDING_ASYNC("socks5::on_handshake1"); + boost::asio::async_write(m_socks5_sock, boost::asio::buffer(m_tmp_buf.data() + , aux::numeric_cast(p - m_tmp_buf.data())) + , std::bind(&socks5::handshake1, self(), _1)); +} + +void socks5::handshake1(error_code const& e) +{ + COMPLETE_ASYNC("socks5::on_handshake1"); + if (m_abort) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake, e); + ++m_failures; + retry_connection(); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5::on_handshake2"); + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf.data(), 2) + , std::bind(&socks5::handshake2, self(), _1)); +} + +void socks5::handshake2(error_code const& e) +{ + COMPLETE_ASYNC("socks5::on_handshake2"); + if (m_abort) return; + + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake, e); + ++m_failures; + retry_connection(); + return; + } + + using namespace libtorrent::detail; + + char* p = m_tmp_buf.data(); + int const version = read_uint8(p); + int const method = read_uint8(p); + + if (version < 5) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake + , socks_error::unsupported_version); + error_code ec; + m_socks5_sock.close(ec); + return; + } + + if (method == 0) + { + socks_forward_udp(/*l*/); + } + else if (method == 2) + { + if (m_proxy_settings.username.empty()) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake + , socks_error::username_required); + error_code ec; + m_socks5_sock.close(ec); + return; + } + + // start sub-negotiation + p = m_tmp_buf.data(); + write_uint8(1, p); + TORRENT_ASSERT(m_proxy_settings.username.size() < 0x100); + write_uint8(uint8_t(m_proxy_settings.username.size()), p); + write_string(m_proxy_settings.username, p); + TORRENT_ASSERT(m_proxy_settings.password.size() < 0x100); + write_uint8(uint8_t(m_proxy_settings.password.size()), p); + write_string(m_proxy_settings.password, p); + TORRENT_ASSERT_VAL(p - m_tmp_buf.data() < int(m_tmp_buf.size()), (p - m_tmp_buf.data())); + ADD_OUTSTANDING_ASYNC("socks5::on_handshake3"); + boost::asio::async_write(m_socks5_sock + , boost::asio::buffer(m_tmp_buf.data(), aux::numeric_cast(p - m_tmp_buf.data())) + , std::bind(&socks5::handshake3, self(), _1)); + } + else + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake + , socks_error::unsupported_authentication_method); + + error_code ec; + m_socks5_sock.close(ec); + return; + } +} + +void socks5::handshake3(error_code const& e) +{ + COMPLETE_ASYNC("socks5::on_handshake3"); + if (m_abort) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake, e); + ++m_failures; + retry_connection(); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5::on_handshake4"); + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf.data(), 2) + , std::bind(&socks5::handshake4, self(), _1)); +} + +void socks5::handshake4(error_code const& e) +{ + COMPLETE_ASYNC("socks5::on_handshake4"); + if (m_abort) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake, e); + ++m_failures; + retry_connection(); + return; + } + + using namespace libtorrent::detail; + + char* p = m_tmp_buf.data(); + int const version = read_uint8(p); + int const status = read_uint8(p); + + if (version != 1 || status != 0) return; + + socks_forward_udp(/*l*/); +} + +void socks5::socks_forward_udp() +{ + using namespace libtorrent::detail; + + // send SOCKS5 UDP command + char* p = m_tmp_buf.data(); + write_uint8(5, p); // SOCKS VERSION 5 + write_uint8(3, p); // UDP ASSOCIATE command + write_uint8(0, p); // reserved + write_uint8(1, p); // ATYP = IPv4 + write_uint32(0, p); // 0.0.0.0 + write_uint16(0, p); // :0 + TORRENT_ASSERT_VAL(p - m_tmp_buf.data() < int(m_tmp_buf.size()), (p - m_tmp_buf.data())); + ADD_OUTSTANDING_ASYNC("socks5::connect1"); + boost::asio::async_write(m_socks5_sock + , boost::asio::buffer(m_tmp_buf.data(), aux::numeric_cast(p - m_tmp_buf.data())) + , std::bind(&socks5::connect1, self(), _1)); +} + +void socks5::connect1(error_code const& e) +{ + COMPLETE_ASYNC("socks5::connect1"); + if (m_abort) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::connect, e); + ++m_failures; + retry_connection(); + return; + } + + ADD_OUTSTANDING_ASYNC("socks5::connect2"); + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf.data(), 10) + , std::bind(&socks5::connect2, self(), _1)); +} + +void socks5::connect2(error_code const& e) +{ + COMPLETE_ASYNC("socks5::connect2"); + + if (m_abort) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::handshake, e); + ++m_failures; + retry_connection(); + return; + } + + using namespace libtorrent::detail; + + char* p = m_tmp_buf.data(); + int const version = read_uint8(p); // VERSION + int const status = read_uint8(p); // STATUS + ++p; // RESERVED + int const atyp = read_uint8(p); // address type + + if (version != 5 || status != 0) return; + + if (atyp == 1) + { + m_udp_proxy_addr.address(address_v4(read_uint32(p))); + m_udp_proxy_addr.port(read_uint16(p)); + } + else + { + // in this case we need to read more data from the socket + // no IPv6 support for UDP socks5 + TORRENT_ASSERT_FAIL(); + return; + } + + // we're done! + m_active = true; + m_failures = 0; + + ADD_OUTSTANDING_ASYNC("socks5::hung_up"); + boost::asio::async_read(m_socks5_sock, boost::asio::buffer(m_tmp_buf.data(), 10) + , std::bind(&socks5::hung_up, self(), _1)); +} + +void socks5::hung_up(error_code const& e) +{ + COMPLETE_ASYNC("socks5::hung_up"); + m_active = false; + + if (e == boost::asio::error::operation_aborted || m_abort) return; + + if (e && m_alerts.should_post()) + m_alerts.emplace_alert(m_proxy_addr, operation_t::sock_read, e); + + retry_connection(); +} + +void socks5::retry_connection() +{ + // the socks connection was closed, re-open it in a bit + // back off exponentially + if (m_failures > 200) m_failures = 200; + m_retry_timer.expires_from_now(seconds(std::min(120, m_failures * m_failures / 2) + 5)); + m_retry_timer.async_wait(std::bind(&socks5::on_retry_socks_connect + , self(), _1)); +} + +void socks5::on_retry_socks_connect(error_code const& e) +{ + if (e || m_abort) return; + error_code ignore; + m_socks5_sock.close(ignore); + start(m_proxy_settings); +} + +void socks5::close() +{ + m_abort = true; + error_code ec; + m_socks5_sock.close(ec); + m_resolver.cancel(); + m_timer.cancel(); + m_retry_timer.cancel(); +} + +constexpr udp_send_flags_t udp_socket::peer_connection; +constexpr udp_send_flags_t udp_socket::tracker_connection; +constexpr udp_send_flags_t udp_socket::dont_queue; +constexpr udp_send_flags_t udp_socket::dont_fragment; + +} diff --git a/src/udp_tracker_connection.cpp b/src/udp_tracker_connection.cpp new file mode 100644 index 0000000..163946e --- /dev/null +++ b/src/udp_tracker_connection.cpp @@ -0,0 +1,776 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include + +#include "libtorrent/parse_url.hpp" +#include "libtorrent/udp_tracker_connection.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_any +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/io.hpp" +#include "libtorrent/peer.hpp" +#include "libtorrent/error_code.hpp" + +#ifndef TORRENT_DISABLE_LOGGING +#include "libtorrent/socket_io.hpp" +#endif + +namespace libtorrent { + + std::map + udp_tracker_connection::m_connection_cache; + + std::mutex udp_tracker_connection::m_cache_mutex; + + udp_tracker_connection::udp_tracker_connection( + io_service& ios + , tracker_manager& man + , tracker_request const& req + , std::weak_ptr c) + : tracker_connection(man, req, ios, std::move(c)) + , m_transaction_id(0) + , m_attempts(0) + , m_state(action_t::error) + , m_abort(false) + { + update_transaction_id(); + } + + void udp_tracker_connection::start() + { + // TODO: 2 support authentication here. tracker_req().auth + std::string hostname; + std::string protocol; + int port; + error_code ec; + + std::tie(protocol, std::ignore, hostname, port, std::ignore) + = parse_url_components(tracker_req().url, ec); + if (port == -1) port = protocol == "http" ? 80 : 443; + + if (ec) + { + tracker_connection::fail(ec); + return; + } + + aux::session_settings const& settings = m_man.settings(); + + int const proxy_type = settings.get_int(settings_pack::proxy_type); + + if (settings.get_bool(settings_pack::proxy_hostnames) + && (proxy_type == settings_pack::socks5 + || proxy_type == settings_pack::socks5_pw)) + { + m_hostname = hostname; + m_target.port(std::uint16_t(port)); + start_announce(); + } + else + { + using namespace std::placeholders; + ADD_OUTSTANDING_ASYNC("udp_tracker_connection::name_lookup"); + // when stopping, pass in the cache-only flag, because we + // don't want to get stuck on DNS lookups when shutting down + m_man.host_resolver().async_resolve(hostname + , (tracker_req().event == tracker_request::stopped + ? resolver_interface::cache_only : resolver_flags{}) + | resolver_interface::abort_on_shutdown + , std::bind(&udp_tracker_connection::name_lookup + , shared_from_this(), _1, _2, port)); + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); + if (cb) cb->debug_log("*** UDP_TRACKER [ initiating name lookup: \"%s\" ]" + , hostname.c_str()); +#endif + } + + set_timeout(tracker_req().event == tracker_request::stopped + ? settings.get_int(settings_pack::stop_tracker_timeout) + : settings.get_int(settings_pack::tracker_completion_timeout) + , settings.get_int(settings_pack::tracker_receive_timeout)); + } + + void udp_tracker_connection::fail(error_code const& ec + , char const* msg, seconds32 const interval, seconds32 const min_interval) + { + // m_target failed. remove it from the endpoint list + auto const i = std::find(m_endpoints.begin() + , m_endpoints.end(), make_tcp(m_target)); + + if (i != m_endpoints.end()) m_endpoints.erase(i); + + // if that was the last one, or the listen socket was closed + // fail the whole announce + if (m_endpoints.empty() || !tracker_req().outgoing_socket) + { + tracker_connection::fail(ec, msg, interval, min_interval); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); + if (cb && cb->should_log()) + { + cb->debug_log(R"(*** UDP_TRACKER [ host: "%s" ip: "%s" | ERROR: "%s" ])" + , m_hostname.c_str(), print_endpoint(m_target).c_str(), ec.message().c_str()); + } +#endif + + // pick another target endpoint and try again + m_target = make_udp(m_endpoints.front()); + +#ifndef TORRENT_DISABLE_LOGGING + if (cb && cb->should_log()) + { + cb->debug_log(R"(*** UDP_TRACKER trying next IP [ host: "%s" ip: "%s" ])" + , m_hostname.c_str(), print_endpoint(m_target).c_str()); + } +#endif + get_io_service().post(std::bind( + &udp_tracker_connection::start_announce, shared_from_this())); + + aux::session_settings const& settings = m_man.settings(); + set_timeout(tracker_req().event == tracker_request::stopped + ? settings.get_int(settings_pack::stop_tracker_timeout) + : settings.get_int(settings_pack::tracker_completion_timeout) + , settings.get_int(settings_pack::tracker_receive_timeout)); + } + + void udp_tracker_connection::name_lookup(error_code const& error + , std::vector
    const& addresses, int port) + { + COMPLETE_ASYNC("udp_tracker_connection::name_lookup"); + if (m_abort) return; + if (error == boost::asio::error::operation_aborted) return; + if (error || addresses.empty()) + { + fail(error); + return; + } + + std::shared_ptr cb = requester(); +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER [ name lookup successful ]"); +#endif + if (cancelled()) + { + fail(error_code(errors::torrent_aborted)); + return; + } + + restart_read_timeout(); + + if (!tracker_req().outgoing_socket) + { + fail(error_code(errors::invalid_listen_socket)); + return; + } + + auto const listen_socket = bind_socket(); + + // filter all endpoints we cannot reach from this listen socket, which may + // be all of them, in which case we should not announce this listen socket + // to this tracker + for (auto const& addr : addresses) + { + if (!listen_socket.can_route(addr)) continue; + m_endpoints.emplace_back(addr, std::uint16_t(port)); + } + + if (m_endpoints.empty()) + { + fail(error_code(boost::system::errc::host_unreachable, generic_category())); + return; + } + + if (tracker_req().filter) + { + // remove endpoints that are filtered by the IP filter + for (auto k = m_endpoints.begin(); k != m_endpoints.end();) + { + if (tracker_req().filter->access(k->address()) == ip_filter::blocked) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb && cb->should_log()) + { + cb->debug_log("*** UDP_TRACKER [ IP blocked by filter: %s ]" + , print_address(k->address()).c_str()); + } +#endif + k = m_endpoints.erase(k); + } + else + ++k; + } + } + + // if all endpoints were filtered by the IP filter, we can't connect + if (m_endpoints.empty()) + { + fail(error_code(errors::banned_by_ip_filter)); + return; + } + + m_target = make_udp(m_endpoints.front()); + + start_announce(); + } + + void udp_tracker_connection::start_announce() + { + std::unique_lock l(m_cache_mutex); + auto const cc = m_connection_cache.find(m_target.address()); + if (cc != m_connection_cache.end()) + { + // we found a cached entry! Now, we can only + // use if if it hasn't expired + if (aux::time_now() < cc->second.expires) + { + if (tracker_req().kind & tracker_request::scrape_request) + send_udp_scrape(); + else + send_udp_announce(); + return; + } + // if it expired, remove it from the cache + m_connection_cache.erase(cc); + } + l.unlock(); + + send_udp_connect(); + } + + void udp_tracker_connection::on_timeout(error_code const& ec) + { + if (ec) + { + fail(ec); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); + if (cb) cb->debug_log("*** UDP_TRACKER [ timed out url: %s ]", tracker_req().url.c_str()); +#endif + fail(error_code(errors::timed_out)); + } + + void udp_tracker_connection::close() + { + cancel(); + m_man.remove_request(this); + } + + bool udp_tracker_connection::on_receive_hostname(char const* hostname + , span buf) + { + TORRENT_UNUSED(hostname); + // just ignore the hostname this came from, pretend that + // it's from the same endpoint we sent it to (i.e. the same + // port). We have so many other ways of confirming this packet + // comes from the tracker anyway, so it's not a big deal + return on_receive(m_target, buf); + } + + bool udp_tracker_connection::on_receive(udp::endpoint const& ep + , span const buf) + { +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); +#endif + + // ignore responses before we've sent any requests + if (m_state == action_t::error) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER [ m_action == error ]"); +#endif + return false; + } + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER [ aborted]"); +#endif + return false; + } + + // ignore packet not sent from the tracker + // if m_target is inaddr_any, it suggests that we + // sent the packet through a proxy only knowing + // the hostname, in which case this packet might be for us + if (!is_any(m_target.address()) && m_target != ep) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb && cb->should_log()) + { + cb->debug_log("<== UDP_TRACKER [ unexpected source IP: %s " + "expected: %s ]" + , print_endpoint(ep).c_str() + , print_endpoint(m_target).c_str()); + } +#endif + return false; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("<== UDP_TRACKER_PACKET [ size: %d ]" + , int(buf.size())); +#endif + + // ignore packets smaller than 8 bytes + if (buf.size() < 8) return false; + + span ptr = buf; + auto const action = static_cast(aux::read_int32(ptr)); + std::uint32_t const transaction = aux::read_uint32(ptr); + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER_PACKET [ action: %d ]" + , static_cast(action)); +#endif + + // ignore packets with incorrect transaction id + if (m_transaction_id != transaction) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER_PACKET [ tid: %x ]" + , int(transaction)); +#endif + return false; + } + + if (action == action_t::error) + { + fail(error_code(errors::tracker_failure) + , std::string(buf.data(), static_cast(buf.size())).c_str()); + return true; + } + + // ignore packets that's not a response to our message + if (action != m_state) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("*** UDP_TRACKER_PACKET [ unexpected action: %d " + " expected: %d ]", static_cast(action), static_cast(m_state)); +#endif + return false; + } + + restart_read_timeout(); + +#ifndef TORRENT_DISABLE_LOGGING + if (cb) + cb->debug_log("*** UDP_TRACKER_RESPONSE [ tid: %x ]" + , int(transaction)); +#endif + + switch (m_state) + { + case action_t::connect: + return on_connect_response(buf); + case action_t::announce: + return on_announce_response(buf); + case action_t::scrape: + return on_scrape_response(buf); + case action_t::error: + return false; + } + return false; + } + + void udp_tracker_connection::update_transaction_id() + { + // don't use 0, because that has special meaning (uninitialized) + std::uint32_t const new_tid = random(0xfffffffe) + 1; + + if (m_transaction_id != 0) + m_man.update_transaction_id(shared_from_this(), new_tid); + m_transaction_id = new_tid; + } + + bool udp_tracker_connection::on_connect_response(span buf) + { + // ignore packets smaller than 16 bytes + if (buf.size() < 16) return false; + + restart_read_timeout(); + + // skip header + buf = buf.subspan(8); + + // reset transaction + update_transaction_id(); + std::int64_t const connection_id = aux::read_int64(buf); + + std::lock_guard l(m_cache_mutex); + connection_cache_entry& cce = m_connection_cache[m_target.address()]; + cce.connection_id = connection_id; + cce.expires = aux::time_now() + seconds(m_man.settings().get_int(settings_pack::udp_tracker_token_expiry)); + + if (0 == (tracker_req().kind & tracker_request::scrape_request)) + send_udp_announce(); + else if (0 != (tracker_req().kind & tracker_request::scrape_request)) + send_udp_scrape(); + return true; + } + + void udp_tracker_connection::send_udp_connect() + { +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); +#endif + + if (m_abort) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb) cb->debug_log("==> UDP_TRACKER_CONNECT [ skipped, m_abort ]"); +#endif + return; + } + + std::size_t const connect_packet_size = 16; + std::array buf; + span view = buf; + + TORRENT_ASSERT(m_transaction_id != 0); + + aux::write_uint32(0x417, view); + aux::write_uint32(0x27101980, view); // connection_id + aux::write_int32(action_t::connect, view); // action (connect) + aux::write_int32(m_transaction_id, view); // transaction_id + TORRENT_ASSERT(view.empty()); + + error_code ec; + if (!m_hostname.empty()) + { + m_man.send_hostname(bind_socket(), m_hostname.c_str() + , m_target.port(), buf, ec + , udp_socket::tracker_connection); + } + else + { + m_man.send(bind_socket(), m_target, buf, ec + , udp_socket::tracker_connection); + } + + ++m_attempts; + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (cb && cb->should_log()) + { + cb->debug_log("==> UDP_TRACKER_CONNECT [ failed: %s ]" + , ec.message().c_str()); + } +#endif + fail(ec); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (cb && cb->should_log()) + { + cb->debug_log("==> UDP_TRACKER_CONNECT [ to: %s ih: %s ]" + , m_hostname.empty() + ? print_endpoint(m_target).c_str() + : (m_hostname + ":" + to_string(m_target.port()).data()).c_str() + , aux::to_hex(tracker_req().info_hash).c_str()); + } +#endif + + m_state = action_t::connect; + sent_bytes(16 + 28); // assuming UDP/IP header + } + + void udp_tracker_connection::send_udp_scrape() + { + if (m_abort) return; + + auto const i = m_connection_cache.find(m_target.address()); + // this isn't really supposed to happen + TORRENT_ASSERT(i != m_connection_cache.end()); + if (i == m_connection_cache.end()) return; + + char buf[8 + 4 + 4 + 20]; + span view = buf; + + aux::write_int64(i->second.connection_id, view); // connection_id + aux::write_int32(action_t::scrape, view); // action (scrape) + aux::write_int32(m_transaction_id, view); // transaction_id + // info_hash + std::copy(tracker_req().info_hash.begin(), tracker_req().info_hash.end() + , view.data()); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(view.size() == 20); +#endif + + error_code ec; + if (!m_hostname.empty()) + { + m_man.send_hostname(bind_socket(), m_hostname.c_str(), m_target.port() + , buf, ec, udp_socket::tracker_connection); + } + else + { + m_man.send(bind_socket(), m_target, buf, ec + , udp_socket::tracker_connection); + } + m_state = action_t::scrape; + sent_bytes(sizeof(buf) + 28); // assuming UDP/IP header + ++m_attempts; + if (ec) + { + fail(ec); + return; + } + } + + bool udp_tracker_connection::on_announce_response(span buf) + { + if (buf.size() < 20) return false; + + buf = buf.subspan(8); + restart_read_timeout(); + + tracker_response resp; + + resp.interval = seconds32(aux::read_int32(buf)); + resp.min_interval = seconds32(60); + resp.incomplete = aux::read_int32(buf); + resp.complete = aux::read_int32(buf); + + int const ip_stride = is_v6(m_target) ? 18 : 6; + auto const num_peers = buf.size() / ip_stride; + if (buf.size() % ip_stride != 0) + { + fail(error_code(errors::invalid_tracker_response_length)); + return false; + } + + std::shared_ptr cb = requester(); +#ifndef TORRENT_DISABLE_LOGGING + if (cb) + { + cb->debug_log("<== UDP_TRACKER_RESPONSE [ url: %s ]", tracker_req().url.c_str()); + } +#endif + + if (!cb) + { + close(); + return true; + } + + if (is_v6(m_target)) + { + resp.peers6.reserve(static_cast(num_peers)); + for (int i = 0; i < num_peers; ++i) + { + ipv6_peer_entry e{}; + std::memcpy(e.ip.data(), buf.data(), 16); + buf = buf.subspan(16); + e.port = aux::read_uint16(buf); + resp.peers6.push_back(e); + } + } + else + { + resp.peers4.reserve(static_cast(num_peers)); + for (int i = 0; i < num_peers; ++i) + { + ipv4_peer_entry e{}; + std::memcpy(e.ip.data(), buf.data(), 4); + buf = buf.subspan(4); + e.port = aux::read_uint16(buf); + resp.peers4.push_back(e); + } + } + + // TODO: why is this a linked list? + std::list
    ip_list; + std::transform(m_endpoints.begin(), m_endpoints.end(), std::back_inserter(ip_list) + , [](tcp::endpoint const& ep) { return ep.address(); } ); + + cb->tracker_response(tracker_req(), m_target.address(), ip_list, resp); + + close(); + return true; + } + + bool udp_tracker_connection::on_scrape_response(span buf) + { + restart_read_timeout(); + auto const action = static_cast(aux::read_int32(buf)); + std::uint32_t const transaction = aux::read_uint32(buf); + + if (transaction != m_transaction_id) + { + fail(error_code(errors::invalid_tracker_transaction_id)); + return false; + } + + if (action == action_t::error) + { + fail(error_code(errors::tracker_failure) + , std::string(buf.data(), static_cast(buf.size())).c_str()); + return true; + } + + if (action != action_t::scrape) + { + fail(error_code(errors::invalid_tracker_action)); + return true; + } + + if (buf.size() < 12) + { + fail(error_code(errors::invalid_tracker_response_length)); + return true; + } + + int const complete = aux::read_int32(buf); + int const downloaded = aux::read_int32(buf); + int const incomplete = aux::read_int32(buf); + + std::shared_ptr cb = requester(); + if (!cb) + { + close(); + return true; + } + + cb->tracker_scrape_response(tracker_req() + , complete, incomplete, downloaded, -1); + + close(); + return true; + } + + void udp_tracker_connection::send_udp_announce() + { + if (m_abort) return; + + char buf[800]; + span out = buf; + + tracker_request const& req = tracker_req(); + aux::session_settings const& settings = m_man.settings(); + + auto const i = m_connection_cache.find(m_target.address()); + // this isn't really supposed to happen + TORRENT_ASSERT(i != m_connection_cache.end()); + if (i == m_connection_cache.end()) return; + + aux::write_int64(i->second.connection_id, out); // connection_id + aux::write_int32(action_t::announce, out); // action (announce) + aux::write_int32(m_transaction_id, out); // transaction_id + std::copy(req.info_hash.begin(), req.info_hash.end(), out.data()); // info_hash + out = out.subspan(20); + std::copy(req.pid.begin(), req.pid.end(), out.data()); // peer_id + out = out.subspan(20); + aux::write_int64(req.downloaded, out); // downloaded + aux::write_int64(req.left, out); // left + aux::write_int64(req.uploaded, out); // uploaded + aux::write_int32(req.event, out); // event + // ip address + address_v4 announce_ip; + + if (!settings.get_bool(settings_pack::anonymous_mode) + && !settings.get_str(settings_pack::announce_ip).empty()) + { + error_code ec; + address ip = make_address(settings.get_str(settings_pack::announce_ip).c_str(), ec); + if (!ec && ip.is_v4()) announce_ip = ip.to_v4(); + } + aux::write_uint32(announce_ip.to_ulong(), out); + aux::write_int32(req.key, out); // key + aux::write_int32(req.num_want, out); // num_want + aux::write_uint16(req.listen_port, out); // port + + std::string request_string; + error_code ec; + using std::ignore; + std::tie(ignore, ignore, ignore, ignore, request_string) + = parse_url_components(req.url, ec); + if (ec) request_string.clear(); + + if (!request_string.empty()) + { + std::size_t str_len = std::min(request_string.size(), std::size_t(255)); + request_string.resize(str_len); + + aux::write_uint8(2, out); + aux::write_uint8(str_len, out); + aux::write_string(request_string, out); + } + +#ifndef TORRENT_DISABLE_LOGGING + std::shared_ptr cb = requester(); + if (cb && cb->should_log()) + { + cb->debug_log("==> UDP_TRACKER_ANNOUNCE [%s]", aux::to_hex(req.info_hash).c_str()); + } +#endif + + if (!m_hostname.empty()) + { + m_man.send_hostname(bind_socket(), m_hostname.c_str() + , m_target.port(), {buf, int(sizeof(buf)) - out.size()}, ec + , udp_socket::tracker_connection); + } + else + { + m_man.send(bind_socket(), m_target, {buf, int(sizeof(buf)) - out.size()}, ec + , udp_socket::tracker_connection); + } + m_state = action_t::announce; + sent_bytes(int(sizeof(buf)) - int(out.size()) + 28); // assuming UDP/IP header + ++m_attempts; + if (ec) + { + fail(ec); + return; + } + } + +} diff --git a/src/upnp.cpp b/src/upnp.cpp new file mode 100644 index 0000000..9849799 --- /dev/null +++ b/src/upnp.cpp @@ -0,0 +1,1666 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/upnp.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/time.hpp" // for aux::time_now() +#include "libtorrent/aux_/escape_string.hpp" // for convert_from_native +#include "libtorrent/http_connection.hpp" + +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif +#include "libtorrent/aux_/numeric_cast.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include +#ifdef TORRENT_USE_OPENSSL +#include +#endif +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include // for snprintf +#include +#include + +using namespace std::placeholders; + +namespace libtorrent { + +using namespace aux; + +// due to the recursive nature of update_map, it's necessary to +// limit the internal list of global mappings to a small size +// this can be changed once the entire UPnP code is refactored +constexpr std::size_t max_global_mappings = 50; + +namespace upnp_errors +{ + boost::system::error_code make_error_code(error_code_enum e) + { return {e, upnp_category()}; } + +} // upnp_errors namespace + +static error_code ignore_error; + +upnp::rootdevice::rootdevice() = default; +#if TORRENT_USE_ASSERTS +upnp::rootdevice::~rootdevice() +{ + TORRENT_ASSERT(magic == 1337); + magic = 0; +} +#else +upnp::rootdevice::~rootdevice() = default; +#endif + +upnp::rootdevice::rootdevice(rootdevice const&) = default; +upnp::rootdevice& upnp::rootdevice::operator=(rootdevice const&) = default; +upnp::rootdevice::rootdevice(rootdevice&&) = default; +upnp::rootdevice& upnp::rootdevice::operator=(rootdevice&&) = default; + +// TODO: 3 bind the broadcast socket. it would probably have to be changed to a vector of interfaces to +// bind to, since the broadcast socket opens one socket per local +// interface by default +upnp::upnp(io_service& ios + , aux::session_settings const& settings + , aux::portmap_callback& cb + , address_v4 const& listen_address + , address_v4 const& netmask + , std::string listen_device) + : m_settings(settings) + , m_callback(cb) + , m_io_service(ios) + , m_resolver(ios) + , m_multicast_socket(ios) + , m_unicast_socket(ios) + , m_broadcast_timer(ios) + , m_refresh_timer(ios) + , m_map_timer(ios) + , m_listen_address(listen_address) + , m_netmask(netmask) + , m_device(std::move(listen_device)) +#ifdef TORRENT_USE_OPENSSL + , m_ssl_ctx(ssl::context::sslv23_client) +#endif +{ +#ifdef TORRENT_USE_OPENSSL + m_ssl_ctx.set_verify_mode(ssl::context::verify_none); +#endif +} + +void upnp::start() +{ + TORRENT_ASSERT(is_single_thread()); + + error_code ec; + open_multicast_socket(m_multicast_socket, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + log("failed to open multicast socket: \"%s\"" + , convert_from_native(ec.message()).c_str()); + m_disabled = true; + return; + } +#endif + + open_unicast_socket(m_unicast_socket, ec); +#ifndef TORRENT_DISABLE_LOGGING + if (ec && should_log()) + { + log("failed to open unicast socket: \"%s\"" + , convert_from_native(ec.message()).c_str()); + m_disabled = true; + return; + } +#endif + + m_mappings.reserve(2); + + discover_device_impl(); +} + +namespace { + address_v4 const ssdp_multicast_addr = make_address_v4("239.255.255.250"); + int const ssdp_port = 1900; + +} + +void upnp::open_multicast_socket(udp::socket& s, error_code& ec) +{ + using namespace boost::asio::ip::multicast; + s.open(udp::v4(), ec); + if (ec) return; + s.set_option(udp::socket::reuse_address(true), ec); + if (ec) return; + s.bind(udp::endpoint(m_listen_address, ssdp_port), ec); + if (ec) return; + s.set_option(join_group(ssdp_multicast_addr), ec); + if (ec) return; + s.set_option(hops(255), ec); + if (ec) return; + s.set_option(enable_loopback(true), ec); + if (ec) return; + s.set_option(outbound_interface(m_listen_address), ec); + if (ec) return; + + ADD_OUTSTANDING_ASYNC("upnp::on_reply"); + s.async_receive(boost::asio::null_buffers{} + , std::bind(&upnp::on_reply, self(), std::ref(s), _1)); +} + +void upnp::open_unicast_socket(udp::socket& s, error_code& ec) +{ + s.open(udp::v4(), ec); + if (ec) return; + s.bind(udp::endpoint(m_listen_address, 0), ec); + if (ec) return; + + ADD_OUTSTANDING_ASYNC("upnp::on_reply"); + s.async_receive(boost::asio::null_buffers{} + , std::bind(&upnp::on_reply, self(), std::ref(s), _1)); +} + +upnp::~upnp() = default; + +#ifndef TORRENT_DISABLE_LOGGING +bool upnp::should_log() const +{ + return m_callback.should_log_portmap(portmap_transport::upnp); +} + +TORRENT_FORMAT(2,3) +void upnp::log(char const* fmt, ...) const +{ + TORRENT_ASSERT(is_single_thread()); + if (!should_log()) return; + va_list v; + va_start(v, fmt); + char msg[1024]; + std::vsnprintf(msg, sizeof(msg), fmt, v); + va_end(v); + m_callback.log_portmap(portmap_transport::upnp, msg); +} +#endif + +void upnp::discover_device_impl() +{ + TORRENT_ASSERT(is_single_thread()); + static const char msearch[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "ST:upnp:rootdevice\r\n" + "MAN:\"ssdp:discover\"\r\n" + "MX:3\r\n" + "\r\n\r\n"; + + error_code ec; +#ifdef TORRENT_DEBUG_UPNP + // simulate packet loss + if (m_retry_count & 1) +#endif + + error_code mcast_ec; + error_code ucast_ec; + m_multicast_socket.send_to(boost::asio::buffer(msearch, sizeof(msearch) - 1) + , udp::endpoint(ssdp_multicast_addr, ssdp_port), 0, mcast_ec); + m_unicast_socket.send_to(boost::asio::buffer(msearch, sizeof(msearch) - 1) + , udp::endpoint(ssdp_multicast_addr, ssdp_port), 0, ucast_ec); + + if (mcast_ec && ucast_ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("multicast send failed: \"%s\" and \"%s\". Aborting." + , convert_from_native(mcast_ec.message()).c_str() + , convert_from_native(ucast_ec.message()).c_str()); + } +#endif + disable(mcast_ec); + return; + } + + ADD_OUTSTANDING_ASYNC("upnp::resend_request"); + ++m_retry_count; + m_broadcast_timer.expires_from_now(seconds(2 * m_retry_count), ec); + m_broadcast_timer.async_wait(std::bind(&upnp::resend_request + , self(), _1)); + +#ifndef TORRENT_DISABLE_LOGGING + log("broadcasting search for rootdevice"); +#endif +} + +// returns a reference to a mapping or -1 on failure +port_mapping_t upnp::add_mapping(portmap_protocol const p, int const external_port + , tcp::endpoint const local_ep) +{ + TORRENT_ASSERT(is_single_thread()); + // external port 0 means _every_ port + TORRENT_ASSERT(external_port != 0); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("adding port map: [ protocol: %s ext_port: %d " + "local_ep: %s ] %s", (p == portmap_protocol::tcp?"tcp":"udp") + , external_port + , print_endpoint(local_ep).c_str(), m_disabled ? "DISABLED": ""); + } +#endif + if (m_disabled) return port_mapping_t{-1}; + + auto mapping_it = std::find_if(m_mappings.begin(), m_mappings.end() + , [](global_mapping_t const& m) { return m.protocol == portmap_protocol::none; }); + + if (mapping_it == m_mappings.end()) + { + TORRENT_ASSERT(m_mappings.size() <= max_global_mappings); + if (m_mappings.size() >= max_global_mappings) + { +#ifndef TORRENT_DISABLE_LOGGING + log("too many mappings registered"); +#endif + return port_mapping_t{-1}; + } + m_mappings.push_back(global_mapping_t()); + mapping_it = m_mappings.end() - 1; + } + + mapping_it->protocol = p; + mapping_it->external_port = external_port; + mapping_it->local_ep = local_ep; + + port_mapping_t const mapping_index{static_cast(mapping_it - m_mappings.begin())}; + + for (auto const& dev : m_devices) + { + rootdevice& d = const_cast(dev); + TORRENT_ASSERT(d.magic == 1337); + if (d.disabled) continue; + + if (d.mapping.end_index() <= mapping_index) + d.mapping.resize(static_cast(mapping_index) + 1); + mapping_t& m = d.mapping[mapping_index]; + + m.act = portmap_action::add; + m.protocol = p; + m.external_port = external_port; + m.local_ep = local_ep; + + if (!d.service_namespace.empty()) update_map(d, mapping_index); + } + + return port_mapping_t{mapping_index}; +} + +void upnp::delete_mapping(port_mapping_t const mapping) +{ + TORRENT_ASSERT(is_single_thread()); + + if (mapping >= m_mappings.end_index()) return; + + global_mapping_t const& m = m_mappings[mapping]; + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("deleting port map: [ protocol: %s ext_port: %u " + "local_ep: %s ]", (m.protocol == portmap_protocol::tcp?"tcp":"udp"), m.external_port + , print_endpoint(m.local_ep).c_str()); + } +#endif + + if (m.protocol == portmap_protocol::none) return; + + for (auto const& dev : m_devices) + { + rootdevice& d = const_cast(dev); + TORRENT_ASSERT(d.magic == 1337); + if (d.disabled) continue; + + TORRENT_ASSERT(mapping < d.mapping.end_index()); + d.mapping[mapping].act = portmap_action::del; + + if (!d.service_namespace.empty()) update_map(d, mapping); + } +} + +bool upnp::get_mapping(port_mapping_t const index + , tcp::endpoint& local_ep + , int& external_port + , portmap_protocol& protocol) const +{ + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(index < m_mappings.end_index() && index >= port_mapping_t{0}); + if (index >= m_mappings.end_index() || index < port_mapping_t{0}) return false; + global_mapping_t const& m = m_mappings[index]; + if (m.protocol == portmap_protocol::none) return false; + local_ep = m.local_ep; + external_port = m.external_port; + protocol = m.protocol; + return true; +} + +void upnp::resend_request(error_code const& ec) +{ + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("upnp::resend_request"); + if (ec) return; + + std::shared_ptr me(self()); + + if (m_closing) return; + + if (m_retry_count < 12 + && (m_devices.empty() || m_retry_count < 4)) + { + discover_device_impl(); + return; + } + + if (m_devices.empty()) + { + disable(errors::no_router); + return; + } + + for (auto const& dev : m_devices) + { + if (!dev.control_url.empty() + || dev.upnp_connection + || dev.disabled) + { + continue; + } + + // we don't have a WANIP or WANPPP url for this device, + // ask for it + connect(const_cast(dev)); + } +} + +void upnp::connect(rootdevice& d) +{ + TORRENT_ASSERT(d.magic == 1337); + TORRENT_TRY + { +#ifndef TORRENT_DISABLE_LOGGING + log("connecting to: %s", d.url.c_str()); +#endif + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection = std::make_shared(m_io_service + , m_resolver + , std::bind(&upnp::on_upnp_xml, self(), _1, _2 + , std::ref(d), _4), true, default_max_bottled_buffer_size + , http_connect_handler() + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , &m_ssl_ctx +#endif + ); + d.upnp_connection->get(d.url, seconds(30), 1); + } + TORRENT_CATCH (std::exception const& exc) + { + TORRENT_DECLARE_DUMMY(std::exception, exc); + TORRENT_UNUSED(exc); +#ifndef TORRENT_DISABLE_LOGGING + log("connection failed to: %s %s", d.url.c_str(), exc.what()); +#endif + d.disabled = true; + } +} + +void upnp::on_reply(udp::socket& s, error_code const& ec) +{ + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("upnp::on_reply"); + + if (ec == boost::asio::error::operation_aborted) return; + if (m_closing) return; + + std::shared_ptr me(self()); + + std::array buffer{}; + udp::endpoint from; + error_code err; + int const len = static_cast(s.receive_from(boost::asio::buffer(buffer) + , from, 0, err)); + + // parse out the url for the device + +/* + the response looks like this: + + HTTP/1.1 200 OK + ST:upnp:rootdevice + USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice + Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc + Server: Custom/1.0 UPnP/1.0 Proc/Ver + EXT: + Cache-Control:max-age=180 + DATE: Fri, 02 Jan 1970 08:10:38 GMT + + a notification looks like this: + + NOTIFY * HTTP/1.1 + Host:239.255.255.250:1900 + NT:urn:schemas-upnp-org:device:MediaServer:1 + NTS:ssdp:alive + Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e + USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1 + Cache-Control:max-age=900 + Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0 + +*/ + + ADD_OUTSTANDING_ASYNC("upnp::on_reply"); + s.async_receive(boost::asio::null_buffers{} + , std::bind(&upnp::on_reply, self(), std::ref(s), _1)); + + if (err) return; + + if (m_settings.get_bool(settings_pack::upnp_ignore_nonrouters) + && !match_addr_mask(m_listen_address, from.address(), m_netmask)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("ignoring response from: %s. IP is not on local network. (addr: %s mask: %s)" + , print_endpoint(from).c_str() + , m_listen_address.to_string().c_str() + , m_netmask.to_string().c_str()); + } +#endif + return; + } + + http_parser p; + bool error = false; + p.incoming({buffer.data(), len}, error); + if (error) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("received malformed HTTP from: %s" + , print_endpoint(from).c_str()); + } +#endif + return; + } + + if (p.status_code() != 200 && p.method() != "notify") + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + if (p.method().empty()) + { + log("HTTP status %u from %s" + , p.status_code(), print_endpoint(from).c_str()); + } + else + { + log("HTTP method %s from %s" + , p.method().c_str(), print_endpoint(from).c_str()); + } + } +#endif + return; + } + + if (!p.header_finished()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("incomplete HTTP packet from %s" + , print_endpoint(from).c_str()); + } +#endif + return; + } + + std::string url = p.header("location"); + if (url.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("missing location header from %s" + , print_endpoint(from).c_str()); + } +#endif + return; + } + + rootdevice d; + d.url = url; + + auto i = m_devices.find(d); + + if (i == m_devices.end()) + { + std::string protocol; + std::string auth; + // we don't have this device in our list. Add it + std::tie(protocol, auth, d.hostname, d.port, d.path) + = parse_url_components(d.url, err); + if (d.port == -1) d.port = protocol == "http" ? 80 : 443; + + if (err) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("invalid URL %s from %s: %s" + , d.url.c_str(), print_endpoint(from).c_str(), convert_from_native(err.message()).c_str()); + } +#endif + return; + } + + // ignore the auth here. It will be re-parsed + // by the http connection later + + if (protocol != "http") + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("unsupported protocol %s from %s" + , protocol.c_str(), print_endpoint(from).c_str()); + } +#endif + return; + } + + if (d.port == 0) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("URL with port 0 from %s", print_endpoint(from).c_str()); + } +#endif + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("found rootdevice: %s (%d)" + , d.url.c_str(), int(m_devices.size())); + } +#endif + + if (m_devices.size() >= 50) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("too many rootdevices: (%d). Ignoring %s" + , int(m_devices.size()), d.url.c_str()); + } +#endif + return; + } + + TORRENT_ASSERT(d.mapping.empty()); + for (auto const& j : m_mappings) + { + mapping_t m; + m.act = portmap_action::add; + m.local_ep = j.local_ep; + m.external_port = j.external_port; + m.protocol = j.protocol; + d.mapping.push_back(m); + } + std::tie(i, std::ignore) = m_devices.insert(d); + } + + + // iterate over the devices we know and connect and issue the mappings + try_map_upnp(); + + // check back in a little bit to see if we have seen any + // devices at one of our default routes. If not, we want to override + // ignoring them and use them instead (better than not working). + m_map_timer.expires_from_now(seconds(1), err); + ADD_OUTSTANDING_ASYNC("upnp::map_timer"); + m_map_timer.async_wait(std::bind(&upnp::map_timer, self(), _1)); +} + +void upnp::map_timer(error_code const& ec) +{ + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("upnp::map_timer"); + if (ec) return; + if (m_closing) return; + + try_map_upnp(); +} + +void upnp::try_map_upnp() +{ + TORRENT_ASSERT(is_single_thread()); + if (m_devices.empty()) return; + + for (auto i = m_devices.begin(), end(m_devices.end()); i != end; ++i) + { + if (i->control_url.empty() && !i->upnp_connection && !i->disabled) + { + // we don't have a WANIP or WANPPP url for this device, + // ask for it + connect(const_cast(*i)); + } + } +} + +void upnp::post(upnp::rootdevice const& d, char const* soap + , char const* soap_action) +{ + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(d.magic == 1337); + TORRENT_ASSERT(d.upnp_connection); + TORRENT_ASSERT(!d.disabled); + + char header[2048]; + std::snprintf(header, sizeof(header), "POST %s HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "Content-Type: text/xml; charset=\"utf-8\"\r\n" + "Content-Length: %d\r\n" + "Soapaction: \"%s#%s\"\r\n\r\n" + "%s" + , d.path.c_str(), d.hostname.c_str(), d.port + , int(strlen(soap)), d.service_namespace.c_str(), soap_action + , soap); + + d.upnp_connection->m_sendbuffer = header; + +#ifndef TORRENT_DISABLE_LOGGING + log("sending: %s", header); +#endif +} + +int upnp::lease_duration(rootdevice const& d) const +{ + return d.use_lease_duration + ? m_settings.get_int(settings_pack::upnp_lease_duration) + : 0; +} + +void upnp::create_port_mapping(http_connection& c, rootdevice& d + , port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(d.magic == 1337); + + if (!d.upnp_connection) + { + TORRENT_ASSERT(d.disabled); +#ifndef TORRENT_DISABLE_LOGGING + log("mapping %u aborted", static_cast(i)); +#endif + return; + } + + char const* soap_action = "AddPortMapping"; + + error_code ec; + std::string local_endpoint = print_address(c.socket().local_endpoint(ec).address()); + + char soap[1024]; + std::snprintf(soap, sizeof(soap), "\n" + "" + "" + "" + "%u" + "%s" + "%u" + "%s" + "1" + "%s" + "%d" + "" + , soap_action, d.service_namespace.c_str(), d.mapping[i].external_port + , to_string(d.mapping[i].protocol) + , d.mapping[i].local_ep.port() + , local_endpoint.c_str() + , m_settings.get_bool(settings_pack::anonymous_mode) + ? "" : m_settings.get_str(settings_pack::user_agent).c_str() + , lease_duration(d), soap_action); + + post(d, soap, soap_action); +} + +void upnp::next(rootdevice& d, port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(!d.disabled); + if (i < prev(m_mappings.end_index())) + { + update_map(d, lt::next(i)); + } + else + { + auto const j = std::find_if(d.mapping.begin(), d.mapping.end() + , [](mapping_t const& m) { return m.act != portmap_action::none; }); + if (j == d.mapping.end()) return; + + update_map(d, port_mapping_t{static_cast(j - d.mapping.begin())}); + } +} + +void upnp::update_map(rootdevice& d, port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(d.magic == 1337); + TORRENT_ASSERT(i < d.mapping.end_index()); + TORRENT_ASSERT(d.mapping.size() == m_mappings.size()); + TORRENT_ASSERT(!d.disabled); + + if (d.upnp_connection) return; + + // this should not happen, but in case it does, don't fail at runtime + if (i >= d.mapping.end_index()) return; + + std::shared_ptr me(self()); + + mapping_t& m = d.mapping[i]; + + if (m.act == portmap_action::none + || m.protocol == portmap_protocol::none) + { +#ifndef TORRENT_DISABLE_LOGGING + log("mapping %u does not need updating, skipping", static_cast(i)); +#endif + m.act = portmap_action::none; + next(d, i); + return; + } + + TORRENT_ASSERT(!d.upnp_connection); + TORRENT_ASSERT(!d.service_namespace.empty()); + +#ifndef TORRENT_DISABLE_LOGGING + log("connecting to %s", d.hostname.c_str()); +#endif + if (m.act == portmap_action::add) + { + if (m.failcount > 5) + { + m.act = portmap_action::none; + // giving up + next(d, i); + return; + } + + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection = std::make_shared(m_io_service + , m_resolver + , std::bind(&upnp::on_upnp_map_response, self(), _1, _2 + , std::ref(d), i, _4), true, default_max_bottled_buffer_size + , std::bind(&upnp::create_port_mapping, self(), _1, std::ref(d), i) + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , &m_ssl_ctx +#endif + ); + + d.upnp_connection->start(d.hostname, d.port + , seconds(10), 1, nullptr, false, 5, m.local_ep.address()); + } + else if (m.act == portmap_action::del) + { + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection = std::make_shared(m_io_service + , m_resolver + , std::bind(&upnp::on_upnp_unmap_response, self(), _1, _2 + , std::ref(d), i, _4), true, default_max_bottled_buffer_size + , std::bind(&upnp::delete_port_mapping, self(), std::ref(d), i) + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , &m_ssl_ctx +#endif + ); + d.upnp_connection->start(d.hostname, d.port + , seconds(10), 1, nullptr, false, 5, m.local_ep.address()); + } + + m.act = portmap_action::none; + m.expires = aux::time_now() + seconds(30); +} + +void upnp::delete_port_mapping(rootdevice& d, port_mapping_t const i) +{ + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(d.magic == 1337); + + if (!d.upnp_connection) + { + TORRENT_ASSERT(d.disabled); +#ifndef TORRENT_DISABLE_LOGGING + log("unmapping %u aborted", static_cast(i)); +#endif + return; + } + + char const* soap_action = "DeletePortMapping"; + + char soap[1024]; + std::snprintf(soap, sizeof(soap), "\n" + "" + "" + "" + "%u" + "%s" + "" + , soap_action, d.service_namespace.c_str() + , d.mapping[i].external_port + , to_string(d.mapping[i].protocol) + , soap_action); + + post(d, soap, soap_action); +} + +void find_control_url(int const type, string_view str, parse_state& state) +{ + if (type == xml_start_tag) + { + state.tag_stack.push_back(str); + } + else if (type == xml_end_tag) + { + if (!state.tag_stack.empty()) + { + if (state.in_service && string_equal_no_case(state.tag_stack.back(), "service")) + state.in_service = false; + state.tag_stack.pop_back(); + } + } + else if (type == xml_string) + { + if (state.tag_stack.empty()) return; + if (!state.in_service && state.top_tags("service", "servicetype")) + { + if (string_equal_no_case(str, "urn:schemas-upnp-org:service:WANIPConnection:1") + || string_equal_no_case(str, "urn:schemas-upnp-org:service:WANIPConnection:2") + || string_equal_no_case(str, "urn:schemas-upnp-org:service:WANPPPConnection:1")) + { + state.service_type.assign(str.begin(), str.end()); + state.in_service = true; + } + } + else if (state.control_url.empty() && state.in_service + && state.top_tags("service", "controlurl") && str.size() > 0) + { + // default to the first (or only) control url in the router's listing + state.control_url.assign(str.begin(), str.end()); + } + else if (state.model.empty() && state.top_tags("device", "modelname")) + { + state.model.assign(str.begin(), str.end()); + } + else if (string_equal_no_case(state.tag_stack.back(), "urlbase")) + { + state.url_base.assign(str.begin(), str.end()); + } + } +} + +void upnp::on_upnp_xml(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c) +{ + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr me(self()); + + TORRENT_ASSERT(d.magic == 1337); + if (d.upnp_connection && d.upnp_connection.get() == &c) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (m_closing) return; + + if (e && e != boost::asio::error::eof) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while fetching control url from: %s: %s" + , d.url.c_str(), convert_from_native(e.message()).c_str()); + } +#endif + d.disabled = true; + return; + } + + if (!p.header_finished()) + { +#ifndef TORRENT_DISABLE_LOGGING + log("error while fetching control url from: %s: incomplete HTTP message" + , d.url.c_str()); +#endif + d.disabled = true; + return; + } + + if (p.status_code() != 200) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while fetching control url from: %s: %s" + , d.url.c_str(), convert_from_native(p.message()).c_str()); + } +#endif + d.disabled = true; + return; + } + + parse_state s; + auto body = p.get_body(); + xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_control_url, _1, _2, std::ref(s))); + if (s.control_url.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + log("could not find a port mapping interface in response from: %s" + , d.url.c_str()); +#endif + d.disabled = true; + return; + } + d.service_namespace = s.service_type; + + TORRENT_ASSERT(!d.service_namespace.empty()); + + if (!s.model.empty()) m_model = s.model; + + if (!s.url_base.empty() && s.control_url.substr(0, 7) != "http://") + { + // avoid double slashes in path + if (s.url_base[s.url_base.size()-1] == '/' + && !s.control_url.empty() + && s.control_url[0] == '/') + s.url_base.erase(s.url_base.end()-1); + d.control_url = s.url_base + s.control_url; + } + else d.control_url = s.control_url; + + std::string protocol; + std::string auth; + error_code ec; + if (!d.control_url.empty() && d.control_url[0] == '/') + { + std::tie(protocol, auth, d.hostname, d.port, d.path) + = parse_url_components(d.url, ec); + if (d.port == -1) d.port = protocol == "http" ? 80 : 443; + d.control_url = protocol + "://" + d.hostname + ":" + + to_string(d.port).data() + s.control_url; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("found control URL: %s namespace %s " + "urlbase: %s in response from %s" + , d.control_url.c_str(), d.service_namespace.c_str() + , s.url_base.c_str(), d.url.c_str()); + } +#endif + + std::tie(protocol, auth, d.hostname, d.port, d.path) + = parse_url_components(d.control_url, ec); + if (d.port == -1) d.port = protocol == "http" ? 80 : 443; + + if (ec) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("failed to parse URL '%s': %s" + , d.control_url.c_str(), convert_from_native(ec.message()).c_str()); + } +#endif + d.disabled = true; + return; + } + + if (d.upnp_connection) d.upnp_connection->close(); + d.upnp_connection = std::make_shared(m_io_service + , m_resolver + , std::bind(&upnp::on_upnp_get_ip_address_response, self(), _1, _2 + , std::ref(d), _4), true, default_max_bottled_buffer_size + , std::bind(&upnp::get_ip_address, self(), std::ref(d)) + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , &m_ssl_ctx +#endif + ); + d.upnp_connection->start(d.hostname, d.port + , seconds(10), 1); +} + +void upnp::get_ip_address(rootdevice& d) +{ + TORRENT_ASSERT(is_single_thread()); + + TORRENT_ASSERT(d.magic == 1337); + + if (!d.upnp_connection) + { + TORRENT_ASSERT(d.disabled); +#ifndef TORRENT_DISABLE_LOGGING + log("getting external IP address"); +#endif + return; + } + + char const* soap_action = "GetExternalIPAddress"; + + char soap[1024]; + std::snprintf(soap, sizeof(soap), "\n" + "" + "" + "" + , soap_action, d.service_namespace.c_str() + , soap_action); + + post(d, soap, soap_action); +} + +void upnp::disable(error_code const& ec) +{ + TORRENT_ASSERT(is_single_thread()); + m_disabled = true; + + // kill all mappings + for (auto i = m_mappings.begin(), end(m_mappings.end()); i != end; ++i) + { + if (i->protocol == portmap_protocol::none) continue; + portmap_protocol const proto = i->protocol; + i->protocol = portmap_protocol::none; + m_callback.on_port_mapping(port_mapping_t(static_cast(i - m_mappings.begin())), address(), 0, proto, ec + , portmap_transport::upnp); + } + + // we cannot clear the devices since there + // might be outstanding requests relying on + // the device entry being present when they + // complete + error_code e; + m_broadcast_timer.cancel(e); + m_refresh_timer.cancel(e); + m_map_timer.cancel(e); + m_unicast_socket.close(e); + m_multicast_socket.close(e); +} + +void find_error_code(int const type, string_view string, error_code_parse_state& state) +{ + if (state.exit) return; + if (type == xml_start_tag && string == "errorCode") + { + state.in_error_code = true; + } + else if (type == xml_string && state.in_error_code) + { + state.error_code = std::atoi(string.to_string().c_str()); + state.exit = true; + } +} + +void find_ip_address(int const type, string_view string, ip_address_parse_state& state) +{ + find_error_code(type, string, state); + if (state.exit) return; + + if (type == xml_start_tag && string == "NewExternalIPAddress") + { + state.in_ip_address = true; + } + else if (type == xml_string && state.in_ip_address) + { + state.ip_address.assign(string.begin(), string.end()); + state.exit = true; + } +} + +namespace { + + struct error_code_t + { + int code; + char const* msg; + }; + + error_code_t error_codes[] = + { + {0, "no error"} + , {402, "Invalid Arguments"} + , {501, "Action Failed"} + , {714, "The specified value does not exist in the array"} + , {715, "The source IP address cannot be wild-carded"} + , {716, "The external port cannot be wild-carded"} + , {718, "The port mapping entry specified conflicts with " + "a mapping assigned previously to another client"} + , {724, "Internal and External port values must be the same"} + , {725, "The NAT implementation only supports permanent " + "lease times on port mappings"} + , {726, "RemoteHost must be a wildcard and cannot be a " + "specific IP address or DNS name"} + , {727, "ExternalPort must be a wildcard and cannot be a specific port "} + }; + +} + +struct upnp_error_category final : boost::system::error_category +{ + const char* name() const BOOST_SYSTEM_NOEXCEPT override + { + return "upnp"; + } + + std::string message(int ev) const override + { + int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); + error_code_t* end = error_codes + num_errors; + error_code_t tmp = {ev, nullptr}; + error_code_t* e = std::lower_bound(error_codes, end, tmp + , [] (error_code_t const& lhs, error_code_t const& rhs) + { return lhs.code < rhs.code; }); + if (e != end && e->code == ev) + { + return e->msg; + } + char msg[500]; + std::snprintf(msg, sizeof(msg), "unknown UPnP error (%d)", ev); + return msg; + } + + boost::system::error_condition default_error_condition( + int ev) const BOOST_SYSTEM_NOEXCEPT override + { + return {ev, *this}; + } +}; + +boost::system::error_category& upnp_category() +{ + static upnp_error_category cat; + return cat; +} + +void upnp::on_upnp_get_ip_address_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , http_connection& c) +{ + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr me(self()); + + TORRENT_ASSERT(d.magic == 1337); + if (d.upnp_connection && d.upnp_connection.get() == &c) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (m_closing) return; + + if (e && e != boost::asio::error::eof) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while getting external IP address: %s" + , convert_from_native(e.message()).c_str()); + } +#endif + if (num_mappings() > 0) update_map(d, port_mapping_t{0}); + return; + } + + if (!p.header_finished()) + { +#ifndef TORRENT_DISABLE_LOGGING + log("error while getting external IP address: incomplete http message"); +#endif + if (num_mappings() > 0) update_map(d, port_mapping_t{0}); + return; + } + + if (p.status_code() != 200) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while getting external IP address: %s" + , convert_from_native(p.message()).c_str()); + } +#endif + if (num_mappings() > 0) update_map(d, port_mapping_t{0}); + return; + } + + // response may look like + // + // + // + // 192.168.160.19 + // + // + // + + span body = p.get_body(); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("get external IP address response: %s" + , std::string(body.data(), static_cast(body.size())).c_str()); + } +#endif + + ip_address_parse_state s; + xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_ip_address, _1, _2, std::ref(s))); +#ifndef TORRENT_DISABLE_LOGGING + if (s.error_code != -1) + { + log("error while getting external IP address, code: %d", s.error_code); + } +#endif + + if (!s.ip_address.empty()) + { +#ifndef TORRENT_DISABLE_LOGGING + log("got router external IP address %s", s.ip_address.c_str()); +#endif + d.external_ip = make_address(s.ip_address.c_str(), ignore_error); + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + log("failed to find external IP address in response"); +#endif + } + + if (num_mappings() > 0) update_map(d, port_mapping_t{0}); +} + +void upnp::on_upnp_map_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d, port_mapping_t const mapping + , http_connection& c) +{ + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr me(self()); + + TORRENT_ASSERT(d.magic == 1337); + if (d.upnp_connection && d.upnp_connection.get() == &c) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (e && e != boost::asio::error::eof) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while adding port map: %s" + , convert_from_native(e.message()).c_str()); + } +#endif + d.disabled = true; + return; + } + + if (m_closing) return; + +// error code response may look like this: +// +// +// +// s:Client +// UPnPError +// +// +// 402 +// Invalid Args +// +// +// +// +// + + if (!p.header_finished()) + { +#ifndef TORRENT_DISABLE_LOGGING + log("error while adding port map: incomplete http message"); +#endif + next(d, mapping); + return; + } + + std::string const& ct = p.header("content-type"); + if (!ct.empty() + && ct.find_first_of("text/xml") == std::string::npos + && ct.find_first_of("text/soap+xml") == std::string::npos + && ct.find_first_of("application/xml") == std::string::npos + && ct.find_first_of("application/soap+xml") == std::string::npos + ) + { +#ifndef TORRENT_DISABLE_LOGGING + log("error while adding port map: invalid content-type, \"%s\". " + "Expected text/xml or application/soap+xml", ct.c_str()); +#endif + next(d, mapping); + return; + } + + // We don't want to ignore responses with return codes other than 200 + // since those might contain valid UPnP error codes + + error_code_parse_state s; + span body = p.get_body(); + xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_error_code, _1, _2, std::ref(s))); + + if (s.error_code != -1) + { +#ifndef TORRENT_DISABLE_LOGGING + log("error while adding port map, code: %d", s.error_code); +#endif + } + + mapping_t& m = d.mapping[mapping]; + + if (s.error_code == 725) + { + // The gateway only supports permanent leases + d.use_lease_duration = false; + m.act = portmap_action::add; + ++m.failcount; + update_map(d, mapping); + return; + } + else if (s.error_code == 727) + { + return_error(mapping, s.error_code); + } + else if ((s.error_code == 718 || s.error_code == 501) && m.failcount < 4) + { + // some routers return 501 action failed, instead of 716 + // The external port conflicts with another mapping + // pick a random port + m.external_port = 40000 + int(random(10000)); + m.act = portmap_action::add; + ++m.failcount; + update_map(d, mapping); + return; + } + else if (s.error_code != -1) + { + return_error(mapping, s.error_code); + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("map response: %s" + , std::string(body.data(), static_cast(body.size())).c_str()); + } +#endif + + if (s.error_code == -1) + { + m_callback.on_port_mapping(mapping, d.external_ip, m.external_port, m.protocol, error_code() + , portmap_transport::upnp); + if (d.use_lease_duration && m_settings.get_int(settings_pack::upnp_lease_duration) != 0) + { + time_point const now = aux::time_now(); + m.expires = now + seconds( + m_settings.get_int(settings_pack::upnp_lease_duration) * 3 / 4); + + time_point next_expire = m_refresh_timer.expires_at(); + if (next_expire < now || next_expire > m.expires) + { + ADD_OUTSTANDING_ASYNC("upnp::on_expire"); + error_code ec; + m_refresh_timer.expires_at(m.expires, ec); + m_refresh_timer.async_wait(std::bind(&upnp::on_expire, self(), _1)); + } + } + else + { + m.expires = max_time(); + } + m.failcount = 0; + } + + next(d, mapping); +} + +void upnp::return_error(port_mapping_t const mapping, int const code) +{ + TORRENT_ASSERT(is_single_thread()); + int num_errors = sizeof(error_codes) / sizeof(error_codes[0]); + error_code_t* end = error_codes + num_errors; + error_code_t tmp = {code, nullptr}; + error_code_t* e = std::lower_bound(error_codes, end, tmp + , [] (error_code_t const& lhs, error_code_t const& rhs) + { return lhs.code < rhs.code; }); + + std::string error_string = "UPnP mapping error "; + error_string += to_string(code).data(); + if (e != end && e->code == code) + { + error_string += ": "; + error_string += e->msg; + } + portmap_protocol const proto = m_mappings[mapping].protocol; + m_callback.on_port_mapping(mapping, address(), 0, proto, error_code(code, upnp_category()) + , portmap_transport::upnp); +} + +void upnp::on_upnp_unmap_response(error_code const& e + , libtorrent::http_parser const& p, rootdevice& d + , port_mapping_t const mapping + , http_connection& c) +{ + TORRENT_ASSERT(is_single_thread()); + std::shared_ptr me(self()); + + TORRENT_ASSERT(d.magic == 1337); + if (d.upnp_connection && d.upnp_connection.get() == &c) + { + d.upnp_connection->close(); + d.upnp_connection.reset(); + } + + if (e && e != boost::asio::error::eof) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while deleting portmap: %s" + , convert_from_native(e.message()).c_str()); + } +#endif + } + else if (!p.header_finished()) + { +#ifndef TORRENT_DISABLE_LOGGING + log("error while deleting portmap: incomplete http message"); +#endif + } + else if (p.status_code() != 200) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + log("error while deleting portmap: %s" + , convert_from_native(p.message()).c_str()); + } +#endif + } + else + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log()) + { + span body = p.get_body(); + log("unmap response: %s" + , std::string(body.data(), static_cast(body.size())).c_str()); + } +#endif + } + + error_code_parse_state s; + if (p.header_finished()) + { + span body = p.get_body(); + xml_parse({body.data(), std::size_t(body.size())}, std::bind(&find_error_code, _1, _2, std::ref(s))); + } + + portmap_protocol const proto = m_mappings[mapping].protocol; + + m_callback.on_port_mapping(mapping, address(), 0, proto, p.status_code() != 200 + ? error_code(p.status_code(), http_category()) + : error_code(s.error_code, upnp_category()) + , portmap_transport::upnp); + + d.mapping[mapping].protocol = portmap_protocol::none; + + // free the slot in global mappings + auto pred = [mapping](rootdevice const& rd) + { return rd.mapping.end_index() <= mapping || rd.mapping[mapping].protocol == portmap_protocol::none; }; + if (std::all_of(m_devices.begin(), m_devices.end(), pred)) + { + m_mappings[mapping].protocol = portmap_protocol::none; + } + + next(d, mapping); +} + +void upnp::on_expire(error_code const& ec) +{ + TORRENT_ASSERT(is_single_thread()); + COMPLETE_ASYNC("upnp::on_expire"); + if (ec) return; + + if (m_closing) return; + + time_point const now = aux::time_now(); + time_point next_expire = max_time(); + + for (auto& dev : m_devices) + { + rootdevice& d = const_cast(dev); + TORRENT_ASSERT(d.magic == 1337); + if (d.disabled) continue; + for (port_mapping_t m{0}; m < m_mappings.end_index(); ++m) + { + if (d.mapping[m].expires == max_time()) + continue; + + if (d.mapping[m].expires <= now) + { + d.mapping[m].act = portmap_action::add; + update_map(d, m); + } + if (d.mapping[m].expires < next_expire) + { + next_expire = d.mapping[m].expires; + } + } + } + if (next_expire != max_time()) + { + ADD_OUTSTANDING_ASYNC("upnp::on_expire"); + error_code e; + m_refresh_timer.expires_at(next_expire, e); + m_refresh_timer.async_wait(std::bind(&upnp::on_expire, self(), _1)); + } +} + +void upnp::close() +{ + TORRENT_ASSERT(is_single_thread()); + + error_code ec; + m_refresh_timer.cancel(ec); + m_broadcast_timer.cancel(ec); + m_map_timer.cancel(ec); + m_closing = true; + m_unicast_socket.close(ec); + m_multicast_socket.close(ec); + + for (auto& dev : m_devices) + { + rootdevice& d = const_cast(dev); + TORRENT_ASSERT(d.magic == 1337); + if (d.disabled || d.control_url.empty()) continue; + for (auto& m : d.mapping) + { + if (m.protocol == portmap_protocol::none) continue; + if (m.act == portmap_action::add) + { + m.act = portmap_action::none; + continue; + } + m.act = portmap_action::del; + m_mappings[port_mapping_t{static_cast(&m - d.mapping.data())}].protocol = portmap_protocol::none; + } + if (num_mappings() > 0) update_map(d, port_mapping_t{0}); + } +} + +} diff --git a/src/ut_metadata.cpp b/src/ut_metadata.cpp new file mode 100644 index 0000000..4a55181 --- /dev/null +++ b/src/ut_metadata.cpp @@ -0,0 +1,645 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include +#include +#include +#include +#include + +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/ut_metadata.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/aux_/time.hpp" + +#if TORRENT_USE_ASSERTS +#include "libtorrent/hasher.hpp" +#endif + +namespace libtorrent {namespace { + + enum + { + // this is the max number of bytes we'll + // queue up in the send buffer. If we exceed this, + // we'll wait another second before checking + // the send buffer size again. So, this may limit + // the rate at which we can server metadata to + // 160 kiB/s + send_buffer_limit = 0x4000 * 10, + + // this is the max number of requests we'll queue + // up. If we get more requests tha this, we'll + // start rejecting them, claiming we don't have + // metadata. If the torrent is greater than 16 MiB, + // we may hit this case (and the client requesting + // doesn't throttle its requests) + max_incoming_requests = 1024, + + metadata_req = 0, + metadata_piece = 1, + metadata_dont_have = 2 + }; + + int div_round_up(int numerator, int denominator) + { + return (numerator + denominator - 1) / denominator; + } + + struct ut_metadata_peer_plugin; + + struct ut_metadata_plugin final + : torrent_plugin + { + explicit ut_metadata_plugin(torrent& t) : m_torrent(t) + { + // initialize m_metadata_size + if (m_torrent.valid_metadata()) + metadata(); + } + + void on_files_checked() override + { + // TODO: 2 if we were to initialize m_metadata_size lazily instead, + // we would probably be more efficient + // initialize m_metadata_size + metadata(); + } + + std::shared_ptr new_connection( + peer_connection_handle const& pc) override; + + int get_metadata_size() const + { + TORRENT_ASSERT(m_metadata_size > 0); + return m_metadata_size; + } + + span metadata() const + { + TORRENT_ASSERT(m_torrent.valid_metadata()); + if (!m_metadata) + { + m_metadata = m_torrent.torrent_file().metadata(); + m_metadata_size = m_torrent.torrent_file().metadata_size(); + TORRENT_ASSERT(hasher(m_metadata.get(), m_metadata_size).final() + == m_torrent.torrent_file().info_hash()); + } + return {m_metadata.get(), m_metadata_size}; + } + + bool received_metadata(ut_metadata_peer_plugin& source + , span buf, int piece, int total_size); + + // returns a piece of the metadata that + // we should request. + // returns -1 if we should hold off the request + int metadata_request(bool has_metadata); + + void on_piece_pass(piece_index_t) override + { + // if we became a seed, copy the metadata from + // the torrent before it is deallocated + if (m_torrent.is_seed()) + metadata(); + } + + void metadata_size(int const size) + { + if (m_metadata_size > 0 || size <= 0 || size > 4 * 1024 * 1024) return; + m_metadata_size = size; + m_metadata.reset(new char[std::size_t(size)]); + m_requested_metadata.resize(div_round_up(size, 16 * 1024)); + } + + // explicitly disallow assignment, to silence msvc warning + ut_metadata_plugin& operator=(ut_metadata_plugin const&) = delete; + + private: + torrent& m_torrent; + + // this buffer is filled with the info-section of + // the metadata file while downloading it from + // peers, and while sending it. + // it is mutable because it's generated lazily + mutable boost::shared_array m_metadata; + + mutable int m_metadata_size = 0; + + struct metadata_piece + { + metadata_piece(): num_requests(0), last_request(min_time()) {} + int num_requests; + time_point last_request; + std::weak_ptr source; + bool operator<(metadata_piece const& rhs) const + { return num_requests < rhs.num_requests; } + }; + + // this vector keeps track of how many times each metadata + // block has been requested and who we ended up getting it from + // std::numeric_limits::max() means we have the piece + aux::vector m_requested_metadata; + }; + + + struct ut_metadata_peer_plugin final + : peer_plugin, std::enable_shared_from_this + { + friend struct ut_metadata_plugin; + + ut_metadata_peer_plugin(torrent& t, bt_peer_connection& pc + , ut_metadata_plugin& tp) + : m_message_index(0) + , m_request_limit(min_time()) + , m_torrent(t) + , m_pc(pc) + , m_tp(tp) + {} + + // can add entries to the extension handshake + void add_handshake(entry& h) override + { + entry& messages = h["m"]; + messages["ut_metadata"] = 2; + if (m_torrent.valid_metadata()) + h["metadata_size"] = m_tp.get_metadata_size(); + } + + // called when the extension handshake from the other end is received + bool on_extension_handshake(bdecode_node const& h) override + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node const messages = h.dict_find_dict("m"); + if (!messages) return false; + + int index = int(messages.dict_find_int_value("ut_metadata", -1)); + if (index == -1) return false; + m_message_index = index; + + int metadata_size = int(h.dict_find_int_value("metadata_size")); + if (metadata_size > 0) + m_tp.metadata_size(metadata_size); + else + m_pc.set_has_metadata(false); + + maybe_send_request(); + return true; + } + + void write_metadata_packet(int const type, int const piece) + { + TORRENT_ASSERT(type >= 0 && type <= 2); + TORRENT_ASSERT(!m_pc.associated_torrent().expired()); + +#ifndef TORRENT_DISABLE_LOGGING + static char const* names[] = {"request", "data", "dont-have"}; + char const* n = ""; + if (type >= 0 && type < 3) n = names[type]; + m_pc.peer_log(peer_log_alert::outgoing_message, "UT_METADATA" + , "type: %d (%s) piece: %d", type, n, piece); +#endif + + // abort if the peer doesn't support the metadata extension + if (m_message_index == 0) return; + + entry e; + e["msg_type"] = type; + e["piece"] = piece; + + char const* metadata = nullptr; + int metadata_piece_size = 0; + + if (m_torrent.valid_metadata()) + e["total_size"] = m_tp.get_metadata_size(); + + if (type == 1) + { + TORRENT_ASSERT(piece >= 0 && piece < (m_tp.get_metadata_size() + 16 * 1024 - 1) / (16 * 1024)); + TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(m_torrent.valid_metadata()); + + int const offset = piece * 16 * 1024; + metadata = m_tp.metadata().data() + offset; + metadata_piece_size = std::min( + m_tp.get_metadata_size() - offset, 16 * 1024); + TORRENT_ASSERT(metadata_piece_size > 0); + TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(offset + metadata_piece_size <= m_tp.get_metadata_size()); + } + + // TODO: 3 use the aux::write_* functions and the span here instead, it + // will fit better with send_buffer() + char msg[200]; + char* header = msg; + char* p = &msg[6]; + int const len = bencode(p, e); + int const total_size = 2 + len + metadata_piece_size; + namespace io = detail; + io::write_uint32(total_size, header); + io::write_uint8(bt_peer_connection::msg_extended, header); + io::write_uint8(m_message_index, header); + + m_pc.send_buffer({msg, len + 6}); + // TODO: we really need to increment the refcounter on the torrent + // while this buffer is still in the peer's send buffer + if (metadata_piece_size) + { + m_pc.append_const_send_buffer( + span(const_cast(metadata), metadata_piece_size), metadata_piece_size); + } + + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended); + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_metadata); + } + + bool on_extended(int const length + , int const extended_msg, span body) override + { + if (extended_msg != 2) return false; + if (m_message_index == 0) return false; + + if (length > 17 * 1024) + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" + , "packet too big %d", length); +#endif + m_pc.disconnect(errors::invalid_metadata_message, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + + if (!m_pc.packet_finished()) return true; + + error_code ec; + bdecode_node msg = bdecode(body, ec); + if (msg.type() != bdecode_node::dict_t) + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" + , "not a dictionary"); +#endif + m_pc.disconnect(errors::invalid_metadata_message, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + + bdecode_node const& type_ent = msg.dict_find_int("msg_type"); + bdecode_node const& piece_ent = msg.dict_find_int("piece"); + if (!type_ent || !piece_ent) + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" + , "missing or invalid keys"); +#endif + m_pc.disconnect(errors::invalid_metadata_message, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + // TODO: make this an enum class + auto const type = static_cast(type_ent.int_value()); + auto const piece = static_cast(piece_ent.int_value()); + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" + , "type: %d piece: %d", type, piece); +#endif + + switch (type) + { + case metadata_req: + { + if (!m_torrent.valid_metadata() + || piece < 0 || piece >= (m_tp.get_metadata_size() + 16 * 1024 - 1) / (16 * 1024)) + { +#ifndef TORRENT_DISABLE_LOGGING + if (m_pc.should_log(peer_log_alert::info)) + { + m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "have: %d invalid piece %d metadata size: %d" + , int(m_torrent.valid_metadata()), piece + , m_torrent.valid_metadata() + ? m_tp.get_metadata_size() : 0); + } +#endif + write_metadata_packet(metadata_dont_have, piece); + return true; + } + if (m_pc.send_buffer_size() < send_buffer_limit) + write_metadata_packet(metadata_piece, piece); + else if (m_incoming_requests.size() < max_incoming_requests) + m_incoming_requests.push_back(piece); + else + write_metadata_packet(metadata_dont_have, piece); + } + break; + case metadata_piece: + { + auto const i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + + // unwanted piece? + if (i == m_sent_requests.end()) + { +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "UNWANTED / TIMED OUT"); +#endif + return true; + } + + m_sent_requests.erase(i); + auto const len = msg.data_section().size(); + auto const total_size = msg.dict_find_int_value("total_size", 0); + m_tp.received_metadata(*this, body.subspan(len), piece, static_cast(total_size)); + maybe_send_request(); + } + break; + case metadata_dont_have: + { + m_request_limit = std::max(aux::time_now() + minutes(1), m_request_limit); + auto const i = std::find(m_sent_requests.begin() + , m_sent_requests.end(), piece); + // unwanted piece? + if (i == m_sent_requests.end()) return true; + m_sent_requests.erase(i); + } + break; + default: + // unknown message, ignore + break; + } + + m_pc.stats_counters().inc_stats_counter(counters::num_incoming_metadata); + + return true; + } + + void tick() override + { + maybe_send_request(); + while (!m_incoming_requests.empty() + && m_pc.send_buffer_size() < send_buffer_limit) + { + int const piece = m_incoming_requests.front(); + m_incoming_requests.erase(m_incoming_requests.begin()); + write_metadata_packet(metadata_piece, piece); + } + } + + void maybe_send_request() + { + if (m_pc.is_disconnecting()) return; + + // if we don't have any metadata, and this peer + // supports the request metadata extension + // and we aren't currently waiting for a request + // reply. Then, send a request for some metadata. + if (!m_torrent.valid_metadata() + && m_message_index != 0 + && m_sent_requests.size() < 2 + && has_metadata()) + { + int const piece = m_tp.metadata_request(m_pc.has_metadata()); + if (piece == -1) return; + + m_sent_requests.push_back(piece); + write_metadata_packet(metadata_req, piece); + } + } + + bool has_metadata() const + { + return m_pc.has_metadata() || (aux::time_now() > m_request_limit); + } + + void failed_hash_check(time_point const& now) + { + m_request_limit = now + seconds(20 + random(50)); + } + + // explicitly disallow assignment, to silence msvc warning + ut_metadata_peer_plugin& operator=(ut_metadata_peer_plugin const&) = delete; + + private: + + // this is the message index the remote peer uses + // for metadata extension messages. + int m_message_index; + + // this is set to the next time we can request pieces + // again. It is updated every time we get a + // "I don't have metadata" message, but also when + // we receive metadata that fails the infohash check + time_point m_request_limit; + + // request queues + std::vector m_sent_requests; + std::vector m_incoming_requests; + + torrent& m_torrent; + bt_peer_connection& m_pc; + ut_metadata_plugin& m_tp; + }; + + std::shared_ptr ut_metadata_plugin::new_connection( + peer_connection_handle const& pc) + { + if (pc.type() != connection_type::bittorrent) return {}; + + bt_peer_connection* c = static_cast(pc.native_handle().get()); + return std::make_shared(m_torrent, *c, *this); + } + + // has_metadata is false if the peer making the request has not announced + // that it has metadata. In this case, it shouldn't prevent other peers + // from requesting this block by setting a timeout on it. + int ut_metadata_plugin::metadata_request(bool const has_metadata) + { + auto i = std::min_element( + m_requested_metadata.begin(), m_requested_metadata.end()); + + if (m_requested_metadata.empty()) + { + // if we don't know how many pieces there are + // just ask for piece 0 + m_requested_metadata.resize(1); + i = m_requested_metadata.begin(); + } + + int const piece = int(i - m_requested_metadata.begin()); + + // don't request the same block more than once every 3 seconds + time_point const now = aux::time_now(); + if (m_requested_metadata[piece].last_request != min_time() + && total_seconds(now - m_requested_metadata[piece].last_request) < 3) + return -1; + + ++m_requested_metadata[piece].num_requests; + + // only set the timeout on this block, only if the peer + // has metadata. This is to prevent peers with no metadata + // to starve out sending requests to peers with metadata + if (has_metadata) + m_requested_metadata[piece].last_request = now; + + return piece; + } + + bool ut_metadata_plugin::received_metadata(ut_metadata_peer_plugin& source + , span buf, int const piece, int const total_size) + { + if (m_torrent.valid_metadata()) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "already have metadata"); +#endif + m_torrent.add_redundant_bytes(static_cast(buf.size()), waste_reason::piece_unknown); + return false; + } + + if (!m_metadata) + { + // verify the total_size + if (total_size <= 0 || total_size > m_torrent.session().settings().get_int(settings_pack::max_metadata_size)) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "metadata size too big: %d", total_size); +#endif +// #error post alert + return false; + } + + m_metadata.reset(new char[std::size_t(total_size)]); + m_requested_metadata.resize(div_round_up(total_size, 16 * 1024)); + m_metadata_size = total_size; + } + + if (piece < 0 || piece >= m_requested_metadata.end_index()) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "piece: %d INVALID", piece); +#endif + return false; + } + + if (total_size != m_metadata_size) + { +#ifndef TORRENT_DISABLE_LOGGING + source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" + , "total_size: %d INCONSISTENT WITH: %d" + , total_size, m_metadata_size); +#endif + // they disagree about the size! + return false; + } + + if (piece * 16 * 1024 + buf.size() > m_metadata_size) + { + // this piece is invalid + return false; + } + + std::memcpy(&m_metadata[piece * 16 * 1024], buf.data(), aux::numeric_cast(buf.size())); + // mark this piece has 'have' + m_requested_metadata[piece].num_requests = std::numeric_limits::max(); + m_requested_metadata[piece].source = source.shared_from_this(); + + bool have_all = std::all_of(m_requested_metadata.begin(), m_requested_metadata.end() + , [](metadata_piece const& mp) { return mp.num_requests == std::numeric_limits::max(); }); + + if (!have_all) return false; + + if (!m_torrent.set_metadata({m_metadata.get(), m_metadata_size})) + { + if (!m_torrent.valid_metadata()) + { + time_point const now = aux::time_now(); + // any peer that we downloaded metadata from gets a random time + // penalty, from 5 to 30 seconds or so. During this time we don't + // make any metadata requests from those peers (to mix it up a bit + // of which peers we use) + // if we only have one block, and thus requested it from a single + // peer, we bump up the retry time a lot more to try other peers + bool single_peer = m_requested_metadata.size() == 1; + for (auto& mp : m_requested_metadata) + { + mp.num_requests = 0; + auto peer = mp.source.lock(); + if (!peer) continue; + + peer->failed_hash_check(single_peer ? now + minutes(5) : now); + } + } + return false; + } + + // free our copy of the metadata and get a reference + // to the torrent's copy instead. No need to keep two + // identical copies around + m_metadata.reset(); + metadata(); + + // clear the storage for the bitfield + m_requested_metadata.clear(); + m_requested_metadata.shrink_to_fit(); + + return true; + } + +} } + +namespace libtorrent { + + std::shared_ptr create_ut_metadata_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + // don't add this extension if the torrent is private + if (t->valid_metadata() && t->torrent_file().priv()) return {}; + return std::make_shared(*t); + } +} + +#endif diff --git a/src/ut_pex.cpp b/src/ut_pex.cpp new file mode 100644 index 0000000..db55a8d --- /dev/null +++ b/src/ut_pex.cpp @@ -0,0 +1,655 @@ +/* + +Copyright (c) 2006-2018, MassaRoddel, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/socket_type.hpp" // for is_utp +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/aux_/time.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS + +namespace libtorrent { namespace { + + const char extension_name[] = "ut_pex"; + + enum + { + extension_index = 1, + max_peer_entries = 100 + }; + + bool send_peer(peer_connection const& p) + { + // don't send out those peers that we haven't connected to + // (that have connected to us) and that aren't sharing their + // listening port + if (!p.is_outgoing() && !p.received_listen_port()) return false; + // don't send out peers that we haven't successfully connected to + if (p.is_connecting()) return false; + if (p.in_handshake()) return false; + return true; + } + + struct ut_pex_plugin final + : torrent_plugin + { + // randomize when we rebuild the pex message + // to evenly spread it out across all torrents + // the more torrents we have, the longer we can + // delay the rebuilding + explicit ut_pex_plugin(torrent& t) + : m_torrent(t) + , m_last_msg(min_time()) + , m_peers_in_message(0) {} + + std::shared_ptr new_connection( + peer_connection_handle const& pc) override; + + std::vector& get_ut_pex_msg() + { + return m_ut_pex_msg; + } + + int peers_in_msg() const + { + return m_peers_in_message; + } + + // the second tick of the torrent + // each minute the new lists of "added" + "added.f" and "dropped" + // are calculated here and the pex message is created + // each peer connection will use this message + // max_peer_entries limits the packet size + void tick() override + { + if (m_torrent.flags() & torrent_flags::disable_pex) return; + + time_point const now = aux::time_now(); + if (now - seconds(60) < m_last_msg) return; + m_last_msg = now; + + if (m_torrent.num_peers() == 0) return; + + entry pex; + std::string& pla = pex["added"].string(); + std::string& pld = pex["dropped"].string(); + std::string& plf = pex["added.f"].string(); + std::back_insert_iterator pla_out(pla); + std::back_insert_iterator pld_out(pld); + std::back_insert_iterator plf_out(plf); + std::string& pla6 = pex["added6"].string(); + std::string& pld6 = pex["dropped6"].string(); + std::string& plf6 = pex["added6.f"].string(); + std::back_insert_iterator pla6_out(pla6); + std::back_insert_iterator pld6_out(pld6); + std::back_insert_iterator plf6_out(plf6); + + std::set dropped; + m_old_peers.swap(dropped); + + m_peers_in_message = 0; + int num_added = 0; + for (auto const peer : m_torrent) + { + if (!send_peer(*peer)) continue; + + tcp::endpoint remote = peer->remote(); + m_old_peers.insert(remote); + + auto const di = dropped.find(remote); + if (di == dropped.end()) + { + // don't write too big of a package + if (num_added >= max_peer_entries) break; + + // only send proper bittorrent peers + if (peer->type() != connection_type::bittorrent) + continue; + + bt_peer_connection* p = static_cast(peer); + + // if the peer has told us which port its listening on, + // use that port. But only if we didn't connect to the peer. + // if we connected to it, use the port we know works + if (!p->is_outgoing()) + { + torrent_peer const* const pi = peer->peer_info_struct(); + if (pi != nullptr && pi->port > 0) + remote.port(pi->port); + } + + // no supported flags to set yet + // 0x01 - peer supports encryption + // 0x02 - peer is a seed + // 0x04 - supports uTP. This is only a positive flags + // passing 0 doesn't mean the peer doesn't + // support uTP + // 0x08 - supports hole punching protocol. If this + // flag is received from a peer, it can be + // used as a rendezvous point in case direct + // connections to the peer fail + pex_flags_t flags = p->is_seed() ? pex_seed : pex_flags_t{}; +#if !defined TORRENT_DISABLE_ENCRYPTION + flags |= p->supports_encryption() ? pex_encryption : pex_flags_t{}; +#endif + flags |= is_utp(*p->get_socket()) ? pex_utp : pex_flags_t{}; + flags |= p->supports_holepunch() ? pex_holepunch : pex_flags_t{}; + + // i->first was added since the last time + if (is_v4(remote)) + { + detail::write_endpoint(remote, pla_out); + detail::write_uint8(static_cast(flags), plf_out); + } + else + { + detail::write_endpoint(remote, pla6_out); + detail::write_uint8(static_cast(flags), plf6_out); + } + ++num_added; + ++m_peers_in_message; + } + else + { + // this was in the previous message + // so, it wasn't dropped + dropped.erase(di); + } + } + + for (auto const& i : dropped) + { + if (is_v4(i)) + detail::write_endpoint(i, pld_out); + else + detail::write_endpoint(i, pld6_out); + ++m_peers_in_message; + } + + m_ut_pex_msg.clear(); + bencode(std::back_inserter(m_ut_pex_msg), pex); + } + + private: + torrent& m_torrent; + + std::set m_old_peers; + time_point m_last_msg; + std::vector m_ut_pex_msg; + int m_peers_in_message; + + // explicitly disallow assignment, to silence msvc warning + ut_pex_plugin& operator=(ut_pex_plugin const&) = delete; + }; + + struct ut_pex_peer_plugin final + : ut_pex_peer_store, peer_plugin + { + ut_pex_peer_plugin(torrent& t, peer_connection& pc, ut_pex_plugin& tp) + : m_torrent(t) + , m_pc(pc) + , m_tp(tp) + , m_last_msg(min_time()) + , m_message_index(0) + , m_first_time(true) + { + const int num_pex_timers = sizeof(m_last_pex) / sizeof(m_last_pex[0]); + for (int i = 0; i < num_pex_timers; ++i) + { + m_last_pex[i] = min_time(); + } + } + + void add_handshake(entry& h) override + { + entry& messages = h["m"]; + messages[extension_name] = extension_index; + } + + bool on_extension_handshake(bdecode_node const& h) override + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node const messages = h.dict_find_dict("m"); + if (!messages) return false; + + int const index = int(messages.dict_find_int_value(extension_name, -1)); + if (index == -1) return false; + m_message_index = index; + return true; + } + + bool on_extended(int const length, int const msg, span body) override + { + if (msg != extension_index) return false; + if (m_message_index == 0) return false; + + if (m_torrent.flags() & torrent_flags::disable_pex) return true; + + if (length > 500 * 1024) + { + m_pc.disconnect(errors::pex_message_too_large, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + + if (int(body.size()) < length) return true; + + time_point const now = aux::time_now(); + if (now - seconds(60) < m_last_pex[0]) + { + // this client appears to be trying to flood us + // with pex messages. Don't allow that. + m_pc.disconnect(errors::too_frequent_pex, operation_t::bittorrent); + return true; + } + + int const num_pex_timers = sizeof(m_last_pex) / sizeof(m_last_pex[0]); + for (int i = 0; i < num_pex_timers - 1; ++i) + m_last_pex[i] = m_last_pex[i + 1]; + m_last_pex[num_pex_timers - 1] = now; + + bdecode_node pex_msg; + error_code ec; + int const ret = bdecode(body.begin(), body.end(), pex_msg, ec); + if (ret != 0 || pex_msg.type() != bdecode_node::dict_t) + { + m_pc.disconnect(errors::invalid_pex_message, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + + bdecode_node p = pex_msg.dict_find_string("dropped"); + +#ifndef TORRENT_DISABLE_LOGGING + int num_dropped = 0; + int num_added = 0; + if (p) num_dropped += p.string_length() / 6; +#endif + if (p) + { + int const num_peers = p.string_length() / 6; + char const* in = p.string_ptr(); + + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint const adr = detail::read_v4_endpoint(in); + peers4_t::value_type const v(adr.address().to_v4().to_bytes(), adr.port()); + auto const j = std::lower_bound(m_peers.begin(), m_peers.end(), v); + if (j != m_peers.end() && *j == v) m_peers.erase(j); + } + } + + p = pex_msg.dict_find_string("added"); + bdecode_node const pf = pex_msg.dict_find_string("added.f"); + + bool peers_added = false; +#ifndef TORRENT_DISABLE_LOGGING + if (p) num_added += p.string_length() / 6; +#endif + if (p && pf && pf.string_length() == p.string_length() / 6) + { + int const num_peers = pf.string_length(); + char const* in = p.string_ptr(); + char const* fin = pf.string_ptr(); + + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint const adr = detail::read_v4_endpoint(in); + pex_flags_t const flags(static_cast(*fin++)); + + if (int(m_peers.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers)) + break; + + // ignore local addresses unless the peer is local to us + if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue; + + peers4_t::value_type const v(adr.address().to_v4().to_bytes(), adr.port()); + auto const j = std::lower_bound(m_peers.begin(), m_peers.end(), v); + // do we already know about this peer? + if (j != m_peers.end() && *j == v) continue; + m_peers.insert(j, v); + m_torrent.add_peer(adr, peer_info::pex, flags); + peers_added = true; + } + } + + bdecode_node p6 = pex_msg.dict_find_string("dropped6"); + if (p6) + { +#ifndef TORRENT_DISABLE_LOGGING + num_dropped += p6.string_length() / 18; +#endif + int const num_peers = p6.string_length() / 18; + char const* in = p6.string_ptr(); + + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint const adr = detail::read_v6_endpoint(in); + peers6_t::value_type const v(adr.address().to_v6().to_bytes(), adr.port()); + auto const j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v); + if (j != m_peers6.end() && *j == v) m_peers6.erase(j); + } + } + + p6 = pex_msg.dict_find_string("added6"); +#ifndef TORRENT_DISABLE_LOGGING + if (p6) num_added += p6.string_length() / 18; +#endif + bdecode_node const p6f = pex_msg.dict_find_string("added6.f"); + if (p6 && p6f && p6f.string_length() == p6.string_length() / 18) + { + int const num_peers = p6f.string_length(); + char const* in = p6.string_ptr(); + char const* fin = p6f.string_ptr(); + + for (int i = 0; i < num_peers; ++i) + { + tcp::endpoint const adr = detail::read_v6_endpoint(in); + pex_flags_t const flags(static_cast(*fin++)); + // ignore local addresses unless the peer is local to us + if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue; + if (int(m_peers6.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers)) + break; + + peers6_t::value_type const v(adr.address().to_v6().to_bytes(), adr.port()); + auto const j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v); + // do we already know about this peer? + if (j != m_peers6.end() && *j == v) continue; + m_peers6.insert(j, v); + m_torrent.add_peer(adr, peer_info::pex, flags); + peers_added = true; + } + } +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "PEX", "dropped: %d added: %d" + , num_dropped, num_added); +#endif + + m_pc.stats_counters().inc_stats_counter(counters::num_incoming_pex); + + if (peers_added) m_torrent.do_connect_boost(); + return true; + } + + // the peers second tick + // every minute we send a pex message + void tick() override + { + // no handshake yet + if (!m_message_index) return; + + time_point const now = aux::time_now(); + if (now - seconds(60) < m_last_msg) + { +#ifndef TORRENT_DISABLE_LOGGING +// m_pc.peer_log(peer_log_alert::info, "PEX", "waiting: %d seconds to next msg" +// , int(total_seconds(seconds(60) - (now - m_last_msg)))); +#endif + return; + } + static time_point global_last = min_time(); + + int const num_peers = m_torrent.num_peers(); + if (num_peers <= 1) return; + + // don't send pex messages more often than 1 every 100 ms, and + // allow pex messages to be sent 5 seconds apart if there isn't + // contention + int const delay = std::min(std::max(60000 / num_peers, 100), 3000); + + if (now - milliseconds(delay) < global_last) + { +#ifndef TORRENT_DISABLE_LOGGING +// m_pc.peer_log(peer_log_alert::info, "PEX", "global-wait: %d" +// , int(total_seconds(milliseconds(delay) - (now - global_last)))); +#endif + return; + } + + // this will allow us to catch up, even if our timer + // has lower resolution than delay + if (global_last == min_time()) + global_last = now; + else + global_last += milliseconds(delay); + + m_last_msg = now; + + if (m_first_time) + { + send_ut_peer_list(); + m_first_time = false; + } + else + { + send_ut_peer_diff(); + } + } + + void send_ut_peer_diff() + { + if (m_torrent.flags() & torrent_flags::disable_pex) return; + + // if there's no change in out peer set, don't send anything + if (m_tp.peers_in_msg() == 0) return; + + std::vector const& pex_msg = m_tp.get_ut_pex_msg(); + + char msg[6]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + int(pex_msg.size()), ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg); + m_pc.send_buffer(pex_msg); + + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended); + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_pex); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_pc.should_log(peer_log_alert::outgoing_message)) + { + bdecode_node m; + error_code ec; + int const ret = bdecode(&pex_msg[0], &pex_msg[0] + pex_msg.size(), m, ec); + TORRENT_ASSERT(ret == 0); + TORRENT_ASSERT(!ec); + TORRENT_UNUSED(ret); + int num_dropped = 0; + int num_added = 0; + bdecode_node e = m.dict_find_string("added"); + if (e) num_added += e.string_length() / 6; + e = m.dict_find_string("dropped"); + if (e) num_dropped += e.string_length() / 6; + e = m.dict_find_string("added6"); + if (e) num_added += e.string_length() / 18; + e = m.dict_find_string("dropped6"); + if (e) num_dropped += e.string_length() / 18; + m_pc.peer_log(peer_log_alert::outgoing_message, "PEX_DIFF", "dropped: %d added: %d msg_size: %d" + , num_dropped, num_added, int(pex_msg.size())); + } +#endif + } + + void send_ut_peer_list() + { + if (m_torrent.flags() & torrent_flags::disable_pex) return; + + entry pex; + // leave the dropped string empty + pex["dropped"].string(); + std::string& pla = pex["added"].string(); + std::string& plf = pex["added.f"].string(); + std::back_insert_iterator pla_out(pla); + std::back_insert_iterator plf_out(plf); + + pex["dropped6"].string(); + std::string& pla6 = pex["added6"].string(); + std::string& plf6 = pex["added6.f"].string(); + std::back_insert_iterator pla6_out(pla6); + std::back_insert_iterator plf6_out(plf6); + + int num_added = 0; + for (auto const peer : m_torrent) + { + if (!send_peer(*peer)) continue; + + // don't write too big of a package + if (num_added >= max_peer_entries) break; + + // only send proper bittorrent peers + if (peer->type() != connection_type::bittorrent) + continue; + + bt_peer_connection* p = static_cast(peer); + + // no supported flags to set yet + // 0x01 - peer supports encryption + // 0x02 - peer is a seed + // 0x04 - supports uTP. This is only a positive flags + // passing 0 doesn't mean the peer doesn't + // support uTP + // 0x08 - supports hole punching protocol. If this + // flag is received from a peer, it can be + // used as a rendezvous point in case direct + // connections to the peer fail + int flags = p->is_seed() ? 2 : 0; +#if !defined TORRENT_DISABLE_ENCRYPTION + flags |= p->supports_encryption() ? 1 : 0; +#endif + flags |= is_utp(*p->get_socket()) ? 4 : 0; + flags |= p->supports_holepunch() ? 8 : 0; + + tcp::endpoint remote = peer->remote(); + + if (!p->is_outgoing()) + { + torrent_peer const* const pi = peer->peer_info_struct(); + if (pi != nullptr && pi->port > 0) + remote.port(pi->port); + } + + // i->first was added since the last time + if (is_v4(remote)) + { + detail::write_endpoint(remote, pla_out); + detail::write_uint8(flags, plf_out); + } + else + { + detail::write_endpoint(remote, pla6_out); + detail::write_uint8(flags, plf6_out); + } + ++num_added; + } + std::vector pex_msg; + bencode(std::back_inserter(pex_msg), pex); + + char msg[6]; + char* ptr = msg; + + detail::write_uint32(1 + 1 + int(pex_msg.size()), ptr); + detail::write_uint8(bt_peer_connection::msg_extended, ptr); + detail::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg); + m_pc.send_buffer(pex_msg); + + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended); + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_pex); + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::outgoing_message, "PEX_FULL" + , "added: %d msg_size: %d", num_added, int(pex_msg.size())); +#endif + } + + torrent& m_torrent; + peer_connection& m_pc; + ut_pex_plugin& m_tp; + + // the last pex messages we received + // [0] is the oldest one. There is a problem with + // rate limited connections, because we may sit + // for a long time, accumulating pex messages, and + // then once we read from the socket it will look like + // we received them all back to back. That's why + // we look at 6 pex messages back. + time_point m_last_pex[6]; + + time_point m_last_msg; + int m_message_index; + + // this is initialized to true, and set to + // false after the first pex message has been sent. + // it is used to know if a diff message or a) ful + // message should be sent. + bool m_first_time; + + // explicitly disallow assignment, to silence msvc warning + ut_pex_peer_plugin& operator=(ut_pex_peer_plugin const&) = delete; + }; + + std::shared_ptr ut_pex_plugin::new_connection(peer_connection_handle const& pc) + { + if (pc.type() != connection_type::bittorrent) return {}; + + bt_peer_connection* c = static_cast(pc.native_handle().get()); + auto p = std::make_shared(m_torrent, *c, *this); + c->set_ut_pex(p); + return p; + } +} } + +namespace libtorrent { + + std::shared_ptr create_ut_pex_plugin(torrent_handle const& th, void*) + { + torrent* t = th.native_handle().get(); + if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + && !t->settings().get_bool(settings_pack::allow_i2p_mixed))) + { + return {}; + } + return std::make_shared(*t); + } +} + +#endif diff --git a/src/utf8.cpp b/src/utf8.cpp new file mode 100644 index 0000000..ceb2b2f --- /dev/null +++ b/src/utf8.cpp @@ -0,0 +1,172 @@ +/* + +Copyright (c) 2012-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include +#include "libtorrent/utf8.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/aux_/throw.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" + + +namespace libtorrent { + +namespace { + // return the number of bytes in the UTF-8 sequence starting with this + // character. Returns 0 if the lead by is invalid + int utf8_sequence_length(char const c) + { + auto const b = static_cast(c); + if (b < 0x80) return 1; + if ((b >> 5) == 0x06) return 2; + if ((b >> 4) == 0x0e) return 3; + if ((b >> 3) == 0x1e) return 4; + // this is an invalid prefix, but we still parse it to skip this many + // bytes + if ((b >> 2) == 0x3e) return 5; + return 0; + } + +} // anonymous namespace + + std::int32_t const max_codepoint = 0x10ffff; + + void append_utf8_codepoint(std::string& ret, std::int32_t codepoint) + { + std::int32_t const surrogate_start = 0xd800; + std::int32_t const surrogate_end = 0xdfff; + + if (codepoint >= surrogate_start + && codepoint <= surrogate_end) + codepoint = '_'; + + if (codepoint > max_codepoint) + codepoint = '_'; + + int seq_len = 0; + if (codepoint < 0x80) seq_len = 1; + else if (codepoint < 0x800) seq_len = 2; + else if (codepoint < 0x10000) seq_len = 3; + else seq_len = 4; + + switch (seq_len) + { + case 1: + ret.push_back(static_cast(codepoint)); + break; + case 2: + ret.push_back(static_cast(0xc0 | (codepoint >> 6))); + break; + case 3: + ret.push_back(static_cast(0xe0 | (codepoint >> 12))); + break; + case 4: + ret.push_back(static_cast(0xf0 | (codepoint >> 18))); + break; + } + + for (int i = seq_len - 2; i >= 0; --i) + ret.push_back(static_cast(0x80 | ((codepoint >> (6 * i)) & 0x3f))); + } + + // returns the unicode codepoint and the number of bytes of the utf8 sequence + // that was parsed. The codepoint is -1 if it's invalid + std::pair parse_utf8_codepoint(string_view str) + { + TORRENT_ASSERT(!str.empty()); + if (str.empty()) return std::make_pair(-1, 0); + + int const sequence_len = utf8_sequence_length(str[0]); + + // this is likely the most common case + if (sequence_len == 1) return std::make_pair(std::int32_t(str[0]), sequence_len); + + // if we find an invalid sequence length, skip one byte + if (sequence_len == 0) + return std::make_pair(-1, 1); + + if (sequence_len > 4) + return std::make_pair(-1, sequence_len); + + if (sequence_len > int(str.size())) + return std::make_pair(-1, static_cast(str.size())); + + std::int32_t ch = 0; + // first byte + switch (sequence_len) + { + case 1: + ch = str[0] & 0x7f; + break; + case 2: + ch = str[0] & 0x1f; + break; + case 3: + ch = str[0] & 0x0f; + break; + case 4: + ch = str[0] & 0x07; + break; + }; + for (int i = 1; i < sequence_len; ++i) + { + auto const b = static_cast(str[static_cast(i)]); + // continuation bytes must start with 10xxxxxx + if (b > 0xbf || b < 0x80) + return std::make_pair(-1, sequence_len); + ch <<= 6; + ch += b & 0x3f; + } + + // check if the sequence is overlong, i.e. whether it has leading + // (redundant) zeros + switch (sequence_len) + { + case 2: + if (ch < 0x80) return std::make_pair(-1, sequence_len); + break; + case 3: + if (ch < 0x800) return std::make_pair(-1, sequence_len); + break; + case 4: + if (ch < 0x10000) return std::make_pair(-1, sequence_len); + break; + }; + + if (ch > max_codepoint) + return std::make_pair(-1, sequence_len); + + return std::make_pair(static_cast(ch), sequence_len); + } +} diff --git a/src/utp_socket_manager.cpp b/src/utp_socket_manager.cpp new file mode 100644 index 0000000..2a3947a --- /dev/null +++ b/src/utp_socket_manager.cpp @@ -0,0 +1,352 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/utp_stream.hpp" +#include "libtorrent/udp_socket.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/aux_/instantiate_connection.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket.hpp" // for TORRENT_HAS_DONT_FRAGMENT +#include "libtorrent/broadcast_socket.hpp" // for is_teredo +#include "libtorrent/random.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/aux_/time.hpp" // for aux::time_now() +#include "libtorrent/span.hpp" + +// #define TORRENT_DEBUG_MTU 1135 + +namespace libtorrent { + + utp_socket_manager::utp_socket_manager( + send_fun_t const& send_fun + , incoming_utp_callback_t const& cb + , io_service& ios + , aux::session_settings const& sett + , counters& cnt + , void* ssl_context) + : m_send_fun(send_fun) + , m_cb(cb) + , m_sett(sett) + , m_counters(cnt) + , m_ios(ios) + , m_ssl_context(ssl_context) + { + m_restrict_mtu.fill(65536); + } + + utp_socket_manager::~utp_socket_manager() + { + for (auto& i : m_utp_sockets) + { + delete_utp_impl(i.second); + } + } + + void utp_socket_manager::tick(time_point now) + { + for (auto i = m_utp_sockets.begin() + , end(m_utp_sockets.end()); i != end;) + { + if (should_delete(i->second)) + { + delete_utp_impl(i->second); + if (m_last_socket == i->second) m_last_socket = nullptr; + i = m_utp_sockets.erase(i); + continue; + } + tick_utp_impl(i->second, now); + ++i; + } + } + + std::pair utp_socket_manager::mtu_for_dest(address const& addr) + { + int mtu = 0; + if (is_teredo(addr)) mtu = TORRENT_TEREDO_MTU; + else mtu = TORRENT_ETHERNET_MTU; + +#if defined __APPLE__ + // apple has a very strange loopback. It appears you can't + // send messages of the reported MTU size, and you don't get + // EWOULDBLOCK either. + if (is_loopback(addr)) + { + if (is_teredo(addr)) mtu = TORRENT_TEREDO_MTU; + else mtu = TORRENT_ETHERNET_MTU; + } +#endif + + int const link_mtu = mtu; + + mtu -= TORRENT_UDP_HEADER; + + if (m_sett.get_int(settings_pack::proxy_type) == settings_pack::socks5 + || m_sett.get_int(settings_pack::proxy_type) == settings_pack::socks5_pw) + { + // this is for the IP layer + // assume the proxy is running over IPv4 + mtu -= TORRENT_IPV4_HEADER; + + // this is for the SOCKS layer + mtu -= TORRENT_SOCKS5_HEADER; + + // the address field in the SOCKS header + if (addr.is_v4()) mtu -= 4; + else mtu -= 16; + } + else + { + if (addr.is_v4()) mtu -= TORRENT_IPV4_HEADER; + else mtu -= TORRENT_IPV6_HEADER; + } + + return std::make_pair(link_mtu, std::min(mtu, restrict_mtu())); + } + + void utp_socket_manager::send_packet(std::weak_ptr sock + , udp::endpoint const& ep, char const* p + , int const len, error_code& ec, udp_send_flags_t const flags) + { +#if !defined TORRENT_HAS_DONT_FRAGMENT && !defined TORRENT_DEBUG_MTU + TORRENT_UNUSED(flags); +#endif + +#ifdef TORRENT_DEBUG_MTU + // drop packets that exceed the debug MTU + if ((flags & dont_fragment) && len > TORRENT_DEBUG_MTU) return; +#endif + + m_send_fun(std::move(sock), ep, {p, len}, ec + , (flags & udp_socket::dont_fragment) + | udp_socket::peer_connection); + } + + bool utp_socket_manager::incoming_packet(std::weak_ptr socket + , udp::endpoint const& ep, span p) + { +// UTP_LOGV("incoming packet size:%d\n", size); + + if (p.size() < std::ptrdiff_t(sizeof(utp_header))) return false; + + auto const* ph = reinterpret_cast(p.data()); + +// UTP_LOGV("incoming packet version:%d\n", int(ph->get_version())); + + if (ph->get_version() != 1) return false; + + const time_point receive_time = clock_type::now(); + + // parse out connection ID and look for existing + // connections. If found, forward to the utp_stream. + std::uint16_t id = ph->connection_id; + + // first test to see if it's the same socket as last time + // in most cases it is + if (m_last_socket + && utp_match(m_last_socket, ep, id)) + { + return utp_incoming_packet(m_last_socket, p, ep, receive_time); + } + + if (m_deferred_ack) + { + utp_send_ack(m_deferred_ack); + m_deferred_ack = nullptr; + } + + auto r = m_utp_sockets.equal_range(id); + + for (; r.first != r.second; ++r.first) + { + if (!utp_match(r.first->second, ep, id)) continue; + bool ret = utp_incoming_packet(r.first->second, p, ep, receive_time); + if (ret) m_last_socket = r.first->second; + return ret; + } + +// UTP_LOGV("incoming packet id:%d source:%s\n", id, print_endpoint(ep).c_str()); + + if (!m_sett.get_bool(settings_pack::enable_incoming_utp)) + return false; + + // if not found, see if it's a SYN packet, if it is, + // create a new utp_stream + if (ph->get_type() == ST_SYN) + { + // possible SYN flood. Just ignore + if (int(m_utp_sockets.size()) > m_sett.get_int(settings_pack::connections_limit) * 2) + return false; + +// UTP_LOGV("not found, new connection id:%d\n", m_new_connection); + + std::shared_ptr c(new (std::nothrow) aux::socket_type(m_ios)); + if (!c) return false; + + TORRENT_ASSERT(m_new_connection == -1); + // create the new socket with this ID + m_new_connection = id; + + aux::instantiate_connection(m_ios, aux::proxy_settings(), *c + , m_ssl_context, this, true, false); + + utp_stream* str = nullptr; +#ifdef TORRENT_USE_OPENSSL + if (is_ssl(*c)) + str = &c->get>()->next_layer(); + else +#endif + str = c->get(); + + TORRENT_ASSERT(str); + int link_mtu, utp_mtu; + std::tie(link_mtu, utp_mtu) = mtu_for_dest(ep.address()); + utp_init_mtu(str->get_impl(), link_mtu, utp_mtu); + utp_init_socket(str->get_impl(), std::move(socket)); + bool ret = utp_incoming_packet(str->get_impl(), p, ep, receive_time); + if (!ret) return false; + m_last_socket = str->get_impl(); + m_cb(c); + // the connection most likely changed its connection ID here + // we need to move it to the correct ID + return true; + } + + if (ph->get_type() == ST_RESET) return false; + + // #error send reset + + return false; + } + + void utp_socket_manager::subscribe_writable(utp_socket_impl* s) + { + TORRENT_ASSERT(std::find(m_stalled_sockets.begin(), m_stalled_sockets.end() + , s) == m_stalled_sockets.end()); + m_stalled_sockets.push_back(s); + } + + void utp_socket_manager::writable() + { + if (!m_stalled_sockets.empty()) + { + m_temp_sockets.clear(); + m_stalled_sockets.swap(m_temp_sockets); + for (auto const &s : m_temp_sockets) + { + utp_writable(s); + } + } + } + + void utp_socket_manager::socket_drained() + { + if (m_deferred_ack) + { + utp_socket_impl* s = m_deferred_ack; + m_deferred_ack = nullptr; + utp_send_ack(s); + } + + if (!m_drained_event.empty()) + { + m_temp_sockets.clear(); + m_drained_event.swap(m_temp_sockets); + for (auto const &s : m_temp_sockets) + { + utp_socket_drained(s); + } + } + } + + void utp_socket_manager::defer_ack(utp_socket_impl* s) + { + TORRENT_ASSERT(m_deferred_ack == NULL || m_deferred_ack == s); + m_deferred_ack = s; + } + + void utp_socket_manager::subscribe_drained(utp_socket_impl* s) + { + TORRENT_ASSERT(std::find(m_drained_event.begin(), m_drained_event.end(), s) + == m_drained_event.end()); + m_drained_event.push_back(s); + } + + void utp_socket_manager::remove_udp_socket(std::weak_ptr sock) + { + for (auto& s : m_utp_sockets) + { + if (!bound_to_udp_socket(s.second, sock)) + continue; + + utp_abort(s.second); + } + } + + void utp_socket_manager::remove_socket(std::uint16_t const id) + { + auto const i = m_utp_sockets.find(id); + if (i == m_utp_sockets.end()) return; + delete_utp_impl(i->second); + if (m_last_socket == i->second) m_last_socket = nullptr; + if (m_deferred_ack == i->second) m_deferred_ack = nullptr; + m_utp_sockets.erase(i); + } + + void utp_socket_manager::inc_stats_counter(int counter, int delta) + { + TORRENT_ASSERT((counter >= counters::utp_packet_loss + && counter <= counters::utp_redundant_pkts_in) + || (counter >= counters::num_utp_idle + && counter <= counters::num_utp_deleted)); + m_counters.inc_stats_counter(counter, delta); + } + + utp_socket_impl* utp_socket_manager::new_utp_socket(utp_stream* str) + { + std::uint16_t send_id = 0; + std::uint16_t recv_id = 0; + if (m_new_connection != -1) + { + send_id = std::uint16_t(m_new_connection); + recv_id = std::uint16_t(m_new_connection + 1); + m_new_connection = -1; + } + else + { + send_id = std::uint16_t(random(0xffff)); + recv_id = send_id - 1; + } + utp_socket_impl* impl = construct_utp_impl(recv_id, send_id, str, *this); + m_utp_sockets.emplace(recv_id, impl); + return impl; + } +} diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp new file mode 100644 index 0000000..82225b3 --- /dev/null +++ b/src/utp_stream.cpp @@ -0,0 +1,3752 @@ +/* + +Copyright (c) 2009-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/utp_stream.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/utp_socket_manager.hpp" +#include "libtorrent/aux_/alloca.hpp" +#include "libtorrent/timestamp_history.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/io_service.hpp" +#include +#include + +#if TORRENT_USE_INVARIANT_CHECKS +#include // for accumulate +#endif + +#if TORRENT_UTP_LOG +#include +#include // for PRId64 et.al. +#include "libtorrent/socket_io.hpp" +#endif + +namespace libtorrent { + +#if TORRENT_UTP_LOG + +static char const* packet_type_names[] = { "ST_DATA", "ST_FIN", "ST_STATE", "ST_RESET", "ST_SYN" }; +static char const* socket_state_names[] = { "NONE", "SYN_SENT", "CONNECTED", "FIN_SENT", "ERROR", "DELETE" }; + +static struct utp_logger +{ + FILE* utp_log_file; + std::mutex utp_log_mutex; + + utp_logger() : utp_log_file(nullptr) {} + ~utp_logger() + { + if (utp_log_file) fclose(utp_log_file); + } +} log_file_holder; + +TORRENT_FORMAT(1, 2) +void utp_log(char const* fmt, ...) +{ + if (log_file_holder.utp_log_file == nullptr) return; + + std::lock_guard lock(log_file_holder.utp_log_mutex); + static time_point start = clock_type::now(); + std::fprintf(log_file_holder.utp_log_file, "[%012" PRId64 "] ", total_microseconds(clock_type::now() - start)); + va_list l; + va_start(l, fmt); + vfprintf(log_file_holder.utp_log_file, fmt, l); + va_end(l); +} + +bool is_utp_stream_logging() { + return log_file_holder.utp_log_file != nullptr; +} + +void set_utp_stream_logging(bool enable) { + if (enable) + { + if (log_file_holder.utp_log_file == nullptr) + { + log_file_holder.utp_log_file = fopen("utp.log", "w+"); + } + } + else + { + if (log_file_holder.utp_log_file != nullptr) + { + FILE* f = log_file_holder.utp_log_file; + log_file_holder.utp_log_file = nullptr; + fclose(f); + } + } +} + +#define UTP_LOG utp_log +#if TORRENT_VERBOSE_UTP_LOG +#define UTP_LOGV utp_log +#else +#define UTP_LOGV TORRENT_WHILE_0 printf +#endif + +#else + +#define UTP_LOG(...) do {} while(false) +#define UTP_LOGV(...) do {} while(false) + +#endif + +enum +{ + ACK_MASK = 0xffff, + + // if a packet receives more than this number of + // duplicate acks, we'll trigger a fast re-send + dup_ack_limit = 3 +}; + +// compare if lhs is less than rhs, taking wrapping +// into account. if lhs is close to UINT_MAX and rhs +// is close to 0, lhs is assumed to have wrapped and +// considered smaller +bool compare_less_wrap(std::uint32_t lhs + , std::uint32_t rhs, std::uint32_t mask) +{ + // distance walking from lhs to rhs, downwards + std::uint32_t dist_down = (lhs - rhs) & mask; + // distance walking from lhs to rhs, upwards + std::uint32_t dist_up = (rhs - lhs) & mask; + + // if the distance walking up is shorter, lhs + // is less than rhs. If the distance walking down + // is shorter, then rhs is less than lhs + return dist_up < dist_down; +} + +// since the uTP socket state may be needed after the +// utp_stream is closed, it's kept in a separate struct +// whose lifetime is not tied to the lifetime of utp_stream + +// the utp socket is closely modelled after the asio async +// operations and handler model. For writing to the socket, +// the client provides a list of buffers (for gather/writev +// style of I/O) and whenever the socket can write another +// packet to the stream, it picks up data from these buffers. +// When all of the data has been written, or enough time has +// passed since we first started writing, the write handler +// is called and the write buffer is reset. This means that +// we're not writing anything at all while waiting for the +// client to re-issue a write request. + +// reading is a little bit more complicated, since we must +// be able to receive data even when the user doesn't have +// an outstanding read operation on the socket. When the user +// does however, we want to receive data directly into the +// user's buffer instead of first copying it into our receive +// buffer. This is why the receive case is more complicated. +// There are two receive buffers. One provided by the user, +// which when present is always used. The other one is used +// when the user doesn't have an outstanding read request, +// and hence hasn't provided any buffer space to receive into. + +// the user provided read buffer is called "m_read_buffer" and +// its size is "m_read_buffer_size". The buffer we spill over +// into when the user provided buffer is full or when there +// is none, is "m_receive_buffer" and "m_receive_buffer_size" +// respectively. + +// in order to know when to trigger the read and write handlers +// there are two counters, m_read and m_written, which count +// the number of bytes we've stuffed into the user provided +// read buffer or written to the stream from the write buffer. +// These are used to trigger the handlers if we're written a +// large number of bytes. It's also triggered if we're filled +// the whole read buffer, or written the entire write buffer. +// The last way the handlers can be triggered is if we're read +// or written some, and enough time has elapsed since then. + +// when we receive data into m_receive_buffer (i.e. the buffer +// used when there's no user provided one) is stored as a +// number of heap allocated packets. This is just because it's +// simple to reuse the data structured and it provides all the +// functionality needed for this buffer. + +struct utp_socket_impl +{ + utp_socket_impl(std::uint16_t recv_id, std::uint16_t send_id + , void* userdata, utp_socket_manager& sm) + : m_sm(sm) + , m_userdata(userdata) + , m_timeout(clock_type::now() + milliseconds(m_sm.connect_timeout())) + , m_send_id(send_id) + , m_recv_id(recv_id) + , m_delay_sample_idx(0) + , m_state(UTP_STATE_NONE) + , m_eof(false) + , m_attached(true) + , m_nagle(true) + , m_slow_start(true) + , m_cwnd_full(false) + , m_null_buffers(false) + , m_deferred_ack(false) + , m_subscribe_drained(false) + , m_stalled(false) + , m_confirmed(false) + { + TORRENT_ASSERT((m_recv_id == ((m_send_id + 1) & 0xffff)) + || (m_send_id == ((m_recv_id + 1) & 0xffff))); + m_sm.inc_stats_counter(counters::num_utp_idle); + TORRENT_ASSERT(m_userdata); + m_delay_sample_hist.fill(std::numeric_limits::max()); + } + + ~utp_socket_impl(); + + void tick(time_point now); + void init_mtu(int link_mtu, int utp_mtu); + bool incoming_packet(span buf + , udp::endpoint const& ep, time_point receive_time); + void writable(); + + bool should_delete() const; + tcp::endpoint remote_endpoint(error_code& ec) const + { + if (m_state == UTP_STATE_NONE) + ec = boost::asio::error::not_connected; + else + TORRENT_ASSERT(m_remote_address != address_v4::any()); + return tcp::endpoint(m_remote_address, m_port); + } + std::size_t available() const; + // returns true if there were handlers cancelled + // if it returns false, we can detach immediately + bool destroy(); + void set_close_reason(close_reason_t code); + void detach(); + void send_syn(); + void send_fin(); + + void subscribe_drained(); + void defer_ack(); + void remove_sack_header(packet* p); + + enum packet_flags_t { pkt_ack = 1, pkt_fin = 2 }; + bool send_pkt(int flags = 0); + bool resend_packet(packet* p, bool fast_resend = false); + void send_reset(utp_header const* ph); + std::pair parse_sack(std::uint16_t packet_ack, std::uint8_t const* ptr + , int size, time_point now); + void parse_close_reason(std::uint8_t const* ptr, int size); + void write_payload(std::uint8_t* ptr, int size); + void maybe_inc_acked_seq_nr(); + std::uint32_t ack_packet(packet_ptr p, time_point receive_time + , std::uint16_t seq_nr); + void write_sack(std::uint8_t* buf, int size) const; + void incoming(std::uint8_t const* buf, int size, packet_ptr p, time_point now); + void do_ledbat(int acked_bytes, int delay, int in_flight); + int packet_timeout() const; + bool test_socket_state(); + void maybe_trigger_receive_callback(); + void maybe_trigger_send_callback(); + bool cancel_handlers(error_code const& ec, bool shutdown); + bool consume_incoming_data( + utp_header const* ph, std::uint8_t const* ptr, int payload_size, time_point now); + void update_mtu_limits(); + void experienced_loss(std::uint32_t seq_nr, time_point now); + + void set_state(int s); + + packet_ptr acquire_packet(int const allocate) { return m_sm.acquire_packet(allocate); } + void release_packet(packet_ptr p) { m_sm.release_packet(std::move(p)); } + + // non-copyable + utp_socket_impl(utp_socket_impl const&) = delete; + utp_socket_impl const& operator=(utp_socket_impl const&) = delete; + + // TODO: 2 it would be nice if not everything would have to be public here + +#if TORRENT_USE_INVARIANT_CHECKS + void check_receive_buffers() const; + void check_invariant() const; +#endif + + utp_socket_manager& m_sm; + std::weak_ptr m_sock; + + // userdata pointer passed along + // with any callback. This is initialized to 0 + // then set to point to the utp_stream when + // hooked up, and then reset to 0 once the utp_stream + // detaches. This is used to know whether or not + // the socket impl is still attached to a utp_stream + // object. When it isn't, we'll never be able to + // signal anything back to the client, and in case + // of errors, we just have to delete ourselves + // i.e. transition to the UTP_STATE_DELETED state + void* m_userdata; + + // This is a platform-independent replacement + // for the regular iovec type in posix. Since + // it's not used in any system call, we might as + // well define our own type instead of wrapping + // the system's type. + struct iovec_t + { + iovec_t(void* b, std::size_t l): buf(b), len(l) {} + void* buf; + std::size_t len; + }; + + // if there's currently an async read or write + // operation in progress, these buffers are initialized + // and used, otherwise any bytes received are stuck in + // m_receive_buffer until another read is made + // as we flush from the write buffer, individual iovecs + // are updated to only refer to unflushed portions of the + // buffers. Buffers that empty are erased from the vector. + std::vector m_write_buffer; + + // if this is non nullptr, it's a packet. This packet was held off because + // of NAGLE. We couldn't send it immediately. It's left + // here to accrue more bytes before we send it. + packet_ptr m_nagle_packet; + + // the user provided read buffer. If this has a size greater + // than 0, we'll always prefer using it over putting received + // data in the m_receive_buffer. As data is stored in the + // read buffer, the iovec_t elements are adjusted to only + // refer to the unwritten portions of the buffers, and the + // ones that fill up are erased from the vector + std::vector m_read_buffer; + + // packets we've received without a read operation + // active. Store them here until the client triggers + // an async_read_some + std::vector m_receive_buffer; + + // this is the error on this socket. If m_state is + // set to UTP_STATE_ERROR_WAIT, this error should be + // forwarded to the client as soon as we have a new + // async operation initiated + error_code m_error; + + // these indicate whether or not there is an outstanding read/write or + // connect operation. i.e. is there upper layer subscribed to these events. + bool m_read_handler = false; + bool m_write_handler = false; + bool m_connect_handler = false; + + // the address of the remote endpoint + address m_remote_address; + + // the send and receive buffers + // maps packet sequence numbers + packet_buffer m_inbuf; + packet_buffer m_outbuf; + + // the time when the last packet we sent times out. Including re-sends. + // if we ever end up not having sent anything in one second ( + // or one mean rtt + 2 average deviations, whichever is greater) + // we set our cwnd to 1 MSS. This condition can happen either because + // a packet has timed out and needs to be resent or because our + // cwnd is set to less than one MSS during congestion control. + // it can also happen if the other end sends an advertised window + // size less than one MSS. + time_point m_timeout; + + // the last time we stepped the timestamp history + time_point m_last_history_step = clock_type::now(); + + // the next time we allow a lost packet to halve cwnd. We only do this once every + // 100 ms + time_point m_next_loss; + + // the max number of bytes in-flight. This is a fixed point + // value, to get the true number of bytes, shift right 16 bits + // the value is always >= 0, but the calculations performed on + // it in do_ledbat() are signed. + std::int64_t m_cwnd = TORRENT_ETHERNET_MTU << 16; + + timestamp_history m_delay_hist; + timestamp_history m_their_delay_hist; + + // the slow-start threshold. This is the congestion window size (m_cwnd) + // in bytes the last time we left slow-start mode. This is used as a + // threshold to leave slow-start earlier next time, to avoid packet-loss + std::int32_t m_ssthres = 0; + + // the number of bytes we have buffered in m_inbuf + std::int32_t m_buffered_incoming_bytes = 0; + + // the timestamp diff in the last packet received + // this is what we'll send back + std::uint32_t m_reply_micro = 0; + + // this is the advertised receive window the other end sent + // we'll never have more un-acked bytes in flight + // if this ever gets set to zero, we'll try one packet every + // second until the window opens up again + std::uint32_t m_adv_wnd = TORRENT_ETHERNET_MTU; + + // the number of un-acked bytes we have sent + std::int32_t m_bytes_in_flight = 0; + + // the number of bytes read into the user provided + // buffer. If this grows too big, we'll trigger the + // read handler. + std::int32_t m_read = 0; + + // the sum of the lengths of all iovec in m_write_buffer + std::int32_t m_write_buffer_size = 0; + + // the number of bytes already written to packets + // from m_write_buffer + std::int32_t m_written = 0; + + // the sum of all packets stored in m_receive_buffer + std::int32_t m_receive_buffer_size = 0; + + // the sum of all buffers in m_read_buffer + std::int32_t m_read_buffer_size = 0; + + // max number of bytes to allocate for receive buffer + std::int32_t m_receive_buffer_capacity = 1024 * 1024; + + // this holds the 3 last delay measurements, + // these are the actual corrected delay measurements. + // the lowest of the 3 last ones is used in the congestion + // controller. This is to not completely close the cwnd + // by a single outlier. + std::array m_delay_sample_hist; + + // counters + std::uint32_t m_in_packets = 0; + std::uint32_t m_out_packets = 0; + + // the last send delay sample + std::int32_t m_send_delay = 0; + // the last receive delay sample + std::int32_t m_recv_delay = 0; + + // average RTT + sliding_average m_rtt; + + // if this is != 0, it means the upper layer provided a reason for why + // the connection is being closed. The reason is indicated by this + // non-zero value which is included in a packet header extension + close_reason_t m_close_reason = close_reason_t::none; + + // port of destination endpoint + std::uint16_t m_port = 0; + + std::uint16_t m_send_id; + std::uint16_t m_recv_id; + + // this is the ack we're sending back. We have + // received all packets up to this sequence number + std::uint16_t m_ack_nr = 0; + + // the sequence number of the next packet + // we'll send + std::uint16_t m_seq_nr = 0; + + // this is the sequence number of the packet that + // everything has been ACKed up to. Everything we've + // sent up to this point has been received by the other + // end. + std::uint16_t m_acked_seq_nr = 0; + + // each packet gets one chance of "fast resend". i.e. + // if we have multiple duplicate acks, we may send a + // packet immediately, if m_fast_resend_seq_nr is set + // to that packet's sequence number + std::uint16_t m_fast_resend_seq_nr = 0; + + // this is the sequence number of the FIN packet + // we've received. This sequence number is only + // valid if m_eof is true. We should not accept + // any packets beyond this sequence number from the + // other end + std::uint16_t m_eof_seq_nr = 0; + + // this is the lowest sequence number that, when lost, + // will cause the window size to be cut in half + std::uint16_t m_loss_seq_nr = 0; + + // the max number of bytes we can send in a packet + // including the header + std::uint16_t m_mtu = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER - 8 - 24 - 36; + + // the floor is the largest packet that we have + // been able to get through without fragmentation + std::uint16_t m_mtu_floor = TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + + // the ceiling is the largest packet that we might + // be able to get through without fragmentation. + // i.e. ceiling +1 is very likely to not get through + // or we have in fact experienced a drop or ICMP + // message indicating that it is + std::uint16_t m_mtu_ceiling = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER; + + // the sequence number of the probe in-flight + // this is 0 if there is no probe in flight + std::uint16_t m_mtu_seq = 0; + + // this is a counter of how many times the current m_acked_seq_nr + // has been ACKed. If it's ACKed more than 3 times, we assume the + // packet with the next sequence number has been lost, and we trigger + // a re-send. Obviously an ACK only counts as a duplicate as long as + // we have outstanding packets following it. + std::uint8_t m_duplicate_acks = 0; + + // the number of packet timeouts we've seen in a row + // this affects the packet timeout time + std::uint8_t m_num_timeouts = 0; + + // it's important that these match the enums in performance_counters for + // num_utp_idle etc. + enum state_t { + // not yet connected + UTP_STATE_NONE, + // sent a syn packet, not received any acks + UTP_STATE_SYN_SENT, + // syn-ack received and in normal operation + // of sending and receiving data + UTP_STATE_CONNECTED, + // fin sent, but all packets up to the fin packet + // have not yet been acked. We might still be waiting + // for a FIN from the other end + UTP_STATE_FIN_SENT, + + // ====== states beyond this point ===== + // === are considered closing states === + // === and will cause the socket to ==== + // ============ be deleted ============= + + // the socket has been gracefully disconnected + // and is waiting for the client to make a + // socket call so that we can communicate this + // fact and actually delete all the state, or + // there is an error on this socket and we're + // waiting to communicate this to the client in + // a callback. The error in either case is stored + // in m_error. If the socket has gracefully shut + // down, the error is error::eof. + UTP_STATE_ERROR_WAIT, + + // there are no more references to this socket + // and we can delete it + UTP_STATE_DELETE + }; + + // this is the cursor into m_delay_sample_hist + std::uint8_t m_delay_sample_idx:2; + + // the state the socket is in + std::uint8_t m_state:3; + + // this is set to true when we receive a fin + bool m_eof:1; + + // is this socket state attached to a user space socket? + bool m_attached:1; + + // this is true if nagle is enabled (which it is by default) + bool m_nagle:1; + + // this is true while the socket is in slow start mode. It's + // only in slow-start during the start-up phase. Slow start + // (contrary to what its name suggest) means that we're growing + // the congestion window (cwnd) exponentially rather than linearly. + // this is done at startup of a socket in order to find its + // link capacity faster. This behaves similar to TCP slow start + bool m_slow_start:1; + + // this is true as long as we have as many packets in + // flight as allowed by the congestion window (cwnd) + bool m_cwnd_full:1; + + // this is set to one if the current read operation + // has a null_buffer. i.e. we're not reading into a user-provided + // buffer, we're just signalling when there's something + // to read from our internal receive buffer + bool m_null_buffers:1; + + // this is set to true when this socket has added itself to + // the utp socket manager's list of deferred acks. Once the + // burst of incoming UDP packets is all drained, the utp socket + // manager will send acks for all sockets on this list. + bool m_deferred_ack:1; + + // this is true if this socket has subscribed to be notified + // when this receive round is done + bool m_subscribe_drained:1; + + // if this socket tries to send a packet via the utp socket + // manager, and it fails with EWOULDBLOCK, the socket + // is stalled and this is set. It's also added to a list + // of sockets in the utp_socket_manager to be notified of + // the socket being writable again + bool m_stalled:1; + + // this is false by default and set to true once we've received a non-SYN + // packet for this connection with a correct ack_nr, confirming that the + // other end is not spoofing its source IP + bool m_confirmed:1; +}; + +utp_socket_impl* construct_utp_impl(std::uint16_t recv_id + , std::uint16_t send_id, void* userdata + , utp_socket_manager& sm) +{ + return new utp_socket_impl(recv_id, send_id, userdata, sm); +} + +void detach_utp_impl(utp_socket_impl* s) +{ + s->detach(); +} + +void delete_utp_impl(utp_socket_impl* s) +{ + delete s; +} + +void utp_abort(utp_socket_impl* s) +{ + s->m_error = boost::asio::error::connection_aborted; + s->set_state(utp_socket_impl::UTP_STATE_ERROR_WAIT); + s->test_socket_state(); +} + +bool should_delete(utp_socket_impl* s) +{ + return s->should_delete(); +} + +bool bound_to_udp_socket(utp_socket_impl* s, std::weak_ptr sock) +{ + return s->m_sock.lock() == sock.lock(); +} + +void tick_utp_impl(utp_socket_impl* s, time_point now) +{ + s->tick(now); +} + +void utp_init_mtu(utp_socket_impl* s, int link_mtu, int utp_mtu) +{ + s->init_mtu(link_mtu, utp_mtu); +} + +void utp_init_socket(utp_socket_impl* s, std::weak_ptr sock) +{ + s->m_sock = std::move(sock); +} + +bool utp_incoming_packet(utp_socket_impl* s + , span p + , udp::endpoint const& ep, time_point const receive_time) +{ + return s->incoming_packet({reinterpret_cast(p.data()), p.size()} + , ep, receive_time); +} + +bool utp_match(utp_socket_impl* s, udp::endpoint const& ep, std::uint16_t const id) +{ + return s->m_recv_id == id + && s->m_port == ep.port() + && s->m_remote_address == ep.address(); +} + +udp::endpoint utp_remote_endpoint(utp_socket_impl* s) +{ + return udp::endpoint(s->m_remote_address, s->m_port); +} + +std::uint16_t utp_receive_id(utp_socket_impl* s) +{ + return s->m_recv_id; +} + +void utp_writable(utp_socket_impl* s) +{ + TORRENT_ASSERT(s->m_stalled); + s->m_stalled = false; + s->writable(); +} + +void utp_send_ack(utp_socket_impl* s) +{ + TORRENT_ASSERT(s->m_deferred_ack); + s->m_deferred_ack = false; + s->send_pkt(utp_socket_impl::pkt_ack); +} + +void utp_socket_drained(utp_socket_impl* s) +{ + s->m_subscribe_drained = false; + + // at this point, we know we won't receive any + // more packets this round. So, we may want to + // call the receive callback function to + // let the user consume it + + s->maybe_trigger_receive_callback(); + s->maybe_trigger_send_callback(); +} + +void utp_socket_impl::update_mtu_limits() +{ + INVARIANT_CHECK; + + if (m_mtu_floor > m_mtu_ceiling) m_mtu_floor = m_mtu_ceiling; + + m_mtu = (m_mtu_floor + m_mtu_ceiling) / 2; + + if ((m_cwnd >> 16) < m_mtu) m_cwnd = std::int64_t(m_mtu) * (1 << 16); + + UTP_LOGV("%8p: updating MTU to: %d [%d, %d]\n" + , static_cast(this), m_mtu, m_mtu_floor, m_mtu_ceiling); + + // clear the mtu probe sequence number since + // it was either dropped or acked + m_mtu_seq = 0; +} + +int utp_socket_state(utp_socket_impl const* s) +{ + return s->m_state; +} + +int utp_stream::send_delay() const +{ + return m_impl ? m_impl->m_send_delay : 0; +} + +int utp_stream::recv_delay() const +{ + return m_impl ? m_impl->m_recv_delay : 0; +} + +utp_stream::utp_stream(io_service& io_service) + : m_io_service(io_service) + , m_impl(nullptr) + , m_open(false) +{ +} + +utp_socket_impl* utp_stream::get_impl() +{ + return m_impl; +} + +void utp_stream::set_close_reason(close_reason_t code) +{ + if (!m_impl) return; + m_impl->set_close_reason(code); +} + +close_reason_t utp_stream::get_close_reason() +{ + return m_incoming_close_reason; +} + +void utp_stream::close() +{ + if (!m_impl) return; + if (!m_impl->destroy()) + { + if (!m_impl) return; + detach_utp_impl(m_impl); + m_impl = nullptr; + } +} + +std::size_t utp_stream::available() const +{ + return m_impl ? m_impl->available() : 0; +} + +utp_stream::endpoint_type utp_stream::remote_endpoint(error_code& ec) const +{ + if (!m_impl) + { + ec = boost::asio::error::not_connected; + return endpoint_type(); + } + return m_impl->remote_endpoint(ec); +} + +utp_stream::endpoint_type utp_stream::local_endpoint(error_code& ec) const +{ + if (m_impl == nullptr) + { + ec = boost::asio::error::not_connected; + return endpoint_type(); + } + + auto s = m_impl->m_sock.lock(); + if (!s) + { + ec = boost::asio::error::not_connected; + return endpoint_type(); + } + + udp::endpoint ep = s->get_local_endpoint(); + return endpoint_type(ep.address(), ep.port()); +} + +utp_stream::~utp_stream() +{ + if (m_impl) + { + UTP_LOGV("%8p: utp_stream destructed\n", static_cast(m_impl)); + m_impl->destroy(); + detach_utp_impl(m_impl); + } + + m_impl = nullptr; +} + +void utp_stream::set_impl(utp_socket_impl* impl) +{ + TORRENT_ASSERT(m_impl == nullptr); + TORRENT_ASSERT(!m_open); + m_impl = impl; + m_open = true; +} + +int utp_stream::read_buffer_size() const +{ + TORRENT_ASSERT(m_impl); + return m_impl->m_receive_buffer_size; +} + +void utp_stream::on_close_reason(void* self, close_reason_t reason) +{ + auto* s = static_cast(self); + + // it's possible the socket has been unlinked already, in which case m_impl + // will be nullptr + if (s->m_impl) + s->m_incoming_close_reason = reason; +} + +void utp_stream::on_read(void* self, std::size_t const bytes_transferred + , error_code const& ec, bool const shutdown) +{ + auto* s = static_cast(self); + + UTP_LOGV("%8p: calling read handler read:%d ec:%s shutdown:%d\n", static_cast(s->m_impl) + , int(bytes_transferred), ec.message().c_str(), shutdown); + + TORRENT_ASSERT(s->m_read_handler); + TORRENT_ASSERT(bytes_transferred > 0 || ec || s->m_impl->m_null_buffers); + s->m_io_service.post(std::bind(std::move(s->m_read_handler), ec, bytes_transferred)); + s->m_read_handler = nullptr; + if (shutdown && s->m_impl) + { + TORRENT_ASSERT(ec); + detach_utp_impl(s->m_impl); + s->m_impl = nullptr; + } +} + +void utp_stream::on_write(void* self, std::size_t const bytes_transferred + , error_code const& ec, bool const shutdown) +{ + auto* s = static_cast(self); + + UTP_LOGV("%8p: calling write handler written:%d ec:%s shutdown:%d\n" + , static_cast(s->m_impl) + , int(bytes_transferred), ec.message().c_str(), shutdown); + + TORRENT_ASSERT(s->m_write_handler); + TORRENT_ASSERT(bytes_transferred > 0 || ec); + s->m_io_service.post(std::bind(std::move(s->m_write_handler), ec, bytes_transferred)); + s->m_write_handler = nullptr; + if (shutdown && s->m_impl) + { + TORRENT_ASSERT(ec); + detach_utp_impl(s->m_impl); + s->m_impl = nullptr; + } +} + +void utp_stream::on_connect(void* self, error_code const& ec, bool const shutdown) +{ + auto* s = static_cast(self); + TORRENT_ASSERT(s); + + UTP_LOGV("%8p: calling connect handler ec:%s shutdown:%d\n" + , static_cast(s->m_impl), ec.message().c_str(), shutdown); + + TORRENT_ASSERT(s->m_connect_handler); + s->m_io_service.post(std::bind(std::move(s->m_connect_handler), ec)); + s->m_connect_handler = nullptr; + if (shutdown && s->m_impl) + { + TORRENT_ASSERT(ec); + detach_utp_impl(s->m_impl); + s->m_impl = nullptr; + } +} + +void utp_stream::add_read_buffer(void* buf, std::size_t const len) +{ + TORRENT_ASSERT(m_impl); + TORRENT_ASSERT(len < INT_MAX); + TORRENT_ASSERT(len > 0); + TORRENT_ASSERT(buf); + m_impl->m_read_buffer.emplace_back(buf, len); + m_impl->m_read_buffer_size += int(len); + + UTP_LOGV("%8p: add_read_buffer %d bytes\n", static_cast(m_impl), int(len)); +} + +// this is the wrapper to add a user provided write buffer to the +// utp_socket_impl. It makes sure the m_write_buffer_size is kept +// up to date +void utp_stream::add_write_buffer(void const* buf, std::size_t const len) +{ + TORRENT_ASSERT(m_impl); + TORRENT_ASSERT(len < INT_MAX); + TORRENT_ASSERT(len > 0); + TORRENT_ASSERT(buf); + +#if TORRENT_USE_ASSERTS + int write_buffer_size = 0; + for (auto const& i : m_impl->m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - int(i.len) > write_buffer_size); + write_buffer_size += int(i.len); + } + TORRENT_ASSERT(m_impl->m_write_buffer_size == write_buffer_size); +#endif + + m_impl->m_write_buffer.emplace_back(const_cast(buf), len); + m_impl->m_write_buffer_size += int(len); + +#if TORRENT_USE_ASSERTS + write_buffer_size = 0; + for (auto const& i : m_impl->m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - int(i.len) > write_buffer_size); + write_buffer_size += int(i.len); + } + TORRENT_ASSERT(m_impl->m_write_buffer_size == write_buffer_size); +#endif + + UTP_LOGV("%8p: add_write_buffer %d bytes\n", static_cast(m_impl), int(len)); +} + +// this is called when all user provided read buffers have been added +// and it's time to execute the async operation. The first thing we +// do is to copy any data stored in m_receive_buffer into the user +// provided buffer. This might be enough to in turn trigger the read +// handler immediately. +void utp_stream::issue_read() +{ + TORRENT_ASSERT(m_impl->m_userdata); + TORRENT_ASSERT(!m_impl->m_read_handler); + + m_impl->m_null_buffers = m_impl->m_read_buffer_size == 0; + + m_impl->m_read_handler = true; + if (m_impl->test_socket_state()) return; + + UTP_LOGV("%8p: new read handler. %d bytes in buffer\n" + , static_cast(m_impl), m_impl->m_receive_buffer_size); + + // so, the client wants to read. If we already + // have some data in the read buffer, move it into the + // client's buffer right away + + m_impl->m_read += int(read_some(false)); + m_impl->maybe_trigger_receive_callback(); +} + +std::size_t utp_stream::read_some(bool const clear_buffers) +{ + if (m_impl->m_receive_buffer_size == 0) + { + if (clear_buffers) + { + m_impl->m_read_buffer_size = 0; + m_impl->m_read_buffer.clear(); + } + return 0; + } + + auto target = m_impl->m_read_buffer.begin(); + + std::size_t ret = 0; + + int pop_packets = 0; + for (auto i = m_impl->m_receive_buffer.begin() + , end(m_impl->m_receive_buffer.end()); i != end;) + { + if (target == m_impl->m_read_buffer.end()) + { + UTP_LOGV(" No more target buffers: %d bytes left in buffer\n" + , m_impl->m_receive_buffer_size); + TORRENT_ASSERT(m_impl->m_read_buffer.empty()); + break; + } + +#if TORRENT_USE_INVARIANT_CHECKS + m_impl->check_receive_buffers(); +#endif + + packet* p = i->get(); + int to_copy = std::min(p->size - p->header_size, aux::numeric_cast(target->len)); + TORRENT_ASSERT(to_copy >= 0); + std::memcpy(target->buf, p->buf + p->header_size, std::size_t(to_copy)); + ret += std::size_t(to_copy); + target->buf = static_cast(target->buf) + to_copy; + TORRENT_ASSERT(target->len >= std::size_t(to_copy)); + target->len -= std::size_t(to_copy); + m_impl->m_receive_buffer_size -= to_copy; + TORRENT_ASSERT(m_impl->m_read_buffer_size >= to_copy); + m_impl->m_read_buffer_size -= to_copy; + p->header_size += std::uint16_t(to_copy); + if (target->len == 0) target = m_impl->m_read_buffer.erase(target); + +#if TORRENT_USE_INVARIANT_CHECKS + m_impl->check_receive_buffers(); +#endif + + TORRENT_ASSERT(m_impl->m_receive_buffer_size >= 0); + + // Consumed entire packet + if (p->header_size == p->size) + { + m_impl->release_packet(std::move(*i)); + i->reset(); + ++pop_packets; + ++i; + } + + if (m_impl->m_receive_buffer_size == 0) + { + UTP_LOGV("%8p: Didn't fill entire target: %d bytes left in buffer\n" + , static_cast(m_impl), m_impl->m_receive_buffer_size); + break; + } + } + // remove the packets from the receive_buffer that we already copied over + // and freed + m_impl->m_receive_buffer.erase(m_impl->m_receive_buffer.begin() + , m_impl->m_receive_buffer.begin() + pop_packets); + // we exited either because we ran out of bytes to copy + // or because we ran out of space to copy the bytes to + TORRENT_ASSERT(m_impl->m_receive_buffer_size == 0 + || m_impl->m_read_buffer.empty()); + + UTP_LOGV("%8p: %d packets moved from buffer to user space (%d bytes)\n" + , static_cast(m_impl), pop_packets, int(ret)); + + if (clear_buffers) + { + m_impl->m_read_buffer_size = 0; + m_impl->m_read_buffer.clear(); + } + TORRENT_ASSERT(ret > 0 || m_impl->m_null_buffers); + return ret; +} + +// this is called when all user provided write buffers have been +// added. Start trying to send packets with the payload immediately. +void utp_stream::issue_write() +{ + UTP_LOGV("%8p: new write handler. %d bytes to write\n" + , static_cast(m_impl), m_impl->m_write_buffer_size); + + TORRENT_ASSERT(m_impl->m_write_buffer_size > 0); + TORRENT_ASSERT(m_impl->m_write_handler == false); + TORRENT_ASSERT(m_impl->m_userdata); + + m_impl->m_write_handler = true; + m_impl->m_written = 0; + if (m_impl->test_socket_state()) return; + + // try to write. send_pkt returns false if there's + // no more payload to send or if the congestion window + // is full and we can't send more packets right now + while (m_impl->send_pkt()); + + // if there was an error in send_pkt(), m_impl may be + // 0 at this point + if (m_impl) m_impl->maybe_trigger_send_callback(); +} + +void utp_stream::do_connect(tcp::endpoint const& ep) +{ + int link_mtu, utp_mtu; + std::tie(link_mtu, utp_mtu) = m_impl->m_sm.mtu_for_dest(ep.address()); + m_impl->init_mtu(link_mtu, utp_mtu); + TORRENT_ASSERT(m_impl->m_connect_handler == false); + m_impl->m_remote_address = ep.address(); + m_impl->m_port = ep.port(); + + m_impl->m_connect_handler = true; + + if (m_impl->test_socket_state()) return; + m_impl->send_syn(); +} + +// =========== utp_socket_impl ============ + +utp_socket_impl::~utp_socket_impl() +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_attached); + TORRENT_ASSERT(!m_deferred_ack); + + m_sm.inc_stats_counter(counters::num_utp_idle + m_state, -1); + + UTP_LOGV("%8p: destroying utp socket state\n", static_cast(this)); + + // free any buffers we're holding + for (std::uint16_t i = std::uint16_t(m_inbuf.cursor()), end((m_inbuf.cursor() + + m_inbuf.capacity()) & ACK_MASK); + i != end; i = (i + 1) & ACK_MASK) + { + packet_ptr p = m_inbuf.remove(i); + release_packet(std::move(p)); + } + for (std::uint16_t i = std::uint16_t(m_outbuf.cursor()), end((m_outbuf.cursor() + + m_outbuf.capacity()) & ACK_MASK); + i != end; i = (i + 1) & ACK_MASK) + { + packet_ptr p = m_outbuf.remove(i); + release_packet(std::move(p)); + } + + for (auto& p : m_receive_buffer) + release_packet(std::move(p)); + + release_packet(std::move(m_nagle_packet)); + m_nagle_packet.reset(); +} + +bool utp_socket_impl::should_delete() const +{ + INVARIANT_CHECK; + + // if the socket state is not attached anymore we're free + // to delete it from the client's point of view. The other + // endpoint however might still need to be told that we're + // closing the socket. Only delete the state if we're not + // attached and we're in a state where the other end doesn't + // expect the socket to still be alive + // when m_stalled is true, it means the socket manager has a + // pointer to this socket, waiting for the UDP socket to + // become writable again. We have to wait for that, so that + // the pointer is removed from that queue. Otherwise we would + // leave a dangling pointer in the socket manager + bool ret = (m_state >= UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_NONE) + && !m_attached && !m_stalled; + + if (ret) + { + UTP_LOGV("%8p: should_delete() = true\n", static_cast(this)); + } + + return ret; +} + +void utp_socket_impl::maybe_trigger_receive_callback() +{ + INVARIANT_CHECK; + + if (m_read_handler == false) return; + + // nothing has been read or there's no outstanding read operation + if (m_null_buffers && m_receive_buffer_size == 0) return; + else if (!m_null_buffers && m_read == 0) return; + + UTP_LOGV("%8p: calling read handler read:%d\n", static_cast(this), m_read); + m_read_handler = false; + utp_stream::on_read(m_userdata, aux::numeric_cast(m_read), m_error, false); + m_read = 0; + m_read_buffer_size = 0; + m_read_buffer.clear(); +} + +void utp_socket_impl::maybe_trigger_send_callback() +{ + INVARIANT_CHECK; + + // nothing has been written or there's no outstanding write operation + if (m_written == 0 || m_write_handler == false) return; + + UTP_LOGV("%8p: calling write handler written:%d\n", static_cast(this), m_written); + + m_write_handler = false; + utp_stream::on_write(m_userdata, aux::numeric_cast(m_written), m_error, false); + m_written = 0; + m_write_buffer_size = 0; + m_write_buffer.clear(); +} + +void utp_socket_impl::set_close_reason(close_reason_t code) +{ +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: set_close_reason: %d\n" + , static_cast(this), static_cast(m_close_reason)); +#endif + m_close_reason = code; +} + +bool utp_socket_impl::destroy() +{ + INVARIANT_CHECK; + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: destroy state:%s (close-reason: %d)\n" + , static_cast(this), socket_state_names[m_state], int(m_close_reason)); +#endif + + if (m_userdata == nullptr) return false; + + if (m_state == UTP_STATE_CONNECTED) + send_fin(); + + bool cancelled = cancel_handlers(boost::asio::error::operation_aborted, true); + + m_userdata = nullptr; + + m_read_buffer.clear(); + m_read_buffer_size = 0; + + m_write_buffer.clear(); + m_write_buffer_size = 0; + + if ((m_state == UTP_STATE_ERROR_WAIT + || m_state == UTP_STATE_NONE + || m_state == UTP_STATE_SYN_SENT) && cancelled) + { + set_state(UTP_STATE_DELETE); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif + } + + return cancelled; + + // #error our end is closing. Wait for everything to be acked +} + +void utp_socket_impl::detach() +{ + INVARIANT_CHECK; + + UTP_LOGV("%8p: detach()\n", static_cast(this)); + m_attached = false; +} + +void utp_socket_impl::send_syn() +{ + INVARIANT_CHECK; + + m_seq_nr = std::uint16_t(random(0xffff)); + m_acked_seq_nr = (m_seq_nr - 1) & ACK_MASK; + m_loss_seq_nr = m_acked_seq_nr; + m_ack_nr = 0; + m_fast_resend_seq_nr = m_seq_nr; + + packet_ptr p = acquire_packet(sizeof(utp_header)); + p->size = sizeof(utp_header); + p->header_size = sizeof(utp_header); + p->num_transmissions = 0; + p->mtu_probe = false; +#if TORRENT_USE_ASSERTS + p->num_fast_resend = 0; +#endif + p->need_resend = false; + auto* h = reinterpret_cast(p->buf); + h->type_ver = (ST_SYN << 4) | 1; + h->extension = utp_no_extension; + // using recv_id here is intentional! This is an odd + // thing in uTP. The syn packet is sent with the connection + // ID that it expects to receive the syn ack on. All + // subsequent connection IDs will be this plus one. + h->connection_id = m_recv_id; + h->timestamp_difference_microseconds = m_reply_micro; + h->wnd_size = 0; + h->seq_nr = m_seq_nr; + h->ack_nr = 0; + + time_point const now = clock_type::now(); + p->send_time = now; + h->timestamp_microseconds = std::uint32_t( + total_microseconds(now.time_since_epoch()) & 0xffffffff); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: send_syn seq_nr:%d id:%d target:%s\n" + , static_cast(this), int(m_seq_nr), int(m_recv_id) + , print_endpoint(udp::endpoint(m_remote_address, m_port)).c_str()); +#endif + + error_code ec; + m_sm.send_packet(m_sock, udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(h) , sizeof(utp_header), ec); + + if (ec == error::would_block || ec == error::try_again) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: socket stalled\n", static_cast(this)); +#endif + if (!m_stalled) + { + m_stalled = true; + m_sm.subscribe_writable(this); + } + } + else if (ec) + { + release_packet(std::move(p)); + m_error = ec; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + + if (!m_stalled) + ++p->num_transmissions; + + TORRENT_ASSERT(!m_outbuf.at(m_seq_nr)); + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + TORRENT_ASSERT(p->buf == reinterpret_cast(h)); + m_outbuf.insert(m_seq_nr, std::move(p)); + + m_seq_nr = (m_seq_nr + 1) & ACK_MASK; + + TORRENT_ASSERT(!m_error); + set_state(UTP_STATE_SYN_SENT); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif +} + +// if a send ever failed with EWOULDBLOCK, we +// subscribe to the udp socket and will be +// signalled with this function. +void utp_socket_impl::writable() +{ +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: writable\n", static_cast(this)); +#endif + if (should_delete()) return; + + while(send_pkt()); + + maybe_trigger_send_callback(); +} + +void utp_socket_impl::send_fin() +{ + INVARIANT_CHECK; + + send_pkt(pkt_fin); + // unless there was an error, we're now + // in FIN-SENT state + if (!m_error) + set_state(UTP_STATE_FIN_SENT); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif +} + +void utp_socket_impl::send_reset(utp_header const* ph) +{ + INVARIANT_CHECK; + + utp_header h; + h.type_ver = (ST_RESET << 4) | 1; + h.extension = utp_no_extension; + h.connection_id = m_send_id; + h.timestamp_difference_microseconds = m_reply_micro; + h.wnd_size = 0; + h.seq_nr = std::uint16_t(random(0xffff)); + h.ack_nr = ph->seq_nr; + time_point const now = clock_type::now(); + h.timestamp_microseconds = std::uint32_t( + total_microseconds(now.time_since_epoch()) & 0xffffffff); + + UTP_LOGV("%8p: send_reset seq_nr:%d id:%d ack_nr:%d\n" + , static_cast(this), int(h.seq_nr), int(m_send_id), int(ph->seq_nr)); + + // ignore errors here + error_code ec; + m_sm.send_packet(m_sock, udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(&h), sizeof(h), ec); + if (ec) + { + UTP_LOGV("%8p: socket error: %s\n" + , static_cast(this) + , ec.message().c_str()); + } +} + +std::size_t utp_socket_impl::available() const +{ + return aux::numeric_cast(m_receive_buffer_size); +} + +void utp_socket_impl::parse_close_reason(std::uint8_t const* ptr, int const size) +{ + if (size != 4) return; + // skip reserved bytes + ptr += 2; + close_reason_t incoming_close_reason = static_cast(detail::read_uint16(ptr)); + + UTP_LOGV("%8p: incoming close_reason: %d\n" + , static_cast(this), int(incoming_close_reason)); + + if (m_userdata == nullptr || !m_attached) return; + + utp_stream::on_close_reason(m_userdata, incoming_close_reason); +} + +// returns (rtt, acked_bytes) +std::pair utp_socket_impl::parse_sack(std::uint16_t const packet_ack + , std::uint8_t const* ptr, int const size, time_point const now) +{ + INVARIANT_CHECK; + + if (size == 0) return { 0u, 0 }; + + // this is the sequence number the current bit represents + std::uint16_t ack_nr = (packet_ack + 2) & ACK_MASK; + +#if TORRENT_VERBOSE_UTP_LOG + std::string bitmask; + bitmask.reserve(size); + for (std::uint8_t const* b = ptr, *end = ptr + size; b != end; ++b) + { + unsigned char bitfield = unsigned(*b); + unsigned char mask = 1; + // for each bit + for (int i = 0; i < 8; ++i) + { + bitmask += (mask & bitfield) ? "1" : "0"; + mask <<= 1; + } + } + UTP_LOGV("%8p: got SACK first:%d %s our_seq_nr:%u fast_resend_seq_nr:%d\n" + , static_cast(this), ack_nr, bitmask.c_str(), m_seq_nr, m_fast_resend_seq_nr); +#endif + + aux::array resend; + int num_to_resend = 0; + + int acked_bytes = 0; + std::uint32_t min_rtt = std::numeric_limits::max(); + + // this was implicitly lost + if (!compare_less_wrap((packet_ack + 1) & ACK_MASK, m_fast_resend_seq_nr, ACK_MASK)) + { + resend[num_to_resend++] = (packet_ack + 1) & ACK_MASK; + } + + // for each byte + std::uint8_t const* const start = ptr; + std::uint8_t const* const end = ptr + size; + for (; ptr != end; ++ptr) + { + std::uint8_t bitfield = *ptr; + std::uint8_t mask = 1; + // for each bit + for (int i = 0; i < 8; ++i) + { + if (mask & bitfield) + { + // this bit was set, ack_nr was received + packet_ptr p = m_outbuf.remove(aux::numeric_cast(ack_nr)); + if (p) + { + acked_bytes += p->size - p->header_size; + // each ACKed packet counts as a duplicate ack + UTP_LOGV("%8p: duplicate_acks:%u fast_resend_seq_nr:%u\n" + , static_cast(this), m_duplicate_acks, m_fast_resend_seq_nr); + min_rtt = std::min(min_rtt, ack_packet(std::move(p), now, std::uint16_t(ack_nr))); + } + else + { + // this packet might have been acked by a previous + // selective ack + maybe_inc_acked_seq_nr(); + } + } + else if (!compare_less_wrap(ack_nr, m_fast_resend_seq_nr, ACK_MASK) + && num_to_resend < int(resend.size())) + { + resend[num_to_resend++] = ack_nr; + } + + mask <<= 1; + ack_nr = (ack_nr + 1) & ACK_MASK; + + // we haven't sent packets past this point. + // if there are any more bits set, we have to + // ignore them anyway + if (ack_nr == m_seq_nr) break; + } + if (ack_nr == m_seq_nr) break; + } + + if (m_outbuf.empty()) m_duplicate_acks = 0; + + // now, scan the bits in reverse, and count the number of ACKed packets. Only + // lost packets followed by 'dup_ack_limit' packets may be resent + // start with the sequence number represented by the last bit in the SACK + // bitmask + std::uint16_t last_resend = (packet_ack + 1 + size * 8) & ACK_MASK; + + // the number of acked packets past the fast re-send sequence number + // this is used to determine if we should trigger more fast re-sends + int dups = 0; + + for (std::uint8_t const* i = end; i != start; --i) + { + std::uint8_t const bitfield = i[-1]; + std::uint8_t mask = 0x80; + // for each bit + for (int k = 0; k < 8; ++k) + { + if (mask & bitfield) ++dups; + if (dups > dup_ack_limit) break; + last_resend = (last_resend - 1) & ACK_MASK; + mask >>= 1; + } + if (dups > dup_ack_limit) break; + } + + // we did not get enough packets acked in this message to warrant a resend + if (dups <= dup_ack_limit) + { + UTP_LOGV("%8p: only %d ACKs in SACK, requires more than %d to trigger fast retransmit\n" + , static_cast(this), dups, dup_ack_limit); + num_to_resend = 0; + } + + // now we need to (likely) prune the tail of the resend list, since all + // "unacked" packets that weren't followed by an acked one, don't count + while (num_to_resend > 0 && !compare_less_wrap(resend[num_to_resend - 1], last_resend, ACK_MASK)) + { + --num_to_resend; + } + + TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); + + bool cut_cwnd = true; + + // we received more than dup_ack_limit ACKs in this SACK message. + // trigger fast re-send. This is not an equal check because 3 identical ACKS + // are only 2 duplicates + for (int i = 0; i < num_to_resend; ++i) + { + std::uint16_t const pkt_seq = resend[i]; + + packet* p = m_outbuf.at(pkt_seq); + UTP_LOGV("%8p: Packet %d lost. (fast_resend_seq_nr:%d trigger fast-resend)\n" + , static_cast(this), pkt_seq, m_fast_resend_seq_nr); + if (!p) continue; + + // don't cut cwnd if the packet we lost was the MTU probe + // the logic to handle a lost MTU probe is in resend_packet() + if (cut_cwnd && (pkt_seq != m_mtu_seq || m_mtu_seq == 0)) + { + experienced_loss(pkt_seq, now); + cut_cwnd = false; + } + + if (resend_packet(p, true)) + { + m_duplicate_acks = 0; + m_fast_resend_seq_nr = (pkt_seq + 1) & ACK_MASK; + } + } + + return { min_rtt, acked_bytes }; +} + +// copies data from the write buffer into the packet +// pointed to by ptr +void utp_socket_impl::write_payload(std::uint8_t* ptr, int size) +{ + INVARIANT_CHECK; + +#if TORRENT_USE_ASSERTS + int write_buffer_size = 0; + for (auto const& i : m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - int(i.len) > write_buffer_size); + write_buffer_size += int(i.len); + } + TORRENT_ASSERT(m_write_buffer_size == write_buffer_size); +#endif + TORRENT_ASSERT(!m_write_buffer.empty() || size == 0); + TORRENT_ASSERT(m_write_buffer_size >= size); + auto i = m_write_buffer.begin(); + + if (size == 0) return; + + int buffers_to_clear = 0; + while (size > 0) + { + // i points to the iovec we'll start copying from + int to_copy = std::min(size, int(i->len)); + TORRENT_ASSERT(to_copy >= 0); + TORRENT_ASSERT(to_copy < INT_MAX / 2 && m_written < INT_MAX / 2); + std::memcpy(ptr, static_cast(i->buf), std::size_t(to_copy)); + size -= to_copy; + m_written += to_copy; + ptr += to_copy; + i->len -= std::size_t(to_copy); + TORRENT_ASSERT(m_write_buffer_size >= to_copy); + m_write_buffer_size -= to_copy; + i->buf = static_cast(i->buf) + to_copy; + if (i->len == 0) ++buffers_to_clear; + ++i; + } + + if (buffers_to_clear) + m_write_buffer.erase(m_write_buffer.begin() + , m_write_buffer.begin() + buffers_to_clear); + +#if TORRENT_USE_ASSERTS + write_buffer_size = 0; + for (auto const& j : m_write_buffer) + { + TORRENT_ASSERT(std::numeric_limits::max() - int(j.len) > write_buffer_size); + write_buffer_size += int(j.len); + } + TORRENT_ASSERT(m_write_buffer_size == write_buffer_size); +#endif +} + +void utp_socket_impl::subscribe_drained() +{ + INVARIANT_CHECK; + + if (m_subscribe_drained) return; + + UTP_LOGV("%8p: subscribe drained\n", static_cast(this)); + m_subscribe_drained = true; + m_sm.subscribe_drained(this); +} + +void utp_socket_impl::defer_ack() +{ + INVARIANT_CHECK; + + if (m_deferred_ack) return; + + UTP_LOGV("%8p: defer ack\n", static_cast(this)); + m_deferred_ack = true; + m_sm.defer_ack(this); +} + +void utp_socket_impl::remove_sack_header(packet* p) +{ + INVARIANT_CHECK; + + // remove the sack header + std::uint8_t* ptr = p->buf + sizeof(utp_header); + auto* h = reinterpret_cast(p->buf); + + TORRENT_ASSERT(h->extension == utp_sack); + + h->extension = ptr[0]; + int sack_size = ptr[1]; + TORRENT_ASSERT(h->extension == utp_no_extension + || h->extension == utp_close_reason); + + UTP_LOGV("%8p: removing SACK header, %d bytes\n" + , static_cast(this), sack_size + 2); + + TORRENT_ASSERT(p->size >= p->header_size); + TORRENT_ASSERT(p->header_size >= sizeof(utp_header) + aux::numeric_cast(sack_size) + 2); + std::memmove(ptr, ptr + sack_size + 2, p->size - p->header_size); + p->header_size -= std::uint16_t(sack_size + 2); + p->size -= std::uint16_t(sack_size + 2); +} + +// sends a packet, pulls data from the write buffer (if there's any) +// if ack is true, we need to send a packet regardless of if there's +// any data. Returns true if we could send more data (i.e. call +// send_pkt() again) +// returns true if there is more space for payload in our +// congestion window, false if there is no more space. +bool utp_socket_impl::send_pkt(int const flags) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + bool const force = (flags & pkt_ack) || (flags & pkt_fin); + +// TORRENT_ASSERT(m_state != UTP_STATE_FIN_SENT || (flags & pkt_ack)); + + // first see if we need to resend any packets + + // TODO: this loop is not very efficient. It could be fixed by having + // a separate list of sequence numbers that need resending + for (int i = (m_acked_seq_nr + 1) & ACK_MASK; i != m_seq_nr; i = (i + 1) & ACK_MASK) + { + packet* p = m_outbuf.at(aux::numeric_cast(i)); + if (!p) continue; + if (!p->need_resend) continue; + if (!resend_packet(p)) + { + // we couldn't resend the packet. It probably doesn't + // fit in our cwnd. If force is set, we need to continue + // to send our packet anyway, if we don't have force set, + // we might as well return + if (!force) return false; + // resend_packet might have failed + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return false; + break; + } + + // don't fast-resend this packet + if (m_fast_resend_seq_nr == i) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + } + + // MTU DISCOVERY + + // under these conditions, the next packet we send should be an MTU probe. + // MTU probes get to use the mid-point packet size, whereas other packets + // use a conservative packet size of the largest known to work. The reason + // for the cwnd condition is to make sure the probe is surrounded by non- + // probes, to be able to distinguish a loss of the probe vs. just loss in + // general. + bool const mtu_probe = (m_mtu_seq == 0 + && m_write_buffer_size >= m_mtu_floor * 3 + && m_seq_nr != 0 + && (m_cwnd >> 16) > m_mtu_floor * 3); + // for non MTU-probes, use the conservative packet size + int const effective_mtu = mtu_probe ? m_mtu : m_mtu_floor; + + std::uint32_t const close_reason = static_cast(m_close_reason); + + int sack = 0; + if (m_inbuf.size()) + { + const int max_sack_size = effective_mtu + - int(sizeof(utp_header)) + - 2 // for sack padding/header + - (close_reason ? 6 : 0); + + // the SACK bitfield should ideally fit all + // the pieces we have successfully received + sack = (m_inbuf.span() + 7) / 8; + if (sack > max_sack_size) sack = max_sack_size; + } + + int const header_size = int(sizeof(utp_header)) + + (sack ? sack + 2 : 0) + + (close_reason ? 6 : 0); + + // for non MTU-probes, use the conservative packet size + int payload_size = std::min(m_write_buffer_size + , effective_mtu - header_size); + TORRENT_ASSERT(payload_size >= 0); + + // if we have one MSS worth of data, make sure it fits in our + // congestion window and the advertised receive window from + // the other end. + if (m_bytes_in_flight + payload_size > std::min(int(m_cwnd >> 16) + , int(m_adv_wnd))) + { + // this means there's not enough room in the send window for + // another packet. We have to hold off sending this data. + // we still need to send an ACK though + // if we're trying to send a FIN, make an exception + if ((flags & pkt_fin) == 0) payload_size = 0; + + // we're constrained by the window size + m_cwnd_full = true; + + UTP_LOGV("%8p: no space in window send_buffer_size:%d cwnd:%d " + "adv_wnd:%u in-flight:%d mtu:%u\n" + , static_cast(this), m_write_buffer_size, int(m_cwnd >> 16) + , m_adv_wnd, m_bytes_in_flight, m_mtu); + + if (!force) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: skipping send seq_nr:%d ack_nr:%d " + "id:%d target:%s header_size:%d error:%s send_buffer_size:%d cwnd:%d " + "adv_wnd:%d in-flight:%d mtu:%u effective-mtu:%d\n" + , static_cast(this), int(m_seq_nr), int(m_ack_nr) + , m_send_id, print_endpoint(udp::endpoint(m_remote_address, m_port)).c_str() + , header_size, m_error.message().c_str(), m_write_buffer_size, int(m_cwnd >> 16) + , m_adv_wnd, m_bytes_in_flight, m_mtu, effective_mtu); +#endif + return false; + } + } + + // if we don't have any data to send, or can't send any data + // and we don't have any data to force, don't send a packet + if (payload_size == 0 && !force && !m_nagle_packet) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: skipping send (no payload and no force) seq_nr:%d ack_nr:%d " + "id:%d target:%s header_size:%d error:%s send_buffer_size:%d cwnd:%d " + "adv_wnd:%u in-flight:%d mtu:%u\n" + , static_cast(this), int(m_seq_nr), int(m_ack_nr) + , m_send_id, print_endpoint(udp::endpoint(m_remote_address, m_port)).c_str() + , header_size, m_error.message().c_str(), m_write_buffer_size, int(m_cwnd >> 16) + , m_adv_wnd, m_bytes_in_flight, m_mtu); +#endif + return false; + } + + packet_ptr p; + std::uint8_t* ptr = nullptr; + utp_header* h = nullptr; + + // payload size being zero means we're just sending + // an force. We should not pick up the nagle packet + if (!m_nagle_packet || (payload_size == 0 && force)) + { + p = acquire_packet(effective_mtu); + + if (payload_size) + { + m_sm.inc_stats_counter(counters::utp_payload_pkts_out); + } + + int const packet_size = header_size + payload_size; + p->size = std::uint16_t(packet_size); + p->header_size = std::uint16_t(packet_size - payload_size); + p->num_transmissions = 0; +#if TORRENT_USE_ASSERTS + p->num_fast_resend = 0; +#endif + p->mtu_probe = false; + p->need_resend = false; + ptr = p->buf; + h = reinterpret_cast(ptr); + ptr += sizeof(utp_header); + + h->extension = std::uint8_t(sack ? utp_sack + : close_reason ? utp_close_reason : utp_no_extension); + h->connection_id = m_send_id; + // seq_nr is ignored for ST_STATE packets, so it doesn't + // matter that we say this is a sequence number we haven't + // actually sent yet + h->seq_nr = m_seq_nr; + h->type_ver = std::uint8_t(((payload_size ? ST_DATA : ST_STATE) << 4) | 1); + + write_payload(p->buf + p->header_size, payload_size); + } + else + { + // pick up the nagle packet and keep adding bytes to it + p = std::move(m_nagle_packet); + m_nagle_packet.reset(); + + ptr = p->buf + sizeof(utp_header); + h = reinterpret_cast(p->buf); + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + + // if the packet has a selective force header, we'll need + // to update it + if (h->extension == utp_sack) + { + sack = ptr[1]; + // if we no longer have any out-of-order packets waiting + // to be delivered, there's no selective ack to be sent. + if (m_inbuf.size() == 0) + { + // we need to remove the sack header + remove_sack_header(p.get()); + sack = 0; + } + } + else + sack = 0; + + std::int32_t const size_left = std::min(p->allocated - p->size + , m_write_buffer_size); + + write_payload(p->buf + p->size, size_left); + p->size += std::uint16_t(size_left); + + if (size_left > 0) + { + UTP_LOGV("%8p: NAGLE appending %d bytes to nagle packet. new size: %d allocated: %d\n" + , static_cast(this), size_left, p->size, p->allocated); + } + + // did we fill up the whole mtu? + // if we didn't, we may still send it if there's + // no bytes in flight + if (m_bytes_in_flight > 0 + && p->size < std::min(p->allocated, m_mtu_floor) + && !force + && m_nagle) + { + // the packet is still not a full MSS, so put it back into the nagle + // packet + m_nagle_packet = std::move(p); + return false; + } + + payload_size = p->size - p->header_size; + } + + if (sack) + { + *ptr++ = std::uint8_t(close_reason ? utp_close_reason : utp_no_extension); + *ptr++ = std::uint8_t(sack); // bytes for SACK bitfield + write_sack(ptr, sack); + ptr += sack; + TORRENT_ASSERT(ptr <= p->buf + p->header_size); + } + + if (close_reason) + { + *ptr++ = utp_no_extension; + *ptr++ = 4; + detail::write_uint32(close_reason, ptr); + } + + if (m_bytes_in_flight > 0 + && p->size < p->allocated + && !force + && m_nagle) + { + // this is nagle. If we don't have a full packet + // worth of payload to send AND we have at least + // one outstanding packet, hold off. Once the + // outstanding packet is acked, we'll send this + // payload + UTP_LOGV("%8p: NAGLE not enough payload send_buffer_size:%d cwnd:%d " + "adv_wnd:%u in-flight:%d mtu:%d effective_mtu:%d\n" + , static_cast(this), m_write_buffer_size, int(m_cwnd >> 16) + , m_adv_wnd, m_bytes_in_flight, m_mtu, effective_mtu); + TORRENT_ASSERT(!m_nagle_packet); + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + m_nagle_packet = std::move(p); + return false; + } + + // for ST_DATA packets, payload size is 0. Such packets do not have unique + // sequence numbers and should never be used as mtu probes + if ((mtu_probe || p->mtu_probe) && payload_size > m_mtu_floor) + { + p->mtu_probe = true; + m_mtu_seq = m_seq_nr; + } + else + { + p->mtu_probe = false; + } + + h->timestamp_difference_microseconds = m_reply_micro; + h->wnd_size = static_cast(std::max( + m_receive_buffer_capacity - m_buffered_incoming_bytes + - m_receive_buffer_size, 0)); + h->ack_nr = m_ack_nr; + + // if this is a FIN packet, override the type + if (flags & pkt_fin) + h->type_ver = (ST_FIN << 4) | 1; + + // fill in the timestamp as late as possible + time_point const now = clock_type::now(); + p->send_time = now; + h->timestamp_microseconds = std::uint32_t( + total_microseconds(now.time_since_epoch()) & 0xffffffff); + +#if TORRENT_UTP_LOG + UTP_LOG("%8p: sending packet seq_nr:%d ack_nr:%d type:%s " + "id:%d target:%s size:%d error:%s send_buffer_size:%d cwnd:%d " + "adv_wnd:%d in-flight:%d mtu:%d timestamp:%u time_diff:%d " + "mtu_probe:%d extension:%d\n" + , static_cast(this), int(h->seq_nr), int(h->ack_nr), packet_type_names[h->get_type()] + , m_send_id, print_endpoint(udp::endpoint(m_remote_address, m_port)).c_str() + , p->size, m_error.message().c_str(), m_write_buffer_size, int(m_cwnd >> 16) + , m_adv_wnd, m_bytes_in_flight, m_mtu, std::uint32_t(h->timestamp_microseconds) + , std::uint32_t(h->timestamp_difference_microseconds), int(p->mtu_probe) + , h->extension); +#endif + + error_code ec; + m_sm.send_packet(m_sock, udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(h), p->size, ec + , p->mtu_probe ? udp_socket::dont_fragment : udp_send_flags_t{}); + + ++m_out_packets; + m_sm.inc_stats_counter(counters::utp_packets_out); + + if (ec == error::message_size) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: error sending packet: %s\n" + , static_cast(this) + , ec.message().c_str()); +#endif + // if we fail even though this is not a probe, we're screwed + // since we'd have to repacketize + TORRENT_ASSERT(p->mtu_probe); + m_mtu_ceiling = p->size - 1; + if (m_mtu_floor > m_mtu_ceiling) m_mtu_floor = m_mtu_ceiling; + update_mtu_limits(); + // resend the packet immediately without + // it being an MTU probe + p->mtu_probe = false; + m_mtu_seq = 0; + ec.clear(); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: re-sending\n", static_cast(this)); +#endif + m_sm.send_packet(m_sock, udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(h), p->size, ec, {}); + } + + if (ec == error::would_block || ec == error::try_again) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: socket stalled\n", static_cast(this)); +#endif + if (!m_stalled) + { + m_stalled = true; + m_sm.subscribe_writable(this); + } + } + else if (ec) + { + m_error = ec; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + release_packet(std::move(p)); + return false; + } + + if (!m_stalled) + ++p->num_transmissions; + + // if we have payload, we need to save the packet until it's acked + // and progress m_seq_nr + if (p->size > p->header_size) + { + // if we're sending a payload packet, there should not + // be a nagle packet waiting for more data + TORRENT_ASSERT(!m_nagle_packet); + + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + + // 0 is a special sequence number, since it's also used as "uninitialized". + // we never send an mtu probe for sequence number 0 + TORRENT_ASSERT(p->mtu_probe == (m_seq_nr == m_mtu_seq) + || m_seq_nr == 0); + + // release the buffer, we're saving it in the circular + // buffer of outgoing packets + int const new_in_flight = p->size - p->header_size; + packet_ptr old = m_outbuf.insert(m_seq_nr, std::move(p)); + if (old) + { +// TORRENT_ASSERT(reinterpret_cast(old->buf)->seq_nr == m_seq_nr); + if (!old->need_resend) m_bytes_in_flight -= old->size - old->header_size; + release_packet(std::move(old)); + } + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + m_seq_nr = (m_seq_nr + 1) & ACK_MASK; + TORRENT_ASSERT(payload_size >= 0); + m_bytes_in_flight += new_in_flight; + } + else + { + TORRENT_ASSERT(h->seq_nr == m_seq_nr); + } + + // if the socket is stalled, always return false, don't + // try to write more packets. We'll keep writing once + // the underlying UDP socket becomes writable + return m_write_buffer_size > 0 && !m_cwnd_full && !m_stalled; +} + +// size is in bytes +void utp_socket_impl::write_sack(std::uint8_t* buf, int const size) const +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(m_inbuf.size()); + int ack_nr = (m_ack_nr + 2) & ACK_MASK; + std::uint8_t* end = buf + size; + + for (; buf != end; ++buf) + { + *buf = 0; + int mask = 1; + for (int i = 0; i < 8; ++i) + { + if (m_inbuf.at(aux::numeric_cast(ack_nr))) *buf |= mask; + mask <<= 1; + ack_nr = (ack_nr + 1) & ACK_MASK; + } + } +} + +bool utp_socket_impl::resend_packet(packet* p, bool fast_resend) +{ + INVARIANT_CHECK; + + // for fast re-sends the packet hasn't been marked as needing resending + TORRENT_ASSERT(p->need_resend || fast_resend); + + if (m_error) return false; + + if (((m_acked_seq_nr + 1) & ACK_MASK) == m_mtu_seq + && m_mtu_seq != 0) + { + m_mtu_seq = 0; + p->mtu_probe = false; + // we got multiple acks for the packet before our probe, assume + // it was dropped because it was too big + m_mtu_ceiling = p->size - 1; + update_mtu_limits(); + } + + // we can only resend the packet if there's + // enough space in our congestion window + // since we can't re-packetize, some packets that are + // larger than the congestion window must be allowed through + // but only if we don't have any outstanding bytes + int const window_size_left = std::min(int(m_cwnd >> 16), int(m_adv_wnd)) - m_bytes_in_flight; + if (!fast_resend + && p->size - p->header_size > window_size_left + && m_bytes_in_flight > 0) + { + m_cwnd_full = true; + return false; + } + + // plus one since we have fast-resend as well, which doesn't + // necessarily trigger by a timeout + TORRENT_ASSERT(p->num_transmissions < m_sm.num_resends() + 1); + + TORRENT_ASSERT(p->size - p->header_size >= 0); + if (p->need_resend) m_bytes_in_flight += p->size - p->header_size; + + m_sm.inc_stats_counter(counters::utp_packet_resend); + if (fast_resend) m_sm.inc_stats_counter(counters::utp_fast_retransmit); + +#if TORRENT_USE_ASSERTS + if (fast_resend) ++p->num_fast_resend; +#endif + p->need_resend = false; + auto* h = reinterpret_cast(p->buf); + // update packet header + h->timestamp_difference_microseconds = m_reply_micro; + p->send_time = clock_type::now(); + h->timestamp_microseconds = std::uint32_t( + total_microseconds(p->send_time.time_since_epoch()) & 0xffffffff); + + // if the packet has a selective ack header, we'll need + // to update it + if (h->extension == utp_sack && h->ack_nr != m_ack_nr) + { + std::uint8_t* ptr = p->buf + sizeof(utp_header); + int sack_size = ptr[1]; + if (m_inbuf.size()) + { + // update the sack header + write_sack(ptr + 2, sack_size); + TORRENT_ASSERT(ptr + sack_size + 2 <= p->buf + p->header_size); + } + else + { + remove_sack_header(p); + } + } + + h->ack_nr = m_ack_nr; + + error_code ec; + m_sm.send_packet(m_sock, udp::endpoint(m_remote_address, m_port) + , reinterpret_cast(p->buf), p->size, ec); + ++m_out_packets; + m_sm.inc_stats_counter(counters::utp_packets_out); + + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: re-sending packet seq_nr:%d ack_nr:%d type:%s " + "id:%d target:%s size:%d error:%s send_buffer_size:%d cwnd:%d " + "adv_wnd:%d in-flight:%d mtu:%d timestamp:%u time_diff:%d\n" + , static_cast(this), int(h->seq_nr), int(h->ack_nr), packet_type_names[h->get_type()] + , m_send_id, print_endpoint(udp::endpoint(m_remote_address, m_port)).c_str() + , p->size, ec.message().c_str(), m_write_buffer_size, int(m_cwnd >> 16) + , m_adv_wnd, m_bytes_in_flight, m_mtu, std::uint32_t(h->timestamp_microseconds) + , std::uint32_t(h->timestamp_difference_microseconds)); +#endif + + if (ec == error::would_block || ec == error::try_again) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: socket stalled\n", static_cast(this)); +#endif + if (!m_stalled) + { + m_stalled = true; + m_sm.subscribe_writable(this); + } + } + else if (ec) + { + m_error = ec; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return false; + } + + if (!m_stalled) + ++p->num_transmissions; + + return !m_stalled; +} + +void utp_socket_impl::experienced_loss(std::uint32_t const seq_nr, time_point const now) +{ + INVARIANT_CHECK; + + // the window size could go below one MMS here, if it does, + // we'll get a timeout in about one second + + m_sm.inc_stats_counter(counters::utp_packet_loss); + + // since loss often comes in bursts, we only cut the + // window in half once per RTT. This is implemented + // by limiting which packets can cause us to cut the + // window size. The first packet that's lost will + // update the limit to the last sequence number we sent. + // i.e. only packet sent after this loss can cause another + // window size cut. The +1 is to turn the comparison into + // less than or equal to. If we experience loss of the + // same packet again, ignore it. + if (compare_less_wrap(seq_nr, m_loss_seq_nr + 1, ACK_MASK)) return; + + // don't reduce cwnd more than once every 100ms + if (m_next_loss >= now) return; + + m_next_loss = now + milliseconds(m_sm.cwnd_reduce_timer()); + + // cut window size in 2 + m_cwnd = std::max(m_cwnd * m_sm.loss_multiplier() / 100 + , std::int64_t(m_mtu) * (1 << 16)); + m_loss_seq_nr = m_seq_nr; + UTP_LOGV("%8p: Lost packet %d caused cwnd cut. m_loss_seq_nr:%d\n" + , static_cast(this), seq_nr, m_seq_nr); + + // if we happen to be in slow-start mode, we need to leave it + // note that we set ssthres to the window size _after_ reducing it. Next slow + // start should end before we over shoot. + if (m_slow_start) + { + m_ssthres = std::int32_t(m_cwnd >> 16); + m_slow_start = false; + UTP_LOGV("%8p: experienced loss, slow_start -> 0 ssthres:%d\n" + , static_cast(this), m_ssthres); + } +} + +void utp_socket_impl::set_state(int s) +{ + if (s == m_state) return; + + m_sm.inc_stats_counter(counters::num_utp_idle + m_state, -1); + m_state = std::uint8_t(s); + m_sm.inc_stats_counter(counters::num_utp_idle + m_state, 1); +} + +void utp_socket_impl::maybe_inc_acked_seq_nr() +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + bool incremented = false; + // don't pass m_seq_nr, since we move into sequence + // numbers that haven't been sent yet, and aren't + // supposed to be in m_outbuf + // if the slot in m_outbuf is 0, it means the + // packet has been ACKed and removed from the send buffer + while (((m_acked_seq_nr + 1) & ACK_MASK) != m_seq_nr + && m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) == nullptr) + { + // increment the fast resend sequence number + if (m_fast_resend_seq_nr == m_acked_seq_nr) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + + m_acked_seq_nr = (m_acked_seq_nr + 1) & ACK_MASK; + incremented = true; + } + + if (!incremented) return; + + // update loss seq number if it's less than the packet + // that was just acked. If loss seq nr is greater, it suggests + // that we're still in a window that has experienced loss + if (compare_less_wrap(m_loss_seq_nr, m_acked_seq_nr, ACK_MASK)) + m_loss_seq_nr = m_acked_seq_nr; + m_duplicate_acks = 0; +} + +// returns RTT +std::uint32_t utp_socket_impl::ack_packet(packet_ptr p, time_point const receive_time + , std::uint16_t seq_nr) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(p); + + // verify that the packet we're removing was in fact sent + // with the sequence number we expect +// TORRENT_ASSERT(reinterpret_cast(p->buf)->seq_nr == seq_nr); + + if (!p->need_resend) + { + TORRENT_ASSERT(m_bytes_in_flight >= p->size - p->header_size); + m_bytes_in_flight -= p->size - p->header_size; + } + + if (seq_nr == m_mtu_seq && m_mtu_seq != 0) + { + TORRENT_ASSERT(p->mtu_probe); + // our mtu probe was acked! + m_mtu_floor = std::max(m_mtu_floor, p->size); + update_mtu_limits(); + } + + // increment the acked sequence number counter + maybe_inc_acked_seq_nr(); + + std::uint32_t rtt = std::uint32_t(total_microseconds(receive_time - p->send_time)); + if (receive_time < p->send_time) + { + // this means our clock is not monotonic. Just assume the RTT was 100 ms + rtt = 100000; + + // the clock for this platform is not monotonic! + TORRENT_ASSERT_FAIL(); + } + + UTP_LOGV("%8p: acked packet %d (%d bytes) (rtt:%u)\n" + , static_cast(this), seq_nr, p->size - p->header_size, rtt / 1000); + + m_rtt.add_sample(rtt / 1000); + release_packet(std::move(p)); + return rtt; +} + +void utp_socket_impl::incoming(std::uint8_t const* buf, int size, packet_ptr p + , time_point /* now */) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + while (!m_read_buffer.empty()) + { + UTP_LOGV("%8p: incoming: have user buffer (%d)\n", static_cast(this), m_read_buffer_size); + if (p) + { + buf = p->buf + p->header_size; + TORRENT_ASSERT(p->size - p->header_size >= size); + } + iovec_t* target = &m_read_buffer.front(); + + int const to_copy = std::min(size, aux::numeric_cast(target->len)); + TORRENT_ASSERT(to_copy >= 0); + std::memcpy(target->buf, buf, std::size_t(to_copy)); + m_read += to_copy; + target->buf = reinterpret_cast(target->buf) + to_copy; + target->len -= std::size_t(to_copy); + buf += to_copy; + UTP_LOGV("%8p: copied %d bytes into user receive buffer\n", static_cast(this), to_copy); + TORRENT_ASSERT(m_read_buffer_size >= to_copy); + m_read_buffer_size -= to_copy; + size -= to_copy; + if (target->len == 0) m_read_buffer.erase(m_read_buffer.begin()); + if (p) + { + p->header_size += std::uint16_t(to_copy); + TORRENT_ASSERT(p->header_size <= p->size); + } + + if (size == 0) + { + TORRENT_ASSERT(!p || p->header_size == p->size); + release_packet(std::move(p)); + return; + } + } + + TORRENT_ASSERT(m_read_buffer_size == 0); + + if (!p) + { + TORRENT_ASSERT(buf); + p = acquire_packet(size); + p->size = std::uint16_t(size); + p->header_size = 0; + std::memcpy(p->buf, buf, aux::numeric_cast(size)); + } + // save this packet until the client issues another read + m_receive_buffer_size += p->size - p->header_size; + m_receive_buffer.emplace_back(std::move(p)); + + UTP_LOGV("%8p: incoming: saving packet in receive buffer (%d)\n", static_cast(this), m_receive_buffer_size); + +#if TORRENT_USE_INVARIANT_CHECKS + check_receive_buffers(); +#endif +} + +bool utp_socket_impl::cancel_handlers(error_code const& ec, bool shutdown) +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(ec); + bool ret = m_read_handler || m_write_handler || m_connect_handler; + + // calling the callbacks with m_userdata being 0 will just crash + TORRENT_ASSERT((ret && m_userdata != nullptr) || !ret); + + bool read = m_read_handler; + bool write = m_write_handler; + bool connect = m_connect_handler; + m_read_handler = false; + m_write_handler = false; + m_connect_handler = false; + + if (read) utp_stream::on_read(m_userdata, 0, ec, shutdown); + if (write) utp_stream::on_write(m_userdata, 0, ec, shutdown); + if (connect) utp_stream::on_connect(m_userdata, ec, shutdown); + return ret; +} + +bool utp_socket_impl::consume_incoming_data( + utp_header const* ph, std::uint8_t const* ptr, int const payload_size + , time_point const now) +{ + INVARIANT_CHECK; + + if (ph->get_type() != ST_DATA) return false; + + if (m_eof && m_ack_nr == m_eof_seq_nr) + { + // What?! We've already received a FIN and everything up + // to it has been acked. Ignore this packet + UTP_LOG("%8p: ERROR: ignoring packet on shut down socket\n" + , static_cast(this)); + return true; + } + + if (m_read_buffer_size == 0 + && m_receive_buffer_size >= m_receive_buffer_capacity - m_buffered_incoming_bytes) + { + // if we don't have a buffer from the upper layer, and the + // number of queued up bytes, waiting for the upper layer, + // exceeds the advertised receive window, start ignoring + // more data packets + UTP_LOG("%8p: ERROR: our advertized window is not honored. " + "recv_buf: %d buffered_in: %d max_size: %d\n" + , static_cast(this), m_receive_buffer_size, m_buffered_incoming_bytes, m_receive_buffer_capacity); + return false; + } + + if (ph->seq_nr == ((m_ack_nr + 1) & ACK_MASK)) + { + TORRENT_ASSERT(m_inbuf.at(m_ack_nr) == nullptr); + + if (m_buffered_incoming_bytes + m_receive_buffer_size + payload_size > m_receive_buffer_capacity) + { + UTP_LOGV("%8p: other end is not honoring our advertised window, dropping packet\n" + , static_cast(this)); + return true; + } + + // we received a packet in order + incoming(ptr, payload_size, packet_ptr(), now); + m_ack_nr = (m_ack_nr + 1) & ACK_MASK; + + // If this packet was previously in the reorder buffer + // it would have been acked when m_ack_nr-1 was acked. + TORRENT_ASSERT(!m_inbuf.at(m_ack_nr)); + + UTP_LOGV("%8p: remove inbuf: %d (%d)\n" + , static_cast(this), m_ack_nr, int(m_inbuf.size())); + + for (;;) + { + int const next_ack_nr = (m_ack_nr + 1) & ACK_MASK; + + packet_ptr p = m_inbuf.remove(aux::numeric_cast(next_ack_nr)); + + if (!p) break; + + TORRENT_ASSERT(p->size >= p->header_size); + int const size = p->size - p->header_size; + m_buffered_incoming_bytes -= size; + incoming(nullptr, size, std::move(p), now); + + m_ack_nr = std::uint16_t(next_ack_nr); + + UTP_LOGV("%8p: reordered remove inbuf: %d (%d)\n" + , static_cast(this), m_ack_nr, int(m_inbuf.size())); + } + } + else + { + // this packet was received out of order. Stick it in the + // reorder buffer until it can be delivered in order + + // have we already received this packet and passed it on + // to the client? + if (!compare_less_wrap(m_ack_nr, ph->seq_nr, ACK_MASK)) + { + UTP_LOGV("%8p: already received seq_nr: %d\n" + , static_cast(this), int(ph->seq_nr)); + return true; + } + + // do we already have this packet? If so, just ignore it + if (m_inbuf.at(ph->seq_nr)) + { + UTP_LOGV("%8p: already received seq_nr: %d\n" + , static_cast(this), int(ph->seq_nr)); + return true; + } + + if (m_buffered_incoming_bytes + m_receive_buffer_size + payload_size > m_receive_buffer_capacity) + { + UTP_LOGV("%8p: other end is not honoring our advertised window, dropping packet %d\n" + , static_cast(this), int(ph->seq_nr)); + return true; + } + + // we don't need to save the packet header, just the payload + packet_ptr p = acquire_packet(payload_size); + p->size = std::uint16_t(payload_size); + p->header_size = 0; + p->num_transmissions = 0; +#if TORRENT_USE_ASSERTS + p->num_fast_resend = 0; +#endif + p->need_resend = false; + std::memcpy(p->buf, ptr, aux::numeric_cast(payload_size)); + m_buffered_incoming_bytes += p->size; + m_inbuf.insert(ph->seq_nr, std::move(p)); + + UTP_LOGV("%8p: out of order. insert inbuf: %d (%d) m_ack_nr: %d\n" + , static_cast(this), int(ph->seq_nr), int(m_inbuf.size()), m_ack_nr); + } + + return false; +} + +// returns true of the socket was closed +bool utp_socket_impl::test_socket_state() +{ + INVARIANT_CHECK; + + // if the socket is in a state where it's dead, just waiting to + // tell the client that it's closed. Do that and transition into + // the deleted state, where it will be deleted + // it might be possible to get here twice, in which we need to + // cancel any new handlers as well, even though we're already + // in the delete state + if (!m_error) return false; + TORRENT_ASSERT(m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE); + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s error:%s\n" + , static_cast(this), socket_state_names[m_state], m_error.message().c_str()); +#endif + + if (cancel_handlers(m_error, true)) + { + set_state(UTP_STATE_DELETE); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif + return true; + } + return false; +} + +void utp_socket_impl::init_mtu(int const link_mtu, int utp_mtu) +{ + INVARIANT_CHECK; + + if (link_mtu > TORRENT_ETHERNET_MTU) + { + // we can't use larger packets than this since we're + // not allocating any more memory for socket buffers + int const decrease = link_mtu - TORRENT_ETHERNET_MTU; + utp_mtu -= decrease; + } + + // set the ceiling to what we found out from the interface + m_mtu_ceiling = std::uint16_t(utp_mtu); + + // start in the middle of the PMTU search space + m_mtu = (m_mtu_ceiling + m_mtu_floor) / 2; + if (m_mtu > m_mtu_ceiling) m_mtu = m_mtu_ceiling; + if (m_mtu_floor > utp_mtu) m_mtu_floor = std::uint16_t(utp_mtu); + + // if the window size is smaller than one packet size + // set it to one + if ((m_cwnd >> 16) < m_mtu) m_cwnd = std::int64_t(m_mtu) * (1 << 16); + + UTP_LOGV("%8p: initializing MTU to: %d [%d, %d]\n" + , static_cast(this), m_mtu, m_mtu_floor, m_mtu_ceiling); +} + +// return false if this is an invalid packet +bool utp_socket_impl::incoming_packet(span buf + , udp::endpoint const& ep, time_point receive_time) +{ + INVARIANT_CHECK; + + auto const* ph = reinterpret_cast(buf.data()); + m_sm.inc_stats_counter(counters::utp_packets_in); + + if (buf.size() < int(sizeof(utp_header))) + { + UTP_LOG("%8p: ERROR: incoming packet size too small:%d (ignored)\n" + , static_cast(this), int(buf.size())); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + if (ph->get_version() != 1) + { + UTP_LOG("%8p: ERROR: incoming packet version:%d (ignored)\n" + , static_cast(this), int(ph->get_version())); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + // SYN packets have special (reverse) connection ids + if (ph->get_type() != ST_SYN && ph->connection_id != m_recv_id) + { + UTP_LOG("%8p: ERROR: incoming packet id:%d expected:%d (ignored)\n" + , static_cast(this), int(ph->connection_id), int(m_recv_id)); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + if (ph->get_type() >= NUM_TYPES) + { + UTP_LOG("%8p: ERROR: incoming packet type:%d (ignored)\n" + , static_cast(this), int(ph->get_type())); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return false; + } + + if (m_state == UTP_STATE_NONE && ph->get_type() == ST_SYN) + { + m_remote_address = ep.address(); + m_port = ep.port(); + } + + if (m_state != UTP_STATE_NONE && ph->get_type() == ST_SYN) + { + UTP_LOG("%8p: ERROR: incoming packet type:ST_SYN (ignored)\n" + , static_cast(this)); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return true; + } + + bool step = false; + if (receive_time - m_last_history_step > minutes(1)) + { + step = true; + m_last_history_step = receive_time; + } + + // this is the difference between their send time and our receive time + // 0 means no sample yet + std::uint32_t their_delay = 0; + if (ph->timestamp_microseconds != 0) + { + std::uint32_t timestamp = std::uint32_t(total_microseconds( + receive_time.time_since_epoch()) & 0xffffffff); + m_reply_micro = timestamp - ph->timestamp_microseconds; + std::uint32_t const prev_base = m_their_delay_hist.initialized() ? m_their_delay_hist.base() : 0; + their_delay = m_their_delay_hist.add_sample(m_reply_micro, step); + int const base_change = int(m_their_delay_hist.base() - prev_base); + UTP_LOGV("%8p: their_delay::add_sample:%u prev_base:%u new_base:%u\n" + , static_cast(this), m_reply_micro, prev_base, m_their_delay_hist.base()); + + if (prev_base && base_change < 0 && base_change > -10000 && m_delay_hist.initialized()) + { + // their base delay went down. This is caused by clock drift. To compensate, + // adjust our base delay upwards + // don't adjust more than 10 ms. If the change is that big, something is probably wrong + m_delay_hist.adjust_base(-base_change); + } + + UTP_LOGV("%8p: incoming packet reply_micro:%u base_change:%d\n" + , static_cast(this), m_reply_micro, prev_base ? base_change : 0); + } + + // is this ACK valid? If the other end is ACKing + // a packet that hasn't been sent yet + // just ignore it. A 3rd party could easily inject a packet + // like this in a stream, don't sever it because of it. + // since m_seq_nr is the sequence number of the next packet + // we'll send (and m_seq_nr-1 was the last packet we sent), + // if the ACK we got is greater than the last packet we sent + // something is wrong. + // If our state is state_none, this packet must be a syn packet + // and the ack_nr should be ignored + std::uint16_t const cmp_seq_nr = + (m_state == UTP_STATE_SYN_SENT && ph->get_type() == ST_STATE) + ? m_seq_nr : (m_seq_nr - 1) & ACK_MASK; + + if ((m_state != UTP_STATE_NONE || ph->get_type() != ST_SYN) + && (compare_less_wrap(cmp_seq_nr, ph->ack_nr, ACK_MASK) + || compare_less_wrap(ph->ack_nr, m_acked_seq_nr + - dup_ack_limit, ACK_MASK))) + { + UTP_LOG("%8p: ERROR: incoming packet ack_nr:%d our seq_nr:%d our " + "acked_seq_nr:%d (ignored)\n" + , static_cast(this), int(ph->ack_nr), m_seq_nr, m_acked_seq_nr); + m_sm.inc_stats_counter(counters::utp_redundant_pkts_in); + return true; + } + + // check to make sure the sequence number of this packet + // is reasonable. If it's a data packet and we've already + // received it, ignore it. This is either a stray old packet + // that finally made it here (after having been re-sent) or + // an attempt to interfere with the connection from a 3rd party + // in both cases, we can safely ignore the timestamp and ACK + // information in this packet +/* + // even if we've already received this packet, we need to + // send another ack to it, since it may be a resend caused by + // our ack getting dropped + if (m_state != UTP_STATE_SYN_SENT + && ph->get_type() == ST_DATA + && !compare_less_wrap(m_ack_nr, ph->seq_nr, ACK_MASK)) + { + // we've already received this packet + UTP_LOGV("%8p: incoming packet seq_nr:%d our ack_nr:%d (ignored)\n" + , static_cast(this), int(ph->seq_nr), m_ack_nr); + m_sm.inc_stats_counter(counters::utp_redundant_pkts_in); + return true; + } +*/ + + // if the socket is closing, always ignore any packet + // with a higher sequence number than the FIN sequence number + // ST_STATE messages always include the next seqnr. + if (m_eof && (compare_less_wrap(m_eof_seq_nr, ph->seq_nr, ACK_MASK) + || (m_eof_seq_nr == ph->seq_nr && ph->get_type() != ST_STATE))) + { +#if TORRENT_UTP_LOG + UTP_LOG("%8p: ERROR: incoming packet type: %s seq_nr:%d eof_seq_nr:%d (ignored)\n" + , static_cast(this), packet_type_names[ph->get_type()], int(ph->seq_nr), m_eof_seq_nr); +#endif + return true; + } + + if (ph->get_type() == ST_DATA) + m_sm.inc_stats_counter(counters::utp_payload_pkts_in); + + // the number of packets that'll fit in the reorder buffer + std::uint32_t const max_packets_reorder + = static_cast(std::max(16, m_receive_buffer_capacity / 1100)); + + if (m_state != UTP_STATE_NONE + && m_state != UTP_STATE_SYN_SENT + && compare_less_wrap((m_ack_nr + max_packets_reorder) & ACK_MASK, ph->seq_nr, ACK_MASK)) + { + // this is too far out to fit in our reorder buffer. Drop it + // This is either an attack to try to break the connection + // or a seriously damaged connection that lost a lot of + // packets. Neither is very likely, and it should be OK + // to drop the timestamp information. + UTP_LOG("%8p: ERROR: incoming packet seq_nr:%d our ack_nr:%d (ignored)\n" + , static_cast(this), int(ph->seq_nr), m_ack_nr); + m_sm.inc_stats_counter(counters::utp_redundant_pkts_in); + return true; + } + + if (ph->get_type() == ST_RESET) + { + if (compare_less_wrap(cmp_seq_nr, ph->ack_nr, ACK_MASK)) + { + UTP_LOG("%8p: ERROR: invalid RESET packet, ack_nr:%d our seq_nr:%d (ignored)\n" + , static_cast(this), int(ph->ack_nr), m_seq_nr); + return true; + } + UTP_LOGV("%8p: incoming packet type:RESET\n", static_cast(this)); + m_error = boost::asio::error::connection_reset; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return true; + } + + ++m_in_packets; + + // this is a valid incoming packet, update the timeout timer + m_num_timeouts = 0; + m_timeout = receive_time + milliseconds(packet_timeout()); + UTP_LOGV("%8p: updating timeout to: now + %d\n" + , static_cast(this), packet_timeout()); + + // the test for INT_MAX here is a work-around for a bug in uTorrent where + // it's sometimes sent as INT_MAX when it is in fact uninitialized + const std::uint32_t sample = ph->timestamp_difference_microseconds == INT_MAX + ? 0 : ph->timestamp_difference_microseconds; + + std::uint32_t delay = 0; + if (sample != 0) + { + delay = m_delay_hist.add_sample(sample, step); + m_delay_sample_hist[m_delay_sample_idx++] = delay; + if (m_delay_sample_idx >= m_delay_sample_hist.size()) + m_delay_sample_idx = 0; + } + + int acked_bytes = 0; + + TORRENT_ASSERT(m_bytes_in_flight >= 0); + int prev_bytes_in_flight = m_bytes_in_flight; + + m_adv_wnd = ph->wnd_size; + + // if we get an ack for the same sequence number as + // was last ACKed, and we have outstanding packets, + // it counts as a duplicate ack. The reason to not count ST_DATA packets as + // duplicate ACKs is because we may be receiving a stream of those + // regardless of our outgoing traffic, which makes their ACK number not + // indicative of a dropped packet + if (ph->ack_nr == m_acked_seq_nr + && m_outbuf.size() + && ph->get_type() == ST_STATE) + { + ++m_duplicate_acks; + } + + TORRENT_ASSERT_VAL(m_outbuf.size() > 0 || m_duplicate_acks == 0, m_duplicate_acks); + + std::uint32_t min_rtt = std::numeric_limits::max(); + + TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); + + // has this packet already been ACKed? + // if the ACK we just got is less than the max ACKed + // sequence number, it doesn't tell us anything. + // So, only act on it if the ACK is greater than the last acked + // sequence number + if (m_state != UTP_STATE_NONE && compare_less_wrap(m_acked_seq_nr, ph->ack_nr, ACK_MASK)) + { + int const next_ack_nr = ph->ack_nr; + + for (int ack_nr = (m_acked_seq_nr + 1) & ACK_MASK; + ack_nr != ((next_ack_nr + 1) & ACK_MASK); + ack_nr = (ack_nr + 1) & ACK_MASK) + { + if (m_fast_resend_seq_nr == ack_nr) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + packet_ptr p = m_outbuf.remove(aux::numeric_cast(ack_nr)); + + if (!p) continue; + + acked_bytes += p->size - p->header_size; + std::uint32_t const rtt = ack_packet(std::move(p), receive_time, std::uint16_t(ack_nr)); + min_rtt = std::min(min_rtt, rtt); + } + + maybe_inc_acked_seq_nr(); + if (m_outbuf.empty()) m_duplicate_acks = 0; + } + + // look for extended headers + std::uint8_t const* ptr = buf.data(); + int const size = int(buf.size()); + ptr += sizeof(utp_header); + + std::uint8_t extension = ph->extension; + while (extension) + { + // invalid packet. It says it has an extension header + // but the packet is too short + if (ptr - buf.data() + 2 > size) + { + UTP_LOG("%8p: ERROR: invalid extension header\n", static_cast(this)); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return true; + } + std::uint8_t const next_extension = *ptr++; + int const len = *ptr++; + if (ptr - buf.data() + len > size) + { + UTP_LOG("%8p: ERROR: invalid extension header size:%d packet:%d\n" + , static_cast(this), len, int(ptr - buf.data())); + m_sm.inc_stats_counter(counters::utp_invalid_pkts_in); + return true; + } + switch(extension) + { + case utp_sack: // selective ACKs + { + std::uint32_t rtt; + std::tie(rtt, acked_bytes) = parse_sack(ph->ack_nr, ptr, len, receive_time); + min_rtt = std::min(min_rtt, rtt); + break; + } + case utp_close_reason: + parse_close_reason(ptr, len); + break; + } + ptr += len; + extension = next_extension; + } + + // the send operation in parse_sack() may have set the socket to an error + // state, in which case we shouldn't continue + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + + if (m_duplicate_acks >= dup_ack_limit + && ((m_acked_seq_nr + 1) & ACK_MASK) == m_fast_resend_seq_nr) + { + // LOSS + + UTP_LOGV("%8p: Packet %d lost. (%d duplicate acks, trigger fast-resend)\n" + , static_cast(this), m_fast_resend_seq_nr, m_duplicate_acks); + + // resend the lost packet + packet* p = m_outbuf.at(m_fast_resend_seq_nr); + TORRENT_ASSERT(p); + + // don't fast-resend this again + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + + if (p) + { + // don't consider a lost probe as proper loss, it doesn't necessarily + // signal congestion + if (!p->mtu_probe) experienced_loss(m_fast_resend_seq_nr, receive_time); + resend_packet(p, true); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + } + + // ptr points to the payload of the packet + // size is the packet size, payload is the + // number of payload bytes are in this packet + const int header_size = int(ptr - buf.data()); + const int payload_size = size - header_size; + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: incoming packet seq_nr:%d ack_nr:%d type:%s id:%d size:%d timestampdiff:%u timestamp:%u " + "our ack_nr:%d our seq_nr:%d our acked_seq_nr:%d our state:%s\n" + , static_cast(this), int(ph->seq_nr), int(ph->ack_nr), packet_type_names[ph->get_type()] + , int(ph->connection_id), payload_size, std::uint32_t(ph->timestamp_difference_microseconds) + , std::uint32_t(ph->timestamp_microseconds), m_ack_nr, m_seq_nr, m_acked_seq_nr, socket_state_names[m_state]); +#endif + + if (ph->get_type() == ST_FIN) + { + // We ignore duplicate FIN packets, but we still need to ACK them. + if (ph->seq_nr == ((m_ack_nr + 1) & ACK_MASK) + || ph->seq_nr == m_ack_nr) + { + UTP_LOGV("%8p: FIN received in order\n", static_cast(this)); + + // The FIN arrived in order, nothing else is in the + // reorder buffer. + +// TORRENT_ASSERT(m_inbuf.size() == 0); + m_ack_nr = ph->seq_nr; + + // Transition to UTP_STATE_FIN_SENT. The sent FIN is also an ack + // to the FIN we received. Once we're in UTP_STATE_FIN_SENT we + // just need to wait for our FIN to be acked. + + if (m_state == UTP_STATE_FIN_SENT) + { + send_pkt(pkt_ack); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + else + { + send_fin(); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + } + + if (m_eof) + { + UTP_LOGV("%8p: duplicate FIN packet (ignoring)\n", static_cast(this)); + return true; + } + m_eof = true; + m_eof_seq_nr = ph->seq_nr; + + // we will respond with a fin once we have received everything up to m_eof_seq_nr + } + + switch (m_state) + { + case UTP_STATE_NONE: + { + if (ph->get_type() == ST_SYN) + { + // if we're in state_none, the only thing + // we accept are SYN packets. + set_state(UTP_STATE_CONNECTED); + + m_remote_address = ep.address(); + m_port = ep.port(); + + m_ack_nr = ph->seq_nr; + m_seq_nr = std::uint16_t(random(0xffff)); + m_acked_seq_nr = (m_seq_nr - 1) & ACK_MASK; + m_loss_seq_nr = m_acked_seq_nr; + m_fast_resend_seq_nr = m_seq_nr; + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: received ST_SYN state:%s seq_nr:%d ack_nr:%d\n" + , static_cast(this), socket_state_names[m_state], m_seq_nr, m_ack_nr); +#endif + if (m_send_id != ph->connection_id) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: received invalid connection_id:%d expected: %d\n" + , static_cast(this), int(ph->connection_id), int(m_send_id)); +#endif + return false; + } + TORRENT_ASSERT(m_recv_id == ((m_send_id + 1) & 0xffff)); + + defer_ack(); + } + else + { +#if TORRENT_UTP_LOG + UTP_LOG("%8p: ERROR: type:%s state:%s (ignored)\n" + , static_cast(this), packet_type_names[ph->get_type()], socket_state_names[m_state]); +#endif + } + break; + } + case UTP_STATE_SYN_SENT: + { + // just wait for an ack to our SYN, ignore everything else + if (ph->ack_nr != ((m_seq_nr - 1) & ACK_MASK)) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: incorrect ack_nr (%d) waiting for %d\n" + , static_cast(this), int(ph->ack_nr), (m_seq_nr - 1) & ACK_MASK); +#endif + break; + } + + TORRENT_ASSERT(!m_error); + set_state(UTP_STATE_CONNECTED); +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: state:%s\n", static_cast(this), socket_state_names[m_state]); +#endif + + // only progress our ack_nr on ST_DATA messages + // since our m_ack_nr is uninitialized at this point + // we still need to set it to something regardless + if (ph->get_type() == ST_DATA) + m_ack_nr = ph->seq_nr; + else + m_ack_nr = (ph->seq_nr - 1) & ACK_MASK; + + // notify the client that the socket connected + if (m_connect_handler) + { + UTP_LOGV("%8p: calling connect handler\n", static_cast(this)); + m_connect_handler = false; + utp_stream::on_connect(m_userdata, m_error, false); + } + BOOST_FALLTHROUGH; + } + // fall through + case UTP_STATE_CONNECTED: + { + // the lowest seen RTT can be used to clamp the delay + // within reasonable bounds. The one-way delay is never + // higher than the round-trip time. + + if (sample && acked_bytes && prev_bytes_in_flight) + { + // only use the minimum from the last 3 delay measurements + delay = *std::min_element(m_delay_sample_hist.begin() + , m_delay_sample_hist.end()); + + // it's impossible for delay to be more than the RTT, so make + // sure to clamp it as a sanity check + if (delay > min_rtt) delay = min_rtt; + + do_ledbat(acked_bytes, int(delay), prev_bytes_in_flight); + m_send_delay = std::int32_t(delay); + } + + m_recv_delay = std::int32_t(std::min(their_delay, min_rtt)); + + consume_incoming_data(ph, ptr, payload_size, receive_time); + + // the parameter to send_pkt tells it if we're acking data + // If we are, we'll send an ACK regardless of if we have any + // space left in our send window or not. If we just got an ACK + // (i.e. ST_STATE) we're not ACKing anything. If we just + // received a FIN packet, we need to ack that as well + bool has_ack = ph->get_type() == ST_DATA || ph->get_type() == ST_FIN || ph->get_type() == ST_SYN; + std::uint32_t prev_out_packets = m_out_packets; + + // the connection is connected and this packet made it past all the + // checks. We can now assume the other end is not spoofing it's IP. + if (ph->get_type() != ST_SYN) m_confirmed = true; + + // try to send more data as long as we can + // if send_pkt returns true + while (send_pkt()); + + if (has_ack && prev_out_packets == m_out_packets) + { + // we need to ack some data we received, and we didn't + // end up sending any payload packets in the loop + // above (because m_out_packets would have been incremented + // in that case). This means we need to send an ack. + // don't do it right away, because we may still receive + // more packets. defer the ack to send as few acks as possible + defer_ack(); + } + + // we may want to call the user callback function at the end + // of this round. Subscribe to that event + subscribe_drained(); + + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + + // Everything up to the FIN has been received, respond with a FIN + // from our side. + if (m_eof && m_ack_nr == ((m_eof_seq_nr - 1) & ACK_MASK)) + { + UTP_LOGV("%8p: incoming stream consumed\n", static_cast(this)); + + // This transitions to the UTP_STATE_FIN_SENT state. + send_fin(); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; + } + + TORRENT_ASSERT(!compare_less_wrap(m_seq_nr, m_acked_seq_nr, ACK_MASK)); + +#if TORRENT_UTP_LOG + if (sample && acked_bytes && prev_bytes_in_flight) + { + char their_delay_base[20]; + if (m_their_delay_hist.initialized()) + std::snprintf(their_delay_base, sizeof(their_delay_base), "%u", m_their_delay_hist.base()); + else + strcpy(their_delay_base, "-"); + + char our_delay_base[20]; + if (m_delay_hist.initialized()) + std::snprintf(our_delay_base, sizeof(our_delay_base), "%u", m_delay_hist.base()); + else + strcpy(our_delay_base, "-"); + + UTP_LOG("%8p: " + "actual_delay:%u " + "our_delay:%f " + "their_delay:%f " + "off_target:%f " + "max_window:%u " + "upload_rate:%d " + "delay_base:%s " + "delay_sum:%f " + "target_delay:%d " + "acked_bytes:%d " + "cur_window:%d " + "scaled_gain:%f " + "rtt:%u " + "rate:%d " + "quota:%d " + "wnduser:%u " + "rto:%d " + "timeout:%d " + "get_microseconds:%u " + "cur_window_packets:%u " + "packet_size:%d " + "their_delay_base:%s " + "their_actual_delay:%u " + "seq_nr:%u " + "acked_seq_nr:%u " + "reply_micro:%u " + "min_rtt:%u " + "send_buffer:%d " + "recv_buffer:%d " + "fast_resend_seq_nr:%d " + "ssthres:%d " + "\n" + , static_cast(this) + , sample + , delay / 1000.0 + , their_delay / 1000.0 + , int(m_sm.target_delay() - delay) / 1000.0 + , std::uint32_t(m_cwnd >> 16) + , 0 + , our_delay_base + , (delay + their_delay) / 1000.0 + , m_sm.target_delay() / 1000 + , acked_bytes + , m_bytes_in_flight + , 0.0 // float(scaled_gain) + , m_rtt.mean() + , int((m_cwnd * 1000 / (m_rtt.mean()?m_rtt.mean():50)) >> 16) + , 0 + , m_adv_wnd + , packet_timeout() + , int(total_milliseconds(m_timeout - receive_time)) + , int(total_microseconds(receive_time.time_since_epoch())) + , m_outbuf.size() + , m_mtu + , their_delay_base + , std::uint32_t(m_reply_micro) + , m_seq_nr + , m_acked_seq_nr + , m_reply_micro + , min_rtt / 1000 + , m_write_buffer_size + , m_read_buffer_size + , m_fast_resend_seq_nr + , m_ssthres); + } +#endif + + break; + } + case UTP_STATE_FIN_SENT: + { + // There are two ways we can end up in this state: + // + // 1. If the socket has been explicitly closed on our + // side, in which case m_eof is false. + // + // 2. If we received a FIN from the remote side, in which + // case m_eof is true. If this is the case, we don't + // come here until everything up to the FIN has been + // received. + // + // + // + + // At this point m_seq_nr - 1 is the FIN sequence number. + + // We can receive both ST_DATA and ST_STATE here, because after + // we have closed our end of the socket, the remote end might + // have data in the pipeline. We don't really care about the + // data, but we do have to ack it. Or rather, we have to ack + // the FIN that will come after the data. + + // Case 1: + // --------------------------------------------------------------- + // + // If we are here because the local endpoint was closed, we need + // to first wait for all of our messages to be acked: + // + // if (m_acked_seq_nr == ((m_seq_nr - 1) & ACK_MASK)) + // + // `m_seq_nr - 1` is the ST_FIN message that we sent. + // + // ---------------------- + // + // After that has happened we need to wait for the remote side + // to send its ST_FIN message. When we receive that we send an + // ST_STATE back to ack, and wait for a sufficient period. + // During this wait we keep acking incoming ST_FIN's. This is + // all handled at the top of this function. + // + // Note that the user handlers are all cancelled when the initial + // close() call happens, so nothing will happen on the user side + // after that. + + // Case 2: + // --------------------------------------------------------------- + // + // If we are here because we received a ST_FIN message, and then + // sent our own ST_FIN to ack that, we need to wait for our ST_FIN + // to be acked: + // + // if (m_acked_seq_nr == ((m_seq_nr - 1) & ACK_MASK)) + // + // `m_seq_nr - 1` is the ST_FIN message that we sent. + // + // After that has happened we know the remote side has all our + // data, and we can gracefully shut down. + + if (consume_incoming_data(ph, ptr, payload_size, receive_time)) + { + break; + } + + if (m_acked_seq_nr == ((m_seq_nr - 1) & ACK_MASK)) + { + // When this happens we know that the remote side has + // received all of our packets. + + UTP_LOGV("%8p: FIN acked\n", static_cast(this)); + + if (!m_attached) + { + UTP_LOGV("%8p: close initiated here, delete socket\n" + , static_cast(this)); + m_error = boost::asio::error::eof; + set_state(UTP_STATE_DELETE); + test_socket_state(); + } + else + { + UTP_LOGV("%8p: closing socket\n", static_cast(this)); + m_error = boost::asio::error::eof; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + } + } + + break; + } + case UTP_STATE_DELETE: + default: + { + // respond with a reset + send_reset(ph); + break; + } + } + return true; +} + +void utp_socket_impl::do_ledbat(const int acked_bytes, const int delay + , const int in_flight) +{ + INVARIANT_CHECK; + + // the portion of the in-flight bytes that were acked. This is used to make + // the gain factor be scaled by the rtt. The formula is applied once per + // rtt, or on every ACK scaled by the number of ACKs per rtt + TORRENT_ASSERT(in_flight > 0); + TORRENT_ASSERT(acked_bytes > 0); + + const int target_delay = std::max(1, m_sm.target_delay()); + + // true if the upper layer is pushing enough data down the socket to be + // limited by the cwnd. If this is not the case, we should not adjust cwnd. + const bool cwnd_saturated = (m_bytes_in_flight + acked_bytes + m_mtu > (m_cwnd >> 16)); + + // all of these are fixed points with 16 bits fraction portion + const std::int64_t window_factor = (std::int64_t(acked_bytes) * (1 << 16)) / in_flight; + const std::int64_t delay_factor = (std::int64_t(target_delay - delay) * (1 << 16)) / target_delay; + std::int64_t scaled_gain; + + if (delay >= target_delay) + { + if (m_slow_start) + { + UTP_LOGV("%8p: off_target: %d slow_start -> 0\n" + , static_cast(this), target_delay - delay); + m_ssthres = std::int32_t((m_cwnd >> 16) / 2); + m_slow_start = false; + } + + m_sm.inc_stats_counter(counters::utp_samples_above_target); + } + else + { + m_sm.inc_stats_counter(counters::utp_samples_below_target); + } + + std::int64_t const linear_gain = ((window_factor * delay_factor) >> 16) + * std::int64_t(m_sm.gain_factor()); + + // if the user is not saturating the link (i.e. not filling the + // congestion window), don't adjust it at all. + if (cwnd_saturated) + { + std::int64_t const exponential_gain = std::int64_t(acked_bytes) * (1 << 16); + if (m_slow_start) + { + // mimic TCP slow-start by adding the number of acked + // bytes to cwnd + if (m_ssthres != 0 && ((m_cwnd + exponential_gain) >> 16) > m_ssthres) + { + // if we would exceed the slow start threshold by growing the cwnd + // exponentially, don't do it, and leave slow-start mode. This + // make us avoid causing more delay and/or packet loss by being too + // aggressive + m_slow_start = false; + scaled_gain = linear_gain; + UTP_LOGV("%8p: cwnd > ssthres (%d) slow_start -> 0\n" + , static_cast(this), m_ssthres); + } + else + { + scaled_gain = std::max(exponential_gain, linear_gain); + } + } + else + { + scaled_gain = linear_gain; + } + } + else + { + scaled_gain = 0; + } + + // make sure we don't wrap the cwnd + if (scaled_gain >= std::numeric_limits::max() - m_cwnd) + scaled_gain = std::numeric_limits::max() - m_cwnd - 1; + + UTP_LOGV("%8p: do_ledbat delay:%d off_target: %d window_factor:%f target_factor:%f " + "scaled_gain:%f cwnd:%d slow_start:%d\n" + , static_cast(this), delay, target_delay - delay, window_factor / double(1 << 16) + , delay_factor / double(1 << 16) + , scaled_gain / double(1 << 16), int(m_cwnd >> 16) + , int(m_slow_start)); + + // if scaled_gain + m_cwnd <= 0, set m_cwnd to 0 + if (-scaled_gain >= m_cwnd) + { + m_cwnd = 0; + } + else + { + m_cwnd += scaled_gain; + TORRENT_ASSERT(m_cwnd > 0); + } + + TORRENT_ASSERT(m_cwnd >= 0); + + int const window_size_left = std::min(int(m_cwnd >> 16), int(m_adv_wnd)) - in_flight + acked_bytes; + if (window_size_left >= m_mtu) + { + UTP_LOGV("%8p: mtu:%d in_flight:%d adv_wnd:%d cwnd:%d acked_bytes:%d cwnd_full -> 0\n" + , static_cast(this), m_mtu, in_flight, int(m_adv_wnd), int(m_cwnd >> 16), acked_bytes); + m_cwnd_full = false; + } +/* + if ((m_cwnd >> 16) >= m_adv_wnd) + { + m_slow_start = false; + m_ssthres = (m_cwnd >> 16); + UTP_LOGV("%8p: cwnd > advertized wnd (%u) slow_start -> 0\n" + , static_cast(this), m_adv_wnd); + } +*/ +} + +void utp_stream::bind(endpoint_type const&, error_code&) { } + +void utp_stream::cancel_handlers(error_code const& ec) +{ + if (!m_impl) return; + m_impl->cancel_handlers(ec, false); +} +// returns the number of milliseconds a packet would have before +// it would time-out if it was sent right now. Takes the RTT estimate +// into account +int utp_socket_impl::packet_timeout() const +{ + INVARIANT_CHECK; + + // SYN packets have a bit longer timeout, since we don't + // have an RTT estimate yet, make a conservative guess + if (m_state == UTP_STATE_NONE) return 3000; + + // avoid overflow by simply capping based on number of timeouts as well + if (m_num_timeouts >= 7) return 60000; + + int timeout = std::max(m_sm.min_timeout(), m_rtt.mean() + m_rtt.avg_deviation() * 2); + if (m_num_timeouts > 0) timeout += (1 << (int(m_num_timeouts) - 1)) * 1000; + + // timeouts over 1 minute are capped + if (timeout > 60000) timeout = 60000; + return timeout; +} + +void utp_socket_impl::tick(time_point const now) +{ + INVARIANT_CHECK; + +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: tick:%s r: %d (%s) w: %d (%s)\n" + , static_cast(this), socket_state_names[m_state], m_read, m_read_handler ? "handler" : "no handler" + , m_written, m_write_handler ? "handler" : "no handler"); +#endif + + TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); + + // if we're already in an error state, we're just waiting for the + // client to perform an operation so that we can communicate the + // error. No need to do anything else with this socket + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return; + + if (now > m_timeout) + { + // TIMEOUT! + + bool ignore_loss = false; + + if (((m_acked_seq_nr + 1) & ACK_MASK) == m_mtu_seq + && ((m_seq_nr - 1) & ACK_MASK) == m_mtu_seq + && m_mtu_seq != 0) + { + // we timed out, and the only outstanding packet + // we had was the probe. Assume it was dropped + // because it was too big + m_mtu_ceiling = m_mtu - 1; + update_mtu_limits(); + ignore_loss = true; + } + + // the close_reason here is a bit of a hack. When it's set, it indicates + // that the upper layer intends to close the socket. However, it has been + // observed that the SSL shutdown sometimes can hang in a state where + // there's no outstanding data, and it won't receive any more from the + // other end. This catches that case and let the socket time out. + if (m_outbuf.size() || m_close_reason != close_reason_t::none) + { + // m_num_timeouts is used to update the connection timeout, and if we + // lose this packet because it's an MTU-probe, don't change the timeout + if (!ignore_loss) ++m_num_timeouts; + m_sm.inc_stats_counter(counters::utp_timeout); + } + + UTP_LOGV("%8p: timeout num-timeouts: %d max-resends: %d confirmed: %d " + " acked-seq-num: %d mtu-seq: %d\n" + , static_cast(this) + , m_num_timeouts + , m_sm.num_resends() + , m_confirmed + , m_acked_seq_nr + , m_mtu_seq); + + // a socket that has not been confirmed to actually have a live remote end + // (the IP may have been spoofed) fail on the first timeout. If we had + // heard anything from this peer, it would have been confirmed. + if (m_num_timeouts > m_sm.num_resends() + || (m_num_timeouts > 0 && !m_confirmed)) + { + // the connection is dead + m_error = boost::asio::error::timed_out; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + + if (!ignore_loss) + { + // set cwnd to 1 MSS + if (m_bytes_in_flight == 0 && (m_cwnd >> 16) >= m_mtu) + { + // this is just a timeout because this direction of + // the stream is idle. Don't reset the cwnd, just decay it + m_cwnd = std::max(m_cwnd * 2 / 3, std::int64_t(m_mtu) * (1 << 16)); + } + else + { + // we timed out because a packet was not ACKed or because + // the cwnd was made smaller than one packet + m_cwnd = std::int64_t(m_mtu) * (1 << 16); + } + + TORRENT_ASSERT(m_cwnd >= 0); + + m_timeout = now + milliseconds(packet_timeout()); + + UTP_LOGV("%8p: resetting cwnd:%d\n" + , static_cast(this), int(m_cwnd >> 16)); + + // since we've already timed out now, don't count + // loss that we might detect for packets that just + // timed out + m_loss_seq_nr = m_seq_nr; + + // when we time out, the cwnd is reset to 1 MSS, which means we + // need to ramp it up quickly again. enter slow start mode. This time + // we're very likely to have an ssthres set, which will make us leave + // slow start before inducing more delay or loss. + m_slow_start = true; + UTP_LOGV("%8p: slow_start -> 1\n", static_cast(this)); + } + + // we dropped all packets, that includes the mtu probe + m_mtu_seq = 0; + + // we need to go one past m_seq_nr to cover the case + // where we just sent a SYN packet and then adjusted for + // the uTorrent sequence number reuse + for (int i = m_acked_seq_nr & ACK_MASK; + i != ((m_seq_nr + 1) & ACK_MASK); + i = (i + 1) & ACK_MASK) + { + packet* p = m_outbuf.at(aux::numeric_cast(i)); + if (!p) continue; + if (p->need_resend) continue; + p->need_resend = true; + TORRENT_ASSERT(m_bytes_in_flight >= p->size - p->header_size); + m_bytes_in_flight -= p->size - p->header_size; + UTP_LOGV("%8p: Packet %d lost (timeout).\n", static_cast(this), i); + } + + TORRENT_ASSERT(m_bytes_in_flight == 0); + + // if we have a packet that needs re-sending, resend it + packet* p = m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK); + if (p) + { + if (p->num_transmissions >= m_sm.num_resends() + || (m_state == UTP_STATE_SYN_SENT && p->num_transmissions >= m_sm.syn_resends()) + || (m_state == UTP_STATE_FIN_SENT && p->num_transmissions >= m_sm.fin_resends())) + { +#if TORRENT_UTP_LOG + UTP_LOGV("%8p: %d failed sends in a row. Socket timed out. state:%s\n" + , static_cast(this), p->num_transmissions, socket_state_names[m_state]); +#endif + + if (p->size > m_mtu_floor) + { + // the packet that caused the connection to fail was an mtu probe + // (note that the mtu_probe field won't be set at this point because + // it's cleared when the packet is re-sent). This suggests that + // perhaps our network throws away oversized packets without + // fragmenting them. Tell the socket manager to be more conservative + // about mtu ceiling in the future + m_sm.restrict_mtu(m_mtu); + } + // the connection is dead + m_error = boost::asio::error::timed_out; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + + // don't fast-resend this packet + if (m_fast_resend_seq_nr == ((m_acked_seq_nr + 1) & ACK_MASK)) + m_fast_resend_seq_nr = (m_fast_resend_seq_nr + 1) & ACK_MASK; + + // the packet timed out, resend it + resend_packet(p); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return; + } + else if (m_state < UTP_STATE_FIN_SENT) + { + send_pkt(); + if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return; + } + else if (m_state == UTP_STATE_FIN_SENT) + { + // the connection is dead + m_error = boost::asio::error::eof; + set_state(UTP_STATE_ERROR_WAIT); + test_socket_state(); + return; + } + } + + switch (m_state) + { + case UTP_STATE_NONE: + case UTP_STATE_DELETE: + return; +// case UTP_STATE_SYN_SENT: +// +// break; + } +} + +#if TORRENT_USE_INVARIANT_CHECKS +void utp_socket_impl::check_receive_buffers() const +{ + INVARIANT_CHECK; + + int const size = std::accumulate(m_receive_buffer.begin(), m_receive_buffer.end(), 0 + , [](int const acc, packet_ptr const& p) { return acc + (p ? p->size - p->header_size : 0); } ); + + TORRENT_ASSERT(size == m_receive_buffer_size); +} +#endif + +#if TORRENT_USE_INVARIANT_CHECKS +void utp_socket_impl::check_invariant() const +{ + for (packet_buffer::index_type i = m_outbuf.cursor(); + i != ((m_outbuf.cursor() + m_outbuf.span()) & ACK_MASK); + i = (i + 1) & ACK_MASK) + { + packet* p = m_outbuf.at(i); + if (!p) continue; + if (m_mtu_seq == i && m_mtu_seq != 0) + { + TORRENT_ASSERT(p->mtu_probe); + } + TORRENT_ASSERT(reinterpret_cast(p->buf)->seq_nr == i); + } + + if (m_nagle_packet) + { + // if this packet is full, it should have been sent + TORRENT_ASSERT(m_nagle_packet->size < m_nagle_packet->allocated); + } +} +#endif +} diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 0000000..2cf272b --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2015-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/version.hpp" + +namespace libtorrent { + +char const* version() +{ + return LIBTORRENT_VERSION; +} + +} diff --git a/src/web_connection_base.cpp b/src/web_connection_base.cpp new file mode 100644 index 0000000..b9a5fe5 --- /dev/null +++ b/src/web_connection_base.cpp @@ -0,0 +1,209 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include +#include + +#include "libtorrent/web_connection_base.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/peer_info.hpp" + +namespace libtorrent { + + web_connection_base::web_connection_base( + peer_connection_args const& pack + , web_seed_t& web) + : peer_connection(pack) + , m_first_request(true) + , m_ssl(false) + , m_external_auth(web.auth) + , m_extra_headers(web.extra_headers) + , m_parser(http_parser::dont_parse_chunks) + , m_body_start(0) + { + TORRENT_ASSERT(&web.peer_info == pack.peerinfo); + // when going through a proxy, we don't necessarily have an endpoint here, + // since the proxy might be resolving the hostname, not us + TORRENT_ASSERT(web.endpoints.empty() || web.endpoints.front() == pack.endp); + + INVARIANT_CHECK; + + TORRENT_ASSERT(is_outgoing()); + + TORRENT_ASSERT(!m_torrent.lock()->is_upload_only()); + + // we only want left-over bandwidth + // TODO: introduce a web-seed default class which has a low download priority + + std::string protocol; + error_code ec; + std::tie(protocol, m_basic_auth, m_host, m_port, m_path) + = parse_url_components(web.url, ec); + TORRENT_ASSERT(!ec); + + if (m_port == -1 && protocol == "http") + m_port = 80; + +#ifdef TORRENT_USE_OPENSSL + if (protocol == "https") + { + m_ssl = true; + if (m_port == -1) m_port = 443; + } +#endif + + if (!m_basic_auth.empty()) + m_basic_auth = base64encode(m_basic_auth); + + m_server_string = "URL seed @ "; + m_server_string += m_host; + } + + int web_connection_base::timeout() const + { + // since this is a web seed, change the timeout + // according to the settings. + return m_settings.get_int(settings_pack::urlseed_timeout); + } + + void web_connection_base::start() + { + // avoid calling torrent::set_seed because it calls torrent::check_invariant + // which fails because the m_num_connecting count is not consistent until + // after we call peer_connection::start + m_upload_only = true; + peer_connection::start(); + // disconnect_if_redundant must be called after start to keep + // m_num_connecting consistent + disconnect_if_redundant(); + } + + web_connection_base::~web_connection_base() = default; + + void web_connection_base::on_connected() + { + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // it is always possible to request pieces + incoming_unchoke(); + + m_recv_buffer.reset(t->block_size() + 1024); + } + + void web_connection_base::add_headers(std::string& request + , aux::session_settings const& sett, bool using_proxy) const + { + request += "Host: "; + request += m_host; + if ((m_first_request || m_settings.get_bool(settings_pack::always_send_user_agent)) + && !m_settings.get_bool(settings_pack::anonymous_mode)) + { + request += "\r\nUser-Agent: "; + request += m_settings.get_str(settings_pack::user_agent); + } + if (!m_external_auth.empty()) + { + request += "\r\nAuthorization: "; + request += m_external_auth; + } + else if (!m_basic_auth.empty()) + { + request += "\r\nAuthorization: Basic "; + request += m_basic_auth; + } + if (sett.get_int(settings_pack::proxy_type) == settings_pack::http_pw) + { + request += "\r\nProxy-Authorization: Basic "; + request += base64encode(sett.get_str(settings_pack::proxy_username) + + ":" + sett.get_str(settings_pack::proxy_password)); + } + for (auto const& h : m_extra_headers) + { + request += "\r\n"; + request += h.first; + request += ": "; + request += h.second; + } + if (using_proxy) { + request += "\r\nProxy-Connection: keep-alive"; + } + if (m_first_request || using_proxy) { + request += "\r\nConnection: keep-alive"; + } + } + + // -------------------------- + // RECEIVE DATA + // -------------------------- + + void web_connection_base::get_specific_peer_info(peer_info& p) const + { + if (is_interesting()) p.flags |= peer_info::interesting; + if (is_choked()) p.flags |= peer_info::choked; + if (!is_connecting() && m_server_string.empty()) + p.flags |= peer_info::handshake; + if (is_connecting()) p.flags |= peer_info::connecting; + + p.client = m_server_string; + } + + bool web_connection_base::in_handshake() const + { + return m_server_string.empty(); + } + + void web_connection_base::on_sent(error_code const& error + , std::size_t bytes_transferred) + { + INVARIANT_CHECK; + + if (error) return; + sent_bytes(0, int(bytes_transferred)); + } + + +#if TORRENT_USE_INVARIANT_CHECKS + void web_connection_base::check_invariant() const + { +/* + TORRENT_ASSERT(m_num_pieces == std::count( + m_have_piece.begin() + , m_have_piece.end() + , true)); +*/ } +#endif + +} diff --git a/src/web_peer_connection.cpp b/src/web_peer_connection.cpp new file mode 100644 index 0000000..673ee29 --- /dev/null +++ b/src/web_peer_connection.cpp @@ -0,0 +1,1232 @@ +/* + +Copyright (c) 2003-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" + +#include +#include +#include // for snprintf +#include // for PRId64 et.al. + +#include "libtorrent/web_peer_connection.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/alert_manager.hpp" // for alert_manager +#include "libtorrent/aux_/escape_string.hpp" // for escape_path +#include "libtorrent/hex.hpp" // for is_hex +#include "libtorrent/torrent.hpp" +#include "libtorrent/http_parser.hpp" + +namespace libtorrent { + +constexpr int request_size_overhead = 5000; + +std::string escape_file_path(file_storage const& storage, file_index_t index); + +web_peer_connection::web_peer_connection(peer_connection_args const& pack + , web_seed_t& web) + : web_connection_base(pack, web) + , m_url(web.url) + , m_web(&web) + , m_received_body(0) + , m_chunk_pos(0) + , m_partial_chunk_header(0) + , m_num_responses(0) +{ + INVARIANT_CHECK; + + if (!m_settings.get_bool(settings_pack::report_web_seed_downloads)) + ignore_stats(true); + + std::shared_ptr tor = pack.tor.lock(); + TORRENT_ASSERT(tor); + + // if the web server is known not to support keep-alive. request 4MiB + // but we want to have at least piece size to prevent block based requests + int const min_size = std::max((web.supports_keepalive ? 1 : 4) * 1024 * 1024, + tor->torrent_file().piece_length()); + + // we prefer downloading large chunks from web seeds, + // but still want to be able to split requests + int const preferred_size = std::max(min_size, m_settings.get_int(settings_pack::urlseed_max_request_bytes)); + + prefer_contiguous_blocks(preferred_size / tor->block_size()); + + std::shared_ptr t = associated_torrent().lock(); + bool const single_file_request = t->torrent_file().num_files() == 1; + + if (!single_file_request) + { + // handle incorrect .torrent files which are multi-file + // but have web seeds not ending with a slash + ensure_trailing_slash(m_path); + ensure_trailing_slash(m_url); + } + else + { + // handle .torrent files that don't include the filename in the url + if (m_path.empty()) m_path += '/'; + if (m_path[m_path.size() - 1] == '/') + { + m_path += escape_string(t->torrent_file().name()); + } + + if (!m_url.empty() && m_url[m_url.size() - 1] == '/') + { + m_url += escape_file_path(t->torrent_file().files(), file_index_t(0)); + } + } + + // we want large blocks as well, so + // we can request more bytes at once + // this setting will merge adjacent requests + // into single larger ones + request_large_blocks(true); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "URL", "web_peer_connection %s", m_url.c_str()); +#endif +} + +std::string escape_file_path(file_storage const& storage, file_index_t index) +{ + std::string new_path { storage.file_path(index) }; +#ifdef TORRENT_WINDOWS + convert_path_to_posix(new_path); +#endif + return escape_path(new_path); +} + +void web_peer_connection::on_connected() +{ + if (m_web->have_files.empty()) + { + incoming_have_all(); + } + else if (m_web->have_files.none_set()) + { + incoming_have_none(); + m_web->interesting = false; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "WEB-SEED", "have no files, not interesting. %s", m_url.c_str()); +#endif + } + else + { + std::shared_ptr t = associated_torrent().lock(); + + // only advertise pieces that are contained within the files we have as + // indicated by m_web->have_files AND padfiles! + // it's important to include pieces that may overlap many files, as long + // as we have all those files, so instead of starting with a clear bitfield + // and setting the pieces corresponding to files we have, we do it the + // other way around. Start with assuming we have all files, and clear + // pieces overlapping with files we *don't* have. + typed_bitfield have; + file_storage const& fs = t->torrent_file().files(); + have.resize(fs.num_pieces(), true); + for (auto const i : fs.file_range()) + { + // if we have the file, no need to do anything + if (m_web->have_files.get_bit(i) || fs.pad_file_at(i)) continue; + + auto const range = aux::file_piece_range_inclusive(fs, i); + for (piece_index_t k = std::get<0>(range); k < std::get<1>(range); ++k) + have.clear_bit(k); + } + if (have.none_set()) + { + incoming_have_none(); + m_web->interesting = false; +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "WEB-SEED", "have no pieces, not interesting. %s", m_url.c_str()); +#endif + } + else + { + incoming_bitfield(have); + } + } + + // TODO: 3 this should be an optional, piece index -1 should + // not be allowed + if (m_web->restart_request.piece != piece_index_t(-1)) + { + // increase the chances of requesting the block + // we have partial data for already, to finish it + incoming_suggest(m_web->restart_request.piece); + } + web_connection_base::on_connected(); +} + +void web_peer_connection::disconnect(error_code const& ec + , operation_t op, disconnect_severity_t const error) +{ + if (is_disconnecting()) return; + + if (op == operation_t::sock_write && ec == boost::system::errc::broken_pipe) + { +#ifndef TORRENT_DISABLE_LOGGING + // a write operation failed with broken-pipe. This typically happens + // with HTTP 1.0 servers that close their incoming channel of the TCP + // stream whenever they're done reading one full request. Instead of + // us bailing out and failing the entire request just because our + // write-end was closed, ignore it and keep reading until the read-end + // also is closed. + peer_log(peer_log_alert::info, "WRITE_DIRECTION", "CLOSED"); +#endif + + // prevent the peer from trying to send anything more + m_send_buffer.clear(); + + // when the web server closed our write-end of the socket (i.e. its + // read-end), if it's an HTTP 1.0 server. we will stop sending more + // requests. We'll close the connection once we receive the last bytes, + // and our read end is closed as well. + incoming_choke(); + return; + } + + if (op == operation_t::connect && m_web && !m_web->endpoints.empty()) + { + // we failed to connect to this IP. remove it so that the next attempt + // uses the next IP in the list. + m_web->endpoints.erase(m_web->endpoints.begin()); + } + + if (ec == errors::uninteresting_upload_peer && m_web) + { + // if this is an "ephemeral" web seed, it means it was added by receiving + // an HTTP redirect. If we disconnect because we're not interested in any + // of its pieces, mark it as uninteresting, to avoid reconnecting to it + // repeatedly + if (m_web->ephemeral) m_web->interesting = false; + + // if the web seed is not ephemeral, but we're still not interested. That + // implies that all files either have failed with 404 or with a + // redirection to a different web server. + m_web->retry = std::max(m_web->retry, aux::time_now32() + + seconds32(m_settings.get_int(settings_pack::urlseed_wait_retry))); + TORRENT_ASSERT(m_web->retry > aux::time_now32()); + } + + std::shared_ptr t = associated_torrent().lock(); + + if (!m_requests.empty() && !m_file_requests.empty() + && !m_piece.empty() && m_web) + { +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "SAVE_RESTART_DATA" + , "data: %d req: %d off: %d" + , int(m_piece.size()), int(m_requests.front().piece) + , m_requests.front().start); + } +#endif + m_web->restart_request = m_requests.front(); + if (!m_web->restart_piece.empty()) + { + // we're about to replace a different restart piece + // buffer. So it was wasted download + if (t) t->add_redundant_bytes(int(m_web->restart_piece.size()) + , waste_reason::piece_closing); + } + m_web->restart_piece.swap(m_piece); + + // we have to do this to not count this data as redundant. The + // upper layer will call downloading_piece_progress and assume + // it's all wasted download. Since we're saving it here, it isn't. + m_requests.clear(); + } + + if (m_web && !m_web->supports_keepalive && error == peer_connection_interface::normal) + { + // if the web server doesn't support keepalive and we were + // disconnected as a graceful EOF, reconnect right away + if (t) get_io_service().post( + std::bind(&torrent::maybe_connect_web_seeds, t)); + } + + if (error >= failure) + { + m_web->retry = std::max(m_web->retry, aux::time_now32() + + seconds32(m_settings.get_int(settings_pack::urlseed_wait_retry))); + } + + peer_connection::disconnect(ec, op, error); + if (t) t->disconnect_web_seed(this); +} + +piece_block_progress web_peer_connection::downloading_piece_progress() const +{ + if (m_requests.empty()) return {}; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + piece_block_progress ret; + + ret.piece_index = m_requests.front().piece; + ret.bytes_downloaded = int(m_piece.size()); + // this is used to make sure that the block_index stays within + // bounds. If the entire piece is downloaded, the block_index + // would otherwise point to one past the end + int correction = m_piece.empty() ? 0 : -1; + ret.block_index = (m_requests.front().start + int(m_piece.size()) + correction) / t->block_size(); + TORRENT_ASSERT(ret.block_index < int(piece_block::invalid.block_index)); + TORRENT_ASSERT(ret.piece_index < piece_block::invalid.piece_index); + + ret.full_block_bytes = t->block_size(); + piece_index_t const last_piece = t->torrent_file().last_piece(); + if (ret.piece_index == last_piece && ret.block_index + == t->torrent_file().piece_size(last_piece) / t->block_size()) + { + ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size(); + } + return ret; +} + +void web_peer_connection::write_request(peer_request const& r) +{ + INVARIANT_CHECK; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + TORRENT_ASSERT(t->valid_metadata()); + + torrent_info const& info = t->torrent_file(); + peer_request req = r; + + std::string request; + request.reserve(400); + + int size = r.length; + const int block_size = t->block_size(); + const int piece_size = t->torrent_file().piece_length(); + peer_request pr{}; + + while (size > 0) + { + int request_offset = r.start + r.length - size; + pr.start = request_offset % piece_size; + pr.length = std::min(block_size, size); + pr.piece = piece_index_t(static_cast(r.piece) + request_offset / piece_size); + m_requests.push_back(pr); + + if (m_web->restart_request == m_requests.front()) + { + m_piece.swap(m_web->restart_piece); + peer_request const& front = m_requests.front(); + TORRENT_ASSERT(front.length > int(m_piece.size())); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "RESTART_DATA", + "data: %d req: (%d, %d) size: %d" + , int(m_piece.size()), static_cast(front.piece), front.start + , front.start + front.length - 1); +#else + TORRENT_UNUSED(front); +#endif + + req.start += int(m_piece.size()); + req.length -= int(m_piece.size()); + + // just to keep the accounting straight for the upper layer. + // it doesn't know we just re-wrote the request + incoming_piece_fragment(int(m_piece.size())); + m_web->restart_request.piece = piece_index_t(-1); + } + +#if 0 + std::cerr << this << " REQ: p: " << pr.piece << " " << pr.start << std::endl; +#endif + size -= pr.length; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REQUESTING", "(piece: %d start: %d) - (piece: %d end: %d)" + , static_cast(r.piece), r.start + , static_cast(pr.piece), pr.start + pr.length); +#endif + + bool const single_file_request = t->torrent_file().num_files() == 1; + int const proxy_type = m_settings.get_int(settings_pack::proxy_type); + bool const using_proxy = (proxy_type == settings_pack::http + || proxy_type == settings_pack::http_pw) && !m_ssl; + + // the number of pad files that have been "requested". In case we _only_ + // request padfiles, we can't rely on handling them in the on_receive() + // callback (because we won't receive anything), instead we have to post a + // pretend read callback where we can deliver the zeroes for the partfile + int num_pad_files = 0; + + // TODO: 3 do we really need a special case here? wouldn't the multi-file + // case handle single file torrents correctly too? + if (single_file_request) + { + file_request_t file_req; + file_req.file_index = file_index_t(0); + file_req.start = std::int64_t(static_cast(req.piece)) * info.piece_length() + + req.start; + file_req.length = req.length; + + request += "GET "; + // do not encode single file paths, they are + // assumed to be encoded in the torrent file + request += using_proxy ? m_url : m_path; + request += " HTTP/1.1\r\n"; + add_headers(request, m_settings, using_proxy); + request += "\r\nRange: bytes="; + request += to_string(file_req.start).data(); + request += "-"; + request += to_string(file_req.start + file_req.length - 1).data(); + request += "\r\n\r\n"; + m_first_request = false; + + m_file_requests.push_back(file_req); + } + else + { + std::vector files = info.orig_files().map_block(req.piece, req.start + , req.length); + + for (auto const &f : files) + { + file_request_t file_req; + file_req.file_index = f.file_index; + file_req.start = f.offset; + file_req.length = int(f.size); + + if (info.orig_files().pad_file_at(f.file_index)) + { + m_file_requests.push_back(file_req); + ++num_pad_files; + continue; + } + + request += "GET "; + if (using_proxy) + { + // m_url is already a properly escaped URL + // with the correct slashes. Don't encode it again + request += m_url; + } + + auto redirection = m_web->redirects.find(f.file_index); + if (redirection != m_web->redirects.end()) + { + auto const& redirect = redirection->second; + // in case of http proxy "request" already contains m_url with trailing slash, so let's skip dup slash + bool const trailing_slash = using_proxy && !redirect.empty() && redirect[0] == '/'; + request.append(redirect, trailing_slash, std::string::npos); + } + else + { + if (!using_proxy) + { + // m_path is already a properly escaped URL + // with the correct slashes. Don't encode it again + request += m_path; + } + + request += escape_file_path(info.orig_files(), f.file_index); + } + request += " HTTP/1.1\r\n"; + add_headers(request, m_settings, using_proxy); + request += "\r\nRange: bytes="; + request += to_string(f.offset).data(); + request += "-"; + request += to_string(f.offset + f.size - 1).data(); + request += "\r\n\r\n"; + m_first_request = false; + +#if 0 + std::cerr << this << " SEND-REQUEST: f: " << f.file_index + << " s: " << f.offset + << " e: " << (f.offset + f.size - 1) << std::endl; +#endif + // TODO: 3 file_index_t should not allow negative values + TORRENT_ASSERT(f.file_index >= file_index_t(0)); + + m_file_requests.push_back(file_req); + } + } + + if (num_pad_files == int(m_file_requests.size())) + { + get_io_service().post(std::bind( + &web_peer_connection::on_receive_padfile, + std::static_pointer_cast(self()))); + return; + } + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::outgoing_message, "REQUEST", "%s", request.c_str()); +#endif + + send_buffer(request); +} + +namespace { + + std::string get_peer_name(http_parser const& p, std::string const& host) + { + std::string ret = "URL seed @ "; + ret += host; + + std::string const& server_version = p.header("server"); + if (!server_version.empty()) + { + ret += " ("; + ret += server_version; + ret += ")"; + } + return ret; + } + + std::tuple get_range( + http_parser const& parser, error_code& ec) + { + std::int64_t range_start; + std::int64_t range_end; + if (parser.status_code() == 206) + { + std::tie(range_start, range_end) = parser.content_range(); + if (range_start < 0 || range_end < range_start) + { + ec = errors::invalid_range; + range_start = 0; + range_end = 0; + } + else + { + // the http range is inclusive + range_end++; + } + } + else + { + range_start = 0; + range_end = parser.content_length(); + if (range_end < 0) + { + range_end = 0; + ec = errors::no_content_length; + } + } + return std::make_tuple(range_start, range_end); + } +} + +// -------------------------- +// RECEIVE DATA +// -------------------------- + +bool web_peer_connection::received_invalid_data(piece_index_t const index, bool single_peer) +{ + if (!single_peer) return peer_connection::received_invalid_data(index, single_peer); + + // when a web seed fails a hash check, do the following: + // 1. if the whole piece only overlaps a single file, mark that file as not + // have for this peer + // 2. if the piece overlaps more than one file, mark the piece as not have + // for this peer + // 3. if it's a single file torrent, just ban it right away + // this handles the case where web seeds may have some files updated but not other + + std::shared_ptr t = associated_torrent().lock(); + file_storage const& fs = t->torrent_file().files(); + + // single file torrent + if (fs.num_files() == 1) return peer_connection::received_invalid_data(index, single_peer); + + std::vector files = fs.map_block(index, 0, fs.piece_size(index)); + + if (files.size() == 1) + { + // assume the web seed has a different copy of this specific file + // than what we expect, and pretend not to have it. + auto const range = file_piece_range_inclusive(fs, files[0].file_index); + for (piece_index_t i = std::get<0>(range); i != std::get<1>(range); ++i) + incoming_dont_have(i); + } + else + { + incoming_dont_have(index); + } + + peer_connection::received_invalid_data(index, single_peer); + + // if we don't think we have any of the files, allow banning the web seed + if (num_have_pieces() == 0) return true; + + // don't disconnect, we won't request anything from this file again + return false; +} + +void web_peer_connection::on_receive_padfile() +{ + handle_padfile(); +} + +void web_peer_connection::handle_error(int const bytes_left) +{ + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // TODO: 2 just make this peer not have the pieces + // associated with the file we just requested. Only + // when it doesn't have any of the file do the following + // pad files will make it complicated + + // temporarily unavailable, retry later + t->retry_web_seed(this, m_parser.header_duration("retry-after")); + if (t->alerts().should_post()) + { + std::string const error_msg = to_string(m_parser.status_code()).data() + + (" " + m_parser.message()); + t->alerts().emplace_alert(t->get_handle(), m_url + , error_msg); + } + received_bytes(0, bytes_left); + disconnect(error_code(m_parser.status_code(), http_category()), operation_t::bittorrent, failure); +} + +void web_peer_connection::handle_redirect(int const bytes_left) +{ + // this means we got a redirection request + // look for the location header + std::string location = m_parser.header("location"); + received_bytes(0, bytes_left); + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + if (location.empty()) + { + // we should not try this server again. + t->remove_web_seed_conn(this, errors::missing_location, operation_t::bittorrent, peer_error); + m_web = nullptr; + TORRENT_ASSERT(is_disconnecting()); + return; + } + + bool const single_file_request = !m_path.empty() + && m_path[m_path.size() - 1] != '/'; + + // add the redirected url and remove the current one + if (!single_file_request) + { + TORRENT_ASSERT(!m_file_requests.empty()); + file_index_t const file_index = m_file_requests.front().file_index; + + location = resolve_redirect_location(m_url, location); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "LOCATION", "%s", location.c_str()); +#endif + // TODO: 3 this could be made more efficient for the case when we use an + // HTTP proxy. Then we wouldn't need to add new web seeds to the torrent, + // we could just make the redirect table contain full URLs. + std::string redirect_base; + std::string redirect_path; + error_code ec; + std::tie(redirect_base, redirect_path) = split_url(location, ec); + + if (ec) + { + // we should not try this server again. + disconnect(errors::missing_location, operation_t::bittorrent, failure); + return; + } + + // add_web_seed won't add duplicates. If we have already added an entry + // with this URL, we'll get back the existing entry + + // "ephemeral" flag should be set to avoid "web_seed_t" saving in resume data. + // E.g. original "web_seed_t" request url points to "http://example1.com/file1" and + // web server responses with redirect location "http://example2.com/subpath/file2". + // "handle_redirect" process this location to create new "web_seed_t" + // with base url=="http://example2.com/" and redirects[0]=="/subpath/file2"). + // If we try to load resume with such "web_seed_t" then "web_peer_connection" will send + // request with wrong path "http://example2.com/file1" (cause "redirects" map is not serialized in resume) + web_seed_t* web = t->add_web_seed(redirect_base, web_seed_entry::url_seed + , m_external_auth, m_extra_headers, torrent::ephemeral); + web->have_files.resize(t->torrent_file().num_files(), false); + + // the new web seed we're adding only has this file for now + // we may add more files later + web->redirects[file_index] = redirect_path; + if (web->have_files.get_bit(file_index) == false) + { + web->have_files.set_bit(file_index); + + if (web->peer_info.connection != nullptr) + { + auto* pc = static_cast(web->peer_info.connection); + + // we just learned that this host has this file, and we're currently + // connected to it. Make it advertise that it has this file to the + // bittorrent engine + file_storage const& fs = t->torrent_file().files(); + auto const range = aux::file_piece_range_inclusive(fs, file_index); + for (piece_index_t i = std::get<0>(range); i < std::get<1>(range); ++i) + pc->incoming_have(i); + } + // we just learned about another file this web server has, make sure + // it's marked interesting to enable connecting to it + web->interesting = true; + } + + // we don't have this file on this server. Don't ask for it again + m_web->have_files.resize(t->torrent_file().num_files(), true); + if (m_web->have_files[file_index]) + { + m_web->have_files.clear_bit(file_index); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "MISSING_FILE", "redirection | file: %d" + , static_cast(file_index)); +#endif + } + disconnect(errors::redirecting, operation_t::bittorrent, normal); + } + else + { + location = resolve_redirect_location(m_url, location); +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "LOCATION", "%s", location.c_str()); +#endif + t->add_web_seed(location, web_seed_entry::url_seed, m_external_auth + , m_extra_headers, torrent::ephemeral); + + // this web seed doesn't have any files. Don't try to request from it + // again this session + m_web->have_files.resize(t->torrent_file().num_files(), false); + disconnect(errors::redirecting, operation_t::bittorrent, normal); + m_web = nullptr; + TORRENT_ASSERT(is_disconnecting()); + } +} + +void web_peer_connection::on_receive(error_code const& error + , std::size_t bytes_transferred) +{ + INVARIANT_CHECK; + + if (error) + { + received_bytes(0, int(bytes_transferred)); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "ERROR" + , "web_peer_connection error: %s", error.message().c_str()); + } +#endif + return; + } + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + // in case the first file on this series of requests is a padfile + // we need to handle it right now + span recv_buffer = m_recv_buffer.get(); + handle_padfile(); + if (associated_torrent().expired()) return; + + for (;;) + { + int payload; + int protocol; + bool header_finished = m_parser.header_finished(); + if (!header_finished) + { + bool failed = false; + std::tie(payload, protocol) = m_parser.incoming(recv_buffer, failed); + received_bytes(0, protocol); + TORRENT_ASSERT(int(recv_buffer.size()) >= protocol); + + if (failed) + { + received_bytes(0, int(recv_buffer.size())); +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "RECEIVE_BYTES" + , "%*s", int(recv_buffer.size()), recv_buffer.data()); + } +#endif + disconnect(errors::http_parse_error, operation_t::bittorrent, peer_error); + return; + } + + TORRENT_ASSERT(recv_buffer.empty() || recv_buffer[0] == 'H'); + TORRENT_ASSERT(int(recv_buffer.size()) <= m_recv_buffer.packet_size()); + + // this means the entire status line hasn't been received yet + if (m_parser.status_code() == -1) + { + TORRENT_ASSERT(payload == 0); + break; + } + + if (!m_parser.header_finished()) + { + TORRENT_ASSERT(payload == 0); + break; + } + + m_body_start = m_parser.body_start(); + m_received_body = 0; + } + + // we just completed reading the header + if (!header_finished) + { + ++m_num_responses; + + if (m_parser.connection_close()) + { + incoming_choke(); + if (m_num_responses == 1) + m_web->supports_keepalive = false; + } + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "STATUS" + , "%d %s", m_parser.status_code(), m_parser.message().c_str()); + std::multimap const& headers = m_parser.headers(); + for (auto const &i : headers) + peer_log(peer_log_alert::info, "STATUS", " %s: %s", i.first.c_str(), i.second.c_str()); + } +#endif + + // if the status code is not one of the accepted ones, abort + if (!is_ok_status(m_parser.status_code())) + { + if (!m_file_requests.empty()) + { + file_request_t const& file_req = m_file_requests.front(); + m_web->have_files.resize(t->torrent_file().num_files(), true); + m_web->have_files.clear_bit(file_req.file_index); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "MISSING_FILE", "http-code: %d | file: %d" + , m_parser.status_code(), static_cast(file_req.file_index)); +#endif + } + handle_error(int(recv_buffer.size())); + return; + } + + if (is_redirect(m_parser.status_code())) + { + handle_redirect(int(recv_buffer.size())); + return; + } + + m_server_string = get_peer_name(m_parser, m_host); + + recv_buffer = recv_buffer.subspan(m_body_start); + + m_body_start = m_parser.body_start(); + m_received_body = 0; + } + + // we only received the header, no data + if (recv_buffer.empty()) break; + + // =================================== + // ======= RESPONSE BYTE RANGE ======= + // =================================== + + // despite the HTTP range being inclusive, range_start and range_end are + // exclusive to fit better into C++. i.e. range_end points one byte past + // the end of the payload + std::int64_t range_start; + std::int64_t range_end; + error_code ec; + std::tie(range_start, range_end) = get_range(m_parser, ec); + if (ec) + { + received_bytes(0, int(recv_buffer.size())); + // we should not try this server again. + t->remove_web_seed_conn(this, ec, operation_t::bittorrent, peer_error); + m_web = nullptr; + TORRENT_ASSERT(is_disconnecting()); + return; + } + + TORRENT_ASSERT(!m_file_requests.empty()); + file_request_t const& file_req = m_file_requests.front(); + if (range_start != file_req.start + || range_end != file_req.start + file_req.length) + { + // the byte range in the http response is different what we expected + received_bytes(0, int(recv_buffer.size())); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::incoming)) + { + peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE" + , "in=(%d, %" PRId64 "-%" PRId64 ") expected=(%d, %" PRId64 "-%" PRId64 ") ]" + , static_cast(file_req.file_index), range_start, range_end + , static_cast(file_req.file_index), file_req.start, file_req.start + file_req.length - 1); + } +#endif + disconnect(errors::invalid_range, operation_t::bittorrent, peer_error); + return; + } + + if (m_parser.chunked_encoding()) + { + + // ========================= + // === CHUNKED ENCODING === + // ========================= + + while (m_chunk_pos >= 0 && !recv_buffer.empty()) + { + // first deliver any payload we have in the buffer so far, ahead of + // the next chunk header. + if (m_chunk_pos > 0) + { + int const copy_size = std::min(m_chunk_pos, int(recv_buffer.size())); + TORRENT_ASSERT(copy_size > 0); + + if (m_received_body + copy_size > file_req.length) + { + // the byte range in the http response is different what we expected + received_bytes(0, int(recv_buffer.size())); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE" + , "received body: %d request size: %d" + , m_received_body, file_req.length); +#endif + disconnect(errors::invalid_range, operation_t::bittorrent, peer_error); + return; + } + incoming_payload(recv_buffer.data(), copy_size); + + recv_buffer = recv_buffer.subspan(copy_size); + m_chunk_pos -= copy_size; + + if (recv_buffer.empty()) goto done; + } + + TORRENT_ASSERT(m_chunk_pos == 0); + + int header_size = 0; + std::int64_t chunk_size = 0; + span chunk_start = recv_buffer.subspan(m_chunk_pos); + TORRENT_ASSERT(chunk_start[0] == '\r' + || aux::is_hex({chunk_start.data(), 1})); + bool const ret = m_parser.parse_chunk_header(chunk_start, &chunk_size, &header_size); + if (!ret) + { + received_bytes(0, int(chunk_start.size()) - m_partial_chunk_header); + m_partial_chunk_header = int(chunk_start.size()); + goto done; + } +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::info, "CHUNKED_ENCODING" + , "parsed chunk: %" PRId64 " header_size: %d" + , chunk_size, header_size); +#endif + received_bytes(0, header_size - m_partial_chunk_header); + m_partial_chunk_header = 0; + TORRENT_ASSERT(chunk_size != 0 + || int(chunk_start.size()) <= header_size || chunk_start[header_size] == 'H'); + TORRENT_ASSERT(m_body_start + m_chunk_pos < INT_MAX); + m_chunk_pos += int(chunk_size); + recv_buffer = recv_buffer.subspan(header_size); + + // a chunk size of zero means the request is complete. Make sure the + // number of payload bytes we've received matches the number we + // requested. If that's not the case, we got an invalid response. + if (chunk_size == 0) + { + TORRENT_ASSERT_VAL(m_chunk_pos == 0, m_chunk_pos); + +#if TORRENT_USE_ASSERTS + span chunk = recv_buffer.subspan(m_chunk_pos); + TORRENT_ASSERT(chunk.size() == 0 || chunk[0] == 'H'); +#endif + m_chunk_pos = -1; + + TORRENT_ASSERT(m_received_body <= file_req.length); + if (m_received_body != file_req.length) + { + // the byte range in the http response is different what we expected + received_bytes(0, int(recv_buffer.size())); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE" + , "received body: %d request size: %d" + , m_received_body, file_req.length); +#endif + disconnect(errors::invalid_range, operation_t::bittorrent, peer_error); + return; + } + // we just completed an HTTP file request. pop it from m_file_requests + m_file_requests.pop_front(); + m_parser.reset(); + m_body_start = 0; + m_received_body = 0; + m_chunk_pos = 0; + m_partial_chunk_header = 0; + + // in between each file request, there may be an implicit + // pad-file request + handle_padfile(); + break; + } + + // if all of the receive buffer was just consumed as chunk + // header, we're done + if (recv_buffer.empty()) goto done; + } + } + else + { + // this is the simple case, where we don't have chunked encoding + TORRENT_ASSERT(m_received_body <= file_req.length); + int const copy_size = std::min(file_req.length - m_received_body + , int(recv_buffer.size())); + incoming_payload(recv_buffer.data(), copy_size); + recv_buffer = recv_buffer.subspan(copy_size); + + TORRENT_ASSERT(m_received_body <= file_req.length); + if (m_received_body == file_req.length) + { + // we just completed an HTTP file request. pop it from m_file_requests + m_file_requests.pop_front(); + m_parser.reset(); + m_body_start = 0; + m_received_body = 0; + m_chunk_pos = 0; + m_partial_chunk_header = 0; + + // in between each file request, there may be an implicit + // pad-file request + handle_padfile(); + } + } + + if (recv_buffer.empty()) break; + } +done: + + // now, remove all the bytes we've processed from the receive buffer + m_recv_buffer.cut(int(recv_buffer.data() - m_recv_buffer.get().begin()) + , t->block_size() + request_size_overhead); +} + +void web_peer_connection::incoming_payload(char const* buf, int len) +{ + received_bytes(len, 0); + m_received_body += len; + + if (is_disconnecting()) return; + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INCOMING_PAYLOAD", "%d bytes", len); +#endif + + // deliver all complete bittorrent requests to the bittorrent engine + while (len > 0) + { + if (m_requests.empty()) return; + + TORRENT_ASSERT(!m_requests.empty()); + peer_request const& front_request = m_requests.front(); + int const piece_size = int(m_piece.size()); + int const copy_size = std::min(front_request.length - piece_size, len); + + // m_piece may not hold more than the response to the next BT request + TORRENT_ASSERT(front_request.length > piece_size); + + // copy_size is the number of bytes we need to add to the end of m_piece + // to not exceed the size of the next bittorrent request to be delivered. + // m_piece can only hold the response for a single BT request at a time + m_piece.resize(piece_size + copy_size); + std::memcpy(m_piece.data() + piece_size, buf, aux::numeric_cast(copy_size)); + len -= copy_size; + buf += copy_size; + + // keep peer stats up-to-date + incoming_piece_fragment(copy_size); + + TORRENT_ASSERT(front_request.length >= piece_size); + if (int(m_piece.size()) == front_request.length) + { + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "POP_REQUEST" + , "piece: %d start: %d len: %d" + , static_cast(front_request.piece), front_request.start, front_request.length); +#endif + + // Make a copy of the request and pop it off the queue before calling + // incoming_piece because that may lead to a call to disconnect() + // which will clear the request queue and invalidate any references + // to the request + peer_request const front_request_copy = front_request; + m_requests.pop_front(); + + incoming_piece(front_request_copy, m_piece.data()); + + m_piece.clear(); + } + } +} + +void web_peer_connection::incoming_zeroes(int len) +{ +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "INCOMING_ZEROES", "%d bytes", len); +#endif + + // deliver all complete bittorrent requests to the bittorrent engine + while (len > 0) + { + TORRENT_ASSERT(!m_requests.empty()); + peer_request const& front_request = m_requests.front(); + int const piece_size = int(m_piece.size()); + int const copy_size = std::min(front_request.length - piece_size, len); + + // m_piece may not hold more than the response to the next BT request + TORRENT_ASSERT(front_request.length > piece_size); + + // copy_size is the number of bytes we need to add to the end of m_piece + // to not exceed the size of the next bittorrent request to be delivered. + // m_piece can only hold the response for a single BT request at a time + m_piece.resize(piece_size + copy_size, 0); + len -= copy_size; + + // keep peer stats up-to-date + incoming_piece_fragment(copy_size); + + maybe_harvest_piece(); + } +} + +void web_peer_connection::maybe_harvest_piece() +{ + peer_request const& front_request = m_requests.front(); + TORRENT_ASSERT(front_request.length >= int(m_piece.size())); + if (int(m_piece.size()) != front_request.length) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + +#ifndef TORRENT_DISABLE_LOGGING + peer_log(peer_log_alert::incoming_message, "POP_REQUEST" + , "piece: %d start: %d len: %d" + , static_cast(front_request.piece) + , front_request.start, front_request.length); +#endif + m_requests.pop_front(); + + incoming_piece(front_request, m_piece.data()); + m_piece.clear(); +} + +void web_peer_connection::get_specific_peer_info(peer_info& p) const +{ + web_connection_base::get_specific_peer_info(p); + p.flags |= peer_info::local_connection; + p.connection_type = peer_info::web_seed; +} + +void web_peer_connection::handle_padfile() +{ + if (m_file_requests.empty()) return; + if (m_requests.empty()) return; + + std::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + torrent_info const& info = t->torrent_file(); + + while (!m_file_requests.empty() + && info.orig_files().pad_file_at(m_file_requests.front().file_index)) + { + // the next file is a pad file. We didn't actually send + // a request for this since it most likely doesn't exist on + // the web server anyway. Just pretend that we received a + // bunch of zeroes here and pop it again + std::int64_t file_size = m_file_requests.front().length; + + // in theory the pad file can span multiple bocks, hence the loop + while (file_size > 0) + { + peer_request const front_request = m_requests.front(); + TORRENT_ASSERT(int(m_piece.size()) < front_request.length); + + int pad_size = int(std::min(file_size + , front_request.length - std::int64_t(m_piece.size()))); + TORRENT_ASSERT(pad_size > 0); + file_size -= pad_size; + + incoming_zeroes(pad_size); + +#ifndef TORRENT_DISABLE_LOGGING + if (should_log(peer_log_alert::info)) + { + peer_log(peer_log_alert::info, "HANDLE_PADFILE" + , "file: %d start: %" PRId64 " len: %d" + , static_cast(m_file_requests.front().file_index) + , m_file_requests.front().start + , m_file_requests.front().length); + } +#endif + } + + m_file_requests.pop_front(); + } +} + +} // libtorrent namespace diff --git a/src/write_resume_data.cpp b/src/write_resume_data.cpp new file mode 100644 index 0000000..6ef4fc1 --- /dev/null +++ b/src/write_resume_data.cpp @@ -0,0 +1,260 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/bdecode.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/socket_io.hpp" // for write_*_endpoint() +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/torrent.hpp" // for default_piece_priority +#include "libtorrent/aux_/numeric_cast.hpp" // for clamp + +namespace libtorrent { + + entry write_resume_data(add_torrent_params const& atp) + { + entry ret; + + using namespace libtorrent::detail; // for write_*_endpoint() + ret["file-format"] = "libtorrent resume file"; + ret["file-version"] = 1; + ret["libtorrent-version"] = LIBTORRENT_VERSION; + ret["allocation"] = atp.storage_mode == storage_mode_allocate + ? "allocate" : "sparse"; + + ret["total_uploaded"] = atp.total_uploaded; + ret["total_downloaded"] = atp.total_downloaded; + + // cast to seconds in case that internal values doesn't have ratio<1> + ret["active_time"] = atp.active_time; + ret["finished_time"] = atp.finished_time; + ret["seeding_time"] = atp.seeding_time; + ret["last_seen_complete"] = atp.last_seen_complete; + ret["last_download"] = atp.last_download; + ret["last_upload"] = atp.last_upload; + + ret["num_complete"] = atp.num_complete; + ret["num_incomplete"] = atp.num_incomplete; + ret["num_downloaded"] = atp.num_downloaded; + + ret["seed_mode"] = bool(atp.flags & torrent_flags::seed_mode); + ret["upload_mode"] = bool(atp.flags & torrent_flags::upload_mode); +#ifndef TORRENT_DISABLE_SHARE_MODE + ret["share_mode"] = bool(atp.flags & torrent_flags::share_mode); +#endif + ret["apply_ip_filter"] = bool(atp.flags & torrent_flags::apply_ip_filter); + ret["paused"] = bool(atp.flags & torrent_flags::paused); + ret["auto_managed"] = bool(atp.flags & torrent_flags::auto_managed); +#ifndef TORRENT_DISABLE_SUPERSEEDING + ret["super_seeding"] = bool(atp.flags & torrent_flags::super_seeding); +#endif + ret["sequential_download"] = bool(atp.flags & torrent_flags::sequential_download); + ret["stop_when_ready"] = bool(atp.flags & torrent_flags::stop_when_ready); + ret["disable_dht"] = bool(atp.flags & torrent_flags::disable_dht); + ret["disable_lsd"] = bool(atp.flags & torrent_flags::disable_lsd); + ret["disable_pex"] = bool(atp.flags & torrent_flags::disable_pex); + + ret["added_time"] = atp.added_time; + ret["completed_time"] = atp.completed_time; + + ret["save_path"] = atp.save_path; + +#if TORRENT_ABI_VERSION == 1 + // deprecated in 1.2 + if (!atp.url.empty()) ret["url"] = atp.url; + if (!atp.uuid.empty()) ret["uuid"] = atp.uuid; +#endif + + ret["info-hash"] = atp.info_hash; + + if (atp.ti) + { + auto const info = atp.ti->metadata(); + int const size = atp.ti->metadata_size(); + ret["info"].preformatted().assign(&info[0], &info[0] + size); + if (!atp.ti->comment().empty()) + ret["comment"] = atp.ti->comment(); + if (atp.ti->creation_date() != 0) + ret["creation date"] = atp.ti->creation_date(); + if (!atp.ti->creator().empty()) + ret["created by"] = atp.ti->creator(); + } + + if (!atp.merkle_tree.empty()) + { + // we need to save the whole merkle hash tree + // in order to resume + std::string& tree_str = ret["merkle tree"].string(); + auto const& tree = atp.merkle_tree; + tree_str.resize(tree.size() * 20); + std::memcpy(&tree_str[0], &tree[0], tree.size() * 20); + } + + if (!atp.unfinished_pieces.empty()) + { + entry::list_type& up = ret["unfinished"].list(); + up.reserve(atp.unfinished_pieces.size()); + + // info for each unfinished piece + for (auto const& p : atp.unfinished_pieces) + { + entry piece_struct(entry::dictionary_t); + + // the unfinished piece's index + piece_struct["piece"] = static_cast(p.first); + piece_struct["bitmask"] = std::string(p.second.data(), std::size_t(p.second.size() + 7) / 8); + // push the struct onto the unfinished-piece list + up.push_back(std::move(piece_struct)); + } + } + + // save trackers + entry::list_type& tr_list = ret["trackers"].list(); + if (!atp.trackers.empty()) + { + tr_list.emplace_back(entry::list_type()); + std::size_t tier = 0; + auto tier_it = atp.tracker_tiers.begin(); + for (std::string const& tr : atp.trackers) + { + if (tier_it != atp.tracker_tiers.end()) + tier = aux::clamp(std::size_t(*tier_it++), std::size_t{0}, std::size_t{1024}); + + if (tr_list.size() <= tier) + tr_list.resize(tier + 1); + + tr_list[tier].list().emplace_back(tr); + } + } + + // save web seeds + entry::list_type& url_list = ret["url-list"].list(); + std::copy(atp.url_seeds.begin(), atp.url_seeds.end(), std::back_inserter(url_list)); + + entry::list_type& httpseeds_list = ret["httpseeds"].list(); + std::copy(atp.http_seeds.begin(), atp.http_seeds.end(), std::back_inserter(httpseeds_list)); + + // write have bitmask + entry::string_type& pieces = ret["pieces"].string(); + pieces.resize(aux::numeric_cast(std::max( + atp.have_pieces.size(), atp.verified_pieces.size()))); + + std::size_t piece(0); + for (auto const bit : atp.have_pieces) + { + pieces[piece] = bit ? 1 : 0; + ++piece; + } + + piece = 0; + for (auto const bit : atp.verified_pieces) + { + pieces[piece] |= bit ? 2 : 0; + ++piece; + } + + // write renamed files + if (!atp.renamed_files.empty()) + { + entry::list_type& fl = ret["mapped_files"].list(); + for (auto const& ent : atp.renamed_files) + { + std::size_t const idx(static_cast(static_cast(ent.first))); + if (idx >= fl.size()) fl.resize(idx + 1); + fl[idx] = ent.second; + } + } + + // write local peers + if (!atp.peers.empty()) + { + std::back_insert_iterator ptr(ret["peers"].string()); + std::back_insert_iterator ptr6(ret["peers6"].string()); + for (auto const& p : atp.peers) + { + if (is_v6(p)) + write_endpoint(p, ptr6); + else + write_endpoint(p, ptr); + } + } + + if (!atp.banned_peers.empty()) + { + std::back_insert_iterator ptr(ret["banned_peers"].string()); + std::back_insert_iterator ptr6(ret["banned_peers6"].string()); + for (auto const& p : atp.banned_peers) + { + if (is_v6(p)) + write_endpoint(p, ptr6); + else + write_endpoint(p, ptr); + } + } + + ret["upload_rate_limit"] = atp.upload_limit; + ret["download_rate_limit"] = atp.download_limit; + ret["max_connections"] = atp.max_connections; + ret["max_uploads"] = atp.upload_limit; + + if (!atp.file_priorities.empty()) + { + // write file priorities + entry::list_type& prio = ret["file_priority"].list(); + prio.reserve(atp.file_priorities.size()); + for (auto const p : atp.file_priorities) + prio.emplace_back(static_cast(p)); + } + + if (!atp.piece_priorities.empty()) + { + // write piece priorities + entry::string_type& prio = ret["piece_priority"].string(); + prio.reserve(atp.piece_priorities.size()); + for (auto const p : atp.piece_priorities) + prio.push_back(static_cast(static_cast(p))); + } + + return ret; + } + + std::vector write_resume_data_buf(add_torrent_params const& atp) + { + std::vector ret; + entry rd = write_resume_data(atp); + bencode(std::back_inserter(ret), rd); + return ret; + } +} diff --git a/src/xml_parse.cpp b/src/xml_parse.cpp new file mode 100644 index 0000000..e326271 --- /dev/null +++ b/src/xml_parse.cpp @@ -0,0 +1,170 @@ +/* + +Copyright (c) 2007-2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/string_util.hpp" + +namespace libtorrent { + + void xml_parse(string_view input + , std::function callback) + { + char const* p = input.data(); + char const* end = input.data() + input.size(); + for (;p != end; ++p) + { + char const* start = p; + // look for tag start + for (; p != end && *p != '<'; ++p); + + if (p != start) + { + callback(xml_string, {start, std::size_t(p - start)}, {}); + } + + if (p == end) break; + + // skip '<' + ++p; + if (p != end && p + 8 < end && string_begins_no_case("![CDATA[", p)) + { + // CDATA. match '![CDATA[' + p += 8; + start = p; + while (p != end && !string_begins_no_case("]]>", p - 2)) ++p; + + // parse error + if (p == end) + { + callback(xml_parse_error, "unexpected end of file", {}); + break; + } + + callback(xml_string, {start, std::size_t(p - start - 2)}, {}); + continue; + } + + // parse the name of the tag. + for (start = p; p != end && *p != '>' && !is_space(*p); ++p); + + char const* tag_name_end = p; + + // skip the attributes for now + for (; p != end && *p != '>'; ++p); + + // parse error + if (p == end) + { + callback(xml_parse_error, "unexpected end of file", {}); + break; + } + + TORRENT_ASSERT(*p == '>'); + + char const* tag_end = p; + if (*start == '/') + { + ++start; + callback(xml_end_tag, {start, std::size_t(tag_name_end - start)}, {}); + } + else if (*(p - 1) == '/') + { + callback(xml_empty_tag, {start, std::size_t(std::min(tag_name_end - start, p - start - 1))}, {}); + tag_end = p - 1; + } + else if (*start == '?' && *(p - 1) == '?') + { + ++start; + callback(xml_declaration_tag, {start, std::size_t(std::min(tag_name_end - start, p - start - 1))}, {}); + tag_end = p - 1; + } + else if (start + 5 < p && std::memcmp(start, "!--", 3) == 0 && std::memcmp(p - 2, "--", 2) == 0) + { + start += 3; + callback(xml_comment, {start, std::size_t(tag_name_end - start - 2)}, {}); + continue; + } + else + { + callback(xml_start_tag, {start, std::size_t(tag_name_end - start)}, {}); + } + + // parse attributes + for (char const* i = tag_name_end; i < tag_end; ++i) + { + char const* val_start = nullptr; + + // find start of attribute name + while (i != tag_end && is_space(*i)) ++i; + if (i == tag_end) break; + start = i; + // find end of attribute name + while (i != tag_end && *i != '=' && !is_space(*i)) ++i; + std::size_t const name_len = std::size_t(i - start); + + // look for equality sign + for (; i != tag_end && *i != '='; ++i); + + // no equality sign found. Report this as xml_tag_content + // instead of a series of key value pairs + if (i == tag_end) + { + callback(xml_tag_content, {start, std::size_t(i - start)}, {}); + break; + } + + ++i; + while (i != tag_end && is_space(*i)) ++i; + // check for parse error (values must be quoted) + if (i == tag_end || (*i != '\'' && *i != '\"')) + { + callback(xml_parse_error, "unquoted attribute value", {}); + break; + } + char quote = *i; + ++i; + val_start = i; + for (; i != tag_end && *i != quote; ++i); + // parse error (missing end quote) + if (i == tag_end) + { + callback(xml_parse_error, "missing end quote on attribute", {}); + break; + } + callback(xml_attribute, {start, name_len}, {val_start, std::size_t(i - val_start)}); + } + } + } + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..538ec61 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 2.6) + +# Our tests contain printf formating checks therefore we disable GCC format string errors: +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + check_cxx_compiler_flag("-Wno-error=format-truncation" _WNO_ERROR_FORMAT_TRUNCATION) + if (_WNO_ERROR_FORMAT_TRUNCATION) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=format-truncation") + endif() +endif() + +file(GLOB tests "${CMAKE_CURRENT_SOURCE_DIR}/test_*.cpp") +list(REMOVE_ITEM tests "${CMAKE_CURRENT_SOURCE_DIR}/test_natpmp.cpp") # doesn't build at time of writing +list(REMOVE_ITEM tests "${CMAKE_CURRENT_SOURCE_DIR}/test_utils.cpp") # helper file, not a test + +add_library(test_common STATIC main.cpp test.cpp setup_transfer.cpp dht_server.cpp udp_tracker.cpp + peer_server.cpp web_seed_suite.cpp swarm_suite.cpp test_utils.cpp make_torrent.cpp settings.cpp +) +target_link_libraries(test_common PUBLIC torrent-rasterbar) +if (MSVC) + # C4127: conditional expression is constant + # C4309: 'conversion' : truncation of constant value + # C4310: cast truncates constant value + target_compile_options(test_common PUBLIC /wd4127 /wd4309 /wd4310 ) +endif() + +foreach(TARGET_SRC ${tests}) + get_filename_component(TARGET ${TARGET_SRC} NAME_WE) + add_executable(${TARGET} ${TARGET_SRC}) + target_link_libraries(${TARGET} test_common) + add_test(${TARGET} ${TARGET}) +endforeach() + +file(GLOB GZIP_ASSETS "${CMAKE_CURRENT_SOURCE_DIR}/*.gz") +file(COPY ${GZIP_ASSETS} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + +file(GLOB PYTHON_ASSETS "${CMAKE_CURRENT_SOURCE_DIR}/*.py") +file(COPY ${PYTHON_ASSETS} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + +file(GLOB XML_ASSETS "${CMAKE_CURRENT_SOURCE_DIR}/*.xml") +file(COPY ${XML_ASSETS} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + +file(COPY "utf8_test.txt" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "mutable_test_torrents" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "test_torrents" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +file(COPY "ssl" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 0000000..de38598 --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,254 @@ +import testing ; +import feature : feature ; + +use-project /torrent : .. ; + +lib libtorrent_test + : # sources + main.cpp + test.cpp + setup_transfer.cpp + dht_server.cpp + udp_tracker.cpp + peer_server.cpp + bittorrent_peer.cpp + print_alerts.cpp + web_seed_suite.cpp + swarm_suite.cpp + test_utils.cpp + settings.cpp + make_torrent.cpp + + : # requirements + # this is used to determine whether + # symbols are exported or imported + shared:TORRENT_BUILDING_TEST_SHARED + shared:ED25519_BUILD_DLL + ../ed25519/src + windows:advapi32 + /torrent//torrent + on + @warnings + + : # default build + shared + + : # user-requirements + shared:TORRENT_LINK_TEST_SHARED + . +; + +explicit libtorrent_test ; + +lib advapi32 : : Advapi32 ; + +variant debug-mode : debug : on on full ; + +local default-build = + multi + shared + on + on + debug-mode + ; + +project + : requirements + on + libtorrent_test + /torrent//torrent + # C4127: conditional expression is constant + msvc:/wd4127 + # C4309: 'conversion' : truncation of constant value + msvc:/wd4309 + # C4310: cast truncates constant value + msvc:/wd4310 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + @warnings + on + : default-build + $(default-build) + ; + +feature launcher : none valgrind : composite ; +feature.compose valgrind : "valgrind --tool=memcheck -v --num-callers=20 --read-var-info=yes --track-origins=yes --error-exitcode=222 --suppressions=valgrind_suppressions.txt" on ; + +exe test_natpmp : test_natpmp.cpp + : # requirements + /torrent//torrent + on + @warnings + : # default-build + $(default-build) + ; + +exe enum_if : enum_if.cpp + : # requirements + /torrent//torrent + on + @warnings + : # default-build + $(default-build) + ; + +install stage_enum_if : enum_if : . ; + +explicit test_natpmp ; +explicit enum_if ; +explicit stage_enum_if ; + +run test_listen_socket.cpp + : : : openssl:/torrent//ssl + openssl:/torrent//crypto ; + +run test_dht.cpp + test_dht_storage.cpp + test_direct_dht.cpp + : : : openssl:/torrent//ssl + openssl:/torrent//crypto ; + +run test_primitives.cpp ; +run test_io.cpp ; +run test_create_torrent.cpp ; +run test_packet_buffer.cpp ; +run test_timestamp_history.cpp ; +run test_bloom_filter.cpp ; +run test_identify_client.cpp ; +run test_merkle.cpp ; +run test_resolve_links.cpp ; +run test_heterogeneous_queue.cpp ; +run test_ip_voter.cpp ; +run test_sliding_average.cpp ; +run test_socket_io.cpp ; +run test_part_file.cpp ; +run test_peer_list.cpp ; +run test_torrent_info.cpp ; +run test_time.cpp ; +run test_file_storage.cpp ; +run test_peer_priority.cpp ; +run test_threads.cpp ; +run test_tailqueue.cpp ; +run test_bandwidth_limiter.cpp ; +run test_buffer.cpp ; +run test_bencoding.cpp ; +run test_bdecode.cpp ; +run test_http_parser.cpp ; +run test_xml.cpp ; +run test_ip_filter.cpp ; +run test_block_cache.cpp ; +run test_peer_classes.cpp ; +run test_settings_pack.cpp ; +run test_fence.cpp ; +run test_dos_blocker.cpp ; +run test_stat_cache.cpp ; +run test_enum_net.cpp ; +run test_linked_list.cpp ; +run test_stack_allocator.cpp ; +run test_file_progress.cpp ; +run test_generate_peer_id.cpp ; +run test_alloca.cpp ; +run test_piece_picker.cpp ; +run test_string.cpp ; +run test_utf8.cpp ; +run test_hasher.cpp ; +run test_hasher512.cpp ; +run test_sha1_hash.cpp ; +run test_span.cpp ; +run test_bitfield.cpp ; +run test_crc32.cpp ; +run test_ffs.cpp ; +run test_ed25519.cpp ; +run test_gzip.cpp ; +run test_receive_buffer.cpp ; +run test_alert_manager.cpp ; +run test_alert_types.cpp ; +run test_magnet.cpp ; +run test_storage.cpp ; +run test_session.cpp ; +run test_session_params.cpp ; +run test_read_piece.cpp ; +run test_remove_torrent.cpp ; +run test_flags.cpp ; +run test_file.cpp ; +run test_fast_extension.cpp ; +run test_privacy.cpp ; +run test_recheck.cpp ; +run test_read_resume.cpp ; +run test_resume.cpp ; +run test_ssl.cpp : : + : openssl:/torrent//ssl + openssl:/torrent//crypto ; +run test_tracker.cpp ; +run test_checking.cpp ; +run test_url_seed.cpp ; +run test_web_seed.cpp ; +run test_web_seed_redirect.cpp ; +run test_web_seed_socks4.cpp ; +run test_web_seed_socks5.cpp ; +run test_web_seed_socks5_no_peers.cpp ; +run test_web_seed_socks5_pw.cpp ; +run test_web_seed_http.cpp ; +run test_web_seed_http_pw.cpp ; +run test_web_seed_chunked.cpp ; +run test_web_seed_ban.cpp ; +run test_pe_crypto.cpp ; + +run test_remap_files.cpp ; +run test_utp.cpp ; +run test_auto_unchoke.cpp ; +run test_http_connection.cpp : : + : openssl:/torrent//ssl + openssl:/torrent//crypto + ; +run test_torrent.cpp ; +run test_transfer.cpp ; +run test_time_critical.cpp ; +run test_pex.cpp ; +run test_priority.cpp ; + +# turn these tests into simulations +run test_upnp.cpp ; + +run test_lsd.cpp ; +explicit test_lsd ; +explicit test_hasher ; +explicit test_hasher512 ; + +# these are the tests run on appveyor, while the flapping ones are being +# transitioned into simulations +alias win-tests : + test_primitives + test_string + test_dht + test_sha1_hash + test_span + test_bitfield + test_crc32 + test_pe_crypto + test_remap_files + test_auto_unchoke + test_torrent + test_transfer + test_time_critical + test_pex + test_priority + test_storage + test_session + test_read_piece + test_file + test_fast_extension + test_recheck + test_resume + test_tracker + test_checking + test_gzip + test_piece_picker + test_ffs + test_ed25519 + test_session_params + ; + +explicit win-tests ; + diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..434f64c --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,289 @@ +AUTOMAKE_OPTIONS = subdir-objects + +test_programs = \ + test_primitives \ + test_recheck \ + test_stat_cache \ + test_file \ + test_privacy \ + test_priority \ + test_remove_torrent \ + test_auto_unchoke \ + test_checking \ + test_fast_extension \ + test_http_connection \ + test_lsd \ + test_pe_crypto \ + test_pex \ + test_read_piece \ + test_receive_buffer \ + test_resume \ + test_read_resume \ + test_stack_allocator \ + test_ssl \ + test_storage \ + test_time_critical \ + test_torrent \ + test_tracker \ + test_transfer \ + test_create_torrent \ + enum_if \ + test_utp \ + test_session \ + test_web_seed \ + test_web_seed_ban \ + test_web_seed_chunked \ + test_web_seed_http \ + test_web_seed_http_pw \ + test_web_seed_redirect \ + test_web_seed_socks4 \ + test_web_seed_socks5 \ + test_web_seed_socks5_no_peers \ + test_web_seed_socks5_pw \ + test_url_seed \ + test_remap_files \ + test_enum_net \ + test_file_progress \ + test_linked_list \ + test_direct_dht \ + test_ffs \ + test_session_params \ + test_span \ + test_io \ + test_alloca + +if ENABLE_TESTS +check_PROGRAMS = $(test_programs) +noinst_LTLIBRARIES = libtest.la +endif + +TESTS = $(test_programs) + +TEST_TORRENTS = \ + absolute_filename.torrent \ + backslash_path.torrent \ + bad_name.torrent \ + base.torrent \ + creation_date.torrent \ + duplicate_files.torrent \ + duplicate_web_seeds.torrent \ + empty_httpseed.torrent \ + empty_path.torrent \ + empty_path_multi.torrent \ + hidden_parent_path.torrent \ + httpseed.torrent \ + invalid_file_size.torrent \ + invalid_filename.torrent \ + invalid_filename2.torrent \ + invalid_info.torrent \ + invalid_merkle.torrent \ + invalid_name.torrent \ + invalid_name2.torrent \ + invalid_name3.torrent \ + invalid_path_list.torrent \ + invalid_piece_len.torrent \ + invalid_pieces.torrent \ + invalid_root_hash.torrent \ + invalid_root_hash2.torrent \ + invalid_symlink.torrent \ + large.torrent \ + long_name.torrent \ + many_pieces.torrent \ + missing_path_list.torrent \ + missing_piece_len.torrent \ + negative_file_size.torrent \ + negative_piece_len.torrent \ + negative_size.torrent \ + no_creation_date.torrent \ + no_files.torrent \ + no_name.torrent \ + pad_file.torrent \ + pad_file_no_path.torrent \ + parent_path.torrent \ + root_hash.torrent \ + sample.torrent \ + single_multi_file.torrent \ + slash_path.torrent \ + slash_path2.torrent \ + slash_path3.torrent \ + string.torrent \ + symlink1.torrent \ + symlink2.torrent \ + symlink_zero_size.torrent \ + unaligned_pieces.torrent \ + unordered.torrent \ + url_list.torrent \ + url_list2.torrent \ + url_list3.torrent \ + url_seed.torrent \ + url_seed_multi.torrent \ + url_seed_multi_space.torrent \ + url_seed_multi_space_nolist.torrent \ + url_seed_multi_single_file.torrent \ + whitespace_url.torrent \ + overlapping_symlinks.torrent + +MUTABLE_TEST_TORRENTS = \ + test1.torrent \ + test1_pad_files.torrent \ + test1_single.torrent \ + test1_single_padded.torrent \ + test2.torrent \ + test2_pad_files.torrent \ + test3.torrent \ + test3_pad_files.torrent + +EXTRA_DIST = Jamfile \ + $(addprefix test_torrents/,${TEST_TORRENTS}) \ + $(addprefix mutable_test_torrents/,${MUTABLE_TEST_TORRENTS}) \ + ssl/regenerate_test_certificate.sh \ + ssl/cert_request.pem \ + ssl/dhparams.pem \ + ssl/invalid_peer_certificate.pem \ + ssl/invalid_peer_private_key.pem \ + ssl/peer_certificate.pem \ + ssl/peer_private_key.pem \ + ssl/root_ca_cert.pem \ + ssl/root_ca_private.pem \ + ssl/server.pem \ + zeroes.gz \ + corrupt.gz \ + invalid1.gz \ + utf8_test.txt \ + web_server.py \ + root1.xml \ + root2.xml \ + root3.xml \ + socks.py \ + http_proxy.py \ + CMakeLists.txt + +EXTRA_PROGRAMS = $(test_programs) + +noinst_HEADERS = test.hpp setup_transfer.hpp dht_server.hpp \ + peer_server.hpp udp_tracker.hpp web_seed_suite.hpp swarm_suite.hpp \ + test_utils.hpp settings.hpp make_torrent.hpp bittorrent_peer.hpp \ + print_alerts.hpp + +libtest_la_SOURCES = main.cpp \ + test.cpp \ + setup_transfer.cpp \ + dht_server.cpp \ + udp_tracker.cpp \ + peer_server.cpp \ + bittorrent_peer.cpp \ + make_torrent.cpp \ + web_seed_suite.cpp \ + swarm_suite.cpp \ + test_utils.cpp \ + settings.cpp \ + print_alerts.cpp + +test_primitives_SOURCES = \ + test_primitives.cpp \ + test_packet_buffer.cpp \ + test_timestamp_history.cpp \ + test_sha1_hash.cpp \ + test_bloom_filter.cpp \ + test_identify_client.cpp \ + test_merkle.cpp \ + test_alert_manager.cpp \ + test_alert_types.cpp \ + test_resolve_links.cpp \ + test_crc32.cpp \ + test_heterogeneous_queue.cpp \ + test_listen_socket.cpp \ + test_ip_voter.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_utf8.cpp \ + test_gzip.cpp \ + test_bitfield.cpp \ + test_part_file.cpp \ + test_peer_list.cpp \ + test_torrent_info.cpp \ + test_time.cpp \ + test_file_storage.cpp \ + test_peer_priority.cpp \ + test_threads.cpp \ + test_tailqueue.cpp \ + test_bandwidth_limiter.cpp \ + test_buffer.cpp \ + test_piece_picker.cpp \ + test_bencoding.cpp \ + test_bdecode.cpp \ + test_http_parser.cpp \ + test_string.cpp \ + test_magnet.cpp \ + test_xml.cpp \ + test_ip_filter.cpp \ + test_hasher.cpp \ + test_hasher512.cpp \ + test_ed25519.cpp \ + test_dht_storage.cpp \ + test_dht.cpp \ + test_block_cache.cpp \ + test_peer_classes.cpp \ + test_settings_pack.cpp \ + test_fence.cpp \ + test_dos_blocker.cpp \ + test_upnp.cpp \ + test_flags.cpp \ + test_generate_peer_id.cpp + +test_recheck_SOURCES = test_recheck.cpp +test_stat_cache_SOURCES = test_stat_cache.cpp +test_file_SOURCES = test_file.cpp +test_privacy_SOURCES = test_privacy.cpp +test_priority_SOURCES = test_priority.cpp +test_remove_torrent_SOURCES = test_remove_torrent.cpp +test_auto_unchoke_SOURCES = test_auto_unchoke.cpp +test_checking_SOURCES = test_checking.cpp +test_enum_net_SOURCES = test_enum_net.cpp +test_fast_extension_SOURCES = test_fast_extension.cpp +test_http_connection_SOURCES = test_http_connection.cpp +test_lsd_SOURCES = test_lsd.cpp +test_pe_crypto_SOURCES = test_pe_crypto.cpp +test_pex_SOURCES = test_pex.cpp +test_read_piece_SOURCES = test_read_piece.cpp +test_receive_buffer_SOURCES = test_receive_buffer.cpp +test_storage_SOURCES = test_storage.cpp +test_time_critical_SOURCES = test_time_critical.cpp +test_resume_SOURCES = test_resume.cpp +test_read_resume_SOURCES = test_read_resume.cpp +test_stack_allocator_SOURCES = test_stack_allocator.cpp +test_ssl_SOURCES = test_ssl.cpp +test_torrent_SOURCES = test_torrent.cpp +test_tracker_SOURCES = test_tracker.cpp +test_transfer_SOURCES = test_transfer.cpp +test_create_torrent_SOURCES = test_create_torrent.cpp +enum_if_SOURCES = enum_if.cpp +test_utp_SOURCES = test_utp.cpp +test_session_SOURCES = test_session.cpp +test_web_seed_SOURCES = test_web_seed.cpp +test_web_seed_ban_SOURCES = test_web_seed_ban.cpp +test_web_seed_chunked_SOURCES = test_web_seed_chunked.cpp +test_web_seed_http_SOURCES = test_web_seed_http.cpp +test_web_seed_http_pw_SOURCES = test_web_seed_http_pw.cpp +test_web_seed_redirect_SOURCES = test_web_seed_redirect.cpp +test_web_seed_socks4_SOURCES = test_web_seed_socks4.cpp +test_web_seed_socks5_SOURCES = test_web_seed_socks5.cpp +test_web_seed_socks5_no_peers_SOURCES = test_web_seed_socks5_no_peers.cpp +test_web_seed_socks5_pw_SOURCES = test_web_seed_socks5_pw.cpp +test_url_seed_SOURCES = test_url_seed.cpp +test_remap_files_SOURCES = test_remap_files.cpp +test_file_progress_SOURCES = test_file_progress.cpp +test_linked_list_SOURCES = test_linked_list.cpp +test_direct_dht_SOURCES = test_direct_dht.cpp +test_ffs_SOURCES = test_ffs.cpp +test_session_params_SOURCES = test_session_params.cpp +test_span_SOURCES = test_span.cpp +test_io_SOURCES = test_io.cpp +test_alloca_SOURCES = test_alloca.cpp + +LDADD = libtest.la $(top_builddir)/src/libtorrent-rasterbar.la + +#AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CPPFLAGS=-ftemplate-depth-50 @DEBUGFLAGS@ +AM_LDFLAGS=@BOOST_SYSTEM_LIB@ @PTHREAD_LIBS@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 0000000..b4cdd45 --- /dev/null +++ b/test/Makefile.in @@ -0,0 +1,2625 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@ENABLE_TESTS_TRUE@check_PROGRAMS = $(am__EXEEXT_1) +TESTS = $(am__EXEEXT_1) +EXTRA_PROGRAMS = $(am__EXEEXT_1) +subdir = test +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = test_primitives$(EXEEXT) test_recheck$(EXEEXT) \ + test_stat_cache$(EXEEXT) test_file$(EXEEXT) \ + test_privacy$(EXEEXT) test_priority$(EXEEXT) \ + test_remove_torrent$(EXEEXT) test_auto_unchoke$(EXEEXT) \ + test_checking$(EXEEXT) test_fast_extension$(EXEEXT) \ + test_http_connection$(EXEEXT) test_lsd$(EXEEXT) \ + test_pe_crypto$(EXEEXT) test_pex$(EXEEXT) \ + test_read_piece$(EXEEXT) test_receive_buffer$(EXEEXT) \ + test_resume$(EXEEXT) test_read_resume$(EXEEXT) \ + test_stack_allocator$(EXEEXT) test_ssl$(EXEEXT) \ + test_storage$(EXEEXT) test_time_critical$(EXEEXT) \ + test_torrent$(EXEEXT) test_tracker$(EXEEXT) \ + test_transfer$(EXEEXT) test_create_torrent$(EXEEXT) \ + enum_if$(EXEEXT) test_utp$(EXEEXT) test_session$(EXEEXT) \ + test_web_seed$(EXEEXT) test_web_seed_ban$(EXEEXT) \ + test_web_seed_chunked$(EXEEXT) test_web_seed_http$(EXEEXT) \ + test_web_seed_http_pw$(EXEEXT) test_web_seed_redirect$(EXEEXT) \ + test_web_seed_socks4$(EXEEXT) test_web_seed_socks5$(EXEEXT) \ + test_web_seed_socks5_no_peers$(EXEEXT) \ + test_web_seed_socks5_pw$(EXEEXT) test_url_seed$(EXEEXT) \ + test_remap_files$(EXEEXT) test_enum_net$(EXEEXT) \ + test_file_progress$(EXEEXT) test_linked_list$(EXEEXT) \ + test_direct_dht$(EXEEXT) test_ffs$(EXEEXT) \ + test_session_params$(EXEEXT) test_span$(EXEEXT) \ + test_io$(EXEEXT) test_alloca$(EXEEXT) +LTLIBRARIES = $(noinst_LTLIBRARIES) +libtest_la_LIBADD = +am_libtest_la_OBJECTS = main.lo test.lo setup_transfer.lo \ + dht_server.lo udp_tracker.lo peer_server.lo bittorrent_peer.lo \ + make_torrent.lo web_seed_suite.lo swarm_suite.lo test_utils.lo \ + settings.lo print_alerts.lo +libtest_la_OBJECTS = $(am_libtest_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +@ENABLE_TESTS_TRUE@am_libtest_la_rpath = +am_enum_if_OBJECTS = enum_if.$(OBJEXT) +enum_if_OBJECTS = $(am_enum_if_OBJECTS) +enum_if_LDADD = $(LDADD) +enum_if_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_alloca_OBJECTS = test_alloca.$(OBJEXT) +test_alloca_OBJECTS = $(am_test_alloca_OBJECTS) +test_alloca_LDADD = $(LDADD) +test_alloca_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_auto_unchoke_OBJECTS = test_auto_unchoke.$(OBJEXT) +test_auto_unchoke_OBJECTS = $(am_test_auto_unchoke_OBJECTS) +test_auto_unchoke_LDADD = $(LDADD) +test_auto_unchoke_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_checking_OBJECTS = test_checking.$(OBJEXT) +test_checking_OBJECTS = $(am_test_checking_OBJECTS) +test_checking_LDADD = $(LDADD) +test_checking_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_create_torrent_OBJECTS = test_create_torrent.$(OBJEXT) +test_create_torrent_OBJECTS = $(am_test_create_torrent_OBJECTS) +test_create_torrent_LDADD = $(LDADD) +test_create_torrent_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_direct_dht_OBJECTS = test_direct_dht.$(OBJEXT) +test_direct_dht_OBJECTS = $(am_test_direct_dht_OBJECTS) +test_direct_dht_LDADD = $(LDADD) +test_direct_dht_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_enum_net_OBJECTS = test_enum_net.$(OBJEXT) +test_enum_net_OBJECTS = $(am_test_enum_net_OBJECTS) +test_enum_net_LDADD = $(LDADD) +test_enum_net_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_fast_extension_OBJECTS = test_fast_extension.$(OBJEXT) +test_fast_extension_OBJECTS = $(am_test_fast_extension_OBJECTS) +test_fast_extension_LDADD = $(LDADD) +test_fast_extension_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_ffs_OBJECTS = test_ffs.$(OBJEXT) +test_ffs_OBJECTS = $(am_test_ffs_OBJECTS) +test_ffs_LDADD = $(LDADD) +test_ffs_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_file_OBJECTS = test_file.$(OBJEXT) +test_file_OBJECTS = $(am_test_file_OBJECTS) +test_file_LDADD = $(LDADD) +test_file_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_file_progress_OBJECTS = test_file_progress.$(OBJEXT) +test_file_progress_OBJECTS = $(am_test_file_progress_OBJECTS) +test_file_progress_LDADD = $(LDADD) +test_file_progress_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_http_connection_OBJECTS = test_http_connection.$(OBJEXT) +test_http_connection_OBJECTS = $(am_test_http_connection_OBJECTS) +test_http_connection_LDADD = $(LDADD) +test_http_connection_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_io_OBJECTS = test_io.$(OBJEXT) +test_io_OBJECTS = $(am_test_io_OBJECTS) +test_io_LDADD = $(LDADD) +test_io_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_linked_list_OBJECTS = test_linked_list.$(OBJEXT) +test_linked_list_OBJECTS = $(am_test_linked_list_OBJECTS) +test_linked_list_LDADD = $(LDADD) +test_linked_list_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_lsd_OBJECTS = test_lsd.$(OBJEXT) +test_lsd_OBJECTS = $(am_test_lsd_OBJECTS) +test_lsd_LDADD = $(LDADD) +test_lsd_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_pe_crypto_OBJECTS = test_pe_crypto.$(OBJEXT) +test_pe_crypto_OBJECTS = $(am_test_pe_crypto_OBJECTS) +test_pe_crypto_LDADD = $(LDADD) +test_pe_crypto_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_pex_OBJECTS = test_pex.$(OBJEXT) +test_pex_OBJECTS = $(am_test_pex_OBJECTS) +test_pex_LDADD = $(LDADD) +test_pex_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_primitives_OBJECTS = test_primitives.$(OBJEXT) \ + test_packet_buffer.$(OBJEXT) test_timestamp_history.$(OBJEXT) \ + test_sha1_hash.$(OBJEXT) test_bloom_filter.$(OBJEXT) \ + test_identify_client.$(OBJEXT) test_merkle.$(OBJEXT) \ + test_alert_manager.$(OBJEXT) test_alert_types.$(OBJEXT) \ + test_resolve_links.$(OBJEXT) test_crc32.$(OBJEXT) \ + test_heterogeneous_queue.$(OBJEXT) \ + test_listen_socket.$(OBJEXT) test_ip_voter.$(OBJEXT) \ + test_sliding_average.$(OBJEXT) test_socket_io.$(OBJEXT) \ + test_utf8.$(OBJEXT) test_gzip.$(OBJEXT) \ + test_bitfield.$(OBJEXT) test_part_file.$(OBJEXT) \ + test_peer_list.$(OBJEXT) test_torrent_info.$(OBJEXT) \ + test_time.$(OBJEXT) test_file_storage.$(OBJEXT) \ + test_peer_priority.$(OBJEXT) test_threads.$(OBJEXT) \ + test_tailqueue.$(OBJEXT) test_bandwidth_limiter.$(OBJEXT) \ + test_buffer.$(OBJEXT) test_piece_picker.$(OBJEXT) \ + test_bencoding.$(OBJEXT) test_bdecode.$(OBJEXT) \ + test_http_parser.$(OBJEXT) test_string.$(OBJEXT) \ + test_magnet.$(OBJEXT) test_xml.$(OBJEXT) \ + test_ip_filter.$(OBJEXT) test_hasher.$(OBJEXT) \ + test_hasher512.$(OBJEXT) test_ed25519.$(OBJEXT) \ + test_dht_storage.$(OBJEXT) test_dht.$(OBJEXT) \ + test_block_cache.$(OBJEXT) test_peer_classes.$(OBJEXT) \ + test_settings_pack.$(OBJEXT) test_fence.$(OBJEXT) \ + test_dos_blocker.$(OBJEXT) test_upnp.$(OBJEXT) \ + test_flags.$(OBJEXT) test_generate_peer_id.$(OBJEXT) +test_primitives_OBJECTS = $(am_test_primitives_OBJECTS) +test_primitives_LDADD = $(LDADD) +test_primitives_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_priority_OBJECTS = test_priority.$(OBJEXT) +test_priority_OBJECTS = $(am_test_priority_OBJECTS) +test_priority_LDADD = $(LDADD) +test_priority_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_privacy_OBJECTS = test_privacy.$(OBJEXT) +test_privacy_OBJECTS = $(am_test_privacy_OBJECTS) +test_privacy_LDADD = $(LDADD) +test_privacy_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_read_piece_OBJECTS = test_read_piece.$(OBJEXT) +test_read_piece_OBJECTS = $(am_test_read_piece_OBJECTS) +test_read_piece_LDADD = $(LDADD) +test_read_piece_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_read_resume_OBJECTS = test_read_resume.$(OBJEXT) +test_read_resume_OBJECTS = $(am_test_read_resume_OBJECTS) +test_read_resume_LDADD = $(LDADD) +test_read_resume_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_receive_buffer_OBJECTS = test_receive_buffer.$(OBJEXT) +test_receive_buffer_OBJECTS = $(am_test_receive_buffer_OBJECTS) +test_receive_buffer_LDADD = $(LDADD) +test_receive_buffer_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_recheck_OBJECTS = test_recheck.$(OBJEXT) +test_recheck_OBJECTS = $(am_test_recheck_OBJECTS) +test_recheck_LDADD = $(LDADD) +test_recheck_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_remap_files_OBJECTS = test_remap_files.$(OBJEXT) +test_remap_files_OBJECTS = $(am_test_remap_files_OBJECTS) +test_remap_files_LDADD = $(LDADD) +test_remap_files_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_remove_torrent_OBJECTS = test_remove_torrent.$(OBJEXT) +test_remove_torrent_OBJECTS = $(am_test_remove_torrent_OBJECTS) +test_remove_torrent_LDADD = $(LDADD) +test_remove_torrent_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_resume_OBJECTS = test_resume.$(OBJEXT) +test_resume_OBJECTS = $(am_test_resume_OBJECTS) +test_resume_LDADD = $(LDADD) +test_resume_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_session_OBJECTS = test_session.$(OBJEXT) +test_session_OBJECTS = $(am_test_session_OBJECTS) +test_session_LDADD = $(LDADD) +test_session_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_session_params_OBJECTS = test_session_params.$(OBJEXT) +test_session_params_OBJECTS = $(am_test_session_params_OBJECTS) +test_session_params_LDADD = $(LDADD) +test_session_params_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_span_OBJECTS = test_span.$(OBJEXT) +test_span_OBJECTS = $(am_test_span_OBJECTS) +test_span_LDADD = $(LDADD) +test_span_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_ssl_OBJECTS = test_ssl.$(OBJEXT) +test_ssl_OBJECTS = $(am_test_ssl_OBJECTS) +test_ssl_LDADD = $(LDADD) +test_ssl_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_stack_allocator_OBJECTS = test_stack_allocator.$(OBJEXT) +test_stack_allocator_OBJECTS = $(am_test_stack_allocator_OBJECTS) +test_stack_allocator_LDADD = $(LDADD) +test_stack_allocator_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_stat_cache_OBJECTS = test_stat_cache.$(OBJEXT) +test_stat_cache_OBJECTS = $(am_test_stat_cache_OBJECTS) +test_stat_cache_LDADD = $(LDADD) +test_stat_cache_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_storage_OBJECTS = test_storage.$(OBJEXT) +test_storage_OBJECTS = $(am_test_storage_OBJECTS) +test_storage_LDADD = $(LDADD) +test_storage_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_time_critical_OBJECTS = test_time_critical.$(OBJEXT) +test_time_critical_OBJECTS = $(am_test_time_critical_OBJECTS) +test_time_critical_LDADD = $(LDADD) +test_time_critical_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_torrent_OBJECTS = test_torrent.$(OBJEXT) +test_torrent_OBJECTS = $(am_test_torrent_OBJECTS) +test_torrent_LDADD = $(LDADD) +test_torrent_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_tracker_OBJECTS = test_tracker.$(OBJEXT) +test_tracker_OBJECTS = $(am_test_tracker_OBJECTS) +test_tracker_LDADD = $(LDADD) +test_tracker_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_transfer_OBJECTS = test_transfer.$(OBJEXT) +test_transfer_OBJECTS = $(am_test_transfer_OBJECTS) +test_transfer_LDADD = $(LDADD) +test_transfer_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_url_seed_OBJECTS = test_url_seed.$(OBJEXT) +test_url_seed_OBJECTS = $(am_test_url_seed_OBJECTS) +test_url_seed_LDADD = $(LDADD) +test_url_seed_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_utp_OBJECTS = test_utp.$(OBJEXT) +test_utp_OBJECTS = $(am_test_utp_OBJECTS) +test_utp_LDADD = $(LDADD) +test_utp_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_OBJECTS = test_web_seed.$(OBJEXT) +test_web_seed_OBJECTS = $(am_test_web_seed_OBJECTS) +test_web_seed_LDADD = $(LDADD) +test_web_seed_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_ban_OBJECTS = test_web_seed_ban.$(OBJEXT) +test_web_seed_ban_OBJECTS = $(am_test_web_seed_ban_OBJECTS) +test_web_seed_ban_LDADD = $(LDADD) +test_web_seed_ban_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_chunked_OBJECTS = test_web_seed_chunked.$(OBJEXT) +test_web_seed_chunked_OBJECTS = $(am_test_web_seed_chunked_OBJECTS) +test_web_seed_chunked_LDADD = $(LDADD) +test_web_seed_chunked_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_http_OBJECTS = test_web_seed_http.$(OBJEXT) +test_web_seed_http_OBJECTS = $(am_test_web_seed_http_OBJECTS) +test_web_seed_http_LDADD = $(LDADD) +test_web_seed_http_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_http_pw_OBJECTS = test_web_seed_http_pw.$(OBJEXT) +test_web_seed_http_pw_OBJECTS = $(am_test_web_seed_http_pw_OBJECTS) +test_web_seed_http_pw_LDADD = $(LDADD) +test_web_seed_http_pw_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_redirect_OBJECTS = test_web_seed_redirect.$(OBJEXT) +test_web_seed_redirect_OBJECTS = $(am_test_web_seed_redirect_OBJECTS) +test_web_seed_redirect_LDADD = $(LDADD) +test_web_seed_redirect_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks4_OBJECTS = test_web_seed_socks4.$(OBJEXT) +test_web_seed_socks4_OBJECTS = $(am_test_web_seed_socks4_OBJECTS) +test_web_seed_socks4_LDADD = $(LDADD) +test_web_seed_socks4_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks5_OBJECTS = test_web_seed_socks5.$(OBJEXT) +test_web_seed_socks5_OBJECTS = $(am_test_web_seed_socks5_OBJECTS) +test_web_seed_socks5_LDADD = $(LDADD) +test_web_seed_socks5_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks5_no_peers_OBJECTS = \ + test_web_seed_socks5_no_peers.$(OBJEXT) +test_web_seed_socks5_no_peers_OBJECTS = \ + $(am_test_web_seed_socks5_no_peers_OBJECTS) +test_web_seed_socks5_no_peers_LDADD = $(LDADD) +test_web_seed_socks5_no_peers_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +am_test_web_seed_socks5_pw_OBJECTS = \ + test_web_seed_socks5_pw.$(OBJEXT) +test_web_seed_socks5_pw_OBJECTS = \ + $(am_test_web_seed_socks5_pw_OBJECTS) +test_web_seed_socks5_pw_LDADD = $(LDADD) +test_web_seed_socks5_pw_DEPENDENCIES = libtest.la \ + $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/bittorrent_peer.Plo \ + ./$(DEPDIR)/dht_server.Plo ./$(DEPDIR)/enum_if.Po \ + ./$(DEPDIR)/main.Plo ./$(DEPDIR)/make_torrent.Plo \ + ./$(DEPDIR)/peer_server.Plo ./$(DEPDIR)/print_alerts.Plo \ + ./$(DEPDIR)/settings.Plo ./$(DEPDIR)/setup_transfer.Plo \ + ./$(DEPDIR)/swarm_suite.Plo ./$(DEPDIR)/test.Plo \ + ./$(DEPDIR)/test_alert_manager.Po \ + ./$(DEPDIR)/test_alert_types.Po ./$(DEPDIR)/test_alloca.Po \ + ./$(DEPDIR)/test_auto_unchoke.Po \ + ./$(DEPDIR)/test_bandwidth_limiter.Po \ + ./$(DEPDIR)/test_bdecode.Po ./$(DEPDIR)/test_bencoding.Po \ + ./$(DEPDIR)/test_bitfield.Po ./$(DEPDIR)/test_block_cache.Po \ + ./$(DEPDIR)/test_bloom_filter.Po ./$(DEPDIR)/test_buffer.Po \ + ./$(DEPDIR)/test_checking.Po ./$(DEPDIR)/test_crc32.Po \ + ./$(DEPDIR)/test_create_torrent.Po ./$(DEPDIR)/test_dht.Po \ + ./$(DEPDIR)/test_dht_storage.Po ./$(DEPDIR)/test_direct_dht.Po \ + ./$(DEPDIR)/test_dos_blocker.Po ./$(DEPDIR)/test_ed25519.Po \ + ./$(DEPDIR)/test_enum_net.Po \ + ./$(DEPDIR)/test_fast_extension.Po ./$(DEPDIR)/test_fence.Po \ + ./$(DEPDIR)/test_ffs.Po ./$(DEPDIR)/test_file.Po \ + ./$(DEPDIR)/test_file_progress.Po \ + ./$(DEPDIR)/test_file_storage.Po ./$(DEPDIR)/test_flags.Po \ + ./$(DEPDIR)/test_generate_peer_id.Po ./$(DEPDIR)/test_gzip.Po \ + ./$(DEPDIR)/test_hasher.Po ./$(DEPDIR)/test_hasher512.Po \ + ./$(DEPDIR)/test_heterogeneous_queue.Po \ + ./$(DEPDIR)/test_http_connection.Po \ + ./$(DEPDIR)/test_http_parser.Po \ + ./$(DEPDIR)/test_identify_client.Po ./$(DEPDIR)/test_io.Po \ + ./$(DEPDIR)/test_ip_filter.Po ./$(DEPDIR)/test_ip_voter.Po \ + ./$(DEPDIR)/test_linked_list.Po \ + ./$(DEPDIR)/test_listen_socket.Po ./$(DEPDIR)/test_lsd.Po \ + ./$(DEPDIR)/test_magnet.Po ./$(DEPDIR)/test_merkle.Po \ + ./$(DEPDIR)/test_packet_buffer.Po \ + ./$(DEPDIR)/test_part_file.Po ./$(DEPDIR)/test_pe_crypto.Po \ + ./$(DEPDIR)/test_peer_classes.Po ./$(DEPDIR)/test_peer_list.Po \ + ./$(DEPDIR)/test_peer_priority.Po ./$(DEPDIR)/test_pex.Po \ + ./$(DEPDIR)/test_piece_picker.Po \ + ./$(DEPDIR)/test_primitives.Po ./$(DEPDIR)/test_priority.Po \ + ./$(DEPDIR)/test_privacy.Po ./$(DEPDIR)/test_read_piece.Po \ + ./$(DEPDIR)/test_read_resume.Po \ + ./$(DEPDIR)/test_receive_buffer.Po ./$(DEPDIR)/test_recheck.Po \ + ./$(DEPDIR)/test_remap_files.Po \ + ./$(DEPDIR)/test_remove_torrent.Po \ + ./$(DEPDIR)/test_resolve_links.Po ./$(DEPDIR)/test_resume.Po \ + ./$(DEPDIR)/test_session.Po ./$(DEPDIR)/test_session_params.Po \ + ./$(DEPDIR)/test_settings_pack.Po \ + ./$(DEPDIR)/test_sha1_hash.Po \ + ./$(DEPDIR)/test_sliding_average.Po \ + ./$(DEPDIR)/test_socket_io.Po ./$(DEPDIR)/test_span.Po \ + ./$(DEPDIR)/test_ssl.Po ./$(DEPDIR)/test_stack_allocator.Po \ + ./$(DEPDIR)/test_stat_cache.Po ./$(DEPDIR)/test_storage.Po \ + ./$(DEPDIR)/test_string.Po ./$(DEPDIR)/test_tailqueue.Po \ + ./$(DEPDIR)/test_threads.Po ./$(DEPDIR)/test_time.Po \ + ./$(DEPDIR)/test_time_critical.Po \ + ./$(DEPDIR)/test_timestamp_history.Po \ + ./$(DEPDIR)/test_torrent.Po ./$(DEPDIR)/test_torrent_info.Po \ + ./$(DEPDIR)/test_tracker.Po ./$(DEPDIR)/test_transfer.Po \ + ./$(DEPDIR)/test_upnp.Po ./$(DEPDIR)/test_url_seed.Po \ + ./$(DEPDIR)/test_utf8.Po ./$(DEPDIR)/test_utils.Plo \ + ./$(DEPDIR)/test_utp.Po ./$(DEPDIR)/test_web_seed.Po \ + ./$(DEPDIR)/test_web_seed_ban.Po \ + ./$(DEPDIR)/test_web_seed_chunked.Po \ + ./$(DEPDIR)/test_web_seed_http.Po \ + ./$(DEPDIR)/test_web_seed_http_pw.Po \ + ./$(DEPDIR)/test_web_seed_redirect.Po \ + ./$(DEPDIR)/test_web_seed_socks4.Po \ + ./$(DEPDIR)/test_web_seed_socks5.Po \ + ./$(DEPDIR)/test_web_seed_socks5_no_peers.Po \ + ./$(DEPDIR)/test_web_seed_socks5_pw.Po ./$(DEPDIR)/test_xml.Po \ + ./$(DEPDIR)/udp_tracker.Plo ./$(DEPDIR)/web_seed_suite.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libtest_la_SOURCES) $(enum_if_SOURCES) \ + $(test_alloca_SOURCES) $(test_auto_unchoke_SOURCES) \ + $(test_checking_SOURCES) $(test_create_torrent_SOURCES) \ + $(test_direct_dht_SOURCES) $(test_enum_net_SOURCES) \ + $(test_fast_extension_SOURCES) $(test_ffs_SOURCES) \ + $(test_file_SOURCES) $(test_file_progress_SOURCES) \ + $(test_http_connection_SOURCES) $(test_io_SOURCES) \ + $(test_linked_list_SOURCES) $(test_lsd_SOURCES) \ + $(test_pe_crypto_SOURCES) $(test_pex_SOURCES) \ + $(test_primitives_SOURCES) $(test_priority_SOURCES) \ + $(test_privacy_SOURCES) $(test_read_piece_SOURCES) \ + $(test_read_resume_SOURCES) $(test_receive_buffer_SOURCES) \ + $(test_recheck_SOURCES) $(test_remap_files_SOURCES) \ + $(test_remove_torrent_SOURCES) $(test_resume_SOURCES) \ + $(test_session_SOURCES) $(test_session_params_SOURCES) \ + $(test_span_SOURCES) $(test_ssl_SOURCES) \ + $(test_stack_allocator_SOURCES) $(test_stat_cache_SOURCES) \ + $(test_storage_SOURCES) $(test_time_critical_SOURCES) \ + $(test_torrent_SOURCES) $(test_tracker_SOURCES) \ + $(test_transfer_SOURCES) $(test_url_seed_SOURCES) \ + $(test_utp_SOURCES) $(test_web_seed_SOURCES) \ + $(test_web_seed_ban_SOURCES) $(test_web_seed_chunked_SOURCES) \ + $(test_web_seed_http_SOURCES) $(test_web_seed_http_pw_SOURCES) \ + $(test_web_seed_redirect_SOURCES) \ + $(test_web_seed_socks4_SOURCES) \ + $(test_web_seed_socks5_SOURCES) \ + $(test_web_seed_socks5_no_peers_SOURCES) \ + $(test_web_seed_socks5_pw_SOURCES) +DIST_SOURCES = $(libtest_la_SOURCES) $(enum_if_SOURCES) \ + $(test_alloca_SOURCES) $(test_auto_unchoke_SOURCES) \ + $(test_checking_SOURCES) $(test_create_torrent_SOURCES) \ + $(test_direct_dht_SOURCES) $(test_enum_net_SOURCES) \ + $(test_fast_extension_SOURCES) $(test_ffs_SOURCES) \ + $(test_file_SOURCES) $(test_file_progress_SOURCES) \ + $(test_http_connection_SOURCES) $(test_io_SOURCES) \ + $(test_linked_list_SOURCES) $(test_lsd_SOURCES) \ + $(test_pe_crypto_SOURCES) $(test_pex_SOURCES) \ + $(test_primitives_SOURCES) $(test_priority_SOURCES) \ + $(test_privacy_SOURCES) $(test_read_piece_SOURCES) \ + $(test_read_resume_SOURCES) $(test_receive_buffer_SOURCES) \ + $(test_recheck_SOURCES) $(test_remap_files_SOURCES) \ + $(test_remove_torrent_SOURCES) $(test_resume_SOURCES) \ + $(test_session_SOURCES) $(test_session_params_SOURCES) \ + $(test_span_SOURCES) $(test_ssl_SOURCES) \ + $(test_stack_allocator_SOURCES) $(test_stat_cache_SOURCES) \ + $(test_storage_SOURCES) $(test_time_critical_SOURCES) \ + $(test_torrent_SOURCES) $(test_tracker_SOURCES) \ + $(test_transfer_SOURCES) $(test_url_seed_SOURCES) \ + $(test_utp_SOURCES) $(test_web_seed_SOURCES) \ + $(test_web_seed_ban_SOURCES) $(test_web_seed_chunked_SOURCES) \ + $(test_web_seed_http_SOURCES) $(test_web_seed_http_pw_SOURCES) \ + $(test_web_seed_redirect_SOURCES) \ + $(test_web_seed_socks4_SOURCES) \ + $(test_web_seed_socks5_SOURCES) \ + $(test_web_seed_socks5_no_peers_SOURCES) \ + $(test_web_seed_socks5_pw_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red=''; \ + grn=''; \ + lgn=''; \ + blu=''; \ + mgn=''; \ + brg=''; \ + std=''; \ + fi; \ +} +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__recheck_rx = ^[ ]*:recheck:[ ]* +am__global_test_result_rx = ^[ ]*:global-test-result:[ ]* +am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]* +# A command that, given a newline-separated list of test names on the +# standard input, print the name of the tests that are to be re-run +# upon "make recheck". +am__list_recheck_tests = $(AWK) '{ \ + recheck = 1; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + { \ + if ((getline line2 < ($$0 ".log")) < 0) \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \ + { \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \ + { \ + break; \ + } \ + }; \ + if (recheck) \ + print $$0; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# A command that, given a newline-separated list of test names on the +# standard input, create the global log from their .trs and .log files. +am__create_global_log = $(AWK) ' \ +function fatal(msg) \ +{ \ + print "fatal: making $@: " msg | "cat >&2"; \ + exit 1; \ +} \ +function rst_section(header) \ +{ \ + print header; \ + len = length(header); \ + for (i = 1; i <= len; i = i + 1) \ + printf "="; \ + printf "\n\n"; \ +} \ +{ \ + copy_in_global_log = 1; \ + global_test_result = "RUN"; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".trs"); \ + if (line ~ /$(am__global_test_result_rx)/) \ + { \ + sub("$(am__global_test_result_rx)", "", line); \ + sub("[ ]*$$", "", line); \ + global_test_result = line; \ + } \ + else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \ + copy_in_global_log = 0; \ + }; \ + if (copy_in_global_log) \ + { \ + rst_section(global_test_result ": " $$0); \ + while ((rc = (getline line < ($$0 ".log"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".log"); \ + print line; \ + }; \ + printf "\n"; \ + }; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# Restructured Text title. +am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; } +# Solaris 10 'make', and several other traditional 'make' implementations, +# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it +# by disabling -e (using the XSI extension "set +e") if it's set. +am__sh_e_setup = case $$- in *e*) set +e;; esac +# Default flags passed to test drivers. +am__common_driver_flags = \ + --color-tests "$$am__color_tests" \ + --enable-hard-errors "$$am__enable_hard_errors" \ + --expect-failure "$$am__expect_failure" +# To be inserted before the command running the test. Creates the +# directory for the log if needed. Stores in $dir the directory +# containing $f, in $tst the test, in $log the log. Executes the +# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and +# passes TESTS_ENVIRONMENT. Set up options for the wrapper that +# will run the test scripts (or their associated LOG_COMPILER, if +# thy have one). +am__check_pre = \ +$(am__sh_e_setup); \ +$(am__vpath_adj_setup) $(am__vpath_adj) \ +$(am__tty_colors); \ +srcdir=$(srcdir); export srcdir; \ +case "$@" in \ + */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \ + *) am__odir=.;; \ +esac; \ +test "x$$am__odir" = x"." || test -d "$$am__odir" \ + || $(MKDIR_P) "$$am__odir" || exit $$?; \ +if test -f "./$$f"; then dir=./; \ +elif test -f "$$f"; then dir=; \ +else dir="$(srcdir)/"; fi; \ +tst=$$dir$$f; log='$@'; \ +if test -n '$(DISABLE_HARD_ERRORS)'; then \ + am__enable_hard_errors=no; \ +else \ + am__enable_hard_errors=yes; \ +fi; \ +case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \ + am__expect_failure=yes;; \ + *) \ + am__expect_failure=no;; \ +esac; \ +$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT) +# A shell command to get the names of the tests scripts with any registered +# extension removed (i.e., equivalently, the names of the test logs, with +# the '.log' extension removed). The result is saved in the shell variable +# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly, +# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)", +# since that might cause problem with VPATH rewrites for suffix-less tests. +# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'. +am__set_TESTS_bases = \ + bases='$(TEST_LOGS)'; \ + bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ + bases=`echo $$bases` +RECHECK_LOGS = $(TEST_LOGS) +AM_RECURSIVE_TARGETS = check recheck +TEST_SUITE_LOG = test-suite.log +TEST_EXTENSIONS = @EXEEXT@ .test +LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver +LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS) +am__set_b = \ + case '$@' in \ + */*) \ + case '$*' in \ + */*) b='$*';; \ + *) b=`echo '$@' | sed 's/\.log$$//'`; \ + esac;; \ + *) \ + b='$*';; \ + esac +am__test_logs1 = $(TESTS:=.log) +am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log) +TEST_LOGS = $(am__test_logs2:.test.log=.log) +TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver +TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \ + $(TEST_LOG_FLAGS) +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/test-driver +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = subdir-objects +test_programs = \ + test_primitives \ + test_recheck \ + test_stat_cache \ + test_file \ + test_privacy \ + test_priority \ + test_remove_torrent \ + test_auto_unchoke \ + test_checking \ + test_fast_extension \ + test_http_connection \ + test_lsd \ + test_pe_crypto \ + test_pex \ + test_read_piece \ + test_receive_buffer \ + test_resume \ + test_read_resume \ + test_stack_allocator \ + test_ssl \ + test_storage \ + test_time_critical \ + test_torrent \ + test_tracker \ + test_transfer \ + test_create_torrent \ + enum_if \ + test_utp \ + test_session \ + test_web_seed \ + test_web_seed_ban \ + test_web_seed_chunked \ + test_web_seed_http \ + test_web_seed_http_pw \ + test_web_seed_redirect \ + test_web_seed_socks4 \ + test_web_seed_socks5 \ + test_web_seed_socks5_no_peers \ + test_web_seed_socks5_pw \ + test_url_seed \ + test_remap_files \ + test_enum_net \ + test_file_progress \ + test_linked_list \ + test_direct_dht \ + test_ffs \ + test_session_params \ + test_span \ + test_io \ + test_alloca + +@ENABLE_TESTS_TRUE@noinst_LTLIBRARIES = libtest.la +TEST_TORRENTS = \ + absolute_filename.torrent \ + backslash_path.torrent \ + bad_name.torrent \ + base.torrent \ + creation_date.torrent \ + duplicate_files.torrent \ + duplicate_web_seeds.torrent \ + empty_httpseed.torrent \ + empty_path.torrent \ + empty_path_multi.torrent \ + hidden_parent_path.torrent \ + httpseed.torrent \ + invalid_file_size.torrent \ + invalid_filename.torrent \ + invalid_filename2.torrent \ + invalid_info.torrent \ + invalid_merkle.torrent \ + invalid_name.torrent \ + invalid_name2.torrent \ + invalid_name3.torrent \ + invalid_path_list.torrent \ + invalid_piece_len.torrent \ + invalid_pieces.torrent \ + invalid_root_hash.torrent \ + invalid_root_hash2.torrent \ + invalid_symlink.torrent \ + large.torrent \ + long_name.torrent \ + many_pieces.torrent \ + missing_path_list.torrent \ + missing_piece_len.torrent \ + negative_file_size.torrent \ + negative_piece_len.torrent \ + negative_size.torrent \ + no_creation_date.torrent \ + no_files.torrent \ + no_name.torrent \ + pad_file.torrent \ + pad_file_no_path.torrent \ + parent_path.torrent \ + root_hash.torrent \ + sample.torrent \ + single_multi_file.torrent \ + slash_path.torrent \ + slash_path2.torrent \ + slash_path3.torrent \ + string.torrent \ + symlink1.torrent \ + symlink2.torrent \ + symlink_zero_size.torrent \ + unaligned_pieces.torrent \ + unordered.torrent \ + url_list.torrent \ + url_list2.torrent \ + url_list3.torrent \ + url_seed.torrent \ + url_seed_multi.torrent \ + url_seed_multi_space.torrent \ + url_seed_multi_space_nolist.torrent \ + url_seed_multi_single_file.torrent \ + whitespace_url.torrent \ + overlapping_symlinks.torrent + +MUTABLE_TEST_TORRENTS = \ + test1.torrent \ + test1_pad_files.torrent \ + test1_single.torrent \ + test1_single_padded.torrent \ + test2.torrent \ + test2_pad_files.torrent \ + test3.torrent \ + test3_pad_files.torrent + +EXTRA_DIST = Jamfile \ + $(addprefix test_torrents/,${TEST_TORRENTS}) \ + $(addprefix mutable_test_torrents/,${MUTABLE_TEST_TORRENTS}) \ + ssl/regenerate_test_certificate.sh \ + ssl/cert_request.pem \ + ssl/dhparams.pem \ + ssl/invalid_peer_certificate.pem \ + ssl/invalid_peer_private_key.pem \ + ssl/peer_certificate.pem \ + ssl/peer_private_key.pem \ + ssl/root_ca_cert.pem \ + ssl/root_ca_private.pem \ + ssl/server.pem \ + zeroes.gz \ + corrupt.gz \ + invalid1.gz \ + utf8_test.txt \ + web_server.py \ + root1.xml \ + root2.xml \ + root3.xml \ + socks.py \ + http_proxy.py \ + CMakeLists.txt + +noinst_HEADERS = test.hpp setup_transfer.hpp dht_server.hpp \ + peer_server.hpp udp_tracker.hpp web_seed_suite.hpp swarm_suite.hpp \ + test_utils.hpp settings.hpp make_torrent.hpp bittorrent_peer.hpp \ + print_alerts.hpp + +libtest_la_SOURCES = main.cpp \ + test.cpp \ + setup_transfer.cpp \ + dht_server.cpp \ + udp_tracker.cpp \ + peer_server.cpp \ + bittorrent_peer.cpp \ + make_torrent.cpp \ + web_seed_suite.cpp \ + swarm_suite.cpp \ + test_utils.cpp \ + settings.cpp \ + print_alerts.cpp + +test_primitives_SOURCES = \ + test_primitives.cpp \ + test_packet_buffer.cpp \ + test_timestamp_history.cpp \ + test_sha1_hash.cpp \ + test_bloom_filter.cpp \ + test_identify_client.cpp \ + test_merkle.cpp \ + test_alert_manager.cpp \ + test_alert_types.cpp \ + test_resolve_links.cpp \ + test_crc32.cpp \ + test_heterogeneous_queue.cpp \ + test_listen_socket.cpp \ + test_ip_voter.cpp \ + test_sliding_average.cpp \ + test_socket_io.cpp \ + test_utf8.cpp \ + test_gzip.cpp \ + test_bitfield.cpp \ + test_part_file.cpp \ + test_peer_list.cpp \ + test_torrent_info.cpp \ + test_time.cpp \ + test_file_storage.cpp \ + test_peer_priority.cpp \ + test_threads.cpp \ + test_tailqueue.cpp \ + test_bandwidth_limiter.cpp \ + test_buffer.cpp \ + test_piece_picker.cpp \ + test_bencoding.cpp \ + test_bdecode.cpp \ + test_http_parser.cpp \ + test_string.cpp \ + test_magnet.cpp \ + test_xml.cpp \ + test_ip_filter.cpp \ + test_hasher.cpp \ + test_hasher512.cpp \ + test_ed25519.cpp \ + test_dht_storage.cpp \ + test_dht.cpp \ + test_block_cache.cpp \ + test_peer_classes.cpp \ + test_settings_pack.cpp \ + test_fence.cpp \ + test_dos_blocker.cpp \ + test_upnp.cpp \ + test_flags.cpp \ + test_generate_peer_id.cpp + +test_recheck_SOURCES = test_recheck.cpp +test_stat_cache_SOURCES = test_stat_cache.cpp +test_file_SOURCES = test_file.cpp +test_privacy_SOURCES = test_privacy.cpp +test_priority_SOURCES = test_priority.cpp +test_remove_torrent_SOURCES = test_remove_torrent.cpp +test_auto_unchoke_SOURCES = test_auto_unchoke.cpp +test_checking_SOURCES = test_checking.cpp +test_enum_net_SOURCES = test_enum_net.cpp +test_fast_extension_SOURCES = test_fast_extension.cpp +test_http_connection_SOURCES = test_http_connection.cpp +test_lsd_SOURCES = test_lsd.cpp +test_pe_crypto_SOURCES = test_pe_crypto.cpp +test_pex_SOURCES = test_pex.cpp +test_read_piece_SOURCES = test_read_piece.cpp +test_receive_buffer_SOURCES = test_receive_buffer.cpp +test_storage_SOURCES = test_storage.cpp +test_time_critical_SOURCES = test_time_critical.cpp +test_resume_SOURCES = test_resume.cpp +test_read_resume_SOURCES = test_read_resume.cpp +test_stack_allocator_SOURCES = test_stack_allocator.cpp +test_ssl_SOURCES = test_ssl.cpp +test_torrent_SOURCES = test_torrent.cpp +test_tracker_SOURCES = test_tracker.cpp +test_transfer_SOURCES = test_transfer.cpp +test_create_torrent_SOURCES = test_create_torrent.cpp +enum_if_SOURCES = enum_if.cpp +test_utp_SOURCES = test_utp.cpp +test_session_SOURCES = test_session.cpp +test_web_seed_SOURCES = test_web_seed.cpp +test_web_seed_ban_SOURCES = test_web_seed_ban.cpp +test_web_seed_chunked_SOURCES = test_web_seed_chunked.cpp +test_web_seed_http_SOURCES = test_web_seed_http.cpp +test_web_seed_http_pw_SOURCES = test_web_seed_http_pw.cpp +test_web_seed_redirect_SOURCES = test_web_seed_redirect.cpp +test_web_seed_socks4_SOURCES = test_web_seed_socks4.cpp +test_web_seed_socks5_SOURCES = test_web_seed_socks5.cpp +test_web_seed_socks5_no_peers_SOURCES = test_web_seed_socks5_no_peers.cpp +test_web_seed_socks5_pw_SOURCES = test_web_seed_socks5_pw.cpp +test_url_seed_SOURCES = test_url_seed.cpp +test_remap_files_SOURCES = test_remap_files.cpp +test_file_progress_SOURCES = test_file_progress.cpp +test_linked_list_SOURCES = test_linked_list.cpp +test_direct_dht_SOURCES = test_direct_dht.cpp +test_ffs_SOURCES = test_ffs.cpp +test_session_params_SOURCES = test_session_params.cpp +test_span_SOURCES = test_span.cpp +test_io_SOURCES = test_io.cpp +test_alloca_SOURCES = test_alloca.cpp +LDADD = libtest.la $(top_builddir)/src/libtorrent-rasterbar.la + +#AM_CXXFLAGS=-ftemplate-depth-50 -I$(top_srcdir)/include -I$(top_srcdir)/include/libtorrent @DEBUGFLAGS@ @PTHREAD_CFLAGS@ +AM_CPPFLAGS = -ftemplate-depth-50 @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ @PTHREAD_LIBS@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include @OPENSSL_INCLUDES@ +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .log .o .obj .test .test$(EXEEXT) .trs +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign test/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign test/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-checkPROGRAMS: + @list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libtest.la: $(libtest_la_OBJECTS) $(libtest_la_DEPENDENCIES) $(EXTRA_libtest_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(am_libtest_la_rpath) $(libtest_la_OBJECTS) $(libtest_la_LIBADD) $(LIBS) + +enum_if$(EXEEXT): $(enum_if_OBJECTS) $(enum_if_DEPENDENCIES) $(EXTRA_enum_if_DEPENDENCIES) + @rm -f enum_if$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(enum_if_OBJECTS) $(enum_if_LDADD) $(LIBS) + +test_alloca$(EXEEXT): $(test_alloca_OBJECTS) $(test_alloca_DEPENDENCIES) $(EXTRA_test_alloca_DEPENDENCIES) + @rm -f test_alloca$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_alloca_OBJECTS) $(test_alloca_LDADD) $(LIBS) + +test_auto_unchoke$(EXEEXT): $(test_auto_unchoke_OBJECTS) $(test_auto_unchoke_DEPENDENCIES) $(EXTRA_test_auto_unchoke_DEPENDENCIES) + @rm -f test_auto_unchoke$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_auto_unchoke_OBJECTS) $(test_auto_unchoke_LDADD) $(LIBS) + +test_checking$(EXEEXT): $(test_checking_OBJECTS) $(test_checking_DEPENDENCIES) $(EXTRA_test_checking_DEPENDENCIES) + @rm -f test_checking$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_checking_OBJECTS) $(test_checking_LDADD) $(LIBS) + +test_create_torrent$(EXEEXT): $(test_create_torrent_OBJECTS) $(test_create_torrent_DEPENDENCIES) $(EXTRA_test_create_torrent_DEPENDENCIES) + @rm -f test_create_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_create_torrent_OBJECTS) $(test_create_torrent_LDADD) $(LIBS) + +test_direct_dht$(EXEEXT): $(test_direct_dht_OBJECTS) $(test_direct_dht_DEPENDENCIES) $(EXTRA_test_direct_dht_DEPENDENCIES) + @rm -f test_direct_dht$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_direct_dht_OBJECTS) $(test_direct_dht_LDADD) $(LIBS) + +test_enum_net$(EXEEXT): $(test_enum_net_OBJECTS) $(test_enum_net_DEPENDENCIES) $(EXTRA_test_enum_net_DEPENDENCIES) + @rm -f test_enum_net$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_enum_net_OBJECTS) $(test_enum_net_LDADD) $(LIBS) + +test_fast_extension$(EXEEXT): $(test_fast_extension_OBJECTS) $(test_fast_extension_DEPENDENCIES) $(EXTRA_test_fast_extension_DEPENDENCIES) + @rm -f test_fast_extension$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_fast_extension_OBJECTS) $(test_fast_extension_LDADD) $(LIBS) + +test_ffs$(EXEEXT): $(test_ffs_OBJECTS) $(test_ffs_DEPENDENCIES) $(EXTRA_test_ffs_DEPENDENCIES) + @rm -f test_ffs$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_ffs_OBJECTS) $(test_ffs_LDADD) $(LIBS) + +test_file$(EXEEXT): $(test_file_OBJECTS) $(test_file_DEPENDENCIES) $(EXTRA_test_file_DEPENDENCIES) + @rm -f test_file$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_file_OBJECTS) $(test_file_LDADD) $(LIBS) + +test_file_progress$(EXEEXT): $(test_file_progress_OBJECTS) $(test_file_progress_DEPENDENCIES) $(EXTRA_test_file_progress_DEPENDENCIES) + @rm -f test_file_progress$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_file_progress_OBJECTS) $(test_file_progress_LDADD) $(LIBS) + +test_http_connection$(EXEEXT): $(test_http_connection_OBJECTS) $(test_http_connection_DEPENDENCIES) $(EXTRA_test_http_connection_DEPENDENCIES) + @rm -f test_http_connection$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_http_connection_OBJECTS) $(test_http_connection_LDADD) $(LIBS) + +test_io$(EXEEXT): $(test_io_OBJECTS) $(test_io_DEPENDENCIES) $(EXTRA_test_io_DEPENDENCIES) + @rm -f test_io$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_io_OBJECTS) $(test_io_LDADD) $(LIBS) + +test_linked_list$(EXEEXT): $(test_linked_list_OBJECTS) $(test_linked_list_DEPENDENCIES) $(EXTRA_test_linked_list_DEPENDENCIES) + @rm -f test_linked_list$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_linked_list_OBJECTS) $(test_linked_list_LDADD) $(LIBS) + +test_lsd$(EXEEXT): $(test_lsd_OBJECTS) $(test_lsd_DEPENDENCIES) $(EXTRA_test_lsd_DEPENDENCIES) + @rm -f test_lsd$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_lsd_OBJECTS) $(test_lsd_LDADD) $(LIBS) + +test_pe_crypto$(EXEEXT): $(test_pe_crypto_OBJECTS) $(test_pe_crypto_DEPENDENCIES) $(EXTRA_test_pe_crypto_DEPENDENCIES) + @rm -f test_pe_crypto$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_pe_crypto_OBJECTS) $(test_pe_crypto_LDADD) $(LIBS) + +test_pex$(EXEEXT): $(test_pex_OBJECTS) $(test_pex_DEPENDENCIES) $(EXTRA_test_pex_DEPENDENCIES) + @rm -f test_pex$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_pex_OBJECTS) $(test_pex_LDADD) $(LIBS) + +test_primitives$(EXEEXT): $(test_primitives_OBJECTS) $(test_primitives_DEPENDENCIES) $(EXTRA_test_primitives_DEPENDENCIES) + @rm -f test_primitives$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_primitives_OBJECTS) $(test_primitives_LDADD) $(LIBS) + +test_priority$(EXEEXT): $(test_priority_OBJECTS) $(test_priority_DEPENDENCIES) $(EXTRA_test_priority_DEPENDENCIES) + @rm -f test_priority$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_priority_OBJECTS) $(test_priority_LDADD) $(LIBS) + +test_privacy$(EXEEXT): $(test_privacy_OBJECTS) $(test_privacy_DEPENDENCIES) $(EXTRA_test_privacy_DEPENDENCIES) + @rm -f test_privacy$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_privacy_OBJECTS) $(test_privacy_LDADD) $(LIBS) + +test_read_piece$(EXEEXT): $(test_read_piece_OBJECTS) $(test_read_piece_DEPENDENCIES) $(EXTRA_test_read_piece_DEPENDENCIES) + @rm -f test_read_piece$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_read_piece_OBJECTS) $(test_read_piece_LDADD) $(LIBS) + +test_read_resume$(EXEEXT): $(test_read_resume_OBJECTS) $(test_read_resume_DEPENDENCIES) $(EXTRA_test_read_resume_DEPENDENCIES) + @rm -f test_read_resume$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_read_resume_OBJECTS) $(test_read_resume_LDADD) $(LIBS) + +test_receive_buffer$(EXEEXT): $(test_receive_buffer_OBJECTS) $(test_receive_buffer_DEPENDENCIES) $(EXTRA_test_receive_buffer_DEPENDENCIES) + @rm -f test_receive_buffer$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_receive_buffer_OBJECTS) $(test_receive_buffer_LDADD) $(LIBS) + +test_recheck$(EXEEXT): $(test_recheck_OBJECTS) $(test_recheck_DEPENDENCIES) $(EXTRA_test_recheck_DEPENDENCIES) + @rm -f test_recheck$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_recheck_OBJECTS) $(test_recheck_LDADD) $(LIBS) + +test_remap_files$(EXEEXT): $(test_remap_files_OBJECTS) $(test_remap_files_DEPENDENCIES) $(EXTRA_test_remap_files_DEPENDENCIES) + @rm -f test_remap_files$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_remap_files_OBJECTS) $(test_remap_files_LDADD) $(LIBS) + +test_remove_torrent$(EXEEXT): $(test_remove_torrent_OBJECTS) $(test_remove_torrent_DEPENDENCIES) $(EXTRA_test_remove_torrent_DEPENDENCIES) + @rm -f test_remove_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_remove_torrent_OBJECTS) $(test_remove_torrent_LDADD) $(LIBS) + +test_resume$(EXEEXT): $(test_resume_OBJECTS) $(test_resume_DEPENDENCIES) $(EXTRA_test_resume_DEPENDENCIES) + @rm -f test_resume$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_resume_OBJECTS) $(test_resume_LDADD) $(LIBS) + +test_session$(EXEEXT): $(test_session_OBJECTS) $(test_session_DEPENDENCIES) $(EXTRA_test_session_DEPENDENCIES) + @rm -f test_session$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_session_OBJECTS) $(test_session_LDADD) $(LIBS) + +test_session_params$(EXEEXT): $(test_session_params_OBJECTS) $(test_session_params_DEPENDENCIES) $(EXTRA_test_session_params_DEPENDENCIES) + @rm -f test_session_params$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_session_params_OBJECTS) $(test_session_params_LDADD) $(LIBS) + +test_span$(EXEEXT): $(test_span_OBJECTS) $(test_span_DEPENDENCIES) $(EXTRA_test_span_DEPENDENCIES) + @rm -f test_span$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_span_OBJECTS) $(test_span_LDADD) $(LIBS) + +test_ssl$(EXEEXT): $(test_ssl_OBJECTS) $(test_ssl_DEPENDENCIES) $(EXTRA_test_ssl_DEPENDENCIES) + @rm -f test_ssl$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_ssl_OBJECTS) $(test_ssl_LDADD) $(LIBS) + +test_stack_allocator$(EXEEXT): $(test_stack_allocator_OBJECTS) $(test_stack_allocator_DEPENDENCIES) $(EXTRA_test_stack_allocator_DEPENDENCIES) + @rm -f test_stack_allocator$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_stack_allocator_OBJECTS) $(test_stack_allocator_LDADD) $(LIBS) + +test_stat_cache$(EXEEXT): $(test_stat_cache_OBJECTS) $(test_stat_cache_DEPENDENCIES) $(EXTRA_test_stat_cache_DEPENDENCIES) + @rm -f test_stat_cache$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_stat_cache_OBJECTS) $(test_stat_cache_LDADD) $(LIBS) + +test_storage$(EXEEXT): $(test_storage_OBJECTS) $(test_storage_DEPENDENCIES) $(EXTRA_test_storage_DEPENDENCIES) + @rm -f test_storage$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_storage_OBJECTS) $(test_storage_LDADD) $(LIBS) + +test_time_critical$(EXEEXT): $(test_time_critical_OBJECTS) $(test_time_critical_DEPENDENCIES) $(EXTRA_test_time_critical_DEPENDENCIES) + @rm -f test_time_critical$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_time_critical_OBJECTS) $(test_time_critical_LDADD) $(LIBS) + +test_torrent$(EXEEXT): $(test_torrent_OBJECTS) $(test_torrent_DEPENDENCIES) $(EXTRA_test_torrent_DEPENDENCIES) + @rm -f test_torrent$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_torrent_OBJECTS) $(test_torrent_LDADD) $(LIBS) + +test_tracker$(EXEEXT): $(test_tracker_OBJECTS) $(test_tracker_DEPENDENCIES) $(EXTRA_test_tracker_DEPENDENCIES) + @rm -f test_tracker$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_tracker_OBJECTS) $(test_tracker_LDADD) $(LIBS) + +test_transfer$(EXEEXT): $(test_transfer_OBJECTS) $(test_transfer_DEPENDENCIES) $(EXTRA_test_transfer_DEPENDENCIES) + @rm -f test_transfer$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_transfer_OBJECTS) $(test_transfer_LDADD) $(LIBS) + +test_url_seed$(EXEEXT): $(test_url_seed_OBJECTS) $(test_url_seed_DEPENDENCIES) $(EXTRA_test_url_seed_DEPENDENCIES) + @rm -f test_url_seed$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_url_seed_OBJECTS) $(test_url_seed_LDADD) $(LIBS) + +test_utp$(EXEEXT): $(test_utp_OBJECTS) $(test_utp_DEPENDENCIES) $(EXTRA_test_utp_DEPENDENCIES) + @rm -f test_utp$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_utp_OBJECTS) $(test_utp_LDADD) $(LIBS) + +test_web_seed$(EXEEXT): $(test_web_seed_OBJECTS) $(test_web_seed_DEPENDENCIES) $(EXTRA_test_web_seed_DEPENDENCIES) + @rm -f test_web_seed$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_OBJECTS) $(test_web_seed_LDADD) $(LIBS) + +test_web_seed_ban$(EXEEXT): $(test_web_seed_ban_OBJECTS) $(test_web_seed_ban_DEPENDENCIES) $(EXTRA_test_web_seed_ban_DEPENDENCIES) + @rm -f test_web_seed_ban$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_ban_OBJECTS) $(test_web_seed_ban_LDADD) $(LIBS) + +test_web_seed_chunked$(EXEEXT): $(test_web_seed_chunked_OBJECTS) $(test_web_seed_chunked_DEPENDENCIES) $(EXTRA_test_web_seed_chunked_DEPENDENCIES) + @rm -f test_web_seed_chunked$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_chunked_OBJECTS) $(test_web_seed_chunked_LDADD) $(LIBS) + +test_web_seed_http$(EXEEXT): $(test_web_seed_http_OBJECTS) $(test_web_seed_http_DEPENDENCIES) $(EXTRA_test_web_seed_http_DEPENDENCIES) + @rm -f test_web_seed_http$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_http_OBJECTS) $(test_web_seed_http_LDADD) $(LIBS) + +test_web_seed_http_pw$(EXEEXT): $(test_web_seed_http_pw_OBJECTS) $(test_web_seed_http_pw_DEPENDENCIES) $(EXTRA_test_web_seed_http_pw_DEPENDENCIES) + @rm -f test_web_seed_http_pw$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_http_pw_OBJECTS) $(test_web_seed_http_pw_LDADD) $(LIBS) + +test_web_seed_redirect$(EXEEXT): $(test_web_seed_redirect_OBJECTS) $(test_web_seed_redirect_DEPENDENCIES) $(EXTRA_test_web_seed_redirect_DEPENDENCIES) + @rm -f test_web_seed_redirect$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_redirect_OBJECTS) $(test_web_seed_redirect_LDADD) $(LIBS) + +test_web_seed_socks4$(EXEEXT): $(test_web_seed_socks4_OBJECTS) $(test_web_seed_socks4_DEPENDENCIES) $(EXTRA_test_web_seed_socks4_DEPENDENCIES) + @rm -f test_web_seed_socks4$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks4_OBJECTS) $(test_web_seed_socks4_LDADD) $(LIBS) + +test_web_seed_socks5$(EXEEXT): $(test_web_seed_socks5_OBJECTS) $(test_web_seed_socks5_DEPENDENCIES) $(EXTRA_test_web_seed_socks5_DEPENDENCIES) + @rm -f test_web_seed_socks5$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks5_OBJECTS) $(test_web_seed_socks5_LDADD) $(LIBS) + +test_web_seed_socks5_no_peers$(EXEEXT): $(test_web_seed_socks5_no_peers_OBJECTS) $(test_web_seed_socks5_no_peers_DEPENDENCIES) $(EXTRA_test_web_seed_socks5_no_peers_DEPENDENCIES) + @rm -f test_web_seed_socks5_no_peers$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks5_no_peers_OBJECTS) $(test_web_seed_socks5_no_peers_LDADD) $(LIBS) + +test_web_seed_socks5_pw$(EXEEXT): $(test_web_seed_socks5_pw_OBJECTS) $(test_web_seed_socks5_pw_DEPENDENCIES) $(EXTRA_test_web_seed_socks5_pw_DEPENDENCIES) + @rm -f test_web_seed_socks5_pw$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(test_web_seed_socks5_pw_OBJECTS) $(test_web_seed_socks5_pw_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bittorrent_peer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dht_server.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/enum_if.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/make_torrent.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/peer_server.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/print_alerts.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/settings.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/setup_transfer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/swarm_suite.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_alert_manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_alert_types.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_alloca.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_auto_unchoke.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bandwidth_limiter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bdecode.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bencoding.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bitfield.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_block_cache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_bloom_filter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_buffer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_checking.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_crc32.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_create_torrent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dht.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dht_storage.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_direct_dht.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_dos_blocker.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ed25519.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_enum_net.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_fast_extension.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_fence.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ffs.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_file.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_file_progress.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_file_storage.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_flags.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_generate_peer_id.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_gzip.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_hasher.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_hasher512.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_heterogeneous_queue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_http_connection.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_http_parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_identify_client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_io.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ip_filter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ip_voter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_linked_list.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_listen_socket.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_lsd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_magnet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_merkle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_packet_buffer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_part_file.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_pe_crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_peer_classes.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_peer_list.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_peer_priority.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_pex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_piece_picker.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_primitives.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_priority.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_privacy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_read_piece.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_read_resume.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_receive_buffer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_recheck.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_remap_files.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_remove_torrent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_resolve_links.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_resume.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_session.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_session_params.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_settings_pack.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sha1_hash.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_sliding_average.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_socket_io.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_span.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_ssl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stack_allocator.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_stat_cache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_storage.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_string.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tailqueue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_threads.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_time.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_time_critical.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_timestamp_history.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_torrent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_torrent_info.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_tracker.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_transfer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_upnp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_url_seed.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_utf8.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_utp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_ban.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_chunked.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_http_pw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_redirect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks4.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks5.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks5_no_peers.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_web_seed_socks5_pw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_xml.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/udp_tracker.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/web_seed_suite.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +# Recover from deleted '.trs' file; this should ensure that +# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create +# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells +# to avoid problems with "make -n". +.log.trs: + rm -f $< $@ + $(MAKE) $(AM_MAKEFLAGS) $< + +# Leading 'am--fnord' is there to ensure the list of targets does not +# expand to empty, as could happen e.g. with make check TESTS=''. +am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck) +am--force-recheck: + @: + +$(TEST_SUITE_LOG): $(TEST_LOGS) + @$(am__set_TESTS_bases); \ + am__f_ok () { test -f "$$1" && test -r "$$1"; }; \ + redo_bases=`for i in $$bases; do \ + am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \ + done`; \ + if test -n "$$redo_bases"; then \ + redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \ + redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \ + if $(am__make_dryrun); then :; else \ + rm -f $$redo_logs && rm -f $$redo_results || exit 1; \ + fi; \ + fi; \ + if test -n "$$am__remaking_logs"; then \ + echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \ + "recursion detected" >&2; \ + elif test -n "$$redo_logs"; then \ + am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \ + fi; \ + if $(am__make_dryrun); then :; else \ + st=0; \ + errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \ + for i in $$redo_bases; do \ + test -f $$i.trs && test -r $$i.trs \ + || { echo "$$errmsg $$i.trs" >&2; st=1; }; \ + test -f $$i.log && test -r $$i.log \ + || { echo "$$errmsg $$i.log" >&2; st=1; }; \ + done; \ + test $$st -eq 0 || exit 1; \ + fi + @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \ + ws='[ ]'; \ + results=`for b in $$bases; do echo $$b.trs; done`; \ + test -n "$$results" || results=/dev/null; \ + all=` grep "^$$ws*:test-result:" $$results | wc -l`; \ + pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \ + fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \ + skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \ + xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \ + xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \ + error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \ + if test `expr $$fail + $$xpass + $$error` -eq 0; then \ + success=true; \ + else \ + success=false; \ + fi; \ + br='==================='; br=$$br$$br$$br$$br; \ + result_count () \ + { \ + if test x"$$1" = x"--maybe-color"; then \ + maybe_colorize=yes; \ + elif test x"$$1" = x"--no-color"; then \ + maybe_colorize=no; \ + else \ + echo "$@: invalid 'result_count' usage" >&2; exit 4; \ + fi; \ + shift; \ + desc=$$1 count=$$2; \ + if test $$maybe_colorize = yes && test $$count -gt 0; then \ + color_start=$$3 color_end=$$std; \ + else \ + color_start= color_end=; \ + fi; \ + echo "$${color_start}# $$desc $$count$${color_end}"; \ + }; \ + create_testsuite_report () \ + { \ + result_count $$1 "TOTAL:" $$all "$$brg"; \ + result_count $$1 "PASS: " $$pass "$$grn"; \ + result_count $$1 "SKIP: " $$skip "$$blu"; \ + result_count $$1 "XFAIL:" $$xfail "$$lgn"; \ + result_count $$1 "FAIL: " $$fail "$$red"; \ + result_count $$1 "XPASS:" $$xpass "$$red"; \ + result_count $$1 "ERROR:" $$error "$$mgn"; \ + }; \ + { \ + echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \ + $(am__rst_title); \ + create_testsuite_report --no-color; \ + echo; \ + echo ".. contents:: :depth: 2"; \ + echo; \ + for b in $$bases; do echo $$b; done \ + | $(am__create_global_log); \ + } >$(TEST_SUITE_LOG).tmp || exit 1; \ + mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \ + if $$success; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ + fi; \ + echo "$${col}$$br$${std}"; \ + echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \ + echo "$${col}$$br$${std}"; \ + create_testsuite_report --maybe-color; \ + echo "$$col$$br$$std"; \ + if $$success; then :; else \ + echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \ + if test -n "$(PACKAGE_BUGREPORT)"; then \ + echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \ + fi; \ + echo "$$col$$br$$std"; \ + fi; \ + $$success || exit 1 + +check-TESTS: $(check_PROGRAMS) + @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list + @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + trs_list=`for i in $$bases; do echo $$i.trs; done`; \ + log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \ + exit $$?; +recheck: all $(check_PROGRAMS) + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + bases=`for i in $$bases; do echo $$i; done \ + | $(am__list_recheck_tests)` || exit 1; \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + log_list=`echo $$log_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \ + am__force_recheck=am--force-recheck \ + TEST_LOGS="$$log_list"; \ + exit $$? +test_primitives.log: test_primitives$(EXEEXT) + @p='test_primitives$(EXEEXT)'; \ + b='test_primitives'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_recheck.log: test_recheck$(EXEEXT) + @p='test_recheck$(EXEEXT)'; \ + b='test_recheck'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_stat_cache.log: test_stat_cache$(EXEEXT) + @p='test_stat_cache$(EXEEXT)'; \ + b='test_stat_cache'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_file.log: test_file$(EXEEXT) + @p='test_file$(EXEEXT)'; \ + b='test_file'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_privacy.log: test_privacy$(EXEEXT) + @p='test_privacy$(EXEEXT)'; \ + b='test_privacy'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_priority.log: test_priority$(EXEEXT) + @p='test_priority$(EXEEXT)'; \ + b='test_priority'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_remove_torrent.log: test_remove_torrent$(EXEEXT) + @p='test_remove_torrent$(EXEEXT)'; \ + b='test_remove_torrent'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_auto_unchoke.log: test_auto_unchoke$(EXEEXT) + @p='test_auto_unchoke$(EXEEXT)'; \ + b='test_auto_unchoke'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_checking.log: test_checking$(EXEEXT) + @p='test_checking$(EXEEXT)'; \ + b='test_checking'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_fast_extension.log: test_fast_extension$(EXEEXT) + @p='test_fast_extension$(EXEEXT)'; \ + b='test_fast_extension'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_http_connection.log: test_http_connection$(EXEEXT) + @p='test_http_connection$(EXEEXT)'; \ + b='test_http_connection'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_lsd.log: test_lsd$(EXEEXT) + @p='test_lsd$(EXEEXT)'; \ + b='test_lsd'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_pe_crypto.log: test_pe_crypto$(EXEEXT) + @p='test_pe_crypto$(EXEEXT)'; \ + b='test_pe_crypto'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_pex.log: test_pex$(EXEEXT) + @p='test_pex$(EXEEXT)'; \ + b='test_pex'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_read_piece.log: test_read_piece$(EXEEXT) + @p='test_read_piece$(EXEEXT)'; \ + b='test_read_piece'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_receive_buffer.log: test_receive_buffer$(EXEEXT) + @p='test_receive_buffer$(EXEEXT)'; \ + b='test_receive_buffer'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_resume.log: test_resume$(EXEEXT) + @p='test_resume$(EXEEXT)'; \ + b='test_resume'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_read_resume.log: test_read_resume$(EXEEXT) + @p='test_read_resume$(EXEEXT)'; \ + b='test_read_resume'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_stack_allocator.log: test_stack_allocator$(EXEEXT) + @p='test_stack_allocator$(EXEEXT)'; \ + b='test_stack_allocator'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_ssl.log: test_ssl$(EXEEXT) + @p='test_ssl$(EXEEXT)'; \ + b='test_ssl'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_storage.log: test_storage$(EXEEXT) + @p='test_storage$(EXEEXT)'; \ + b='test_storage'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_time_critical.log: test_time_critical$(EXEEXT) + @p='test_time_critical$(EXEEXT)'; \ + b='test_time_critical'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_torrent.log: test_torrent$(EXEEXT) + @p='test_torrent$(EXEEXT)'; \ + b='test_torrent'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_tracker.log: test_tracker$(EXEEXT) + @p='test_tracker$(EXEEXT)'; \ + b='test_tracker'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_transfer.log: test_transfer$(EXEEXT) + @p='test_transfer$(EXEEXT)'; \ + b='test_transfer'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_create_torrent.log: test_create_torrent$(EXEEXT) + @p='test_create_torrent$(EXEEXT)'; \ + b='test_create_torrent'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +enum_if.log: enum_if$(EXEEXT) + @p='enum_if$(EXEEXT)'; \ + b='enum_if'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_utp.log: test_utp$(EXEEXT) + @p='test_utp$(EXEEXT)'; \ + b='test_utp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_session.log: test_session$(EXEEXT) + @p='test_session$(EXEEXT)'; \ + b='test_session'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed.log: test_web_seed$(EXEEXT) + @p='test_web_seed$(EXEEXT)'; \ + b='test_web_seed'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_ban.log: test_web_seed_ban$(EXEEXT) + @p='test_web_seed_ban$(EXEEXT)'; \ + b='test_web_seed_ban'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_chunked.log: test_web_seed_chunked$(EXEEXT) + @p='test_web_seed_chunked$(EXEEXT)'; \ + b='test_web_seed_chunked'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_http.log: test_web_seed_http$(EXEEXT) + @p='test_web_seed_http$(EXEEXT)'; \ + b='test_web_seed_http'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_http_pw.log: test_web_seed_http_pw$(EXEEXT) + @p='test_web_seed_http_pw$(EXEEXT)'; \ + b='test_web_seed_http_pw'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_redirect.log: test_web_seed_redirect$(EXEEXT) + @p='test_web_seed_redirect$(EXEEXT)'; \ + b='test_web_seed_redirect'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks4.log: test_web_seed_socks4$(EXEEXT) + @p='test_web_seed_socks4$(EXEEXT)'; \ + b='test_web_seed_socks4'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks5.log: test_web_seed_socks5$(EXEEXT) + @p='test_web_seed_socks5$(EXEEXT)'; \ + b='test_web_seed_socks5'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks5_no_peers.log: test_web_seed_socks5_no_peers$(EXEEXT) + @p='test_web_seed_socks5_no_peers$(EXEEXT)'; \ + b='test_web_seed_socks5_no_peers'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_web_seed_socks5_pw.log: test_web_seed_socks5_pw$(EXEEXT) + @p='test_web_seed_socks5_pw$(EXEEXT)'; \ + b='test_web_seed_socks5_pw'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_url_seed.log: test_url_seed$(EXEEXT) + @p='test_url_seed$(EXEEXT)'; \ + b='test_url_seed'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_remap_files.log: test_remap_files$(EXEEXT) + @p='test_remap_files$(EXEEXT)'; \ + b='test_remap_files'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_enum_net.log: test_enum_net$(EXEEXT) + @p='test_enum_net$(EXEEXT)'; \ + b='test_enum_net'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_file_progress.log: test_file_progress$(EXEEXT) + @p='test_file_progress$(EXEEXT)'; \ + b='test_file_progress'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_linked_list.log: test_linked_list$(EXEEXT) + @p='test_linked_list$(EXEEXT)'; \ + b='test_linked_list'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_direct_dht.log: test_direct_dht$(EXEEXT) + @p='test_direct_dht$(EXEEXT)'; \ + b='test_direct_dht'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_ffs.log: test_ffs$(EXEEXT) + @p='test_ffs$(EXEEXT)'; \ + b='test_ffs'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_session_params.log: test_session_params$(EXEEXT) + @p='test_session_params$(EXEEXT)'; \ + b='test_session_params'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_span.log: test_span$(EXEEXT) + @p='test_span$(EXEEXT)'; \ + b='test_span'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_io.log: test_io$(EXEEXT) + @p='test_io$(EXEEXT)'; \ + b='test_io'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +test_alloca.log: test_alloca$(EXEEXT) + @p='test_alloca$(EXEEXT)'; \ + b='test_alloca'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +.test.log: + @p='$<'; \ + $(am__set_b); \ + $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +@am__EXEEXT_TRUE@.test$(EXEEXT).log: +@am__EXEEXT_TRUE@ @p='$<'; \ +@am__EXEEXT_TRUE@ $(am__set_b); \ +@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ +@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \ +@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ +@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT) + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS) + -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs) + -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-checkPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/bittorrent_peer.Plo + -rm -f ./$(DEPDIR)/dht_server.Plo + -rm -f ./$(DEPDIR)/enum_if.Po + -rm -f ./$(DEPDIR)/main.Plo + -rm -f ./$(DEPDIR)/make_torrent.Plo + -rm -f ./$(DEPDIR)/peer_server.Plo + -rm -f ./$(DEPDIR)/print_alerts.Plo + -rm -f ./$(DEPDIR)/settings.Plo + -rm -f ./$(DEPDIR)/setup_transfer.Plo + -rm -f ./$(DEPDIR)/swarm_suite.Plo + -rm -f ./$(DEPDIR)/test.Plo + -rm -f ./$(DEPDIR)/test_alert_manager.Po + -rm -f ./$(DEPDIR)/test_alert_types.Po + -rm -f ./$(DEPDIR)/test_alloca.Po + -rm -f ./$(DEPDIR)/test_auto_unchoke.Po + -rm -f ./$(DEPDIR)/test_bandwidth_limiter.Po + -rm -f ./$(DEPDIR)/test_bdecode.Po + -rm -f ./$(DEPDIR)/test_bencoding.Po + -rm -f ./$(DEPDIR)/test_bitfield.Po + -rm -f ./$(DEPDIR)/test_block_cache.Po + -rm -f ./$(DEPDIR)/test_bloom_filter.Po + -rm -f ./$(DEPDIR)/test_buffer.Po + -rm -f ./$(DEPDIR)/test_checking.Po + -rm -f ./$(DEPDIR)/test_crc32.Po + -rm -f ./$(DEPDIR)/test_create_torrent.Po + -rm -f ./$(DEPDIR)/test_dht.Po + -rm -f ./$(DEPDIR)/test_dht_storage.Po + -rm -f ./$(DEPDIR)/test_direct_dht.Po + -rm -f ./$(DEPDIR)/test_dos_blocker.Po + -rm -f ./$(DEPDIR)/test_ed25519.Po + -rm -f ./$(DEPDIR)/test_enum_net.Po + -rm -f ./$(DEPDIR)/test_fast_extension.Po + -rm -f ./$(DEPDIR)/test_fence.Po + -rm -f ./$(DEPDIR)/test_ffs.Po + -rm -f ./$(DEPDIR)/test_file.Po + -rm -f ./$(DEPDIR)/test_file_progress.Po + -rm -f ./$(DEPDIR)/test_file_storage.Po + -rm -f ./$(DEPDIR)/test_flags.Po + -rm -f ./$(DEPDIR)/test_generate_peer_id.Po + -rm -f ./$(DEPDIR)/test_gzip.Po + -rm -f ./$(DEPDIR)/test_hasher.Po + -rm -f ./$(DEPDIR)/test_hasher512.Po + -rm -f ./$(DEPDIR)/test_heterogeneous_queue.Po + -rm -f ./$(DEPDIR)/test_http_connection.Po + -rm -f ./$(DEPDIR)/test_http_parser.Po + -rm -f ./$(DEPDIR)/test_identify_client.Po + -rm -f ./$(DEPDIR)/test_io.Po + -rm -f ./$(DEPDIR)/test_ip_filter.Po + -rm -f ./$(DEPDIR)/test_ip_voter.Po + -rm -f ./$(DEPDIR)/test_linked_list.Po + -rm -f ./$(DEPDIR)/test_listen_socket.Po + -rm -f ./$(DEPDIR)/test_lsd.Po + -rm -f ./$(DEPDIR)/test_magnet.Po + -rm -f ./$(DEPDIR)/test_merkle.Po + -rm -f ./$(DEPDIR)/test_packet_buffer.Po + -rm -f ./$(DEPDIR)/test_part_file.Po + -rm -f ./$(DEPDIR)/test_pe_crypto.Po + -rm -f ./$(DEPDIR)/test_peer_classes.Po + -rm -f ./$(DEPDIR)/test_peer_list.Po + -rm -f ./$(DEPDIR)/test_peer_priority.Po + -rm -f ./$(DEPDIR)/test_pex.Po + -rm -f ./$(DEPDIR)/test_piece_picker.Po + -rm -f ./$(DEPDIR)/test_primitives.Po + -rm -f ./$(DEPDIR)/test_priority.Po + -rm -f ./$(DEPDIR)/test_privacy.Po + -rm -f ./$(DEPDIR)/test_read_piece.Po + -rm -f ./$(DEPDIR)/test_read_resume.Po + -rm -f ./$(DEPDIR)/test_receive_buffer.Po + -rm -f ./$(DEPDIR)/test_recheck.Po + -rm -f ./$(DEPDIR)/test_remap_files.Po + -rm -f ./$(DEPDIR)/test_remove_torrent.Po + -rm -f ./$(DEPDIR)/test_resolve_links.Po + -rm -f ./$(DEPDIR)/test_resume.Po + -rm -f ./$(DEPDIR)/test_session.Po + -rm -f ./$(DEPDIR)/test_session_params.Po + -rm -f ./$(DEPDIR)/test_settings_pack.Po + -rm -f ./$(DEPDIR)/test_sha1_hash.Po + -rm -f ./$(DEPDIR)/test_sliding_average.Po + -rm -f ./$(DEPDIR)/test_socket_io.Po + -rm -f ./$(DEPDIR)/test_span.Po + -rm -f ./$(DEPDIR)/test_ssl.Po + -rm -f ./$(DEPDIR)/test_stack_allocator.Po + -rm -f ./$(DEPDIR)/test_stat_cache.Po + -rm -f ./$(DEPDIR)/test_storage.Po + -rm -f ./$(DEPDIR)/test_string.Po + -rm -f ./$(DEPDIR)/test_tailqueue.Po + -rm -f ./$(DEPDIR)/test_threads.Po + -rm -f ./$(DEPDIR)/test_time.Po + -rm -f ./$(DEPDIR)/test_time_critical.Po + -rm -f ./$(DEPDIR)/test_timestamp_history.Po + -rm -f ./$(DEPDIR)/test_torrent.Po + -rm -f ./$(DEPDIR)/test_torrent_info.Po + -rm -f ./$(DEPDIR)/test_tracker.Po + -rm -f ./$(DEPDIR)/test_transfer.Po + -rm -f ./$(DEPDIR)/test_upnp.Po + -rm -f ./$(DEPDIR)/test_url_seed.Po + -rm -f ./$(DEPDIR)/test_utf8.Po + -rm -f ./$(DEPDIR)/test_utils.Plo + -rm -f ./$(DEPDIR)/test_utp.Po + -rm -f ./$(DEPDIR)/test_web_seed.Po + -rm -f ./$(DEPDIR)/test_web_seed_ban.Po + -rm -f ./$(DEPDIR)/test_web_seed_chunked.Po + -rm -f ./$(DEPDIR)/test_web_seed_http.Po + -rm -f ./$(DEPDIR)/test_web_seed_http_pw.Po + -rm -f ./$(DEPDIR)/test_web_seed_redirect.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks4.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks5.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks5_no_peers.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks5_pw.Po + -rm -f ./$(DEPDIR)/test_xml.Po + -rm -f ./$(DEPDIR)/udp_tracker.Plo + -rm -f ./$(DEPDIR)/web_seed_suite.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/bittorrent_peer.Plo + -rm -f ./$(DEPDIR)/dht_server.Plo + -rm -f ./$(DEPDIR)/enum_if.Po + -rm -f ./$(DEPDIR)/main.Plo + -rm -f ./$(DEPDIR)/make_torrent.Plo + -rm -f ./$(DEPDIR)/peer_server.Plo + -rm -f ./$(DEPDIR)/print_alerts.Plo + -rm -f ./$(DEPDIR)/settings.Plo + -rm -f ./$(DEPDIR)/setup_transfer.Plo + -rm -f ./$(DEPDIR)/swarm_suite.Plo + -rm -f ./$(DEPDIR)/test.Plo + -rm -f ./$(DEPDIR)/test_alert_manager.Po + -rm -f ./$(DEPDIR)/test_alert_types.Po + -rm -f ./$(DEPDIR)/test_alloca.Po + -rm -f ./$(DEPDIR)/test_auto_unchoke.Po + -rm -f ./$(DEPDIR)/test_bandwidth_limiter.Po + -rm -f ./$(DEPDIR)/test_bdecode.Po + -rm -f ./$(DEPDIR)/test_bencoding.Po + -rm -f ./$(DEPDIR)/test_bitfield.Po + -rm -f ./$(DEPDIR)/test_block_cache.Po + -rm -f ./$(DEPDIR)/test_bloom_filter.Po + -rm -f ./$(DEPDIR)/test_buffer.Po + -rm -f ./$(DEPDIR)/test_checking.Po + -rm -f ./$(DEPDIR)/test_crc32.Po + -rm -f ./$(DEPDIR)/test_create_torrent.Po + -rm -f ./$(DEPDIR)/test_dht.Po + -rm -f ./$(DEPDIR)/test_dht_storage.Po + -rm -f ./$(DEPDIR)/test_direct_dht.Po + -rm -f ./$(DEPDIR)/test_dos_blocker.Po + -rm -f ./$(DEPDIR)/test_ed25519.Po + -rm -f ./$(DEPDIR)/test_enum_net.Po + -rm -f ./$(DEPDIR)/test_fast_extension.Po + -rm -f ./$(DEPDIR)/test_fence.Po + -rm -f ./$(DEPDIR)/test_ffs.Po + -rm -f ./$(DEPDIR)/test_file.Po + -rm -f ./$(DEPDIR)/test_file_progress.Po + -rm -f ./$(DEPDIR)/test_file_storage.Po + -rm -f ./$(DEPDIR)/test_flags.Po + -rm -f ./$(DEPDIR)/test_generate_peer_id.Po + -rm -f ./$(DEPDIR)/test_gzip.Po + -rm -f ./$(DEPDIR)/test_hasher.Po + -rm -f ./$(DEPDIR)/test_hasher512.Po + -rm -f ./$(DEPDIR)/test_heterogeneous_queue.Po + -rm -f ./$(DEPDIR)/test_http_connection.Po + -rm -f ./$(DEPDIR)/test_http_parser.Po + -rm -f ./$(DEPDIR)/test_identify_client.Po + -rm -f ./$(DEPDIR)/test_io.Po + -rm -f ./$(DEPDIR)/test_ip_filter.Po + -rm -f ./$(DEPDIR)/test_ip_voter.Po + -rm -f ./$(DEPDIR)/test_linked_list.Po + -rm -f ./$(DEPDIR)/test_listen_socket.Po + -rm -f ./$(DEPDIR)/test_lsd.Po + -rm -f ./$(DEPDIR)/test_magnet.Po + -rm -f ./$(DEPDIR)/test_merkle.Po + -rm -f ./$(DEPDIR)/test_packet_buffer.Po + -rm -f ./$(DEPDIR)/test_part_file.Po + -rm -f ./$(DEPDIR)/test_pe_crypto.Po + -rm -f ./$(DEPDIR)/test_peer_classes.Po + -rm -f ./$(DEPDIR)/test_peer_list.Po + -rm -f ./$(DEPDIR)/test_peer_priority.Po + -rm -f ./$(DEPDIR)/test_pex.Po + -rm -f ./$(DEPDIR)/test_piece_picker.Po + -rm -f ./$(DEPDIR)/test_primitives.Po + -rm -f ./$(DEPDIR)/test_priority.Po + -rm -f ./$(DEPDIR)/test_privacy.Po + -rm -f ./$(DEPDIR)/test_read_piece.Po + -rm -f ./$(DEPDIR)/test_read_resume.Po + -rm -f ./$(DEPDIR)/test_receive_buffer.Po + -rm -f ./$(DEPDIR)/test_recheck.Po + -rm -f ./$(DEPDIR)/test_remap_files.Po + -rm -f ./$(DEPDIR)/test_remove_torrent.Po + -rm -f ./$(DEPDIR)/test_resolve_links.Po + -rm -f ./$(DEPDIR)/test_resume.Po + -rm -f ./$(DEPDIR)/test_session.Po + -rm -f ./$(DEPDIR)/test_session_params.Po + -rm -f ./$(DEPDIR)/test_settings_pack.Po + -rm -f ./$(DEPDIR)/test_sha1_hash.Po + -rm -f ./$(DEPDIR)/test_sliding_average.Po + -rm -f ./$(DEPDIR)/test_socket_io.Po + -rm -f ./$(DEPDIR)/test_span.Po + -rm -f ./$(DEPDIR)/test_ssl.Po + -rm -f ./$(DEPDIR)/test_stack_allocator.Po + -rm -f ./$(DEPDIR)/test_stat_cache.Po + -rm -f ./$(DEPDIR)/test_storage.Po + -rm -f ./$(DEPDIR)/test_string.Po + -rm -f ./$(DEPDIR)/test_tailqueue.Po + -rm -f ./$(DEPDIR)/test_threads.Po + -rm -f ./$(DEPDIR)/test_time.Po + -rm -f ./$(DEPDIR)/test_time_critical.Po + -rm -f ./$(DEPDIR)/test_timestamp_history.Po + -rm -f ./$(DEPDIR)/test_torrent.Po + -rm -f ./$(DEPDIR)/test_torrent_info.Po + -rm -f ./$(DEPDIR)/test_tracker.Po + -rm -f ./$(DEPDIR)/test_transfer.Po + -rm -f ./$(DEPDIR)/test_upnp.Po + -rm -f ./$(DEPDIR)/test_url_seed.Po + -rm -f ./$(DEPDIR)/test_utf8.Po + -rm -f ./$(DEPDIR)/test_utils.Plo + -rm -f ./$(DEPDIR)/test_utp.Po + -rm -f ./$(DEPDIR)/test_web_seed.Po + -rm -f ./$(DEPDIR)/test_web_seed_ban.Po + -rm -f ./$(DEPDIR)/test_web_seed_chunked.Po + -rm -f ./$(DEPDIR)/test_web_seed_http.Po + -rm -f ./$(DEPDIR)/test_web_seed_http_pw.Po + -rm -f ./$(DEPDIR)/test_web_seed_redirect.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks4.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks5.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks5_no_peers.Po + -rm -f ./$(DEPDIR)/test_web_seed_socks5_pw.Po + -rm -f ./$(DEPDIR)/test_xml.Po + -rm -f ./$(DEPDIR)/udp_tracker.Plo + -rm -f ./$(DEPDIR)/web_seed_suite.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-checkPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am recheck tags tags-am uninstall \ + uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/test/bittorrent_peer.cpp b/test/bittorrent_peer.cpp new file mode 100644 index 0000000..dd383ac --- /dev/null +++ b/test/bittorrent_peer.cpp @@ -0,0 +1,560 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/assert.hpp" +#include "bittorrent_peer.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/random.hpp" + +#include +#include +#include + +using namespace lt; +using namespace std::placeholders; + +peer_conn::peer_conn(io_service& ios + , std::function on_msg + , torrent_info const& ti + , tcp::endpoint const& ep + , peer_mode_t const mode) + : s(ios) + , m_mode(mode) + , m_ti(ti) + , m_on_msg(std::move(on_msg)) + , m_blocks_per_piece((m_ti.piece_length() + 0x3fff) / 0x4000) + , endpoint(ep) +{ + pieces.reserve(static_cast(m_ti.num_pieces())); + start_conn(); +} + +void peer_conn::start_conn() +{ + restarting = false; + s.async_connect(endpoint, std::bind(&peer_conn::on_connect, this, _1)); +} + +void peer_conn::on_connect(error_code const& ec) +{ + if (ec) + { + close("ERROR CONNECT: %s", ec); + return; + } + + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + char* h = static_cast(malloc(sizeof(handshake))); + memcpy(h, handshake, sizeof(handshake)); + std::memcpy(h + 28, m_ti.info_hash().data(), 20); + lt::aux::random_bytes({h + 48, 20}); + // for seeds, don't send the interested message + boost::asio::async_write(s, boost::asio::buffer(h, (sizeof(handshake) - 1) + - (m_mode == peer_mode_t::uploader ? 5 : 0)) + , std::bind(&peer_conn::on_handshake, this, h, _1, _2)); +} + +void peer_conn::on_handshake(char* h, error_code const& ec, size_t) +{ + free(h); + if (ec) + { + close("ERROR SEND HANDSHAKE: %s", ec); + return; + } + + // read handshake + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), 68) + , std::bind(&peer_conn::on_handshake2, this, _1, _2)); +} + +void peer_conn::on_handshake2(error_code const& ec, size_t) +{ + if (ec) + { + close("ERROR READ HANDSHAKE: %s", ec); + return; + } + + // buffer is the full 68 byte handshake + // look at the extension bits + + fast_extension = (buffer[27] & 4) != 0; + + if (m_mode == peer_mode_t::uploader) + { + write_have_all(); + } + else + { + work_download(); + } +} + +void peer_conn::write_have_all() +{ + using namespace lt::detail; + + if (fast_extension) + { + char* ptr = write_buf_proto.data(); + // have_all + write_uint32(1, ptr); + write_uint8(0xe, ptr); + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto.data() + , static_cast(ptr - write_buf_proto.data())) + , std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } + else + { + // bitfield + int len = (m_ti.num_pieces() + 7) / 8; + char* ptr = buffer.data(); + write_uint32(len + 1, ptr); + write_uint8(5, ptr); + std::fill(ptr, ptr + len, 255); + ptr += len; + // unchoke + write_uint32(1, ptr); + write_uint8(1, ptr); + boost::asio::async_write(s, boost::asio::buffer(buffer.data() + , static_cast(len + 10)) + , std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + } +} + +void peer_conn::on_have_all_sent(error_code const& ec, size_t) +{ + if (ec) + { + close("ERROR SEND HAVE ALL: %s", ec); + return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); +} + +bool peer_conn::write_request() +{ + using namespace lt::detail; + + // if we're choked (and there are no allowed-fast pieces left) + if (choked && allowed_fast.empty() && !m_current_piece_is_allowed) return false; + + // if there are no pieces left to request + if (pieces.empty() && suggested_pieces.empty() && current_piece == -1) return false; + + if (current_piece == -1) + { + // pick a new piece + if (choked && allowed_fast.size() > 0) + { + current_piece = allowed_fast.front(); + allowed_fast.erase(allowed_fast.begin()); + m_current_piece_is_allowed = true; + } + else if (suggested_pieces.size() > 0) + { + current_piece = suggested_pieces.front(); + suggested_pieces.erase(suggested_pieces.begin()); + m_current_piece_is_allowed = false; + } + else if (pieces.size() > 0) + { + current_piece = pieces.front(); + pieces.erase(pieces.begin()); + m_current_piece_is_allowed = false; + } + else + { + TORRENT_ASSERT_FAIL(); + } + } + char msg[] = "\0\0\0\xd\x06" + " " // piece + " " // offset + " "; // length + char* m = static_cast(malloc(sizeof(msg))); + std::copy(msg, msg + sizeof(msg), m); + char* ptr = m + 5; + write_uint32(current_piece, ptr); + write_uint32(block * 16 * 1024, ptr); + write_uint32(16 * 1024, ptr); + boost::asio::async_write(s, boost::asio::buffer(m, sizeof(msg) - 1) + , std::bind(&peer_conn::on_req_sent, this, m, _1, _2)); + + ++outstanding_requests; + ++block; + if (block == m_blocks_per_piece) + { + block = 0; + current_piece = -1; + m_current_piece_is_allowed = false; + } + return true; +} + +void peer_conn::on_req_sent(char* m, error_code const& ec, size_t) +{ + free(m); + if (ec) + { + close("ERROR SEND REQUEST: %s", ec); + return; + } + + work_download(); +} + +void peer_conn::close(char const* fmt, error_code const& ec) +{ + end_time = clock_type::now(); + char tmp[1024]; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + std::snprintf(tmp, sizeof(tmp), fmt, ec.message().c_str()); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + int time = int(total_milliseconds(end_time - start_time)); + if (time == 0) time = 1; + double const up = (std::int64_t(blocks_sent) * 0x4000) / time / 1000.0; + double const down = (std::int64_t(blocks_received) * 0x4000) / time / 1000.0; + error_code e; + + char ep_str[200]; + address const& addr = s.local_endpoint(e).address(); + if (addr.is_v6()) + std::snprintf(ep_str, sizeof(ep_str), "[%s]:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + else + std::snprintf(ep_str, sizeof(ep_str), "%s:%d", addr.to_string(e).c_str() + , s.local_endpoint(e).port()); + std::printf("%s ep: %s sent: %d received: %d duration: %d ms up: %.1fMB/s down: %.1fMB/s\n" + , tmp, ep_str, blocks_sent, blocks_received, time, up, down); +} + +void peer_conn::work_download() +{ + if (pieces.empty() + && suggested_pieces.empty() + && current_piece == -1 + && outstanding_requests == 0 + && blocks_received >= m_ti.num_pieces() * m_blocks_per_piece) + { + close("COMPLETED DOWNLOAD", error_code()); + return; + } + + // send requests + if (outstanding_requests < 40) + { + if (write_request()) return; + } + + // read message + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); +} + +void peer_conn::on_msg_length(error_code const& ec, size_t) +{ + using namespace lt::detail; + + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE PREFIX: %s", ec); + return; + } + char* ptr = buffer.data(); + unsigned int length = read_uint32(ptr); + if (length > buffer.size()) + { + std::printf("len: %u\n", length); + close("ERROR RECEIVE MESSAGE PREFIX: packet too big", error_code()); + return; + } + if (length == 0) + { + // keep-alive messate. read another length prefix + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + else + { + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), length) + , std::bind(&peer_conn::on_message, this, _1, _2)); + } +} + +void peer_conn::on_message(error_code const& ec, size_t bytes_transferred) +{ + using namespace lt::detail; + + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + + if (ec) + { + close("ERROR RECEIVE MESSAGE: %s", ec); + return; + } + char* ptr = buffer.data(); + int msg = read_uint8(ptr); + + m_on_msg(msg, ptr, int(bytes_transferred)); + + switch (m_mode) + { + case peer_mode_t::uploader: + if (msg == 6) + { + if (bytes_transferred != 13) + { + close("REQUEST packet has invalid size", error_code()); + return; + } + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + write_piece(piece, start, length); + } + else if (msg == 3) // not-interested + { + close("DONE", error_code()); + return; + } + else + { + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + } + break; + case peer_mode_t::downloader: + if (msg == 0xe) // have_all + { + // build a list of all pieces and request them all! + pieces.clear(); + for (int i = 0; i < int(pieces.size()); ++i) + pieces.push_back(i); + aux::random_shuffle(pieces); + } + else if (msg == 4) // have + { + int piece = detail::read_int32(ptr); + if (pieces.empty()) pieces.push_back(piece); + else pieces.insert(pieces.begin() + static_cast(lt::random(static_cast(pieces.size()))), piece); + } + else if (msg == 5) // bitfield + { + pieces.reserve(static_cast(m_ti.num_pieces())); + int piece = 0; + for (int i = 0; i < int(bytes_transferred); ++i) + { + int mask = 0x80; + for (int k = 0; k < 8; ++k) + { + if (piece > m_ti.num_pieces()) break; + if (*ptr & mask) pieces.push_back(piece); + mask >>= 1; + ++piece; + } + ++ptr; + } + aux::random_shuffle(pieces); + } + else if (msg == 7) // piece + { +/* + if (verify_downloads) + { + int piece = read_uint32(ptr); + int start = read_uint32(ptr); + int size = bytes_transferred - 9; + verify_piece(piece, start, ptr, size); + } +*/ + ++blocks_received; + --outstanding_requests; + int const piece = detail::read_int32(ptr); + int const start = detail::read_int32(ptr); + + if ((start + int(bytes_transferred)) / 0x4000 == m_blocks_per_piece) + { + write_have(piece); + return; + } + } + else if (msg == 13) // suggest + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + suggested_pieces.push_back(piece); + } + } + else if (msg == 16) // reject request + { + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + + // put it back! + if (current_piece != piece) + { + if (pieces.empty() || pieces.back() != piece) + pieces.push_back(piece); + } + else + { + block = std::min(start / 0x4000, block); + if (block == 0) + { + pieces.push_back(current_piece); + current_piece = -1; + m_current_piece_is_allowed = false; + } + } + --outstanding_requests; + std::printf("REJECT: [ piece: %d start: %d length: %d ]\n", piece, start, length); + } + else if (msg == 0) // choke + { + choked = true; + } + else if (msg == 1) // unchoke + { + choked = false; + } + else if (msg == 17) // allowed_fast + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + allowed_fast.push_back(piece); + } + } + work_download(); + break; + case peer_mode_t::idle: + // read another message + boost::asio::async_read(s, boost::asio::buffer(buffer.data(), 4) + , std::bind(&peer_conn::on_msg_length, this, _1, _2)); + break; + } +} +/* +bool peer_conn::verify_piece(int piece, int start, char const* ptr, int size) +{ + std::uint32_t* buf = (std::uint32_t*)ptr; + std::uint32_t fill = (piece << 8) | ((start / 0x4000) & 0xff); + for (int i = 0; i < size / 4; ++i) + { + if (buf[i] != fill) + { + std::printf("received invalid block. piece %d block %d\n", piece, start / 0x4000); + exit(1); + return false; + } + } + return true; +} +*/ +void peer_conn::write_piece(int piece, int start, int length) +{ + using namespace lt::detail; + +// generate_block(write_buffer, piece, start, length); + + char* ptr = write_buf_proto.data(); + write_uint32(9 + length, ptr); + TORRENT_ASSERT(length == 0x4000); + write_uint8(7, ptr); + write_uint32(piece, ptr); + write_uint32(start, ptr); + std::array vec; + vec[0] = boost::asio::buffer(write_buf_proto.data(), static_cast(ptr - write_buf_proto.data())); + vec[1] = boost::asio::buffer(write_buffer.data(), static_cast(length)); + boost::asio::async_write(s, vec, std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); + ++blocks_sent; +} + +void peer_conn::write_have(int piece) +{ + using namespace lt::detail; + + char* ptr = write_buf_proto.data(); + write_uint32(5, ptr); + write_uint8(4, ptr); + write_uint32(piece, ptr); + boost::asio::async_write(s, boost::asio::buffer(write_buf_proto.data(), 9) + , std::bind(&peer_conn::on_have_all_sent, this, _1, _2)); +} + +void peer_conn::abort() +{ + error_code ec; + s.close(ec); +} + diff --git a/test/bittorrent_peer.hpp b/test/bittorrent_peer.hpp new file mode 100644 index 0000000..7938195 --- /dev/null +++ b/test/bittorrent_peer.hpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef BITTORRENT_PEER_HPP +#define BITTORRENT_PEER_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/torrent_info.hpp" +#include "test.hpp" // for EXPORT +#include +#include + +struct EXPORT peer_conn +{ + enum class peer_mode_t + { uploader, downloader, idle }; + + peer_conn(lt::io_service& ios + , std::function on_msg + , lt::torrent_info const& ti + , lt::tcp::endpoint const& ep + , peer_mode_t mode); + + void start_conn(); + + void on_connect(lt::error_code const& ec); + void on_handshake(char* h, lt::error_code const& ec, size_t bytes_transferred); + void on_handshake2(lt::error_code const& ec, size_t bytes_transferred); + void write_have_all(); + void on_have_all_sent(lt::error_code const& ec, size_t bytes_transferred); + bool write_request(); + void on_req_sent(char* m, lt::error_code const& ec, size_t bytes_transferred); + void close(char const* fmt, lt::error_code const& ec); + void work_download(); + void on_msg_length(lt::error_code const& ec, size_t bytes_transferred); + void on_message(lt::error_code const& ec, size_t bytes_transferred); + bool verify_piece(int piece, int start, char const* ptr, int size); + void write_piece(int piece, int start, int length); + void write_have(int piece); + + void abort(); + +private: + + lt::tcp::socket s; + std::array write_buf_proto; + std::array write_buffer; + std::array buffer; + + peer_mode_t const m_mode; + lt::torrent_info const& m_ti; + + int read_pos = 0; + + std::function m_on_msg; + + std::vector pieces; + std::vector suggested_pieces; + std::vector allowed_fast; + bool choked = true; + int current_piece = -1; // the piece we're currently requesting blocks from + bool m_current_piece_is_allowed = false; + int block = 0; + int const m_blocks_per_piece; + int outstanding_requests = 0; + // if this is true, this connection is a seed + bool fast_extension = false; + int blocks_received = 0; + int blocks_sent = 0; + lt::time_point start_time = lt::clock_type::now(); + lt::time_point end_time; + lt::tcp::endpoint endpoint; + bool restarting = false; +}; + +#endif + diff --git a/test/corrupt.gz b/test/corrupt.gz new file mode 100644 index 0000000000000000000000000000000000000000..9ed0b14bf3eee158f1a53bd977a8a919f1fd741e GIT binary patch literal 296 zcmb2|=3wA>oRE|hS=zwBz>p9L2C+Zz1BQPbJRIDoXYxcc|4&L{_|M7!WU@k;{~7k| zQD9)0jtUqUAVx>h((FivUoeNkTm!W=GLoU;|9>clfU2aVBmtHbhi_ojJUl$WAaVf! DT@aBF literal 0 HcmV?d00001 diff --git a/test/dht_server.cpp b/test/dht_server.cpp new file mode 100644 index 0000000..83c5a3f --- /dev/null +++ b/test/dht_server.cpp @@ -0,0 +1,182 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/bdecode.hpp" +#include "dht_server.hpp" +#include "test_utils.hpp" + +#if TORRENT_USE_IOSTREAM +#include +#endif + +#include +#include +#include +#include + +using namespace lt; +using namespace std::placeholders; + +struct dht_server +{ + + lt::io_service m_ios; + std::atomic m_dht_requests; + udp::socket m_socket; + int m_port; + + std::shared_ptr m_thread; + + dht_server() + : m_dht_requests(0) + , m_socket(m_ios) + , m_port(0) + { + error_code ec; + m_socket.open(udp::v4(), ec); + if (ec) + { + std::printf("Error opening listen DHT socket: %s\n", ec.message().c_str()); + return; + } + + m_socket.bind(udp::endpoint(address_v4::any(), 0), ec); + if (ec) + { + std::printf("Error binding DHT socket to port 0: %s\n", ec.message().c_str()); + return; + } + m_port = m_socket.local_endpoint(ec).port(); + if (ec) + { + std::printf("Error getting local endpoint of DHT socket: %s\n", ec.message().c_str()); + return; + } + + std::printf("%s: DHT initialized on port %d\n", time_now_string(), m_port); + + m_thread = std::make_shared(&dht_server::thread_fun, this); + } + + ~dht_server() + { + m_socket.cancel(); + m_socket.close(); + if (m_thread) m_thread->join(); + } + + int port() const { return m_port; } + + int num_hits() const { return m_dht_requests; } + + static void incoming_packet(error_code const& ec, size_t bytes_transferred + , size_t *ret, error_code* error, bool* done) + { + *ret = bytes_transferred; + *error = ec; + *done = true; + } + + void thread_fun() + { + std::array buffer; + + for (;;) + { + error_code ec; + udp::endpoint from; + size_t bytes_transferred; + bool done = false; + m_socket.async_receive_from( + boost::asio::buffer(buffer.data(), buffer.size()), from, 0 + , std::bind(&incoming_packet, _1, _2, &bytes_transferred, &ec, &done)); + while (!done) + { + m_ios.poll_one(); + m_ios.reset(); + } + + if (ec == boost::asio::error::operation_aborted + || ec == boost::asio::error::bad_descriptor) return; + + if (ec) + { + std::printf("Error receiving on DHT socket: %s\n", ec.message().c_str()); + return; + } + + try + { + entry msg = bdecode(span(buffer).first(int(bytes_transferred))); + +#if TORRENT_USE_IOSTREAM + std::cout << msg << std::endl; +#endif + ++m_dht_requests; + } + catch (std::exception const& e) + { + std::printf("failed to decode DHT message: %s\n", e.what()); + } + } + } +}; + +namespace { +std::shared_ptr g_dht; +} + +int start_dht() +{ + g_dht.reset(new dht_server); + return g_dht->port(); +} + +// the number of DHT messages received +int num_dht_hits() +{ + if (g_dht) return g_dht->num_hits(); + return 0; +} + +void stop_dht() +{ + g_dht.reset(); +} + diff --git a/test/dht_server.hpp b/test/dht_server.hpp new file mode 100644 index 0000000..3c5484a --- /dev/null +++ b/test/dht_server.hpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" // for EXPORT + +// returns the port the DHT is running on +int EXPORT start_dht(); + +// the number of DHT messages received +int EXPORT num_dht_hits(); + +void EXPORT stop_dht(); + diff --git a/test/enum_if.cpp b/test/enum_if.cpp new file mode 100644 index 0000000..1d20568 --- /dev/null +++ b/test/enum_if.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +using namespace lt; + +namespace { + +std::string operator "" _s(char const* str, size_t len) { return std::string(str, len); } + +std::string print_flags(interface_flags const f) +{ + return + ((f & if_flags::up) ? "UP "_s : ""_s) + + ((f & if_flags::broadcast) ? "BROADCAST "_s : ""_s) + + ((f & if_flags::loopback) ? "LOOP "_s : ""_s) + + ((f & if_flags::pointopoint) ? "PPP "_s : ""_s) + + ((f & if_flags::running) ? "RUN "_s : ""_s) + + ((f & if_flags::noarp) ? "NOARP "_s : ""_s) + + ((f & if_flags::promisc) ? "PROMISC "_s : ""_s) + + ((f & if_flags::allmulti) ? "ALLMULTI "_s : ""_s) + + ((f & if_flags::master) ? "MASTER "_s : ""_s) + + ((f & if_flags::slave) ? "SLAVE "_s : ""_s) + + ((f & if_flags::multicast) ? "MULTICAST "_s : ""_s) + + ((f & if_flags::dynamic) ? "SYN "_s : ""_s) + + ((f & if_flags::lower_up) ? "LWR_UP "_s : ""_s) + + ((f & if_flags::dormant) ? "DORMANT "_s : ""_s) + ; +} + +char const* print_state(if_state const s) +{ + switch (s) + { + case if_state::up: return "up"; + case if_state::dormant: return "dormant"; + case if_state::lowerlayerdown: return "lowerlayerdown"; + case if_state::down: return "down"; + case if_state::notpresent: return "notpresent"; + case if_state::testing: return "testing"; + case if_state::unknown: return "unknown"; + }; + return "unknown"; +} + +} + +int main() +{ + io_service ios; + error_code ec; + + std::printf("=========== Routes ===========\n"); + auto const routes = enum_routes(ios, ec); + if (ec) + { + std::printf("enum_routes: %s\n", ec.message().c_str()); + return 1; + } + + std::printf("%-45s%-45s%-35s%-7s%-18s%s\n", "destination", "network", "gateway", "mtu", "source-hint", "interface"); + + for (auto const& r : routes) + { + std::printf("%-45s%-45s%-35s%-7d%-18s%s\n" + , r.destination.to_string(ec).c_str() + , r.netmask.to_string(ec).c_str() + , r.gateway.is_unspecified() ? "-" : r.gateway.to_string(ec).c_str() + , r.mtu + , r.source_hint.is_unspecified() ? "-" : r.source_hint.to_string(ec).c_str() + , r.name); + } + + std::printf("========= Interfaces =========\n"); + + auto const net = enum_net_interfaces(ios, ec); + if (ec) + { + std::printf("enum_ifs: %s\n", ec.message().c_str()); + return 1; + } + + std::printf("%-34s%-45s%-20s%-20s%-15s%-20s%s\n", "address", "netmask", "name", "gateway", "state", "flags", "description"); + + for (auto const& i : net) + { + boost::optional
    const gateway = get_gateway(i, routes); + std::printf("%-34s%-45s%-20s%-20s%-15s%-20s%s %s\n" + , i.interface_address.to_string(ec).c_str() + , i.netmask.to_string(ec).c_str() + , i.name + , gateway ? gateway->to_string(ec).c_str() : "-" + , print_state(i.state) + , print_flags(i.flags).c_str() + , i.friendly_name + , i.description); + } +} diff --git a/test/http_proxy.py b/test/http_proxy.py new file mode 100755 index 0000000..6e07695 --- /dev/null +++ b/test/http_proxy.py @@ -0,0 +1,550 @@ +#!/usr/bin/env python3 + +# The author disclaims copyright to this source code. Please see the +# accompanying UNLICENSE file. +"""A HTTP proxy module suitable for testing. + +See http://github.com/AllSeeingEyeTolledEweSew/http_proxy for more information +about this project. +""" + +import argparse +import base64 +import http.client +import http.server +import select +import socket +import socketserver +import traceback +import urllib.parse + + +class ChunkError(Exception): + """Raised while processing chunked encoding, for invalid chunk sizes.""" + + +class _HTTPError(Exception): + """Raised internally to simplify processing. + + Attributes: + code: The HTTP error code (4xx or 5xx) we should return. + message: The "reason"/"message" part we should return in the status + line. + explain: The full body of the error message, usually a traceback. + """ + + def __init__(self, code, message=None, explain=None): + super().__init__() + self.code = code + self.message = message + self.explain = explain + + +def read_to_end_of_chunks(file_like): + """Reads a chunked-encoded stream from a file-like object. + + This will read up to the end of the chunked encoding, including chunk + delimeters, trailers, and the terminal empty line. + + The stream will be returned as an iterator of bytes objects. The split + between bytes objects is arbitrary. + + Args: + file_like: A file-like object with read() and readline() methods. + + Yields: + bytes objects. + + Raises: + ChunkError: if an invalid chunk size is encountered. + """ + + def inner(): + while True: + size_line = file_like.readline() + yield size_line + try: + size = int(size_line, 16) + except ValueError: + raise ChunkError("Invalid chunk size: %r" % size_line) + if size < 0: + raise ChunkError("Invalid chunk size: %d" % size) + if size == 0: + # Allow trailers, if any + while True: + line = file_like.readline() + yield line + if line in (b"\r\n", b"", b"\n"): + return + # Chunk size + crlf + chunk = file_like.read(size + 2) + yield chunk + + # Interpret any empty read as a closed connection, and stop + for chunk in inner(): + if not chunk: + return + yield chunk + + +def read_to_limit(file_like, limit, buffer_size): + """Reads a file-like object up to a number of bytes. + + This will read up to the given number of bytes from the given file. The + stream will be returned as an iterator of bytes objects, having size up to + the given buffer_size. + + Args: + file_like: A file-like object with a read() method. + limit: The total number of bytes to read. + buffer_size: Read data chunks of this size. + + Yields: + bytes objects. + """ + offset = 0 + while offset < limit: + amount = min(limit - offset, buffer_size) + buf = file_like.read(amount) + if not buf: + return + yield buf + offset += len(buf) + + +def read_all(file_like, buffer_size): + """Reads a file-like object to its end. + + This will read an entire file. The stream will be returned as an iterator + of bytes objects, having size up to the given buffer_size. + + Args: + file_like: A file-like object with a read() method. + buffer_size: Read data chunks of this size. + + Yields: + bytes objects. + """ + while True: + buf = file_like.read(buffer_size) + if not buf: + return + yield buf + + +class Handler(http.server.BaseHTTPRequestHandler): + """An HTTP proxy Handler class, for use with http.server classes. + + Attributes: + timeout: Timeout value in seconds. Applies to upstream connections, + idle timeouts for CONNECT-method streams, and reading data from + both client and upstream. + basic_auth: If set, proxy will require basic authorization with this + credential. + """ + + # BaseHTTPRequestHandler tests this value + protocol_version = "HTTP/1.1" + + BUFLEN = 8192 + + # This is really here to keep pylint happy + close_connection = True + timeout = 30 + basic_auth = None + + def authorize(self): + """Returns whether the request is authorized.""" + if not self.basic_auth: + return True + + header = self.headers.get("Proxy-Authorization", "") + split = header.split(None, 1) + if len(split) != 2: + return False + scheme, credentials = split + if scheme.lower() != "basic": + return False + return credentials == self.basic_auth + + def do_auth(self): + """Fail the request if unauthorized. + + Should be called early from the do_* method handler method. + + Returns: + False if the request was unauthorized and we sent an error + response, True otherwise. + """ + if self.authorize(): + return True + + # send_error doesn't let us send headers, so do it by hand + self.log_error("code %d, message %s", 407, + "Proxy authorization required") + self.send_response(407, "Proxy authorization required") + self.send_header("Connection", "close") + self.send_header("Proxy-Authenticate", "Basic") + self.end_headers() + return False + + def connect_request(self): + """Connect to the upstream, for a CONNECT request. + + Should be called from the do_CONNECT handler method. + + Returns: + A socket connection to the upstream. + + Raises: + _HTTPError: If the CONNECT target was invalid, or there was an + error connecting to the upstream. + """ + split = self.path.split(":") + if len(split) != 2: + raise _HTTPError(400, explain="Target must be host:port") + host, port = split + + try: + return socket.create_connection((host, port), self.timeout) + except socket.timeout: + raise _HTTPError(504, explain=traceback.format_exc()) + except OSError: + raise _HTTPError(502, explain=traceback.format_exc()) + + def bidirectional_proxy(self, upstream): + """Forward data between the client and the given upstream. + + Should be called from the do_CONNECT method handler. + + Runs forever, until either upstream or client close their side of the + connection, or the idle timeout expires. + + Args: + upstream: A socket connection to the upstream. + """ + socks = (upstream, self.request) + while True: + (rlist, _, xlist) = select.select(socks, (), socks, self.timeout) + if xlist: + return + if not rlist: + return + for sock in rlist: + data = sock.recv(self.BUFLEN) + if not data: + return + if sock is upstream: + self.request.sendall(data) + else: + upstream.sendall(data) + + # pylint:disable=invalid-name + def do_CONNECT(self): + """Handler for the CONNECT method. + + Should be called from the superclass handler logic. + """ + upstream = None + try: + if not self.do_auth(): + return + + upstream = self.connect_request() + except _HTTPError as err: + self.send_error(err.code, message=err.message, explain=err.explain) + except Exception: + self.log_error("%s", traceback.format_exc()) + self.send_error(500, explain=traceback.format_exc()) + + if upstream is None: + return + + self.send_response(200) + self.send_header("Connection", "close") + self.end_headers() + + try: + self.bidirectional_proxy(upstream) + except Exception: + self.log_error("%s", traceback.format_exc()) + self.close_connection = True + + upstream.close() + + def proxy_request(self): + """Forward a normal HTTP request. + + Should be called from the do_* handlers for normal HTTP requests (not + CONNECT). + + Returns: + A tuple of (HTTPConnection, HTTPResponse). + + Raises: + _HTTPError: If the request does not conform to HTTP/1.1 + expectations. + """ + url = urllib.parse.urlsplit(self.path) + + if url.scheme != "http": + raise _HTTPError(400, message="Target scheme is not http") + + # We need to read only the expected amount from the client + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 + if self.headers.get("Transfer-Encoding", "identity") != "identity": + # BaseHTTPHandler never parses chunked encoding itself + message_body = read_to_end_of_chunks(self.rfile) + elif "Content-Length" in self.headers: + try: + length = int(self.headers["Content-Length"]) + except ValueError: + raise _HTTPError(411) + message_body = read_to_limit(self.rfile, length, self.BUFLEN) + elif self.command not in ("PATCH", "POST", "PUT"): + # Not expecting a body + message_body = None + else: + raise _HTTPError(411) + + try: + upstream = http.client.HTTPConnection(url.netloc, + timeout=self.timeout) + except http.client.InvalidURL as exc: + raise _HTTPError(400, + message=str(exc), + explain=traceback.format_exc()) + + path = urllib.parse.urlunsplit(("", "", url.path, url.query, "")) + upstream.putrequest(self.command, + path, + skip_host=True, + skip_accept_encoding=True) + + connection_tokens = [] + filter_headers = set( + ("proxy-authorization", "connection", "keep-alive")) + pass_headers = set(("transfer-encoding", "te", "trailer")) + if "Connection" in self.headers: + request_connection_tokens = [ + token.strip() for token in self.headers["Connection"].split(",") + ] + else: + request_connection_tokens = [] + for token in request_connection_tokens: + # Better parsing than base class, I think + if token.lower() == "keep-alive": + self.close_connection = False + filter_headers.add(token.lower()) + elif token.lower() == "close": + self.close_connection = True + elif token.lower() in pass_headers: + connection_tokens.append(token) + else: + filter_headers.add(token.lower()) + + for name, value in self.headers.items(): + if name.lower() in filter_headers: + continue + upstream.putheader(name, value) + + # No pipelineing to upstream + if "close" not in connection_tokens: + connection_tokens.append("close") + + upstream.putheader("Connection", ", ".join(connection_tokens)) + + try: + # Never use encode_chunked here, as we pass through + # transfer-encoding from the client. + # Calls socket.create_connection, so catch socket exceptions here. + upstream.endheaders(message_body=message_body) + # This parses the upstream response line and headers + return (upstream, upstream.getresponse()) + except socket.timeout: + raise _HTTPError(504, explain=traceback.format_exc()) + except (OSError, http.client.HTTPException): + upstream.close() + raise _HTTPError(502, explain=traceback.format_exc()) + except ChunkError as exc: + upstream.close() + raise _HTTPError(400, + message=str(exc), + explain=traceback.format_exc()) + + def proxy_response(self, response): + """Forwards an upstream response back to the client. + + Should be called from the do_* handlers for normal HTTP requests (not + CONNECT). + + Args: + response: An HTTPResponse from upstream. + """ + # send_response supplies some headers unconditionally + self.log_request(response.code) + self.send_response_only(response.code, response.reason) + + connection_tokens = [] + filter_headers = set( + ("proxy-authorization", "connection", "keep-alive")) + pass_headers = set(("transfer-encoding", "te", "trailer")) + if response.getheader("Connection"): + response_connection_tokens = [ + token.strip() + for token in response.getheader("Connection").split(",") + ] + else: + response_connection_tokens = [] + for token in response_connection_tokens: + if token.lower() == "close": + continue + if token.lower() in pass_headers: + connection_tokens.append(token) + else: + filter_headers.add(token.lower()) + # Close the connection if the client requested it + if self.close_connection: + connection_tokens.append("close") + for name, value in response.getheaders(): + if name.lower() in filter_headers: + continue + self.send_header(name, value) + if connection_tokens: + self.send_header("Connection", ", ".join(connection_tokens)) + + self.end_headers() + + # HTTPResponse.read() will decode chunks, but we want to pass them + # through. Use this "hack" to pass through the encoding, and just use + # our own reader. Field is undocumented, but public. + response.chunked = False + + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 + if response.getheader("Transfer-Encoding", "identity") != "identity": + body = read_to_end_of_chunks(response) + elif response.getheader("Content-Length"): + try: + length = int(response.getheader("Content-Length")) + except ValueError: + body = read_all(response, self.BUFLEN) + else: + body = read_to_limit(response, length, self.BUFLEN) + else: + # May hang if the server wrongly keeps the connection alive + body = read_all(response, self.BUFLEN) + + for chunk in body: + self.wfile.write(chunk) + + def do_proxy(self): + """Handles proxying any normal HTTP request (not CONNECT). + + This method is a generic implementation of the do_* handlers for normal + HTTP methods. + """ + upstream = None + response = None + try: + if not self.do_auth(): + return + + upstream, response = self.proxy_request() + except _HTTPError as exc: + self.send_error(exc.code, message=exc.message, explain=exc.explain) + except Exception: + self.log_error("%s", traceback.format_exc()) + self.send_error(500, explain=traceback.format_exc()) + + if not response: + return + + try: + self.proxy_response(response) + except Exception: + self.log_error("%s", traceback.format_exc()) + self.close_connection = True + + upstream.close() + + # pylint:disable=invalid-name + def do_GET(self): + """Handles a proxy GET request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_POST(self): + """Handles a proxy POST request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_PUT(self): + """Handles a proxy PUT request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_PATCH(self): + """Handles a proxy PATCH request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_HEAD(self): + """Handles a proxy HEAD request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_OPTIONS(self): + """Handles a proxy OPTIONS request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_DELETE(self): + """Handles a proxy DELETE request.""" + self.do_proxy() + + # pylint:disable=invalid-name + def do_TRACE(self): + """Handles a proxy TRACE request.""" + self.do_proxy() + + +class _ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): + + daemon_threads = True + + +class Main: + + def __init__(self): + self.parser = argparse.ArgumentParser("Simple HTTP proxy") + self.parser.add_argument("--port", type=int, default=8080) + self.parser.add_argument("--basic-auth") + self.parser.add_argument("--timeout", type=int, default=30) + self.parser.add_argument("--bind-host", default="localhost") + + self.args = None + self.server = None + self.address = None + + def run(self): + """Command-line entry point for http_proxy.""" + self.args = self.parser.parse_args() + + self.address = (self.args.bind_host, self.args.port) + + if self.args.basic_auth: + Handler.basic_auth = base64.b64encode( + self.args.basic_auth.encode()).decode() + else: + Handler.basic_auth = None + + self.server = _ThreadingHTTPServer(self.address, Handler) + self.server.serve_forever() + + def shutdown(self): + if self.server is not None: + self.server.shutdown() + + +if __name__ == "__main__": + Main().run() diff --git a/test/invalid1.gz b/test/invalid1.gz new file mode 100644 index 0000000000000000000000000000000000000000..731ebd1d563781fceea56f422938fbbc77aeb648 GIT binary patch literal 27 Ucmb2|<`7OT%1 +#include +#include +#include +#include // for exit() +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "setup_transfer.hpp" // for _g_test_failures +#include "test.hpp" +#include "dht_server.hpp" // for stop_dht +#include "peer_server.hpp" // for stop_peer +#include "udp_tracker.hpp" // for stop_udp_tracker +#include + +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include + +#ifdef _WIN32 +#include "libtorrent/aux_/windows.hpp" // fot SetErrorMode +#include // for _dup and _dup2 +#include // for _getpid +#include + +#define dup _dup +#define dup2 _dup2 + +#else + +#include // for getpid() + +#endif + +using namespace lt; + +namespace { + +// these are global so we can restore them on abnormal exits and print stuff +// out, such as the log +int old_stdout = -1; +int old_stderr = -1; +bool redirect_stdout = true; +// sanitizer output will go to stderr and we won't get an opportunity to print +// it, so don't redirect stderr by default +bool redirect_stderr = false; +bool keep_files = false; + +// the current tests file descriptor +unit_test_t* current_test = nullptr; + +void output_test_log_to_terminal() +{ + if (current_test == nullptr + || current_test->output == nullptr) + return; + + fflush(stdout); + fflush(stderr); + if (old_stdout != -1) + { + dup2(old_stdout, fileno(stdout)); + old_stdout = -1; + } + if (old_stderr != -1) + { + dup2(old_stderr, fileno(stderr)); + old_stderr = -1; + } + + fseek(current_test->output, 0, SEEK_SET); + std::printf("\x1b[1m[%s]\x1b[0m\n\n", current_test->name); + char buf[4096]; + std::size_t size = 0; + do { + size = fread(buf, 1, sizeof(buf), current_test->output); + if (size > 0) fwrite(buf, 1, size, stdout); + } while (size > 0); +} + +#ifdef _WIN32 +LONG WINAPI seh_exception_handler(LPEXCEPTION_POINTERS p) +{ + char stack_text[10000]; + +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + print_backtrace(stack_text, sizeof(stack_text), 30 + , p->ContextRecord); +#elif defined __FUNCTION__ + strcpy(stack_text, __FUNCTION__); +#else + strcpy(stack_text, ""); +#endif + + int const code = p->ExceptionRecord->ExceptionCode; + char const* name = ""; + switch (code) + { +#define EXC(x) case x: name = #x; break + EXC(EXCEPTION_ACCESS_VIOLATION); + EXC(EXCEPTION_ARRAY_BOUNDS_EXCEEDED); + EXC(EXCEPTION_BREAKPOINT); + EXC(EXCEPTION_DATATYPE_MISALIGNMENT); + EXC(EXCEPTION_FLT_DENORMAL_OPERAND); + EXC(EXCEPTION_FLT_DIVIDE_BY_ZERO); + EXC(EXCEPTION_FLT_INEXACT_RESULT); + EXC(EXCEPTION_FLT_INVALID_OPERATION); + EXC(EXCEPTION_FLT_OVERFLOW); + EXC(EXCEPTION_FLT_STACK_CHECK); + EXC(EXCEPTION_FLT_UNDERFLOW); + EXC(EXCEPTION_ILLEGAL_INSTRUCTION); + EXC(EXCEPTION_IN_PAGE_ERROR); + EXC(EXCEPTION_INT_DIVIDE_BY_ZERO); + EXC(EXCEPTION_INT_OVERFLOW); + EXC(EXCEPTION_INVALID_DISPOSITION); + EXC(EXCEPTION_NONCONTINUABLE_EXCEPTION); + EXC(EXCEPTION_PRIV_INSTRUCTION); + EXC(EXCEPTION_SINGLE_STEP); + EXC(EXCEPTION_STACK_OVERFLOW); +#undef EXC + }; + + std::printf("exception: (0x%x) %s caught:\n%s\n" + , code, name, stack_text); + + output_test_log_to_terminal(); + + exit(code); +} + +#endif + +[[noreturn]] void sig_handler(int sig) +{ + char stack_text[10000]; + +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + print_backtrace(stack_text, sizeof(stack_text), 30); +#elif defined __FUNCTION__ + strcpy(stack_text, __FUNCTION__); +#else + strcpy(stack_text, ""); +#endif + char const* name = ""; + switch (sig) + { +#define SIG(x) case x: name = #x; break + SIG(SIGSEGV); +#ifdef SIGBUS + SIG(SIGBUS); +#endif + SIG(SIGINT); + SIG(SIGTERM); + SIG(SIGILL); + SIG(SIGABRT); + SIG(SIGFPE); +#ifdef SIGSYS + SIG(SIGSYS); +#endif +#undef SIG + } + std::printf("signal: (%d) %s caught:\n%s\n", sig, name, stack_text); + + output_test_log_to_terminal(); + + std::exit(128 + sig); +} + +[[noreturn]] void term_handler() +{ + char stack_text[10000]; +#if TORRENT_USE_ASSERTS \ + || defined TORRENT_ASIO_DEBUGGING \ + || defined TORRENT_PROFILE_CALLS \ + || defined TORRENT_DEBUG_BUFFERS + print_backtrace(stack_text, sizeof(stack_text), 30); +#elif defined __FUNCTION__ + strcpy(stack_text, __FUNCTION__); +#else + strcpy(stack_text, ""); +#endif + std::printf("\n\nterminate called:\n%s\n\n\n", stack_text); + std::exit(-1); +} + +void print_usage(char const* executable) +{ + std::printf("%s [options] [tests...]\n" + "\n" + "OPTIONS:\n" + "-h,--help show this help\n" + "-l,--list list the tests available to run\n" + "-k,--keep keep files created by the test\n" + " regardless of whether it passed or not\n" + "-n,--no-redirect don't redirect test output to\n" + " temporary file, but let it go straight\n" + " to stdout\n" + "--stderr-redirect also redirect stderr in addition to stdout\n" + "\n" + "for tests, specify one or more test names as printed\n" + "by -l. If no test is specified, all tests are run\n", executable); +} + +void change_directory(std::string const& f, error_code& ec) +{ + ec.clear(); + + native_path_string const n = convert_to_native_path_string(f); + +#ifdef TORRENT_WINDOWS + if (SetCurrentDirectoryW(n.c_str()) == 0) + ec.assign(GetLastError(), system_category()); +#else + int ret = ::chdir(n.c_str()); + if (ret != 0) + ec.assign(errno, system_category()); +#endif +} + +} // anonymous namespace + +struct unit_directory_guard +{ + explicit unit_directory_guard(std::string d) : dir(std::move(d)) {} + unit_directory_guard(unit_directory_guard const&) = delete; + unit_directory_guard& operator=(unit_directory_guard const&) = delete; + ~unit_directory_guard() + { + if (keep_files) return; + error_code ec; + std::string const parent_dir = parent_path(dir); + // windows will not allow to remove current dir, so let's change it to root + change_directory(parent_dir, ec); + if (ec) + { + TEST_ERROR("Failed to change directory: " + ec.message()); + return; + } + remove_all(dir, ec); +#ifdef TORRENT_WINDOWS + if (ec.value() == ERROR_SHARING_VIOLATION) + { + // on windows, files are removed in the background, and we may need + // to wait a little bit + std::this_thread::sleep_for(milliseconds(400)); + remove_all(dir, ec); + } +#endif + if (ec) std::cerr << "Failed to remove unit test directory: " << ec.message() << "\n"; + } +private: + std::string dir; +}; + +void EXPORT reset_output() +{ + if (current_test == nullptr || current_test->output == nullptr) return; + fflush(stdout); + fflush(stderr); + rewind(current_test->output); +#ifdef TORRENT_WINDOWS + int const r = _chsize(fileno(current_test->output), 0); +#else + int const r = ftruncate(fileno(current_test->output), 0); +#endif + if (r != 0) + { + // this is best effort, it's not the end of the world if we fail + std::cerr << "ftruncate of temporary test output file failed: " << strerror(errno) << "\n"; + } +} + +int EXPORT main(int argc, char const* argv[]) +{ + char const* executable = argv[0]; + // skip executable name + ++argv; + --argc; + + // pick up options + while (argc > 0 && argv[0][0] == '-') + { + if (argv[0] == "-h"_sv || argv[0] == "--help"_sv) + { + print_usage(executable); + return 0; + } + + if (argv[0] == "-l"_sv || argv[0] == "--list"_sv) + { + std::printf("TESTS:\n"); + for (int i = 0; i < _g_num_unit_tests; ++i) + { + std::printf(" - %s\n", _g_unit_tests[i].name); + } + return 0; + } + + if (argv[0] == "-n"_sv || argv[0] == "--no-redirect"_sv) + { + redirect_stdout = false; + redirect_stderr = false; + } + + if (argv[0] == "--stderr-redirect"_sv) + { + redirect_stderr = true; + } + + if (argv[0] == "-k"_sv || argv[0] == "--keep"_sv) + { + keep_files = true; + } + ++argv; + --argc; + } + + std::set tests_to_run; + bool filter = false; + + for (int i = 0; i < argc; ++i) + { + tests_to_run.insert(argv[i]); + filter = true; + } + +#ifdef O_NONBLOCK + // on darwin, stdout is set to non-blocking mode by default + // which sometimes causes tests to fail with EAGAIN just + // by printing logs + int flags = fcntl(fileno(stdout), F_GETFL, 0); + fcntl(fileno(stdout), F_SETFL, flags & ~O_NONBLOCK); + flags = fcntl(fileno(stderr), F_GETFL, 0); + fcntl(fileno(stderr), F_SETFL, flags & ~O_NONBLOCK); +#endif + +#ifdef _WIN32 + // try to suppress hanging the process by windows displaying + // modal dialogs. + SetErrorMode(SEM_NOALIGNMENTFAULTEXCEPT + | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + + SetUnhandledExceptionFilter(&seh_exception_handler); + +#ifdef _DEBUG + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); +#endif + +#endif + + std::set_terminate(term_handler); + + signal(SIGSEGV, &sig_handler); +#ifdef SIGBUS + signal(SIGBUS, &sig_handler); +#endif + signal(SIGILL, &sig_handler); + signal(SIGINT, &sig_handler); + signal(SIGABRT, &sig_handler); + signal(SIGFPE, &sig_handler); +#ifdef SIGSYS + signal(SIGSYS, &sig_handler); +#endif + + int process_id = -1; +#ifdef _WIN32 + process_id = _getpid(); +#else + process_id = getpid(); +#endif + std::string const root_dir = current_working_directory(); + std::string const unit_dir_prefix = combine_path(root_dir, "test_tmp_" + std::to_string(process_id) + "_"); + std::printf("test: %s\ncwd_prefix = \"%s\"\nrnd = %x\n" + , executable, unit_dir_prefix.c_str(), lt::random(0xffffffff)); + + if (_g_num_unit_tests == 0) + { + std::printf("\x1b[31mTEST_ERROR: no unit tests registered\x1b[0m\n"); + return 1; + } + + if (redirect_stdout) old_stdout = dup(fileno(stdout)); + if (redirect_stderr) old_stderr = dup(fileno(stderr)); + + int num_run = 0; + for (int i = 0; i < _g_num_unit_tests; ++i) + { + if (filter && tests_to_run.count(_g_unit_tests[i].name) == 0) + continue; + + std::string const unit_dir = unit_dir_prefix + std::to_string(i); + error_code ec; + create_directory(unit_dir, ec); + if (ec) + { + std::printf("Failed to create unit test directory: %s\n", ec.message().c_str()); + output_test_log_to_terminal(); + return 1; + } + unit_directory_guard unit_dir_guard{unit_dir}; + change_directory(unit_dir, ec); + if (ec) + { + std::printf("Failed to change unit test directory: %s\n", ec.message().c_str()); + output_test_log_to_terminal(); + return 1; + } + + std::printf("cwd: %s\n", unit_dir.c_str()); + unit_test_t& t = _g_unit_tests[i]; + + if (redirect_stdout || redirect_stderr) + { + // redirect test output to a temporary file + fflush(stdout); + fflush(stderr); + +#ifdef TORRENT_MINGW + // mingw has a buggy tmpfile() and tmpname() that needs a . prepended + // to it (or some other directory) + char temp_name[512]; + FILE* f = nullptr; + if (tmpnam_s(temp_name + 1, sizeof(temp_name) - 1) == 0) + { + temp_name[0] = '.'; + std::printf("using temporary filename %s\n", temp_name); + f = fopen(temp_name, "wb+"); + } + else + { + std::printf("failed to generate filename for redirecting " + "output: (%d) %s\n", errno, strerror(errno)); + } +#else + FILE* f = tmpfile(); +#endif + if (f != nullptr) + { + int ret1 = 0; + if (redirect_stdout) ret1 |= dup2(fileno(f), fileno(stdout)); + if (redirect_stderr) ret1 |= dup2(fileno(f), fileno(stderr)); + if (ret1 >= 0) + { + t.output = f; + } + else + { + std::printf("failed to redirect output: (%d) %s\n" + , errno, strerror(errno)); + } + } + else + { + std::printf("failed to create temporary file for redirecting " + "output: (%d) %s\n", errno, strerror(errno)); + } + } + + // get proper interleaving of stderr and stdout + setbuf(stdout, nullptr); + setbuf(stderr, nullptr); + + _g_test_idx = i; + current_test = &t; + +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + +#if defined TORRENT_BUILD_SIMULATOR + lt::aux::random_engine().seed(0x82daf973); +#endif + + _g_test_failures = 0; + (*t.fun)(); +#ifndef BOOST_NO_EXCEPTIONS + } + catch (boost::system::system_error const& e) + { + char buf[200]; + std::snprintf(buf, sizeof(buf), "TEST_ERROR: Terminated with system_error: (%d) [%s] \"%s\"" + , e.code().value() + , e.code().category().name() + , e.code().message().c_str()); + report_failure(buf, __FILE__, __LINE__); + } + catch (std::exception const& e) + { + char buf[200]; + std::snprintf(buf, sizeof(buf), "TEST_ERROR: Terminated with exception: \"%s\"", e.what()); + report_failure(buf, __FILE__, __LINE__); + } + catch (...) + { + report_failure("TEST_ERROR: Terminated with unknown exception", __FILE__, __LINE__); + } +#endif + + if (!tests_to_run.empty()) tests_to_run.erase(t.name); + + if (_g_test_failures > 0) + { + output_test_log_to_terminal(); + } + + t.num_failures = _g_test_failures; + t.run = true; + ++num_run; + + if (redirect_stdout && t.output) + fclose(t.output); + } + + if (redirect_stdout && old_stdout != -1) dup2(old_stdout, fileno(stdout)); + if (redirect_stderr && old_stderr != -1) dup2(old_stderr, fileno(stderr)); + + if (!tests_to_run.empty()) + { + std::printf("\x1b[1mUNKONWN tests:\x1b[0m\n"); + for (std::set::iterator i = tests_to_run.begin() + , end(tests_to_run.end()); i != end; ++i) + { + std::printf(" %s\n", i->c_str()); + } + } + + if (num_run == 0) + { + std::printf("\x1b[31mTEST_ERROR: no unit tests run\x1b[0m\n"); + output_test_log_to_terminal(); + return 1; + } + + // just in case of premature exits + // make sure we try to clean up some + stop_udp_tracker(); + stop_all_proxies(); + stop_web_server(); + stop_peer(); + stop_dht(); + + if (redirect_stdout) fflush(stdout); + if (redirect_stderr) fflush(stderr); + + return print_failures() ? 333 : 0; +} + diff --git a/test/make_torrent.cpp b/test/make_torrent.cpp new file mode 100644 index 0000000..6af5cc9 --- /dev/null +++ b/test/make_torrent.cpp @@ -0,0 +1,208 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "make_torrent.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/storage_defs.hpp" + +using namespace lt; + +std::shared_ptr make_test_torrent(torrent_args const& args) +{ + entry e; + + entry::dictionary_type& info = e["info"].dict(); + int total_size = 0; + + if (args.m_priv) + { + info["priv"] = 1; + } + + // torrent offset ranges where the pad files are + // used when generating hashes + std::deque> pad_files; + + int const piece_length = 32768; + info["piece length"] = piece_length; + + if (args.m_files.size() == 1) + { + std::string const& ent = args.m_files[0]; + std::string name = "test_file-1"; + if (ent.find("name=") != std::string::npos) + { + std::string::size_type pos = ent.find("name=") + 5; + name = ent.substr(pos, ent.find(',', pos)); + } + info["name"] = name; + int file_size = atoi(args.m_files[0].c_str()); + info["length"] = file_size; + total_size = file_size; + } + else + { + info["name"] = args.m_name; + + entry::list_type& files = info["files"].list(); + for (int i = 0; i < int(args.m_files.size()); ++i) + { + auto const idx = static_cast(i); + int file_size = atoi(args.m_files[idx].c_str()); + + files.push_back(entry()); + entry::dictionary_type& file_entry = files.back().dict(); + std::string const& ent = args.m_files[idx]; + if (ent.find("padfile") != std::string::npos) + { + file_entry["attr"].string() += "p"; + pad_files.push_back(std::make_pair(total_size, total_size + file_size)); + } + if (ent.find("executable") != std::string::npos) + file_entry["attr"].string() += "x"; + + char filename[100]; + std::snprintf(filename, sizeof(filename), "test_file-%d", i); + + std::string name = filename; + if (ent.find("name=") != std::string::npos) + { + std::string::size_type pos = ent.find("name=") + 5; + name = ent.substr(pos, ent.find(',', pos)); + } + file_entry["path"].list().push_back(name); + file_entry["length"] = file_size; + total_size += file_size; + } + } + + if (!args.m_url_seed.empty()) + { + e["url-list"] = args.m_url_seed; + } + + if (!args.m_http_seed.empty()) + { + e["httpseeds"] = args.m_http_seed; + } + + std::string piece_hashes; + + int num_pieces = (total_size + piece_length - 1) / piece_length; + int torrent_offset = 0; + for (int i = 0; i < num_pieces; ++i) + { + hasher h; + int const piece_size = (i < num_pieces - 1) + ? piece_length + : total_size - (num_pieces - 1) * piece_length; + + char const data = char(i & 0xff); + char const zero = 0; + for (int o = 0; o < piece_size; ++o, ++torrent_offset) + { + while (!pad_files.empty() && torrent_offset >= pad_files.front().second) + pad_files.pop_front(); + + if (!pad_files.empty() && torrent_offset >= pad_files.front().first) + { + h.update(zero); + } + else + { + h.update(data); + } + } + piece_hashes += h.final().to_string(); + } + + info["pieces"] = piece_hashes; + + std::vector tmp; + std::back_insert_iterator> out(tmp); + bencode(out, e); + + FILE* f = fopen("test.torrent", "w+"); + fwrite(&tmp[0], 1, tmp.size(), f); + fclose(f); + + return std::make_shared(tmp, from_span); +} + +void generate_files(lt::torrent_info const& ti, std::string const& path + , bool alternate_data) +{ + file_pool fp; + + aux::vector priorities; + sha1_hash info_hash; + storage_params params{ + ti.files(), + nullptr, + path, + storage_mode_t::storage_mode_sparse, + priorities, + info_hash + }; + + default_storage st(params, fp); + + file_storage const& fs = ti.files(); + std::vector buffer; + for (auto const i : fs.piece_range()) + { + int const piece_size = ti.piece_size(i); + buffer.resize(static_cast(ti.piece_length())); + + char const data = static_cast((alternate_data + ? 255 - static_cast(i) : static_cast(i)) & 0xff); + for (int o = 0; o < piece_size; ++o) + { + buffer[static_cast(o)] = data; + } + + iovec_t b = { &buffer[0], piece_size }; + storage_error ec; + int ret = st.writev(b, i, 0, open_mode::read_only, ec); + if (ret != piece_size || ec) + { + std::printf("ERROR writing files: (%d expected %d) %s\n" + , ret, piece_size, ec.ec.message().c_str()); + } + } +} diff --git a/test/make_torrent.hpp b/test/make_torrent.hpp new file mode 100644 index 0000000..85111ab --- /dev/null +++ b/test/make_torrent.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef MAKE_TORRENT_HPP +#define MAKE_TORRENT_HPP + +#include "libtorrent/torrent_info.hpp" +#include +#include +#include "test.hpp" + +enum flags_t +{ + private_torrent = 1 +}; + +struct torrent_args +{ + torrent_args() : m_priv(false) {} + torrent_args& name(char const* n) { m_name = n; return *this; } + torrent_args& file(char const* f) { m_files.push_back(f); return *this; } + torrent_args& url_seed(char const* u) { m_url_seed = u; return *this; } + torrent_args& http_seed(char const* u) { m_http_seed = u; return *this; } + torrent_args& priv() { m_priv = true; return *this; } + + bool m_priv; + std::string m_name; + std::vector m_files; + std::string m_url_seed; + std::string m_http_seed; +}; + +EXPORT std::shared_ptr + make_test_torrent(torrent_args const& args); + +EXPORT void generate_files(lt::torrent_info const& ti, std::string const& path, bool random = false); + +#endif diff --git a/test/mutable_test_torrents/test1.torrent b/test/mutable_test_torrents/test1.torrent new file mode 100644 index 0000000..d73236c --- /dev/null +++ b/test/mutable_test_torrents/test1.torrent @@ -0,0 +1,3 @@ +d10:created by10:libtorrent13:creation datei1419452992e4:infod5:filesld6:lengthi51200e4:pathl1:aeed6:lengthi18e4:pathl1:beed6:lengthi19e4:pathl1:ceed6:lengthi53248e4:pathl1:deee4:name5:test112:piece lengthi16384e6:pieces140:ÈÄÔ}„Ç_ML¤)دym$„¶ +ޟÏk}@ûˆÑ¹bƏ®¸ßòüÝ ‡=åæâvw‰¯Q\¯\tÿr§Ÿsx ^ñzò57¸F¬ëW`žnøryܱt§VÈÛÀÔBôo­HQÝk¦¥•ÄЂÜ÷—½UçôÀ6¤Q +ñkª•kØà t¢ee \ No newline at end of file diff --git a/test/mutable_test_torrents/test1_pad_files.torrent b/test/mutable_test_torrents/test1_pad_files.torrent new file mode 100644 index 0000000..b0c6088 --- /dev/null +++ b/test/mutable_test_torrents/test1_pad_files.torrent @@ -0,0 +1,2 @@ +d10:created by10:libtorrent13:creation datei1419490165e4:infod5:filesld6:lengthi53248e4:pathl1:deed4:attr1:p6:lengthi12288e4:pathl17:.____padding_file1:0eed6:lengthi51200e4:pathl1:aeed4:attr1:p6:lengthi14336e4:pathl17:.____padding_file1:1eed6:lengthi19e4:pathl1:ceed6:lengthi18e4:pathl1:beee4:name5:test112:piece lengthi16384e6:pieces180:1QìêÄô«5ðOÓۃ»åÌ@…´îÍÜMsêt0üz€«F‹؉9™V{0Š“]y¥ÅrÞöv¤c>F†Ú×O½~zmÀNÈÄÔ}„Ç_ML¤)دym$„¶ +ޟÏk}@ûˆÑ¹bƏ®¸ßòüÝ ‡=åæâvw‰¯Q“ âF?ó{¶ô+,/ß¼Gæ:¥k†të´Û‰Û±geee \ No newline at end of file diff --git a/test/mutable_test_torrents/test1_single.torrent b/test/mutable_test_torrents/test1_single.torrent new file mode 100644 index 0000000..eccef4b --- /dev/null +++ b/test/mutable_test_torrents/test1_single.torrent @@ -0,0 +1,2 @@ +d10:created by10:libtorrent13:creation datei1419490700e4:infod6:lengthi51200e4:name1:a12:piece lengthi16384e6:pieces80:ÈÄÔ}„Ç_ML¤)دym$„¶ +ޟÏk}@ûˆÑ¹bƏ®¸ßòüÝ ‡=åæâvw‰¯Q¦(R!¯+O0^+w÷I•Çee \ No newline at end of file diff --git a/test/mutable_test_torrents/test1_single_padded.torrent b/test/mutable_test_torrents/test1_single_padded.torrent new file mode 100644 index 0000000..9e349cd --- /dev/null +++ b/test/mutable_test_torrents/test1_single_padded.torrent @@ -0,0 +1,2 @@ +d10:created by10:libtorrent13:creation datei1419490711e4:infod6:lengthi51200e4:name1:a12:piece lengthi16384e6:pieces80:ÈÄÔ}„Ç_ML¤)دym$„¶ +ޟÏk}@ûˆÑ¹bƏ®¸ßòüÝ ‡=åæâvw‰¯Q“ âF?ó{¶ô+,/ß¼Gee \ No newline at end of file diff --git a/test/mutable_test_torrents/test2.torrent b/test/mutable_test_torrents/test2.torrent new file mode 100644 index 0000000..1a60005 --- /dev/null +++ b/test/mutable_test_torrents/test2.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1419452987e4:infod5:filesld6:lengthi18e4:pathl1:aeed6:lengthi51200e4:pathl1:beed6:lengthi19e4:pathl1:ceed6:lengthi46080e4:pathl1:deee4:name5:test212:piece lengthi16384e6:pieces120:Ή!utŠ Ë%˝0½¼£½ÍîiŒÇØ/%Äá¿O¸N´\´<< ú,3J«~ÊÛ,tS’žÊ Ęì…@EÜi»›ú½Áƒ¯ˆQôו…™7GFÖì®¶yê-I›å«Ç¿RbÁ0ˆòðgïG_Xo8N=+Z&nfZANDL@68h9-uVCYA<<=EkWe zR+)Ke`6;GWX_+~x#W^WvRynD8=_MJNriMla20-b8#FC60L#w3J)D#n|#FCOCL#qO~ zB102nV>7rSb1S`gASg&oNy*Ghj|Z7(Xk`G@2v=`nW?+G2XbM(CjSb9=@SACfY^I?l zT(MO$ii8D1A~7`;$jM90O*OSDNi8lhGBmO($V^R6Re*Ze(9GDvB-IQeSZru!V0Gfi zmD-l$_3^$wOEhn+m#fTGk&$ZI#&vK0`RrN;$=@9pdv_)s>tDCy{--~4?<%z0K9zm; zsI0!cvv+-<$YfUDM{XkapR2chnJcX=siS{?kNd5Mp1iKS&-U}oPWV{aDC?Vil~?%c_BD)&pbt~w~GT<_V)Q0k(wIpqlNlCF}1N374RmS(q=yq1s_ P+j6_}_Qu9^5uk?v%bCG( literal 0 HcmV?d00001 diff --git a/test/mutable_test_torrents/test3.torrent b/test/mutable_test_torrents/test3.torrent new file mode 100644 index 0000000000000000000000000000000000000000..11867c229ae72c15c4f5a0b502c0636db795e980 GIT binary patch literal 366 zcmYc>G_Xo8N=+Z&nfZANDL@68h9-uVCZ?p!;#rl(qeuhfKG2?q*-Z-*)xL{+0tL sq?J{stm*iZD>KFH%Yk1ihSJaVm(FQAFu8T3;pw=Z?eha>PDxD#0NSCF-T(jq literal 0 HcmV?d00001 diff --git a/test/mutable_test_torrents/test3_pad_files.torrent b/test/mutable_test_torrents/test3_pad_files.torrent new file mode 100644 index 0000000..a71f143 --- /dev/null +++ b/test/mutable_test_torrents/test3_pad_files.torrent @@ -0,0 +1,4 @@ +d10:created by10:libtorrent13:creation datei1419490178e4:infod5:filesld6:lengthi51200e4:pathl1:ceed4:attr1:p6:lengthi14336e4:pathl17:.____padding_file1:0eed6:lengthi51200e4:pathl1:deed4:attr1:p6:lengthi14336e4:pathl17:.____padding_file1:1eed6:lengthi19e4:pathl1:aeed6:lengthi18e4:pathl1:beee4:name5:test312:piece lengthi16384e6:pieces180:ÈÄÔ}„Ç_ML¤)دym$„¶ +ޟÏk}@ûˆÑ¹bƏ®¸ßòüÝ ‡=åæâvw‰¯Q“ âF?ó{¶ô+,/ß¼G[°nÂ=s½'ÚRÞ;œ&¡•>à’hX¹¾âÆBVD8ô-..45k×qÝDÄ- +fH„ó섔ýŽ†åƒ¸’Kyûñ€ª +A•,õ@¦¤f0kHOæ:¥k†të´Û‰Û±geee \ No newline at end of file diff --git a/test/peer_server.cpp b/test/peer_server.cpp new file mode 100644 index 0000000..2e238ae --- /dev/null +++ b/test/peer_server.cpp @@ -0,0 +1,169 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/io_service.hpp" +#include "peer_server.hpp" +#include "test_utils.hpp" + +#include +#include +#include +#include +#include + +using namespace lt; +using namespace std::placeholders; + +struct peer_server +{ + lt::io_service m_ios; + std::atomic m_peer_requests{0}; + tcp::acceptor m_acceptor{m_ios}; + int m_port = 0; + + std::shared_ptr m_thread; + + peer_server() + { + error_code ec; + m_acceptor.open(tcp::v4(), ec); + if (ec) + { + std::printf("PEER Error opening peer listen socket: %s\n", ec.message().c_str()); + return; + } + + m_acceptor.bind(tcp::endpoint(address_v4::any(), 0), ec); + if (ec) + { + std::printf("PEER Error binding peer socket to port 0: %s\n", ec.message().c_str()); + return; + } + m_port = m_acceptor.local_endpoint(ec).port(); + if (ec) + { + std::printf("PEER Error getting local endpoint of peer socket: %s\n", ec.message().c_str()); + return; + } + m_acceptor.listen(10, ec); + if (ec) + { + std::printf("PEER Error listening on peer socket: %s\n", ec.message().c_str()); + return; + } + + std::printf("%s: PEER peer initialized on port %d\n", time_now_string(), m_port); + + m_thread = std::make_shared(&peer_server::thread_fun, this); + } + + ~peer_server() + { + error_code ignore; + m_acceptor.cancel(ignore); + m_acceptor.close(ignore); + if (m_thread) m_thread->join(); + } + + int port() const { return m_port; } + + int num_hits() const { return m_peer_requests; } + + static void new_connection(error_code const& ec, error_code* ret, bool* done) + { + *ret = ec; + *done = true; + } + + void thread_fun() + { + for (;;) + { + error_code ec; + tcp::endpoint from; + tcp::socket socket(m_ios); + std::condition_variable cond; + bool done = false; + m_acceptor.async_accept(socket, from, std::bind(&new_connection, _1, &ec, &done)); + while (!done) + { + m_ios.poll_one(); + m_ios.reset(); + } + + if (ec == boost::asio::error::operation_aborted + || ec == boost::asio::error::bad_descriptor) return; + + if (ec) + { + std::printf("PEER Error accepting connection on peer socket: %s\n", ec.message().c_str()); + return; + } + + std::printf("%s: PEER incoming peer connection\n", time_now_string()); + ++m_peer_requests; + socket.close(ec); + } + } +}; + +namespace { + +std::shared_ptr g_peer; + +} + +int start_peer() +{ + g_peer.reset(new peer_server); + return g_peer->port(); +} + +// the number of DHT messages received +int num_peer_hits() +{ + if (g_peer) return g_peer->num_hits(); + return 0; +} + +void stop_peer() +{ + g_peer.reset(); +} + diff --git a/test/peer_server.hpp b/test/peer_server.hpp new file mode 100644 index 0000000..7070421 --- /dev/null +++ b/test/peer_server.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PEER_SERVER_HPP +#define PEER_SERVER_HPP + +#include "test.hpp" // for EXPORT + +// returns the port the peer is running on +EXPORT int start_peer(); + +// the number of incoming connections to this peer +EXPORT int num_peer_hits(); + +EXPORT void stop_peer(); + +#endif + diff --git a/test/print_alerts.cpp b/test/print_alerts.cpp new file mode 100644 index 0000000..34973ed --- /dev/null +++ b/test/print_alerts.cpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "print_alerts.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "print_alerts.hpp" + +void print_alerts(lt::session* ses, lt::time_point start_time) +{ + using namespace lt; + + if (ses == nullptr) return; + + std::vector alerts; + ses->pop_alerts(&alerts); + + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + alert* a = *i; +#ifndef TORRENT_DISABLE_LOGGING + if (peer_log_alert* pla = alert_cast(a)) + { + // in order to keep down the amount of logging, just log actual peer + // messages + if (pla->direction != peer_log_alert::incoming_message + && pla->direction != peer_log_alert::outgoing_message) + { + continue; + } + } +#endif + lt::time_duration d = a->timestamp() - start_time; + std::uint32_t millis = std::uint32_t(lt::duration_cast(d).count()); + std::printf("%4d.%03d: %-25s %s\n", millis / 1000, millis % 1000 + , a->what() + , a->message().c_str()); + } + +} + diff --git a/test/print_alerts.hpp b/test/print_alerts.hpp new file mode 100644 index 0000000..5bbd428 --- /dev/null +++ b/test/print_alerts.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PRINT_ALERTS_HPP +#define PRINT_ALERTS_HPP + +#include "libtorrent/time.hpp" +#include "libtorrent/session.hpp" +#include "test.hpp" // for EXPORT + +EXPORT void print_alerts(lt::session* ses, lt::time_point start_time); + +#endif + diff --git a/test/root1.xml b/test/root1.xml new file mode 100644 index 0000000..94b71d4 --- /dev/null +++ b/test/root1.xml @@ -0,0 +1,135 @@ + + + + 1 + 0 + + http://127.0.0.1 + + + + + urn:schemas-upnp-org:device:InternetGatewayDevice:1 + / + Xtreme N GIGABIT Router + D-Link Systems + http://www.dlink.com + Xtreme N GIGABIT Router + Xtreme N GIGABIT Router + DIR-655 + http://www.dlink.com + none + uuid:E17FAEB7-CABB-3BCC-9C36-7207D5397C0E + + + + 00000-00001 + + + urn:schemas-upnp-org:service:Layer3Forwarding:1 + + urn:upnp-org:serviceId:L3Forwarding1 + http://192.168.0.1:4444/l3fw + http://192.168.0.1:9393/l3fw + http://192.168.0.1/l3fw.xml + + + + + urn:schemas-upnp-org:device:WANDevice:1 + + Xtreme N GIGABIT Router + D-Link Systems + http://www.dlink.com + Xtreme N GIGABIT Router + Xtreme N GIGABIT Router + DIR-655 + http://www.dlink.com + none + uuid:EC4A6889-B956-363F-9849-62C053B72D72 + + 00000-00001 + + + urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + + urn:upnp-org:serviceId:WANCommonIFC1 + http://192.168.0.1:4444/wcommifc + http://192.168.0.1:9393/wcommifc + http://192.168.0.1/WANCommonIFC1.xml + + + + + urn:schemas-upnp-org:device:WANConnectionDevice:1 + + Xtreme N GIGABIT Router + D-Link Systems + http://www.dlink.com + Xtreme N GIGABIT Router + Xtreme N GIGABIT Router + DIR-655 + http://www.dlink.com + none + uuid:8843AAB0-7191-39C5-A763-9592A8C5AB32 + + 00000-00001 + + + urn:schemas-upnp-org:service:WANIPConnection:1 + + urn:upnp-org:serviceId:WANIPConn1 + http://127.0.0.1:%d/wipconn + http://192.168.0.1:9393/wipconn + http://192.168.0.1/WANIPConn1.xml + + + + + + + + + + urn:schemas-wifialliance-org:device:WFADevice:1 + / + WFADevice + D-Link Systems + http://www.dlink.com + Xtreme N GIGABIT Router + Xtreme N GIGABIT Router + DIR-655 + http://www.dlink.com + none + + uuid:003F35AE-C81C-3EE7-972E-8AEF712289D5 + + + + + + + + + 00000-00001 + + + urn:schemas-wifialliance-org:service:WFAWLANConfig:1 + + + + urn:wifialliance-org:serviceId:WFAWLANConfig1 + http://192.168.0.1:8832/wfawc + http://192.168.0.1:8456/wfawc + http://192.168.0.1/WFAwc.xml + + + + + + + + + + + diff --git a/test/root2.xml b/test/root2.xml new file mode 100644 index 0000000..84a1d81 --- /dev/null +++ b/test/root2.xml @@ -0,0 +1 @@ +10http://127.0.0.1:%durn:schemas-upnp-org:device:InternetGatewayDevice:1http://192.168.0.1:80D-Link RouterD-Linkhttp://www.dlink.comInternet Access RouterD-Link Routeruuid:upnp-InternetGatewayDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:Layer3Forwarding:1urn:upnp-org:serviceId:L3Forwarding1/Layer3Forwarding/Layer3Forwarding/Layer3Forwarding.xmlurn:schemas-upnp-org:device:WANDevice:1WANDeviceD-Linkhttp://www.dlink.comInternet Access RouterD-Link Router1http://support.dlink.com12345678900001uuid:upnp-WANDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1urn:upnp-org:serviceId:WANCommonInterfaceConfig/WANCommonInterfaceConfig/WANCommonInterfaceConfig/WANCommonInterfaceConfig.xmlurn:schemas-upnp-org:device:WANConnectionDevice:1WAN Connection DeviceD-Linkhttp://www.dlink.comInternet Access RouterD-Link Router1http://support.dlink.com12345678900001uuid:upnp-WANConnectionDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:WANIPConnection:1urn:upnp-org:serviceId:WANIPConnection/WANIPConnection/WANIPConnection/WANIPConnection.xml diff --git a/test/root3.xml b/test/root3.xml new file mode 100644 index 0000000..6544111 --- /dev/null +++ b/test/root3.xml @@ -0,0 +1 @@ +10http://127.0.0.1:%durn:schemas-upnp-org:device:InternetGatewayDevice:2http://192.168.0.1:80D-Link RouterD-Linkhttp://www.dlink.comInternet Access RouterD-Link Routeruuid:upnp-InternetGatewayDevice-2_0-12345678900001123456789001urn:schemas-upnp-org:service:Layer3Forwarding:1urn:upnp-org:serviceId:L3Forwarding1/Layer3Forwarding/Layer3Forwarding/Layer3Forwarding.xmlurn:schemas-upnp-org:device:WANDevice:2WANDeviceD-Linkhttp://www.dlink.comInternet Access RouterD-Link Router1http://support.dlink.com12345678900001uuid:upnp-WANDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1urn:upnp-org:serviceId:WANCommonInterfaceConfig/WANCommonInterfaceConfig/WANCommonInterfaceConfig/WANCommonInterfaceConfig.xmlurn:schemas-upnp-org:device:WANConnectionDevice:2WAN Connection DeviceD-Linkhttp://www.dlink.comInternet Access RouterD-Link Router1http://support.dlink.com12345678900001uuid:upnp-WANConnectionDevice-2_0-12345678900001123456789001urn:schemas-upnp-org:service:WANIPConnection:2urn:upnp-org:serviceId:WANIPConnection/WANIPConnection/WANIPConnection/WANIPConnection.xml diff --git a/test/settings.cpp b/test/settings.cpp new file mode 100644 index 0000000..01f5d22 --- /dev/null +++ b/test/settings.cpp @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert.hpp" +#include "settings.hpp" + +using namespace lt; + +lt::settings_pack settings() +{ + alert_category_t const mask = + alert_category::error + | alert_category::peer + | alert_category::port_mapping + | alert_category::storage + | alert_category::tracker + | alert_category::connect + | alert_category::status + | alert_category::ip_block + | alert_category::dht + | alert_category::session_log + | alert_category::torrent_log + | alert_category::peer_log + | alert_category::incoming_request + | alert_category::dht_log + | alert_category::dht_operation + | alert_category::port_mapping_log + | alert_category::file_progress + | alert_category::piece_progress; + + settings_pack pack; + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_str(settings_pack::dht_bootstrap_nodes, ""); + + pack.set_bool(settings_pack::prefer_rc4, false); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::allowed_enc_level, settings_pack::pe_both); +#if TORRENT_ABI_VERSION == 1 + pack.set_bool(settings_pack::rate_limit_utp, true); +#endif + + pack.set_int(settings_pack::alert_mask, mask); + +#ifndef TORRENT_BUILD_SIMULATOR + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); +#else + // we use 0 threads (disk I/O operations will be performed in the network + // thread) to be simulator friendly. + pack.set_int(settings_pack::aio_threads, 0); +#endif + +#if TORRENT_ABI_VERSION == 1 + pack.set_int(settings_pack::half_open_limit, 1); +#endif + + return pack; +} + diff --git a/test/settings.hpp b/test/settings.hpp new file mode 100644 index 0000000..fae29b8 --- /dev/null +++ b/test/settings.hpp @@ -0,0 +1,37 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/settings_pack.hpp" +#include "test.hpp" + +lt::settings_pack EXPORT settings(); + diff --git a/test/setup_transfer.cpp b/test/setup_transfer.cpp new file mode 100644 index 0000000..541b8b0 --- /dev/null +++ b/test/setup_transfer.cpp @@ -0,0 +1,1110 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include + +#include "libtorrent/session.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6() +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/vector.hpp" +#include "libtorrent/aux_/path.hpp" + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" + +#ifndef _WIN32 +#include +#include +#endif + +using namespace lt; + +#if defined TORRENT_WINDOWS +#include +#endif + +std::shared_ptr generate_torrent(bool const with_files) +{ + if (with_files) + { + error_code ec; + create_directories("test_resume", ec); + std::vector a(128 * 1024 * 8); + std::vector b(128 * 1024); + std::ofstream("test_resume/tmp1").write(a.data(), std::streamsize(a.size())); + std::ofstream("test_resume/tmp2").write(b.data(), std::streamsize(b.size())); + std::ofstream("test_resume/tmp3").write(b.data(), std::streamsize(b.size())); + } + file_storage fs; + fs.add_file("test_resume/tmp1", 128 * 1024 * 8); + fs.add_file("test_resume/tmp2", 128 * 1024); + fs.add_file("test_resume/tmp3", 128 * 1024); + lt::create_torrent t(fs, 128 * 1024, 6); + + t.set_comment("test comment"); + t.set_creator("libtorrent test"); + t.add_tracker("http://torrent_file_tracker.com/announce"); + t.add_url_seed("http://torrent_file_url_seed.com/"); + + TEST_CHECK(t.num_pieces() > 0); + for (auto const i : fs.piece_range()) + { + sha1_hash ph; + aux::random_bytes(ph); + t.set_hash(i, ph); + } + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + return std::make_shared(buf, from_span); +} + +namespace { + std::uint32_t g_addr = 0x92343023; +} + +void init_rand_address() +{ + g_addr = 0x92343023; +} + +address rand_v4() +{ + address_v4 ret; + do + { + g_addr += 0x3080ca; + ret = address_v4(g_addr); + } while (is_any(ret) || is_local(ret) || is_loopback(ret)); + return ret; +} + +sha1_hash rand_hash() +{ + sha1_hash ret; + aux::random_bytes(ret); + return ret; +} + +sha1_hash to_hash(char const* s) +{ + sha1_hash ret; + aux::from_hex({s, 40}, ret.data()); + return ret; +} + +address rand_v6() +{ + address_v6::bytes_type bytes; + for (int i = 0; i < int(bytes.size()); ++i) + bytes[static_cast(i)] = std::uint8_t(lt::random(0xff)); + return address_v6(bytes); +} + +static std::uint16_t g_port = 0; + +tcp::endpoint rand_tcp_ep(lt::address(&rand_addr)()) +{ + // make sure we don't produce the same "random" port twice + g_port = (g_port + 1) % 14038; + return tcp::endpoint(rand_addr(), g_port + 1024); +} + +udp::endpoint rand_udp_ep(lt::address(&rand_addr)()) +{ + g_port = (g_port + 1) % 14037; + return udp::endpoint(rand_addr(), g_port + 1024); +} + +std::map get_counters(lt::session& s) +{ + using namespace lt; + s.post_session_stats(); + + std::map ret; + alert const* a = wait_for_alert(s, session_stats_alert::alert_type + , "get_counters()"); + + TEST_CHECK(a); + if (!a) return ret; + + session_stats_alert const* sa = alert_cast(a); + if (!sa) return ret; + + static std::vector metrics = session_stats_metrics(); + for (auto const& m : metrics) + ret[m.name] = sa->counters()[m.value_index]; + return ret; +} +namespace { +bool should_print(lt::alert* a) +{ +#ifndef TORRENT_DISABLE_LOGGING + if (auto pla = alert_cast(a)) + { + if (pla->direction != peer_log_alert::incoming_message + && pla->direction != peer_log_alert::outgoing_message + && pla->direction != peer_log_alert::info) + return false; + } +#endif + if (alert_cast(a) + || alert_cast(a) + || alert_cast(a) + || alert_cast(a)) + { + return false; + } + return true; +} +} + +alert const* wait_for_alert(lt::session& ses, int type, char const* name + , pop_alerts const p, lt::time_duration timeout) +{ + // we pop alerts in batches, but we wait for individual messages. This is a + // cache to keep around alerts that came *after* the one we're waiting for. + // To let subsequent calls to this function be able to pick those up, despite + // already being popped off the sessions alert queue. + static std::map> cache; + auto& alerts = cache[&ses]; + + time_point const end_time = lt::clock_type::now() + timeout; + + while (true) + { + time_point const now = clock_type::now(); + if (now > end_time) return nullptr; + + alert const* ret = nullptr; + + if (alerts.empty()) + { + ses.wait_for_alert(end_time - now); + ses.pop_alerts(&alerts); + } + for (auto i = alerts.begin(); i != alerts.end(); ++i) + { + auto a = *i; + if (should_print(a)) + { + std::printf("%s: %s: [%s] %s\n", time_now_string(), name + , a->what(), a->message().c_str()); + } + if (a->type() == type) + { + ret = a; + if (p == pop_alerts::pop_all) alerts.clear(); + else alerts.erase(alerts.begin(), std::next(i)); + return ret; + } + } + alerts.clear(); + } +} + +int load_file(std::string const& filename, std::vector& v + , lt::error_code& ec, int limit) +{ + ec.clear(); + FILE* f = fopen(filename.c_str(), "rb"); + if (f == nullptr) + { + ec.assign(errno, boost::system::system_category()); + return -1; + } + + int r = fseek(f, 0, SEEK_END); + if (r != 0) + { + ec.assign(errno, boost::system::system_category()); + fclose(f); + return -1; + } + long s = ftell(f); + if (s < 0) + { + ec.assign(errno, boost::system::system_category()); + fclose(f); + return -1; + } + + if (s > limit) + { + fclose(f); + return -2; + } + + r = fseek(f, 0, SEEK_SET); + if (r != 0) + { + ec.assign(errno, boost::system::system_category()); + fclose(f); + return -1; + } + + v.resize(static_cast(s)); + if (s == 0) + { + fclose(f); + return 0; + } + + r = int(fread(&v[0], 1, v.size(), f)); + if (r < 0) + { + ec.assign(errno, boost::system::system_category()); + fclose(f); + return -1; + } + + fclose(f); + + if (r != s) return -3; + + return 0; +} + +bool print_alerts(lt::session& ses, char const* name + , bool allow_no_torrents, bool allow_failed_fastresume + , std::function predicate, bool no_output) +{ + TEST_CHECK(!ses.get_torrents().empty() || allow_no_torrents); + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + if (peer_disconnected_alert const* p = alert_cast(a)) + { + std::printf("%s: %s: [%s] (%s): %s\n", time_now_string(), name, a->what() + , print_endpoint(p->endpoint).c_str(), p->message().c_str()); + } + else if (a->type() == invalid_request_alert::alert_type) + { + fprintf(stdout, "peer error: %s\n", a->message().c_str()); + TEST_CHECK(false); + } + else if (a->type() == fastresume_rejected_alert::alert_type) + { + fprintf(stdout, "resume data error: %s\n", a->message().c_str()); + TEST_CHECK(allow_failed_fastresume); + } + else if (should_print(a) && !no_output) + { + std::printf("%s: %s: [%s] %s\n", time_now_string(), name, a->what(), a->message().c_str()); + } + + TEST_CHECK(alert_cast(a) == nullptr || allow_failed_fastresume); + + invalid_request_alert const* ira = alert_cast(a); + if (ira) + { + std::printf("peer error: %s\n", ira->message().c_str()); + TEST_CHECK(false); + } + } + return predicate && std::any_of(alerts.begin(), alerts.end(), predicate); +} + +void wait_for_listen(lt::session& ses, char const* name) +{ + bool listen_done = false; + alert const* a = nullptr; + do + { + listen_done = print_alerts(ses, name, true, true, [](lt::alert const* al) + { return alert_cast(al) || alert_cast(al); } + , false); + if (listen_done) break; + a = ses.wait_for_alert(milliseconds(500)); + } while (a); + // we din't receive a listen alert! + TEST_CHECK(listen_done); +} + +void wait_for_downloading(lt::session& ses, char const* name) +{ + time_point start = clock_type::now(); + bool downloading_done = false; + alert const* a = nullptr; + do + { + downloading_done = print_alerts(ses, name, true, true + , [](lt::alert const* al) + { + state_changed_alert const* sc = alert_cast(al); + return sc && sc->state == torrent_status::downloading; + }, false); + if (downloading_done) break; + if (total_seconds(clock_type::now() - start) > 10) break; + a = ses.wait_for_alert(seconds(2)); + } while (a); + if (!downloading_done) + { + std::printf("%s: did not receive a state_changed_alert indicating " + "the torrent is downloading. waited: %d ms\n" + , name, int(total_milliseconds(clock_type::now() - start))); + } +} + +void print_ses_rate(float const time + , lt::torrent_status const* st1 + , lt::torrent_status const* st2 + , lt::torrent_status const* st3) +{ + if (st1) + { + std::printf("%3.1fs | %dkB/s %dkB/s %d%% %d cc:%d%s", static_cast(time) + , int(st1->download_payload_rate / 1000) + , int(st1->upload_payload_rate / 1000) + , int(st1->progress * 100) + , st1->num_peers + , st1->connect_candidates + , st1->errc ? (" [" + st1->errc.message() + "]").c_str() : ""); + } + if (st2) + std::printf(" : %3.1fs | %dkB/s %dkB/s %d%% %d cc:%d%s", static_cast(time) + , int(st2->download_payload_rate / 1000) + , int(st2->upload_payload_rate / 1000) + , int(st2->progress * 100) + , st2->num_peers + , st2->connect_candidates + , st2->errc ? (" [" + st1->errc.message() + "]").c_str() : ""); + if (st3) + std::printf(" : %3.1fs | %dkB/s %dkB/s %d%% %d cc:%d%s", static_cast(time) + , int(st3->download_payload_rate / 1000) + , int(st3->upload_payload_rate / 1000) + , int(st3->progress * 100) + , st3->num_peers + , st3->connect_candidates + , st3->errc ? (" [" + st1->errc.message() + "]").c_str() : ""); + + std::printf("\n"); +} + +#ifdef _WIN32 +using pid_type = DWORD; +#else +using pid_type = pid_t; +#endif + +namespace { + +// returns 0 on failure, otherwise pid +pid_type async_run(char const* cmdline) +{ +#ifdef _WIN32 + char buf[2048]; + std::snprintf(buf, sizeof(buf), "%s", cmdline); + + std::printf("CreateProcess %s\n", buf); + PROCESS_INFORMATION pi; + STARTUPINFOA startup{}; + startup.cb = sizeof(startup); + startup.dwFlags = STARTF_USESTDHANDLES; + startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup.hStdError = GetStdHandle(STD_OUTPUT_HANDLE); + int const ret = CreateProcessA(NULL, buf, NULL, NULL, TRUE + , 0, NULL, NULL, &startup, &pi); + + if (ret == 0) + { + int const error = GetLastError(); + std::printf("ERROR: (%d) %s\n", error, error_code(error, system_category()).message().c_str()); + return 0; + } + + DWORD len = sizeof(buf); + if (QueryFullProcessImageNameA(pi.hProcess, PROCESS_NAME_NATIVE, buf, &len) == 0) + { + int const error = GetLastError(); + std::printf("ERROR: QueryFullProcessImageName (%d) %s\n", error + , error_code(error, system_category()).message().c_str()); + } + else + { + std::printf("launched: %s\n", buf); + } + return pi.dwProcessId; +#else + pid_type p; + char arg_storage[4096]; + char* argp = arg_storage; + std::vector argv; + argv.push_back(argp); + for (char const* in = cmdline; *in != '\0'; ++in) + { + if (*in != ' ') + { + *argp++ = *in; + continue; + } + *argp++ = '\0'; + argv.push_back(argp); + } + *argp = '\0'; + argv.push_back(nullptr); + + int ret = posix_spawnp(&p, argv[0], nullptr, nullptr, &argv[0], nullptr); + if (ret != 0) + { + std::printf("ERROR (%d) %s\n", errno, strerror(errno)); + return 0; + } + return p; +#endif +} + +void stop_process(pid_type p) +{ +#ifdef _WIN32 + HANDLE proc = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, p); + if (proc == NULL) return; + TerminateProcess(proc, 138); + WaitForSingleObject(proc, 5000); + CloseHandle(proc); +#else + std::printf("killing pid: %d\n", p); + kill(p, SIGKILL); +#endif +} + +} // anonymous namespace + +struct proxy_t +{ + pid_type pid; + int type; +}; + +// maps port to proxy type +static std::map running_proxies; + +void stop_proxy(int port) +{ + auto const it = running_proxies.find(port); + + if (it == running_proxies.end()) return; + + std::printf("stopping proxy on port %d\n", port); + + stop_process(it->second.pid); + running_proxies.erase(it); +} + +void stop_all_proxies() +{ + std::map proxies = running_proxies; + running_proxies.clear(); + for (auto const& i : proxies) + { + stop_process(i.second.pid); + } +} + +namespace { + +#ifdef TORRENT_BUILD_SIMULATOR +void wait_for_port(int) {} +#else +void wait_for_port(int const port) +{ + // wait until the python program is ready to accept connections + int i = 0; + io_service ios; + for (;;) + { + tcp::socket s(ios); + error_code ec; + s.open(tcp::v4(), ec); + if (ec) + { + std::printf("ERROR opening probe socket: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + s.connect(tcp::endpoint(address::from_string("127.0.0.1") + , std::uint16_t(port)), ec); + if (ec == boost::system::errc::connection_refused) + { + if (i == 100) + { + std::printf("ERROR: somehow the python program still hasn't " + "opened its socket on port %d\n", port); + return; + } + ++i; + std::this_thread::sleep_for(lt::milliseconds(500)); + continue; + } + if (ec) + { + std::printf("ERROR connecting probe socket: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + return; + } +} +#endif + +std::string get_python() +{ +#ifdef _WIN32 + char dummy[1]; + DWORD const req_size = GetEnvironmentVariable("PYTHON_INTERPRETER", dummy, sizeof(dummy)); + if (req_size > 1 && req_size < 4096) + { + std::vector buf(req_size); + DWORD const sz = GetEnvironmentVariable("PYTHON_INTERPRETER", buf.data(), buf.size()); + if (sz == buf.size() - 1) return buf.data(); + } +#endif + return "python3"; +} + +} + +// returns a port on success and -1 on failure +int start_proxy(int proxy_type) +{ + using namespace lt; + + std::map :: iterator i = running_proxies.begin(); + for (; i != running_proxies.end(); ++i) + { + if (i->second.type == proxy_type) { return i->first; } + } + + int port = 10000 + static_cast(lt::random(50000)); + error_code ec; + io_service ios; + + // make sure the port we pick is free + do { + ++port; + tcp::socket s(ios); + s.open(tcp::v4(), ec); + if (ec) break; + s.bind(tcp::endpoint(address::from_string("127.0.0.1") + , std::uint16_t(port)), ec); + } while (ec); + + + char const* type = ""; + char const* auth = ""; + char const* cmd = ""; + + switch (proxy_type) + { + case settings_pack::socks4: + type = "socks4"; + auth = " --allow-v4"; + cmd = "../socks.py"; + break; + case settings_pack::socks5: + type = "socks5"; + cmd = "../socks.py"; + break; + case settings_pack::socks5_pw: + type = "socks5"; + auth = " --username testuser --password testpass"; + cmd = "../socks.py"; + break; + case settings_pack::http: + type = "http"; + cmd = "../http_proxy.py"; + break; + case settings_pack::http_pw: + type = "http"; + auth = " --basic-auth testuser:testpass"; + cmd = "../http_proxy.py"; + break; + } + std::string python_exe = get_python(); + char buf[1024]; + std::snprintf(buf, sizeof(buf), "%s %s --port %d%s", python_exe.c_str(), cmd, port, auth); + + std::printf("%s starting proxy on port %d (%s %s)...\n", time_now_string(), port, type, auth); + std::printf("%s\n", buf); + pid_type r = async_run(buf); + if (r == 0) abort(); + proxy_t t = { r, proxy_type }; + running_proxies.insert(std::make_pair(port, t)); + std::printf("%s launched\n", time_now_string()); + std::this_thread::sleep_for(lt::milliseconds(500)); + wait_for_port(port); + return port; +} + +using namespace lt; + +template +std::shared_ptr clone_ptr(std::shared_ptr const& ptr) +{ + return std::make_shared(*ptr); +} + +std::vector generate_piece(piece_index_t const idx, int const piece_size) +{ + using namespace lt; + std::vector ret(static_cast(piece_size)); + + std::mt19937 rng(static_cast(static_cast(idx))); + std::uniform_int_distribution rand(-128, 127); + for (char& c : ret) + { + c = static_cast(rand(rng)); + } + return ret; +} + +lt::file_storage make_file_storage(span const file_sizes + , int const piece_size, std::string base_name) +{ + using namespace lt; + file_storage fs; + for (std::ptrdiff_t i = 0; i != file_sizes.size(); ++i) + { + char filename[200]; + std::snprintf(filename, sizeof(filename), "test%d", int(i)); + char dirname[200]; + std::snprintf(dirname, sizeof(dirname), "%s%d", base_name.c_str() + , int(i) / 5); + std::string full_path = combine_path(dirname, filename); + + fs.add_file(full_path, file_sizes[i]); + } + + fs.set_piece_length(piece_size); + fs.set_num_pieces(int((fs.total_size() + piece_size - 1) / piece_size)); + + return fs; +} + +std::shared_ptr make_torrent(span const file_sizes + , int const piece_size) +{ + using namespace lt; + file_storage fs = make_file_storage(file_sizes, piece_size); + + lt::create_torrent ct(fs, piece_size, 0x4000 + , lt::create_torrent::optimize_alignment); + + for (auto const i : fs.piece_range()) + { + std::vector piece = generate_piece(i, fs.piece_size(i)); + ct.set_hash(i, hasher(piece).final()); + } + + std::vector buf; + bencode(std::back_inserter(buf), ct.generate()); + return std::make_shared(buf, from_span); +} + +void create_random_files(std::string const& path, span file_sizes + , file_storage* fs) +{ + error_code ec; + aux::vector random_data(300000); + for (std::ptrdiff_t i = 0; i != file_sizes.size(); ++i) + { + aux::random_bytes(random_data); + char filename[200]; + std::snprintf(filename, sizeof(filename), "test%d", int(i)); + char dirname[200]; + std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5); + + std::string full_path = combine_path(path, dirname); + lt::create_directories(full_path, ec); + if (ec) std::printf("create_directory(%s) failed: (%d) %s\n" + , full_path.c_str(), ec.value(), ec.message().c_str()); + + full_path = combine_path(full_path, filename); + std::printf("creating file: %s\n", full_path.c_str()); + + int to_write = file_sizes[i]; + if (fs) fs->add_file(full_path, to_write); + file f(full_path, open_mode::write_only, ec); + if (ec) std::printf("failed to create file \"%s\": (%d) %s\n" + , full_path.c_str(), ec.value(), ec.message().c_str()); + std::int64_t offset = 0; + while (to_write > 0) + { + int const s = std::min(to_write, static_cast(random_data.size())); + iovec_t const b = { random_data.data(), s}; + f.writev(offset, b, ec); + if (ec) std::printf("failed to write file \"%s\": (%d) %s\n" + , full_path.c_str(), ec.value(), ec.message().c_str()); + offset += s; + to_write -= s; + } + } +} + +std::shared_ptr create_torrent(std::ostream* file + , char const* name, int piece_size + , int num_pieces, bool add_tracker, std::string ssl_certificate) +{ + // exercise the path when encountering invalid urls + char const* invalid_tracker_url = "http:"; + char const* invalid_tracker_protocol = "foo://non/existent-name.com/announce"; + + file_storage fs; + int total_size = piece_size * num_pieces; + fs.add_file(name, total_size); + lt::create_torrent t(fs, piece_size); + if (add_tracker) + { + t.add_tracker(invalid_tracker_url); + t.add_tracker(invalid_tracker_protocol); + } + + if (!ssl_certificate.empty()) + { + std::vector file_buf; + error_code ec; + int res = load_file(ssl_certificate, file_buf, ec); + if (ec || res < 0) + { + std::printf("failed to load SSL certificate: %s\n", ec.message().c_str()); + } + else + { + std::string pem; + std::copy(file_buf.begin(), file_buf.end(), std::back_inserter(pem)); + t.set_root_cert(pem); + } + } + + aux::vector piece(static_cast(piece_size)); + for (int i = 0; i < piece.end_index(); ++i) + piece[i] = (i % 26) + 'A'; + + // calculate the hash for all pieces + sha1_hash ph = hasher(piece).final(); + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + if (file) + { + while (total_size > 0) + { + file->write(&piece[0], std::min(piece.end_index(), total_size)); + total_size -= piece.end_index(); + } + } + + entry tor = t.generate(); + + std::vector tmp; + bencode(std::back_inserter(tmp), tor); + error_code ec; + return std::make_shared(tmp, ec, from_span); +} + +std::tuple +setup_transfer(lt::session* ses1, lt::session* ses2, lt::session* ses3 + , bool clear_files, bool use_metadata_transfer, bool connect_peers + , std::string suffix, int piece_size + , std::shared_ptr* torrent, bool super_seeding + , add_torrent_params const* p, bool stop_lsd, bool use_ssl_ports + , std::shared_ptr* torrent2) +{ + TORRENT_ASSERT(ses1); + TORRENT_ASSERT(ses2); + + if (stop_lsd) + { + settings_pack pack; + pack.set_bool(settings_pack::enable_lsd, false); + ses1->apply_settings(pack); + ses2->apply_settings(pack); + if (ses3) ses3->apply_settings(pack); + } + + // This has the effect of applying the global + // rule to all peers, regardless of if they're local or not + ip_filter f; + f.add_rule(address_v4::from_string("0.0.0.0") + , address_v4::from_string("255.255.255.255") + , 1 << static_cast(lt::session::global_peer_class_id)); + ses1->set_peer_class_filter(f); + ses2->set_peer_class_filter(f); + if (ses3) ses3->set_peer_class_filter(f); + + settings_pack pack; + if (ses3) pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + pack.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); + pack.set_int(settings_pack::max_failcount, 1); + ses1->apply_settings(pack); + ses2->apply_settings(pack); + if (ses3) + { + ses3->apply_settings(pack); + } + + std::shared_ptr t; + if (torrent == nullptr) + { + error_code ec; + create_directory("tmp1" + suffix, ec); + std::string const file_path = combine_path("tmp1" + suffix, "temporary"); + std::ofstream file(file_path.c_str()); + t = ::create_torrent(&file, "temporary", piece_size, 9, false); + file.close(); + if (clear_files) + { + remove_all(combine_path("tmp2" + suffix, "temporary"), ec); + remove_all(combine_path("tmp3" + suffix, "temporary"), ec); + } + std::printf("generated torrent: %s %s\n", aux::to_hex(t->info_hash()).c_str(), file_path.c_str()); + } + else + { + t = *torrent; + } + + // they should not use the same save dir, because the + // file pool will complain if two torrents are trying to + // use the same files + add_torrent_params param; + param.flags &= ~torrent_flags::paused; + param.flags &= ~torrent_flags::auto_managed; + if (p) param = *p; + param.ti = clone_ptr(t); + param.save_path = "tmp1" + suffix; + param.flags |= torrent_flags::seed_mode; + error_code ec; + torrent_handle tor1 = ses1->add_torrent(param, ec); + if (ec) + { + std::printf("ses1.add_torrent: %s\n", ec.message().c_str()); + return std::make_tuple(torrent_handle(), torrent_handle(), torrent_handle()); + } + if (super_seeding) + { + tor1.set_flags(torrent_flags::super_seeding); + } + + // the downloader cannot use seed_mode + param.flags &= ~torrent_flags::seed_mode; + + TEST_CHECK(!ses1->get_torrents().empty()); + + torrent_handle tor2; + torrent_handle tor3; + + if (ses3) + { + param.ti = clone_ptr(t); + param.save_path = "tmp3" + suffix; + tor3 = ses3->add_torrent(param, ec); + TEST_CHECK(!ses3->get_torrents().empty()); + } + + if (use_metadata_transfer) + { + param.ti.reset(); + param.info_hash = t->info_hash(); + } + else if (torrent2) + { + param.ti = clone_ptr(*torrent2); + } + else + { + param.ti = clone_ptr(t); + } + param.save_path = "tmp2" + suffix; + + tor2 = ses2->add_torrent(param, ec); + TEST_CHECK(!ses2->get_torrents().empty()); + + TORRENT_ASSERT(ses1->get_torrents().size() == 1); + TORRENT_ASSERT(ses2->get_torrents().size() == 1); + +// std::this_thread::sleep_for(lt::milliseconds(100)); + + if (connect_peers) + { + wait_for_downloading(*ses2, "ses2"); + + int port = 0; + if (use_ssl_ports) + { + port = ses2->ssl_listen_port(); + std::printf("%s: ses2->ssl_listen_port(): %d\n", time_now_string(), port); + } + + if (port == 0) + { + port = ses2->listen_port(); + std::printf("%s: ses2->listen_port(): %d\n", time_now_string(), port); + } + + std::printf("%s: ses1: connecting peer port: %d\n" + , time_now_string(), port); + tor1.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec) + , std::uint16_t(port))); + + if (ses3) + { + // give the other peers some time to get an initial + // set of pieces before they start sharing with each-other + + wait_for_downloading(*ses3, "ses3"); + + port = 0; + int port2 = 0; + if (use_ssl_ports) + { + port = ses2->ssl_listen_port(); + port2 = ses1->ssl_listen_port(); + } + + if (port == 0) port = ses2->listen_port(); + if (port2 == 0) port2 = ses1->listen_port(); + + std::printf("ses3: connecting peer port: %d\n", port); + tor3.connect_peer(tcp::endpoint( + address::from_string("127.0.0.1", ec), std::uint16_t(port))); + std::printf("ses3: connecting peer port: %d\n", port2); + tor3.connect_peer(tcp::endpoint( + address::from_string("127.0.0.1", ec) + , std::uint16_t(port2))); + } + } + + return std::make_tuple(tor1, tor2, tor3); +} + +namespace { +pid_type web_server_pid = 0; +} + +int start_web_server(bool ssl, bool chunked_encoding, bool keepalive, int min_interval) +{ + int port = 2000 + static_cast(lt::random(6000)); + error_code ec; + io_service ios; + + // make sure the port we pick is free + do { + ++port; + tcp::socket s(ios); + s.open(tcp::v4(), ec); + if (ec) break; + s.bind(tcp::endpoint(address::from_string("127.0.0.1") + , std::uint16_t(port)), ec); + } while (ec); + + std::string python_exe = get_python(); + + char buf[200]; + std::snprintf(buf, sizeof(buf), "%s ../web_server.py %d %d %d %d %d" + , python_exe.c_str(), port, chunked_encoding, ssl, keepalive, min_interval); + + std::printf("%s starting web_server on port %d...\n", time_now_string(), port); + + std::printf("%s\n", buf); + pid_type r = async_run(buf); + if (r == 0) abort(); + web_server_pid = r; + std::printf("%s launched\n", time_now_string()); + std::this_thread::sleep_for(lt::milliseconds(1000)); + wait_for_port(port); + return port; +} + +void stop_web_server() +{ + if (web_server_pid == 0) return; + std::printf("stopping web server\n"); + stop_process(web_server_pid); + web_server_pid = 0; +} + +tcp::endpoint ep(char const* ip, int port) +{ + error_code ec; + tcp::endpoint ret(address::from_string(ip, ec), std::uint16_t(port)); + TEST_CHECK(!ec); + return ret; +} + +udp::endpoint uep(char const* ip, int port) +{ + error_code ec; + udp::endpoint ret(address::from_string(ip, ec), std::uint16_t(port)); + TEST_CHECK(!ec); + return ret; +} + +lt::address addr(char const* ip) +{ + lt::error_code ec; + auto ret = lt::address::from_string(ip, ec); + TEST_CHECK(!ec); + return ret; +} + +lt::address_v4 addr4(char const* ip) +{ + lt::error_code ec; + auto ret = lt::address_v4::from_string(ip, ec); + TEST_CHECK(!ec); + return ret; +} + +lt::address_v6 addr6(char const* ip) +{ + lt::error_code ec; + auto ret = lt::address_v6::from_string(ip, ec); + TEST_CHECK(!ec); + return ret; +} diff --git a/test/setup_transfer.hpp b/test/setup_transfer.hpp new file mode 100644 index 0000000..6aebb4a --- /dev/null +++ b/test/setup_transfer.hpp @@ -0,0 +1,123 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SETUP_TRANSFER_HPP +#define SETUP_TRANSFER_HPP + +#include +#include +#include "test.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/units.hpp" +#include "libtorrent/fwd.hpp" + +EXPORT std::shared_ptr generate_torrent(bool with_files = false); + +EXPORT int print_failures(); + +EXPORT int load_file(std::string const& filename, std::vector& v + , lt::error_code& ec, int limit = 8000000); + +EXPORT void report_failure(char const* err, char const* file, int line); + +EXPORT void init_rand_address(); +EXPORT lt::address rand_v4(); +EXPORT lt::address rand_v6(); +EXPORT lt::tcp::endpoint rand_tcp_ep(lt::address(&rand_addr)() = rand_v4); +EXPORT lt::udp::endpoint rand_udp_ep(lt::address(&rand_addr)() = rand_v4); + +EXPORT lt::sha1_hash rand_hash(); +EXPORT lt::sha1_hash to_hash(char const* s); + +EXPORT std::map get_counters(lt::session& s); + +enum class pop_alerts { pop_all, cache_alerts }; + +EXPORT lt::alert const* wait_for_alert( + lt::session& ses, int type, char const* name = "" + , pop_alerts const p = pop_alerts::pop_all + , lt::time_duration timeout = lt::seconds(10)); + +EXPORT void print_ses_rate(float time + , lt::torrent_status const* st1 + , lt::torrent_status const* st2 + , lt::torrent_status const* st3 = nullptr); + +EXPORT bool print_alerts(lt::session& ses, char const* name + , bool allow_no_torrents = false + , bool allow_failed_fastresume = false + , std::function predicate + = std::function() + , bool no_output = false); + +EXPORT void wait_for_listen(lt::session& ses, char const* name); +EXPORT void wait_for_downloading(lt::session& ses, char const* name); + +EXPORT std::vector generate_piece(lt::piece_index_t idx, int piece_size = 0x4000); +EXPORT lt::file_storage make_file_storage(lt::span file_sizes + , int const piece_size, std::string base_name = "test_dir-"); +EXPORT std::shared_ptr make_torrent(lt::span file_sizes + , int piece_size); +EXPORT void create_random_files(std::string const& path, lt::span file_sizes + , libtorrent::file_storage* fs = nullptr); + +EXPORT std::shared_ptr create_torrent(std::ostream* file = nullptr + , char const* name = "temporary", int piece_size = 16 * 1024, int num_pieces = 13 + , bool add_tracker = true, std::string ssl_certificate = ""); + +EXPORT std::tuple +setup_transfer(lt::session* ses1, lt::session* ses2 + , lt::session* ses3, bool clear_files, bool use_metadata_transfer = true + , bool connect = true, std::string suffix = "", int piece_size = 16 * 1024 + , std::shared_ptr* torrent = nullptr + , bool super_seeding = false + , lt::add_torrent_params const* p = nullptr + , bool stop_lsd = true, bool use_ssl_ports = false + , std::shared_ptr* torrent2 = nullptr); + +EXPORT int start_web_server(bool ssl = false, bool chunked = false + , bool keepalive = true, int min_interval = 30); + +EXPORT void stop_web_server(); +EXPORT int start_proxy(int type); +EXPORT void stop_proxy(int port); +EXPORT void stop_all_proxies(); + +EXPORT lt::tcp::endpoint ep(char const* ip, int port); +EXPORT lt::udp::endpoint uep(char const* ip, int port); +EXPORT lt::address addr(char const* ip); +EXPORT lt::address_v4 addr4(char const* ip); +EXPORT lt::address_v6 addr6(char const* ip); + +#endif diff --git a/test/socks.py b/test/socks.py new file mode 100755 index 0000000..7a29473 --- /dev/null +++ b/test/socks.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 + +"""Minimal non-feature complete socks proxy""" + +import socket +from struct import pack, unpack +import threading +import sys +import traceback + +from socketserver import StreamRequestHandler, ThreadingTCPServer + + +def debug(s): + print('socks.py: ', s) + sys.stdout.flush() + + +def error(s): + print('socks.py, ERROR: ', s) + sys.stdout.flush() + + +class MyTCPServer(ThreadingTCPServer): + allow_reuse_address = True + + def handle_timeout(self): + raise Exception('socks.py: timeout') + + +CLOSE = object() + +VERSION = b'\x05' +NOAUTH = b'\x00' +USERPASS = b'\x02' +CONNECT = b'\x01' +UDP_ASSOCIATE = b'\x03' +IPV4 = b'\x01' +IPV6 = b'\x04' +DOMAIN_NAME = b'\x03' +SUCCESS = b'\x00' + +password = None +username = None +allow_v4 = False + + +def send(dest, msg): + if msg == CLOSE: + try: + dest.shutdown(socket.SHUT_WR) + except Exception: + pass + dest.close() + return 0 + else: + return dest.sendall(msg) + + +def recv(source, n): + data = source.recv(n) + if data == b'': + return CLOSE + else: + return data + + +def forward(source, dest, name): + while True: + data = recv(source, 4000) + if data == CLOSE: + send(dest, CLOSE) + debug('%s hung up' % name) + return +# debug('Forwarding (%d) %r' % (len(data), data)) + send(dest, data) + + +def spawn_forwarder(source, dest, name): + t = threading.Thread(target=forward, args=(source, dest, name)) + t.daemon = True + t.start() + + +class SocksHandler(StreamRequestHandler): + """Highly feature incomplete SOCKS 5 implementation""" + + def close_request(self): + self.server.close_request(self.request) + + def read(self, n): + data = b'' + while len(data) < n: + extra = self.rfile.read(n) + if extra == b'': + raise Exception('Connection closed') + data += extra + return data + + def handle(self): + try: + self.inner_handle() + except Exception: + error("Unhandled exception") + traceback.print_exc(file=sys.stdout) + sys.stdout.flush() + + def inner_handle(self): + # IMRPOVEMENT: Report who requests are from in logging + # IMPROVEMENT: Timeout on client + debug('Connection - authenticating') + version = self.read(1) + + if allow_v4 and version == b'\x04': + cmd = self.read(1) + if cmd != CONNECT and cmd != UDP_ASSOCIATE: + error('Only supports connect and udp-associate method not (%r) closing' % cmd) + self.close_request() + return + + raw_dest_port = self.read(2) + dest_port, = unpack('>H', raw_dest_port) + + raw_dest_address = self.read(4) + dest_address = '.'.join(map(str, unpack('>4B', raw_dest_address))) + + user_id = b'' + c = self.read(1) + while c != b'\0': + user_id += c + c = self.read(1) + + outbound_sock = socket.socket(socket.AF_INET) + out_address = socket.getaddrinfo(dest_address, dest_port)[0][4] + debug("Creating forwarder connection to %s:%d" % (out_address[0], out_address[1])) + outbound_sock.connect(out_address) + + self.send_reply_v4(outbound_sock.getsockname()) + + spawn_forwarder(outbound_sock, self.request, 'destination') + forward(self.request, outbound_sock, 'client') + return + + if version != b'\x05': + error('Wrong version number (%r) closing...' % version) + self.close_request() + return + + nmethods = ord(self.read(1)) + method_list = self.read(nmethods) + + global password + global username + + if password is None and NOAUTH in method_list: + self.send_no_auth_method() + debug('Authenticated (no-auth)') + elif USERPASS in method_list: + self.send_user_pass_auth_method() + auth_version = self.read(1) + if auth_version != b'\x01': + error('Wrong sub-negotiation version number (%r) closing...' % version) + self.close_request() + return + usr_len = ord(self.read(1)) + usr_name = self.read(usr_len) + pwd_len = ord(self.read(1)) + pwd = self.read(pwd_len) + + if usr_name != username.encode() or pwd != password.encode(): + error('Invalid username or password') + self.close_request() + return + debug('Authenticated (user/password)') + self.send_authenticated() + else: + error('Server only supports NOAUTH and user/pass') + self.send_no_method() + return + + # If we were authenticating it would go here + version = self.read(1) + cmd = self.read(1) + zero = self.read(1) + address_type = self.read(1) + if version != b'\x05': + error('Wrong version number (%r) closing...' % version) + self.close_request() + elif cmd != CONNECT and cmd != UDP_ASSOCIATE: + error('Only supports connect and udp-associate method not (%r) closing' % cmd) + self.close_request() + elif zero != b'\x00': + error('Mangled request. Reserved field (%r) is not null' % zero) + self.close_request() + + if address_type == IPV4: + raw_dest_address = self.read(4) + dest_address = '.'.join(map(str, unpack('>4B', raw_dest_address))) + elif address_type == IPV6: + raw_dest_address = self.read(16) + dest_address = ":".join([hex(x)[2:] for x in unpack('>8H', raw_dest_address)]) + elif address_type == DOMAIN_NAME: + dns_length = ord(self.read(1)) + dns_name = self.read(dns_length) + dest_address = dns_name + else: + error('Unknown addressing (%r)' % address_type) + self.close_request() + + raw_dest_port = self.read(2) + dest_port, = unpack('>H', raw_dest_port) + + if address_type == IPV6: + outbound_sock = socket.socket(socket.AF_INET6) + else: + outbound_sock = socket.socket(socket.AF_INET) + try: + out_address = socket.getaddrinfo(dest_address, dest_port)[0][4] + except Exception as e: + error('%s' % e) + traceback.print_exc(file=sys.stdout) + return + + if cmd == UDP_ASSOCIATE: + debug("no UDP support yet, closing") + return + + debug("Creating forwarder connection to %s:%d" % (out_address[0], out_address[1])) + + try: + outbound_sock.connect(out_address) + except Exception as e: + error('%s' % e) + traceback.print_exc(file=sys.stdout) + return + + if address_type == IPV6: + self.send_reply6(outbound_sock.getsockname()) + else: + self.send_reply(outbound_sock.getsockname()) + + spawn_forwarder(outbound_sock, self.request, 'destination') + try: + forward(self.request, outbound_sock, 'client') + except Exception as e: + error('%s' % e) + traceback.print_exc(file=sys.stdout) + + def send_reply_v4(self, xxx_todo_changeme): + (bind_addr, bind_port) = xxx_todo_changeme + self.wfile.write(b'\0\x5a\0\0\0\0\0\0') + self.wfile.flush() + + def send_reply(self, xxx_todo_changeme1): + (bind_addr, bind_port) = xxx_todo_changeme1 + bind_tuple = tuple(map(int, bind_addr.split('.'))) + full_address = bind_tuple + (bind_port,) + debug('Setting up forwarding port %r' % (full_address,)) + msg = pack('>cccc4BH', VERSION, SUCCESS, b'\x00', IPV4, *full_address) + self.wfile.write(msg) + + def send_reply6(self, xxx_todo_changeme2): + (bind_addr, bind_port, unused1, unused2) = xxx_todo_changeme2 + bind_tuple = tuple([int(x, 16) for x in bind_addr.split(':')]) + full_address = bind_tuple + (bind_port,) + debug('Setting up forwarding port %r' % (full_address,)) + msg = pack('>cccc8HH', VERSION, SUCCESS, b'\x00', IPV6, *full_address) + self.wfile.write(msg) + + def send_no_method(self): + self.wfile.write(b'\x05\xff') + self.close_request() + + def send_no_auth_method(self): + self.wfile.write(b'\x05\x00') + self.wfile.flush() + + def send_user_pass_auth_method(self): + self.wfile.write(b'\x05\x02') + self.wfile.flush() + + def send_authenticated(self): + self.wfile.write(b'\x01\x00') + self.wfile.flush() + + +if __name__ == '__main__': + + debug('starting socks.py %s' % " ".join(sys.argv)) + debug('python version: %s' % sys.version_info.__str__()) + listen_port = 8002 + i = 1 + while i < len(sys.argv): + if sys.argv[i] == '--username': + username = sys.argv[i + 1] + i += 1 + elif sys.argv[i] == '--password': + password = sys.argv[i + 1] + i += 1 + elif sys.argv[i] == '--port': + listen_port = int(sys.argv[i + 1]) + i += 1 + elif sys.argv[i] == '--allow-v4': + allow_v4 = True + else: + if sys.argv[i] != '--help': + debug('unknown option "%s"' % sys.argv[i]) + print('usage: socks.py [--username --password ] [--port ]') + sys.stdout.flush() + sys.exit(1) + i += 1 + + debug('Listening on port %d...' % listen_port) + server = MyTCPServer(('localhost', listen_port), SocksHandler) + server.timeout = 190 + while True: + server.handle_request() diff --git a/test/ssl/cert_request.pem b/test/ssl/cert_request.pem new file mode 100644 index 0000000..b386c9a --- /dev/null +++ b/test/ssl/cert_request.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBsTCCARoCAQAwXDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEVMBMGA1UEAwwMdGVz +dCB0b3JyZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClM6eDsvQiXVj7 +ZCrWko+PmlJUMBzPjTbDvSfLfw8zGH3OjPJev1lf3JCPRAGu8CRVFLPZg2RZUfLi +9dITBThnTD2T9/5sHeNpxAU0JW7FPETHay+Q4B1abuYJ+nPmdLbdaiegQjJgoAcH +GlYi6YPNM5n5B8y9vA+k3DOFTAXnLwIDAQABoBUwEwYJKoZIhvcNAQkHMQYMBHRl +c3QwDQYJKoZIhvcNAQEFBQADgYEAJuhp3OEKPqYfF2pz/BooTU5cu/bcgy21M6IM +rEzQSIBO5bNxetK3VlVaclC+QlKMDN5efHw1PiAZPXGGD3BfZyNtf1xEH7urCDqy +McGog59bL/wOwUqGwq5iq18e05XPyrc6JgZUwjKLFu3lSP6FkW0z8c1dbLrMduL+ +M9dvHdk= +-----END CERTIFICATE REQUEST----- diff --git a/test/ssl/dhparams.pem b/test/ssl/dhparams.pem new file mode 100644 index 0000000..7aadb71 --- /dev/null +++ b/test/ssl/dhparams.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEArlE2duUZ0gTqEWzwGywNnwy9DKxoYg8sN/qqkWoRPGAbOfIRczQa +E4egyVamf6grYdZ2cA3yo1s4gmHzp3AJdou6iU4D0Uuka/hCtS9qIANPolFw0pUL +jX+N0ut5jqA8zmDDPx7eyjwKVz7yRsjak4l0pPuj1042vOKM8aFHcZf68731Xlhq +9dNIYK6N67gwSICDFFy4+mTAsH7OSpOw4NFi2+pXsH7aHBXLmk0BcV51NYYf3qFQ +TuoxFUX4qQyip+uJguFrCv1RvXHKpWbm8ThgP3Yv3PpA2FbjnQosHeLp1Soq/vgP +NgDyDb3l8+pW1gmPDZ9O/og6bJruECmK2hTQ+TIw4FI7TGcN1k8XFK1jnFIZxRPN +I3Nbcu0BJtApF7h8GG5dKIBVU3JXJtnqidZh8+kovwOE6FcV8ys8yAg3NuviHw2L +CBQJGOUW15bhN4GXTPjNhK2BcMNu40coRdKjDhknxJjsMSpNMxruKbMlJICFngg+ +NE3SCr8vNvYquOv5Ztr7YHyufBmRWEfmZ9dkASCHZgGsVWf3rOcIbqchjdw/qqA4 +7h9QLo4JSk9/QLNtLly/HJGdwN2+LHKdhcgBW2B3lrmKxP4uTEYXpEes6owWL0Bz +ojV60n782g4soueXjhgiv1qPGAkZnOTpbcVQVHSdpH9s/vKen/LVgcsCAQI= +-----END DH PARAMETERS----- diff --git a/test/ssl/invalid_peer_certificate.pem b/test/ssl/invalid_peer_certificate.pem new file mode 100644 index 0000000..d78cabd --- /dev/null +++ b/test/ssl/invalid_peer_certificate.pem @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 77:43:23:01:98:f8:32:24:72:c9:71:f2:86:1e:e3:3e:59:b8:d3:33 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test + Validity + Not Before: Nov 3 12:58:08 2019 GMT + Not After : Nov 2 12:58:08 2020 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=* + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:c7:a5:3d:f6:73:69:02:2b:97:a6:9d:8a:31:73: + 82:23:08:fb:df:bd:b5:e2:e3:43:7a:c3:3f:65:a1: + 72:50:e1:34:8a:30:b4:69:a9:b1:f7:d1:67:eb:53: + 0f:6b:01:2c:09:98:0c:55:09:1d:7d:3a:9e:86:ac: + f8:99:55:0c:5b:38:18:73:da:c9:19:e7:47:e3:ad: + 57:3e:90:2c:a9:11:73:2b:b9:ee:5a:a4:f0:58:57: + 13:83:33:f8:9d:de:c3:3b:a4:2c:be:a8:95:33:ac: + d2:08:b4:c6:f4:0f:39:5c:cd:4a:b4:f9:91:0b:fd: + 3d:8c:c1:13:bc:b2:09:fc:cc:cb:43:15:51:7e:f5: + 99:02:07:cb:89:3b:c0:0c:ae:92:5d:7e:3b:9e:18: + d0:48:2b:ad:94:ca:88:85:b1:d8:49:ba:cd:3f:a8: + 69:c7:40:12:50:40:e1:a1:11:e0:62:c0:be:e8:93: + 6e:35:08:c6:d4:b9:2e:4e:70:f1:59:76:32:7f:89: + ca:f1:b5:1c:54:c5:bb:09:47:5d:6a:19:9c:8f:86: + 10:5f:ee:e3:7d:07:1d:43:56:90:8c:57:b2:94:02: + 13:9d:34:33:e5:f6:84:c4:4c:99:d6:3e:28:d7:6b: + 89:12:a4:8b:f9:c2:79:35:63:80:bc:b5:e1:cf:5e: + b2:0d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + FE:32:16:2B:5C:D7:3D:BA:11:FD:33:65:F2:2E:D3:F6:B0:DE:C9:4B + X509v3 Authority Key Identifier: + keyid:F0:EA:C4:93:7C:DA:4E:3A:D6:4F:FD:F0:0B:60:B8:B4:3C:FB:BA:2B + + Signature Algorithm: sha256WithRSAEncryption + a2:56:19:c7:12:94:71:af:54:fe:c6:5f:07:da:84:e4:b4:92: + 02:95:1e:26:b2:9a:ae:7f:3d:d8:11:cb:ff:db:e6:f3:88:5e: + 8b:ba:ce:44:ff:77:40:1a:a8:9f:0e:bb:37:63:c2:53:8b:a4: + 29:1e:a0:07:39:7f:5e:ea:91:2e:a8:3c:f0:dc:66:c6:e7:0d: + 90:80:ec:b9:06:a4:ef:2a:0e:d6:9b:e2:dc:cf:16:24:b1:05: + 66:08:70:6d:c3:fa:87:1d:1e:7b:eb:9a:3f:03:5f:73:c4:0e: + 62:7d:a9:5e:11:d4:1b:b3:1e:72:68:a5:55:a6:4c:48:5d:f6: + c1:4f:75:b0:4e:09:d4:a8:c4:19:bf:7d:72:a7:3c:d5:08:f7: + ab:93:9d:e7:b4:4f:52:3f:23:3e:a3:50:18:aa:e1:22:3b:92: + f9:b6:a2:2d:12:04:5a:49:3e:58:f7:15:c5:6f:93:ba:a1:c8: + 04:fe:67:37:49:13:cf:39:0a:91:12:b6:61:b8:ac:b4:f6:b4: + c0:4f:f8:05:f1:39:18:f3:cd:87:f6:76:dc:7b:3c:d7:eb:4a: + 2d:a7:e5:b5:62:f5:a4:3e:d6:55:c7:38:6f:e3:12:c4:af:ad: + c4:83:1c:45:64:a2:18:62:c3:62:c4:f4:1a:47:ed:8c:53:93: + d9:8a:81:b3 +-----BEGIN CERTIFICATE----- +MIIDrjCCApagAwIBAgIUd0MjAZj4MiRyyXHyhh7jPlm40zMwDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDENMAsGA1UEAwwEdGVzdDAeFw0xOTEx +MDMxMjU4MDhaFw0yMDExMDIxMjU4MDhaMFExCzAJBgNVBAYTAkFVMRMwEQYDVQQI +DApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx +CjAIBgNVBAMMASowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHpT32 +c2kCK5emnYoxc4IjCPvfvbXi40N6wz9loXJQ4TSKMLRpqbH30WfrUw9rASwJmAxV +CR19Op6GrPiZVQxbOBhz2skZ50fjrVc+kCypEXMrue5apPBYVxODM/id3sM7pCy+ +qJUzrNIItMb0DzlczUq0+ZEL/T2MwRO8sgn8zMtDFVF+9ZkCB8uJO8AMrpJdfjue +GNBIK62UyoiFsdhJus0/qGnHQBJQQOGhEeBiwL7ok241CMbUuS5OcPFZdjJ/icrx +tRxUxbsJR11qGZyPhhBf7uN9Bx1DVpCMV7KUAhOdNDPl9oTETJnWPijXa4kSpIv5 +wnk1Y4C8teHPXrINAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBT+MhYrXNc9 +uhH9M2XyLtP2sN7JSzAfBgNVHSMEGDAWgBTw6sSTfNpOOtZP/fALYLi0PPu6KzAN +BgkqhkiG9w0BAQsFAAOCAQEAolYZxxKUca9U/sZfB9qE5LSSApUeJrKarn892BHL +/9vm84hei7rORP93QBqonw67N2PCU4ukKR6gBzl/XuqRLqg88NxmxucNkIDsuQak +7yoO1pvi3M8WJLEFZghwbcP6hx0ee+uaPwNfc8QOYn2pXhHUG7MecmilVaZMSF32 +wU91sE4J1KjEGb99cqc81Qj3q5Od57RPUj8jPqNQGKrhIjuS+baiLRIEWkk+WPcV +xW+TuqHIBP5nN0kTzzkKkRK2YbistPa0wE/4BfE5GPPNh/Z23Hs81+tKLafltWL1 +pD7WVcc4b+MSxK+txIMcRWSiGGLDYsT0GkftjFOT2YqBsw== +-----END CERTIFICATE----- diff --git a/test/ssl/invalid_peer_private_key.pem b/test/ssl/invalid_peer_private_key.pem new file mode 100644 index 0000000..3ef7e3e --- /dev/null +++ b/test/ssl/invalid_peer_private_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI1s+ba8HLEMICAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECHfQOovsexxPBIIEyG6tu/yqWU8E +VxjibEwwlEOp1igSWfm3FMe45zwsmjpsgLyFdc6nWab+Y1c1MS/+6Jc74F8HyPuj +Yja19NKUZg4oQanwwi2xGqU54LdcWotOcyOfQPXRnHJ/qUOCFq4EKxr5pgriQrEb +iCFQonAoxMfAVlcI2a+qHq2UxK4Te/P4y+KKee2D7XUAe8dZu9tp2+j+2fz94Cgw +0xn1LdO59F8xi512LfLGBtLJBS/RA1DBCw9n1/P+dkGmA2AxTv/mbWtcHCfP0O9r +Xot5McmQWJ78iPV+TH5s+x+48/IKzxPOfj5DB2p+MxDegv2uzC2cLAzR/pxA8JuY +mN2ChvPXdV7ZMSgN+X9IG7Lq/8v2bcJkJlPBdEOUpPDQtkolqg3LhDm4Ex2DKD9i +47PTKMZzbzMH94rSJYtOMGbkhi8s2Ib+8mJwrkHFuuHKMizNoE3t41C8nxfT2Unq +qs2Br7+bK6iSJ4JQ8Z/Tic3eOborooJ5+FrwkN2nHrHvTQyVdMWc/YpQ7vG2y7aP +s9Im4B37BlB8k+rVByNk+A6fOTNAvTEytoAAwMXVioqNbxB+oyBakEQ6M7cu3WJ5 +FukA7stXwdbJ2D3BvJS7YfzQNccJWqQx6G9xmzH+XkqXlhb/xNiqXieqxAlQeIuh +1nxlhYZCgxPsD21HQeDXytWrblJd3uKZ0T2NZFsvtdnetscv9/Mgx99gga/lcPrN +tiJ1piayLqNUOsSQyq3YqAFj/++BbJH52LYEeugct6CGx6zPzjWXaT/bo2fVwqNV +U6HxsclqnN//Zyz5CxPpBosdavlZ3Rrb60edT9uoLosdiB35XR9BQenmbvYtADFN +sOAd2DI7Nh+LaijxeNz745hPF6qot01VjR2QbQWsr/FnLTSQ4fZC5Z4IOi6o2br9 +qQIozNwNXBR0p/s6MkSNvug9U++m83Zj8h5lDa4Jp+J6x2i8w8adiJOK2d2AufTi +eQRPWZ6BF/73AfS/FJ/7MM4JHY6CQHiokbO6Nel2KOXBbjFQfiy9rWxAv27fc+8h +ev4fhef1KTtLAkGGrR1RFOA3UWywyCQzmm5dgUgEA14Zr9UJRzWcy87wjoT4vvSQ +QY0ajQOkOve4NPxtsCVhF4FScG4ULMpDoG6N6sayBIY0YktwV16mFCZ7qW8U85p0 +wqCS2Nno/22dAQyMIAJO+tCaARMjUDt0CM7t6Jx0KRRqpNJ/88QTkEgl4KbBEEZ2 +q/P+sKLmaau/GAdRK+EDIVV1kH47vzQr/yomenWNn8c0iaIenjHRpgb5MWUvr96p +5es7KtnT9dKP5mqSxfQ0ZbHBDJ4rK4k36UlQ/H6RylxSHQLIJHQ0IkMmfD4Ioof6 +UhOomD5m7G5TYjROO5W1UvAb/iGM5AUyVgQIceiZEPVvNcEDwcyIyLztqsVX8rWO +HQ9dwQEXVrrpX91gtrV+3qxDQuposQWHPIM+dT3P5iE/Qzy4k7vmHPgKFdXIMn7T +Uc/H8SCxcUCYAXb6b4Df4fd0TOlLRxPa1HNlZJEHsroma0NPGlAUFtJxMJLYXiXG +Gn3nrY/3WRhlXSYiy9QkKF5A3XOpLS7vDd7pQ0d11kDKF6Wt0oqwyEkn4rwQiSZG +jO6Gsw/gto4wXCyTTgBCDw== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/ssl/peer_certificate.pem b/test/ssl/peer_certificate.pem new file mode 100644 index 0000000..d575c70 --- /dev/null +++ b/test/ssl/peer_certificate.pem @@ -0,0 +1,82 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 2a:3b:39:ec:ad:1b:1c:d7:e1:c2:4b:2c:ab:35:5c:3e:1b:5a:33:ca + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test + Validity + Not Before: Jun 11 09:20:44 2020 GMT + Not After : Jun 11 09:20:44 2021 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=* + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:c3:f5:9e:4c:d0:27:c9:93:02:1a:00:5c:cc:d2: + db:73:b9:cb:75:35:86:ee:60:17:b3:6d:2c:b1:4e: + c7:a3:e2:9d:5b:c2:65:9b:e0:f4:3d:65:13:68:ee: + 87:be:58:1a:ac:2c:4a:d9:59:61:1c:a9:37:2c:3f: + f1:3c:dc:4e:ab:6b:6d:0e:2e:6c:b6:d8:6e:79:99: + b4:28:d0:e7:1a:05:f7:5d:90:2b:6f:2a:a3:88:fe: + 0a:17:e7:e0:47:23:69:92:4d:37:96:c1:c0:db:91: + 7c:9c:42:66:05:4f:ef:fa:a5:87:1d:51:e4:7f:16: + b3:d5:6d:32:f0:46:b5:e8:84:bc:2f:29:c2:2b:8e: + e9:b9:04:be:22:93:7f:a3:ae:d2:f1:6d:96:14:c2: + a8:65:7c:72:8f:1e:bf:1c:17:3a:0b:4f:97:d4:7c: + ed:80:2c:85:23:f6:8f:99:ed:09:5c:df:51:7d:fd: + 9c:95:98:45:9e:9f:f2:4e:38:3e:32:c2:0e:63:d4: + ae:56:a8:40:af:0b:e0:72:2d:c0:00:7a:e2:5c:72: + 0d:45:9e:10:d6:a9:76:aa:80:b7:f0:a5:c5:1d:80: + 2c:94:38:70:c5:2c:6f:5e:68:43:9a:7c:58:73:45: + e8:61:43:c1:23:ab:44:f8:e3:14:a4:22:93:6b:f2: + c1:23 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + DA:09:C5:F5:19:AD:F1:93:60:D6:72:9D:7D:8B:25:E9:D0:21:EE:50 + X509v3 Authority Key Identifier: + keyid:48:61:63:44:F6:1D:B2:0C:3D:C9:9F:0E:50:A0:53:46:53:C1:B0:76 + + Signature Algorithm: sha256WithRSAEncryption + 8d:81:a4:c6:fe:79:61:96:cf:48:28:63:f9:1a:bc:04:0e:92: + e8:c2:0c:ad:f9:12:9c:ac:e7:fa:b6:fe:96:64:4f:9d:40:3e: + 82:58:4a:f4:1e:a9:e5:ae:08:13:82:ed:09:c0:ed:52:aa:29: + 49:c9:b1:9e:c6:e6:2c:6d:c2:0d:11:4b:57:05:60:ef:2b:7a: + ea:d3:86:d4:55:12:11:e6:42:e4:ea:44:10:9a:2d:1d:e2:2b: + 1c:29:b4:45:5a:9a:a6:5d:a4:75:ec:b7:f0:eb:c3:31:2b:47: + 4b:55:6e:b9:65:4d:1f:f9:4c:37:7e:ea:4a:d4:8f:42:20:38: + 99:4c:9a:1a:39:7d:c1:68:67:9d:a9:e4:ba:d7:14:d2:00:91: + 57:f8:ec:85:e6:83:0f:f6:58:0f:25:96:ce:c9:52:15:a8:b1: + 92:5f:f3:43:eb:a2:3d:3c:15:29:80:80:12:f2:b2:fd:9b:e0: + 9a:86:3d:59:42:e2:8b:fd:e0:c7:3b:99:32:d3:08:54:b8:d0: + 39:02:6f:9e:ca:e9:52:85:8e:0a:72:09:37:11:59:76:69:aa: + 9d:99:0f:0f:a3:fe:55:80:b5:74:74:89:7d:fa:cf:35:f5:4c: + 50:6c:96:96:88:87:9a:5f:7b:b3:29:ea:0c:f0:5d:69:fd:40: + 66:61:61:57 +-----BEGIN CERTIFICATE----- +MIIDrjCCApagAwIBAgIUKjs57K0bHNfhwkssqzVcPhtaM8owDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDENMAsGA1UEAwwEdGVzdDAeFw0yMDA2 +MTEwOTIwNDRaFw0yMTA2MTEwOTIwNDRaMFExCzAJBgNVBAYTAkFVMRMwEQYDVQQI +DApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx +CjAIBgNVBAMMASowggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD9Z5M +0CfJkwIaAFzM0ttzuct1NYbuYBezbSyxTsej4p1bwmWb4PQ9ZRNo7oe+WBqsLErZ +WWEcqTcsP/E83E6ra20OLmy22G55mbQo0OcaBfddkCtvKqOI/goX5+BHI2mSTTeW +wcDbkXycQmYFT+/6pYcdUeR/FrPVbTLwRrXohLwvKcIrjum5BL4ik3+jrtLxbZYU +wqhlfHKPHr8cFzoLT5fUfO2ALIUj9o+Z7Qlc31F9/ZyVmEWen/JOOD4ywg5j1K5W +qECvC+ByLcAAeuJccg1FnhDWqXaqgLfwpcUdgCyUOHDFLG9eaEOafFhzRehhQ8Ej +q0T44xSkIpNr8sEjAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8W +HU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTaCcX1Ga3x +k2DWcp19iyXp0CHuUDAfBgNVHSMEGDAWgBRIYWNE9h2yDD3Jnw5QoFNGU8GwdjAN +BgkqhkiG9w0BAQsFAAOCAQEAjYGkxv55YZbPSChj+Rq8BA6S6MIMrfkSnKzn+rb+ +lmRPnUA+glhK9B6p5a4IE4LtCcDtUqopScmxnsbmLG3CDRFLVwVg7yt66tOG1FUS +EeZC5OpEEJotHeIrHCm0RVqapl2kdey38OvDMStHS1VuuWVNH/lMN37qStSPQiA4 +mUyaGjl9wWhnnankutcU0gCRV/jsheaDD/ZYDyWWzslSFaixkl/zQ+uiPTwVKYCA +EvKy/ZvgmoY9WULii/3gxzuZMtMIVLjQOQJvnsrpUoWOCnIJNxFZdmmqnZkPD6P+ +VYC1dHSJffrPNfVMUGyWloiHml97synqDPBdaf1AZmFhVw== +-----END CERTIFICATE----- diff --git a/test/ssl/peer_private_key.pem b/test/ssl/peer_private_key.pem new file mode 100644 index 0000000..226751c --- /dev/null +++ b/test/ssl/peer_private_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIMGZVYcqHMkcCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECMLdhALGDat0BIIEyDcsKyo8Leyv +mHXIYaPV/JVIUonbDouOb1jMI04Ph+b0YTx+cd7+3WPN7Qvtmf7JsYsee71VHint +fuP5wCi3oGFjm8T4t6WXKWOraJUEE/nZdzK5t1BFsQA4UMI/eczwt4nesiNrHVsL +8fGRm6wsYl+LbORXYIDEiWy6YkWgEZcggp4Tf1NCsdN4rLc4fmJiPoVrfWPBtmFm +0sKJe/VUNbEnjg7d5zhGn773QLGLuR0h7klKFyW3TJiYOD9Bp9gTKQPfmM64l/zl +l2ZZUlzp5pThdBv/YBoFT60fvlE8BL1plv0fIhNfN3g5ozJ0b6ZXkeK8A10wPf4V +11Imo3HbkbUz6uf4/wWG5bZfXV90q2Z92vfLLGnvZB9tfK1e9gH0RYLCaF7uSXJ8 +0nhFqtB6otfkuVX3aIBq5r9dQ9WeMXcTmf1pgYLkiTJLtirp4evYCYGovumgag2a +oby1MuhoESNSBpvp8aFziwBu0DKpQzvnOACYpNUr9jrMF4/4WPwxSIuF4xMgI327 +xUpTiYrBe9ErwO1qq37y6ozmtxYQHgtCE5ST/ip0tC/ypgOyfLsj0TyU76JS7wp0 +FmxoVpdYuCT30ZzmsVWqz4NeiFbiCOOdF9lbuEPCZmn7TCqnZPVaVfsXtEdy14MI +dMn3iKaeFtfM8CdnlJVMH3MD0Rald9lJOqD+TI4NrgQNSv87ro8QkAo9u853Oh8P +7SBeEpcLXfh1oC07SahE3OfcIV9TTogQZCdqzSrlb9357yMOby90SuX8N7em+/oh +70BVp1bcVj0ya7lFMEhkVkEXMJTcm/bOaCBsEhrld9ECWxTjDv/jPde1OMqOHAxX +m5YO58lS/ZuFjavmnXfTUqAnsfewR8E3QBc83m3PglktERBJ050OWnJ+V7SDkmOb +PwW29l4PEom5HIHpZi6ZUKkqL2jt1m3WKaDMkjoFXPBUbsmmQMtn6pVJYaJ4LEBS +BmvUXhysYZDEvzsbDAh1v7UZta7+8neqx8+wPxoTtQZE+3iu2IKtuaLj2GvYMqW+ +w9RUV4bmgRFNtffIimbG987o4mj1yFzXKSQpNxxMQDHd+RW5JnDBkpPNJCiLLcpg +AfR+3cobvgthdgKIV32aMSBV1QBC1LwbZFkVb89SZ08nUI+DATrZI6AKImEGZd6B +jq+MSopib2M3HK9fQBiR222Bdcn/H2hFoxv09PgWjiUiq8yc2lJHY2I8zkE1t24O +ROJaynUpSuAfniPifWFghSJloYm0k9mt58StHWytcc20A/xgbErNqwID48ZP6YQq +OeTyMmIDFJBovHPD/gJgIZ344HRI/gmsA75apo6hTc3srKfZf6RpC+oWV8FFsTG2 +I3WEocz7bZ7ZBYY+1QqJJfE4nagsvIDVfLta3IlPjiGmUwHJVYX74O9eQGHX354G +B2Qjg5ocgwgwZDhc1nr/EyCYUiLVGTEp5Ho3thUs7qnFNO/4GrwtgcKAsvbdKlI6 +8z9W09fKcahjtToxKZgAfZN8iAA2Gm9BEY1FjFOUkvTxiNnz0RrWEeNu1yD/2RP4 +HuaqGN3uuQE31GhdsJv4ksDZjBfePLeP2xdTOyQkMFcTkszylRG44YdD397Kk1VJ +r7VMX8xDXGuJ2S1pE/4dOw== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/ssl/regenerate_test_certificate.sh b/test/ssl/regenerate_test_certificate.sh new file mode 100755 index 0000000..ef1308c --- /dev/null +++ b/test/ssl/regenerate_test_certificate.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +set -e + +cp peer_certificate.pem invalid_peer_certificate.pem +cp peer_private_key.pem invalid_peer_private_key.pem + +printf "\n\n ======== \e[33muse common name: * \e[0m=========\n\n" + +openssl req -newkey rsa:4096 -nodes -keyout server_key.pem -x509 -days 1097 -out server_cert.pem +cat server_key.pem server_cert.pem >server.pem + +PATH=$PATH:/usr/local/etc/openssl/misc:/usr/lib/ssl/misc + +rm -rf demoCA + +printf "\n\n ======== \e[33muse passphrase: \"test\" \e[0m=========\n\n" +printf "\n\n ======== \e[33muse common name: \"test\" \e[0m=========\n\n" + +CA.pl -newca -extra-cmd rsa:4096 + +cp ./demoCA/cacert.pem root_ca_cert.pem +cp ./demoCA/private/cakey.pem root_ca_private.pem + +printf "\n\n ======== \e[33muse passphrase: \"test\" \e[0m=========\n\n" +printf "\n\n ======== \e[33muse common name: * \e[0m=========\n\n" + +CA.pl -newreq -extra-cmd rsa:4096 + +cp newkey.pem peer_private_key.pem + +CA.pl -sign + +cp newcert.pem peer_certificate.pem + +openssl dhparam -outform PEM -out dhparams.pem 4096 + +printf "\n\nSUCCESS!\n" + diff --git a/test/ssl/root_ca_cert.pem b/test/ssl/root_ca_cert.pem new file mode 100644 index 0000000..f7c2bba --- /dev/null +++ b/test/ssl/root_ca_cert.pem @@ -0,0 +1,79 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 2a:3b:39:ec:ad:1b:1c:d7:e1:c2:4b:2c:ab:35:5c:3e:1b:5a:33:c9 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test + Validity + Not Before: Jun 11 09:20:10 2020 GMT + Not After : Jun 11 09:20:10 2023 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=test + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:9b:75:16:48:ff:5e:c2:d1:f6:cc:db:a4:24:d7: + e6:98:97:e5:3c:b8:4d:8c:f3:c6:41:79:fc:3f:e9: + b8:63:a9:a1:f6:f4:e6:10:ee:15:44:d7:65:bc:3e: + 22:9e:84:cc:00:86:e0:08:6f:fd:98:1f:6b:9d:e1: + e4:ee:4a:ee:08:7a:fb:bb:65:01:d6:33:97:3c:8f: + a1:a2:3b:10:88:b9:66:b0:ec:ab:bd:aa:9e:84:7d: + e3:a0:a7:c7:dc:03:70:52:a6:1c:04:cd:9e:e4:27: + f8:2a:39:99:f6:1c:73:87:ca:31:32:c7:c6:65:ad: + 1d:94:77:f8:6a:89:1b:af:5f:d8:e5:cd:7b:9f:80: + 17:4a:72:b1:f9:2b:89:1b:df:52:85:7e:57:6a:37: + e7:b7:5b:4b:c8:c6:d4:d4:ca:85:ab:0c:0f:53:01: + 89:2e:bc:24:d5:a3:e9:28:3b:7f:b0:4c:dd:53:35: + a6:8c:4c:e1:00:47:e1:1e:2c:ad:e1:c7:85:18:46: + 1e:83:64:c7:45:71:64:31:9e:cd:34:e3:41:6e:a8: + b7:9d:52:6a:ba:51:30:21:d3:c4:f2:a3:9a:d1:09: + 2b:81:32:ef:c2:a0:b6:68:c9:43:93:b3:44:59:0e: + 42:6c:04:79:97:b8:b6:33:3c:34:56:f8:e5:8e:38: + 98:15 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 48:61:63:44:F6:1D:B2:0C:3D:C9:9F:0E:50:A0:53:46:53:C1:B0:76 + X509v3 Authority Key Identifier: + keyid:48:61:63:44:F6:1D:B2:0C:3D:C9:9F:0E:50:A0:53:46:53:C1:B0:76 + + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + 13:76:0c:18:d2:b7:d8:95:a1:4a:da:8f:8d:fb:92:8d:0a:d8: + d3:6e:98:97:85:0b:29:fc:94:0f:75:71:b3:98:cf:19:a7:11: + c3:19:7b:07:bd:52:1f:58:f2:36:39:f8:f1:6f:a9:ac:f2:88: + 37:98:5d:ad:f8:bc:46:72:f1:aa:93:0c:92:e4:80:54:5e:ac: + 20:3c:f7:56:3a:a3:75:c5:5c:1e:0a:da:5c:55:4c:32:84:10: + ab:a2:b9:db:66:f0:48:74:c6:f5:69:da:f8:b3:52:e3:76:db: + 38:fc:14:65:db:20:af:b6:08:bd:c5:db:60:d4:dd:f2:bd:3a: + 31:24:7c:fc:3b:3a:ed:43:16:69:59:48:0a:d8:ad:d4:0f:dd: + 8d:97:8e:50:0c:7b:60:4e:91:7a:52:87:c9:19:72:3d:6f:62: + 18:45:a2:7c:26:bb:25:0f:81:9c:d7:55:08:18:d0:7e:6b:0e: + 9c:86:4b:51:ce:0e:56:40:ac:d9:6e:4f:84:ca:7b:03:07:cf: + bd:8a:86:0a:a3:85:b0:f0:2d:ac:c4:4e:d7:92:09:15:a3:0d: + 62:77:25:8d:c1:f2:56:de:06:95:e3:7b:2c:57:b7:3c:ef:98: + dc:78:16:c1:95:75:b1:98:1f:4e:d8:30:c3:4c:4c:bb:ed:ed: + b0:3e:ae:f3 +-----BEGIN CERTIFICATE----- +MIIDiTCCAnGgAwIBAgIUKjs57K0bHNfhwkssqzVcPhtaM8kwDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDENMAsGA1UEAwwEdGVzdDAeFw0yMDA2 +MTEwOTIwMTBaFw0yMzA2MTEwOTIwMTBaMFQxCzAJBgNVBAYTAkFVMRMwEQYDVQQI +DApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx +DTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb +dRZI/17C0fbM26Qk1+aYl+U8uE2M88ZBefw/6bhjqaH29OYQ7hVE12W8PiKehMwA +huAIb/2YH2ud4eTuSu4Ievu7ZQHWM5c8j6GiOxCIuWaw7Ku9qp6EfeOgp8fcA3BS +phwEzZ7kJ/gqOZn2HHOHyjEyx8ZlrR2Ud/hqiRuvX9jlzXufgBdKcrH5K4kb31KF +fldqN+e3W0vIxtTUyoWrDA9TAYkuvCTVo+koO3+wTN1TNaaMTOEAR+EeLK3hx4UY +Rh6DZMdFcWQxns0040FuqLedUmq6UTAh08Tyo5rRCSuBMu/CoLZoyUOTs0RZDkJs +BHmXuLYzPDRW+OWOOJgVAgMBAAGjUzBRMB0GA1UdDgQWBBRIYWNE9h2yDD3Jnw5Q +oFNGU8GwdjAfBgNVHSMEGDAWgBRIYWNE9h2yDD3Jnw5QoFNGU8GwdjAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQATdgwY0rfYlaFK2o+N+5KNCtjT +bpiXhQsp/JQPdXGzmM8ZpxHDGXsHvVIfWPI2Ofjxb6ms8og3mF2t+LxGcvGqkwyS +5IBUXqwgPPdWOqN1xVweCtpcVUwyhBCrornbZvBIdMb1adr4s1Ljdts4/BRl2yCv +tgi9xdtg1N3yvToxJHz8OzrtQxZpWUgK2K3UD92Nl45QDHtgTpF6UofJGXI9b2IY +RaJ8JrslD4Gc11UIGNB+aw6chktRzg5WQKzZbk+EynsDB8+9ioYKo4Ww8C2sxE7X +kgkVow1idyWNwfJW3gaV43ssV7c875jceBbBlXWxmB9O2DDDTEy77e2wPq7z +-----END CERTIFICATE----- diff --git a/test/ssl/root_ca_private.pem b/test/ssl/root_ca_private.pem new file mode 100644 index 0000000..839320b --- /dev/null +++ b/test/ssl/root_ca_private.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI/zTjgOm/w6ECAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECCPS3zAIj3X/BIIEyOCNPW47rYWh +EcGYI4xdaj5RE5pQAODUsxBVbKYNIUqsaTztQONXEjePs6BM1R+eE0xM9ZiFgP40 +ScJMbohtcacVohRipbFSSb3menK4naMN5eEkGxOJaFMEOT1uYa9eDygCO+QprqNM +S+MY/a6n7SYz0j90mkljYhBZLR/Xi7WpGITsBUPbcVmuqJYMQOnERj5PmxHw8wWg +anvMNaUZpOLGEunX523ZgcMf+HqZI4gWSiTxL5wG4JWgRRj7foiFAlFAIzyTbguk ++JnuJ6ShfJEhZuOGhQ/Qrsw8/ITzsiLUoixIhSyKbW3WlbHWkH4DFXD8NuZnaKQV +l6F5OPgjaE425GOYt20uxsH/tCCZ2iMtB4pROrLhr2KhUqbSBcWEa4tJN9Q8pizc +9kU+ZT2IstN6i9UzsDnLedyfE1PKU5FL7PPTVwg0C3M/4iiY3aGkqd2cSnKxnK8a +MmrhHPCOAHjwCewp7pBa3m0zPBuVDcQeF3jrRSa4lVndDGiMJ+diMobPNrfciKZ0 +AyeM9j+KfPZsZ8GfTdSjC8MgePmrugaJ870yUb7DDCDpAxjWRE2T3kneLqZY1iGJ +Rk09AIIKth7zAQc5VrLsvslWtn4RcJgSpLmlTxf89sMB6KA/6TAbw5j6Jqp1NHUE +CFC/w9fOeDvmBOvDqSEYXM79kS3JLtGD80zj6MqWT3iIbLpXjbStsGJbpZWuAe73 +99QnJNvDTW+7FB4bRWBhbjeGzNoVPQowS8oB+9epsk+4P0wNCi0SxgvedHheO8Ql +rg9LtZMa+8CmgFpkkZdCYc/cpJVLWCi+i/l4cWU+aujmBFqnGjxa2lorKUwng6gw +Tp3yuPYAJQmWXCL/3fO4++258/V1UIWO2oFqBo7HiJzCJZ9WHtWEmSYi5Bz09Unm +0XckQmD88/f3MHhIjHthrxozkg20pIARsiBkJBQ/cXXwRNPaKtC8gJMHkC8T/Xsb +MjWE/5EsMvoRapRfRAJuQ2XeYL1sM44AydCLpAaO2tay7+s7JMKQjy/zOcgoTL+3 +pON7FhvJVvZf1RlQUkq9m4gNNyWDOkZdCzTuYeULgqvaXw8QbZaAJQ280FuexVIH +saQDv2fDEVwB/q8wCrka3xdRpK7wf5W/xYo9l8U1xMSBdIbS4icJF0gq6u7Da9Ww +DrQceipTFVNeioO3kvX8oFJnZ3bdBr/ifyfWL0IgpFanXDM7iYye+NKflDh6VV3t +SQ+Kb4g3ttFTDCtVRNXFwCatILetq8amIC9/YTyjCSiGAIFcsdGjksOx4E7E4Vy1 +tyUPLoWV5cNxJA1kaB1SNibIaFA2ecyRMGp54a2Hqpq+gNZHtT8lU+WbDQ7byuR+ +yCDh+G+f3oYCMJjqtq9wH3ZNrEge7YsEacNa8qEXPiWhU5rfsxWO67vIWg2Tq2KH +V/gk78EajbFL6yI4+D4CbGhUFbQykUW2PeqzeXUSeT9ffFF9mWNPC0eV9BnXYICb +8adMbw+H9Sd7lewg1bglvjg6q7KK/NFTTuZoLNAepLYN99ezwt0G5VhF7lxxeWfZ +j2G8nJP/Tq8ILAfmi4usMvN9iUg+z4dfr6Q807A3HvjAVzUTWmkYpqjmUYAW8BrR +kwKW1gqSc+BGDprPYsRt9g== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/ssl/server.pem b/test/ssl/server.pem new file mode 100644 index 0000000..2a3faa7 --- /dev/null +++ b/test/ssl/server.pem @@ -0,0 +1,84 @@ +-----BEGIN PRIVATE KEY----- +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 +fzUqIPuq7jM27TpNqCyu+1YAdJfYENw= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIUIoHlGg6C4To4/8285eyVzrYCxYUwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEKMAgGA1UEAwwBKjAeFw0yMDA2MTEw +OTM0MTNaFw0yMzA2MTMwOTM0MTNaMFExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApT +b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCjAI +BgNVBAMMASowggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDI7aFbjoi8 +eCqD9mGu/Bjka1De70rhNwxIZjtdm6/3mAe5Fj0DKh/8jiZAuI0alkXR+8i/FSIN +AUWiedl6eNc5KqM/sOLV1y1XCOqOllcCy/2BQxhG0HvmbvvFl2wRvlk4Xt2jcZ+C +lQb199tL67ztxipITKoi+1yHOKSSltwVQw6daKKK0dP/CQniEhm5wuolJzfaf3Gk +GTrhTC9FG3HNL1B+T5I4ZzR3PulwT9HmuZS5m9OxiKEp9wcAzUSd5pR7IcGRpxOw +RVMvoiOJ6lTPjUo+MDqDMMQ4kseH2FxRl8nNENNa/ZTa5Xbs97OaFbQxOnUlnCJG +xXB8VO9nEAJHCVa1cWCkCQG6f3bnK/AK7B53BDp13zPlFuEZITMsCaDjGb5SZfYQ +AkuPOJS7oes7ammj7w2GvSV1hO8wV6+u51R//EhE9lGq2H1jDEfG7AqgzmuPvIQO +bl46dpM68H4y1mDRVJjtCnG+kKO+A8DtDPDo7b3CjYnIluky1RZjaZXmIpgWx6Hu +B01g4HOeSxmfXRNqL5MvT8dHLtfmxnoCUKG8qR7OAPsaQnRcYSGOXwdvnDLVQyyw +ukNrkoy8NHY0cnjWtl22lwQeOXeN97aDzvgUegYL+Ti0xdwDayCrRzWUstb2RQfJ +eEwxYrxZ5CsoHus4tbTf16d47dQy/XY7lwIDAQABo1MwUTAdBgNVHQ4EFgQUSINR +CWIfUI4fczdRlaZCC0vnV84wHwYDVR0jBBgwFoAUSINRCWIfUI4fczdRlaZCC0vn +V84wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAWsostRR7hK/w +znZoy2fmKLc+lvo8jzroJzFCS0XJ4vkAvsiAQ1rkT0kK+cxHeTZ0j9BK41hV1gBl +C0ZQwqvzosUNnl98qVIkDL2rQyy8k+ciicpY2+gafPfKAYZSYMdBAQS4twBsXZ5B +2febTD7Np94m6jpBDlhhnsV817nrlueEkf2SPqov42LxAT6mNiiMBe2SQ/0UIdXJ +iofPdP81Ztqq/4/pYr9WFzISuMld8GzOon8z7a6pv1pq/lKmGzJyM2QtbnCvosNT +uAww7ytkCSca4+D08/zIdBg7xNBPPScv78jTCg8V+tgjWM0CT0xY15OnKKHwfqig +y4/quJ7hOu5a9e3HZmmb4OQZs4/ARE4pkFCD6D0YY5MtAkW0Ve5UC8HM013SpxW+ +vSsprH1MvBrV72/ZFRWGxlqW+JMAlHyNA1Fp5NJFIJ+qyRr3SmUeMLz0QJrJpsfv +dyxxBfXCrhIybafYVc6mdrTQP9LfHo2IRGe+n5/LgWAwghbmBZPx7h7Ur2E79AWZ +gPBR9u6h7GlxJ9aL4cHboL320er73iheRpgDOkhpiNStdEu2enkUiLvHYhYtCZAH +SCH6DVaBmUwTHy+tK1Z5Ui7Hmknbv8QfEIeZwmvAFShza2JFZxco80kD4w+iKhEL +4Mzvptgh7+xsIcEW5a0rBCIAU/FcKzQ= +-----END CERTIFICATE----- diff --git a/test/swarm_suite.cpp b/test/swarm_suite.cpp new file mode 100644 index 0000000..e057e1e --- /dev/null +++ b/test/swarm_suite.cpp @@ -0,0 +1,237 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/aux_/path.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "swarm_suite.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4706: assignment within conditional expression +#pragma warning( disable : 4706 ) +#endif + +void test_swarm(test_flags_t const flags) +{ + using namespace lt; + + std::printf("\n\n ==== TEST SWARM === %s%s%s%s%s ===\n\n\n" + , (flags & test_flags::super_seeding) ? "super-seeding ": "" + , (flags & test_flags::strict_super_seeding) ? "strict-super-seeding ": "" + , (flags & test_flags::seed_mode) ? "seed-mode ": "" + , (flags & test_flags::time_critical) ? "time-critical ": "" + , (flags & test_flags::suggest) ? "suggest ": "" + ); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_swarm", ec); + remove_all("tmp2_swarm", ec); + remove_all("tmp3_swarm", ec); + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + session_proxy p3; + + settings_pack pack = settings(); + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + +#if TORRENT_ABI_VERSION == 1 + if (flags & test_flags::strict_super_seeding) + pack.set_bool(settings_pack::strict_super_seeding, true); +#endif + + if (flags & test_flags::suggest) + pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + + // this is to avoid everything finish from a single peer + // immediately. To make the swarm actually connect all + // three peers before finishing. + float const rate_limit = 100000; + + int const port = static_cast(lt::random(100)); + char iface[50]; + std::snprintf(iface, sizeof(iface), "0.0.0.0:480%02d", port); + pack.set_int(settings_pack::upload_rate_limit, int(rate_limit)); + pack.set_str(settings_pack::listen_interfaces, iface); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + + lt::session ses1(pack); + + std::snprintf(iface, sizeof(iface), "0.0.0.0:490%02d", port); + pack.set_str(settings_pack::listen_interfaces, iface); + pack.set_int(settings_pack::download_rate_limit, int(rate_limit / 2)); + pack.set_int(settings_pack::upload_rate_limit, int(rate_limit)); + lt::session ses2(pack); + + std::snprintf(iface, sizeof(iface), "0.0.0.0:500%02d", port); + pack.set_str(settings_pack::listen_interfaces, iface); + lt::session ses3(pack); + + torrent_handle tor1; + torrent_handle tor2; + torrent_handle tor3; + + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + if (flags & test_flags::seed_mode) p.flags |= torrent_flags::seed_mode; + // test using piece sizes smaller than 16kB + std::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true + , false, true, "_swarm", 8 * 1024, nullptr, bool(flags & test_flags::super_seeding), &p); + + if (flags & test_flags::time_critical) + { + tor2.set_piece_deadline(piece_index_t(2), 0); + tor2.set_piece_deadline(piece_index_t(5), 1000); + tor2.set_piece_deadline(piece_index_t(8), 2000); + } + + float sum_dl_rate2 = 0.f; + float sum_dl_rate3 = 0.f; + int count_dl_rates2 = 0; + int count_dl_rates3 = 0; + + for (int i = 0; i < 80; ++i) + { + print_alerts(ses1, "ses1"); + print_alerts(ses2, "ses2"); + print_alerts(ses3, "ses3"); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + torrent_status st3 = tor3.status(); + + if (flags & test_flags::super_seeding) + { + TEST_CHECK(st1.is_seeding); + TEST_CHECK(tor1.flags() & torrent_flags::super_seeding); + } + + if (st2.progress < 1.f && st2.progress > 0.5f) + { + sum_dl_rate2 += st2.download_payload_rate; + ++count_dl_rates2; + } + if (st3.progress < 1.f && st3.progress > 0.5f) + { + sum_dl_rate3 += st3.download_rate; + ++count_dl_rates3; + } + + print_ses_rate(float(i), &st1, &st2, &st3); + + if (st2.is_seeding && st3.is_seeding) break; + std::this_thread::sleep_for(lt::milliseconds(1000)); + } + + TEST_CHECK(tor2.status().is_seeding); + TEST_CHECK(tor3.status().is_seeding); + + float average2 = count_dl_rates2 > 0 ? sum_dl_rate2 / float(count_dl_rates2) : -1; + float average3 = count_dl_rates3 > 0 ? sum_dl_rate3 / float(count_dl_rates3) : -1; + + std::cout << "average rate: " << (average2 / 1000.f) << "kB/s - " + << (average3 / 1000.f) << "kB/s" << std::endl; + + if (tor2.status().is_seeding && tor3.status().is_seeding) std::cout << "done\n"; + + // make sure the files are deleted + ses1.remove_torrent(tor1, lt::session::delete_files); + ses2.remove_torrent(tor2, lt::session::delete_files); + ses3.remove_torrent(tor3, lt::session::delete_files); + + alert const* a = wait_for_alert(ses1, torrent_deleted_alert::alert_type, "swarm_suite"); + + TEST_CHECK(alert_cast(a) != nullptr); + + // there shouldn't be any alerts generated from now on + // make sure that the timer in wait_for_alert() works + // this should time out (ret == 0) and it should take + // about 2 seconds + time_point start = clock_type::now(); + alert const* ret; + while ((ret = ses1.wait_for_alert(seconds(2)))) + { + std::printf("wait returned: %d ms\n" + , int(total_milliseconds(clock_type::now() - start))); + std::vector alerts; + ses1.pop_alerts(&alerts); + for (auto ap : alerts) + { + std::printf("%s\n", ap->message().c_str()); + } + start = clock_type::now(); + } + + std::printf("loop returned: %d ms\n" + , int(total_milliseconds(clock_type::now() - start))); + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); + p3 = ses3.abort(); + + time_point const end = clock_type::now(); + + std::printf("time: %d ms\n", int(total_milliseconds(end - start))); + TEST_CHECK(end - start < milliseconds(3000)); + TEST_CHECK(end - start > milliseconds(1900)); + + TEST_CHECK(!exists("tmp1_swarm/temporary")); + TEST_CHECK(!exists("tmp2_swarm/temporary")); + TEST_CHECK(!exists("tmp3_swarm/temporary")); + + remove_all("tmp1_swarm", ec); + remove_all("tmp2_swarm", ec); + remove_all("tmp3_swarm", ec); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/test/swarm_suite.hpp b/test/swarm_suite.hpp new file mode 100644 index 0000000..78243a3 --- /dev/null +++ b/test/swarm_suite.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/flags.hpp" + +using test_flags_t = libtorrent::flags::bitfield_flag; + +namespace test_flags +{ + using libtorrent::operator "" _bit; + constexpr test_flags_t super_seeding = 1_bit; + constexpr test_flags_t strict_super_seeding = 2_bit; + constexpr test_flags_t seed_mode = 3_bit; + constexpr test_flags_t time_critical = 4_bit; + constexpr test_flags_t suggest = 5_bit; +} + +EXPORT void test_swarm(test_flags_t flags = test_flags_t{}); diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..8fcc282 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2008-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +#include "test.hpp" + +unit_test_t _g_unit_tests[1024]; +int _g_num_unit_tests = 0; +int _g_test_failures = 0; // flushed at start of every unit +int _g_test_idx = 0; + +static std::vector failure_strings; + +int test_counter() +{ + return _g_test_idx; +} + +void report_failure(char const* err, char const* file, int line) +{ + char buf[2000]; + std::snprintf(buf, sizeof(buf), "\x1b[41m***** %s:%d \"%s\" *****\x1b[0m\n", file, line, err); + std::printf("\n%s\n", buf); + failure_strings.push_back(buf); + ++_g_test_failures; +} + +int print_failures() +{ + int longest_name = 0; + for (int i = 0; i < _g_num_unit_tests; ++i) + { + int len = int(strlen(_g_unit_tests[i].name)); + if (len > longest_name) longest_name = len; + } + + std::printf("\n\n"); + int total_num_failures = 0; + + for (int i = 0; i < _g_num_unit_tests; ++i) + { + if (_g_unit_tests[i].run == false) continue; + + if (_g_unit_tests[i].num_failures == 0) + { + std::printf("\x1b[32m[%-*s] ***PASS***\n" + , longest_name, _g_unit_tests[i].name); + } + else + { + total_num_failures += _g_unit_tests[i].num_failures; + std::printf("\x1b[31m[%-*s] %d FAILURES\n" + , longest_name + , _g_unit_tests[i].name + , _g_unit_tests[i].num_failures); + } + } + + std::printf("\x1b[0m"); + + if (total_num_failures > 0) + std::printf("\n\n\x1b[41m == %d TEST(S) FAILED ==\x1b[0m\n\n\n" + , total_num_failures); + return total_num_failures; +} + diff --git a/test/test.hpp b/test/test.hpp new file mode 100644 index 0000000..abd1607 --- /dev/null +++ b/test/test.hpp @@ -0,0 +1,204 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TEST_HPP +#define TEST_HPP + +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" + +#include +#include +#include +#include +#include // for std::snprintf +#include // for PRId64 et.al. + +#include + +#include "libtorrent/config.hpp" + +// tests are expected to even test deprecated functionality. There is no point +// in warning about deprecated use in any of the tests. +// the unreachable code warnings are disabled since the test macros may +// sometimes have conditions that are known at compile time +#if defined __clang__ +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wunreachable-code" + +#elif defined __GNUC__ +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wunreachable-code" + +#elif defined _MSC_VER +#pragma warning(disable : 4996) +#endif + +#if defined TORRENT_BUILDING_TEST_SHARED +#define EXPORT BOOST_SYMBOL_EXPORT +#elif defined TORRENT_LINK_TEST_SHARED +#define EXPORT BOOST_SYMBOL_IMPORT +#else +#define EXPORT +#endif + +void EXPORT report_failure(char const* err, char const* file, int line); +int EXPORT print_failures(); +int EXPORT test_counter(); +void EXPORT reset_output(); + +using unit_test_fun_t = void (*)(); + +struct unit_test_t +{ + unit_test_fun_t fun; + char const* name; + int num_failures; + bool run; + FILE* output; +}; + +extern unit_test_t EXPORT _g_unit_tests[1024]; +extern int EXPORT _g_num_unit_tests; +extern int EXPORT _g_test_failures; +extern int _g_test_idx; + +#define TORRENT_TEST(test_name) \ + static void BOOST_PP_CAT(unit_test_, test_name)(); \ + static struct BOOST_PP_CAT(register_class_, test_name) { \ + BOOST_PP_CAT(register_class_, test_name) () { \ + unit_test_t& t = _g_unit_tests[_g_num_unit_tests]; \ + t.fun = &BOOST_PP_CAT(unit_test_, test_name); \ + t.name = __FILE__ "." #test_name; \ + t.num_failures = 0; \ + t.run = false; \ + t.output = nullptr; \ + _g_num_unit_tests++; \ + } \ + } BOOST_PP_CAT(_static_registrar_, test_name); \ + static void BOOST_PP_CAT(unit_test_, test_name)() + +#define TEST_REPORT_AUX(x, line, file) \ + report_failure(x, line, file) + +#ifdef BOOST_NO_EXCEPTIONS +#define TEST_CHECK(x) \ + do if (!(x)) { \ + TEST_REPORT_AUX("TEST_ERROR: check failed: \"" #x "\"", __FILE__, __LINE__); + } while (false) +#define TEST_EQUAL(x, y) \ + do if ((x) != (y)) { \ + std::stringstream s__; \ + s__ << "TEST_ERROR: equal check failed:\n" #x ": " << (x) << "\nexpected: " << (y); \ + TEST_REPORT_AUX(s__.str().c_str(), __FILE__, __LINE__); \ + } while (false) +#define TEST_NE(x, y) \ + do if ((x) == (y)) { \ + std::stringstream s__; \ + s__ << "TEST_ERROR: not equal check failed:\n" #x ": " << (x) << "\nexpected not equal to: " << (y); \ + TEST_REPORT_AUX(s__.str().c_str(), __FILE__, __LINE__); \ + } while (false) +#else +#define TEST_CHECK(x) \ + do try \ + { \ + if (!(x)) \ + TEST_REPORT_AUX("TEST_ERROR: check failed: \"" #x "\"", __FILE__, __LINE__); \ + } \ + catch (std::exception const& e) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x " :" + std::string(e.what())); \ + } \ + catch (...) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x); \ + } while (false) + +#define TEST_EQUAL(x, y) \ + do try { \ + if ((x) != (y)) { \ + std::stringstream s__; \ + s__ << "TEST_ERROR: " #x ": " << (x) << " expected: " << (y); \ + TEST_REPORT_AUX(s__.str().c_str(), __FILE__, __LINE__); \ + } \ + } \ + catch (std::exception const& e) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x " :" + std::string(e.what())); \ + } \ + catch (...) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x); \ + } while (false) +#define TEST_NE(x, y) \ + do try { \ + if ((x) == (y)) { \ + std::stringstream s__; \ + s__ << "TEST_ERROR: " #x ": " << (x) << " expected not equal to: " << (y); \ + TEST_REPORT_AUX(s__.str().c_str(), __FILE__, __LINE__); \ + } \ + } \ + catch (std::exception const& e) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x " :" + std::string(e.what())); \ + } \ + catch (...) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x); \ + } while (false) +#endif + +#define TEST_ERROR(x) \ + TEST_REPORT_AUX((std::string("TEST_ERROR: \"") + (x) + "\"").c_str(), __FILE__, __LINE__) + +#define TEST_NOTHROW(x) \ + do try \ + { \ + x; \ + } \ + catch (...) \ + { \ + TEST_ERROR("TEST_ERROR: Exception thrown: " #x); \ + } while (false) + +#define TEST_THROW(x) \ + do try \ + { \ + x; \ + TEST_ERROR("No exception thrown: " #x); \ + } \ + catch (...) {} while (false) + +#endif // TEST_HPP + diff --git a/test/test_alert_manager.cpp b/test/test_alert_manager.cpp new file mode 100644 index 0000000..12c3968 --- /dev/null +++ b/test/test_alert_manager.cpp @@ -0,0 +1,356 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/extensions.hpp" +#include "setup_transfer.hpp" + +#include +#include + +using namespace lt; + +TORRENT_TEST(limit) +{ + alert_manager mgr(500, alert_category::all); + + TEST_EQUAL(mgr.alert_queue_size_limit(), 500); + TEST_EQUAL(mgr.pending(), false); + + // try add 600 torrent_add_alert to make sure we honor the limit of 500 + // alerts. + for (piece_index_t i{0}; i < piece_index_t{600}; ++i) + mgr.emplace_alert(torrent_handle(), i); + + TEST_EQUAL(mgr.pending(), true); + + std::vector alerts; + mgr.get_all(alerts); + + // even though we posted 600, the limit was 500 + // +1 for the alerts_dropped_alert + TEST_EQUAL(alerts.size(), 501); + + TEST_EQUAL(mgr.pending(), false); + + // now, try lowering the limit and do the same thing again + mgr.set_alert_queue_size_limit(200); + + for (piece_index_t i{0}; i < piece_index_t{600}; ++i) + mgr.emplace_alert(torrent_handle(), i); + + TEST_EQUAL(mgr.pending(), true); + + mgr.get_all(alerts); + + // even though we posted 600, the limit was 200 + // +1 for the alerts_dropped_alert + TEST_EQUAL(alerts.size(), 201); +} + +TORRENT_TEST(limit_int_max) +{ + int const inf = std::numeric_limits::max(); + alert_manager mgr(inf, alert_category::all); + + TEST_EQUAL(mgr.alert_queue_size_limit(), inf); + + for (piece_index_t i{0}; i < piece_index_t{600}; ++i) + mgr.emplace_alert(torrent_handle(), i); + + for (piece_index_t i{0}; i < piece_index_t{600}; ++i) + mgr.emplace_alert(torrent_handle(), sha1_hash()); + + std::vector alerts; + mgr.get_all(alerts); + + TEST_EQUAL(alerts.size(), 1200); +} + +TORRENT_TEST(priority_limit) +{ + alert_manager mgr(100, alert_category::all); + + TEST_EQUAL(mgr.alert_queue_size_limit(), 100); + + // this should only add 100 because of the limit + for (piece_index_t i{0}; i < piece_index_t{200}; ++i) + mgr.emplace_alert(torrent_handle(), i); + + // the limit is twice as high for priority alerts + for (file_index_t i(0); i < file_index_t(300); ++i) + mgr.emplace_alert(torrent_handle(), i, error_code()); + + std::vector alerts; + mgr.get_all(alerts); + + // even though we posted 500, the limit was 100 for half of them and + // 100 + 200 for the other half, meaning we should have 300 alerts now + // +1 for the alerts_dropped_alert + TEST_EQUAL(alerts.size(), 301); +} + +namespace { +void test_notify_fun(int& cnt) +{ + ++cnt; +} +} // anonymous namespace + +TORRENT_TEST(notify_function) +{ + int cnt = 0; + alert_manager mgr(100, alert_category::all); + + TEST_EQUAL(mgr.alert_queue_size_limit(), 100); + TEST_EQUAL(mgr.pending(), false); + + for (int i = 0; i < 20; ++i) + mgr.emplace_alert(torrent_handle(), add_torrent_params(), error_code()); + + TEST_EQUAL(mgr.pending(), true); + + // if there are queued alerts when we set the notify function, + // that counts as an edge and it's called + mgr.set_notify_function(std::bind(&test_notify_fun, std::ref(cnt))); + + TEST_EQUAL(mgr.pending(), true); + TEST_EQUAL(cnt, 1); + + // subsequent posted alerts will not cause an edge (because there are + // already alerts queued) + for (int i = 0; i < 20; ++i) + mgr.emplace_alert(torrent_handle(), add_torrent_params(), error_code()); + + TEST_EQUAL(mgr.pending(), true); + TEST_EQUAL(cnt, 1); + + // however, if we pop all the alerts and post new ones, there will be + // and edge triggering the notify call + std::vector alerts; + mgr.get_all(alerts); + + TEST_EQUAL(mgr.pending(), false); + + for (int i = 0; i < 20; ++i) + mgr.emplace_alert(torrent_handle(), add_torrent_params(), error_code()); + + TEST_EQUAL(mgr.pending(), true); + TEST_EQUAL(cnt, 2); +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +namespace { +int plugin_alerts[3] = { 0, 0, 0 }; + +struct test_plugin : lt::plugin +{ + explicit test_plugin(int index) : m_index(index) {} + void on_alert(alert const*) override + { + ++plugin_alerts[m_index]; + } + int m_index; +}; +} // anonymous namespace +#endif + +TORRENT_TEST(extensions) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + memset(plugin_alerts, 0, sizeof(plugin_alerts)); + alert_manager mgr(100, alert_category::all); + + mgr.add_extension(std::make_shared(0)); + mgr.add_extension(std::make_shared(1)); + mgr.add_extension(std::make_shared(2)); + + for (int i = 0; i < 53; ++i) + mgr.emplace_alert(torrent_handle(), add_torrent_params(), error_code()); + + TEST_EQUAL(plugin_alerts[0], 53); + TEST_EQUAL(plugin_alerts[1], 53); + TEST_EQUAL(plugin_alerts[2], 53); + + for (int i = 0; i < 17; ++i) + mgr.emplace_alert(torrent_handle(), add_torrent_params(), error_code()); + + TEST_EQUAL(plugin_alerts[0], 70); + TEST_EQUAL(plugin_alerts[1], 70); + TEST_EQUAL(plugin_alerts[2], 70); +#endif +} + +/* +namespace { + +void post_torrent_added(alert_manager* mgr) +{ + std::this_thread::sleep_for(lt::milliseconds(10)); + mgr->emplace_alert(torrent_handle(), add_torrent_params(), error_code()); +} + +} // anonymous namespace + +// this test is too flaky + +TORRENT_TEST(wait_for_alert) +{ + alert_manager mgr(100, alert_category::all); + + time_point start = clock_type::now(); + + alert* a = mgr.wait_for_alert(seconds(1)); + + time_point end = clock_type::now(); + TEST_EQUAL(a, static_cast(nullptr)); + std::printf("delay: %d ms (expected 1 second)\n" + , int(total_milliseconds(end - start))); + TEST_CHECK(end - start > milliseconds(900)); + TEST_CHECK(end - start < milliseconds(1100)); + + mgr.emplace_alert(torrent_handle(), add_torrent_params(), error_code()); + + start = clock_type::now(); + a = mgr.wait_for_alert(seconds(1)); + end = clock_type::now(); + + std::printf("delay: %d ms\n", int(total_milliseconds(end - start))); + TEST_CHECK(end - start < milliseconds(1)); + TEST_CHECK(a->type() == add_torrent_alert::alert_type); + + std::vector alerts; + mgr.get_all(alerts); + + start = clock_type::now(); + std::thread posting_thread(&post_torrent_added, &mgr); + + a = mgr.wait_for_alert(seconds(10)); + end = clock_type::now(); + + std::printf("delay: %d ms\n", int(total_milliseconds(end - start))); + TEST_CHECK(end - start < milliseconds(500)); + TEST_CHECK(a->type() == add_torrent_alert::alert_type); + + posting_thread.join(); +} +*/ + +TORRENT_TEST(alert_mask) +{ + alert_manager mgr(100, alert_category::all); + + TEST_CHECK(mgr.should_post()); + TEST_CHECK(mgr.should_post()); + + mgr.set_alert_mask({}); + + TEST_CHECK(!mgr.should_post()); + TEST_CHECK(!mgr.should_post()); +} + +TORRENT_TEST(get_all_empty) +{ + alert_manager mgr(100, alert_category::all); + std::vector alerts(10); + + mgr.get_all(alerts); + + TEST_CHECK(alerts.empty()); +} + +TORRENT_TEST(dropped_alerts) +{ + alert_manager mgr(1, alert_category::all); + + // nothing has dropped yet + mgr.emplace_alert(torrent_handle()); + // still nothing, there's space for one alert + mgr.emplace_alert(torrent_handle()); + // still nothing, there's space for one alert + mgr.emplace_alert(torrent_handle()); + // that last alert got dropped though, since it would have brought the queue + // size to 3 + std::vector alerts; + mgr.get_all(alerts); + auto const d = alert_cast(alerts.back())->dropped_alerts; + TEST_EQUAL(d.count(), 1); + TEST_CHECK(d.test(torrent_finished_alert::alert_type)); +} + +TORRENT_TEST(alerts_dropped_alert) +{ + alert_manager mgr(1, alert_category::all); + + mgr.emplace_alert(torrent_handle()); + mgr.emplace_alert(torrent_handle()); + mgr.emplace_alert(torrent_handle()); + // that last alert got dropped though, since it would have brought the queue + // size to 3 + std::vector alerts; + mgr.get_all(alerts); + + TEST_EQUAL(alerts.back()->message(), "dropped alerts: torrent_finished "); +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +struct post_plugin : lt::plugin +{ + explicit post_plugin(alert_manager& m) : mgr(m) {} + void on_alert(alert const*) + { + if (++depth > 10) return; + mgr.emplace_alert(torrent_handle(), piece_index_t{0}); + } + + alert_manager& mgr; + int depth = 0; +}; + +// make sure the alert manager supports alerts being posted while executing a +// plugin handler +TORRENT_TEST(recursive_alerts) +{ + alert_manager mgr(100, alert_category::all); + auto pl = std::make_shared(mgr); + mgr.add_extension(pl); + + mgr.emplace_alert(torrent_handle(), piece_index_t{0}); + + TEST_EQUAL(pl->depth, 11); +} + +#endif // TORRENT_DISABLE_EXTENSIONS + diff --git a/test/test_alert_types.cpp b/test/test_alert_types.cpp new file mode 100644 index 0000000..1697fbb --- /dev/null +++ b/test/test_alert_types.cpp @@ -0,0 +1,344 @@ +/* + +Copyright (c) 2017, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/alert_manager.hpp" +#include "libtorrent/alert_types.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" + +#include + +using namespace lt; + +TORRENT_TEST(alerts_types) +{ + // this counter is incremented sequentially + // with each call to TEST_ALERT_TYPE + // it starts at 3 because the first alerts + // are abstract + int count_alert_types = 3; + +#if TORRENT_ABI_VERSION == 1 + TEST_EQUAL(torrent_alert::alert_type, 0); + TEST_EQUAL(peer_alert::alert_type, 1); + TEST_EQUAL(tracker_alert::alert_type, 2); + TEST_EQUAL(alert::debug_notification, alert_category::connect); +#endif + +#define TEST_ALERT_TYPE(name, seq, prio, cat) \ + TEST_EQUAL(name::priority, prio); \ + TEST_EQUAL(name::alert_type, seq); \ + TEST_EQUAL(name::static_category, cat); \ + TEST_EQUAL(count_alert_types, seq); \ + TEST_EQUAL(std::string(alert_name(name::alert_type)) + "_alert", #name); \ + count_alert_types++ + +#if TORRENT_ABI_VERSION == 1 + TEST_ALERT_TYPE(torrent_added_alert, 3, 0, alert_category::status); +#else + ++count_alert_types; +#endif + +#if TORRENT_ABI_VERSION == 1 +#define PROGRESS_NOTIFICATION alert::progress_notification | +#else +#define PROGRESS_NOTIFICATION +#endif + + TEST_ALERT_TYPE(torrent_removed_alert, 4, 2, alert_category::status); + TEST_ALERT_TYPE(read_piece_alert, 5, 2, alert_category::storage); + TEST_ALERT_TYPE(file_completed_alert, 6, 0, PROGRESS_NOTIFICATION alert_category::file_progress); + TEST_ALERT_TYPE(file_renamed_alert, 7, 2, alert_category::storage); + TEST_ALERT_TYPE(file_rename_failed_alert, 8, 2, alert_category::storage); + TEST_ALERT_TYPE(performance_alert, 9, 0, alert_category::performance_warning); + TEST_ALERT_TYPE(state_changed_alert, 10, 1, alert_category::status); + TEST_ALERT_TYPE(tracker_error_alert, 11, 1, alert_category::tracker | alert_category::error); + TEST_ALERT_TYPE(tracker_warning_alert, 12, 0, alert_category::tracker | alert_category::error); + TEST_ALERT_TYPE(scrape_reply_alert, 13, 2, alert_category::tracker); + TEST_ALERT_TYPE(scrape_failed_alert, 14, 2, alert_category::tracker | alert_category::error); + TEST_ALERT_TYPE(tracker_reply_alert, 15, 0, alert_category::tracker); + TEST_ALERT_TYPE(dht_reply_alert, 16, 0, alert_category::dht | alert_category::tracker); + TEST_ALERT_TYPE(tracker_announce_alert, 17, 0, alert_category::tracker); + TEST_ALERT_TYPE(hash_failed_alert, 18, 0, alert_category::status); + TEST_ALERT_TYPE(peer_ban_alert, 19, 0, alert_category::peer); + TEST_ALERT_TYPE(peer_unsnubbed_alert, 20, 0, alert_category::peer); + TEST_ALERT_TYPE(peer_snubbed_alert, 21, 0, alert_category::peer); + TEST_ALERT_TYPE(peer_error_alert, 22, 0, alert_category::peer); + TEST_ALERT_TYPE(peer_connect_alert, 23, 0, alert_category::connect); + TEST_ALERT_TYPE(peer_disconnected_alert, 24, 0, alert_category::connect); + TEST_ALERT_TYPE(invalid_request_alert, 25, 0, alert_category::peer); + TEST_ALERT_TYPE(torrent_finished_alert, 26, 1, alert_category::status); + TEST_ALERT_TYPE(piece_finished_alert, 27, 0, PROGRESS_NOTIFICATION alert_category::piece_progress); + TEST_ALERT_TYPE(request_dropped_alert, 28, 0, PROGRESS_NOTIFICATION alert_category::block_progress | alert_category::peer); + TEST_ALERT_TYPE(block_timeout_alert, 29, 0, PROGRESS_NOTIFICATION alert_category::block_progress | alert_category::peer); + TEST_ALERT_TYPE(block_finished_alert, 30, 0, PROGRESS_NOTIFICATION alert_category::block_progress); + TEST_ALERT_TYPE(block_downloading_alert, 31, 0, PROGRESS_NOTIFICATION alert_category::block_progress); + TEST_ALERT_TYPE(unwanted_block_alert, 32, 0, alert_category::peer); + TEST_ALERT_TYPE(storage_moved_alert, 33, 2, alert_category::storage); + TEST_ALERT_TYPE(storage_moved_failed_alert, 34, 2, alert_category::storage); + TEST_ALERT_TYPE(torrent_deleted_alert, 35, 2, alert_category::storage); + TEST_ALERT_TYPE(torrent_delete_failed_alert, 36, 2, alert_category::storage | alert_category::error); + TEST_ALERT_TYPE(save_resume_data_alert, 37, 2, alert_category::storage); + TEST_ALERT_TYPE(save_resume_data_failed_alert, 38, 2, alert_category::storage | alert_category::error); + TEST_ALERT_TYPE(torrent_paused_alert, 39, 1, alert_category::status); + TEST_ALERT_TYPE(torrent_resumed_alert, 40, 1, alert_category::status); + TEST_ALERT_TYPE(torrent_checked_alert, 41, 1, alert_category::status); + TEST_ALERT_TYPE(url_seed_alert, 42, 0, alert_category::peer | alert_category::error); + TEST_ALERT_TYPE(file_error_alert, 43, 1, alert_category::status | alert_category::error | alert_category::storage); + TEST_ALERT_TYPE(metadata_failed_alert, 44, 0, alert_category::error); + TEST_ALERT_TYPE(metadata_received_alert, 45, 0, alert_category::status); + TEST_ALERT_TYPE(udp_error_alert, 46, 0, alert_category::error); + TEST_ALERT_TYPE(external_ip_alert, 47, 0, alert_category::status); + TEST_ALERT_TYPE(listen_failed_alert, 48, 2, alert_category::status | alert_category::error); + TEST_ALERT_TYPE(listen_succeeded_alert, 49, 2, alert_category::status); + TEST_ALERT_TYPE(portmap_error_alert, 50, 0, alert_category::port_mapping | alert_category::error); + TEST_ALERT_TYPE(portmap_alert, 51, 0, alert_category::port_mapping); + TEST_ALERT_TYPE(portmap_log_alert, 52, 0, alert_category::port_mapping_log); + TEST_ALERT_TYPE(fastresume_rejected_alert, 53, 2, alert_category::status | alert_category::error); + TEST_ALERT_TYPE(peer_blocked_alert, 54, 0, alert_category::ip_block); + TEST_ALERT_TYPE(dht_announce_alert, 55, 0, alert_category::dht); + TEST_ALERT_TYPE(dht_get_peers_alert, 56, 0, alert_category::dht); + TEST_ALERT_TYPE(stats_alert, 57, 0, alert_category::stats); + TEST_ALERT_TYPE(cache_flushed_alert, 58, 1, alert_category::storage); +#if TORRENT_ABI_VERSION == 1 + TEST_ALERT_TYPE(anonymous_mode_alert, 59, 0, alert_category::error); +#else + count_alert_types++; +#endif + TEST_ALERT_TYPE(lsd_peer_alert, 60, 0, alert_category::peer); + TEST_ALERT_TYPE(trackerid_alert, 61, 0, alert_category::status); + TEST_ALERT_TYPE(dht_bootstrap_alert, 62, 0, alert_category::dht); + count_alert_types++; // 63 is gone + TEST_ALERT_TYPE(torrent_error_alert, 64, 1, alert_category::error | alert_category::status); + TEST_ALERT_TYPE(torrent_need_cert_alert, 65, 2, alert_category::status); + TEST_ALERT_TYPE(incoming_connection_alert, 66, 0, alert_category::peer); + TEST_ALERT_TYPE(add_torrent_alert, 67, 2, alert_category::status); + TEST_ALERT_TYPE(state_update_alert, 68, 1, alert_category::status); +#if TORRENT_ABI_VERSION == 1 + TEST_ALERT_TYPE(mmap_cache_alert, 69, 0, alert_category::error); +#else + count_alert_types++; +#endif + TEST_ALERT_TYPE(session_stats_alert, 70, 2, alert_category::stats); +#if TORRENT_ABI_VERSION == 1 + TEST_ALERT_TYPE(torrent_update_alert, 71, 2, alert_category::status); +#else + count_alert_types++; +#endif + count_alert_types++; // 72 is gone + TEST_ALERT_TYPE(dht_error_alert, 73, 0, alert_category::error | alert_category::dht); + TEST_ALERT_TYPE(dht_immutable_item_alert, 74, 2, alert_category::dht); + TEST_ALERT_TYPE(dht_mutable_item_alert, 75, 2, alert_category::dht); + TEST_ALERT_TYPE(dht_put_alert, 76, 0, alert_category::dht); + TEST_ALERT_TYPE(i2p_alert, 77, 0, alert_category::error); + TEST_ALERT_TYPE(dht_outgoing_get_peers_alert, 78, 0, alert_category::dht); + TEST_ALERT_TYPE(log_alert, 79, 0, alert_category::session_log); + TEST_ALERT_TYPE(torrent_log_alert, 80, 0, alert_category::torrent_log); + TEST_ALERT_TYPE(peer_log_alert, 81, 0, alert_category::peer_log); + TEST_ALERT_TYPE(lsd_error_alert, 82, 0, alert_category::error); + TEST_ALERT_TYPE(dht_stats_alert, 83, 0, alert_category::stats); + TEST_ALERT_TYPE(incoming_request_alert, 84, 0, alert_category::incoming_request); + TEST_ALERT_TYPE(dht_log_alert, 85, 0, alert_category::dht_log); + TEST_ALERT_TYPE(dht_pkt_alert, 86, 0, alert_category::dht_log); + TEST_ALERT_TYPE(dht_get_peers_reply_alert, 87, 0, alert_category::dht_operation); + TEST_ALERT_TYPE(dht_direct_response_alert, 88, 2, alert_category::dht); + TEST_ALERT_TYPE(picker_log_alert, 89, 0, alert_category::picker_log); + TEST_ALERT_TYPE(session_error_alert, 90, 0, alert_category::error); + TEST_ALERT_TYPE(dht_live_nodes_alert, 91, 0, alert_category::dht); + TEST_ALERT_TYPE(session_stats_header_alert, 92, 0, alert_category::stats); + TEST_ALERT_TYPE(dht_sample_infohashes_alert, 93, 0, alert_category::dht_operation); + TEST_ALERT_TYPE(block_uploaded_alert, 94, 0, PROGRESS_NOTIFICATION alert_category::upload); + TEST_ALERT_TYPE(alerts_dropped_alert, 95, 3, alert_category::error); + TEST_ALERT_TYPE(socks5_alert, 96, 0, alert_category::error); + +#undef TEST_ALERT_TYPE + + TEST_EQUAL(num_alert_types, 97); + TEST_EQUAL(num_alert_types, count_alert_types); +} + +TORRENT_TEST(dht_get_peers_reply_alert) +{ + alert_manager mgr(1, dht_get_peers_reply_alert::static_category); + + TEST_EQUAL(mgr.should_post(), true); + + sha1_hash const ih = rand_hash(); + tcp::endpoint const ep1 = rand_tcp_ep(rand_v4); + tcp::endpoint const ep2 = rand_tcp_ep(rand_v4); + tcp::endpoint const ep3 = rand_tcp_ep(rand_v4); + tcp::endpoint const ep4 = rand_tcp_ep(rand_v6); + tcp::endpoint const ep5 = rand_tcp_ep(rand_v6); + std::vector v = {ep1, ep2, ep3, ep4, ep5}; + + mgr.emplace_alert(ih, v); + + auto const* a = alert_cast(mgr.wait_for_alert(seconds(0))); + TEST_CHECK(a != nullptr); + + TEST_EQUAL(a->info_hash, ih); + TEST_EQUAL(a->num_peers(), 5); + + std::vector peers = a->peers(); + std::sort(v.begin(), v.end()); + std::sort(peers.begin(), peers.end()); + TEST_CHECK(v == peers); +} + +TORRENT_TEST(dht_live_nodes_alert) +{ + alert_manager mgr(1, dht_live_nodes_alert::static_category); + + TEST_EQUAL(mgr.should_post(), true); + + sha1_hash const ih = rand_hash(); + sha1_hash const h1 = rand_hash(); + sha1_hash const h2 = rand_hash(); + sha1_hash const h3 = rand_hash(); + sha1_hash const h4 = rand_hash(); + sha1_hash const h5 = rand_hash(); + udp::endpoint const ep1 = rand_udp_ep(rand_v4); + udp::endpoint const ep2 = rand_udp_ep(rand_v4); + udp::endpoint const ep3 = rand_udp_ep(rand_v4); + udp::endpoint const ep4 = rand_udp_ep(rand_v6); + udp::endpoint const ep5 = rand_udp_ep(rand_v6); + std::vector> v; + v.emplace_back(h1, ep1); + v.emplace_back(h2, ep2); + v.emplace_back(h3, ep3); + v.emplace_back(h4, ep4); + v.emplace_back(h5, ep5); + + mgr.emplace_alert(ih, v); + + auto const* a = alert_cast(mgr.wait_for_alert(seconds(0))); + TEST_CHECK(a != nullptr); + + TEST_EQUAL(a->node_id, ih); + TEST_EQUAL(a->num_nodes(), 5); + + auto nodes = a->nodes(); + std::sort(v.begin(), v.end()); + std::sort(nodes.begin(), nodes.end()); + TEST_CHECK(v == nodes); +} + +TORRENT_TEST(session_stats_alert) +{ + alert_manager mgr(1, alert_category::stats); + + std::vector alerts; + counters cnt; + + mgr.emplace_alert(); + mgr.emplace_alert(cnt); + mgr.get_all(alerts); + TEST_EQUAL(alerts.size(), 2); + + auto const* h = alert_cast(alerts[0]); + TEST_CHECK(h != nullptr); + TEST_CHECK(h->message().find("session stats header: ") != std::string::npos); + + auto const* v = alert_cast(alerts[1]); + TEST_CHECK(v != nullptr); + TEST_CHECK(v->message().find("session stats (") != std::string::npos); +} + +TORRENT_TEST(dht_sample_infohashes_alert) +{ + alert_manager mgr(1, dht_sample_infohashes_alert::static_category); + + TEST_EQUAL(mgr.should_post(), true); + + udp::endpoint const endpoint = rand_udp_ep(); + time_duration const interval = seconds(10); + int const num = 100; + sha1_hash const h1 = rand_hash(); + sha1_hash const h2 = rand_hash(); + sha1_hash const h3 = rand_hash(); + sha1_hash const h4 = rand_hash(); + sha1_hash const h5 = rand_hash(); + + std::vector const v = {h1, h2, h3, h4, h5}; + + sha1_hash const nh1 = rand_hash(); + sha1_hash const nh2 = rand_hash(); + sha1_hash const nh3 = rand_hash(); + sha1_hash const nh4 = rand_hash(); + sha1_hash const nh5 = rand_hash(); + udp::endpoint const nep1 = rand_udp_ep(rand_v4); + udp::endpoint const nep2 = rand_udp_ep(rand_v4); + udp::endpoint const nep3 = rand_udp_ep(rand_v4); + udp::endpoint const nep4 = rand_udp_ep(rand_v6); + udp::endpoint const nep5 = rand_udp_ep(rand_v6); + std::vector> nv; + nv.emplace_back(nh1, nep1); + nv.emplace_back(nh2, nep2); + nv.emplace_back(nh3, nep3); + nv.emplace_back(nh4, nep4); + nv.emplace_back(nh5, nep5); + + mgr.emplace_alert(endpoint, interval, num, v, nv); + + auto const* a = alert_cast(mgr.wait_for_alert(seconds(0))); + TEST_CHECK(a != nullptr); + + TEST_EQUAL(a->endpoint, endpoint); + TEST_CHECK(a->interval == interval); + TEST_EQUAL(a->num_infohashes, num); + TEST_EQUAL(a->num_samples(), 5); + TEST_CHECK(a->samples() == v); + TEST_EQUAL(a->num_nodes(), 5); + + auto nodes = a->nodes(); + std::sort(nv.begin(), nv.end()); + std::sort(nodes.begin(), nodes.end()); + TEST_CHECK(nv == nodes); +} + +TORRENT_TEST(performance_warning) +{ + using pw = lt::performance_alert; + TEST_EQUAL(performance_warning_str(pw::outstanding_disk_buffer_limit_reached), "max outstanding disk writes reached"_sv); + TEST_EQUAL(performance_warning_str(pw::outstanding_request_limit_reached), "max outstanding piece requests reached"_sv); + TEST_EQUAL(performance_warning_str(pw::upload_limit_too_low), "upload limit too low (download rate will suffer)"_sv); + TEST_EQUAL(performance_warning_str(pw::download_limit_too_low), "download limit too low (upload rate will suffer)"_sv); + TEST_EQUAL(performance_warning_str(pw::send_buffer_watermark_too_low), "send buffer watermark too low (upload rate will suffer)"_sv); + TEST_EQUAL(performance_warning_str(pw::too_many_optimistic_unchoke_slots), "too many optimistic unchoke slots"_sv); + TEST_EQUAL(performance_warning_str(pw::too_high_disk_queue_limit), "the disk queue limit is too high compared to the cache size. The disk queue eats into the cache size"_sv); + TEST_EQUAL(performance_warning_str(pw::aio_limit_reached), "outstanding AIO operations limit reached"_sv); + TEST_EQUAL(performance_warning_str(pw::too_few_outgoing_ports), "too few ports allowed for outgoing connections"_sv); + TEST_EQUAL(performance_warning_str(pw::too_few_file_descriptors), "too few file descriptors are allowed for this process. connection limit lowered"_sv); +} + +#undef PROGRESS_NOTIFICATION + diff --git a/test/test_alloca.cpp b/test/test_alloca.cpp new file mode 100644 index 0000000..8c7e814 --- /dev/null +++ b/test/test_alloca.cpp @@ -0,0 +1,73 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/alloca.hpp" + +using namespace lt; + +namespace { + +struct A +{ + int val = 1337; +}; + +int destructed = 0; + +struct B +{ + ~B() { ++destructed; } +}; + +} + +TORRENT_TEST(alloca_construct) +{ + TORRENT_ALLOCA(vec, A, 13); + + TEST_EQUAL(vec.size(), 13); + for (auto const& o : vec) + { + TEST_EQUAL(o.val, 1337); + } +} + +TORRENT_TEST(alloca_destruct) +{ + { + destructed = 0; + TORRENT_ALLOCA(vec, B, 3); + } + TEST_EQUAL(destructed, 3); +} + diff --git a/test/test_auto_unchoke.cpp b/test/test_auto_unchoke.cpp new file mode 100644 index 0000000..5e01916 --- /dev/null +++ b/test/test_auto_unchoke.cpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2011, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/path.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +namespace { + +void test_swarm() +{ + using namespace lt; + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + session_proxy p3; + + // this is to avoid everything finish from a single peer + // immediately. To make the swarm actually connect all + // three peers before finishing. + float rate_limit = 50000; + + settings_pack pack = settings(); + // run the choker once per second, to make it more likely to actually trigger + // during the test. + pack.set_int(settings_pack::unchoke_interval, 1); + + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + pack.set_int(settings_pack::choking_algorithm, settings_pack::rate_based_choker); + pack.set_int(settings_pack::upload_rate_limit, int(rate_limit)); + pack.set_int(settings_pack::unchoke_slots_limit, 1); + pack.set_int(settings_pack::max_retry_port_bind, 900); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48010"); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_dht, false); +#if TORRENT_ABI_VERSION == 1 + pack.set_bool(settings_pack::rate_limit_utp, true); +#endif + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + + lt::session ses1(pack); + + pack.set_int(settings_pack::upload_rate_limit, int(rate_limit / 10)); + pack.set_int(settings_pack::download_rate_limit, int(rate_limit / 5)); + pack.set_int(settings_pack::unchoke_slots_limit, 0); + pack.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49010"); + + lt::session ses2(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49010"); + + lt::session ses3(pack); + + torrent_handle tor1; + torrent_handle tor2; + torrent_handle tor3; + + std::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true, false, true, "_unchoke"); + + std::map cnt = get_counters(ses1); + + std::printf("allowed_upload_slots: %d\n", int(cnt["ses.num_unchoke_slots"])); + TEST_EQUAL(cnt["ses.num_unchoke_slots"], 1); + for (int i = 0; i < 200; ++i) + { + print_alerts(ses1, "ses1"); + print_alerts(ses2, "ses2"); + print_alerts(ses3, "ses3"); + + cnt = get_counters(ses1); + std::printf("allowed unchoked: %d\n", int(cnt["ses.num_unchoke_slots"])); + if (cnt["ses.num_unchoke_slots"] >= 2) break; + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + torrent_status st3 = tor3.status(); + + print_ses_rate(i / 10.f, &st1, &st2, &st3); + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + TEST_CHECK(cnt["ses.num_unchoke_slots"] >= 2); + + // make sure the files are deleted + ses1.remove_torrent(tor1, lt::session::delete_files); + ses2.remove_torrent(tor2, lt::session::delete_files); + ses3.remove_torrent(tor3, lt::session::delete_files); + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); + p3 = ses3.abort(); +} + +} // anonymous namespace + +TORRENT_TEST(auto_unchoke) +{ + using namespace lt; + + // in case the previous run was terminated + error_code ec; + remove_all("./tmp1_unchoke", ec); + remove_all("./tmp2_unchoke", ec); + remove_all("./tmp3_unchoke", ec); + + test_swarm(); + + TEST_CHECK(!exists("./tmp1_unchoke/temporary")); + TEST_CHECK(!exists("./tmp2_unchoke/temporary")); + TEST_CHECK(!exists("./tmp3_unchoke/temporary")); + + remove_all("./tmp1_unchoke", ec); + remove_all("./tmp2_unchoke", ec); + remove_all("./tmp3_unchoke", ec); +} diff --git a/test/test_bandwidth_limiter.cpp b/test/test_bandwidth_limiter.cpp new file mode 100644 index 0000000..6059627 --- /dev/null +++ b/test/test_bandwidth_limiter.cpp @@ -0,0 +1,517 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/bandwidth_queue_entry.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/bandwidth_socket.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/session_settings.hpp" + +#include +#include +#include +#include + +struct torrent; +struct peer_connection; + +using namespace lt; +using namespace std::placeholders; + +namespace { + +const float sample_time = 20.f; // seconds + +//#define VERBOSE_LOGGING + +bandwidth_channel global_bwc; + +struct peer_connection: bandwidth_socket, std::enable_shared_from_this +{ + peer_connection(bandwidth_manager& bwm + , bandwidth_channel& torrent_bwc, int prio, bool ignore_limits, std::string name) + : m_bwm(bwm) + , m_torrent_bandwidth_channel(torrent_bwc) + , m_priority(prio) + , m_ignore_limits(ignore_limits) + , m_name(std::move(name)) + , m_quota(0) + {} + + bool is_disconnecting() const override { return false; } + void assign_bandwidth(int channel, int amount) override; + + void throttle(int limit) { m_bandwidth_channel.throttle(limit); } + + void start(); + + bandwidth_manager& m_bwm; + + bandwidth_channel m_bandwidth_channel; + bandwidth_channel& m_torrent_bandwidth_channel; + + int m_priority; + bool m_ignore_limits; + std::string m_name; + std::int64_t m_quota; +}; + +void peer_connection::assign_bandwidth(int /*channel*/, int amount) +{ + m_quota += amount; +#ifdef VERBOSE_LOGGING + std::cout << " [" << m_name + << "] assign bandwidth, " << amount << std::endl; +#endif + TEST_CHECK(amount > 0); + start(); +} + +void peer_connection::start() +{ + bandwidth_channel* channels[] = { + &m_bandwidth_channel + , &m_torrent_bandwidth_channel + , &global_bwc + }; + + m_bwm.request_bandwidth(shared_from_this(), 400000000, m_priority, channels, 3); +} + + +using connections_t = std::vector>; + +void do_change_rate(bandwidth_channel& t1, bandwidth_channel& t2, int limit) +{ + static int counter = 10; + --counter; + if (counter == 0) + { + t1.throttle(limit); + t2.throttle(limit); + return; + } + + t1.throttle(limit + limit / 2 * ((counter & 1)?-1:1)); + t2.throttle(limit + limit / 2 * ((counter & 1)?1:-1)); +} + +void do_change_peer_rate(connections_t& v, int limit) +{ + static int count = 10; + --count; + if (count == 0) + { + std::for_each(v.begin(), v.end() + , std::bind(&peer_connection::throttle, _1, limit)); + return; + } + + int c = count; + for (connections_t::iterator i = v.begin(); i != v.end(); ++i, ++c) + i->get()->throttle(limit + limit / 2 * ((c & 1)?-1:1)); +} + +static void nop() {} + +void run_test(connections_t& v + , bandwidth_manager& manager + , std::function f = &nop) +{ + std::cout << "-------------" << std::endl; + + std::for_each(v.begin(), v.end() + , std::bind(&peer_connection::start, _1)); + + lt::aux::session_settings s; + int tick_interval = s.get_int(settings_pack::tick_interval); + + for (int i = 0; i < int(sample_time * 1000 / tick_interval); ++i) + { + manager.update_quotas(milliseconds(tick_interval)); + if ((i % 15) == 0) f(); + } +} + +bool close_to(float const val, float const comp, float const err) +{ + return std::abs(val - comp) <= err; +} + +void spawn_connections(connections_t& v, bandwidth_manager& bwm + , bandwidth_channel& bwc, int num, char const* prefix) +{ + for (int i = 0; i < num; ++i) + { + char name[200]; + std::snprintf(name, sizeof(name), "%s%d", prefix, i); + v.push_back(std::make_shared(bwm, bwc, 200, false, name)); + } +} + +void test_equal_connections(int num, int limit) +{ + std::cout << "\ntest equal connections " << num << " " << limit << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(limit); + + bandwidth_channel t1; + + connections_t v; + spawn_connections(v, manager, t1, num, "p"); + run_test(v, manager); + + float sum = 0.f; + float const err = std::max(limit / num * 0.3f, 1000.f); + for (connections_t::iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + sum += (*i)->m_quota; + + std::cout << (*i)->m_quota / sample_time + << " target: " << (limit / num) << " eps: " << err << std::endl; + TEST_CHECK(close_to((*i)->m_quota / sample_time, float(limit) / num, err)); + } + sum /= sample_time; + std::cout << "sum: " << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit), 50)); +} + +void test_connections_variable_rate(int num, int limit, int torrent_limit) +{ + std::cout << "\ntest connections variable rate" << num + << " l: " << limit + << " t: " << torrent_limit + << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(0); + + bandwidth_channel t1; + if (torrent_limit) + t1.throttle(torrent_limit); + + connections_t v; + spawn_connections(v, manager, t1, num, "p"); + std::for_each(v.begin(), v.end() + , std::bind(&peer_connection::throttle, _1, limit)); + + run_test(v, manager, std::bind(&do_change_peer_rate + , std::ref(v), limit)); + + if (torrent_limit > 0 && limit * num > torrent_limit) + limit = torrent_limit / num; + + float sum = 0.f; + float err = limit * 0.3f; + for (connections_t::iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + sum += (*i)->m_quota; + + std::cout << (*i)->m_quota / sample_time + << " target: " << limit << " eps: " << err << std::endl; + TEST_CHECK(close_to((*i)->m_quota / sample_time, float(limit), err)); + } + sum /= sample_time; + std::cout << "sum: " << sum << " target: " << (limit * num) << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit) * num, limit * 0.3f * num)); +} + +void test_single_peer(int limit, bool torrent_limit) +{ + std::cout << "\ntest single peer " << limit << " " << torrent_limit << std::endl; + bandwidth_manager manager(0); + bandwidth_channel t1; + global_bwc.throttle(0); + + if (torrent_limit) + t1.throttle(limit); + else + global_bwc.throttle(limit); + + connections_t v; + spawn_connections(v, manager, t1, 1, "p"); + run_test(v, manager); + + float sum = 0.f; + for (connections_t::iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit), 1000)); +} + +void test_torrents(int num, int limit1, int limit2, int global_limit) +{ + std::cout << "\ntest equal torrents " << num + << " l1: " << limit1 + << " l2: " << limit2 + << " g: " << global_limit << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(global_limit); + + bandwidth_channel t1; + bandwidth_channel t2; + + t1.throttle(limit1); + t2.throttle(limit2); + + connections_t v1; + spawn_connections(v1, manager, t1, num, "t1p"); + connections_t v2; + spawn_connections(v2, manager, t2, num, "t2p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + std::copy(v2.begin(), v2.end(), std::back_inserter(v)); + run_test(v, manager); + + if (global_limit > 0 && global_limit < limit1 + limit2) + { + limit1 = std::min(limit1, global_limit / 2); + limit2 = global_limit - limit1; + } + float sum = 0.f; + for (connections_t::iterator i = v1.begin() + , end(v1.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit1 << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit1), 1000)); + + sum = 0.f; + for (connections_t::iterator i = v2.begin() + , end(v2.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit2 << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit2), 1000)); +} + +void test_torrents_variable_rate(int num, int limit, int global_limit) +{ + std::cout << "\ntest torrents variable rate" << num + << " l: " << limit + << " g: " << global_limit << std::endl; + bandwidth_manager manager(0); + global_bwc.throttle(global_limit); + + bandwidth_channel t1; + bandwidth_channel t2; + + t1.throttle(limit); + t2.throttle(limit); + + connections_t v1; + spawn_connections(v1, manager, t1, num, "t1p"); + connections_t v2; + spawn_connections(v2, manager, t2, num, "t2p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + std::copy(v2.begin(), v2.end(), std::back_inserter(v)); + + run_test(v, manager, std::bind(&do_change_rate, std::ref(t1), std::ref(t2), limit)); + + if (global_limit > 0 && global_limit < 2 * limit) + limit = global_limit / 2; + + float sum = 0.f; + for (connections_t::iterator i = v1.begin() + , end(v1.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit), 1000)); + + sum = 0.f; + for (connections_t::iterator i = v2.begin() + , end(v2.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit), 1000)); +} + +void test_peer_priority(int limit, bool torrent_limit) +{ + std::cout << "\ntest peer priority " << limit << " " << torrent_limit << std::endl; + bandwidth_manager manager(0); + bandwidth_channel t1; + global_bwc.throttle(0); + + if (torrent_limit) + t1.throttle(limit); + else + global_bwc.throttle(limit); + + connections_t v1; + spawn_connections(v1, manager, t1, 10, "p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + std::shared_ptr p = + std::make_shared(manager, t1, 1, false, "no-priority"); + v.push_back(p); + run_test(v, manager); + + float sum = 0.f; + for (connections_t::iterator i = v1.begin() + , end(v1.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit), 50)); + + std::cout << "non-prioritized rate: " << p->m_quota / sample_time + << " target: " << (limit / 200 / 10) << std::endl; + TEST_CHECK(close_to(p->m_quota / sample_time, float(limit) / 200 / 10, 5)); +} + +void test_no_starvation(int limit) +{ + std::cout << "\ntest no starvation " << limit << std::endl; + bandwidth_manager manager(0); + bandwidth_channel t1; + bandwidth_channel t2; + + global_bwc.throttle(limit); + + const int num_peers = 20; + + connections_t v1; + spawn_connections(v1, manager, t1, num_peers, "p"); + connections_t v; + std::copy(v1.begin(), v1.end(), std::back_inserter(v)); + std::shared_ptr p = + std::make_shared(manager, t2, 1, false, "no-priority"); + v.push_back(p); + run_test(v, manager); + + float sum = 0.f; + for (connections_t::iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + sum += (*i)->m_quota; + } + sum /= sample_time; + std::cout << sum << " target: " << limit << std::endl; + TEST_CHECK(sum > 0); + TEST_CHECK(close_to(sum, float(limit), 50)); + + std::cout << "non-prioritized rate: " << p->m_quota / sample_time + << " target: " << (limit / 200 / num_peers) << std::endl; + TEST_CHECK(close_to(p->m_quota / sample_time, float(limit) / 200 / num_peers, 5)); +} + +} // anonymous namespace + +TORRENT_TEST(equal_connection) +{ + test_equal_connections( 2, 20); + test_equal_connections( 2, 2000); + test_equal_connections( 2, 20000); + test_equal_connections( 3, 20000); + test_equal_connections( 5, 20000); + test_equal_connections( 7, 20000); + test_equal_connections(33, 60000); + test_equal_connections(33, 500000); + test_equal_connections( 1, 1000000); + test_equal_connections( 1, 6000000); +} + +TORRENT_TEST(conn_var_rate) +{ + test_connections_variable_rate( 2, 20, 0); + test_connections_variable_rate( 5, 20000, 0); + test_connections_variable_rate( 3, 2000, 6000); + test_connections_variable_rate( 5, 2000, 30000); + test_connections_variable_rate(33, 500000, 0); +} + +TORRENT_TEST(torrents) +{ + test_torrents( 2, 400, 400, 0); + test_torrents( 2, 100, 500, 0); + test_torrents( 2, 3000, 3000, 6000); + test_torrents( 1, 40000, 40000, 0); + test_torrents(24, 50000, 50000, 0); + test_torrents( 5, 6000, 6000, 3000); + test_torrents( 5, 6000, 5000, 4000); + test_torrents( 5, 20000, 20000, 30000); +} + +TORRENT_TEST(torrent_var_rate) +{ + test_torrents_variable_rate(5, 6000, 3000); + test_torrents_variable_rate(5, 20000, 30000); +} + +TORRENT_TEST(bandwidth_limiter) +{ + test_single_peer(40000, true); + test_single_peer(40000, false); +} + +TORRENT_TEST(peer_priority) +{ + test_peer_priority(40000, false); + test_peer_priority(40000, true); +} + +TORRENT_TEST(no_starvation) +{ + test_no_starvation(40000); +} diff --git a/test/test_bdecode.cpp b/test/test_bdecode.cpp new file mode 100644 index 0000000..d5dfe7c --- /dev/null +++ b/test/test_bdecode.cpp @@ -0,0 +1,1300 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/entry.hpp" + +using namespace lt; + +// test integer +TORRENT_TEST(integer) +{ + char b[] = "i12453e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(span(b, sizeof(b) - 1) == e.data_section()); + TEST_EQUAL(e.type(), bdecode_node::int_t); + TEST_EQUAL(e.int_value(), 12453); +} + +TORRENT_TEST(construct_string) +{ + entry e(std::string("abc123")); + TEST_EQUAL(e.string(), "abc123"); +} + +TORRENT_TEST(construct_string_literal) +{ + entry e("abc123"); + TEST_EQUAL(e.string(), "abc123"); +} + + +TORRENT_TEST(construct_string_view) +{ + entry e(string_view("abc123")); + TEST_EQUAL(e.string(), "abc123"); +} + +TORRENT_TEST(construct_integer) +{ + entry e(4); + TEST_EQUAL(e.integer(), 4); +} + +// test string +TORRENT_TEST(string) +{ + char b[] = "26:abcdefghijklmnopqrstuvwxyz"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(span(b, sizeof(b) - 1) == e.data_section()); + TEST_EQUAL(e.type(), bdecode_node::string_t); + TEST_EQUAL(e.string_value(), std::string("abcdefghijklmnopqrstuvwxyz")); + TEST_EQUAL(e.string_length(), 26); +} + +// test string-prefix +TORRENT_TEST(string_prefix1) +{ + // test edge-case of a string that's nearly too long + std::string test; + test.resize(1000000 + 8); + memcpy(&test[0], "1000000:", 8); + // test is a valid bencoded string, that's quite long + error_code ec; + bdecode_node e = bdecode(test, ec); + TEST_CHECK(!ec); + std::printf("%d bytes string\n", e.string_length()); + TEST_CHECK(span(test) == e.data_section()); + TEST_EQUAL(e.type(), bdecode_node::string_t); + TEST_EQUAL(e.string_length(), 1000000); + TEST_EQUAL(e.string_ptr(), test.c_str() + 8); +} + +// test list +TORRENT_TEST(list) +{ + char b[] = "li12453e3:aaae"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(span(b, sizeof(b) - 1) == e.data_section()); + TEST_EQUAL(e.type(), bdecode_node::list_t); + TEST_EQUAL(e.list_size(), 2); + TEST_EQUAL(e.list_at(0).type(), bdecode_node::int_t); + TEST_EQUAL(e.list_at(1).type(), bdecode_node::string_t); + TEST_EQUAL(e.list_at(0).int_value(), 12453); + TEST_EQUAL(e.list_at(1).string_value(), std::string("aaa")); + TEST_EQUAL(e.list_at(1).string_length(), 3); + TEST_CHECK(span("3:aaa", 5) == e.list_at(1).data_section()); +} + +// test dict +TORRENT_TEST(dict) +{ + char b[] = "d1:ai12453e1:b3:aaa1:c3:bbb1:X10:0123456789e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(span(b, sizeof(b) - 1) == e.data_section()); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_size(), 4); + TEST_EQUAL(e.dict_find("a").type(), bdecode_node::int_t); + TEST_EQUAL(e.dict_find("a").int_value(), 12453); + TEST_EQUAL(e.dict_find("b").type(), bdecode_node::string_t); + TEST_EQUAL(e.dict_find("b").string_value(), std::string("aaa")); + TEST_EQUAL(e.dict_find("b").string_length(), 3); + TEST_EQUAL(e.dict_find("c").type(), bdecode_node::string_t); + TEST_EQUAL(e.dict_find("c").string_value(), std::string("bbb")); + TEST_EQUAL(e.dict_find("c").string_length(), 3); + TEST_EQUAL(e.dict_find_string_value("X"), "0123456789"); + char error_string[200]; + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("unsorted dictionary key")); +} + +// test dictionary with a key without a value +TORRENT_TEST(dict_key_novalue) +{ + char b[] = "d1:ai1e1:be"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 10); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_value)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test dictionary with a key that's not a string +TORRENT_TEST(dict_nonstring_key) +{ + char b[] = "di5e1:ae"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// dictionary key with \0 +TORRENT_TEST(dict_null_key) +{ + char b[] = "d3:a\0bi1ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(e.dict_size() == 1); + bdecode_node d = e.dict_find(std::string("a\0b", 3)); + TEST_EQUAL(d.type(), bdecode_node::int_t); + TEST_EQUAL(d.int_value(), 1); +} + +// soft error reported for dictionary with unordered keys +TORRENT_TEST(dict_unordered_keys) +{ + char error_string[200]; + { + char b[] = "d2:abi1e2:aai2ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("unsorted dictionary key")); + } + { + char b[] = "d2:bai1e2:aai2ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("unsorted dictionary key")); + } + { + char b[] = "d2:aai1e1:ai2ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("unsorted dictionary key")); + } + { + char b[] = "d1:ai1e2:aai2ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("unsorted dictionary key")); + } + { + char b[] = "d2:aai1e1:bi2ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("unsorted dictionary key")); + } +} + +TORRENT_TEST(dict_duplicate_key) +{ + char b[] = "d2:aai1e2:aai2ee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + char error_string[200]; + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("duplicate dictionary key")); +} + +// premature e +TORRENT_TEST(premature_e) +{ + char b[] = "e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test strings with negative length-prefix +TORRENT_TEST(negative_length_prefix) +{ + char b[] = "-10:foobar"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 0); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_value)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test strings with overflow length-prefix +TORRENT_TEST(overflow_length_prefix) +{ + char b[] = "18446744073709551615:foobar"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 19); + TEST_EQUAL(ec, error_code(bdecode_errors::overflow)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test strings with almost overflow (more than 8 digits) +TORRENT_TEST(close_overflow_length_prefix) +{ + char b[] = "99999999:foobar"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 8); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test strings with overflow (more than 8 digits) +TORRENT_TEST(overflow_length_prefix2) +{ + char b[] = "199999999:foobar"; + error_code ec; + int pos; + // pretend that we have a large buffer like that + bdecode_node e = bdecode({b, 999999999}, ec, &pos); + TEST_EQUAL(pos, 0); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded)); + std::printf("%s\n", print_entry(e).c_str()); +} + +TORRENT_TEST(leading_zero_length_prefix) +{ + { + char b[] = "06:foobar"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + char error_string[200]; + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("leading zero in string length")); + std::printf("%s\n", print_entry(e).c_str()); + } + { + char b[] = "0:"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + char error_string[200]; + TEST_CHECK(!e.has_soft_error(error_string)); + std::printf("%s\n", print_entry(e).c_str()); + } +} + +// test integer without any digits +TORRENT_TEST(nodigit_int) +{ + char b[] = "ie"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test integer with just a minus +TORRENT_TEST(minus_int) +{ + char b[] = "i-e"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 2); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test integer with a minus inserted in it +TORRENT_TEST(interior_minus_int) +{ + char b[] = "i35412-5633e"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 6); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test integers that don't fit in 64 bits +TORRENT_TEST(int_overflow) +{ + char b[] = "i18446744073709551615e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + std::printf("%s\n", print_entry(e).c_str()); + // the lazy aspect makes this overflow when asking for + // the value. turning it to zero. + TEST_EQUAL(e.int_value(), 0); +} + +// test integers with more than 20 digits (overflow on parsing) +TORRENT_TEST(int_overflow2) +{ + char b[] = "i184467440737095516154e"; + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 22); + TEST_EQUAL(ec, error_code(bdecode_errors::overflow)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test truncated negative integer +TORRENT_TEST(int_truncated) +{ + char b[] = "i-"; + error_code ec; + int pos; + bdecode_node e = bdecode({b, 2}, ec, &pos); + TEST_EQUAL(pos, 2); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +TORRENT_TEST(int_leading_zero) +{ + { + char b[] = "i01e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + char error_string[200]; + TEST_CHECK(e.has_soft_error(error_string)); + TEST_EQUAL(std::string(error_string), std::string("leading zero in integer")); + std::printf("%s\n", print_entry(e).c_str()); + } + { + char b[] = "i0e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + char error_string[200]; + TEST_CHECK(!e.has_soft_error(error_string)); + std::printf("%s\n", print_entry(e).c_str()); + } +} + +// bdecode_error +TORRENT_TEST(bdecode_error) +{ + error_code ec(bdecode_errors::overflow); + TEST_EQUAL(ec.message(), "integer overflow"); + TEST_EQUAL(ec.category().name(), std::string("bdecode")); + ec.assign(5434, bdecode_category()); + TEST_EQUAL(ec.message(), "Unknown error"); +} + +// test integers that just exactly fit in 64 bits +TORRENT_TEST(64bit_int) +{ + char b[] = "i9223372036854775807e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(e.int_value() == 9223372036854775807LL); +} + +// test integers that just exactly fit in 64 bits +TORRENT_TEST(64bit_int_negative) +{ + char b[] = "i-9223372036854775807e"; + error_code ec; + bdecode_node e = bdecode(b, ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(e.int_value() == -9223372036854775807LL); +} + +// test integers that have invalid digits +TORRENT_TEST(int_invalid_digit) +{ + char b[] = "i92337203t854775807e"; + error_code ec; + int pos = 0; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 9); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test invalid encoding +TORRENT_TEST(invalid_encoding) +{ + unsigned char buf[] = + { 0x64, 0x31, 0x3a, 0x61, 0x64, 0x32, 0x3a, 0x69 + , 0x64, 0x32, 0x30, 0x3a, 0x2a, 0x21, 0x19, 0x89 + , 0x9f, 0xcd, 0x5f, 0xc9, 0xbc, 0x80, 0xc1, 0x76 + , 0xfe, 0xe0, 0xc6, 0x84, 0x2d, 0xf6, 0xfc, 0xb8 + , 0x39, 0x3a, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x68 + , 0x61, 0xae, 0x68, 0x32, 0x30, 0x3a, 0x14, 0x78 + , 0xd5, 0xb0, 0xdc, 0xf6, 0x82, 0x42, 0x32, 0xa0 + , 0xd6, 0x88, 0xeb, 0x48, 0x57, 0x01, 0x89, 0x40 + , 0x4e, 0xbc, 0x65, 0x31, 0x3a, 0x71, 0x39, 0x3a + , 0x67, 0x65, 0x74, 0x5f, 0x70, 0x65, 0x65, 0x72 + , 0x78, 0xff, 0x3a, 0x74, 0x38, 0x3a, 0xaa, 0xd4 + , 0xa1, 0x88, 0x7a, 0x8d, 0xc3, 0xd6, 0x31, 0x3a + , 0x79, 0x31, 0xae, 0x71, 0x65, 0}; + + std::printf("%s\n", buf); + error_code ec; + bdecode_node e = bdecode({reinterpret_cast(buf), sizeof(buf)}, ec); + TEST_CHECK(ec); +} + +// test the depth limit +TORRENT_TEST(depth_limit) +{ + char b[2048]; + for (int i = 0; i < 1024; ++i) + b[i]= 'l'; + + for (int i = 1024; i < 2048; ++i) + b[i]= 'e'; + + // 1024 levels nested lists + + error_code ec; + bdecode_node e = bdecode({b, sizeof(b)}, ec, nullptr, 100); + TEST_EQUAL(ec, error_code(bdecode_errors::depth_exceeded)); +} + +// test the item limit +TORRENT_TEST(item_limit) +{ + char b[10240]; + b[0] = 'l'; + std::ptrdiff_t i = 1; + for (i = 1; i < 10239; i += 2) + memcpy(&b[i], "0:", 2); + b[i] = 'e'; + + error_code ec; + bdecode_node e = bdecode({b, i + 1}, ec, nullptr, 1000, 1000); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded)); +} + +// test unexpected EOF +TORRENT_TEST(unepected_eof) +{ + char b[] = "l2:.."; // expected terminating 'e' + + error_code ec; + int pos; + bdecode_node e = bdecode({b, 5}, ec, &pos); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF in string length +TORRENT_TEST(unepected_eof2) +{ + char b[] = "l2:..0"; // expected ':' delimiter instead of EOF + + error_code ec; + int pos; + bdecode_node e = bdecode({b, 6}, ec, &pos); + TEST_EQUAL(pos, 6); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test expected string +TORRENT_TEST(expected_string) +{ + char b[] = "di2ei0ee"; + // expected string (dict keys must be strings) + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing dict key +TORRENT_TEST(unexpected_eof_dict_key) +{ + char b[] = "d1000:..e"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing dict key +TORRENT_TEST(unexpected_eof_dict_key2) +{ + char b[] = "d1000:"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test expected string while parsing dict key +TORRENT_TEST(expected_string_dict_key2) +{ + char b[] = "df00:"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing int +TORRENT_TEST(unexpected_eof_int) +{ + char b[] = "i"; + + error_code ec; + int pos; + bdecode_node e = bdecode({b, 1}, ec, &pos); + TEST_EQUAL(pos, 1); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test unexpected EOF while parsing int +TORRENT_TEST(unexpected_eof_int2) +{ + char b[] = "i10"; + + error_code ec; + int pos; + bdecode_node e = bdecode({b, 3}, ec, &pos); + TEST_EQUAL(pos, 3); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + + +// test expected colon +TORRENT_TEST(expected_colon_dict) +{ + char b[] = "d1000"; + + error_code ec; + int pos; + bdecode_node e = bdecode({b, 5}, ec, &pos); + TEST_EQUAL(pos, 5); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_colon)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test empty string +TORRENT_TEST(empty_string) +{ + error_code ec; + bdecode_node e = bdecode({}, ec, nullptr); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test partial string +TORRENT_TEST(partial_string) +{ + char b[] = "100:.."; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 3); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); +} + +TORRENT_TEST(list_ints) +{ + std::string buf; + buf += "l"; + for (int i = 0; i < 1000; ++i) + { + char tmp[20]; + std::snprintf(tmp, sizeof(tmp), "i%de", i); + buf += tmp; + } + buf += "e"; + + error_code ec; + bdecode_node e = bdecode(buf, ec); + TEST_CHECK(!ec); + TEST_EQUAL(e.type(), bdecode_node::list_t); + TEST_EQUAL(e.list_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + TEST_EQUAL(e.list_int_value_at(i), i); + } +} + +TORRENT_TEST(dict_ints) +{ + std::string buf; + buf += "d"; + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + std::snprintf(tmp, sizeof(tmp), "4:%04di%de", i, i); + buf += tmp; + } + buf += "e"; + + std::printf("%s\n", buf.c_str()); + error_code ec; + bdecode_node e = bdecode(buf, ec); + TEST_CHECK(!ec); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + std::snprintf(tmp, sizeof(tmp), "%04d", i); + TEST_EQUAL(e.dict_find_int_value(tmp), i); + } +} + +// test dict_at +TORRENT_TEST(dict_at) +{ + char b[] = "d3:fooi1e3:bari2ee"; + + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + + TEST_EQUAL(e.type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_size(), 2); + TEST_EQUAL(e.dict_at(0).first, "foo"); + TEST_EQUAL(e.dict_at(0).second.type(), bdecode_node::int_t); + TEST_EQUAL(e.dict_at(0).second.int_value(), 1); + TEST_EQUAL(e.dict_at(1).first, "bar"); + TEST_EQUAL(e.dict_at(1).second.type(), bdecode_node::int_t); + TEST_EQUAL(e.dict_at(1).second.int_value(), 2); +} + +// test string_ptr +TORRENT_TEST(string_ptr) +{ + char b[] = "l3:fooe"; + + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + + TEST_EQUAL(e.type(), bdecode_node::list_t); + TEST_EQUAL(e.list_size(), 1); + TEST_EQUAL(e.list_at(0).type(), bdecode_node::string_t); + TEST_EQUAL(e.list_at(0).string_ptr(), b + 3); + TEST_EQUAL(e.list_at(0).string_length(), 3); +} + +// test exceeding buffer size limit +TORRENT_TEST(exceed_buf_limit) +{ + char b[] = "l3:fooe"; + + error_code ec; + bdecode_node e = bdecode({b, 0x3fffffff}, ec); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded)); + std::printf("%s\n", print_entry(e).c_str()); +} + +// test parse_int +TORRENT_TEST(parse_int) +{ + char b[] = "1234567890e"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + char const* e = parse_int(b, b + sizeof(b)-1, 'e', val, ec); + TEST_EQUAL(ec, bdecode_errors::no_error); + TEST_EQUAL(val, 1234567890); + TEST_EQUAL(e, b + sizeof(b) - 2); +} + +// test invalid digit +TORRENT_TEST(invalid_digit) +{ + char b[] = "0o"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, 'e', val, ec); + TEST_EQUAL(ec, bdecode_errors::expected_digit); + TEST_EQUAL(e, b + 1); +} + +// test parse_int overflow +TORRENT_TEST(parse_int_overflow) +{ + char b[] = "9223372036854775808:"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, ':', val, ec); + TEST_EQUAL(ec, bdecode_errors::overflow); + TEST_EQUAL(e, b + 18); +} + +TORRENT_TEST(parse_length_overflow) +{ + string_view const b[] = { + "d1:a1919191010:11111"_sv, + "d2143289344:a4:aaaae"_sv, + "d214328934114:a4:aaaae"_sv, + "d9205357638345293824:a4:aaaae"_sv, + "d1:a9205357638345293824:11111"_sv + }; + + for (auto const& buf : b) + { + error_code ec; + bdecode_node e = bdecode(buf, ec); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } +} + + +TORRENT_TEST(expected_colon_string) +{ + char b[] = "928"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + char const* e = parse_int(b, b + sizeof(b)-1, ':', val, ec); + TEST_EQUAL(ec, bdecode_errors::no_error); + TEST_EQUAL(e, b + 3); +} + +// test dict_find_* functions +TORRENT_TEST(dict_find_funs) +{ + // a: int + // b: string + // c: list + // d: dict + char b[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + // dict_find_int* + + TEST_EQUAL(e.dict_find_int_value("a"), 1); + TEST_EQUAL(e.dict_find_int("a").type(), bdecode_node::int_t); + TEST_EQUAL(e.dict_find_int_value("b", -10), -10); + TEST_EQUAL(e.dict_find_int_value("x", -10), -10); + TEST_EQUAL(e.dict_find_int("b").type(), bdecode_node::none_t); + TEST_EQUAL(e.dict_find_int("x").type(), bdecode_node::none_t); + + // dict_find_string* + + TEST_EQUAL(e.dict_find_string_value("b"), "foo"); + TEST_EQUAL(e.dict_find_string("b").type(), bdecode_node::string_t); + TEST_EQUAL(e.dict_find_string_value("c", "blah"), "blah"); + TEST_EQUAL(e.dict_find_string_value("x", "blah"), "blah"); + TEST_EQUAL(e.dict_find_string("c").type(), bdecode_node::none_t); + TEST_EQUAL(e.dict_find_string("x").type(), bdecode_node::none_t); + + // dict_find_list + + TEST_CHECK(e.dict_find_list("c")); + TEST_EQUAL(e.dict_find_list("c").list_size(), 2); + TEST_EQUAL(e.dict_find_list("c").list_int_value_at(0), 1); + TEST_EQUAL(e.dict_find_list("c").list_int_value_at(1), 2); + TEST_CHECK(!e.dict_find_list("d")); + + // dict_find_dict + + TEST_CHECK(e.dict_find_dict("d")); + TEST_EQUAL(e.dict_find_dict("d").dict_find_int_value("x"), 1); + TEST_EQUAL(e.dict_find_dict("d").dict_find_int_value("y", -10), -10); + TEST_CHECK(!e.dict_find_dict("c")); + + // variants taking std::string + TEST_EQUAL(e.dict_find_dict(std::string("d")).dict_find_int_value("x"), 1); + TEST_CHECK(!e.dict_find_dict(std::string("c"))); + TEST_CHECK(!e.dict_find_dict(std::string("x"))); + + TEST_EQUAL(e.dict_size(), 4); + TEST_EQUAL(e.dict_size(), 4); + + // dict_at + + TEST_EQUAL(e.dict_at(0).first, "a"); + TEST_EQUAL(e.dict_at(0).second.int_value(), 1); + TEST_EQUAL(e.dict_at(1).first, "b"); + TEST_EQUAL(e.dict_at(1).second.string_value(), "foo"); + TEST_EQUAL(e.dict_at(2).first, "c"); + TEST_EQUAL(e.dict_at(2).second.type(), bdecode_node::list_t); + TEST_EQUAL(e.dict_at(3).first, "d"); + TEST_EQUAL(e.dict_at(3).second.type(), bdecode_node::dict_t); +} + +// test list_*_at functions +TORRENT_TEST(list_at_funs) +{ + // int + // string + // list + // dict + char b[] = "li1e3:fooli1ei2eed1:xi1eee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.type(), bdecode_node::list_t); + + TEST_EQUAL(e.list_int_value_at(0), 1); + // make sure default values work + TEST_EQUAL(e.list_int_value_at(1, -10), -10); + + TEST_EQUAL(e.list_string_value_at(1), "foo"); + // make sure default values work + TEST_EQUAL(e.list_string_value_at(2, "blah"), "blah"); + + TEST_EQUAL(e.list_at(2).type(), bdecode_node::list_t); + TEST_EQUAL(e.list_at(2).list_size(), 2); + TEST_EQUAL(e.list_at(2).list_int_value_at(0), 1); + TEST_EQUAL(e.list_at(2).list_int_value_at(1), 2); + + TEST_EQUAL(e.list_at(3).type(), bdecode_node::dict_t); + TEST_EQUAL(e.list_at(3).dict_size(), 1); + TEST_EQUAL(e.list_at(3).dict_find_int_value("x"), 1); + TEST_EQUAL(e.list_at(3).dict_find_int_value("y", -10), -10); + + TEST_EQUAL(e.list_size(), 4); + TEST_EQUAL(e.list_size(), 4); +} + +// test list_at in reverse order +TORRENT_TEST(list_at_reverse) +{ + // int + // string + // list + // dict + char b[] = "li1e3:fooli1ei2eed1:xi1eee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.type(), bdecode_node::list_t); + + TEST_EQUAL(e.list_at(3).type(), bdecode_node::dict_t); + TEST_EQUAL(e.list_at(2).type(), bdecode_node::list_t); + TEST_EQUAL(e.list_string_value_at(1), "foo"); + TEST_EQUAL(e.list_int_value_at(0), 1); + + TEST_EQUAL(e.list_size(), 4); + TEST_EQUAL(e.list_size(), 4); +} + + +// test dict_find_* functions +TORRENT_TEST(dict_find_funs2) +{ + // a: int + // b: string + // c: list + // d: dict + char b[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + // try finding the last item in a dict (to skip all the other ones) + TEST_EQUAL(e.dict_find("d").type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_find(std::string("d")).type(), bdecode_node::dict_t); +} + +// print_entry + +TORRENT_TEST(print_entry) +{ + char b[] = "li1e3:fooli1ei2eed1:xi1eee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "[ 1, 'foo', [ 1, 2 ], { 'x': 1 } ]"); +} + +TORRENT_TEST(print_entry2) +{ + char b[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + error_code ec; + bdecode_node e = bdecode(b, ec); + TEST_CHECK(!ec); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "{ 'a': 1, 'b': 'foo', 'c': [ 1, 2 ], 'd': { 'x': 1 } }"); +} + +// test swap() +TORRENT_TEST(swap) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + char b2[] = "i1e"; + + error_code ec; + + bdecode_node e1 = bdecode({b1, sizeof(b1)-1}, ec); + TEST_CHECK(!ec); + bdecode_node e2 = bdecode({b2, sizeof(b2)-1}, ec); + TEST_CHECK(!ec); + + std::string str1 = print_entry(e1); + std::string str2 = print_entry(e2); + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.type(), bdecode_node::int_t); + std::printf("%s\n", print_entry(e1).c_str()); + + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::int_t); + TEST_EQUAL(e2.type(), bdecode_node::dict_t); + TEST_EQUAL(print_entry(e1), str2); + TEST_EQUAL(print_entry(e2), str1); + std::printf("%s\n", print_entry(e1).c_str()); + + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.type(), bdecode_node::int_t); + TEST_EQUAL(print_entry(e1), str1); + TEST_EQUAL(print_entry(e2), str2); + std::printf("%s\n", print_entry(e1).c_str()); +} + +// test swap() (one node is the root of the other node) +TORRENT_TEST(swap_root) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + + error_code ec; + + bdecode_node e1 = bdecode({b1, sizeof(b1)-1}, ec); + TEST_CHECK(!ec); + + bdecode_node e2 = e1.dict_find("c").list_at(0); + + std::string str1 = print_entry(e1); + std::string str2 = print_entry(e2); + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.type(), bdecode_node::int_t); + std::printf("%s\n", print_entry(e1).c_str()); + + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::int_t); + TEST_EQUAL(e2.type(), bdecode_node::dict_t); + TEST_EQUAL(print_entry(e1), str2); + TEST_EQUAL(print_entry(e2), str1); + std::printf("%s\n", print_entry(e1).c_str()); + + // swap back + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.type(), bdecode_node::int_t); + TEST_EQUAL(print_entry(e1), str1); + TEST_EQUAL(print_entry(e2), str2); + std::printf("%s\n", print_entry(e1).c_str()); +} + +// test swap() (neither is a root and they don't share a root) +TORRENT_TEST(swap_disjoint) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + char b2[] = "li1e3:fooli1ei2eed1:xi1eee"; + + + error_code ec; + + bdecode_node e1_root = bdecode({b1, sizeof(b1)-1}, ec); + TEST_CHECK(!ec); + bdecode_node e2_root = bdecode({b2, sizeof(b2)-1}, ec); + TEST_CHECK(!ec); + + bdecode_node e1 = e1_root.dict_find("c").list_at(0); + bdecode_node e2 = e2_root.list_at(1); + + std::string str1 = print_entry(e1); + std::string str2 = print_entry(e2); + TEST_EQUAL(e1.type(), bdecode_node::int_t); + TEST_EQUAL(e2.type(), bdecode_node::string_t); + + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::string_t); + TEST_EQUAL(e2.type(), bdecode_node::int_t); + TEST_EQUAL(print_entry(e1), str2); + TEST_EQUAL(print_entry(e2), str1); + + // swap back + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::int_t); + TEST_EQUAL(e2.type(), bdecode_node::string_t); + TEST_EQUAL(print_entry(e1), str1); + TEST_EQUAL(print_entry(e2), str2); +} + +// test swap() (one is a root and they don't share a root) +TORRENT_TEST(swap_root_disjoint) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + char b2[] = "li1e3:fooli1ei2eed1:xi1eee"; + + + error_code ec; + + bdecode_node e1_root = bdecode({b1, sizeof(b1)-1}, ec); + TEST_CHECK(!ec); + bdecode_node e2 = bdecode({b2, sizeof(b2)-1}, ec); + TEST_CHECK(!ec); + + bdecode_node e1 = e1_root.dict_find("d"); + + std::string str1 = print_entry(e1); + std::string str2 = print_entry(e2); + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.type(), bdecode_node::list_t); + + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::list_t); + TEST_EQUAL(e2.type(), bdecode_node::dict_t); + TEST_EQUAL(print_entry(e1), str2); + TEST_EQUAL(print_entry(e2), str1); + + // swap back + e1.swap(e2); + + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.type(), bdecode_node::list_t); + TEST_EQUAL(print_entry(e1), str1); + TEST_EQUAL(print_entry(e2), str2); +} + +// make sure it's safe to reuse bdecode_nodes after clear() is called +TORRENT_TEST(clear) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + char b2[] = "li1ei2ee"; + + error_code ec; + bdecode_node e = bdecode({b1, sizeof(b1)-1}, ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(!ec); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + TEST_EQUAL(e.dict_size(), 4); + TEST_EQUAL(e.dict_at(1).first, "b"); + + e = bdecode({b2, sizeof(b2)-1}, ec); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(!ec); + TEST_EQUAL(e.type(), bdecode_node::list_t); + TEST_EQUAL(e.list_size(), 2); + TEST_EQUAL(e.list_int_value_at(1), 2); +} + +// assignment/copy of root nodes +TORRENT_TEST(copy_root) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + + error_code ec; + bdecode_node e1 = bdecode({b1, sizeof(b1)-1}, ec); + TEST_CHECK(!ec); + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + std::printf("%s\n", print_entry(e1).c_str()); + + bdecode_node e2(e1); + bdecode_node e3; + e3 = e1; + + e1.clear(); + + TEST_EQUAL(e2.type(), bdecode_node::dict_t); + TEST_EQUAL(e2.dict_size(), 4); + TEST_EQUAL(e2.dict_at(1).first, "b"); + + TEST_EQUAL(e3.type(), bdecode_node::dict_t); + TEST_EQUAL(e3.dict_size(), 4); + TEST_EQUAL(e3.dict_at(1).first, "b"); +} + +// non-owning references +TORRENT_TEST(non_owning_refs) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1eee"; + + error_code ec; + bdecode_node e1 = bdecode({b1, sizeof(b1)-1}, ec); + TEST_CHECK(!ec); + + TEST_EQUAL(e1.type(), bdecode_node::dict_t); + std::printf("%s\n", print_entry(e1).c_str()); + + bdecode_node e2 = e1.non_owning(); + + TEST_EQUAL(e2.type(), bdecode_node::dict_t); + + e1.clear(); + + // e2 is invalid now +} + +// test that a partial parse can be still be printed up to the +// point where it faild +TORRENT_TEST(partial_parse) +{ + char b[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:dd1:xi1-eee"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 35); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "{ 'a': 1, 'b': 'foo', 'c': [ 1, 2 ], 'd': { 'x': {} } }"); +} + +TORRENT_TEST(partial_parse2) +{ + char b[] = "d1:ai1e1:b3:foo1:cli1ei2ee1:d-d1:xi1eee"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 29); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "{ 'a': 1, 'b': 'foo', 'c': [ 1, 2 ], 'd': {} }"); +} + +TORRENT_TEST(partial_parse3) +{ + char b[] = "d1:ai1e1:b3:foo1:cli1ei2ee-1:dd1:xi1eee"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 26); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "{ 'a': 1, 'b': 'foo', 'c': [ 1, 2 ] }"); +} + +TORRENT_TEST(partial_parse4) +{ + char b[] = "d1:ai1e1:b3:foo1:cli1e-i2ee1:dd1:xi1eee"; + + error_code ec; + int pos; + bdecode_node e = bdecode(b, ec, &pos); + TEST_EQUAL(pos, 22); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(print_entry(e), "{ 'a': 1, 'b': 'foo', 'c': [ 1 ] }"); +} + +TORRENT_TEST(partial_parse_string) +{ + // it's important to not have a null terminator here + // to allow address sanitizer to trigger in case the decoder reads past the + // end + char b[] = { '5', '5'}; + + error_code ec; + int pos; + bdecode_node e = bdecode({b, sizeof(b)}, ec, &pos); + TEST_CHECK(ec); + TEST_EQUAL(pos, 2); +} + +// test switch_underlying_buffer +TORRENT_TEST(switch_buffer) +{ + char b1[] = "d1:ai1e1:b3:foo1:cli1e-i2ee1:dd1:xi1eee"; + char b2[] = "d1:ai1e1:b3:foo1:cli1e-i2ee1:dd1:xi1eee"; + + error_code ec; + int pos; + bdecode_node e = bdecode({b1, sizeof(b1)-1}, ec, &pos); + TEST_EQUAL(pos, 22); + TEST_EQUAL(e.type(), bdecode_node::dict_t); + + std::string string1 = print_entry(e); + std::printf("%s\n", string1.c_str()); + + e.switch_underlying_buffer(b2); + + std::string string2 = print_entry(e); + std::printf("%s\n", string2.c_str()); + + TEST_EQUAL(string1, string2); +} + diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp new file mode 100644 index 0000000..0636478 --- /dev/null +++ b/test/test_bencoding.cpp @@ -0,0 +1,745 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/bencode.hpp" +#include "libtorrent/bdecode.hpp" + +#include +#include +#include + +#if TORRENT_ABI_VERSION == 1 +#include "libtorrent/lazy_entry.hpp" +#endif + +#include "test.hpp" + +using namespace lt; + +// test vectors from bittorrent protocol description +// http://www.bittorrent.com/protocol.html + +namespace { + +std::string encode(entry const& e) +{ + std::string ret; + bencode(std::back_inserter(ret), e); + return ret; +} + +} // anonymous namespace + +TORRENT_TEST(strings) +{ + entry e("spam"); + TEST_CHECK(encode(e) == "4:spam"); + TEST_CHECK(bdecode(encode(e)) == e); +} + +TORRENT_TEST(integers) +{ + entry e(3); + TEST_CHECK(encode(e) == "i3e"); + TEST_CHECK(bdecode(encode(e)) == e); +} + +TORRENT_TEST(integers2) +{ + entry e(-3); + TEST_CHECK(encode(e) == "i-3e"); + TEST_CHECK(bdecode(encode(e)) == e); +} + +TORRENT_TEST(integers3) +{ + entry e(int(0)); + TEST_CHECK(encode(e) == "i0e"); + TEST_CHECK(bdecode(encode(e)) == e); +} + +TORRENT_TEST(lists) +{ + entry::list_type l; + l.push_back(entry("spam")); + l.push_back(entry("eggs")); + entry e(l); + TEST_CHECK(encode(e) == "l4:spam4:eggse"); + TEST_CHECK(bdecode(encode(e)) == e); +} + +TORRENT_TEST(dictionaries) +{ + entry e(entry::dictionary_t); + e["spam"] = entry("eggs"); + e["cow"] = entry("moo"); + TEST_CHECK(encode(e) == "d3:cow3:moo4:spam4:eggse"); + TEST_CHECK(bdecode(encode(e)) == e); +} + +TORRENT_TEST(preformatted) +{ + entry e(entry::preformatted_t); + char const str[] = "foobar"; + e.preformatted().assign(str, str + sizeof(str)-1); + TEST_EQUAL(encode(e), "foobar"); +} + +TORRENT_TEST(preformatted_node) +{ + entry e(entry::dictionary_t); + char const str[] = "foobar"; + e["info"] = entry::preformatted_type(str, str + sizeof(str)-1); + TEST_EQUAL(encode(e), "d4:infofoobare"); +} + +TORRENT_TEST(undefined_node) +{ + entry e(entry::undefined_t); + TEST_EQUAL(encode(e), "0:"); +} + +TORRENT_TEST(undefined_node2) +{ + entry e(entry::dictionary_t); + e["info"] = entry(entry::undefined_t); + TEST_EQUAL(encode(e), "d4:info0:e"); +} + +TORRENT_TEST(implicit_construct) +{ + entry e(entry::list_t); + e.list().push_back(entry::list_t); + TEST_EQUAL(e.list().back().type(), entry::list_t); +} + +TORRENT_TEST(print_dict_single_line) +{ + entry e; + e["foo"] = "bar"; + e["bar"] = "foo"; + TEST_EQUAL(e.to_string(true), "{ 'bar': 'foo', 'foo': 'bar' }"); +} + +TORRENT_TEST(print_dict) +{ + entry e; + e["foo"] = "bar"; + e["bar"] = "foo"; + TEST_EQUAL(e.to_string(), "{\n 'bar': 'foo',\n 'foo': 'bar' }"); +} + +TORRENT_TEST(print_list_single_line) +{ + entry e; + e.list().push_back(entry("foo")); + e.list().push_back(entry("bar")); + TEST_EQUAL(e.to_string(true), "[ 'foo', 'bar' ]"); +} + + +TORRENT_TEST(print_list) +{ + entry e; + e.list().push_back(entry("foo")); + e.list().push_back(entry("bar")); + TEST_EQUAL(e.to_string(), "[\n 'foo',\n 'bar' ]"); +} + +TORRENT_TEST(print_int_single_line) +{ + entry e(1337); + TEST_EQUAL(e.to_string(true), "1337"); +} + +TORRENT_TEST(print_int) +{ + entry e(1337); + TEST_EQUAL(e.to_string(), "1337"); +} + +TORRENT_TEST(print_string_single_line) +{ + entry e("foobar"); + TEST_EQUAL(e.to_string(true), "'foobar'"); +} + +TORRENT_TEST(print_string) +{ + entry e("foobar"); + TEST_EQUAL(e.to_string(), "'foobar'"); +} + +TORRENT_TEST(print_deep_dict_single_line) +{ + entry e; + e["strings"].list().push_back(entry("foo")); + e["strings"].list().push_back(entry("bar")); + e["ints"].list().push_back(entry(1)); + e["ints"].list().push_back(entry(2)); + e["ints"].list().push_back(entry(3)); + e["a"] = "foobar"; + TEST_EQUAL(e.to_string(true), "{ 'a': 'foobar', 'ints': [ 1, 2, 3 ], 'strings': [ 'foo', 'bar' ] }"); +} + +TORRENT_TEST(print_deep_dict) +{ + entry e; + e["strings"].list().push_back(entry("foo")); + e["strings"].list().push_back(entry("bar")); + e["ints"].list().push_back(entry(1)); + e["ints"].list().push_back(entry(2)); + e["ints"].list().push_back(entry(3)); + e["a"] = "foobar"; + TEST_EQUAL(e.to_string(), "{\n 'a': 'foobar',\n 'ints': [\n 1,\n 2,\n 3 ],\n 'strings': [\n 'foo',\n 'bar' ] }"); +} + +TORRENT_TEST(dict_constructor) +{ + entry::dictionary_type e{{std::string("foo"), std::string("bar")}, + {std::string("bar"), 1234}}; + + TEST_EQUAL(entry(e).to_string(), "{\n 'bar': 1234,\n 'foo': 'bar' }"); +} + +TORRENT_TEST(integer_to_str) +{ + using lt::detail::integer_to_str; + + char buf[30]; + TEST_CHECK(integer_to_str(buf, 0) == "0"_sv); + TEST_CHECK(integer_to_str(buf, 1234) == "1234"_sv); + TEST_CHECK(integer_to_str(buf, -1234) == "-1234"_sv); + TEST_CHECK(integer_to_str(buf, 123456789012345678LL) == "123456789012345678"_sv); + TEST_CHECK(integer_to_str(buf, -123456789012345678LL) == "-123456789012345678"_sv); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(lazy_entry) +{ + { + char b[] = "i12453e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, std::size_t(section.second)) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::int_t); + TEST_CHECK(e.int_value() == 12453); + } + + { + char b[] = "26:abcdefghijklmnopqrstuvwxyz"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, std::size_t(section.second)) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::string_t); + TEST_CHECK(e.string_value() == std::string("abcdefghijklmnopqrstuvwxyz")); + TEST_CHECK(e.string_length() == 26); + } + + { + char b[] = "li12453e3:aaae"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, std::size_t(section.second)) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::list_t); + TEST_CHECK(e.list_size() == 2); + TEST_CHECK(e.list_at(0)->type() == lazy_entry::int_t); + TEST_CHECK(e.list_at(1)->type() == lazy_entry::string_t); + TEST_CHECK(e.list_at(0)->int_value() == 12453); + TEST_CHECK(e.list_at(1)->string_value() == std::string("aaa")); + TEST_CHECK(e.list_at(1)->string_length() == 3); + section = e.list_at(1)->data_section(); + TEST_CHECK(std::memcmp("3:aaa", section.first, std::size_t(section.second)) == 0); + TEST_CHECK(section.second == 5); + } + + { + char b[] = "d1:ai12453e1:b3:aaa1:c3:bbb1:X10:0123456789e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + std::pair section = e.data_section(); + TEST_CHECK(std::memcmp(b, section.first, std::size_t(section.second)) == 0); + TEST_CHECK(section.second == sizeof(b) - 1); + TEST_CHECK(e.type() == lazy_entry::dict_t); + TEST_CHECK(e.dict_size() == 4); + TEST_CHECK(e.dict_find("a")->type() == lazy_entry::int_t); + TEST_CHECK(e.dict_find("a")->int_value() == 12453); + TEST_CHECK(e.dict_find("b")->type() == lazy_entry::string_t); + TEST_CHECK(e.dict_find("b")->string_value() == std::string("aaa")); + TEST_CHECK(e.dict_find("b")->string_length() == 3); + TEST_CHECK(e.dict_find("c")->type() == lazy_entry::string_t); + TEST_CHECK(e.dict_find("c")->string_value() == std::string("bbb")); + TEST_CHECK(e.dict_find("c")->string_length() == 3); + TEST_CHECK(e.dict_find_string_value("X") == "0123456789"); + } + + // dictionary key with \0 + { + char b[] = "d3:a\0bi1ee"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + TEST_CHECK(e.dict_size() == 1); + lazy_entry* d = e.dict_find({"a\0b", 3}); + TEST_CHECK(d); + TEST_EQUAL(d->type(), lazy_entry::int_t); + TEST_EQUAL(d->int_value(), 1); + } + + // test strings with negative length-prefix + { + char b[] = "-10:foobar"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_value)); + } + + // test strings with overflow length-prefix + { + char b[] = "18446744073709551615:foobar"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::overflow)); + } + + // test integers that don't fit in 64 bits + { + char b[] = "i18446744073709551615e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + // the lazy aspect makes this overflow when asking for + // the value. turning it to zero. + TEST_CHECK(e.int_value() == 0); + } + + // test integers that just exactly fit in 64 bits + { + char b[] = "i9223372036854775807e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(e.int_value() == 9223372036854775807LL); + } + + // test integers that just exactly fit in 64 bits + { + char b[] = "i-9223372036854775807e"; + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec); + TEST_CHECK(ret == 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_CHECK(e.int_value() == -9223372036854775807LL); + } + + // test invalid encoding + { + unsigned char buf[] = + { 0x64, 0x31, 0x3a, 0x61, 0x64, 0x32, 0x3a, 0x69 + , 0x64, 0x32, 0x30, 0x3a, 0x2a, 0x21, 0x19, 0x89 + , 0x9f, 0xcd, 0x5f, 0xc9, 0xbc, 0x80, 0xc1, 0x76 + , 0xfe, 0xe0, 0xc6, 0x84, 0x2d, 0xf6, 0xfc, 0xb8 + , 0x39, 0x3a, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x68 + , 0x61, 0xae, 0x68, 0x32, 0x30, 0x3a, 0x14, 0x78 + , 0xd5, 0xb0, 0xdc, 0xf6, 0x82, 0x42, 0x32, 0xa0 + , 0xd6, 0x88, 0xeb, 0x48, 0x57, 0x01, 0x89, 0x40 + , 0x4e, 0xbc, 0x65, 0x31, 0x3a, 0x71, 0x39, 0x3a + , 0x67, 0x65, 0x74, 0x5f, 0x70, 0x65, 0x65, 0x72 + , 0x78, 0xff, 0x3a, 0x74, 0x38, 0x3a, 0xaa, 0xd4 + , 0xa1, 0x88, 0x7a, 0x8d, 0xc3, 0xd6, 0x31, 0x3a + , 0x79, 0x31, 0xae, 0x71, 0x65, 0}; + + std::printf("%s\n", buf); + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(reinterpret_cast(buf), reinterpret_cast(buf) + sizeof(buf), e, ec); + TEST_CHECK(ret == -1); + } + + // test the depth limit + { + char b[2048]; + for (int i = 0; i < 1024; ++i) + b[i]= 'l'; + + for (int i = 1024; i < 2048; ++i) + b[i]= 'e'; + + // 1024 levels nested lists + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b), e, ec); + TEST_CHECK(ret != 0); + TEST_EQUAL(ec, error_code(bdecode_errors::depth_exceeded)); + } + + // test the item limit + { + char b[10240]; + b[0] = 'l'; + int i = 1; + for (i = 1; i < 10239; i += 2) + memcpy(&b[i], "0:", 2); + b[i] = 'e'; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + i + 1, e, ec, nullptr, 1000, 1000); + TEST_CHECK(ret != 0); + TEST_EQUAL(ec, error_code(bdecode_errors::limit_exceeded)); + } + + // test unexpected EOF + { + char b[] = "l2:.."; // expected terminating 'e' + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + // test unexpected EOF (really expected terminator) + { + char b[] = "l2:..0"; // expected terminating 'e' instead of '0' + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + // test expected string + { + char b[] = "di2ei0ee"; + // expected string (dict keys must be strings) + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + } + + // test unexpected EOF while parsing dict key + { + char b[] = "d1000:..e"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + // test unexpected EOF while parsing dict key + { + char b[] = "d1000:"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + // test expected string while parsing dict key + { + char b[] = "df00:"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_digit)); + } + + // test unexpected EOF while parsing int + { + char b[] = "i"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + // test unexpected EOF while parsing int + { + char b[] = "i10"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + + // test expected colon + { + char b[] = "d1000"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::expected_colon)); + } + + // test empty string + { + char b[] = ""; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_EQUAL(ret, -1); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); + } + + // test partial string + { + char b[] = "100:.."; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_CHECK(ret != 0); + std::printf("%s\n", print_entry(e).c_str()); + TEST_EQUAL(ec, error_code(bdecode_errors::unexpected_eof)); + } + + // test pascal string dict + { + char b[] = "d6:foobar6:barfooe"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_EQUAL(ret, 0); + std::printf("%s\n", print_entry(e).c_str()); + + pascal_string ps = e.dict_find_pstr("foobar"); + TEST_EQUAL(std::memcmp(ps.ptr, "barfoo", std::size_t(ps.len)), 0); + TEST_EQUAL(ps.len, 6); + + ps = e.dict_find_pstr("foobar2"); + TEST_EQUAL(ps.ptr, static_cast(nullptr)); + TEST_EQUAL(ps.len, 0); + } + + // test pascal string in list + { + char b[] = "l6:foobari4ee"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(b, b + sizeof(b)-1, e, ec, nullptr); + TEST_EQUAL(ret, 0); + std::printf("%s\n", print_entry(e).c_str()); + + TEST_EQUAL(e.list_size(), 2); + pascal_string ps = e.list_pstr_at(0); + TEST_EQUAL(std::memcmp(ps.ptr, "foobar", std::size_t(ps.len)), 0); + TEST_EQUAL(ps.len, 6); + + ps = e.list_pstr_at(1); + TEST_EQUAL(ps.ptr, static_cast(nullptr)); + TEST_EQUAL(ps.len, 0); + } + + { + unsigned char buf[] = { 0x44, 0x91, 0x3a }; + error_code ec; + entry ent = bdecode({reinterpret_cast(buf), int(sizeof(buf))}, ec); + TEST_CHECK(ent == entry()); + } + + { + std::string buf; + buf += "l"; + for (int i = 0; i < 1000; ++i) + { + char tmp[20]; + std::snprintf(tmp, sizeof(tmp), "i%de", i); + buf += tmp; + } + buf += "e"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(buf.data(), buf.data() + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), lazy_entry::list_t); + TEST_EQUAL(e.list_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + TEST_EQUAL(e.list_int_value_at(i), i); + } + } + + { + std::string buf; + buf += "d"; + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + std::snprintf(tmp, sizeof(tmp), "4:%04di%de", i, i); + buf += tmp; + } + buf += "e"; + + std::printf("%s\n", buf.c_str()); + lazy_entry e; + error_code ec; + int ret = lazy_bdecode(buf.data(), buf.data() + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), lazy_entry::dict_t); + TEST_EQUAL(e.dict_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + std::snprintf(tmp, sizeof(tmp), "%04d", i); + TEST_EQUAL(e.dict_find_int_value(tmp), i); + } + } + + // test parse_int + { + char b[] = "1234567890e"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + char const* e = parse_int(b, b + sizeof(b)-1, 'e', val, ec); + TEST_CHECK(ec == bdecode_errors::no_error); + TEST_EQUAL(val, 1234567890); + TEST_EQUAL(e, b + sizeof(b) - 2); + } + + // test invalid digit + { + char b[] = "0o"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, 'e', val, ec); + TEST_EQUAL(ec, bdecode_errors::expected_digit); + TEST_EQUAL(e, b + 1); + } + + { + char b[] = "9223372036854775808:"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec; + char const* e = parse_int(b, b + sizeof(b)-1, ':', val, ec); + TEST_CHECK(ec == bdecode_errors::overflow); + TEST_EQUAL(e, b + 18); + } + + { + char b[] = "928"; + std::int64_t val = 0; + bdecode_errors::error_code_enum ec = bdecode_errors::no_error; + char const* e = parse_int(b, b + sizeof(b)-1, ':', val, ec); + TEST_CHECK(ec == bdecode_errors::no_error); + TEST_EQUAL(e, b + 3); + } + + { + char const* b[] = { + "d1:a1919191010:11111", + "d2143289344:a4:aaaae", + "d214328934114:a4:aaaae", + "d9205357638345293824:a4:aaaae", + "d1:a9205357638345293824:11111", + }; + + for (int i = 0; i < int(sizeof(b)/sizeof(b[0])); ++i) + { + lazy_entry tmp; + error_code ec; + int ret = lazy_bdecode(b[i], b[i] + strlen(b[i]), tmp, ec, nullptr); + lazy_entry e; + e = std::move(tmp); + TEST_EQUAL(ret, -1); + TEST_CHECK(ec == error_code(bdecode_errors::unexpected_eof)); + std::printf("%s\n", print_entry(e).c_str()); + + lazy_entry* moved = new lazy_entry(std::move(e)); + delete moved; + } + } +} +#endif // TORRENT_ABI_VERSION diff --git a/test/test_bitfield.cpp b/test/test_bitfield.cpp new file mode 100644 index 0000000..ec416dc --- /dev/null +++ b/test/test_bitfield.cpp @@ -0,0 +1,428 @@ +/* + +Copyright (c) 2008-2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/aux_/cpuid.hpp" +#include + +using namespace lt; + +namespace { + +void print_bitfield(bitfield const& b) +{ + std::string out; + out.reserve(std::size_t(b.size())); + for (bool bit : b) + out += bit ? '1' : '0'; + std::printf("%s\n", out.c_str()); +} + +void test_iterators(bitfield& test1) +{ + test1.set_all(); + int num = 0; + + std::printf("expecting %d ones\n", test1.size()); + for (bitfield::const_iterator i = test1.begin(); i != test1.end(); ++i) + { + std::printf("%d", *i); + TEST_EQUAL(*i, true); + num += *i; + } + std::printf("\n"); + TEST_EQUAL(num, test1.size()); + TEST_EQUAL(num, test1.count()); +} + +} // anonymous namespace + +TORRENT_TEST(bitfield) +{ + bitfield test1(10, false); + TEST_EQUAL(test1.size(), 10); + TEST_EQUAL(test1.empty(), false); + TEST_EQUAL(test1.count(), 0); + test1.set_bit(9); + TEST_EQUAL(test1.count(), 1); + test1.clear_bit(9); + TEST_EQUAL(test1.count(), 0); + test1.set_bit(2); + TEST_EQUAL(test1.count(), 1); + test1.set_bit(1); + test1.set_bit(9); + TEST_EQUAL(test1.count(), 3); + TEST_CHECK(test1.all_set() == false); + test1.clear_bit(2); + TEST_EQUAL(test1.count(), 2); + int distance = int(std::distance(test1.begin(), test1.end())); + std::printf("distance: %d\n", distance); + TEST_CHECK(distance == 10); + + print_bitfield(test1); + + test1.set_all(); + TEST_EQUAL(test1.count(), 10); + + test1.clear_all(); + TEST_EQUAL(test1.count(), 0); + + test1.resize(2); + test1.set_bit(0); + test1.resize(16, true); + TEST_EQUAL(test1.count(), 15); + test1.resize(20, true); + TEST_EQUAL(test1.count(), 19); + TEST_EQUAL(test1.get_bit(0), true); + TEST_EQUAL(test1.get_bit(1), false); + + bitfield test2 = test1; + print_bitfield(test2); + TEST_EQUAL(test2.count(), 19); + TEST_EQUAL(test2.get_bit(0), true); + TEST_EQUAL(test2.get_bit(1), false); + TEST_EQUAL(test2.get_bit(2), true); + + test1.set_bit(1); + test1.resize(1); + TEST_EQUAL(test1.count(), 1); + + test1.resize(100, true); + TEST_CHECK(test1.all_set() == true); + TEST_EQUAL(test1.count(), 100); + test1.resize(200, false); + TEST_CHECK(test1.all_set() == false); + TEST_EQUAL(test1.count(), 100); + test1.resize(50, false); + TEST_CHECK(test1.all_set() == true); + TEST_EQUAL(test1.count(), 50); + test1.resize(101, true); + TEST_CHECK(test1.all_set() == true); + TEST_EQUAL(test1.count(), 101); + + std::uint8_t b1[] = { 0x08, 0x10 }; + test1.assign(reinterpret_cast(b1), 14); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 2); + TEST_EQUAL(test1.get_bit(3), false); + TEST_EQUAL(test1.get_bit(4), true); + TEST_EQUAL(test1.get_bit(5), false); + TEST_EQUAL(test1.get_bit(10), false); + TEST_EQUAL(test1.get_bit(11), true); + TEST_EQUAL(test1.get_bit(12), false); + + test1 = bitfield(); + TEST_EQUAL(test1.size(), 0); + TEST_EQUAL(test1.empty(), true); + TEST_EQUAL(bitfield().empty(), true); + + test1 = test2; + TEST_EQUAL(test1.size(), 20); + TEST_EQUAL(test1.count(), 19); + TEST_EQUAL(test1.get_bit(0), true); + TEST_EQUAL(test1.get_bit(1), false); + TEST_EQUAL(test1.get_bit(2), true); +} + +TORRENT_TEST(test_assign3) +{ + bitfield test1; + std::uint8_t b2[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc, 0x7f }; + test1.assign(reinterpret_cast(b2), 72); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 47); + + std::uint8_t b3[] = { 0x08, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf, 0xc }; + test1.assign(reinterpret_cast(b3), 64); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 40); +} + +TORRENT_TEST(test_iterators) +{ + bitfield test1; + for (int i = 0; i < 100; ++i) + { + test1.resize(i, false); + test_iterators(test1); + } +} + +TORRENT_TEST(test_assign) +{ + std::array b; + bitfield test1; + + for (std::size_t i = 0; i < 4; ++i) + { + b[i] = char(0xc0); + test1.assign(&b[i], 2); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 2); + TEST_EQUAL(test1.all_set(), true); + } +} + +TORRENT_TEST(test_assign2) +{ + std::array b; + bitfield test1; + for (std::size_t i = 0; i < 4; ++i) + { + std::memset(&b[i], 0xff, 5); + b[i + 5] = char(0xc0); + test1.assign(&b[i], 32 + 8 + 2); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 32 + 8 + 2); + TEST_EQUAL(test1.all_set(), true); + } + +#if !TORRENT_HAS_ARM + TORRENT_ASSERT(!aux::arm_neon_support); +#endif +} + +TORRENT_TEST(test_resize_val) +{ + std::array b; + b.fill(-52); + + bitfield test1(b.data(), 8 * 8); + print_bitfield(test1); + TEST_EQUAL(test1.size(), 8 * 8); + TEST_EQUAL(test1.count(), 4 * 8); + + for (int i = 1; i < 4 * 8; ++i) + { + test1.resize(8 * 8 + i, true); + print_bitfield(test1); + TEST_EQUAL(test1.count(), 4 * 8 + i); + } +} + +TORRENT_TEST(test_resize_up) +{ + std::array b; + b.fill(-52); + + bitfield test1(b.data(), 8 * 8); + print_bitfield(test1); + TEST_EQUAL(test1.size(), 8 * 8); + TEST_EQUAL(test1.count(), 4 * 8); + + for (int i = 1; i < 5 * 8; ++i) + { + test1.resize(8 * 8 + i); + print_bitfield(test1); + TEST_EQUAL(test1.size(), 8 * 8 + i); + TEST_EQUAL(test1.count(), 4 * 8); + } +} + +TORRENT_TEST(test_resize_down) +{ + std::array b; + b.fill(0x55); + + bitfield test1(b.data(), 8 * 8); + + for (int i = 8 * 8; i > -1; --i) + { + test1.resize(i); + print_bitfield(test1); + TEST_EQUAL(test1.size(), i); + TEST_EQUAL(test1.count(), i / 2); + } +} + +TORRENT_TEST(find_first_set_empty) +{ + bitfield test1(0); + TEST_EQUAL(test1.find_first_set(), -1); +} + +TORRENT_TEST(find_first_set_small) +{ + bitfield test1(10, false); + TEST_EQUAL(test1.find_first_set(), -1); +} + +TORRENT_TEST(find_first_set_large) +{ + bitfield test1(100, false); + TEST_EQUAL(test1.find_first_set(), -1); +} + +TORRENT_TEST(find_first_set_early) +{ + bitfield test1(100, false); + test1.set_bit(4); + TEST_EQUAL(test1.find_first_set(), 4); +} + +TORRENT_TEST(find_first_set_late) +{ + bitfield test1(100, false); + test1.set_bit(98); + TEST_EQUAL(test1.find_first_set(), 98); +} + +TORRENT_TEST(find_last_clear_empty) +{ + bitfield test1(0); + TEST_EQUAL(test1.find_last_clear(), -1); +} + +TORRENT_TEST(find_last_clear_small) +{ + bitfield test1(10, true); + TEST_EQUAL(test1.find_last_clear(), -1); +} + +TORRENT_TEST(find_last_clear_large) +{ + bitfield test1(100, true); + TEST_EQUAL(test1.find_last_clear(), -1); +} + +TORRENT_TEST(find_last_clear_early) +{ + bitfield test1(100, true); + test1.clear_bit(4); + TEST_EQUAL(test1.find_last_clear(), 4); +} + +TORRENT_TEST(find_last_clear_late) +{ + bitfield test1(100, true); + test1.clear_bit(98); + TEST_EQUAL(test1.find_last_clear(), 98); +} + +TORRENT_TEST(find_last_clear_misc) +{ + bitfield test1(100, true); + test1.clear_bit(11); + test1.clear_bit(91); + TEST_EQUAL(test1.find_last_clear(), 91); + + bitfield test2(78, true); + test2.clear_bit(12); + test2.clear_bit(43); + test2.clear_bit(34); + TEST_EQUAL(test2.find_last_clear(), 43); + + bitfield test3(123, true); + test3.clear_bit(49); + test3.clear_bit(33); + test3.clear_bit(32); + test3.clear_bit(50); + TEST_EQUAL(test3.find_last_clear(), 50); + + bitfield test4(1000, true); + test4.clear_bit(11); + test4.clear_bit(91); + test4.clear_bit(14); + test4.clear_bit(15); + test4.clear_bit(89); + TEST_EQUAL(test4.find_last_clear(), 91); +} + +TORRENT_TEST(not_initialized) +{ + // check a not initialized empty bitfield + bitfield test1(0); + TEST_EQUAL(test1.none_set(), true); + TEST_EQUAL(test1.all_set(), false); + TEST_EQUAL(test1.size(), 0); + TEST_EQUAL(test1.num_words(), 0); + TEST_EQUAL(test1.empty(), true); + TEST_CHECK(test1.data() == nullptr); + TEST_EQUAL(test1.count(), 0); + TEST_EQUAL(test1.find_first_set(), -1); + TEST_EQUAL(test1.find_last_clear(), -1); + + test1.clear_all(); + TEST_EQUAL(test1.size(), 0); + + test1.clear(); + TEST_EQUAL(test1.size(), 0); + + test1.set_all(); + TEST_EQUAL(test1.size(), 0); + + // don't test methods which aren't defined for empty sets: + // get_bit, clear_bit, set_bit +} + +TORRENT_TEST(self_assign) +{ + bitfield test1(123, false); + bitfield* self_ptr = &test1; + test1 = *self_ptr; + TEST_EQUAL(test1.size(), 123); + TEST_EQUAL(test1.count(), 0); +} + +TORRENT_TEST(not_initialized_assign) +{ + // check a not initialized empty bitfield + bitfield test1(0); + std::uint8_t b1[] = { 0xff }; + test1.assign(reinterpret_cast(b1), 8); + TEST_EQUAL(test1.count(), 8); +} + +TORRENT_TEST(not_initialized_resize) +{ + // check a not initialized empty bitfield + bitfield test1(0); + test1.resize(8, true); + TEST_EQUAL(test1.count(), 8); + + bitfield test2(0); + test2.resize(8); + TEST_EQUAL(test2.size(), 8); +} + +TORRENT_TEST(bitfield_index_range) +{ + typed_bitfield b1(16); + int sum = 0; + for (auto i : b1.range()) + { + sum += i; + } + TEST_EQUAL(sum, 15 * 16 / 2); +} + diff --git a/test/test_block_cache.cpp b/test/test_block_cache.cpp new file mode 100644 index 0000000..4c7c1bd --- /dev/null +++ b/test/test_block_cache.cpp @@ -0,0 +1,515 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/aux_/path.hpp" // for bufs_size + +#include +#include + +using namespace lt; + +namespace { + +struct test_storage_impl : storage_interface +{ + explicit test_storage_impl(file_storage const& fs) : storage_interface(fs) {} + void initialize(storage_error&) override {} + + int readv(span bufs + , piece_index_t, int /*offset*/, open_mode_t, storage_error&) override + { + return bufs_size(bufs); + } + int writev(span bufs + , piece_index_t, int /*offset*/, open_mode_t, storage_error&) override + { + return bufs_size(bufs); + } + + bool has_any_file(storage_error&) override { return false; } + void set_file_priority(aux::vector& + , storage_error&) override {} + status_t move_storage(std::string const&, move_flags_t + , storage_error&) override { return status_t::no_error; } + bool verify_resume_data(add_torrent_params const& + , aux::vector const& + , storage_error&) override { return true; } + void release_files(storage_error&) override {} + void rename_file(file_index_t, std::string const& + , storage_error&) override {} + void delete_files(remove_flags_t, storage_error&) override {} +}; + +struct allocator : buffer_allocator_interface +{ + allocator(block_cache& bc, storage_interface* st) + : m_cache(bc), m_storage(st) {} + + void free_disk_buffer(char* b) override + { m_cache.free_buffer(b); } + + void reclaim_blocks(span refs) override + { + for (auto ref : refs) + m_cache.reclaim_block(m_storage, ref); + } + + virtual ~allocator() = default; +private: + block_cache& m_cache; + storage_interface* m_storage; +}; + +static void nop() {} + +#if TORRENT_USE_ASSERTS +#define INITIALIZE_JOB(j) j.in_use = true; +#else +#define INITIALIZE_JOB(j) +#endif + +#define TEST_SETUP \ + io_service ios; \ + block_cache bc(ios, std::bind(&nop)); \ + aux::session_settings sett; \ + file_storage fs; \ + fs.add_file("a/test0", 0x4000); \ + fs.add_file("a/test1", 0x4000); \ + fs.add_file("a/test2", 0x4000); \ + fs.add_file("a/test3", 0x4000); \ + fs.add_file("a/test4", 0x4000); \ + fs.add_file("a/test5", 0x4000); \ + fs.add_file("a/test6", 0x4000); \ + fs.add_file("a/test7", 0x4000); \ + fs.set_piece_length(0x8000); \ + fs.set_num_pieces(5); \ + std::shared_ptr pm \ + = std::make_shared(fs); \ + allocator alloc(bc, pm.get()); \ + bc.set_settings(sett); \ + pm->m_settings = &sett; \ + disk_io_job rj; \ + disk_io_job wj; \ + INITIALIZE_JOB(rj) \ + INITIALIZE_JOB(wj) \ + rj.storage = pm; \ + wj.storage = pm; \ + cached_piece_entry* pe = nullptr; \ + int ret = 0; \ + iovec_t iov; \ + (void)iov; \ + (void)ret; \ + (void)pe + +#define WRITE_BLOCK(p, b) \ + wj.flags = disk_io_job::in_progress; \ + wj.action = job_action_t::write; \ + wj.d.io.offset = (b) * 0x4000; \ + wj.d.io.buffer_size = 0x4000; \ + wj.piece = piece_index_t(p); \ + wj.argument = disk_buffer_holder(alloc, bc.allocate_buffer("write-test"), 0x4000); \ + pe = bc.add_dirty_block(&wj, true) + +#define READ_BLOCK(p, b, r) \ + rj.action = job_action_t::read; \ + rj.d.io.offset = (b) * 0x4000; \ + rj.d.io.buffer_size = 0x4000; \ + rj.piece = piece_index_t(p); \ + rj.storage = pm; \ + rj.argument = disk_buffer_holder(alloc, nullptr, 0); \ + ret = bc.try_read(&rj, alloc) + +#define FLUSH(flushing) \ + for (int i = 0; i < int(sizeof(flushing)/sizeof((flushing)[0])); ++i) \ + { \ + pe->blocks[(flushing)[i]].pending = true; \ + bc.inc_block_refcount(pe, 0, block_cache::ref_flushing); \ + } \ + bc.blocks_flushed(pe, flushing, sizeof(flushing)/sizeof((flushing)[0])) + +#define INSERT(p, b) \ + wj.piece = piece_index_t(p); \ + pe = bc.allocate_piece(&wj, cached_piece_entry::read_lru1); \ + ret = bc.allocate_iovec(iov); \ + TEST_EQUAL(ret, 0); \ + bc.insert_blocks(pe, b, iov, &wj) + +void test_write() +{ + TEST_SETUP; + + // write block (0,0) + WRITE_BLOCK(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 1); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 1); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + // try to read it back + READ_BLOCK(0, 0, 1); + TEST_EQUAL(bc.pinned_blocks(), 1); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + + // return the reference to the buffer we just read + rj.argument = remove_flags_t{}; + + TEST_EQUAL(bc.pinned_blocks(), 0); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 0); + + // try to read block (1, 0) + READ_BLOCK(1, 0, 1); + + // that's supposed to be a cache miss + TEST_CHECK(ret < 0); + TEST_EQUAL(bc.pinned_blocks(), 0); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 0); + + rj.argument = remove_flags_t{}; + + tailqueue jobs; + bc.clear(jobs); +} + +void test_flush() +{ + TEST_SETUP; + + // write block (0,0) + WRITE_BLOCK(0, 0); + + // pretend to flush to disk + int flushing[1] = {0}; + FLUSH(flushing); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_insert() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_evict() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + // this should make it not be evicted + // just free the buffers + ++pe->piece_refcount; + bc.evict_piece(pe, jobs, block_cache::allow_ghost); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + --pe->piece_refcount; + bc.evict_piece(pe, jobs, block_cache::allow_ghost); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 1); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + bc.clear(jobs); +} + +// test to have two different requestors read a block and +// make sure it moves into the MFU list +void test_arc_promote() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + READ_BLOCK(0, 0, 1); + TEST_EQUAL(bc.pinned_blocks(), 1); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + rj.argument = remove_flags_t{}; + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + READ_BLOCK(0, 0, 2); + TEST_EQUAL(bc.pinned_blocks(), 1); + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + rj.argument = remove_flags_t{}; + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 1); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_arc_unghost() +{ + TEST_SETUP; + + INSERT(0, 0); + + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 1); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + tailqueue jobs; + bc.evict_piece(pe, jobs, block_cache::allow_ghost); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 0); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 1); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + // the block is now a ghost. If we cache-hit it, + // it should be promoted back to the main list + bc.cache_hit(pe, 0, false); + + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::write_cache_blocks], 0); + // we didn't actually read in any blocks, so the cache size + // is still 0 + TEST_EQUAL(c[counters::read_cache_blocks], 0); + TEST_EQUAL(c[counters::pinned_blocks], 0); + TEST_EQUAL(c[counters::arc_mru_size], 1); + TEST_EQUAL(c[counters::arc_mru_ghost_size], 0); + TEST_EQUAL(c[counters::arc_mfu_size], 0); + TEST_EQUAL(c[counters::arc_mfu_ghost_size], 0); + TEST_EQUAL(c[counters::arc_write_size], 0); + TEST_EQUAL(c[counters::arc_volatile_size], 0); + + bc.clear(jobs); +} + +void test_iovec() +{ + TEST_SETUP; + + ret = bc.allocate_iovec(iov); + TEST_EQUAL(ret, 0); + bc.free_iovec(iov); +} + +void test_unaligned_read() +{ + TEST_SETUP; + + INSERT(0, 0); + INSERT(0, 1); + + rj.action = job_action_t::read; + rj.d.io.offset = 0x2000; + rj.d.io.buffer_size = 0x4000; + rj.piece = piece_index_t(0); + rj.storage = pm; + rj.argument = disk_buffer_holder(alloc, nullptr, 0); + ret = bc.try_read(&rj, alloc); + + // unaligned reads copies the data into a new buffer + // rather than + TEST_EQUAL(bc.pinned_blocks(), 0); + counters c; + bc.update_stats_counters(c); + TEST_EQUAL(c[counters::pinned_blocks], 0); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + rj.argument = remove_flags_t{}; + + tailqueue jobs; + bc.clear(jobs); +} + +} // anonymous namespace + +TORRENT_TEST(block_cache) +{ + test_write(); + test_flush(); + test_insert(); + test_evict(); + test_arc_promote(); + test_arc_unghost(); + test_iovec(); + test_unaligned_read(); + + // TODO: test try_evict_blocks + // TODO: test evicting volatile pieces, to see them be removed + // TODO: test evicting dirty pieces + // TODO: test free_piece + // TODO: test abort_dirty + // TODO: test unaligned reads +} + +TORRENT_TEST(delete_piece) +{ + TEST_SETUP; + + TEST_CHECK(bc.num_pieces() == 0); + + INSERT(0, 0); + + TEST_CHECK(bc.num_pieces() == 1); + + rj.action = job_action_t::read; + rj.d.io.offset = 0x2000; + rj.d.io.buffer_size = 0x4000; + rj.piece = piece_index_t(0); + rj.storage = pm; + rj.argument = remove_flags_t{}; + ret = bc.try_read(&rj, alloc); + TEST_EQUAL(ret, -1); + + cached_piece_entry* pe_ = bc.find_piece(pm.get(), piece_index_t(0)); + bc.mark_for_eviction(pe_, block_cache::disallow_ghost); + + TEST_CHECK(bc.num_pieces() == 0); +} diff --git a/test/test_bloom_filter.cpp b/test/test_bloom_filter.cpp new file mode 100644 index 0000000..874d86b --- /dev/null +++ b/test/test_bloom_filter.cpp @@ -0,0 +1,136 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/sha1_hash.hpp" +#include + +using namespace lt; + +namespace { + +void test_set_and_get() +{ + bloom_filter<32> filter; + sha1_hash k1 = hasher("test1", 5).final(); + sha1_hash k2 = hasher("test2", 5).final(); + sha1_hash k3 = hasher("test3", 5).final(); + sha1_hash k4 = hasher("test4", 5).final(); + TEST_CHECK(!filter.find(k1)); + TEST_CHECK(!filter.find(k2)); + TEST_CHECK(!filter.find(k3)); + TEST_CHECK(!filter.find(k4)); + + filter.set(k1); + TEST_CHECK(filter.find(k1)); + TEST_CHECK(!filter.find(k2)); + TEST_CHECK(!filter.find(k3)); + TEST_CHECK(!filter.find(k4)); + + filter.set(k4); + TEST_CHECK(filter.find(k1)); + TEST_CHECK(!filter.find(k2)); + TEST_CHECK(!filter.find(k3)); + TEST_CHECK(filter.find(k4)); +} + +void test_set_bits() +{ + std::uint8_t bits[4] = {0x00, 0x00, 0x00, 0x00}; + + for (int i = 0; i < 4 * 8; ++i) + { + std::uint8_t t[4] = { std::uint8_t(i & 0xff), 0, std::uint8_t(i & 0xff), 0 }; + TEST_CHECK(!has_bits(t, bits, 6)); + } + + for (int i = 0; i < 4 * 8; i += 2) + { + std::uint8_t t[4] = { std::uint8_t(i & 0xff), 0, std::uint8_t(i & 0xff), 0 }; + TEST_CHECK(!has_bits(t, bits, 4)); + set_bits(t, bits, 4); + TEST_CHECK(has_bits(t, bits, 4)); + } + + std::uint8_t compare[4] = { 0x55, 0x55, 0x55, 0x55}; + TEST_EQUAL(memcmp(compare, bits, 4), 0); +} + +void test_count_zeroes() +{ + std::uint8_t bits[4] = {0x00, 0xff, 0x55, 0xaa}; + + TEST_EQUAL(count_zero_bits(bits, 4), 16); + + std::uint8_t t[4] = { 4, 0, 4, 0 }; + set_bits(t, bits, 4); + TEST_EQUAL(count_zero_bits(bits, 4), 15); + + std::uint8_t compare[4] = { 0x10, 0xff, 0x55, 0xaa}; + TEST_EQUAL(memcmp(compare, bits, 4), 0); +} + +void test_to_from_string() +{ + std::uint8_t bits[4] = { 0x10, 0xff, 0x55, 0xaa}; + + bloom_filter<4> filter; + filter.from_string(reinterpret_cast(bits)); + + std::string bits_out = filter.to_string(); + TEST_EQUAL(memcmp(bits_out.c_str(), bits, 4), 0); + + sha1_hash k("\x01\x00\x02\x00 "); + TEST_CHECK(!filter.find(k)); + filter.set(k); + TEST_CHECK(filter.find(k)); + + std::uint8_t compare[4] = { 0x16, 0xff, 0x55, 0xaa}; + + bits_out = filter.to_string(); + TEST_EQUAL(memcmp(compare, bits_out.c_str(), 4), 0); +} + +} // anonymous namespace + +TORRENT_TEST(bloom_filter) +{ + test_set_and_get(); + test_set_bits(); + test_count_zeroes(); + test_to_from_string(); + + // TODO: test size() + // TODO: test clear() +} diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp new file mode 100644 index 0000000..51ae718 --- /dev/null +++ b/test/test_buffer.cpp @@ -0,0 +1,309 @@ +/* + Copyright (c) 2003 - 2005, Arvid Norberg, Daniel Wallin + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of Rasterbar Software nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "libtorrent/buffer.hpp" +#include "libtorrent/chained_buffer.hpp" +#include "libtorrent/socket.hpp" + +#include "test.hpp" + +using namespace lt; + +// -- test buffer -- + +static char const data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + +TORRENT_TEST(buffer_constructor) +{ + + { + buffer b; + TEST_CHECK(b.size() == 0); + TEST_CHECK(b.empty()); + } + + { + buffer b(10); + TEST_CHECK(b.size() >= 10); + } + + { + buffer b(50, data); + TEST_CHECK(std::memcmp(b.data(), data, 10) == 0); + TEST_CHECK(b.size() >= 50); + } +} + +TORRENT_TEST(buffer_swap) +{ + buffer b1; + TEST_CHECK(b1.size() == 0); + buffer b2(10, data); + auto const b2_size = b2.size(); + TEST_CHECK(b2_size >= 10); + + b1.swap(b2); + + TEST_CHECK(b2.size() == 0); + TEST_CHECK(b1.size() == b2_size); + TEST_CHECK(std::memcmp(b1.data(), data, 10) == 0); +} + +TORRENT_TEST(buffer_subscript) +{ + buffer b(50, data); + TEST_CHECK(std::memcmp(b.data(), data, 10) == 0); + TEST_CHECK(b.size() >= 50); + + for (int i = 0; i < int(sizeof(data)/sizeof(data[0])); ++i) + TEST_CHECK(b[i] == data[i]); +} + +TORRENT_TEST(buffer_subscript2) +{ + buffer b(1); + TEST_CHECK(b.size() >= 1); + + for (int i = 0; i < int(b.size()); ++i) + b[i] = char(i & 0xff); + + for (int i = 0; i < int(b.size()); ++i) + TEST_CHECK(b[i] == (i & 0xff)); +} + +TORRENT_TEST(buffer_move_construct) +{ + buffer b1(50, data); + TEST_CHECK(std::memcmp(b1.data(), data, 10) == 0); + TEST_CHECK(b1.size() >= 50); + + buffer b2(std::move(b1)); + + TEST_CHECK(b1.empty()); + + TEST_CHECK(std::memcmp(b2.data(), data, 10) == 0); + TEST_CHECK(b2.size() >= 50); +} + +TORRENT_TEST(buffer_move_assign) +{ + buffer b1(50, data); + TEST_CHECK(std::memcmp(b1.data(), data, 10) == 0); + TEST_CHECK(b1.size() >= 50); + + buffer b2; + TEST_CHECK(b2.size() == 0); + + b2 = std::move(b1); + + TEST_CHECK(b1.size() == 0); + + TEST_CHECK(std::memcmp(b2.data(), data, 10) == 0); + TEST_CHECK(b2.size() >= 50); +} + +namespace { +// -- test chained buffer -- + +std::set buffer_list; + +void free_buffer(char* m) +{ + auto const i = buffer_list.find(m); + TEST_CHECK(i != buffer_list.end()); + + buffer_list.erase(i); + std::free(m); +} + +char* allocate_buffer(int size) +{ + char* mem = static_cast(std::malloc(std::size_t(size))); + buffer_list.insert(mem); + return mem; +} + +template +int copy_buffers(T const& b, char* target) +{ + int copied = 0; + for (typename T::const_iterator i = b.begin() + , end(b.end()); i != end; ++i) + { + memcpy(target, boost::asio::buffer_cast(*i), boost::asio::buffer_size(*i)); + target += boost::asio::buffer_size(*i); + copied += int(boost::asio::buffer_size(*i)); + } + return copied; +} + +bool compare_chained_buffer(chained_buffer& b, char const* mem, int size) +{ + if (size == 0) return true; + std::vector flat((std::size_t(size))); + auto const iovec2 = b.build_iovec(size); + int copied = copy_buffers(iovec2, &flat[0]); + TEST_CHECK(copied == size); + return std::memcmp(&flat[0], mem, std::size_t(size)) == 0; +} + +struct holder +{ + holder(char* buf, std::size_t size) : m_buf(buf), m_size(size) {} + ~holder() { if (m_buf) free_buffer(m_buf); } + holder(holder const&) = delete; + holder& operator=(holder const&) = delete; + holder(holder&& rhs) noexcept : m_buf(rhs.m_buf), m_size(rhs.m_size) { rhs.m_buf = nullptr; } + holder& operator=(holder&& rhs) = delete; + char* data() const { return m_buf; } + std::size_t size() const { return m_size; } +private: + char* m_buf; + std::size_t m_size; +}; + +} // anonymous namespace + +TORRENT_TEST(chained_buffer) +{ + char data_test[] = "foobar"; + { + chained_buffer b; + + TEST_CHECK(b.empty()); + TEST_EQUAL(b.capacity(), 0); + TEST_EQUAL(b.size(), 0); + TEST_EQUAL(b.space_in_last_buffer(), 0); + TEST_CHECK(buffer_list.empty()); + + // there are no buffers, we should not be able to allocate + // an appendix in an existing buffer + TEST_EQUAL(b.allocate_appendix(1), static_cast(nullptr)); + + char* b1 = allocate_buffer(512); + std::memcpy(b1, data_test, 6); + b.append_buffer(holder(b1, 512), 6); + TEST_EQUAL(buffer_list.size(), 1); + + TEST_EQUAL(b.capacity(), 512); + TEST_EQUAL(b.size(), 6); + TEST_CHECK(!b.empty()); + TEST_EQUAL(b.space_in_last_buffer(), 512 - 6); + + b.pop_front(3); + + TEST_EQUAL(b.capacity(), 512 - 3); + TEST_EQUAL(b.size(), 3); + TEST_CHECK(!b.empty()); + TEST_EQUAL(b.space_in_last_buffer(), 512 - 6); + + bool ret = b.append({data_test, 6}) != nullptr; + + TEST_CHECK(ret == true); + TEST_EQUAL(b.capacity(), 512 - 3); + TEST_EQUAL(b.size(), 9); + TEST_CHECK(!b.empty()); + TEST_EQUAL(b.space_in_last_buffer(), 512 - 12); + + char data2[1024]; + ret = b.append(data2) != nullptr; + + TEST_CHECK(ret == false); + + char* b2 = allocate_buffer(512); + std::memcpy(b2, data_test, 6); + b.append_buffer(holder(b2, 512), 6); + TEST_EQUAL(buffer_list.size(), 2); + + char* b3 = allocate_buffer(512); + std::memcpy(b3, data_test, 6); + b.append_buffer(holder(b3, 512), 6); + TEST_EQUAL(buffer_list.size(), 3); + + TEST_EQUAL(b.capacity(), 512 * 3 - 3); + TEST_EQUAL(b.size(), 21); + TEST_CHECK(!b.empty()); + TEST_EQUAL(b.space_in_last_buffer(), 512 - 6); + + TEST_CHECK(compare_chained_buffer(b, "barfoobar", 9)); + + for (int i = 1; i < 21; ++i) + TEST_CHECK(compare_chained_buffer(b, "barfoobarfoobarfoobar", i)); + + b.pop_front(5 + 6); + + TEST_EQUAL(buffer_list.size(), 2); + TEST_EQUAL(b.capacity(), 512 * 2 - 2); + TEST_EQUAL(b.size(), 10); + TEST_CHECK(!b.empty()); + TEST_EQUAL(b.space_in_last_buffer(), 512 - 6); + + char const* str = "obarfooba"; + TEST_CHECK(compare_chained_buffer(b, str, 9)); + + for (int i = 0; i < 9; ++i) + { + b.pop_front(1); + ++str; + TEST_CHECK(compare_chained_buffer(b, str, 8 - i)); + TEST_EQUAL(b.size(), 9 - i); + } + + char* b4 = allocate_buffer(20); + std::memcpy(b4, data_test, 6); + std::memcpy(b4 + 6, data_test, 6); + b.append_buffer(holder(b4, 20), 12); + TEST_EQUAL(b.space_in_last_buffer(), 8); + + ret = b.append({data_test, 6}) != nullptr; + TEST_CHECK(ret == true); + TEST_EQUAL(b.space_in_last_buffer(), 2); + std::cout << b.space_in_last_buffer() << std::endl; + ret = b.append({data_test, 2}) != nullptr; + TEST_CHECK(ret == true); + TEST_EQUAL(b.space_in_last_buffer(), 0); + std::cout << b.space_in_last_buffer() << std::endl; + + char* b5 = allocate_buffer(20); + std::memcpy(b5, data_test, 6); + b.append_buffer(holder(b5, 20), 6); + + b.pop_front(22); + TEST_EQUAL(b.size(), 5); + } + TEST_CHECK(buffer_list.empty()); +} diff --git a/test/test_checking.cpp b/test/test_checking.cpp new file mode 100644 index 0000000..9aa772f --- /dev/null +++ b/test/test_checking.cpp @@ -0,0 +1,370 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include // for chmod + +#include "libtorrent/session.hpp" +#include "test.hpp" +#include "settings.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/path.hpp" + +namespace { + +namespace +{ + bool is_checking(int const state) + { + return state == lt::torrent_status::checking_files +#if TORRENT_ABI_VERSION == 1 + || state == lt::torrent_status::queued_for_checking +#endif + || state == lt::torrent_status::checking_resume_data; + } +} + +enum +{ + // make sure we don't accidentally require files to be writable just to + // check their hashes + read_only_files = 1, + + // make sure we detect corrupt files and mark the appropriate pieces + // as not had + corrupt_files = 2, + + incomplete_files = 4, + + // make the files not be there when starting up, move the files in place and + // force-recheck. Make sure the stat cache is cleared and let us pick up the + // new files + force_recheck = 8, + + // files that are *bigger* than expected still considered OK. We don't + // truncate files willy-nilly. Checking is a read-only operation. + extended_files = 16, +}; + +void test_checking(int flags) +{ + using namespace lt; + + std::printf("\n==== TEST CHECKING %s%s%s%s%s=====\n\n" + , (flags & read_only_files) ? "read-only-files ":"" + , (flags & corrupt_files) ? "corrupt ":"" + , (flags & incomplete_files) ? "incomplete ":"" + , (flags & force_recheck) ? "force_recheck ":"" + , (flags & extended_files) ? "extended_files ":""); + + error_code ec; + create_directory("test_torrent_dir", ec); + if (ec) fprintf(stdout, "ERROR: creating directory test_torrent_dir: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + file_storage fs; + std::srand(10); + int piece_size = 0x4000; + + static std::array const file_sizes + {{ 0, 5, 16 - 5, 16000, 17, 10, 8000, 8000, 1,1,1,1,1,100,1,1,1,1,100,1,1,1,1,1,1 + ,1,1,1,1,1,1,13,65000,34,75,2,30,400,500,23000,900,43000,400,4300,6, 4 }}; + + create_random_files("test_torrent_dir", file_sizes, &fs); + + lt::create_torrent t(fs, piece_size, 0x4000 + , lt::create_torrent::optimize_alignment); + + // calculate the hash for all pieces + set_piece_hashes(t, ".", ec); + if (ec) std::printf("ERROR: set_piece_hashes: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, ec, from_span); + + std::printf("generated torrent: %s test_torrent_dir\n" + , aux::to_hex(ti->info_hash()).c_str()); + + // truncate every file in half + if (flags & (incomplete_files | extended_files)) + { + for (std::size_t i = 0; i < file_sizes.size(); ++i) + { + char name[1024]; + std::snprintf(name, sizeof(name), "test%d", int(i)); + char dirname[200]; + std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5); + std::string path = combine_path("test_torrent_dir", dirname); + path = combine_path(path, name); + + file f(path, open_mode::read_write, ec); + if (ec) std::printf("ERROR: opening file \"%s\": (%d) %s\n" + , path.c_str(), ec.value(), ec.message().c_str()); + if (flags & extended_files) + { + f.set_size(file_sizes[i] + 10, ec); + } + else + { + f.set_size(file_sizes[i] / 2, ec); + } + if (ec) std::printf("ERROR: truncating file \"%s\": (%d) %s\n" + , path.c_str(), ec.value(), ec.message().c_str()); + } + } + + // overwrite the files with new random data + if (flags & corrupt_files) + { + std::printf("corrupt file test. overwriting files\n"); + // increase the size of some files. When they're read only that forces + // the checker to open them in write-mode to truncate them + static std::array const file_sizes2 + {{ 0, 5, 16 - 5, 16001, 30, 10, 8000, 8000, 1,1,1,1,1,100,1,1,1,1,100,1,1,1,1,1,1 + ,1,1,1,1,1,1,13,65000,34,75,2,30,400,500,23000,900,43000,400,4300,6, 4}}; + create_random_files("test_torrent_dir", file_sizes2); + } + + // make the files read only + if (flags & read_only_files) + { + std::printf("making files read-only\n"); + for (std::size_t i = 0; i < file_sizes.size(); ++i) + { + char name[1024]; + std::snprintf(name, sizeof(name), "test%d", int(i)); + char dirname[200]; + std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5); + + std::string path = combine_path("test_torrent_dir", dirname); + path = combine_path(path, name); + std::printf(" %s\n", path.c_str()); + +#ifdef TORRENT_WINDOWS + SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_READONLY); +#else + chmod(path.c_str(), S_IRUSR); +#endif + } + } + + if (flags & force_recheck) + { + remove_all("test_torrent_dir_tmp", ec); + if (ec) std::printf("ERROR: removing \"test_torrent_dir_tmp\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + rename("test_torrent_dir", "test_torrent_dir_tmp", ec); + if (ec) std::printf("ERROR: renaming dir \"test_torrent_dir\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + + lt::session ses1(settings()); + + add_torrent_params p; + p.save_path = "."; + p.ti = ti; + torrent_handle tor1 = ses1.add_torrent(p, ec); + TEST_CHECK(!ec); + + if (flags & force_recheck) + { + // first make sure the session tries to check for the file and can't find + // them + libtorrent::alert const* a = wait_for_alert( + ses1, torrent_checked_alert::alert_type, "checking"); + TEST_CHECK(a); + + // now, move back the files and force-recheck. make sure we pick up the + // files this time + remove_all("test_torrent_dir", ec); + if (ec) fprintf(stdout, "ERROR: removing \"test_torrent_dir\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + rename("test_torrent_dir_tmp", "test_torrent_dir", ec); + if (ec) fprintf(stdout, "ERROR: renaming dir \"test_torrent_dir_tmp\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + tor1.force_recheck(); + } + + torrent_status st; + for (int i = 0; i < 20; ++i) + { + print_alerts(ses1, "ses1"); + + st = tor1.status(); + + std::printf("%d %f %s\n", st.state, st.progress_ppm / 10000.0, st.errc.message().c_str()); + + if (!is_checking(st.state) || st.errc) break; + std::this_thread::sleep_for(lt::milliseconds(500)); + } + + if (flags & incomplete_files) + { + TEST_CHECK(!st.is_seeding); + + std::this_thread::sleep_for(lt::milliseconds(500)); + st = tor1.status(); + TEST_CHECK(!st.is_seeding); + } + + if (flags & corrupt_files) + { + TEST_CHECK(!st.is_seeding); + + TEST_CHECK(!st.errc); + if (st.errc) + std::printf("error: %s\n", st.errc.message().c_str()); + } + + if ((flags & (incomplete_files | corrupt_files)) == 0) + { + TEST_CHECK(st.is_seeding); + if (st.errc) + std::printf("error: %s\n", st.errc.message().c_str()); + } + + // make the files writable again + if (flags & read_only_files) + { + for (std::size_t i = 0; i < file_sizes.size(); ++i) + { + char name[1024]; + std::snprintf(name, sizeof(name), "test%d", int(i)); + char dirname[200]; + std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5); + + std::string path = combine_path("test_torrent_dir", dirname); + path = combine_path(path, name); +#ifdef TORRENT_WINDOWS + SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_NORMAL); +#else + chmod(path.c_str(), S_IRUSR | S_IWUSR); +#endif + } + } + + remove_all("test_torrent_dir", ec); + if (ec) std::printf("ERROR: removing test_torrent_dir: (%d) %s\n" + , ec.value(), ec.message().c_str()); +} + +} // anonymous namespace + +TORRENT_TEST(checking) +{ + test_checking(0); +} + +TORRENT_TEST(read_only_corrupt) +{ + test_checking(read_only_files | corrupt_files); +} + +TORRENT_TEST(read_only) +{ + test_checking(read_only_files); +} + +TORRENT_TEST(incomplete) +{ + test_checking(incomplete_files); +} + +TORRENT_TEST(extended) +{ + test_checking(extended_files); +} + +TORRENT_TEST(corrupt) +{ + test_checking(corrupt_files); +} + +TORRENT_TEST(force_recheck) +{ + test_checking(force_recheck); +} + +TORRENT_TEST(discrete_checking) +{ + using namespace lt; + printf("\n==== TEST CHECKING discrete =====\n\n"); + error_code ec; + create_directory("test_torrent_dir", ec); + if (ec) printf("ERROR: creating directory test_torrent_dir: (%d) %s\n", ec.value(), ec.message().c_str()); + + int const megabyte = 0x100000; + int const piece_size = 2 * megabyte; + static std::array const file_sizes{{ 9 * megabyte, 3 * megabyte }}; + + file_storage fs; + create_random_files("test_torrent_dir", file_sizes, &fs); + TEST_EQUAL(fs.num_files(), 2); + + lt::create_torrent t(fs, piece_size, 1, lt::create_torrent::optimize_alignment); + set_piece_hashes(t, ".", ec); + if (ec) printf("ERROR: set_piece_hashes: (%d) %s\n", ec.value(), ec.message().c_str()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, ec, from_span); + printf("generated torrent: %s test_torrent_dir\n", aux::to_hex(ti->info_hash().to_string()).c_str()); + + // we have two files, but there's a padfile now too + TEST_EQUAL(ti->num_files(), 3); + + { + session ses1(settings()); + add_torrent_params p; + p.file_priorities.resize(std::size_t(ti->num_files())); + p.file_priorities[0] = 1_pri; + p.save_path = "."; + p.ti = ti; + torrent_handle tor1 = ses1.add_torrent(p, ec); + // change the priority of a file while checking and make sure it doesn't interrupt the checking. + std::vector prio(std::size_t(ti->num_files()), 0_pri); + prio[2] = 1_pri; + tor1.prioritize_files(prio); + TEST_CHECK(wait_for_alert(ses1, torrent_checked_alert::alert_type + , "torrent checked", pop_alerts::pop_all, seconds(50))); + TEST_CHECK(tor1.status({}).is_seeding); + } + remove_all("test_torrent_dir", ec); + if (ec) fprintf(stdout, "ERROR: removing test_torrent_dir: (%d) %s\n", ec.value(), ec.message().c_str()); +} diff --git a/test/test_crc32.cpp b/test/test_crc32.cpp new file mode 100644 index 0000000..007aa1d --- /dev/null +++ b/test/test_crc32.cpp @@ -0,0 +1,70 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/crc32c.hpp" +#include "libtorrent/aux_/cpuid.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/aux_/byteswap.hpp" +#include "test.hpp" + +TORRENT_TEST(crc32) +{ + using namespace lt; + + std::uint32_t out; + + std::uint32_t in1 = aux::host_to_network(0xeffea55a); + out = crc32c_32(in1); + TEST_EQUAL(out, 0x5ee3b9d5); + + std::uint64_t buf[4]; + memcpy(buf, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 32); + + // https://tools.ietf.org/html/rfc3720#appendix-B.4 + out = crc32c(buf, 4); + TEST_EQUAL(out, 0x8a9136aaU); + + memcpy(buf, "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", 32); + out = crc32c(buf, 4); + TEST_EQUAL(out, 0x62a8ab43U); + + memcpy(buf, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", 32); + out = crc32c(buf, 4); + TEST_EQUAL(out, 0x46dd794eU); + +#if !TORRENT_HAS_ARM + TORRENT_ASSERT(!aux::arm_crc32c_support); +#endif +} diff --git a/test/test_create_torrent.cpp b/test/test_create_torrent.cpp new file mode 100644 index 0000000..9820d21 --- /dev/null +++ b/test/test_create_torrent.cpp @@ -0,0 +1,121 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix +#include "libtorrent/announce_entry.hpp" + +#include + + +// make sure creating a torrent from an existing handle preserves the +// info-dictionary verbatim, so as to not alter the info-hash +TORRENT_TEST(create_verbatim_torrent) +{ + char const test_torrent[] = "d4:infod4:name6:foobar6:lengthi12345e" + "12:piece lengthi65536e6:pieces20:ababababababababababee"; + + lt::torrent_info info(test_torrent, lt::from_span); + + info.add_tracker("http://test.com"); + info.add_tracker("http://test.com"); + TEST_EQUAL(info.trackers().size(), 1); + + lt::create_torrent t(info); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + + // now, make sure the info dictionary was unchanged + buffer.push_back('\0'); + char const* dest_info = std::strstr(&buffer[0], "4:info"); + + TEST_CHECK(dest_info != nullptr); + + // +1 and -2 here is to strip the outermost dictionary from the source + // torrent, since create_torrent may have added items next to the info dict + TEST_CHECK(memcmp(dest_info, test_torrent + 1, sizeof(test_torrent)-3) == 0); +} + +TORRENT_TEST(piece_size) +{ + std::int64_t const kiB = 1024; + std::int64_t const MiB = 1024 * 1024; + std::int64_t const GiB = 1024 * 1024 * 1024; + std::array, 11> samples{{ + {100LL, 16 * kiB}, + {3 * MiB, 32 * kiB}, + {11 * MiB, 64 * kiB}, + {43 * MiB, 128 * kiB}, + {172 * MiB, 256 * kiB}, + {688 * MiB, 512 * kiB}, + {3 * GiB, 1 * MiB}, + {11 * GiB, 2 * MiB}, + {44 * GiB, 4 * MiB}, + {176 * GiB, 8 * MiB}, + {704 * GiB, 16 * MiB}, + }}; + + for (auto const& t : samples) + { + lt::file_storage fs; + fs.add_file("a", t.first); + lt::create_torrent ct(fs, 0); + TEST_CHECK(ct.piece_length() == static_cast(t.second)); + } +} + +TORRENT_TEST(create_torrent_round_trip) +{ + char const test_torrent[] = "d8:announce26:udp://testurl.com/announce7:comment22:this is a test comment13:creation datei1337e4:infod6:lengthi12345e4:name6:foobar12:piece lengthi65536e6:pieces20:ababababababababababee"; + lt::torrent_info info1(test_torrent, lt::from_span); + TEST_EQUAL(info1.comment(), "this is a test comment"); + TEST_EQUAL(info1.trackers().size(), 1); + TEST_EQUAL(info1.trackers().front().url, "udp://testurl.com/announce"); + + lt::create_torrent t(info1); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + lt::torrent_info info2(buffer, lt::from_span); + + TEST_EQUAL(info2.comment(), "this is a test comment"); + TEST_EQUAL(info2.trackers().size(), 1); + TEST_EQUAL(info2.trackers().front().url, "udp://testurl.com/announce"); + TEST_CHECK(info1.info_hash() == info2.info_hash()); +} + diff --git a/test/test_dht.cpp b/test/test_dht.cpp new file mode 100644 index 0000000..63d5c5a --- /dev/null +++ b/test/test_dht.cpp @@ -0,0 +1,4056 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#ifndef TORRENT_DISABLE_DHT + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/kademlia/msg.hpp" // for verify_message +#include "libtorrent/kademlia/node.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6 +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/hex.hpp" // to_hex, from_hex +#include "libtorrent/bloom_filter.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/time.hpp" +#include "libtorrent/aux_/listen_socket_handle.hpp" +#include "libtorrent/aux_/session_impl.hpp" + +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" + +#include +#include +#include +#include +#include // for vsnprintf + +#include "setup_transfer.hpp" + +using namespace lt; +using namespace lt::dht; +using namespace std::placeholders; + +namespace { + +void get_test_keypair(public_key& pk, secret_key& sk) +{ + aux::from_hex({"77ff84905a91936367c01360803104f92432fcd904a43511876df5cdf3e7e548", 64} + , pk.bytes.data()); + aux::from_hex({"e06d3183d14159228433ed599221b80bd0a5ce8352e4bdf0262f76786ef1c74d" + "b7e7a9fea2c0eb269d61e3b38e450a22e754941ac78479d6c54e1faf6037881d", 128} + , sk.bytes.data()); +} + +sequence_number prev_seq(sequence_number s) +{ + return sequence_number(s.value - 1); +} + +sequence_number next_seq(sequence_number s) +{ + return sequence_number(s.value + 1); +} + +void add_and_replace(node_id& dst, node_id const& add) +{ + bool carry = false; + for (int k = 19; k >= 0; --k) + { + int sum = dst[k] + add[k] + (carry ? 1 : 0); + dst[k] = sum & 255; + carry = sum > 255; + } +} + +void node_push_back(std::vector* nv, node_entry const& n) +{ + nv->push_back(n); +} + +void nop_node() {} + +// TODO: 3 make the mock_socket hold a reference to the list of where to record +// packets instead of having a global variable +std::list> g_sent_packets; + +struct mock_socket final : socket_manager +{ + bool has_quota() override { return true; } + bool send_packet(aux::listen_socket_handle const&, entry& msg, udp::endpoint const& ep) override + { + // TODO: 3 ideally the mock_socket would contain this queue of packets, to + // make tests independent + g_sent_packets.push_back(std::make_pair(ep, msg)); + return true; + } +}; + +std::shared_ptr dummy_listen_socket(udp::endpoint src) +{ + auto ret = std::make_shared(); + ret->local_endpoint = tcp::endpoint(src.address(), src.port()); + ret->external_address.cast_vote(src.address() + , aux::session_interface::source_dht, rand_v4()); + return ret; +} + +std::shared_ptr dummy_listen_socket4() +{ + auto ret = std::make_shared(); + ret->local_endpoint = tcp::endpoint(addr4("192.168.4.1"), 6881); + ret->external_address.cast_vote(addr4("236.0.0.1") + , aux::session_interface::source_dht, rand_v4()); + return ret; +} + +std::shared_ptr dummy_listen_socket6() +{ + auto ret = std::make_shared(); + ret->local_endpoint = tcp::endpoint(addr6("2002::1"), 6881); + ret->external_address.cast_vote(addr6("2002::1") + , aux::session_interface::source_dht, rand_v6()); + return ret; +} + +node* get_foreign_node_stub(node_id const&, std::string const&) +{ + return nullptr; +} + +sha1_hash generate_next() +{ + sha1_hash ret; + aux::random_bytes(ret); + return ret; +} + +std::list>::iterator +find_packet(udp::endpoint ep) +{ + return std::find_if(g_sent_packets.begin(), g_sent_packets.end() + , [&ep] (std::pair const& p) + { return p.first == ep; }); +} + +void node_from_entry(entry const& e, bdecode_node& l) +{ + error_code ec; + static char inbuf[1500]; + int len = bencode(inbuf, e); + int ret = bdecode(inbuf, inbuf + len, l, ec); + TEST_CHECK(ret == 0); +} + +entry write_peers(std::set const& peers) +{ + entry r; + entry::list_type& pe = r.list(); + for (auto const& p : peers) + { + std::string endpoint(18, '\0'); + std::string::iterator out = endpoint.begin(); + lt::detail::write_endpoint(p, out); + endpoint.resize(std::size_t(out - endpoint.begin())); + pe.push_back(entry(endpoint)); + } + return r; +} + +struct msg_args +{ + msg_args& info_hash(char const* i) + { if (i) a["info_hash"] = std::string(i, 20); return *this; } + + msg_args& name(char const* n) + { if (n) a["n"] = n; return *this; } + + msg_args& token(std::string t) + { a["token"] = t; return *this; } + + msg_args& port(int p) + { a["port"] = p; return *this; } + + msg_args& target(sha1_hash const& t) + { a["target"] = t.to_string(); return *this; } + + msg_args& value(entry const& v) + { a["v"] = v; return *this; } + + msg_args& scrape(bool s) + { a["scrape"] = s ? 1 : 0; return *this; } + + msg_args& seed(bool s) + { a["seed"] = s ? 1 : 0; return *this; } + + msg_args& key(public_key const& k) + { a["k"] = k.bytes; return *this; } + + msg_args& sig(signature const& s) + { a["sig"] = s.bytes; return *this; } + + msg_args& seq(sequence_number s) + { a["seq"] = s.value; return *this; } + + msg_args& cas(sequence_number c) + { a["cas"] = c.value; return *this; } + + msg_args& nid(sha1_hash const& n) + { a["id"] = n.to_string(); return *this; } + + msg_args& salt(span s) + { if (!s.empty()) a["salt"] = s; return *this; } + + msg_args& want(std::string w) + { a["want"].list().push_back(w); return *this; } + + msg_args& nodes(std::vector const& n) + { if (!n.empty()) a["nodes"] = dht::write_nodes_entry(n); return *this; } + + msg_args& nodes6(std::vector const& n) + { if (!n.empty()) a["nodes6"] = dht::write_nodes_entry(n); return *this; } + + msg_args& peers(std::set const& p) + { if (!p.empty()) a.dict()["values"] = write_peers(p); return *this; } + + msg_args& interval(time_duration interval) + { a["interval"] = total_seconds(interval); return *this; } + + msg_args& num(int num) + { a["num"] = num; return *this; } + + msg_args& samples(std::vector const& samples) + { + a["samples"] = span( + reinterpret_cast(samples.data()), int(samples.size()) * 20); + return *this; + } + + entry a; +}; + +void send_dht_request(node& node, char const* msg, udp::endpoint const& ep + , bdecode_node* reply, msg_args const& args = msg_args() + , char const* t = "10", bool has_response = true) +{ + // we're about to clear out the backing buffer + // for this bdecode_node, so we better clear it now + reply->clear(); + entry e; + e["q"] = msg; + e["t"] = t; + e["y"] = "q"; + e["a"] = args.a; + e["a"].dict().insert(std::make_pair("id", generate_next().to_string())); + char msg_buf[1500]; + int size = bencode(msg_buf, e); + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) std::printf("bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, ep); + node.incoming(node.m_sock, m); + + // If the request is supposed to get a response, by now the node should have + // invoked the send function and put the response in g_sent_packets + auto const i = find_packet(ep); + if (has_response) + { + if (i == g_sent_packets.end()) + { + TEST_ERROR("not response from DHT node"); + return; + } + + node_from_entry(i->second, *reply); + g_sent_packets.erase(i); + + return; + } + + // this request suppose won't be responsed. + if (i != g_sent_packets.end()) + { + TEST_ERROR("shouldn't have response from DHT node"); + return; + } +} + +void send_dht_response(node& node, bdecode_node const& request, udp::endpoint const& ep + , msg_args const& args = msg_args()) +{ + entry e; + e["y"] = "r"; + e["t"] = request.dict_find_string_value("t").to_string(); +// e["ip"] = endpoint_to_bytes(ep); + e["r"] = args.a; + e["r"].dict().insert(std::make_pair("id", generate_next().to_string())); + char msg_buf[1500]; + int const size = bencode(msg_buf, e); + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) std::printf("bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, ep); + node.incoming(node.m_sock, m); +} + +struct announce_item +{ + announce_item(sha1_hash nxt, int const num) + : next(nxt) + , num_peers(num) + { + num_peers = (rand() % 5) + 2; + ent["next"] = next.to_string(); + ent["A"] = "a"; + ent["B"] = "b"; + ent["num_peers"] = num_peers; + + char buf[512]; + char* ptr = buf; + int len = bencode(ptr, ent); + target = hasher(buf, len).final(); + } + sha1_hash next; + int num_peers; + entry ent; + sha1_hash target; +}; + +void announce_immutable_items(node& node, udp::endpoint const* eps + , announce_item const* items, int num_items) +{ + std::string token; + for (int i = 0; i < 1000; ++i) + { + for (int j = 0; j < num_items; ++j) + { + if ((i % items[j].num_peers) == 0) continue; + bdecode_node response; + send_dht_request(node, "get", eps[i], &response + , msg_args().target(items[j].target)); + + key_desc_t const desc[] = + { + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0}, + { "token", bdecode_node::string_t, 0, 0}, + { "ip", bdecode_node::string_t, 0, key_desc_t::optional | key_desc_t::last_child}, + { "y", bdecode_node::string_t, 1, 0}, + }; + + bdecode_node parsed[5]; + char error_string[200]; + +// std::printf("msg: %s\n", print_entry(response).c_str()); + int ret = verify_message(response, desc, parsed, error_string); + if (ret) + { + TEST_EQUAL(parsed[4].string_value(), "r"); + token = parsed[2].string_value().to_string(); +// std::printf("got token: %s\n", token.c_str()); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid get response: %s\n", error_string); + TEST_ERROR(error_string); + } + + if (parsed[3]) + { + address_v4::bytes_type b; + memcpy(&b[0], parsed[3].string_ptr(), b.size()); + address_v4 addr(b); + TEST_EQUAL(addr, eps[i].address()); + } + + send_dht_request(node, "put", eps[i], &response + , msg_args() + .token(token) + .target(items[j].target) + .value(items[j].ent)); + + key_desc_t const desc2[] = + { + { "y", bdecode_node::string_t, 1, 0 } + }; + + bdecode_node parsed2[1]; + ret = verify_message(response, desc2, parsed2, error_string); + if (ret) + { + if (parsed2[0].string_value() != "r") + std::printf("msg: %s\n", print_entry(response).c_str()); + + TEST_EQUAL(parsed2[0].string_value(), "r"); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid put response: %s\n", error_string); + TEST_ERROR(error_string); + } + } + } + + std::set items_num; + for (int j = 0; j < num_items; ++j) + { + bdecode_node response; + send_dht_request(node, "get", eps[j], &response + , msg_args().target(items[j].target)); + + key_desc_t const desc[] = + { + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "v", bdecode_node::dict_t, 0, 0}, + { "id", bdecode_node::string_t, 20, key_desc_t::last_child}, + { "y", bdecode_node::string_t, 1, 0}, + }; + + bdecode_node parsed[4]; + char error_string[200]; + + int ret = verify_message(response, desc, parsed, error_string); + if (ret) + { + items_num.insert(items_num.begin(), j); + } + } + + // TODO: check to make sure the "best" items are stored + TEST_EQUAL(items_num.size(), 4); +} + +int sum_distance_exp(int s, node_entry const& e, node_id const& ref) +{ + return s + distance_exp(e.id, ref); +} + +std::vector g_got_peers; + +void get_peers_cb(std::vector const& peers) +{ + g_got_peers.insert(g_got_peers.end(), peers.begin(), peers.end()); +} + +std::vector g_got_items; +dht::item g_put_item; +int g_put_count; + +void get_mutable_item_cb(dht::item const& i, bool a) +{ + if (!a) return; + if (!i.empty()) + g_got_items.push_back(i); +} + +void put_mutable_item_data_cb(dht::item& i) +{ + if (!i.empty()) + g_got_items.push_back(i); + + TEST_CHECK(!g_put_item.empty()); + i = g_put_item; + g_put_count++; +} + +void put_mutable_item_cb(dht::item const&, int num, int expect) +{ + TEST_EQUAL(num, expect); +} + +void get_immutable_item_cb(dht::item const& i) +{ + if (!i.empty()) + g_got_items.push_back(i); +} + +void put_immutable_item_cb(int num, int expect) +{ + TEST_EQUAL(num, expect); +} + +struct obs : dht::dht_observer +{ + void set_external_address(aux::listen_socket_handle const& s, address const& addr + , address const& /*source*/) override + { + s.get()->external_address.cast_vote(addr + , aux::session_interface::source_dht, rand_v4()); + } + + int get_listen_port(aux::transport, aux::listen_socket_handle const& s) override + { return s.get()->udp_external_port(); } + + void get_peers(sha1_hash const&) override {} + void outgoing_get_peers(sha1_hash const& /*target*/ + , sha1_hash const& /*sent_target*/, udp::endpoint const&) override {} + void announce(sha1_hash const&, address const&, int) override {} +#ifndef TORRENT_DISABLE_LOGGING + bool should_log(module_t) const override { return true; } + void log(dht_logger::module_t, char const* fmt, ...) override + { + va_list v; + va_start(v, fmt); + char buf[1024]; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + std::vsnprintf(buf, sizeof(buf), fmt, v); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + va_end(v); + std::printf("%s\n", buf); + m_log.emplace_back(buf); + } + void log_packet(message_direction_t, span + , udp::endpoint const&) override {} +#endif + bool on_dht_request(string_view + , dht::msg const&, entry&) override { return false; } + + virtual ~obs() = default; + +#ifndef TORRENT_DISABLE_LOGGING + std::vector m_log; +#endif +}; + +dht::settings test_settings() +{ + dht::settings sett; + sett.max_torrents = 4; + sett.max_dht_items = 4; + sett.enforce_node_id = false; + return sett; +} + +struct dht_test_setup +{ + explicit dht_test_setup(udp::endpoint src) + : sett(test_settings()) + , ls(dummy_listen_socket(src)) + , dht_storage(dht_default_storage_constructor(sett)) + , source(src) + , dht_node(ls, &s, sett + , node_id(nullptr), &observer, cnt, get_foreign_node_stub, *dht_storage) + { + dht_storage->update_node_ids({node_id::min()}); + } + + dht::settings sett; + mock_socket s; + std::shared_ptr ls; + obs observer; + counters cnt; + std::unique_ptr dht_storage; + udp::endpoint source; + dht::node dht_node; + char error_string[200]; +}; + +dht::key_desc_t const err_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"e", bdecode_node::list_t, 2, 0} +}; + +dht::key_desc_t const peer1_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"r", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"token", bdecode_node::string_t, 0, 0}, + {"id", bdecode_node::string_t, 20, key_desc_t::last_child}, +}; + +dht::key_desc_t const get_item_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 3, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"target", bdecode_node::string_t, 20, key_desc_t::last_child}, +}; + +dht::key_desc_t const put_mutable_item_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 3, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"cas", bdecode_node::string_t, 20, key_desc_t::optional}, + {"k", bdecode_node::string_t, public_key::len, 0}, + {"seq", bdecode_node::int_t, 0, 0}, + {"sig", bdecode_node::string_t, signature::len, 0}, + {"token", bdecode_node::string_t, 2, 0}, + {"v", bdecode_node::none_t, 0, key_desc_t::last_child}, +}; + +dht::key_desc_t const sample_infohashes_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 17, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"target", bdecode_node::string_t, 20, key_desc_t::last_child}, +}; + +void print_state(std::ostream& os, routing_table const& table) +{ +#define BUFFER_CURSOR_POS &buf[std::size_t(cursor)], buf.size() - std::size_t(cursor) + std::vector buf(2048); + int cursor = 0; + + cursor += std::snprintf(BUFFER_CURSOR_POS + , "kademlia routing table state\n" + "bucket_size: %d\n" + "global node count: %" PRId64 "\n" + "node_id: %s\n\n" + "number of nodes per bucket:\n" + , table.bucket_size() + , table.num_global_nodes() + , aux::to_hex(table.id()).c_str()); + if (cursor > int(buf.size()) - 500) buf.resize(buf.size() * 3 / 2); + + int idx = 0; + + for (auto i = table.buckets().begin(), end(table.buckets().end()); + i != end; ++i, ++idx) + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , "%2d: ", idx); + for (int k = 0; k < int(i->live_nodes.size()); ++k) + cursor += std::snprintf(BUFFER_CURSOR_POS, "#"); + for (int k = 0; k < int(i->replacements.size()); ++k) + cursor += std::snprintf(BUFFER_CURSOR_POS, "-"); + cursor += std::snprintf(BUFFER_CURSOR_POS, "\n"); + + if (cursor > int(buf.size()) - 500) buf.resize(buf.size() * 3 / 2); + } + + time_point now = aux::time_now(); + + cursor += std::snprintf(BUFFER_CURSOR_POS + , "\nnodes:"); + + int bucket_index = 0; + for (auto i = table.buckets().begin(), end(table.buckets().end()); + i != end; ++i, ++bucket_index) + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , "\n=== BUCKET == %d == %d|%d ==== \n" + , bucket_index, int(i->live_nodes.size()) + , int(i->replacements.size())); + if (cursor > int(buf.size()) - 500) buf.resize(buf.size() * 3 / 2); + + bucket_t nodes = i->live_nodes; + + std::sort(nodes.begin(), nodes.end() + , [](node_entry const& lhs, node_entry const& rhs) + { return lhs.id < rhs.id; } + ); + + for (auto j = nodes.begin(); j != nodes.end(); ++j) + { + int const bucket_size_limit = table.bucket_limit(bucket_index); + TORRENT_ASSERT_VAL(bucket_size_limit <= 256, bucket_size_limit); + TORRENT_ASSERT_VAL(bucket_size_limit > 0, bucket_size_limit); + + bool const last_bucket = bucket_index + 1 == int(table.buckets().size()); + int const prefix = classify_prefix(bucket_index, last_bucket + , bucket_size_limit, j->id); + + cursor += std::snprintf(BUFFER_CURSOR_POS + , " prefix: %2x id: %s", prefix, aux::to_hex(j->id).c_str()); + + if (j->rtt == 0xffff) + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , " rtt: "); + } + else + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , " rtt: %4d", j->rtt); + } + + cursor += std::snprintf(BUFFER_CURSOR_POS + , " fail: %3d ping: %d dist: %3d" + , j->fail_count() + , j->pinged() + , distance_exp(table.id(), j->id)); + + if (j->last_queried == min_time()) + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , " query: "); + } + else + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , " query: %3d", int(total_seconds(now - j->last_queried))); + } + + cursor += std::snprintf(BUFFER_CURSOR_POS + , " ip: %s\n", print_endpoint(j->ep()).c_str()); + if (cursor > int(buf.size()) - 500) buf.resize(buf.size() * 3 / 2); + } + } + + cursor += std::snprintf(BUFFER_CURSOR_POS + , "\nnode spread per bucket:\n"); + bucket_index = 0; + for (auto i = table.buckets().begin(), end(table.buckets().end()); + i != end; ++i, ++bucket_index) + { + int const bucket_size_limit = table.bucket_limit(bucket_index); + TORRENT_ASSERT_VAL(bucket_size_limit <= 256, bucket_size_limit); + TORRENT_ASSERT_VAL(bucket_size_limit > 0, bucket_size_limit); + std::array sub_buckets; + sub_buckets.fill(false); + + // the last bucket is special, since it hasn't been split yet, it + // includes that top bit as well + bool const last_bucket = bucket_index + 1 == int(table.buckets().size()); + + for (auto const& e : i->live_nodes) + { + std::size_t const prefix = static_cast( + classify_prefix(bucket_index, last_bucket, bucket_size_limit, e.id)); + sub_buckets[prefix] = true; + } + + cursor += std::snprintf(BUFFER_CURSOR_POS, "%2d: [", bucket_index); + + for (int j = 0; j < bucket_size_limit; ++j) + { + cursor += std::snprintf(BUFFER_CURSOR_POS + , (sub_buckets[static_cast(j)] ? "X" : " ")); + } + cursor += std::snprintf(BUFFER_CURSOR_POS + , "]\n"); + if (cursor > int(buf.size()) - 500) buf.resize(buf.size() * 3 / 2); + } + buf[std::size_t(cursor)] = '\0'; + os << &buf[0]; +#undef BUFFER_CURSOR_POS +} + +} // anonymous namespace + +TORRENT_TEST(ping) +{ + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + bdecode_node response; + + send_dht_request(t.dht_node, "ping", t.source, &response); + + dht::key_desc_t const pong_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"r", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + bdecode_node pong_keys[4]; + + std::printf("msg: %s\n", print_entry(response).c_str()); + bool ret = dht::verify_message(response, pong_desc, pong_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(pong_keys[0].string_value() == "r"); + TEST_CHECK(pong_keys[1].string_value() == "10"); + } + else + { + std::printf(" invalid ping response: %s\n", t.error_string); + } +} + +TORRENT_TEST(invalid_message) +{ + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + bdecode_node response; + bdecode_node err_keys[2]; + + send_dht_request(t.dht_node, "find_node", t.source, &response); + + std::printf("msg: %s\n", print_entry(response).c_str()); + bool ret = dht::verify_message(response, err_desc, err_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(err_keys[0].string_value() == "e"); + if (err_keys[1].list_at(0).type() == bdecode_node::int_t + && err_keys[1].list_at(1).type() == bdecode_node::string_t) + { + TEST_CHECK(err_keys[1].list_at(1).string_value() == "missing 'target' key"); + } + else + { + TEST_ERROR("invalid error response"); + } + } + else + { + std::printf(" invalid error response: %s\n", t.error_string); + } +} + +TORRENT_TEST(node_id_testng) +{ + node_id rnd = generate_secret_id(); + TEST_CHECK(verify_secret_id(rnd)); + + rnd[19] ^= 0x55; + TEST_CHECK(!verify_secret_id(rnd)); + + rnd = generate_random_id(); + make_id_secret(rnd); + TEST_CHECK(verify_secret_id(rnd)); +} + +TORRENT_TEST(get_peers_announce) +{ + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + bdecode_node response; + + send_dht_request(t.dht_node, "get_peers", t.source, &response + , msg_args().info_hash("01010101010101010101")); + + bdecode_node peer1_keys[4]; + + std::string token; + std::printf("msg: %s\n", print_entry(response).c_str()); + bool ret = dht::verify_message(response, peer1_desc, peer1_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(peer1_keys[0].string_value() == "r"); + token = peer1_keys[2].string_value().to_string(); +// std::printf("got token: %s\n", token.c_str()); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid get_peers response: %s\n", t.error_string); + } + + send_dht_request(t.dht_node, "announce_peer", t.source, &response + , msg_args() + .info_hash("01010101010101010101") + .name("test") + .token(token) + .port(8080)); + + dht::key_desc_t const ann_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"r", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + bdecode_node ann_keys[3]; + + std::printf("msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, ann_desc, ann_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(ann_keys[0].string_value() == "r"); + } + else + { + std::printf(" invalid announce response:\n"); + TEST_ERROR(t.error_string); + } +} + +namespace { + +void test_scrape(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + + init_rand_address(); + + // announce from 100 random IPs and make sure scrape works + // 50 downloaders and 50 seeds + for (int i = 0; i < 100; ++i) + { + t.source = udp::endpoint(rand_addr(), 6000); + send_dht_request(t.dht_node, "get_peers", t.source, &response + , msg_args().info_hash("01010101010101010101")); + + bdecode_node peer1_keys[4]; + bool ret = dht::verify_message(response, peer1_desc, peer1_keys, t.error_string); + + std::string token; + if (ret) + { + TEST_CHECK(peer1_keys[0].string_value() == "r"); + token = peer1_keys[2].string_value().to_string(); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid get_peers response: %s\n", t.error_string); + } + response.clear(); + send_dht_request(t.dht_node, "announce_peer", t.source, &response + , msg_args() + .info_hash("01010101010101010101") + .name("test") + .token(token) + .port(8080) + .seed(i >= 50)); + + response.clear(); + } + + // ====== get_peers ====== + + send_dht_request(t.dht_node, "get_peers", t.source, &response + , msg_args().info_hash("01010101010101010101").scrape(true)); + + dht::key_desc_t const peer2_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"r", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"BFpe", bdecode_node::string_t, 256, 0}, + {"BFsd", bdecode_node::string_t, 256, 0}, + {"id", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + bdecode_node peer2_keys[5]; + + std::printf("msg: %s\n", print_entry(response).c_str()); + bool ret = dht::verify_message(response, peer2_desc, peer2_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(peer2_keys[0].string_value() == "r"); + TEST_EQUAL(peer2_keys[1].dict_find_string_value("n"), "test"); + + bloom_filter<256> downloaders; + bloom_filter<256> seeds; + downloaders.from_string(peer2_keys[2].string_ptr()); + seeds.from_string(peer2_keys[3].string_ptr()); + + std::printf("seeds: %f\n", double(seeds.size())); + std::printf("downloaders: %f\n", double(downloaders.size())); + + TEST_CHECK(std::abs(seeds.size() - 50.f) <= 3.f); + TEST_CHECK(std::abs(downloaders.size() - 50.f) <= 3.f); + } + else + { + std::printf("invalid get_peers response:\n"); + TEST_ERROR(t.error_string); + } +} + +} // anonymous namespace + +TORRENT_TEST(scrape_v4) +{ + test_scrape(rand_v4); +} + +TORRENT_TEST(scrape_v6) +{ + if (supports_ipv6()) + test_scrape(rand_v6); +} + +namespace { + +void test_id_enforcement(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + + // enable node_id enforcement + t.sett.enforce_node_id = true; + + node_id nid; + if (is_v4(t.source)) + { + // this is one of the test vectors from: + // http://libtorrent.org/dht_sec.html + t.source = udp::endpoint(addr("124.31.75.21"), 1); + nid = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401"); + } + else + { + t.source = udp::endpoint(addr("2001:b829:2123:be84:e16c:d6ae:5290:49f1"), 1); + nid = to_hash("0a8ad123be84e16cd6ae529049f1f1bbe9ebb304"); + } + + // verify that we reject invalid node IDs + // this is now an invalid node-id for 'source' + nid[0] = 0x18; + // the test nodes don't get pinged so they will only get as far + // as the replacement bucket + int nodes_num = std::get<1>(t.dht_node.size()); + send_dht_request(t.dht_node, "find_node", t.source, &response + , msg_args() + .target(sha1_hash("0101010101010101010101010101010101010101")) + .nid(nid)); + + bdecode_node err_keys[2]; + bool ret = dht::verify_message(response, err_desc, err_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(err_keys[0].string_value() == "e"); + if (err_keys[1].list_at(0).type() == bdecode_node::int_t + && err_keys[1].list_at(1).type() == bdecode_node::string_t) + { + TEST_CHECK(err_keys[1].list_at(1).string_value() == "invalid node ID"); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + TEST_ERROR("invalid error response"); + } + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid error response: %s\n", t.error_string); + } + + // a node with invalid node-id shouldn't be added to routing table. + TEST_EQUAL(std::get<1>(t.dht_node.size()), nodes_num); + + // now the node-id is valid. + if (is_v4(t.source)) + nid[0] = 0x5f; + else + nid[0] = 0x0a; + send_dht_request(t.dht_node, "find_node", t.source, &response + , msg_args() + .target(sha1_hash("0101010101010101010101010101010101010101")) + .nid(nid)); + + dht::key_desc_t const nodes_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"r", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + bdecode_node nodes_keys[3]; + + std::printf("msg: %s\n", print_entry(response).c_str()); + ret = dht::verify_message(response, nodes_desc, nodes_keys, t.error_string); + TEST_CHECK(ret); + if (ret) + { + TEST_CHECK(nodes_keys[0].string_value() == "r"); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid error response: %s\n", t.error_string); + } + // node with valid node-id should be added to routing table. + TEST_EQUAL(std::get<1>(t.dht_node.size()), nodes_num + 1); +} + +} // anonymous namespace + +TORRENT_TEST(id_enforcement_v4) +{ + test_id_enforcement(rand_v4); +} + +TORRENT_TEST(id_enforcement_v6) +{ + if (supports_ipv6()) + test_id_enforcement(rand_v6); +} + +TORRENT_TEST(bloom_filter) +{ + bloom_filter<256> test; + for (int i = 0; i < 256; ++i) + { + char adr[50]; + std::snprintf(adr, sizeof(adr), "192.0.2.%d", i); + address a = addr(adr); + sha1_hash const iphash = hash_address(a); + test.set(iphash); + } + + if (supports_ipv6()) + { + for (int i = 0; i < 0x3E8; ++i) + { + char adr[50]; + std::snprintf(adr, sizeof(adr), "2001:db8::%x", i); + address a = addr(adr); + sha1_hash const iphash = hash_address(a); + test.set(iphash); + } + } + + // these are test vectors from BEP 33 + // http://www.bittorrent.org/beps/bep_0033.html + std::printf("test.size: %f\n", double(test.size())); + std::string const bf_str = test.to_string(); + std::printf("%s\n", aux::to_hex(bf_str).c_str()); + if (supports_ipv6()) + { + TEST_CHECK(std::abs(double(test.size()) - 1224.93) < 0.001); + TEST_CHECK(aux::to_hex(bf_str) == + "f6c3f5eaa07ffd91bde89f777f26fb2b" + "ff37bdb8fb2bbaa2fd3ddde7bacfff75" + "ee7ccbaefe5eedb1fbfaff67f6abff5e" + "43ddbca3fd9b9ffdf4ffd3e9dff12d1b" + "df59db53dbe9fa5b7ff3b8fdfcde1afb" + "8bedd7be2f3ee71ebbbfe93bcdeefe14" + "8246c2bc5dbff7e7efdcf24fd8dc7adf" + "fd8fffdfddfff7a4bbeedf5cb95ce81f" + "c7fcff1ff4ffffdfe5f7fdcbb7fd79b3" + "fa1fc77bfe07fff905b7b7ffc7fefeff" + "e0b8370bb0cd3f5b7f2bd93feb4386cf" + "dd6f7fd5bfaf2e9ebffffeecd67adbf7" + "c67f17efd5d75eba6ffeba7fff47a91e" + "b1bfbb53e8abfb5762abe8ff237279bf" + "efbfeef5ffc5febfdfe5adffadfee1fb" + "737ffffbfd9f6aeffeee76b6fd8f72ef"); + } + else + { + TEST_CHECK(std::abs(double(test.size()) - 257.854) < 0.001); + TEST_CHECK(aux::to_hex(bf_str) == + "24c0004020043000102012743e004800" + "37110820422110008000c0e302854835" + "a05401a4045021302a306c0600018810" + "02d8a0a3a8001901b40a800900310008" + "d2108110c2496a0028700010d804188b" + "01415200082004088026411104a80404" + "8002002000080680828c400080cc4002" + "0c042c0494447280928041402104080d" + "4240040414a41f0205654800b0811830" + "d2020042b002c5800004a71d0204804a" + "0028120a004c10017801490b83400404" + "4106005421000c86900a002050020351" + "0060144e900100924a1018141a028012" + "913f0041802250042280481200002004" + "430804210101c08111c1080100108000" + "2038008211004266848606b035001048"); + } +} + +namespace { + announce_item const items[] = + { + { generate_next(), 1 }, + { generate_next(), 2 }, + { generate_next(), 3 }, + { generate_next(), 4 }, + { generate_next(), 5 }, + { generate_next(), 6 }, + { generate_next(), 7 }, + { generate_next(), 8 } + }; + + lt::aux::array build_nodes() + { + return lt::aux::array( + std::array { + { { items[0].target, udp::endpoint(addr4("1.1.1.1"), 1231), 10, true} + , { items[1].target, udp::endpoint(addr4("2.2.2.2"), 1232), 10, true} + , { items[2].target, udp::endpoint(addr4("3.3.3.3"), 1233), 10, true} + , { items[3].target, udp::endpoint(addr4("4.4.4.4"), 1234), 10, true} + , { items[4].target, udp::endpoint(addr4("5.5.5.5"), 1235), 10, true} + , { items[5].target, udp::endpoint(addr4("6.6.6.6"), 1236), 10, true} + , { items[6].target, udp::endpoint(addr4("7.7.7.7"), 1237), 10, true} + , { items[7].target, udp::endpoint(addr4("8.8.8.8"), 1238), 10, true} } + }); + } + + lt::aux::array build_nodes(sha1_hash target) + { + return lt::aux::array( + std::array { + { { target, udp::endpoint(addr4("1.1.1.1"), 1231), 10, true} + , { target, udp::endpoint(addr4("2.2.2.2"), 1232), 10, true} + , { target, udp::endpoint(addr4("3.3.3.3"), 1233), 10, true} + , { target, udp::endpoint(addr4("4.4.4.4"), 1234), 10, true} + , { target, udp::endpoint(addr4("5.5.5.5"), 1235), 10, true} + , { target, udp::endpoint(addr4("6.6.6.6"), 1236), 10, true} + , { target, udp::endpoint(addr4("7.7.7.7"), 1237), 10, true} + , { target, udp::endpoint(addr4("8.8.8.8"), 1238), 10, true} + , { target, udp::endpoint(addr4("9.9.9.9"), 1239), 10, true} } + }); + } + +span const empty_salt; + +// TODO: 3 split this up into smaller tests +void test_put(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + + bdecode_node response; + bool ret; + + // ====== put ====== + + init_rand_address(); + udp::endpoint eps[1000]; + for (int i = 0; i < 1000; ++i) + eps[i] = udp::endpoint(rand_addr(), (rand() % 16534) + 1); + + announce_immutable_items(t.dht_node, eps, items, sizeof(items)/sizeof(items[0])); + + key_desc_t const desc2[] = + { + { "y", bdecode_node::string_t, 1, 0 } + }; + + bdecode_node desc2_keys[1]; + + key_desc_t const desc_error[] = + { + { "e", bdecode_node::list_t, 2, 0 }, + { "y", bdecode_node::string_t, 1, 0}, + }; + + bdecode_node desc_error_keys[2]; + + // ==== get / put mutable items === + + span itemv; + + signature sig; + char buffer[1200]; + sequence_number seq(4); + public_key pk; + secret_key sk; + get_test_keypair(pk, sk); + + // TODO: 4 pass in the actual salt as a parameter + for (int with_salt = 0; with_salt < 2; ++with_salt) + { + seq = sequence_number(4); + std::printf("\nTEST GET/PUT%s \ngenerating ed25519 keys\n\n" + , with_salt ? " with-salt" : " no-salt"); + std::array seed = ed25519_create_seed(); + + std::tie(pk, sk) = ed25519_create_keypair(seed); + std::printf("pub: %s priv: %s\n" + , aux::to_hex(pk.bytes).c_str() + , aux::to_hex(sk.bytes).c_str()); + + std::string salt; + if (with_salt) salt = "foobar"; + + hasher h(pk.bytes); + if (with_salt) h.update(salt); + sha1_hash target_id = h.final(); + + std::printf("target_id: %s\n" + , aux::to_hex(target_id).c_str()); + + send_dht_request(t.dht_node, "get", t.source, &response + , msg_args().target(target_id)); + + key_desc_t const desc[] = + { + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0}, + { "token", bdecode_node::string_t, 0, 0}, + { "ip", bdecode_node::string_t, 0, key_desc_t::optional | key_desc_t::last_child}, + { "y", bdecode_node::string_t, 1, 0}, + }; + + bdecode_node desc_keys[5]; + + ret = verify_message(response, desc, desc_keys, t.error_string); + std::string token; + if (ret) + { + TEST_EQUAL(desc_keys[4].string_value(), "r"); + token = desc_keys[2].string_value().to_string(); + std::printf("get response: %s\n" + , print_entry(response).c_str()); + std::printf("got token: %s\n", aux::to_hex(token).c_str()); + } + else + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid get response: %s\n%s\n" + , t.error_string, print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } + + itemv = span(buffer, bencode(buffer, items[0].ent)); + sig = sign_mutable_item(itemv, salt, seq, pk, sk); + TEST_EQUAL(verify_mutable_item(itemv, salt, seq, pk, sig), true); + + send_dht_request(t.dht_node, "put", t.source, &response + , msg_args() + .token(token) + .value(items[0].ent) + .key(pk) + .sig(sig) + .seq(seq) + .salt(salt)); + + ret = verify_message(response, desc2, desc2_keys, t.error_string); + if (ret) + { + std::printf("put response: %s\n" + , print_entry(response).c_str()); + TEST_EQUAL(desc2_keys[0].string_value(), "r"); + } + else + { + std::printf(" invalid put response: %s\n%s\n" + , t.error_string, print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } + + send_dht_request(t.dht_node, "get", t.source, &response + , msg_args().target(target_id)); + + std::printf("target_id: %s\n" + , aux::to_hex(target_id).c_str()); + + key_desc_t const desc3[] = + { + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0}, + { "v", bdecode_node::none_t, 0, 0}, + { "seq", bdecode_node::int_t, 0, 0}, + { "sig", bdecode_node::string_t, 0, 0}, + { "ip", bdecode_node::string_t, 0, key_desc_t::optional | key_desc_t::last_child}, + { "y", bdecode_node::string_t, 1, 0}, + }; + + bdecode_node desc3_keys[7]; + + ret = verify_message(response, desc3, desc3_keys, t.error_string); + if (ret == 0) + { + std::printf("msg: %s\n", print_entry(response).c_str()); + std::printf(" invalid get response: %s\n%s\n" + , t.error_string, print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } + else + { + std::printf("get response: %s\n" + , print_entry(response).c_str()); + char value[1020]; + char* ptr = value; + int const value_len = bencode(ptr, items[0].ent); + TEST_EQUAL(value_len, int(desc3_keys[2].data_section().size())); + TEST_CHECK(std::memcmp(desc3_keys[2].data_section().data(), value, std::size_t(value_len)) == 0); + + TEST_EQUAL(int(seq.value), desc3_keys[3].int_value()); + } + + // also test that invalid signatures fail! + + itemv = span(buffer, bencode(buffer, items[0].ent)); + sig = sign_mutable_item(itemv, salt, seq, pk, sk); + TEST_EQUAL(verify_mutable_item(itemv, salt, seq, pk, sig), 1); + // break the signature + sig.bytes[2] ^= 0xaa; + + std::printf("PUT broken signature\n"); + + TEST_CHECK(verify_mutable_item(itemv, salt, seq, pk, sig) != 1); + + send_dht_request(t.dht_node, "put", t.source, &response + , msg_args() + .token(token) + .value(items[0].ent) + .key(pk) + .sig(sig) + .seq(seq) + .salt(salt)); + + ret = verify_message(response, desc_error, desc_error_keys, t.error_string); + if (ret) + { + std::printf("put response: %s\n", print_entry(response).c_str()); + TEST_EQUAL(desc_error_keys[1].string_value(), "e"); + // 206 is the code for invalid signature + TEST_EQUAL(desc_error_keys[0].list_int_value_at(0), 206); + } + else + { + std::printf(" invalid put response: %s\n%s\n" + , t.error_string, print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } + + // === test conditional get === + + send_dht_request(t.dht_node, "get", t.source, &response + , msg_args().target(target_id).seq(prev_seq(seq))); + + { + bdecode_node const r = response.dict_find_dict("r"); + TEST_CHECK(r.dict_find("v")); + TEST_CHECK(r.dict_find("k")); + TEST_CHECK(r.dict_find("sig")); + } + + send_dht_request(t.dht_node, "get", t.source, &response + , msg_args().target(target_id).seq(seq)); + + { + bdecode_node r = response.dict_find_dict("r"); + TEST_CHECK(!r.dict_find("v")); + TEST_CHECK(!r.dict_find("k")); + TEST_CHECK(!r.dict_find("sig")); + } + + // === test CAS put === + + // this is the sequence number we expect to be there + sequence_number cas = seq; + + // increment sequence number + seq = next_seq(seq); + // put item 1 + itemv = span(buffer, bencode(buffer, items[1].ent)); + sig = sign_mutable_item(itemv, salt, seq, pk, sk); + TEST_EQUAL(verify_mutable_item(itemv, salt, seq, pk, sig), 1); + + TEST_CHECK(item_target_id(salt, pk) == target_id); + + std::printf("PUT CAS 1\n"); + + send_dht_request(t.dht_node, "put", t.source, &response + , msg_args() + .token(token) + .value(items[1].ent) + .key(pk) + .sig(sig) + .seq(seq) + .cas(cas) + .salt(salt)); + + ret = verify_message(response, desc2, desc2_keys, t.error_string); + if (ret) + { + std::printf("put response: %s\n" + , print_entry(response).c_str()); + TEST_EQUAL(desc2_keys[0].string_value(), "r"); + } + else + { + std::printf(" invalid put response: %s\n%s\n" + , t.error_string, print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } + + std::printf("PUT CAS 2\n"); + + // put the same message again. This should fail because the + // CAS hash is outdated, it's not the hash of the value that's + // stored anymore + send_dht_request(t.dht_node, "put", t.source, &response + , msg_args() + .token(token) + .value(items[1].ent) + .key(pk) + .sig(sig) + .seq(seq) + .cas(cas) + .salt(salt)); + + ret = verify_message(response, desc_error, desc_error_keys, t.error_string); + if (ret) + { + std::printf("put response: %s\n" + , print_entry(response).c_str()); + TEST_EQUAL(desc_error_keys[1].string_value(), "e"); + // 301 is the error code for CAS hash mismatch + TEST_EQUAL(desc_error_keys[0].list_int_value_at(0), 301); + } + else + { + std::printf(" invalid put response: %s\n%s\nExpected failure 301 (CAS hash mismatch)\n" + , t.error_string, print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } + } +} + +} // anonymous namespace + +TORRENT_TEST(put_v4) +{ + test_put(rand_v4); +} + +TORRENT_TEST(put_v6) +{ + if (supports_ipv6()) + test_put(rand_v6); +} + +namespace { + +void test_routing_table(address(&rand_addr)()) +{ + init_rand_address(); + + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + + // test kademlia routing table + dht::settings s; + s.extended_routing_table = false; + // s.restrict_routing_ips = false; + node_id const nid = to_hash("3123456789abcdef01232456789abcdef0123456"); + const int bucket_size = 8; + dht::routing_table table(nid, t.source.protocol(), bucket_size, s, &t.observer); + TEST_EQUAL(std::get<0>(table.size()), 0); + + address node_addr; + address node_near_addr; + if (is_v6(t.source)) + { + node_addr = addr6("2001:1111:1111:1111:1111:1111:1111:1111"); + node_near_addr = addr6("2001:1111:1111:1111:eeee:eeee:eeee:eeee"); + } + else + { + node_addr = addr4("4.4.4.4"); + node_near_addr = addr4("4.4.4.5"); + } + + // test a node with the same IP:port changing ID + node_id const tmp = generate_id_impl(node_addr, 1); + table.node_seen(tmp, udp::endpoint(node_addr, 4), 10); + + std::vector nodes; + table.find_node(nid, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + TEST_EQUAL(std::get<0>(table.size()), 1); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), node_addr); + TEST_EQUAL(nodes[0].port(), 4); + TEST_EQUAL(nodes[0].timeout_count, 0); + } + + // set timeout_count to 1 + table.node_failed(tmp, udp::endpoint(node_addr, 4)); + + nodes.clear(); + table.for_each_node(std::bind(node_push_back, &nodes, _1), nullptr); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), node_addr); + TEST_EQUAL(nodes[0].port(), 4); + TEST_EQUAL(nodes[0].timeout_count, 1); + } + + // add the exact same node again, it should set the timeout_count to 0 + table.node_seen(tmp, udp::endpoint(node_addr, 4), 10); + nodes.clear(); + table.for_each_node(std::bind(node_push_back, &nodes, _1), nullptr); + TEST_EQUAL(nodes.size(), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), node_addr); + TEST_EQUAL(nodes[0].port(), 4); + TEST_EQUAL(nodes[0].timeout_count, 0); + } + + // test adding the same node ID again with a different IP (should be ignored) + table.node_seen(tmp, udp::endpoint(node_addr, 5), 10); + table.find_node(nid, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), node_addr); + TEST_EQUAL(nodes[0].port(), 4); + } + + // test adding a node that ends up in the same bucket with an IP + // very close to the current one (should be ignored) + // if restrict_routing_ips == true + table.node_seen(tmp, udp::endpoint(node_near_addr, 5), 10); + table.find_node(nid, nodes, 0, 10); + TEST_EQUAL(table.bucket_size(0), 1); + if (!nodes.empty()) + { + TEST_EQUAL(nodes[0].id, tmp); + TEST_EQUAL(nodes[0].addr(), node_addr); + TEST_EQUAL(nodes[0].port(), 4); + } + + // test adding the same IP:port again with a new node ID (should remove the node) + { + auto const id = generate_id_impl(node_addr, 2); + table.node_seen(id, udp::endpoint(node_addr, 4), 10); + table.find_node(id, nodes, 0, 10); + } + TEST_EQUAL(table.bucket_size(0), 0); + TEST_EQUAL(nodes.size(), 0); + + s.restrict_routing_ips = false; + + { + auto const ep = rand_udp_ep(rand_addr); + auto const id = generate_id_impl(ep.address(), 2); + table.node_seen(id, ep, 10); + } + + nodes.clear(); + for (int i = 0; i < 10000; ++i) + { + auto const ep = rand_udp_ep(rand_addr); + auto const id = generate_id_impl(ep.address(), 6); + table.node_seen(id, ep, 20 + (id[19] & 0xff)); + } + std::printf("active buckets: %d\n", table.num_active_buckets()); + TEST_CHECK(table.num_active_buckets() == 11 + || table.num_active_buckets() == 12); + TEST_CHECK(std::get<0>(table.size()) >= bucket_size * 10); + //TODO: 2 test num_global_nodes + //TODO: 2 test need_refresh + + print_state(std::cout, table); + + table.for_each_node(std::bind(node_push_back, &nodes, _1), nullptr); + + std::printf("nodes: %d\n", int(nodes.size())); + + std::vector temp; + + { + node_id const id = generate_random_id(); + table.find_node(id, temp, 0, int(nodes.size()) * 2); + std::printf("returned-all: %d\n", int(temp.size())); + TEST_EQUAL(temp.size(), nodes.size()); + } + + // This makes sure enough of the nodes returned are actually + // part of the closest nodes + std::set duplicates; + + const int reps = 50; + + for (int r = 0; r < reps; ++r) + { + node_id const id = generate_random_id(); + table.find_node(id, temp, 0, bucket_size * 2); + TEST_EQUAL(int(temp.size()), std::min(bucket_size * 2, int(nodes.size()))); + + std::sort(nodes.begin(), nodes.end(), std::bind(&compare_ref + , std::bind(&node_entry::id, _1) + , std::bind(&node_entry::id, _2), id)); + + int expected = std::accumulate(nodes.begin(), nodes.begin() + int(temp.size()) + , 0, std::bind(&sum_distance_exp, _1, _2, id)); + int sum_hits = std::accumulate(temp.begin(), temp.end() + , 0, std::bind(&sum_distance_exp, _1, _2, id)); + TEST_EQUAL(bucket_size * 2, int(temp.size())); + TEST_EQUAL(expected, sum_hits); + + duplicates.clear(); + // This makes sure enough of the nodes returned are actually + // part of the closest nodes + for (std::vector::iterator i = temp.begin() + , end(temp.end()); i != end; ++i) + { + TEST_CHECK(duplicates.count(i->id) == 0); + duplicates.insert(i->id); + } + } + + char const* ips[] = { + "124.31.75.21", + "21.75.31.124", + "65.23.51.170", + "84.124.73.14", + "43.213.53.83", + }; + + int rs[] = { 1,86,22,65,90 }; + + std::uint8_t prefixes[][3] = + { + { 0x5f, 0xbf, 0xbf }, + { 0x5a, 0x3c, 0xe9 }, + { 0xa5, 0xd4, 0x32 }, + { 0x1b, 0x03, 0x21 }, + { 0xe5, 0x6f, 0x6c } + }; + + for (int i = 0; i < 5; ++i) + { + address const a = addr4(ips[i]); + node_id const new_id = generate_id_impl(a, std::uint32_t(rs[i])); + TEST_CHECK(new_id[0] == prefixes[i][0]); + TEST_CHECK(new_id[1] == prefixes[i][1]); + TEST_CHECK((new_id[2] & 0xf8) == (prefixes[i][2] & 0xf8)); + + TEST_CHECK(new_id[19] == rs[i]); + std::printf("IP address: %s r: %d node ID: %s\n", ips[i] + , rs[i], aux::to_hex(new_id).c_str()); + } +} + +} // anonymous namespace + +TORRENT_TEST(routing_table_v4) +{ + test_routing_table(rand_v4); +} + +TORRENT_TEST(routing_table_v6) +{ + if (supports_ipv6()) + test_routing_table(rand_v6); +} + +namespace { + +void test_bootstrap(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + bool ret; + + dht::key_desc_t const find_node_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 9, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"target", bdecode_node::string_t, 20, key_desc_t::optional}, + {"info_hash", bdecode_node::string_t, 20, key_desc_t::optional | key_desc_t::last_child}, + }; + + bdecode_node find_node_keys[7]; + + // bootstrap + + g_sent_packets.clear(); + + udp::endpoint initial_node(rand_addr(), 1234); + std::vector nodesv; + nodesv.push_back(initial_node); + t.dht_node.bootstrap(nodesv, std::bind(&nop_node)); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + node_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, find_node_desc, find_node_keys, t.error_string); + if (ret) + { + TEST_EQUAL(find_node_keys[0].string_value(), "q"); + TEST_CHECK(find_node_keys[2].string_value() == "find_node" + || find_node_keys[2].string_value() == "get_peers"); + + if (find_node_keys[0].string_value() != "q" + || (find_node_keys[2].string_value() != "find_node" + && find_node_keys[2].string_value() != "get_peers")) return; + } + else + { + std::printf(" invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + udp::endpoint found_node(rand_addr(), 2235); + std::vector nodes; + nodes.push_back(node_entry{found_node}); + g_sent_packets.clear(); + if (is_v4(initial_node)) + send_dht_response(t.dht_node, response, initial_node, msg_args().nodes(nodes)); + else + send_dht_response(t.dht_node, response, initial_node, msg_args().nodes6(nodes)); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, found_node); + + node_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, find_node_desc, find_node_keys, t.error_string); + if (ret) + { + TEST_EQUAL(find_node_keys[0].string_value(), "q"); + TEST_CHECK(find_node_keys[2].string_value() == "find_node" + || find_node_keys[2].string_value() == "get_peers"); + if (find_node_keys[0].string_value() != "q" + || (find_node_keys[2].string_value() != "find_node" + && find_node_keys[2].string_value() != "get_peers")) return; + } + else + { + std::printf(" invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + g_sent_packets.clear(); + send_dht_response(t.dht_node, response, found_node); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(t.dht_node.num_global_nodes(), 3); +} + +} // anonymous namespace + +TORRENT_TEST(bootstrap_v4) +{ + test_bootstrap(rand_v4); +} + +TORRENT_TEST(bootstrap_v6) +{ + if (supports_ipv6()) + test_bootstrap(rand_v6); +} + +namespace { + +void test_bootstrap_want(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + bool ret; + + dht::key_desc_t const find_node_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 9, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"target", bdecode_node::string_t, 20, key_desc_t::optional}, + {"info_hash", bdecode_node::string_t, 20, key_desc_t::optional}, + {"want", bdecode_node::list_t, 0, key_desc_t::last_child}, + }; + + bdecode_node find_node_keys[8]; + + g_sent_packets.clear(); + + std::vector nodesv; + if (is_v4(t.source)) + nodesv.push_back(rand_udp_ep(rand_v6)); + else + nodesv.push_back(rand_udp_ep(rand_v4)); + + t.dht_node.bootstrap(nodesv, std::bind(&nop_node)); + + TEST_EQUAL(g_sent_packets.size(), 1); + TEST_EQUAL(g_sent_packets.front().first, nodesv[0]); + + node_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, find_node_desc, find_node_keys, t.error_string); + if (ret) + { + TEST_EQUAL(find_node_keys[0].string_value(), "q"); + TEST_CHECK(find_node_keys[2].string_value() == "find_node" + || find_node_keys[2].string_value() == "get_peers"); + + TEST_EQUAL(find_node_keys[7].list_size(), 1); + if (is_v4(t.source)) + { + TEST_EQUAL(find_node_keys[7].list_string_value_at(0), "n4"); + } + else + { + TEST_EQUAL(find_node_keys[7].list_string_value_at(0), "n6"); + } + } + else + { + std::printf(" invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + } +} + +} // anonymous namespace + +TORRENT_TEST(bootstrap_want_v4) +{ + test_bootstrap_want(rand_v4); +} + +TORRENT_TEST(bootstrap_want_v6) +{ + test_bootstrap_want(rand_v6); +} + +namespace { + +// test that the node ignores a nodes entry which is too short +void test_short_nodes(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + bool ret; + + dht::key_desc_t const find_node_desc[] = { + { "y", bdecode_node::string_t, 1, 0 }, + { "t", bdecode_node::string_t, 2, 0 }, + { "q", bdecode_node::string_t, 9, 0 }, + { "a", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0 }, + { "target", bdecode_node::string_t, 20, key_desc_t::optional }, + { "info_hash", bdecode_node::string_t, 20, key_desc_t::optional | key_desc_t::last_child }, + }; + + bdecode_node find_node_keys[7]; + + // bootstrap + + g_sent_packets.clear(); + + udp::endpoint initial_node(rand_addr(), 1234); + std::vector nodesv; + nodesv.push_back(initial_node); + t.dht_node.bootstrap(nodesv, std::bind(&nop_node)); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + node_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, find_node_desc, find_node_keys, t.error_string); + if (ret) + { + TEST_EQUAL(find_node_keys[0].string_value(), "q"); + TEST_CHECK(find_node_keys[2].string_value() == "find_node" + || find_node_keys[2].string_value() == "get_peers"); + + if (find_node_keys[0].string_value() != "q" + || (find_node_keys[2].string_value() != "find_node" + && find_node_keys[2].string_value() != "get_peers")) return; + } + else + { + std::printf(" invalid find_node request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + udp::endpoint found_node(rand_addr(), 2235); + std::vector nodes; + nodes.push_back(node_entry{found_node}); + g_sent_packets.clear(); + msg_args args; + // chop one byte off of the nodes string + if (is_v4(initial_node)) + { + args.nodes(nodes); + args.a["nodes"] = args.a["nodes"].string().substr(1); + } + else + { + args.nodes6(nodes); + args.a["nodes6"] = args.a["nodes6"].string().substr(1); + } + + send_dht_response(t.dht_node, response, initial_node, args); + + TEST_EQUAL(g_sent_packets.size(), 0); +} + +} // anonymous namespace + +TORRENT_TEST(short_nodes_v4) +{ + test_short_nodes(rand_v4); +} + +TORRENT_TEST(short_nodes_v6) +{ + if (supports_ipv6()) + test_short_nodes(rand_v6); +} + +namespace { + +void test_get_peers(address(&rand_addr)()) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + bdecode_node response; + bool ret; + + dht::key_desc_t const get_peers_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 9, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"info_hash", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + bdecode_node get_peers_keys[6]; + + // get_peers + + g_sent_packets.clear(); + + dht::node_id const target = to_hash("1234876923549721020394873245098347598635"); + + udp::endpoint const initial_node(rand_addr(), 1234); + dht::node_id const initial_node_id = to_hash("1111111111222222222233333333334444444444"); + t.dht_node.m_table.add_node(node_entry{initial_node_id, initial_node, 10, true}); + + t.dht_node.announce(target, 1234, {}, get_peers_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + node_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, get_peers_desc, get_peers_keys, t.error_string); + if (ret) + { + TEST_EQUAL(get_peers_keys[0].string_value(), "q"); + TEST_EQUAL(get_peers_keys[2].string_value(), "get_peers"); + TEST_EQUAL(get_peers_keys[5].string_value(), target.to_string()); + if (get_peers_keys[0].string_value() != "q" + || get_peers_keys[2].string_value() != "get_peers") + return; + } + else + { + std::printf(" invalid get_peers request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + std::set peers[2]; + peers[0].insert(tcp::endpoint(rand_addr(), 4111)); + peers[0].insert(tcp::endpoint(rand_addr(), 4112)); + peers[0].insert(tcp::endpoint(rand_addr(), 4113)); + + udp::endpoint next_node(rand_addr(), 2235); + std::vector nodes; + nodes.push_back(node_entry{next_node}); + + g_sent_packets.clear(); + if (is_v4(initial_node)) + { + send_dht_response(t.dht_node, response, initial_node + , msg_args().nodes(nodes).token("10").port(1234).peers(peers[0])); + } + else + { + send_dht_response(t.dht_node, response, initial_node + , msg_args().nodes6(nodes).token("10").port(1234).peers(peers[0])); + } + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, next_node); + + node_from_entry(g_sent_packets.front().second, response); + ret = verify_message(response, get_peers_desc, get_peers_keys, t.error_string); + if (ret) + { + TEST_EQUAL(get_peers_keys[0].string_value(), "q"); + TEST_EQUAL(get_peers_keys[2].string_value(), "get_peers"); + TEST_EQUAL(get_peers_keys[5].string_value(), target.to_string()); + if (get_peers_keys[0].string_value() != "q" + || get_peers_keys[2].string_value() != "get_peers") + return; + } + else + { + std::printf(" invalid get_peers request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + peers[1].insert(tcp::endpoint(rand_addr(), 4114)); + peers[1].insert(tcp::endpoint(rand_addr(), 4115)); + peers[1].insert(tcp::endpoint(rand_addr(), 4116)); + + g_sent_packets.clear(); + send_dht_response(t.dht_node, response, next_node + , msg_args().token("11").port(1234).peers(peers[1])); + + for (auto const& p : g_sent_packets) + { +// std::printf(" %s:%d: %s\n", i->first.address().to_string(ec).c_str() +// , i->first.port(), i->second.to_string().c_str()); + TEST_EQUAL(p.second["q"].string(), "announce_peer"); + } + + g_sent_packets.clear(); + + for (int i = 0; i < 2; ++i) + { + for (auto const& peer : peers[i]) + { + TEST_CHECK(std::find(g_got_peers.begin(), g_got_peers.end(), peer) != g_got_peers.end()); + } + } + g_got_peers.clear(); +} + +} // anonymous namespace + +TORRENT_TEST(get_peers_v4) +{ + test_get_peers(rand_v4); +} + +TORRENT_TEST(get_peers_v6) +{ + if (supports_ipv6()) + test_get_peers(rand_v6); +} + +namespace { + +// TODO: 4 pass in th actual salt as the argument +void test_mutable_get(address(&rand_addr)(), bool const with_salt) +{ + dht_test_setup t(udp::endpoint(rand_addr(), 20)); + + public_key pk; + secret_key sk; + get_test_keypair(pk, sk); + + char buffer[1200]; + sequence_number seq(4); + span itemv; + bdecode_node response; + + std::string salt; + if (with_salt) salt = "foobar"; + + // mutable get + + g_sent_packets.clear(); + + udp::endpoint const initial_node(rand_addr(), 1234); + dht::node_id const initial_node_id = to_hash("1111111111222222222233333333334444444444"); + t.dht_node.m_table.add_node(node_entry{initial_node_id, initial_node, 10, true}); + + g_put_item.assign(items[0].ent, salt, seq, pk, sk); + t.dht_node.put_item(pk, std::string() + , std::bind(&put_mutable_item_cb, _1, _2, 0) + , put_mutable_item_data_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + + // mutable_get + + g_sent_packets.clear(); + + t.dht_node.get_item(pk, salt, get_mutable_item_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + node_from_entry(g_sent_packets.front().second, response); + bdecode_node get_item_keys[6]; + bool const ret = verify_message(response, get_item_desc, get_item_keys, t.error_string); + if (ret) + { + TEST_EQUAL(get_item_keys[0].string_value(), "q"); + TEST_EQUAL(get_item_keys[2].string_value(), "get"); + if (get_item_keys[0].string_value() != "q" + || get_item_keys[2].string_value() != "get") + return; + } + else + { + std::printf(" invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + g_sent_packets.clear(); + + signature sig; + itemv = span(buffer, bencode(buffer, items[0].ent)); + sig = sign_mutable_item(itemv, salt, seq, pk, sk); + send_dht_response(t.dht_node, response, initial_node + , msg_args() + .token("10") + .port(1234) + .value(items[0].ent) + .key(pk) + .sig(sig) + .salt(salt) + .seq(seq)); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(g_got_items.size(), 1); + if (g_got_items.empty()) return; + + TEST_EQUAL(g_got_items.front().value(), items[0].ent); + TEST_CHECK(g_got_items.front().pk() == pk); + TEST_CHECK(g_got_items.front().sig() == sig); + TEST_CHECK(g_got_items.front().seq() == seq); + g_got_items.clear(); +} + +} // anonymous namespace + +TORRENT_TEST(mutable_get_v4) +{ + test_mutable_get(rand_v4, false); +} + +TORRENT_TEST(mutable_get_salt_v4) +{ + test_mutable_get(rand_v4, true); +} + +TORRENT_TEST(mutable_get_v6) +{ + if (supports_ipv6()) + test_mutable_get(rand_v6, false); +} + +TORRENT_TEST(mutable_get_salt_v6) +{ + if (supports_ipv6()) + test_mutable_get(rand_v6, true); +} + +TORRENT_TEST(immutable_get) +{ + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + bdecode_node response; + + // immutable get + + g_sent_packets.clear(); + + udp::endpoint initial_node(addr4("4.4.4.4"), 1234); + dht::node_id const initial_node_id = to_hash("1111111111222222222233333333334444444444"); + t.dht_node.m_table.add_node(node_entry{initial_node_id, initial_node, 10, true}); + + t.dht_node.get_item(items[0].target, get_immutable_item_cb); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + node_from_entry(g_sent_packets.front().second, response); + bdecode_node get_item_keys[6]; + bool const ret = verify_message(response, get_item_desc, get_item_keys, t.error_string); + if (ret) + { + TEST_EQUAL(get_item_keys[0].string_value(), "q"); + TEST_EQUAL(get_item_keys[2].string_value(), "get"); + TEST_EQUAL(get_item_keys[5].string_value(), items[0].target.to_string()); + if (get_item_keys[0].string_value() != "q" || get_item_keys[2].string_value() != "get") return; + } + else + { + std::printf(" invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + g_sent_packets.clear(); + send_dht_response(t.dht_node, response, initial_node + , msg_args().token("10").port(1234).value(items[0].ent)); + + TEST_CHECK(g_sent_packets.empty()); + TEST_EQUAL(g_got_items.size(), 1); + if (g_got_items.empty()) return; + + TEST_EQUAL(g_got_items.front().value(), items[0].ent); + g_got_items.clear(); +} + +TORRENT_TEST(immutable_put) +{ + bdecode_node response; + span itemv; + char buffer[1200]; + + dht::key_desc_t const put_immutable_item_desc[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 3, 0}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"token", bdecode_node::string_t, 2, 0}, + {"v", bdecode_node::none_t, 0, key_desc_t::last_child}, + }; + + bdecode_node put_immutable_item_keys[7]; + + // immutable put + g_sent_packets.clear(); + for (int loop = 0; loop < 9; loop++) + { + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + + // set the branching factor to k to make this a little easier + t.sett.search_branching = 8; + + lt::aux::array const nodes = build_nodes(); + + for (node_entry const& n : nodes) + t.dht_node.m_table.add_node(n); + + entry put_data; + put_data = "Hello world"; + std::string flat_data; + bencode(std::back_inserter(flat_data), put_data); + sha1_hash target = item_target_id(flat_data); + + t.dht_node.put_item(target, put_data, std::bind(&put_immutable_item_cb, _1, loop)); + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + int idx = -1; + for (auto& node : nodes) + { + ++idx; + auto const packet = find_packet(node.ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + node_from_entry(packet->second, response); + bdecode_node get_item_keys[6]; + bool const ret = verify_message(response, get_item_desc, get_item_keys, t.error_string); + if (!ret) + { + std::printf(" invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + continue; + } + char tok[11]; + std::snprintf(tok, sizeof(tok), "%02d", idx); + + msg_args args; + args.token(tok).port(1234).nid(node.id).nodes({node}); + send_dht_response(t.dht_node, response, node.ep(), args); + g_sent_packets.erase(packet); + } + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + itemv = span(buffer, bencode(buffer, put_data)); + + idx = -1; + for (auto& node : nodes) + { + ++idx; + auto const packet = find_packet(node.ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + node_from_entry(packet->second, response); + bool const ret = verify_message(response, put_immutable_item_desc, put_immutable_item_keys + , t.error_string); + if (ret) + { + TEST_EQUAL(put_immutable_item_keys[0].string_value(), "q"); + TEST_EQUAL(put_immutable_item_keys[2].string_value(), "put"); + span const v = put_immutable_item_keys[6].data_section(); + TEST_EQUAL(v, span(flat_data)); + char tok[11]; + std::snprintf(tok, sizeof(tok), "%02d", idx); + TEST_EQUAL(put_immutable_item_keys[5].string_value(), tok); + if (put_immutable_item_keys[0].string_value() != "q" + || put_immutable_item_keys[2].string_value() != "put") continue; + + if (idx < loop) send_dht_response(t.dht_node, response, node.ep()); + } + else + { + std::printf(" invalid immutable put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + continue; + } + } + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; + } +} + +TORRENT_TEST(mutable_put) +{ + bdecode_node response; + span itemv; + char buffer[1200]; + bdecode_node put_mutable_item_keys[11]; + public_key pk; + secret_key sk; + get_test_keypair(pk, sk); + + sequence_number seq(4); + + // mutable put + g_sent_packets.clear(); + for (int loop = 0; loop < 9; loop++) + { + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + + // set the branching factor to k to make this a little easier + t.sett.search_branching = 8; + + enum { num_test_nodes = 8 }; + lt::aux::array const nodes = build_nodes(); + + for (auto const& n : nodes) + t.dht_node.m_table.add_node(n); + + g_put_item.assign(items[0].ent, empty_salt, seq, pk, sk); + signature const sig = g_put_item.sig(); + t.dht_node.put_item(pk, std::string() + , std::bind(&put_mutable_item_cb, _1, _2, loop) + , put_mutable_item_data_cb); + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + int idx = -1; + for (auto& node : nodes) + { + ++idx; + auto const packet = find_packet(node.ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + node_from_entry(packet->second, response); + bdecode_node get_item_keys[6]; + bool const ret = verify_message(response, get_item_desc, get_item_keys, t.error_string); + if (!ret) + { + std::printf(" invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + continue; + } + char tok[11]; + std::snprintf(tok, sizeof(tok), "%02d", idx); + + msg_args args; + args.token(tok).port(1234).nid(node.id).nodes({node}); + send_dht_response(t.dht_node, response, node.ep(), args); + g_sent_packets.erase(packet); + } + + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) break; + + itemv = span(buffer, bencode(buffer, items[0].ent)); + + idx = -1; + for (auto& node : nodes) + { + ++idx; + auto const packet = find_packet(node.ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + node_from_entry(packet->second, response); + bool const ret = verify_message(response, put_mutable_item_desc, put_mutable_item_keys + , t.error_string); + if (ret) + { + TEST_EQUAL(put_mutable_item_keys[0].string_value(), "q"); + TEST_EQUAL(put_mutable_item_keys[2].string_value(), "put"); + TEST_EQUAL(put_mutable_item_keys[6].string_value() + , std::string(pk.bytes.data(), public_key::len)); + TEST_EQUAL(put_mutable_item_keys[7].int_value(), int(seq.value)); + TEST_EQUAL(put_mutable_item_keys[8].string_value() + , std::string(sig.bytes.data(), signature::len)); + span const v = put_mutable_item_keys[10].data_section(); + TEST_CHECK(v == itemv); + char tok[11]; + std::snprintf(tok, sizeof(tok), "%02d", idx); + TEST_EQUAL(put_mutable_item_keys[9].string_value(), tok); + if (put_mutable_item_keys[0].string_value() != "q" + || put_mutable_item_keys[2].string_value() != "put") continue; + + if (idx < loop) send_dht_response(t.dht_node, response, node.ep()); + } + else + { + std::printf(" invalid put request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + continue; + } + } + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; + } +} + +TORRENT_TEST(traversal_done) +{ + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + + // set the branching factor to k to make this a little easier + t.sett.search_branching = 8; + + public_key pk; + secret_key sk; + get_test_keypair(pk, sk); + + sequence_number seq(4); + bdecode_node response; + + // verify that done() is only invoked once + // See PR 252 + g_sent_packets.clear(); + + sha1_hash const target = hasher(pk.bytes).final(); + constexpr int num_test_nodes = 9; // we need K + 1 nodes to create the failing sequence + + lt::aux::array nodes = build_nodes(target); + + // invert the ith most significant byte so that the test nodes are + // progressively closer to the target item + for (int i = 0; i < num_test_nodes; ++i) + nodes[i].id[i] = static_cast(~nodes[i].id[i]); + + // add the first k nodes to the subject's routing table + for (int i = 0; i < 8; ++i) + t.dht_node.m_table.add_node(nodes[i]); + + // kick off a mutable put request + g_put_item.assign(items[0].ent, empty_salt, seq, pk, sk); + t.dht_node.put_item(pk, std::string() + , std::bind(&put_mutable_item_cb, _1, _2, 0) + , put_mutable_item_data_cb); + TEST_EQUAL(g_sent_packets.size(), 8); + if (g_sent_packets.size() != 8) return; + + // first send responses for the k closest nodes + for (int i = 1;; ++i) + { + // once the k closest nodes have responded, send the final response + // from the farthest node, this shouldn't trigger a second call to + // get_item_cb + if (i == num_test_nodes) i = 0; + + auto const packet = find_packet(nodes[i].ep()); + TEST_CHECK(packet != g_sent_packets.end()); + if (packet == g_sent_packets.end()) continue; + + node_from_entry(packet->second, response); + bdecode_node get_item_keys[6]; + bool const ret = verify_message(response, get_item_desc, get_item_keys, t.error_string); + if (!ret) + { + std::printf(" invalid get request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + continue; + } + char tok[11]; + std::snprintf(tok, sizeof(tok), "%02d", i); + + msg_args args; + args.token(tok).port(1234).nid(nodes[i].id); + + // add the address of the closest node to the first response + if (i == 1) + args.nodes({nodes[8]}); + + send_dht_response(t.dht_node, response, nodes[i].ep(), args); + g_sent_packets.erase(packet); + + // once we've sent the response from the farthest node, we're done + if (i == 0) break; + } + + TEST_EQUAL(g_put_count, 1); + // k nodes should now have outstanding put requests + TEST_EQUAL(g_sent_packets.size(), 8); + + g_sent_packets.clear(); + g_put_item.clear(); + g_put_count = 0; +} + +TORRENT_TEST(dht_dual_stack) +{ + // TODO: 3 use dht_test_setup class to simplify the node setup + dht::settings sett = test_settings(); + mock_socket s; + auto sock4 = dummy_listen_socket4(); + auto sock6 = dummy_listen_socket6(); + obs observer; + counters cnt; + node* node4p = nullptr, *node6p = nullptr; + auto get_foreign_node = [&](node_id const&, std::string const& family) + { + if (family == "n4") return node4p; + if (family == "n6") return node6p; + TEST_CHECK(false); + return static_cast(nullptr); + }; + std::unique_ptr dht_storage(dht_default_storage_constructor(sett)); + dht_storage->update_node_ids({node_id(nullptr)}); + dht::node node4(sock4, &s, sett, node_id(nullptr), &observer, cnt, get_foreign_node, *dht_storage); + dht::node node6(sock6, &s, sett, node_id(nullptr), &observer, cnt, get_foreign_node, *dht_storage); + node4p = &node4; + node6p = &node6; + + // DHT should be running on port 48199 now + bdecode_node response; + char error_string[200]; + bool ret; + + node_id id = to_hash("3123456789abcdef01232456789abcdef0123456"); + node4.m_table.node_seen(id, udp::endpoint(addr("4.4.4.4"), 4440), 10); + node6.m_table.node_seen(id, udp::endpoint(addr("4::4"), 4441), 10); + + // v4 node requesting v6 nodes + + udp::endpoint source(addr("10.0.0.1"), 20); + + send_dht_request(node4, "find_node", source, &response + , msg_args() + .target(sha1_hash("0101010101010101010101010101010101010101")) + .want("n6")); + + dht::key_desc_t const nodes6_desc[] = { + { "y", bdecode_node::string_t, 1, 0 }, + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0 }, + { "nodes6", bdecode_node::string_t, 38, key_desc_t::last_child } + }; + + bdecode_node nodes6_keys[4]; + + ret = verify_message(response, nodes6_desc, nodes6_keys, error_string); + + if (ret) + { + char const* nodes_ptr = nodes6_keys[3].string_ptr(); + TEST_CHECK(memcmp(nodes_ptr, id.data(), id.size()) == 0); + nodes_ptr += id.size(); + udp::endpoint rep = detail::read_v6_endpoint(nodes_ptr); + TEST_EQUAL(rep, udp::endpoint(addr("4::4"), 4441)); + } + else + { + std::printf("find_node response: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + // v6 node requesting v4 nodes + + source.address(addr("10::1")); + + send_dht_request(node6, "get_peers", source, &response + , msg_args().info_hash("0101010101010101010101010101010101010101").want("n4")); + + dht::key_desc_t const nodes_desc[] = { + { "y", bdecode_node::string_t, 1, 0 }, + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0 }, + { "nodes", bdecode_node::string_t, 26, key_desc_t::last_child } + }; + + bdecode_node nodes_keys[4]; + + ret = verify_message(response, nodes_desc, nodes_keys, error_string); + + if (ret) + { + char const* nodes_ptr = nodes_keys[3].string_ptr(); + TEST_CHECK(memcmp(nodes_ptr, id.data(), id.size()) == 0); + nodes_ptr += id.size(); + udp::endpoint rep = detail::read_v4_endpoint(nodes_ptr); + TEST_EQUAL(rep, udp::endpoint(addr("4.4.4.4"), 4440)); + } + else + { + std::printf("find_node response: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + } + + // v6 node requesting both v4 and v6 nodes + + send_dht_request(node6, "find_nodes", source, &response + , msg_args().info_hash("0101010101010101010101010101010101010101") + .want("n4") + .want("n6")); + + dht::key_desc_t const nodes46_desc[] = { + { "y", bdecode_node::string_t, 1, 0 }, + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, 0 }, + { "nodes", bdecode_node::string_t, 26, 0 }, + { "nodes6", bdecode_node::string_t, 38, key_desc_t::last_child } + }; + + bdecode_node nodes46_keys[5]; + + ret = verify_message(response, nodes46_desc, nodes46_keys, error_string); + + if (ret) + { + char const* nodes_ptr = nodes46_keys[3].string_ptr(); + TEST_CHECK(memcmp(nodes_ptr, id.data(), id.size()) == 0); + nodes_ptr += id.size(); + udp::endpoint rep = detail::read_v4_endpoint(nodes_ptr); + TEST_EQUAL(rep, udp::endpoint(addr("4.4.4.4"), 4440)); + + nodes_ptr = nodes46_keys[4].string_ptr(); + TEST_CHECK(memcmp(nodes_ptr, id.data(), id.size()) == 0); + nodes_ptr += id.size(); + rep = detail::read_v6_endpoint(nodes_ptr); + TEST_EQUAL(rep, udp::endpoint(addr("4::4"), 4441)); + } + else + { + std::printf("find_node response: %s\n", print_entry(response).c_str()); + TEST_ERROR(error_string); + } +} + +TORRENT_TEST(multi_home) +{ + // send a request with a different listen socket and make sure the node ignores it + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + bdecode_node response; + + entry e; + e["q"] = "ping"; + e["t"] = "10"; + e["y"] = "q"; + e["a"].dict().insert(std::make_pair("id", generate_next().to_string())); + char msg_buf[1500]; + int size = bencode(msg_buf, e); + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) std::printf("bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, t.source); + t.dht_node.incoming(dummy_listen_socket(udp::endpoint(rand_v4(), 21)), m); + TEST_CHECK(g_sent_packets.empty()); + g_sent_packets.clear(); +} + +TORRENT_TEST(signing_test1) +{ + // test vector 1 + + // test content + span test_content("12:Hello World!", 15); + // test salt + span test_salt("foobar", 6); + + public_key pk; + secret_key sk; + get_test_keypair(pk, sk); + + signature sig; + sig = sign_mutable_item(test_content, empty_salt, sequence_number(1), pk, sk); + + TEST_EQUAL(aux::to_hex(sig.bytes) + , "305ac8aeb6c9c151fa120f120ea2cfb923564e11552d06a5d856091e5e853cff" + "1260d3f39e4999684aa92eb73ffd136e6f4f3ecbfda0ce53a1608ecd7ae21f01"); + + sha1_hash const target_id = item_target_id(empty_salt, pk); + TEST_EQUAL(aux::to_hex(target_id), "4a533d47ec9c7d95b1ad75f576cffc641853b750"); +} + +TORRENT_TEST(signing_test2) +{ + public_key pk; + secret_key sk; + get_test_keypair(pk, sk); + + // test content + span test_content("12:Hello World!", 15); + + signature sig; + // test salt + span test_salt("foobar", 6); + + // test vector 2 (the keypair is the same as test 1) + sig = sign_mutable_item(test_content, test_salt, sequence_number(1), pk, sk); + + TEST_EQUAL(aux::to_hex(sig.bytes) + , "6834284b6b24c3204eb2fea824d82f88883a3d95e8b4a21b8c0ded553d17d17d" + "df9a8a7104b1258f30bed3787e6cb896fca78c58f8e03b5f18f14951a87d9a08"); + + sha1_hash target_id = item_target_id(test_salt, pk); + TEST_EQUAL(aux::to_hex(target_id), "411eba73b6f087ca51a3795d9c8c938d365e32c1"); +} + +TORRENT_TEST(signing_test3) +{ + // test vector 3 + + // test content + span test_content("12:Hello World!", 15); + + sha1_hash target_id = item_target_id(test_content); + TEST_EQUAL(aux::to_hex(target_id), "e5f96f6f38320f0f33959cb4d3d656452117aadb"); +} + +// TODO: 2 split this up into smaller test cases +TORRENT_TEST(verify_message) +{ + char error_string[200]; + + // test verify_message + static const key_desc_t msg_desc[] = { + {"A", bdecode_node::string_t, 4, 0}, + {"B", bdecode_node::dict_t, 0, key_desc_t::optional | key_desc_t::parse_children}, + {"B1", bdecode_node::string_t, 0, 0}, + {"B2", bdecode_node::string_t, 0, key_desc_t::last_child}, + {"C", bdecode_node::dict_t, 0, key_desc_t::optional | key_desc_t::parse_children}, + {"C1", bdecode_node::string_t, 0, 0}, + {"C2", bdecode_node::string_t, 0, key_desc_t::last_child}, + }; + + bdecode_node msg_keys[7]; + + bdecode_node ent; + + error_code ec; + char const test_msg[] = "d1:A4:test1:Bd2:B15:test22:B25:test3ee"; + bdecode(test_msg, test_msg + sizeof(test_msg)-1, ent, ec); + std::printf("%s\n", print_entry(ent).c_str()); + + bool ret = verify_message(ent, msg_desc, msg_keys, error_string); + TEST_CHECK(ret); + TEST_CHECK(msg_keys[0]); + if (msg_keys[0]) TEST_EQUAL(msg_keys[0].string_value(), "test"); + TEST_CHECK(msg_keys[1]); + TEST_CHECK(msg_keys[2]); + if (msg_keys[2]) TEST_EQUAL(msg_keys[2].string_value(), "test2"); + TEST_CHECK(msg_keys[3]); + if (msg_keys[3]) TEST_EQUAL(msg_keys[3].string_value(), "test3"); + TEST_CHECK(!msg_keys[4]); + TEST_CHECK(!msg_keys[5]); + TEST_CHECK(!msg_keys[6]); + + char const test_msg2[] = "d1:A4:test1:Cd2:C15:test22:C25:test3ee"; + bdecode(test_msg2, test_msg2 + sizeof(test_msg2)-1, ent, ec); + std::printf("%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string); + TEST_CHECK(ret); + TEST_CHECK(msg_keys[0]); + if (msg_keys[0]) TEST_EQUAL(msg_keys[0].string_value(), "test"); + TEST_CHECK(!msg_keys[1]); + TEST_CHECK(!msg_keys[2]); + TEST_CHECK(!msg_keys[3]); + TEST_CHECK(msg_keys[4]); + TEST_CHECK(msg_keys[5]); + if (msg_keys[5]) TEST_EQUAL(msg_keys[5].string_value(), "test2"); + TEST_CHECK(msg_keys[6]); + if (msg_keys[6]) TEST_EQUAL(msg_keys[6].string_value(), "test3"); + + + char const test_msg3[] = "d1:Cd2:C15:test22:C25:test3ee"; + bdecode(test_msg3, test_msg3 + sizeof(test_msg3)-1, ent, ec); + std::printf("%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string); + TEST_CHECK(!ret); + std::printf("%s\n", error_string); + TEST_EQUAL(error_string, std::string("missing 'A' key")); + + char const test_msg4[] = "d1:A6:foobare"; + bdecode(test_msg4, test_msg4 + sizeof(test_msg4)-1, ent, ec); + std::printf("%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string); + TEST_CHECK(!ret); + std::printf("%s\n", error_string); + TEST_EQUAL(error_string, std::string("invalid value for 'A'")); + + char const test_msg5[] = "d1:A4:test1:Cd2:C15:test2ee"; + bdecode(test_msg5, test_msg5 + sizeof(test_msg5)-1, ent, ec); + std::printf("%s\n", print_entry(ent).c_str()); + + ret = verify_message(ent, msg_desc, msg_keys, error_string); + TEST_CHECK(!ret); + std::printf("%s\n", error_string); + TEST_EQUAL(error_string, std::string("missing 'C2' key")); + + // test empty strings [ { "":1 }, "" ] + char const test_msg6[] = "ld0:i1ee0:e"; + bdecode(test_msg6, test_msg6 + sizeof(test_msg6)-1, ent, ec); + std::printf("%s\n", print_entry(ent).c_str()); + TEST_CHECK(ent.type() == bdecode_node::list_t); + if (ent.type() == bdecode_node::list_t) + { + TEST_CHECK(ent.list_size() == 2); + if (ent.list_size() == 2) + { + TEST_CHECK(ent.list_at(0).dict_find_int_value("") == 1); + TEST_CHECK(ent.list_at(1).string_value() == ""); + } + } +} + +TORRENT_TEST(routing_table_uniform) +{ + // test routing table + dht::settings sett = test_settings(); + obs observer; + + sett.extended_routing_table = false; + // it's difficult to generate valid nodes with specific node IDs, so just + // turn off that check + sett.prefer_verified_node_ids = false; + node_id id = to_hash("1234876923549721020394873245098347598635"); + node_id diff = to_hash("15764f7459456a9453f8719b09547c11d5f34061"); + + routing_table tbl(id, udp::v4(), 8, sett, &observer); + + // insert 256 nodes evenly distributed across the ID space. + // we expect to fill the top 5 buckets + for (int i = 255; i >= 0; --i) + { + // test a node with the same IP:port changing ID + add_and_replace(id, diff); + // in order to make this node-load a bit more realistic, start from + // distant nodes and work our way in closer to the node id + // the routing table will reject nodes that are too imbalanced (if all + // nodes are very close to our ID and none are far away, it's + // suspicious). + id[0] ^= i; + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); + + // restore the first byte of the node ID + id[0] ^= i; + } + std::printf("num_active_buckets: %d\n", tbl.num_active_buckets()); + // number of nodes per tree level (when adding 256 evenly distributed + // nodes): + // 0: 128 + // 1: 64 + // 2: 32 + // 3: 16 + // 4: 8 + // i.e. no more than 5 levels + TEST_EQUAL(tbl.num_active_buckets(), 6); + + print_state(std::cout, tbl); +} + +TORRENT_TEST(routing_table_balance) +{ + dht::settings sett = test_settings(); + obs observer; + + sett.extended_routing_table = false; + sett.prefer_verified_node_ids = false; + node_id id = to_hash("1234876923549721020394873245098347598635"); + + routing_table tbl(id, udp::v4(), 8, sett, &observer); + + // insert nodes in the routing table that will force it to split + // and make sure we don't end up with a table completely out of balance + for (int i = 0; i < 32; ++i) + { + id[0] = (i << 3) & 0xff; + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); + } + std::printf("num_active_buckets: %d\n", tbl.num_active_buckets()); + TEST_EQUAL(tbl.num_active_buckets(), 2); + + print_state(std::cout, tbl); +} + +TORRENT_TEST(routing_table_extended) +{ + dht::settings sett = test_settings(); + obs observer; + sett.extended_routing_table = true; + sett.prefer_verified_node_ids = false; + node_id id = to_hash("1234876923549721020394873245098347598635"); + node_id diff = to_hash("15764f7459456a9453f8719b09547c11d5f34061"); + + // we can't add the nodes in straight 0,1,2,3 order. That way the routing + // table would get unbalanced and intermediate nodes would be dropped + std::vector node_id_prefix; + node_id_prefix.reserve(256); + for (int i = 0; i < 256; ++i) node_id_prefix.push_back(i & 0xff); + aux::random_shuffle(node_id_prefix); + + routing_table tbl(id, udp::v4(), 8, sett, &observer); + for (std::size_t i = 0; i < 256; ++i) + { + add_and_replace(id, diff); + id[0] = node_id_prefix[i]; + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); + } + TEST_EQUAL(tbl.num_active_buckets(), 6); + + print_state(std::cout, tbl); +} + +namespace { +void inserter(std::set* nodes, node_entry const& ne) +{ + nodes->insert(nodes->begin(), ne.id); +} +} // anonymous namespace + +TORRENT_TEST(routing_table_set_id) +{ + dht::settings sett = test_settings(); + sett.enforce_node_id = false; + sett.extended_routing_table = false; + sett.prefer_verified_node_ids = false; + obs observer; + node_id id = to_hash("0000000000000000000000000000000000000000"); + + // we can't add the nodes in straight 0,1,2,3 order. That way the routing + // table would get unbalanced and intermediate nodes would be dropped + std::vector node_id_prefix; + node_id_prefix.reserve(256); + for (int i = 0; i < 256; ++i) node_id_prefix.push_back(i & 0xff); + aux::random_shuffle(node_id_prefix); + routing_table tbl(id, udp::v4(), 8, sett, &observer); + for (std::size_t i = 0; i < 256; ++i) + { + id[0] = node_id_prefix[i]; + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); + } + TEST_EQUAL(tbl.num_active_buckets(), 6); + + std::set original_nodes; + tbl.for_each_node(std::bind(&inserter, &original_nodes, _1)); + + print_state(std::cout, tbl); + + id = to_hash("ffffffffffffffffffffffffffffffffffffffff"); + + tbl.update_node_id(id); + + TEST_CHECK(tbl.num_active_buckets() <= 4); + std::set remaining_nodes; + tbl.for_each_node(std::bind(&inserter, &remaining_nodes, _1)); + + std::set intersection; + std::set_intersection(remaining_nodes.begin(), remaining_nodes.end() + , original_nodes.begin(), original_nodes.end() + , std::inserter(intersection, intersection.begin())); + + // all remaining nodes also exist in the original nodes + TEST_EQUAL(intersection.size(), remaining_nodes.size()); + + print_state(std::cout, tbl); +} + +TORRENT_TEST(routing_table_for_each) +{ + dht::settings sett = test_settings(); + obs observer; + + sett.extended_routing_table = false; + sett.prefer_verified_node_ids = false; + node_id id = to_hash("1234876923549721020394873245098347598635"); + + routing_table tbl(id, udp::v4(), 2, sett, &observer); + + for (int i = 0; i < 32; ++i) + { + id[0] = (i << 3) & 0xff; + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); + } + + int nodes; + int replacements; + std::tie(nodes, replacements, std::ignore) = tbl.size(); + + std::printf("num_active_buckets: %d\n", tbl.num_active_buckets()); + std::printf("live nodes: %d\n", nodes); + std::printf("replacements: %d\n", replacements); + + TEST_EQUAL(tbl.num_active_buckets(), 2); + TEST_EQUAL(nodes, 4); + TEST_EQUAL(replacements, 4); + + print_state(std::cout, tbl); + + std::vector v; + tbl.for_each_node([&](node_entry const& e) { v.push_back(e); }, nullptr); + TEST_EQUAL(v.size(), 4); + v.clear(); + tbl.for_each_node(nullptr, [&](node_entry const& e) { v.push_back(e); }); + TEST_EQUAL(v.size(), 4); + v.clear(); + tbl.for_each_node([&](node_entry const& e) { v.push_back(e); }); + TEST_EQUAL(v.size(), 8); +} + +TORRENT_TEST(node_set_id) +{ + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + node_id old_nid = t.dht_node.nid(); + // put in a few votes to make sure the address really changes + for (int i = 0; i < 25; ++i) + t.observer.set_external_address(aux::listen_socket_handle(t.ls), addr4("237.0.0.1"), rand_v4()); + t.dht_node.update_node_id(); + TEST_CHECK(old_nid != t.dht_node.nid()); + // now that we've changed the node's id, make sure the id sent in outgoing messages + // reflects the change + + bdecode_node response; + send_dht_request(t.dht_node, "ping", t.source, &response); + + dht::key_desc_t const pong_desc[] = { + { "y", bdecode_node::string_t, 1, 0 }, + { "t", bdecode_node::string_t, 2, 0 }, + { "r", bdecode_node::dict_t, 0, key_desc_t::parse_children }, + { "id", bdecode_node::string_t, 20, key_desc_t::last_child }, + }; + bdecode_node pong_keys[4]; + bool ret = dht::verify_message(response, pong_desc, pong_keys, t.error_string); + TEST_CHECK(ret); + if (!ret) return; + + TEST_EQUAL(node_id(pong_keys[3].string_ptr()), t.dht_node.nid()); +} + +TORRENT_TEST(read_only_node) +{ + // TODO: 3 use dht_test_setup class to simplify the node setup + dht::settings sett = test_settings(); + sett.read_only = true; + mock_socket s; + auto ls = dummy_listen_socket4(); + obs observer; + counters cnt; + + std::unique_ptr dht_storage(dht_default_storage_constructor(sett)); + dht_storage->update_node_ids({node_id(nullptr)}); + dht::node node(ls, &s, sett, node_id(nullptr), &observer, cnt, get_foreign_node_stub, *dht_storage); + udp::endpoint source(addr("10.0.0.1"), 20); + bdecode_node response; + msg_args args; + + // for incoming requests, read_only node won't response. + send_dht_request(node, "ping", source, &response, args, "10", false); + TEST_EQUAL(response.type(), bdecode_node::none_t); + + args.target(sha1_hash("01010101010101010101")); + send_dht_request(node, "get", source, &response, args, "10", false); + TEST_EQUAL(response.type(), bdecode_node::none_t); + + // also, the sender shouldn't be added to routing table. + TEST_EQUAL(std::get<0>(node.size()), 0); + + // for outgoing requests, read_only node will add 'ro' key (value == 1) + // in top-level of request. + bdecode_node parsed[7]; + char error_string[200]; + udp::endpoint initial_node(addr("4.4.4.4"), 1234); + dht::node_id const initial_node_id = to_hash("1111111111222222222233333333334444444444"); + node.m_table.add_node(node_entry{initial_node_id, initial_node, 10, true}); + bdecode_node request; + sha1_hash target = generate_next(); + + node.get_item(target, get_immutable_item_cb); + TEST_EQUAL(g_sent_packets.size(), 1); + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + dht::key_desc_t const get_item_desc_ro[] = { + {"y", bdecode_node::string_t, 1, 0}, + {"t", bdecode_node::string_t, 2, 0}, + {"q", bdecode_node::string_t, 3, 0}, + {"ro", bdecode_node::int_t, 4, key_desc_t::optional}, + {"a", bdecode_node::dict_t, 0, key_desc_t::parse_children}, + {"id", bdecode_node::string_t, 20, 0}, + {"target", bdecode_node::string_t, 20, key_desc_t::last_child}, + }; + + node_from_entry(g_sent_packets.front().second, request); + bool ret = verify_message(request, get_item_desc_ro, parsed, error_string); + + TEST_CHECK(ret); + TEST_EQUAL(parsed[3].int_value(), 1); + + // should have one node now, which is 4.4.4.4:1234 + TEST_EQUAL(std::get<0>(node.size()), 1); + // and no replacement nodes + TEST_EQUAL(std::get<1>(node.size()), 0); + + // now, disable read_only, try again. + g_sent_packets.clear(); + sett.read_only = false; + + send_dht_request(node, "get", source, &response); + // sender should be added to repacement bucket + TEST_EQUAL(std::get<1>(node.size()), 1); + + g_sent_packets.clear(); +#if 0 + // TODO: this won't work because the second node isn't pinged so it wont + // be added to the routing table + target = generate_next(); + node.get_item(target, get_immutable_item_cb); + + // since we have 2 nodes, we should have two packets. + TEST_EQUAL(g_sent_packets.size(), 2); + + // both of them shouldn't have a 'ro' key. + node_from_entry(g_sent_packets.front().second, request); + ret = verify_message(request, get_item_desc_ro, parsed, error_string); + + TEST_CHECK(ret); + TEST_CHECK(!parsed[3]); + + node_from_entry(g_sent_packets.back().second, request); + ret = verify_message(request, get_item_desc_ro, parsed, error_string); + + TEST_CHECK(ret); + TEST_CHECK(!parsed[3]); +#endif +} + +#ifndef TORRENT_DISABLE_LOGGING +// these tests rely on logging being enabled + +TORRENT_TEST(invalid_error_msg) +{ + // TODO: 3 use dht_test_setup class to simplify the node setup + dht::settings sett = test_settings(); + mock_socket s; + auto ls = dummy_listen_socket4(); + obs observer; + counters cnt; + + std::unique_ptr dht_storage(dht_default_storage_constructor(sett)); + dht_storage->update_node_ids({node_id(nullptr)}); + dht::node node(ls, &s, sett, node_id(nullptr), &observer, cnt, get_foreign_node_stub, *dht_storage); + udp::endpoint source(addr("10.0.0.1"), 20); + + entry e; + e["y"] = "e"; + e["e"].string() = "Malformed Error"; + char msg_buf[1500]; + int size = bencode(msg_buf, e); + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) std::printf("bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, source); + node.incoming(node.m_sock, m); + + bool found = false; + for (auto const& log : observer.m_log) + { + if (log.find("INCOMING ERROR") != std::string::npos + && log.find("(malformed)") != std::string::npos) + found = true; + + std::printf("%s\n", log.c_str()); + } + + TEST_EQUAL(found, true); +} + +struct test_algo : dht::traversal_algorithm +{ + test_algo(node& dht_node, node_id const& target) + : traversal_algorithm(dht_node, target) + {} + + void done() { this->dht::traversal_algorithm::done(); } + + std::vector const& results() const { return m_results; } + + using traversal_algorithm::num_sorted_results; +}; + +TORRENT_TEST(unsorted_traversal_results) +{ + init_rand_address(); + + // make sure the handling of an unsorted tail of nodes is correct in the + // traversal algorithm. Initial nodes (that we bootstrap from) remain + // unsorted, since we don't know their node IDs + + dht_test_setup t(udp::endpoint(rand_v4(), 20)); + + node_id const our_id = t.dht_node.nid(); + auto algo = std::make_shared(t.dht_node, our_id); + + std::vector eps; + for (int i = 0; i < 10; ++i) + { + eps.push_back(rand_udp_ep(rand_v4)); + algo->add_entry(node_id(), eps.back(), observer::flag_initial); + } + + // we should have 10 unsorted nodes now + TEST_CHECK(algo->num_sorted_results() == 0); + auto results = algo->results(); + TEST_CHECK(results.size() == eps.size()); + for (std::size_t i = 0; i < eps.size(); ++i) + TEST_CHECK(eps[i] == results[i]->target_ep()); + + // setting the node ID, regardless of what we set it to, should cause this + // observer to become sorted. i.e. be moved to the beginning of the result + // list. + results[5]->set_id(node_id("abababababababababab")); + + TEST_CHECK(algo->num_sorted_results() == 1); + results = algo->results(); + TEST_CHECK(results.size() == eps.size()); + TEST_CHECK(eps[5] == results[0]->target_ep()); + algo->done(); +} + +TORRENT_TEST(rpc_invalid_error_msg) +{ + // TODO: 3 use dht_test_setup class to simplify the node setup + dht::settings sett = test_settings(); + mock_socket s; + auto ls = dummy_listen_socket4(); + obs observer; + counters cnt; + + dht::routing_table table(node_id(), udp::v4(), 8, sett, &observer); + dht::rpc_manager rpc(node_id(), sett, table, ls, &s, &observer); + std::unique_ptr dht_storage(dht_default_storage_constructor(sett)); + dht_storage->update_node_ids({node_id(nullptr)}); + dht::node node(ls, &s, sett, node_id(nullptr), &observer, cnt, get_foreign_node_stub, *dht_storage); + + udp::endpoint source(addr("10.0.0.1"), 20); + + // we need this to create an entry for this transaction ID, otherwise the + // incoming message will just be dropped + entry req; + req["y"] = "q"; + req["q"] = "bogus_query"; + req["t"] = "\0\0\0\0"; + + g_sent_packets.clear(); + auto algo = std::make_shared(node, node_id()); + + auto o = rpc.allocate_observer(std::move(algo), source, node_id()); +#if TORRENT_USE_ASSERTS + o->m_in_constructor = false; +#endif + rpc.invoke(req, source, o); + + // here's the incoming (malformed) error message + entry err; + err["y"] = "e"; + err["e"].string() = "Malformed Error"; + err["t"] = g_sent_packets.begin()->second["t"].string(); + char msg_buf[1500]; + int size = bencode(msg_buf, err); + + bdecode_node decoded; + error_code ec; + bdecode(msg_buf, msg_buf + size, decoded, ec); + if (ec) std::printf("bdecode failed: %s\n", ec.message().c_str()); + + dht::msg m(decoded, source); + node_id nid; + rpc.incoming(m, &nid); + + bool found = false; + for (auto const& log : observer.m_log) + { + if (log.find("reply with") != std::string::npos + && log.find("(malformed)") != std::string::npos + && log.find("error") != std::string::npos) + found = true; + + std::printf("%s\n", log.c_str()); + } + + TEST_EQUAL(found, true); +} +#endif + +// test bucket distribution +TORRENT_TEST(node_id_bucket_distribution) +{ + init_rand_address(); + + int nodes_per_bucket[160] = {0}; + dht::node_id reference_id = generate_id(rand_v4()); + int const num_samples = 100000; + for (int i = 0; i < num_samples; ++i) + { + dht::node_id nid = generate_id(rand_v4()); + int const bucket = 159 - distance_exp(reference_id, nid); + ++nodes_per_bucket[bucket]; + } + + for (int i = 0; i < 25; ++i) + { + std::printf("%3d ", nodes_per_bucket[i]); + } + std::printf("\n"); + + int expected = num_samples / 2; + for (int i = 0; i < 25; ++i) + { + TEST_CHECK(std::abs(nodes_per_bucket[i] - expected) < num_samples / 20); + expected /= 2; + } +} + +TORRENT_TEST(node_id_min_distance_exp) +{ + node_id const n1 = to_hash("0000000000000000000000000000000000000002"); + node_id const n2 = to_hash("0000000000000000000000000000000000000004"); + node_id const n3 = to_hash("0000000000000000000000000000000000000008"); + + std::vector ids; + + ids.push_back(n1); + + TEST_EQUAL(min_distance_exp(sha1_hash::min(), ids), 1); + + ids.push_back(n1); + ids.push_back(n2); + + TEST_EQUAL(min_distance_exp(sha1_hash::min(), ids), 1); + + ids.push_back(n1); + ids.push_back(n2); + ids.push_back(n3); + + TEST_EQUAL(min_distance_exp(sha1_hash::min(), ids), 1); + + ids.clear(); + ids.push_back(n3); + ids.push_back(n2); + ids.push_back(n2); + + TEST_EQUAL(min_distance_exp(sha1_hash::min(), ids), 2); +} + +TORRENT_TEST(dht_verify_node_address) +{ + obs observer; + // initial setup taken from dht test above + dht::settings s; + s.extended_routing_table = false; + node_id id = to_hash("3123456789abcdef01232456789abcdef0123456"); + const int bucket_size = 8; + dht::routing_table table(id, udp::v4(), bucket_size, s, &observer); + std::vector nodes; + TEST_EQUAL(std::get<0>(table.size()), 0); + + node_id tmp = id; + node_id diff = to_hash("15764f7459456a9453f8719b09547c11d5f34061"); + + add_and_replace(tmp, diff); + table.node_seen(tmp, udp::endpoint(addr("4.4.4.4"), 4), 10); + table.find_node(id, nodes, 0, 10); + TEST_EQUAL(std::get<0>(table.size()), 1); + TEST_EQUAL(nodes.size(), 1); + + // incorrect data, wrong IP + table.node_seen(tmp + , udp::endpoint(addr("4.4.4.6"), 4), 10); + table.find_node(id, nodes, 0, 10); + + TEST_EQUAL(std::get<0>(table.size()), 1); + TEST_EQUAL(nodes.size(), 1); + + // incorrect data, wrong id, should cause node to be removed + table.node_seen(to_hash("0123456789abcdef01232456789abcdef0123456") + , udp::endpoint(addr("4.4.4.4"), 4), 10); + table.find_node(id, nodes, 0, 10); + + TEST_EQUAL(std::get<0>(table.size()), 0); + TEST_EQUAL(nodes.size(), 0); +} + +TORRENT_TEST(generate_prefix_mask) +{ + std::vector> const test = { + { 0, "0000000000000000000000000000000000000000" }, + { 1, "8000000000000000000000000000000000000000" }, + { 2, "c000000000000000000000000000000000000000" }, + { 11, "ffe0000000000000000000000000000000000000" }, + { 17, "ffff800000000000000000000000000000000000" }, + { 37, "fffffffff8000000000000000000000000000000" }, + { 160, "ffffffffffffffffffffffffffffffffffffffff" }, + }; + + for (auto const& i : test) + { + TEST_EQUAL(generate_prefix_mask(i.first), to_hash(i.second)); + } +} + +TORRENT_TEST(distance_exp) +{ + // distance_exp + + + using tst = std::tuple; + + std::vector> const distance_tests = { + tst{ "ffffffffffffffffffffffffffffffffffffffff" + , "0000000000000000000000000000000000000000", 159 }, + + tst{ "ffffffffffffffffffffffffffffffffffffffff" + , "7fffffffffffffffffffffffffffffffffffffff", 159 }, + + tst{ "ffffffffffffffffffffffffffffffffffffffff" + , "ffffffffffffffffffffffffffffffffffffffff", 0 }, + + tst{ "ffffffffffffffffffffffffffffffffffffffff" + , "fffffffffffffffffffffffffffffffffffffffe", 0 }, + + tst{ "8000000000000000000000000000000000000000" + , "fffffffffffffffffffffffffffffffffffffffe", 158 }, + + tst{ "c000000000000000000000000000000000000000" + , "fffffffffffffffffffffffffffffffffffffffe", 157 }, + + tst{ "e000000000000000000000000000000000000000" + , "fffffffffffffffffffffffffffffffffffffffe", 156 }, + + tst{ "f000000000000000000000000000000000000000" + , "fffffffffffffffffffffffffffffffffffffffe", 155 }, + + tst{ "f8f2340985723049587230495872304958703294" + , "f743589043r890f023980f90e203980d090c3840", 155 }, + + tst{ "ffff740985723049587230495872304958703294" + , "ffff889043r890f023980f90e203980d090c3840", 159 - 16 }, + }; + + for (auto const& t : distance_tests) + { + std::printf("%s %s: %d\n" + , std::get<0>(t), std::get<1>(t), std::get<2>(t)); + + TEST_EQUAL(distance_exp(to_hash(std::get<0>(t)) + , to_hash(std::get<1>(t))), std::get<2>(t)); + } +} + +TORRENT_TEST(compare_ip_cidr) +{ + using tst = std::tuple; + std::vector const v4tests = { + tst{"10.255.255.0", "10.255.255.255", true}, + tst{"11.0.0.0", "10.255.255.255", false}, + tst{"0.0.0.0", "128.255.255.255", false}, + tst{"0.0.0.0", "127.255.255.255", false}, + tst{"255.255.255.0", "255.255.255.255", true}, + tst{"255.254.255.0", "255.255.255.255", false}, + tst{"0.0.0.0", "0.0.0.0", true}, + tst{"255.255.255.255", "255.255.255.255", true}, + }; + + for (auto const& t : v4tests) + { + std::printf("%s %s\n", std::get<0>(t), std::get<1>(t)); + TEST_EQUAL(compare_ip_cidr( + addr4(std::get<0>(t)), addr4(std::get<1>(t))), std::get<2>(t)); + } + + std::vector const v6tests = { + tst{"::1", "::ffff:ffff:ffff:ffff", true}, + tst{"::2:0000:0000:0000:0000", "::1:ffff:ffff:ffff:ffff", false}, + tst{"::ff:0000:0000:0000:0000", "::ffff:ffff:ffff:ffff", false}, + tst{"::caca:0000:0000:0000:0000", "::ffff:ffff:ffff:ffff:ffff", false}, + tst{"::a:0000:0000:0000:0000", "::b:ffff:ffff:ffff:ffff", false}, + tst{"::7f:0000:0000:0000:0000", "::ffff:ffff:ffff:ffff", false}, + tst{"7f::", "ff::", false}, + tst{"ff::", "ff::", true}, + tst{"::", "::", true}, + tst{"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, + }; + + for (auto const& t : v6tests) + { + TEST_EQUAL(compare_ip_cidr( + addr6(std::get<0>(t)), addr6(std::get<1>(t))), std::get<2>(t)); + } +} + +TORRENT_TEST(dht_state) +{ + dht_state s; + + s.nids.emplace_back(address::from_string("1.1.1.1"), to_hash("0000000000000000000000000000000000000001")); + s.nodes.push_back(uep("1.1.1.1", 1)); + s.nodes.push_back(uep("2.2.2.2", 2)); + // remove these for now because they will only get used if the host system has IPv6 support + // hopefully in the future we can rely on the test system supporting IPv6 + //s.nids.emplace_back(address::from_string("1::1"), to_hash("0000000000000000000000000000000000000002")); + //s.nodes6.push_back(uep("1::1", 3)); + //s.nodes6.push_back(uep("2::2", 4)); + + entry const e = save_dht_state(s); + + std::vector tmp; + bencode(std::back_inserter(tmp), e); + + bdecode_node n; + error_code ec; + int r = bdecode(&tmp[0], &tmp[0] + tmp.size(), n, ec); + TEST_CHECK(!r); + + dht_state const s1 = read_dht_state(n); + TEST_CHECK(s1.nids == s.nids); + TEST_CHECK(s1.nodes == s.nodes); + + // empty + bdecode_node n1; + dht_state const s2 = read_dht_state(n1); + TEST_CHECK(s2.nids.empty()); + TEST_CHECK(s2.nodes.empty()); +} + +TORRENT_TEST(sample_infohashes) +{ + init_rand_address(); + + dht_test_setup t(rand_udp_ep()); + bdecode_node response; + + g_sent_packets.clear(); + + udp::endpoint initial_node = rand_udp_ep(); + t.dht_node.m_table.add_node(node_entry{initial_node}); + + // nodes + sha1_hash const h1 = rand_hash(); + sha1_hash const h2 = rand_hash(); + udp::endpoint const ep1 = rand_udp_ep(rand_v4); + udp::endpoint const ep2 = rand_udp_ep(rand_v4); + + t.dht_node.sample_infohashes(initial_node, items[0].target, + [h1, ep1, h2, ep2](time_duration interval, int num + , std::vector samples + , std::vector> const& nodes) + { + TEST_EQUAL(total_seconds(interval), 10); + TEST_EQUAL(num, 2); + TEST_EQUAL(samples.size(), 1); + TEST_EQUAL(samples[0], to_hash("1000000000000000000000000000000000000001")); + TEST_EQUAL(nodes.size(), 2); + TEST_EQUAL(nodes[0].first, h1); + TEST_EQUAL(nodes[0].second, ep1); + TEST_EQUAL(nodes[1].first, h2); + TEST_EQUAL(nodes[1].second, ep2); + }); + + TEST_EQUAL(g_sent_packets.size(), 1); + if (g_sent_packets.empty()) return; + TEST_EQUAL(g_sent_packets.front().first, initial_node); + + node_from_entry(g_sent_packets.front().second, response); + bdecode_node sample_infohashes_keys[6]; + bool const ret = verify_message(response + , sample_infohashes_desc, sample_infohashes_keys, t.error_string); + if (ret) + { + TEST_EQUAL(sample_infohashes_keys[0].string_value(), "q"); + TEST_EQUAL(sample_infohashes_keys[2].string_value(), "sample_infohashes"); + TEST_EQUAL(sample_infohashes_keys[5].string_value(), items[0].target.to_string()); + } + else + { + std::printf(" invalid sample_infohashes request: %s\n", print_entry(response).c_str()); + TEST_ERROR(t.error_string); + return; + } + + std::vector nodes; + nodes.emplace_back(h1, ep1); + nodes.emplace_back(h2, ep2); + + g_sent_packets.clear(); + send_dht_response(t.dht_node, response, initial_node + , msg_args() + .interval(seconds(10)) + .num(2) + .samples({to_hash("1000000000000000000000000000000000000001")}) + .nodes(nodes)); + + TEST_CHECK(g_sent_packets.empty()); +} + +namespace { +node_entry fake_node(bool verified, int rtt = 0) +{ + node_entry e(rand_udp_ep()); + e.verified = verified; + e.rtt = static_cast(rtt); + return e; +} +} + +TORRENT_TEST(node_entry_comparison) +{ + // being verified or not always trumps RTT in sort order + TEST_CHECK(fake_node(true, 10) < fake_node(false, 5)); + TEST_CHECK(fake_node(true, 5) < fake_node(false, 10)); + TEST_CHECK(!(fake_node(false, 10) < fake_node(true, 5))); + TEST_CHECK(!(fake_node(false, 5) < fake_node(true, 10))); + + // if both are verified, lower RTT is better + TEST_CHECK(fake_node(true, 5) < fake_node(true, 10)); + TEST_CHECK(!(fake_node(true, 10) < fake_node(true, 5))); + + // if neither are verified, lower RTT is better + TEST_CHECK(fake_node(false, 5) < fake_node(false, 10)); + TEST_CHECK(!(fake_node(false, 10) < fake_node(false, 5))); +} + +TORRENT_TEST(mostly_verified_nodes) +{ + // an empty bucket is OK + TEST_CHECK(mostly_verified_nodes({})); + TEST_CHECK(mostly_verified_nodes({fake_node(true)})); + TEST_CHECK(mostly_verified_nodes({fake_node(true), fake_node(false)})); + TEST_CHECK(mostly_verified_nodes({fake_node(true), fake_node(true), fake_node(false)})); + TEST_CHECK(mostly_verified_nodes({fake_node(true), fake_node(true), fake_node(true), fake_node(false)})); + + // a large bucket with only half of the nodes verified, does not count as + // "mostly" + TEST_CHECK(!mostly_verified_nodes({fake_node(true), fake_node(false) + , fake_node(true), fake_node(false) + , fake_node(true), fake_node(false) + , fake_node(true), fake_node(false) + , fake_node(true), fake_node(false) + , fake_node(true), fake_node(false)})); + + // 1 of 3 is not "mostly" + TEST_CHECK(!mostly_verified_nodes({fake_node(false), fake_node(true), fake_node(false)})); + + TEST_CHECK(!mostly_verified_nodes({fake_node(false)})); + TEST_CHECK(!mostly_verified_nodes({fake_node(false), fake_node(false)})); + TEST_CHECK(!mostly_verified_nodes({fake_node(false), fake_node(false), fake_node(false)})); +} + +TORRENT_TEST(classify_prefix) +{ + // the last bucket in the routing table + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("0cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 0); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("2cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 1); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("4cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 2); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("6cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 3); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 4); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("acdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 5); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("ccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 6); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("ecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 7); + TEST_EQUAL(int(classify_prefix(0, true, 8, to_hash("fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 7); + + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("c0cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 0); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("c2cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 1); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("c4cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 2); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("c6cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 3); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("c8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 4); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("cacdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 5); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("cccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 6); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("cecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + TEST_EQUAL(int(classify_prefix(4, true, 8, to_hash("cfcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dc0cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 0); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dc2cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 1); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dc4cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 2); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dc6cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 3); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dc8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 4); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dcacdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 5); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dcccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 6); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dcecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 7); + TEST_EQUAL(int(classify_prefix(8, true, 8, to_hash("dcfcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc"))), 7); + + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdc0cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 0); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdc2cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 1); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdc4cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 2); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdc6cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 3); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdc8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 4); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdcacdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 5); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdcccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 6); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdcecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + TEST_EQUAL(int(classify_prefix(12, true, 8, to_hash("cdcfcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + + // not the last bucket in the routing table + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdc0cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 0); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdc2cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 1); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdc4cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 2); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdc6cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 3); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdc8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 4); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdcacdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 5); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdcccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 6); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdcecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + TEST_EQUAL(int(classify_prefix(11, false, 8, to_hash("cdcfcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdc8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 0); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdc9cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 1); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdcacdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 2); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdcbcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 3); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdcccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 4); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 5); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdcecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 6); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdcfcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + TEST_EQUAL(int(classify_prefix(12, false, 8, to_hash("cdc7cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + + // larger bucket + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc0cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 0); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc1cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 1); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc2cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 2); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc3cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 3); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc4cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 4); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc5cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 5); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc6cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 6); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc7cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 7); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc8cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 8); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdc9cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 9); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdcacdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 10); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdcbcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 11); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdcccdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 12); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 13); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdcecdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 14); + TEST_EQUAL(int(classify_prefix(12, true, 16, to_hash("cdcfcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"))), 15); +} + +namespace { +node_entry n(ip_set* ips, char const* nid, bool verified = true, int rtt = 0, int failed = 0) +{ + node_entry e(rand_udp_ep()); + if (ips) ips->insert(e.addr()); + e.verified = verified; + e.rtt = static_cast(rtt); + e.id = to_hash(nid); + if (failed != 0) e.timeout_count = static_cast(failed); + return e; +} +} + +#ifndef TORRENT_DISABLE_LOGGING +#define LOGGER , nullptr +#else +#define LOGGER +#endif +TORRENT_TEST(replace_node_impl) +{ + // replace specific prefix "slot" + { + ip_set p; + dht::bucket_t b = { + n(&p, "1fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "3fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "5fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "7fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "9fffffffffffffffffffffffffffffffffffffff", true, 50), // <== replaced + n(&p, "bfffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "dfffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "ffffffffffffffffffffffffffffffffffffffff", true, 50), + }; + TEST_EQUAL(p.size(), 8); + TEST_CHECK( + replace_node_impl(n(nullptr, "9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd") + , b, p, 0, 8, true LOGGER) == routing_table::node_added); + TEST_CHECK(b[4].id == to_hash("9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd")); + TEST_EQUAL(p.size(), 8); + } + + // only try to replace specific prefix "slot", and if we fail (RTT is + // higher), don't replace anything else + { + ip_set p; + dht::bucket_t b = { + n(&p, "1fffffffffffffffffffffffffffffffffffffff", true, 500), + n(&p, "3fffffffffffffffffffffffffffffffffffffff", true, 500), + n(&p, "5fffffffffffffffffffffffffffffffffffffff", true, 500), + n(&p, "7fffffffffffffffffffffffffffffffffffffff", true, 500), + n(&p, "9fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "bfffffffffffffffffffffffffffffffffffffff", true, 500), + n(&p, "dfffffffffffffffffffffffffffffffffffffff", true, 500), + n(&p, "ffffffffffffffffffffffffffffffffffffffff", true, 500), + }; + TEST_EQUAL(p.size(), 8); + TEST_CHECK( + replace_node_impl(n(nullptr, "9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", true, 100) + , b, p, 0, 8, true LOGGER) != routing_table::node_added); + TEST_CHECK(b[4].id == to_hash("9fffffffffffffffffffffffffffffffffffffff")); + TEST_EQUAL(p.size(), 8); + } + + // if there are multiple candidates to replace, pick the one with the highest + // RTT. We're picking the prefix slots with duplicates + { + ip_set p; + dht::bucket_t b = { + n(&p, "1fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "3fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "5fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "7fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "bfffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "bfffffffffffffffffffffffffffffffffffffff", true, 51), // <== replaced + n(&p, "dfffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "ffffffffffffffffffffffffffffffffffffffff", true, 50), + }; + TEST_EQUAL(p.size(), 8); + TEST_CHECK( + replace_node_impl(n(nullptr, "9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", true, 50) + , b, p, 0, 8, true LOGGER) == routing_table::node_added); + TEST_CHECK(b[5].id == to_hash("9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd")); + TEST_EQUAL(p.size(), 8); + } + + // if there is a node with fail count > 0, replaec that, regardless of + // anything else + { + ip_set p; + dht::bucket_t b = { + n(&p, "1fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "3fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "5fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "7fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "9fffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "bfffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "dfffffffffffffffffffffffffffffffffffffff", true, 50), + n(&p, "ffffffffffffffffffffffffffffffffffffffff", true, 50, 1), // <== replaced + }; + TEST_EQUAL(p.size(), 8); + TEST_CHECK( + replace_node_impl(n(nullptr, "9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", true, 50) + , b, p, 0, 8, true LOGGER) == routing_table::node_added); + TEST_CHECK(b[7].id == to_hash("9fcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd")); + TEST_EQUAL(p.size(), 8); + } +} + +TORRENT_TEST(all_in_same_bucket) +{ + TEST_CHECK(all_in_same_bucket({}, to_hash("8000000000000000000000000000000000000000"), 0) == true); + TEST_CHECK(all_in_same_bucket({}, to_hash("8000000000000000000000000000000000000001"), 1) == true); + TEST_CHECK(all_in_same_bucket({}, to_hash("8000000000000000000000000000000000000002"), 2) == true); + TEST_CHECK(all_in_same_bucket({}, to_hash("8000000000000000000000000000000000000003"), 3) == true); + { + dht::bucket_t b = { + n(nullptr, "0000000000000000000000000000000000000000"), + }; + TEST_CHECK(all_in_same_bucket(b, to_hash("8000000000000000000000000000000000000000"), 0) == false); + } + + { + dht::bucket_t b = { + n(nullptr, "8000000000000000000000000000000000000000"), + n(nullptr, "f000000000000000000000000000000000000000"), + }; + TEST_CHECK(all_in_same_bucket(b, to_hash("8000000000000000000000000000000000000000"), 0) == true); + } + { + dht::bucket_t b = { + n(nullptr, "8000000000000000000000000000000000000000"), + n(nullptr, "0000000000000000000000000000000000000000"), + }; + TEST_CHECK(all_in_same_bucket(b, to_hash("8000000000000000000000000000000000000000"), 0) == false); + } + { + dht::bucket_t b = { + n(nullptr, "0800000000000000000000000000000000000000"), + n(nullptr, "0000000000000000000000000000000000000000"), + }; + TEST_CHECK(all_in_same_bucket(b, to_hash("0800000000000000000000000000000000000000"), 4) == false); + } + { + dht::bucket_t b = { + n(nullptr, "0800000000000000000000000000000000000000"), + n(nullptr, "0800000000000000000000000000000000000000"), + }; + + TEST_CHECK(all_in_same_bucket(b, to_hash("0800000000000000000000000000000000000000"), 4) == true); + } + { + dht::bucket_t b = { + n(nullptr, "0007000000000000000000000000000000000000"), + n(nullptr, "0004000000000000000000000000000000000000"), + }; + + TEST_CHECK(all_in_same_bucket(b, to_hash("0005000000000000000000000000000000000000"), 13) == true); + } +} + +// TODO: test obfuscated_get_peers + +#else +TORRENT_TEST(dht) +{ + // dummy dht test + TEST_CHECK(true); +} + +#endif diff --git a/test/test_dht_storage.cpp b/test/test_dht_storage.cpp new file mode 100644 index 0000000..c6eac46 --- /dev/null +++ b/test/test_dht_storage.cpp @@ -0,0 +1,508 @@ +/* + +Copyright (c) 2015, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/kademlia/dht_settings.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/socket_io.hpp" // for hash_address +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6 +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" +#include "libtorrent/ed25519.hpp" +#include "libtorrent/hex.hpp" // from_hex + +#include "libtorrent/kademlia/dht_storage.hpp" +#include "libtorrent/kademlia/node_id.hpp" +#include "libtorrent/kademlia/routing_table.hpp" +#include "libtorrent/kademlia/item.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" + +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +#ifndef TORRENT_DISABLE_DHT + +using namespace lt; +using namespace lt::dht; + +namespace +{ + dht::dht_settings test_settings() { + dht::dht_settings sett; + sett.max_torrents = 2; + sett.max_dht_items = 2; + sett.item_lifetime = int(seconds(120 * 60).count()); + return sett; + } + + bool g_storage_constructor_invoked = false; + + std::unique_ptr dht_custom_storage_constructor( + dht::dht_settings const& settings) + { + g_storage_constructor_invoked = true; + return dht_default_storage_constructor(settings); + } + + std::unique_ptr create_default_dht_storage( + dht::dht_settings const& sett) + { + std::unique_ptr s(dht_default_storage_constructor(sett)); + TEST_CHECK(s != nullptr); + + s->update_node_ids({to_hash("0000000000000000000000000000000000000200")}); + + return s; + } +} + +sha1_hash const n1 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401"); +sha1_hash const n2 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee402"); +sha1_hash const n3 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee403"); +sha1_hash const n4 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee404"); + +TORRENT_TEST(announce_peer) +{ + dht::dht_settings sett = test_settings(); + std::unique_ptr s(create_default_dht_storage(sett)); + + entry peers; + s->get_peers(n1, false, false, address(), peers); + + TEST_CHECK(peers["n"].string().empty()); + TEST_CHECK(peers["values"].list().empty()); + + tcp::endpoint const p1 = ep("124.31.75.21", 1); + tcp::endpoint const p2 = ep("124.31.75.22", 1); + tcp::endpoint const p3 = ep("124.31.75.23", 1); + tcp::endpoint const p4 = ep("124.31.75.24", 1); + + s->announce_peer(n1, p1, "torrent_name", false); + peers = entry(); + s->get_peers(n1, false, false, address(), peers); + TEST_EQUAL(peers["n"].string(), "torrent_name"); + TEST_EQUAL(peers["values"].list().size(), 1); + + s->announce_peer(n2, p2, "torrent_name1", false); + s->announce_peer(n2, p3, "torrent_name1", false); + s->announce_peer(n3, p4, "torrent_name2", false); + peers = entry(); + s->get_peers(n3, false, false, address(), peers); + TEST_CHECK(!peers.find_key("values")); +} + +TORRENT_TEST(dual_stack) +{ + dht::dht_settings sett = test_settings(); + std::unique_ptr s(create_default_dht_storage(sett)); + + tcp::endpoint const p1 = ep("124.31.75.21", 1); + tcp::endpoint const p2 = ep("124.31.75.22", 1); + tcp::endpoint const p3 = ep("124.31.75.23", 1); + tcp::endpoint const p4 = ep("2000::1", 1); + tcp::endpoint const p5 = ep("2000::2", 1); + + s->announce_peer(n1, p1, "torrent_name", false); + s->announce_peer(n1, p2, "torrent_name", false); + s->announce_peer(n1, p3, "torrent_name", false); + s->announce_peer(n1, p4, "torrent_name", false); + s->announce_peer(n1, p5, "torrent_name", false); + + entry peers4; + s->get_peers(n1, false, false, address(), peers4); + TEST_EQUAL(peers4["values"].list().size(), 3); + + entry peers6; + s->get_peers(n1, false, false, address_v6(), peers6); + TEST_EQUAL(peers6["values"].list().size(), 2); +} + +TORRENT_TEST(put_items) +{ + dht::dht_settings sett = test_settings(); + std::unique_ptr s(create_default_dht_storage(sett)); + + entry item; + bool r = s->get_immutable_item(n4, item); + TEST_CHECK(!r); + + s->put_immutable_item(n4, {"123", 3}, addr("124.31.75.21")); + r = s->get_immutable_item(n4, item); + TEST_CHECK(r); + + s->put_immutable_item(n1, {"123", 3}, addr("124.31.75.21")); + s->put_immutable_item(n2, {"123", 3}, addr("124.31.75.21")); + s->put_immutable_item(n3, {"123", 3}, addr("124.31.75.21")); + r = s->get_immutable_item(n1, item); + TEST_CHECK(!r); + + r = s->get_mutable_item(n4, sequence_number(0), false, item); + TEST_CHECK(!r); + + public_key pk; + signature sig; + s->put_mutable_item(n4, {"123", 3}, sig, sequence_number(1), pk + , {"salt", 4}, addr("124.31.75.21")); + r = s->get_mutable_item(n4, sequence_number(0), false, item); + TEST_CHECK(r); +} + +TORRENT_TEST(counters) +{ + dht::dht_settings sett = test_settings(); + std::unique_ptr s(create_default_dht_storage(sett)); + + TEST_EQUAL(s->counters().peers, 0); + TEST_EQUAL(s->counters().torrents, 0); + + tcp::endpoint const p1 = ep("124.31.75.21", 1); + tcp::endpoint const p2 = ep("124.31.75.22", 1); + tcp::endpoint const p3 = ep("124.31.75.23", 1); + tcp::endpoint const p4 = ep("124.31.75.24", 1); + + s->announce_peer(n1, p1, "torrent_name", false); + TEST_EQUAL(s->counters().peers, 1); + TEST_EQUAL(s->counters().torrents, 1); + + s->announce_peer(n2, p2, "torrent_name1", false); + s->announce_peer(n2, p3, "torrent_name1", false); + s->announce_peer(n3, p4, "torrent_name2", false); + TEST_EQUAL(s->counters().peers, 3); + TEST_EQUAL(s->counters().torrents, 2); + + entry item; + + s->put_immutable_item(n4, {"123", 3}, addr("124.31.75.21")); + TEST_EQUAL(s->counters().immutable_data, 1); + + s->put_immutable_item(n1, {"123", 3}, addr("124.31.75.21")); + s->put_immutable_item(n2, {"123", 3}, addr("124.31.75.21")); + s->put_immutable_item(n3, {"123", 3}, addr("124.31.75.21")); + TEST_EQUAL(s->counters().immutable_data, 2); + + public_key pk; + signature sig; + s->put_mutable_item(n4, {"123", 3}, sig, sequence_number(1), pk + , {"salt", 4}, addr("124.31.75.21")); + TEST_EQUAL(s->counters().mutable_data, 1); +} + +TORRENT_TEST(set_custom) +{ + g_storage_constructor_invoked = false; + settings_pack p; + p.set_bool(settings_pack::enable_dht, false); + p.set_str(settings_pack::dht_bootstrap_nodes, ""); + lt::session ses(p); + + TEST_EQUAL(g_storage_constructor_invoked, false); + TEST_CHECK(ses.is_dht_running() == false); + + ses.set_dht_storage(dht_custom_storage_constructor); + + p.set_bool(settings_pack::enable_dht, true); + p.set_str(settings_pack::dht_bootstrap_nodes, ""); + ses.apply_settings(p); // async with dispatch + TEST_CHECK(ses.is_dht_running()); + TEST_EQUAL(g_storage_constructor_invoked, true); +} + +TORRENT_TEST(default_set_custom) +{ + g_storage_constructor_invoked = false; + settings_pack p; + p.set_bool(settings_pack::enable_dht, true); + p.set_str(settings_pack::dht_bootstrap_nodes, ""); + lt::session ses(p); + + TEST_CHECK(ses.is_dht_running()); + + ses.set_dht_storage(dht_custom_storage_constructor); + + p.set_bool(settings_pack::enable_dht, false); + ses.apply_settings(p); // async with dispatch + TEST_CHECK(ses.is_dht_running() == false); + TEST_EQUAL(g_storage_constructor_invoked, false); + + ses.set_dht_storage(dht_custom_storage_constructor); + + p.set_bool(settings_pack::enable_dht, true); + ses.apply_settings(p); // async with dispatch + TEST_CHECK(ses.is_dht_running()); + TEST_EQUAL(g_storage_constructor_invoked, true); +} + +TORRENT_TEST(peer_limit) +{ + dht::dht_settings sett = test_settings(); + sett.max_peers = 42; + std::unique_ptr s(create_default_dht_storage(sett)); + + for (int i = 0; i < 200; ++i) + { + s->announce_peer(n1, {rand_v4(), std::uint16_t(lt::random(0xffff))} + , "torrent_name", false); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.peers <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.peers, 42); +} + +TORRENT_TEST(torrent_limit) +{ + dht::dht_settings sett = test_settings(); + sett.max_torrents = 42; + std::unique_ptr s(create_default_dht_storage(sett)); + + for (int i = 0; i < 200; ++i) + { + s->announce_peer(rand_hash(), {rand_v4(), std::uint16_t(lt::random(0xffff))} + , "", false); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.torrents <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.torrents, 42); +} + +TORRENT_TEST(immutable_item_limit) +{ + dht::dht_settings sett = test_settings(); + sett.max_dht_items = 42; + std::unique_ptr s(create_default_dht_storage(sett)); + + for (int i = 0; i < 200; ++i) + { + s->put_immutable_item(rand_hash(), {"123", 3}, rand_v4()); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.immutable_data <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.immutable_data, 42); +} + +TORRENT_TEST(mutable_item_limit) +{ + dht::dht_settings sett = test_settings(); + sett.max_dht_items = 42; + std::unique_ptr s(create_default_dht_storage(sett)); + + public_key pk; + signature sig; + for (int i = 0; i < 200; ++i) + { + s->put_mutable_item(rand_hash(), {"123", 3}, sig, sequence_number(1) + , pk, {"salt", 4}, rand_v4()); + dht_storage_counters cnt = s->counters(); + TEST_CHECK(cnt.mutable_data <= 42); + } + dht_storage_counters cnt = s->counters(); + TEST_EQUAL(cnt.mutable_data, 42); +} + +TORRENT_TEST(get_peers_dist) +{ + // test that get_peers returns reasonably disjoint sets of peers with each call + // take two samples of 100 peers from 1000 and make sure there aren't too many + // peers found in both lists + dht::dht_settings sett = test_settings(); + sett.max_peers = 2000; + sett.max_peers_reply = 100; + std::unique_ptr s(create_default_dht_storage(sett)); + + address addr = rand_v4(); + for (int i = 0; i < 1000; ++i) + { + s->announce_peer(n1, tcp::endpoint(addr, uint16_t(i)) + , "torrent_name", false); + } + + std::set peer_set; + int duplicates = 0; + for (int i = 0; i < 2; ++i) + { + entry peers; + s->get_peers(n1, false, false, address(), peers); + TEST_EQUAL(peers["values"].list().size(), 100); + for (auto const& p : peers["values"].list()) + { + int port = detail::read_v4_endpoint(p.string().begin()).port(); + if (!peer_set.insert(port).second) + ++duplicates; + } + } + std::printf("duplicate peers found: %d\n", duplicates); + TEST_CHECK(duplicates < 20); + + // add 1000 seeds to the mix and make sure we still pick the desired number + // of peers if we select only non-seeds + for (int i = 1000; i < 2000; ++i) + { + s->announce_peer(n1, tcp::endpoint(addr, uint16_t(i)) + , "torrent_name", true); + } + + { + entry peers; + s->get_peers(n1, true, false, address(), peers); + TEST_EQUAL(peers["values"].list().size(), 100); + } +} + +TORRENT_TEST(update_node_ids) +{ + dht::dht_settings sett = test_settings(); + std::unique_ptr s(dht_default_storage_constructor(sett)); + TEST_CHECK(s != nullptr); + + node_id const nid1 = to_hash("0000000000000000000000000000000000000200"); + node_id const nid2 = to_hash("0000000000000000000000000000000000000400"); + node_id const nid3 = to_hash("0000000000000000000000000000000000000800"); + + std::vector node_ids; + node_ids.push_back(nid1); + node_ids.push_back(nid2); + node_ids.push_back(nid3); + s->update_node_ids(node_ids); + + entry item; + dht_storage_counters cnt; + bool r; + + sha1_hash const h1 = to_hash("0000000000000000000000000000000000010200"); + sha1_hash const h2 = to_hash("0000000000000000000000000000000100000400"); + sha1_hash const h3 = to_hash("0000000000000000000000010000000000000800"); + + TEST_EQUAL(min_distance_exp(h1, node_ids), 16); + TEST_EQUAL(min_distance_exp(h2, node_ids), 32); + TEST_EQUAL(min_distance_exp(h3, node_ids), 64); + + // all items will have one announcer, all calculations + // for item erase will be reduced to the distance + s->put_immutable_item(h1, {"123", 3}, addr("124.31.75.21")); + cnt = s->counters(); + TEST_EQUAL(cnt.immutable_data, 1); + s->put_immutable_item(h2, {"123", 3}, addr("124.31.75.21")); + cnt = s->counters(); + TEST_EQUAL(cnt.immutable_data, 2); + // at this point, the least important (h2) will removed + // to make room for h3 + s->put_immutable_item(h3, {"123", 3}, addr("124.31.75.21")); + cnt = s->counters(); + TEST_EQUAL(cnt.immutable_data, 2); + + r = s->get_immutable_item(h1, item); + TEST_CHECK(r); + r = s->get_immutable_item(h2, item); + TEST_CHECK(!r); + r = s->get_immutable_item(h3, item); + TEST_CHECK(r); +} + +TORRENT_TEST(infohashes_sample) +{ + dht::dht_settings sett = test_settings(); + sett.max_torrents = 5; + sett.sample_infohashes_interval = 10; + sett.max_infohashes_sample_count = 2; + std::unique_ptr s(create_default_dht_storage(sett)); + + tcp::endpoint const p1 = ep("124.31.75.21", 1); + tcp::endpoint const p2 = ep("124.31.75.22", 1); + tcp::endpoint const p3 = ep("124.31.75.23", 1); + tcp::endpoint const p4 = ep("124.31.75.24", 1); + + s->announce_peer(n1, p1, "torrent_name1", false); + s->announce_peer(n2, p2, "torrent_name2", false); + s->announce_peer(n3, p3, "torrent_name3", false); + s->announce_peer(n4, p4, "torrent_name4", false); + + entry item; + int r = s->get_infohashes_sample(item); + TEST_EQUAL(r, 2); + TEST_EQUAL(item["interval"].integer(), 10); + TEST_EQUAL(item["num"].integer(), 4); + TEST_EQUAL(item["samples"].string().size(), 2 * 20); + + // get all of them + sett.max_infohashes_sample_count = 5; + + item = entry(); + r = s->get_infohashes_sample(item); + TEST_EQUAL(r, 4); + TEST_EQUAL(item["interval"].integer(), 10); + TEST_EQUAL(item["num"].integer(), 4); + TEST_EQUAL(item["samples"].string().size(), 4 * 20); + + std::string const samples = item["samples"].to_string(); + TEST_CHECK(samples.find(aux::to_hex(n1)) != std::string::npos); + TEST_CHECK(samples.find(aux::to_hex(n2)) != std::string::npos); + TEST_CHECK(samples.find(aux::to_hex(n3)) != std::string::npos); + TEST_CHECK(samples.find(aux::to_hex(n4)) != std::string::npos); +} + +TORRENT_TEST(infohashes_sample_dist) +{ + dht::dht_settings sett = test_settings(); + sett.max_torrents = 1000; + sett.sample_infohashes_interval = 0; // need this to force refresh every call + sett.max_infohashes_sample_count = 1; + std::unique_ptr s(create_default_dht_storage(sett)); + + for (int i = 0; i < 1000; ++i) + { + s->announce_peer(rand_hash(), tcp::endpoint(rand_v4(), std::uint16_t(i)) + , "torrent_name", false); + } + + std::set infohash_set; + for (int i = 0; i < 1000; ++i) + { + entry item; + int r = s->get_infohashes_sample(item); + TEST_EQUAL(r, 1); + TEST_EQUAL(item["interval"].integer(), 0); + TEST_EQUAL(item["num"].integer(), 1000); + TEST_EQUAL(item["samples"].string().size(), 20); + + infohash_set.insert(sha1_hash(item["samples"].string())); + } + std::printf("infohashes set size: %d\n", int(infohash_set.size())); + TEST_CHECK(infohash_set.size() > 500); +} +#else +TORRENT_TEST(dummy) {} +#endif diff --git a/test/test_direct_dht.cpp b/test/test_direct_dht.cpp new file mode 100644 index 0000000..53a72c3 --- /dev/null +++ b/test/test_direct_dht.cpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2015, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#if !defined TORRENT_DISABLE_EXTENSIONS && !defined TORRENT_DISABLE_DHT + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bdecode.hpp" + +using namespace lt; + +namespace +{ + +struct test_plugin : plugin +{ + feature_flags_t implemented_features() override + { + return plugin::dht_request_feature; + } + + bool on_dht_request(string_view /* query */ + , udp::endpoint const& /* source */, bdecode_node const& message + , entry& response) override + { + if (message.dict_find_string_value("q") == "test_good") + { + response["r"]["good"] = 1; + return true; + } + return false; + } +}; + +dht_direct_response_alert* get_direct_response(lt::session& ses) +{ + for (;;) + { + alert* a = ses.wait_for_alert(seconds(30)); + // it shouldn't take more than 30 seconds to get a response + // so fail the test and bail out if we don't get an alert in that time + TEST_CHECK(a); + if (!a) return nullptr; + std::vector alerts; + ses.pop_alerts(&alerts); + for (std::vector::iterator i = alerts.begin(); i != alerts.end(); ++i) + { + if ((*i)->type() == dht_direct_response_alert::alert_type) + return static_cast(&**i); + } + } +} + +} + +#endif // #if !defined TORRENT_DISABLE_EXTENSIONS && !defined TORRENT_DISABLE_DHT + +TORRENT_TEST(direct_dht_request) +{ +#if !defined TORRENT_DISABLE_EXTENSIONS && !defined TORRENT_DISABLE_DHT + + std::vector abort; + settings_pack sp; + sp.set_bool(settings_pack::enable_lsd, false); + sp.set_bool(settings_pack::enable_natpmp, false); + sp.set_bool(settings_pack::enable_upnp, false); + sp.set_str(settings_pack::dht_bootstrap_nodes, ""); + sp.set_int(settings_pack::max_retry_port_bind, 800); + sp.set_str(settings_pack::listen_interfaces, "127.0.0.1:42434"); + lt::session responder(sp, {}); + sp.set_str(settings_pack::listen_interfaces, "127.0.0.1:45434"); + lt::session requester(sp, {}); + + responder.add_extension(std::make_shared()); + + // successful request + + entry r; + r["q"] = "test_good"; + requester.dht_direct_request(udp::endpoint(address::from_string("127.0.0.1") + , responder.listen_port()), r, reinterpret_cast(12345)); + + dht_direct_response_alert* ra = get_direct_response(requester); + TEST_CHECK(ra); + if (ra) + { + bdecode_node response = ra->response(); + TEST_EQUAL(ra->endpoint.address(), address::from_string("127.0.0.1")); + TEST_EQUAL(ra->endpoint.port(), responder.listen_port()); + TEST_EQUAL(response.type(), bdecode_node::dict_t); + TEST_EQUAL(response.dict_find_dict("r").dict_find_int_value("good"), 1); + TEST_EQUAL(ra->userdata, reinterpret_cast(12345)); + } + + // failed request + + requester.dht_direct_request(udp::endpoint(address::from_string("127.0.0.1"), 53545) + , r, reinterpret_cast(123456)); + + ra = get_direct_response(requester); + TEST_CHECK(ra); + if (ra) + { + TEST_EQUAL(ra->endpoint.address(), address::from_string("127.0.0.1")); + TEST_EQUAL(ra->endpoint.port(), 53545); + TEST_EQUAL(ra->response().type(), bdecode_node::none_t); + TEST_EQUAL(ra->userdata, reinterpret_cast(123456)); + } + + abort.emplace_back(responder.abort()); + abort.emplace_back(requester.abort()); +#endif // #if !defined TORRENT_DISABLE_EXTENSIONS && !defined TORRENT_DISABLE_DHT +} diff --git a/test/test_dos_blocker.cpp b/test/test_dos_blocker.cpp new file mode 100644 index 0000000..93a6683 --- /dev/null +++ b/test/test_dos_blocker.cpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" +#include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include + +using namespace lt; + +#ifndef TORRENT_DISABLE_LOGGING +struct log_t : lt::dht::dht_logger +{ + bool should_log(module_t) const override { return true; } + + void log(dht_logger::module_t, char const* fmt, ...) + override TORRENT_FORMAT(3, 4) + { + va_list v; + va_start(v, fmt); + std::vprintf(fmt, v); + va_end(v); + } + + void log_packet(message_direction_t dir, span pkt + , udp::endpoint const& node) override + { + lt::bdecode_node print; + lt::error_code ec; + int ret = bdecode(pkt.data(), pkt.data() + int(pkt.size()), print, ec, nullptr, 100, 100); + TEST_EQUAL(ret, 0); + + std::string msg = print_entry(print, true); + std::printf("%s", msg.c_str()); + + char const* prefix[2] = { "<==", "==>"}; + std::printf("%s [%s] %s", prefix[dir], print_endpoint(node).c_str() + , msg.c_str()); + } + + virtual ~log_t() = default; +}; +#endif + +TORRENT_TEST(dos_blocker) +{ +#ifndef TORRENT_DISABLE_LOGGING +#ifndef TORRENT_DISABLE_DHT + using namespace lt::dht; + + log_t l; + dos_blocker b; + + address spammer = address_v4::from_string("10.10.10.10"); + + time_point now = clock_type::now(); + for (int i = 0; i < 1000; ++i) + { + b.incoming(spammer, now, &l); + now += milliseconds(1); + TEST_EQUAL(b.incoming(rand_v4(), now, &l), true); + now += milliseconds(1); + } + + now += milliseconds(1); + + TEST_EQUAL(b.incoming(spammer, now, &l), false); +#endif +#endif +} diff --git a/test/test_ed25519.cpp b/test/test_ed25519.cpp new file mode 100644 index 0000000..8e6565a --- /dev/null +++ b/test/test_ed25519.cpp @@ -0,0 +1,289 @@ +/* + +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#ifndef TORRENT_DISABLE_DHT + +#include + +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/hex.hpp" + +using namespace lt; +using namespace lt::dht; + +namespace +{ + void test_vector(std::string seed, std::string pub, std::string sig_hex, std::string message) + { + std::array s; + secret_key sk; + public_key pk; + signature sig; + std::vector msg(message.size() / 2); + + aux::from_hex(seed, s.data()); + std::tie(pk, sk) = ed25519_create_keypair(s); + + TEST_EQUAL(aux::to_hex(pk.bytes), pub); + + aux::from_hex(message, msg.data()); + sig = ed25519_sign(msg, pk, sk); + + TEST_EQUAL(aux::to_hex(sig.bytes), sig_hex); + + bool r = ed25519_verify(sig, msg, pk); + + TEST_CHECK(r); + } +} + +// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=libgcrypt.git;a=blob;f=tests/t-ed25519.inp;hb=HEAD +TORRENT_TEST(ed25519_test_vec1) +{ + // TST: 2 + test_vector( + "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb" + , "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c" + , "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da" + "085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00" + , "72" + ); + + // TST: 3 + test_vector( + "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7" + , "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025" + , "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac" + "18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a" + , "af82" + ); + + // TST: 4 + test_vector( + "0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9" + , "e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057" + , "d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8" + "e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00c" + , "cbc77b" + ); + + // TST: 47 + test_vector( + "89f0d68299ba0a5a83f248ae0c169f8e3849a9b47bd4549884305c9912b46603" + , "aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832" + , "2c691fa8d487ce20d5d2fa41559116e0bbf4397cf5240e152556183541d66cf7" + "53582401a4388d390339dbef4d384743caa346f55f8daba68ba7b9131a8a6e0b" + , "4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c9141" + "1388bc7653e2d893d1eac2107d05" + ); + + // TST: 48 + test_vector( + "0a3c1844e2db070fb24e3c95cb1cc6714ef84e2ccd2b9dd2f1460ebf7ecf13b1" + , "72e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01" + , "87f7fdf46095201e877a588fe3e5aaf476bd63138d8a878b89d6ac60631b3458" + "b9d41a3c61a588e1db8d29a5968981b018776c588780922f5aa732ba6379dd05" + , "4c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722" + "cdead7d22aaead2bfaa1ad00b82957" + ); + + // TST: 49 + test_vector( + "c8d7a8818b98dfdb20839c871cb5c48e9e9470ca3ad35ba2613a5d3199c8ab23" + , "90d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f" + , "fa2e994421aef1d5856674813d05cbd2cf84ef5eb424af6ecd0dc6fdbdc2fe60" + "5fe985883312ecf34f59bfb2f1c9149e5b9cc9ecda05b2731130f3ed28ddae0b" + , "783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89" + "e38cfd3b4d0885661ca547fb9764abff" + ); + + // TST: 50 + test_vector( + "b482703612d0c586f76cfcb21cfd2103c957251504a8c0ac4c86c9c6f3e429ff" + , "fd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad" + , "58832bdeb26feafc31b46277cf3fb5d7a17dfb7ccd9b1f58ecbe6feb97966682" + "8f239ba4d75219260ecac0acf40f0e5e2590f4caa16bbbcd8a155d347967a607" + , "29d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cb" + "d771e184a9a75f316b648c6920db92b87b" + ); + + // TST: 51 + test_vector( + "84e50dd9a0f197e3893c38dbd91fafc344c1776d3a400e2f0f0ee7aa829eb8a2" + , "2c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea" + , "69e6a4491a63837316e86a5f4ba7cd0d731ecc58f1d0a264c67c89befdd8d382" + "9d8de13b33cc0bf513931715c7809657e2bfb960e5c764c971d733746093e500" + , "f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff" + "724ff47d29344391dc536166b8671cbbf123" + ); + + // TST: 52 + test_vector( + "b322d46577a2a991a4d1698287832a39c487ef776b4bff037a05c7f1812bdeec" + , "eb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f" + , "c7b55137317ca21e33489ff6a9bfab97c855dc6f85684a70a9125a261b56d5e6" + "f149c5774d734f2d8debfc77b721896a8267c23768e9badb910eef83ec258802" + , "19f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a3" + "0f7de9e5da4108c52a4ce70a3e114a52a3b3c5" + ); + + // TST: 53 + test_vector( + "960cab5034b9838d098d2dcbf4364bec16d388f6376d73a6273b70f82bbc98c0" + , "5e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a" + , "27d4c3a1811ef9d4360b3bdd133c2ccc30d02c2f248215776cb07ee4177f9b13" + "fc42dd70a6c2fed8f225c7663c7f182e7ee8eccff20dc7b0e1d5834ec5b1ea01" + , "f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d8183" + "57db938eac73e0af6d31206b3948f8c48a447308" + ); + + // TST: 224 + test_vector( + "ae1d2c6b171be24c2e413d364dcda97fa476aaf9123d3366b0be03a142fe6e7d" + , "d437f57542c681dd543487408ec7a44bd42a5fd545ce2f4c8297d67bb0b3aa7b" + , "909008f3fcfff43988aee1314b15b1822caaa8dab120bd452af494e08335b44a" + "94c313c4b145eadd5166eaac034e29b7e6ac7941d5961fc49d260e1c4820b00e" + , "9e6c2fc76e30f17cd8b498845da44f22d55bec150c6130b411c6339d14b39969" + "ab1033be687569a991a06f70b2a8a6931a777b0e4be6723cd75e5aa7532813ef" + "50b3d37271640fa2fb287c0355257641ea935c851c0b6ac68be72c88dfc5856f" + "b53543fb377b0dbf64808afcc4274aa456855ad28f61267a419bc72166b9ca73" + "cd3bb79bf7dd259baa75911440974b68e8ba95a78cbbe1cb6ad807a33a1cce2f" + "406ff7bcbd058b44a311b38ab4d4e61416c4a74d883d6a6a794abd9cf1c03902" + "8bf1b20e3d4990aae86f32bf06cd8349a7a884cce0165e36a0640e987b9d51" + ); + + // TST: 225 + test_vector( + "0265a7944baccfebf417b87ae1e6df2ff2a544ffb58225a08e092be03f026097" + , "63d327615ea0139be0740b618aff1acfa818d4b0c2cfeaf0da93cdd5245fb5a9" + , "b6c445b7eddca5935c61708d44ea5906bd19cc54224eae3c8e46ce99f5cbbd34" + "1f26623938f5fe04070b1b02e71fbb7c78a90c0dda66cb143fab02e6a0bae306" + , "874ed712a2c41c26a2d9527c55233fde0a4ffb86af8e8a1dd0a820502c5a2693" + "2bf87ee0de72a8874ef2eebf83384d443f7a5f46a1233b4fb514a24699818248" + "94f325bf86aa0fe1217153d40f3556c43a8ea9269444e149fb70e9415ae0766c" + "565d93d1d6368f9a23a0ad76f9a09dbf79634aa97178677734d04ef1a5b3f87c" + "e1ee9fc5a9ac4e7a72c9d7d31ec89e28a845d2e1103c15d6410ce3c723b0cc22" + "09f698aa9fa288bbbecfd9e5f89cdcb09d3c215feb47a58b71ea70e2abead67f" + "1b08ea6f561fb93ef05232eedabfc1c7702ab039bc465cf57e207f1093fc8208" + ); +} + +TORRENT_TEST(create_seed) +{ + std::array s1 = ed25519_create_seed(); + std::array s2 = ed25519_create_seed(); + + TEST_CHECK(s1 != s2); // what are the odds + + int n1 = 0; + int n2 = 0; + for (std::size_t i = 0; i < 32; i++) + { + if (s1[i] != 0) n1++; + if (s2[i] != 0) n2++; + } + TEST_CHECK(n1 > 0); + TEST_CHECK(n2 > 0); +} + +TORRENT_TEST(add_scalar) +{ + // client + std::array s1 = ed25519_create_seed(); + + public_key pk1; + secret_key sk1; + std::tie(pk1, sk1) = ed25519_create_keypair(s1); + + // client sends to server the public key + + // server generates another seed, it could be an scalar + // n = HMAC(k, pk1) and sends it back to the client + // see http://crypto.stackexchange.com/a/6215/4697 + std::array n = ed25519_create_seed(); + + // now the server knows that the client's public key + // must be (or assigns) pk1 + n + pk1 = ed25519_add_scalar(pk1, n); + + // server sends n to the client + + // the client, in order to properly sign messages, must + // adjust the private key + sk1 = ed25519_add_scalar(sk1, n); + + // test sign and verification + std::string msg = "Hello world"; + signature sig = ed25519_sign(msg, pk1, sk1); + bool r = ed25519_verify(sig, msg, pk1); + TEST_CHECK(r); +} + +TORRENT_TEST(key_exchange) +{ + // user A + std::array s1 = ed25519_create_seed(); + + public_key pk1; + secret_key sk1; + std::tie(pk1, sk1) = ed25519_create_keypair(s1); + + // user A sends to user B the public key + + // user B + std::array s2 = ed25519_create_seed(); + + public_key pk2; + secret_key sk2; + std::tie(pk2, sk2) = ed25519_create_keypair(s2); + + // user B performs the key exchange + std::array secretB = ed25519_key_exchange(pk1, sk2); + + // user B sends to user A the public key + + // user A performs the key exchange + std::array secretA = ed25519_key_exchange(pk2, sk1); + + // now both users A and B must shared the same secret + TEST_EQUAL(aux::to_hex(secretA), aux::to_hex(secretB)); +} + +#else +TORRENT_TEST(empty) +{ + TEST_CHECK(true); +} +#endif // TORRENT_DISABLE_DHT + diff --git a/test/test_enum_net.cpp b/test/test_enum_net.cpp new file mode 100644 index 0000000..9dae1ec --- /dev/null +++ b/test/test_enum_net.cpp @@ -0,0 +1,301 @@ +/* + +Copyright (c) 2008-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/error_code.hpp" +#include + +using namespace lt; +using boost::none; + +TORRENT_TEST(is_local) +{ + error_code ec; + TEST_CHECK(is_local(address::from_string("192.168.0.1", ec))); + TEST_CHECK(!ec); + TEST_CHECK(is_local(address::from_string("10.1.1.56", ec))); + TEST_CHECK(!ec); + TEST_CHECK(!is_local(address::from_string("14.14.251.63", ec))); + TEST_CHECK(!ec); +} + +TORRENT_TEST(is_loopback) +{ + error_code ec; + TEST_CHECK(is_loopback(address::from_string("127.0.0.1", ec))); + TEST_CHECK(!ec); + if (supports_ipv6()) + { + TEST_CHECK(is_loopback(address::from_string("::1", ec))); + TEST_CHECK(!ec); + } +} + +TORRENT_TEST(is_any) +{ + TEST_CHECK(is_any(address_v4::any())); + error_code ec; + TEST_CHECK(!is_any(address::from_string("31.53.21.64", ec))); + TEST_CHECK(!ec); + if (supports_ipv6()) + { + TEST_CHECK(is_any(address_v6::any())); + TEST_CHECK(!ec); + } +} + +TORRENT_TEST(match_addr_mask) +{ + TEST_CHECK(match_addr_mask( + address::from_string("10.0.1.176"), + address::from_string("10.0.1.176"), + address::from_string("255.255.255.0"))); + + TEST_CHECK(match_addr_mask( + address::from_string("10.0.1.3"), + address::from_string("10.0.3.3"), + address::from_string("255.255.0.0"))); + + TEST_CHECK(!match_addr_mask( + address::from_string("10.0.1.3"), + address::from_string("10.1.3.3"), + address::from_string("255.255.0.0"))); + + TEST_CHECK(match_addr_mask( + address::from_string("ff00:1234::"), + address::from_string("ff00:5678::"), + address::from_string("ffff::"))); + + TEST_CHECK(!match_addr_mask( + address::from_string("ff00:1234::"), + address::from_string("ff00:5678::"), + address::from_string("ffff:f000::"))); + + // different scope IDs always means a mismatch + TEST_CHECK(!match_addr_mask( + address::from_string("ff00:1234::%1"), + address::from_string("ff00:1234::%2"), + address::from_string("ffff::"))); +} + +TORRENT_TEST(is_ip_address) +{ + TEST_EQUAL(is_ip_address("1.2.3.4"), true); + TEST_EQUAL(is_ip_address("a.b.c.d"), false); + TEST_EQUAL(is_ip_address("a:b:b:c"), false); + TEST_EQUAL(is_ip_address("::1"), true); + TEST_EQUAL(is_ip_address("2001:db8:85a3:0:0:8a2e:370:7334"), true); +} + +TORRENT_TEST(build_netmask_v4) +{ + TEST_CHECK(build_netmask(0, AF_INET) == make_address("0.0.0.0")); + TEST_CHECK(build_netmask(1, AF_INET) == make_address("128.0.0.0")); + TEST_CHECK(build_netmask(2, AF_INET) == make_address("192.0.0.0")); + TEST_CHECK(build_netmask(3, AF_INET) == make_address("224.0.0.0")); + TEST_CHECK(build_netmask(4, AF_INET) == make_address("240.0.0.0")); + TEST_CHECK(build_netmask(5, AF_INET) == make_address("248.0.0.0")); + TEST_CHECK(build_netmask(6, AF_INET) == make_address("252.0.0.0")); + TEST_CHECK(build_netmask(7, AF_INET) == make_address("254.0.0.0")); + TEST_CHECK(build_netmask(8, AF_INET) == make_address("255.0.0.0")); + TEST_CHECK(build_netmask(9, AF_INET) == make_address("255.128.0.0")); + TEST_CHECK(build_netmask(10, AF_INET) == make_address("255.192.0.0")); + TEST_CHECK(build_netmask(11, AF_INET) == make_address("255.224.0.0")); + + TEST_CHECK(build_netmask(22, AF_INET) == make_address("255.255.252.0")); + TEST_CHECK(build_netmask(23, AF_INET) == make_address("255.255.254.0")); + TEST_CHECK(build_netmask(24, AF_INET) == make_address("255.255.255.0")); + TEST_CHECK(build_netmask(25, AF_INET) == make_address("255.255.255.128")); + TEST_CHECK(build_netmask(26, AF_INET) == make_address("255.255.255.192")); + TEST_CHECK(build_netmask(27, AF_INET) == make_address("255.255.255.224")); + TEST_CHECK(build_netmask(28, AF_INET) == make_address("255.255.255.240")); + TEST_CHECK(build_netmask(29, AF_INET) == make_address("255.255.255.248")); + TEST_CHECK(build_netmask(30, AF_INET) == make_address("255.255.255.252")); + TEST_CHECK(build_netmask(31, AF_INET) == make_address("255.255.255.254")); + TEST_CHECK(build_netmask(32, AF_INET) == make_address("255.255.255.255")); +} + +TORRENT_TEST(build_netmask_v6) +{ + TEST_CHECK(build_netmask(0, AF_INET6) == make_address("::")); + TEST_CHECK(build_netmask(1, AF_INET6) == make_address("8000::")); + TEST_CHECK(build_netmask(2, AF_INET6) == make_address("c000::")); + TEST_CHECK(build_netmask(3, AF_INET6) == make_address("e000::")); + TEST_CHECK(build_netmask(4, AF_INET6) == make_address("f000::")); + TEST_CHECK(build_netmask(5, AF_INET6) == make_address("f800::")); + TEST_CHECK(build_netmask(6, AF_INET6) == make_address("fc00::")); + TEST_CHECK(build_netmask(7, AF_INET6) == make_address("fe00::")); + TEST_CHECK(build_netmask(8, AF_INET6) == make_address("ff00::")); + TEST_CHECK(build_netmask(9, AF_INET6) == make_address("ff80::")); + TEST_CHECK(build_netmask(10, AF_INET6) == make_address("ffc0::")); + TEST_CHECK(build_netmask(11, AF_INET6) == make_address("ffe0::")); + + TEST_CHECK(build_netmask(119, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00")); + TEST_CHECK(build_netmask(120, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00")); + TEST_CHECK(build_netmask(121, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80")); + TEST_CHECK(build_netmask(122, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0")); + TEST_CHECK(build_netmask(123, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0")); + TEST_CHECK(build_netmask(124, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0")); + TEST_CHECK(build_netmask(125, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8")); + TEST_CHECK(build_netmask(126, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc")); + TEST_CHECK(build_netmask(127, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe")); + TEST_CHECK(build_netmask(128, AF_INET6) == make_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); +} + +TORRENT_TEST(build_netmask_unknown) +{ + TEST_CHECK(build_netmask(0, -1) == address{}); +} + +namespace { + ip_route rt(char const* ip, char const* device, char const* gateway, char const* mask) + { + ip_route ret; + ret.destination = address::from_string(ip); + ret.gateway = address::from_string(gateway); + ret.netmask = address::from_string(mask); + std::strncpy(ret.name, device, sizeof(ret.name) - 1); + ret.name[sizeof(ret.name) - 1] = '\0'; + return ret; + } + + ip_interface ip(char const* addr, char const* name) + { + ip_interface ret; + ret.interface_address = address::from_string(addr); + ret.netmask = address::from_string("255.255.255.255"); + std::strncpy(ret.name, name, sizeof(ret.name) - 1); + ret.name[sizeof(ret.name) - 1] = '\0'; + return ret; + } +} + +TORRENT_TEST(get_gateway_basic) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1", "255.255.255.0"), + rt("::", "eth0", "2a02::1234", "ffff::") + }; + + TEST_CHECK(get_gateway(ip("192.168.0.130", "eth0"), routes) == address::from_string("192.168.0.1")); + TEST_CHECK(get_gateway(ip("2a02::4567", "eth0"), routes) == address::from_string("2a02::1234")); + + // the device name does not match the route + TEST_CHECK(get_gateway(ip("192.168.0.130", "eth1"), routes) == none); + TEST_CHECK(get_gateway(ip("2a02::4567", "eth1"), routes) == none); + + // for IPv6, the address family and device name matches, so it's a match + TEST_CHECK(get_gateway(ip("2a02:8000::0123:4567", "eth0"), routes) == address::from_string("2a02::1234")); +} + +TORRENT_TEST(get_gateway_no_default_route) +{ + std::vector const routes = { + rt("192.168.0.0", "eth0", "0.0.0.0", "0.0.0.0"), + rt("2a02::", "eth0", "::", "ffff::") + }; + + // no default route + TEST_CHECK(get_gateway(ip("192.168.1.130", "eth0"), routes) == none); + TEST_CHECK(get_gateway(ip("2a02::1234", "eth0"), routes) == none); +} + +TORRENT_TEST(get_gateway_local_v6) +{ + std::vector const routes = { + rt("2a02::", "eth0", "::", "ffff::") + }; + + // local IPv6 addresses never have a gateway + TEST_CHECK(get_gateway(ip("fe80::1234", "eth0"), routes) == none); +} + +// an odd, imaginary setup, where the loopback network has a gateway +TORRENT_TEST(get_gateway_loopback) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1", "255.255.0.0"), + rt("0.0.0.0", "lo", "127.1.1.1", "255.0.0.0"), + rt("::", "eth0", "fec0::1234", "ffff::"), + rt("::", "lo", "::2", "ffff:ffff:ffff:ffff::") + }; + + TEST_CHECK(get_gateway(ip("127.0.0.1", "lo"), routes) == address::from_string("127.1.1.1")); + + // with IPv6, there are no gateways for local or loopback addresses + TEST_CHECK(get_gateway(ip("::1", "lo"), routes) == none); +} + +TORRENT_TEST(get_gateway_multi_homed) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1", "255.255.0.0"), + rt("0.0.0.0", "eth1", "10.0.0.1", "255.0.0.0") + }; + + TEST_CHECK(get_gateway(ip("192.168.0.130", "eth0"), routes) == address::from_string("192.168.0.1")); + TEST_CHECK(get_gateway(ip("10.0.1.130", "eth1"), routes) == address::from_string("10.0.0.1")); +} + +TORRENT_TEST(has_internet_route) +{ + std::vector const routes = { + rt("0.0.0.0", "eth0", "192.168.0.1", "255.255.0.0"), + rt("0.0.0.0", "eth1", "0.0.0.0", "255.0.0.0"), + rt("127.0.0.0", "lo", "0.0.0.0", "255.0.0.0"), + rt("0.0.0.0", "tun0", "0.0.0.0", "128.0.0.0"), + rt("128.0.0.0", "tun0", "0.0.0.0", "128.0.0.0"), + rt("1.2.3.4", "tun1", "0.0.0.0", "255.255.0.0"), + rt("2000:5678::1", "tun1", "::", "f::"), + }; + + TEST_CHECK(has_internet_route("eth0", AF_INET, routes)); + TEST_CHECK(!has_internet_route("eth0", AF_INET6, routes)); + + TEST_CHECK(has_internet_route("eth1", AF_INET, routes)); + TEST_CHECK(!has_internet_route("eth1", AF_INET6, routes)); + + TEST_CHECK(!has_internet_route("lo", AF_INET, routes)); + TEST_CHECK(!has_internet_route("lo", AF_INET6, routes)); + + TEST_CHECK(!has_internet_route("eth2", AF_INET, routes)); + TEST_CHECK(!has_internet_route("eth2", AF_INET6, routes)); + + TEST_CHECK(has_internet_route("tun0", AF_INET, routes)); + TEST_CHECK(!has_internet_route("tun0", AF_INET6, routes)); + + TEST_CHECK(has_internet_route("tun1", AF_INET, routes)); + TEST_CHECK(has_internet_route("tun1", AF_INET6, routes)); +} diff --git a/test/test_fast_extension.cpp b/test/test_fast_extension.cpp new file mode 100644 index 0000000..4d196de --- /dev/null +++ b/test/test_fast_extension.cpp @@ -0,0 +1,1129 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "test_utils.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/aux_/alloca.hpp" // for use of private TORRENT_ALLOCA +#include "libtorrent/time.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/path.hpp" + +#include +#include +#include +#include +#include +#include // for vsnprintf + +using namespace lt; +using namespace std::placeholders; + +namespace { + +void log(char const* fmt, ...) +{ + va_list v; + va_start(v, fmt); + + char buf[1024]; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + std::vsnprintf(buf, sizeof(buf), fmt, v); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + va_end(v); + + std::printf("\x1b[1m\x1b[36m%s: %s\x1b[0m\n" + , time_now_string(), buf); +} + +void print_session_log(lt::session& ses) +{ + print_alerts(ses, "ses", true); +} + +int read_message(tcp::socket& s, span buffer) +{ + using namespace lt::detail; + error_code ec; + boost::asio::read(s, boost::asio::buffer(buffer.data(), 4) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return -1; + } + char const* ptr = buffer.data(); + int const length = read_int32(ptr); + if (length > buffer.size()) + { + log("message size: %d", length); + TEST_ERROR("message size exceeds max limit"); + return -1; + } + + boost::asio::read(s, boost::asio::buffer(buffer.data(), std::size_t(length)) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return -1; + } + return length; +} + +void print_message(span buffer) +{ + char const* message_name[] = {"choke", "unchoke", "interested", "not_interested" + , "have", "bitfield", "request", "piece", "cancel", "dht_port", "", "", "" + , "suggest_piece", "have_all", "have_none", "reject_request", "allowed_fast"}; + + std::stringstream message; + char extra[300]; + extra[0] = 0; + if (buffer.empty()) + { + message << "keepalive"; + } + else + { + int const msg = buffer[0]; + if (msg >= 0 && msg < int(sizeof(message_name)/sizeof(message_name[0]))) + message << message_name[msg]; + else if (msg == 20) + message << "extension msg [" << int(buffer[1]) << "]"; + else + message << "unknown[" << msg << "]"; + + if (msg == 0x6 && buffer.size() == 13) + { + peer_request r; + const char* ptr = buffer.data() + 1; + r.piece = piece_index_t(detail::read_int32(ptr)); + r.start = detail::read_int32(ptr); + r.length = detail::read_int32(ptr); + std::snprintf(extra, sizeof(extra), "p: %d s: %d l: %d" + , static_cast(r.piece), r.start, r.length); + } + else if (msg == 0x11 && buffer.size() == 5) + { + const char* ptr = buffer.data() + 1; + int index = detail::read_int32(ptr); + std::snprintf(extra, sizeof(extra), "p: %d", index); + } + else if (msg == 20 && buffer.size() > 4 && buffer[1] == 0 ) + { + std::snprintf(extra, sizeof(extra), "%s" + , print_entry(bdecode(buffer.subspan(2))).c_str()); + } + } + + log("<== %s %s", message.str().c_str(), extra); +} + +void send_allow_fast(tcp::socket& s, int piece) +{ + log("==> allow fast: %d", piece); + using namespace lt::detail; + char msg[] = "\0\0\0\x05\x11\0\0\0\0"; + char* ptr = msg + 5; + write_int32(piece, ptr); + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 9) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_suggest_piece(tcp::socket& s, int piece) +{ + log("==> suggest piece: %d", piece); + using namespace lt::detail; + char msg[] = "\0\0\0\x05\x0d\0\0\0\0"; + char* ptr = msg + 5; + write_int32(piece, ptr); + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 9) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_keepalive(tcp::socket& s) +{ + log("==> keepalive"); + char msg[] = "\0\0\0\0"; + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 4) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_unchoke(tcp::socket& s) +{ + log("==> unchoke"); + char msg[] = "\0\0\0\x01\x01"; + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 5) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_have_all(tcp::socket& s) +{ + log("==> have_all"); + char msg[] = "\0\0\0\x01\x0e"; // have_all + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 5) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_have_none(tcp::socket& s) +{ + log("==> have_none"); + char msg[] = "\0\0\0\x01\x0f"; // have_none + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 5) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_dht_port(tcp::socket& s, int port) +{ + using namespace lt::detail; + + log("==> dht_port"); + char msg[] = "\0\0\0\x03\x09\0\0"; // dht_port + char* ptr = msg + 5; + write_uint16(port, ptr); + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 7) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_bitfield(tcp::socket& s, char const* bits) +{ + using namespace lt::detail; + + int num_pieces = int(strlen(bits)); + int packet_size = (num_pieces+7)/8 + 5; + TORRENT_ALLOCA(msg, char, packet_size); + std::fill(msg.begin(), msg.end(), 0); + char* ptr = msg.data(); + write_int32(packet_size-4, ptr); + write_int8(5, ptr); + log("==> bitfield [%s]", bits); + for (int i = 0; i < num_pieces; ++i) + { + ptr[i/8] |= (bits[i] == '1' ? 1 : 0) << i % 8; + } + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg.data(), std::size_t(msg.size())) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void do_handshake(tcp::socket& s, sha1_hash const& ih, char* buffer) +{ + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\x10\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa"; // peer-id + log("==> handshake"); + error_code ec; + std::memcpy(handshake + 28, ih.begin(), 20); + boost::asio::write(s, boost::asio::buffer(handshake, sizeof(handshake) - 1) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return; + } + + // read handshake + boost::asio::read(s, boost::asio::buffer(buffer, 68) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + return; + } + log("<== handshake"); + + TEST_CHECK(buffer[0] == 19); + TEST_CHECK(std::memcmp(buffer + 1, "BitTorrent protocol", 19) == 0); + + char* extensions = buffer + 20; + // check for fast extension support + TEST_CHECK(extensions[7] & 0x4); + + // check for extension protocol support + bool const lt_extension_protocol = (extensions[5] & 0x10) != 0; + TEST_CHECK(lt_extension_protocol == true); + + // check for DHT support + bool const dht_support = (extensions[7] & 0x1) != 0; +#ifndef TORRENT_DISABLE_DHT + TEST_CHECK(dht_support == true); +#else + TEST_CHECK(dht_support == false); +#endif + + TEST_CHECK(std::memcmp(buffer + 28, ih.begin(), 20) == 0); +} + +void send_extension_handshake(tcp::socket& s, entry const& e) +{ + std::vector buf; + + // reserve space for the message header + // uint32: packet-length + // uint8: 20 (extension message) + // uint8: 0 (handshake) + buf.resize(4 + 1 + 1); + + bencode(std::back_inserter(buf), e); + + using namespace lt::detail; + + char* ptr = &buf[0]; + write_uint32(int(buf.size()) - 4, ptr); + write_uint8(20, ptr); + write_uint8(0, ptr); + + error_code ec; + boost::asio::write(s, boost::asio::buffer(&buf[0], buf.size()) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +void send_request(tcp::socket& s, peer_request req) +{ + using namespace lt::detail; + + log("==> request %d (%d,%d)", static_cast(req.piece), req.start, req.length); + char msg[] = "\0\0\0\x0d\x06 "; // have_none + char* ptr = msg + 5; + write_uint32(static_cast(req.piece), ptr); + write_uint32(req.start, ptr); + write_uint32(req.length, ptr); + error_code ec; + boost::asio::write(s, boost::asio::buffer(msg, 17) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +entry read_extension_handshake(tcp::socket& s, span recv_buffer) +{ + for (;;) + { + int const len = read_message(s, recv_buffer); + if (len == -1) + { + TEST_ERROR("failed to read message"); + return entry(); + } + recv_buffer = recv_buffer.first(len); + print_message(recv_buffer); + + if (len < 4) continue; + int msg = recv_buffer[0]; + if (msg != 20) continue; + int extmsg = recv_buffer[1]; + if (extmsg != 0) continue; + + return bdecode(recv_buffer.subspan(2)); + } +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +void send_ut_metadata_msg(tcp::socket& s, int ut_metadata_msg, int type, int piece) +{ + std::vector buf; + + // reserve space for the message header + // uint32: packet-length + // uint8: 20 (extension message) + // uint8: (ut_metadata) + buf.resize(4 + 1 + 1); + + entry e; + e["msg_type"] = type; + e["piece"] = piece; + bencode(std::back_inserter(buf), e); + + using namespace lt::detail; + + char* ptr = &buf[0]; + write_uint32(int(buf.size()) - 4, ptr); + write_uint8(20, ptr); + write_uint8(ut_metadata_msg, ptr); + + log("==> ut_metadata [ type: %d piece: %d ]", type, piece); + + error_code ec; + boost::asio::write(s, boost::asio::buffer(&buf[0], buf.size()) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); +} + +entry read_ut_metadata_msg(tcp::socket& s, span recv_buffer) +{ + for (;;) + { + int const len = read_message(s, recv_buffer); + if (len == -1) + { + TEST_ERROR("failed to read message"); + return entry(); + } + auto const buffer = recv_buffer.first(len); + print_message(buffer); + + if (len < 4) continue; + int const msg = buffer[0]; + if (msg != 20) continue; + int const extmsg = buffer[1]; + if (extmsg != 1) continue; + + return bdecode(buffer.subspan(2)); + } +} +#endif // TORRENT_DISABLE_EXTENSIONS + +std::shared_ptr setup_peer(tcp::socket& s, sha1_hash& ih + , std::shared_ptr& ses, bool const incoming = true + , bool const magnet_link = false, bool const dht = false + , torrent_flags_t const flags = torrent_flags_t{} + , torrent_handle* th = nullptr) +{ + std::ofstream out_file; + std::ofstream* file = nullptr; + if (flags & torrent_flags::seed_mode) + { + error_code ec; + create_directory("tmp1_fast", ec); + out_file.open(combine_path("tmp1_fast", "temporary").c_str(), std::ios_base::trunc | std::ios_base::binary); + file = &out_file; + } + else + { + error_code ec; + remove(combine_path("tmp1_fast","temporary").c_str(), ec); + if (ec) log("remove(): %s", ec.message().c_str()); + } + + std::shared_ptr t = ::create_torrent(file); + out_file.close(); + ih = t->info_hash(); + settings_pack sett = settings(); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48900"); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_dht, dht); + sett.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + sett.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + sett.set_bool(settings_pack::enable_outgoing_utp, false); + sett.set_bool(settings_pack::enable_incoming_utp, false); +#if TORRENT_ABI_VERSION == 1 + sett.set_bool(settings_pack::rate_limit_utp, true); +#endif + ses.reset(new lt::session(sett, lt::session::add_default_plugins)); + + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.flags |= flags; + if (magnet_link) + p.info_hash = ih; + else + p.ti = t; + p.save_path = "tmp1_fast"; + + torrent_handle ret = ses->add_torrent(p); + if (th) *th = ret; + + // wait for the torrent to be ready + wait_for_downloading(*ses, "ses"); + + if (incoming) + { + error_code ec; + s.connect(tcp::endpoint(address::from_string("127.0.0.1", ec), ses->listen_port()), ec); + if (ec) TEST_ERROR(ec.message()); + } + else + { + tcp::acceptor l(lt::get_io_service(s)); + l.open(tcp::v4()); + l.bind(tcp::endpoint(address_v4::from_string("127.0.0.1"), 0)); + l.listen(); + tcp::endpoint addr = l.local_endpoint(); + + ret.connect_peer(addr); + print_session_log(*ses); + l.accept(s); + } + + print_session_log(*ses); + + return t; +} + +} // anonymous namespace + +// makes sure that pieces that are allowed and then +// rejected aren't requested again +TORRENT_TEST(reject_fast) +{ + std::cout << "\n === test reject ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::vector allowed_fast; + allowed_fast.push_back(0); + allowed_fast.push_back(1); + allowed_fast.push_back(2); + allowed_fast.push_back(3); + + std::for_each(allowed_fast.begin(), allowed_fast.end() + , std::bind(&send_allow_fast, std::ref(s), _1)); + print_session_log(*ses); + + while (!allowed_fast.empty()) + { + print_session_log(*ses); + int const len = read_message(s, recv_buffer); + if (len == -1) break; + auto buffer = span(recv_buffer).first(len); + print_message(buffer); + int msg = buffer[0]; + if (msg != 0x6) continue; + + using namespace lt::detail; + char const* ptr = buffer.data() + 1; + int const piece = read_int32(ptr); + + std::vector::iterator i = std::find(allowed_fast.begin() + , allowed_fast.end(), piece); + TEST_CHECK(i != allowed_fast.end()); + if (i != allowed_fast.end()) + allowed_fast.erase(i); + // send reject request + recv_buffer[0] = 0x10; + error_code ec; + log("==> reject"); + boost::asio::write(s, boost::asio::buffer("\0\0\0\x0d", 4) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + break; + } + boost::asio::write(s, boost::asio::buffer(recv_buffer, 13) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + break; + } + } + print_session_log(*ses); + s.close(); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); +} + +TORRENT_TEST(invalid_suggest) +{ + std::cout << "\n === test suggest ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + // this is an invalid suggest message. We would not expect to receive a + // request for that piece index. + send_suggest_piece(s, -234); + send_unchoke(s); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); + + int len = read_message(s, recv_buffer); + auto buffer = span(recv_buffer).first(len); + int idx = -1; + while (len > 0) + { + if (buffer[0] == 6) + { + char const* ptr = buffer.data() + 1; + idx = detail::read_int32(ptr); + break; + } + len = read_message(s, recv_buffer); + buffer = span(recv_buffer).first(len); + } + TEST_CHECK(idx != -234); + TEST_CHECK(idx != -1); + s.close(); +} + +TORRENT_TEST(reject_suggest) +{ + std::cout << "\n === test suggest ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::vector suggested; + suggested.push_back(0); + suggested.push_back(1); + suggested.push_back(2); + suggested.push_back(3); + + std::for_each(suggested.begin(), suggested.end() + , std::bind(&send_suggest_piece, std::ref(s), _1)); + print_session_log(*ses); + + send_unchoke(s); + print_session_log(*ses); + + send_keepalive(s); + print_session_log(*ses); + + int fail_counter = 100; + while (!suggested.empty() && fail_counter > 0) + { + print_session_log(*ses); + int const len = read_message(s, recv_buffer); + if (len == -1) break; + auto buffer = span(recv_buffer).first(len); + print_message(buffer); + int const msg = buffer[0]; + fail_counter--; + if (msg != 0x6) continue; + + using namespace lt::detail; + char const* ptr = buffer.data() + 1; + int const piece = read_int32(ptr); + + std::vector::iterator i = std::find(suggested.begin() + , suggested.end(), piece); + TEST_CHECK(i != suggested.end()); + if (i != suggested.end()) + suggested.erase(i); + // send reject request + recv_buffer[0] = 0x10; + error_code ec; + log("==> reject"); + boost::asio::write(s, boost::asio::buffer("\0\0\0\x0d", 4) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + break; + } + boost::asio::write(s, boost::asio::buffer(recv_buffer, 13) + , boost::asio::transfer_all(), ec); + if (ec) + { + TEST_ERROR(ec.message()); + break; + } + } + print_session_log(*ses); + TEST_CHECK(fail_counter > 0); + + s.close(); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); +} + +TORRENT_TEST(suggest_order) +{ + std::cout << "\n === test suggest ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::vector suggested; + suggested.push_back(0); + suggested.push_back(1); + suggested.push_back(2); + suggested.push_back(3); + + std::for_each(suggested.begin(), suggested.end() + , std::bind(&send_suggest_piece, std::ref(s), _1)); + print_session_log(*ses); + + send_unchoke(s); + print_session_log(*ses); + + int fail_counter = 100; + while (!suggested.empty() && fail_counter > 0) + { + print_session_log(*ses); + int const len = read_message(s, recv_buffer); + if (len == -1) break; + auto const buffer = span(recv_buffer).first(len); + print_message({recv_buffer, len}); + int const msg = recv_buffer[0]; + fail_counter--; + + // we're just interested in requests + if (msg != 0x6) continue; + + using namespace lt::detail; + char const* ptr = buffer.data() + 1; + int const piece = read_int32(ptr); + + // make sure we receive the requests inverse order of sending the suggest + // messages. The last suggest should be the highest priority + int const expected_piece = suggested.back(); + suggested.pop_back(); + TEST_EQUAL(piece, expected_piece); + } + print_session_log(*ses); + TEST_CHECK(fail_counter > 0); + + s.close(); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); +} + +TORRENT_TEST(multiple_bitfields) +{ + std::cout << "\n === test multiple bitfields ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ih, ses); + print_session_log(*ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + + std::string bitfield; + bitfield.resize(std::size_t(ti->num_pieces()), '0'); + send_bitfield(s, bitfield.c_str()); + print_session_log(*ses); + bitfield[0] = '1'; + send_bitfield(s, bitfield.c_str()); + print_session_log(*ses); + bitfield[1] = '1'; + send_bitfield(s, bitfield.c_str()); + print_session_log(*ses); + bitfield[2] = '1'; + send_bitfield(s, bitfield.c_str()); + print_session_log(*ses); + + s.close(); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); +} + +TORRENT_TEST(multiple_have_all) +{ + std::cout << "\n === test multiple have_all ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + + print_session_log(*ses); + + send_have_all(s); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + send_have_none(s); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + s.close(); + print_session_log(*ses); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); +} + +// makes sure that pieces that are lost are not requested +TORRENT_TEST(dont_have) +{ + using namespace lt::detail; + + std::cout << "\n === test dont_have ===\n" << std::endl; + + sha1_hash ih; + torrent_handle th; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ih, ses, true + , false, false, torrent_flags_t{}, &th); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + std::this_thread::sleep_for(lt::milliseconds(300)); + print_session_log(*ses); + + std::vector pi; + th.get_peer_info(pi); + + TEST_EQUAL(pi.size(), 1); + if (pi.size() != 1) return; + + // at this point, the peer should be considered a seed + TEST_CHECK(pi[0].flags & peer_info::seed); + + int lt_dont_have = 0; + error_code ec; + while (lt_dont_have == 0) + { + print_session_log(*ses); + + int const len = read_message(s, recv_buffer); + if (len == -1) break; + auto const buffer = span(recv_buffer).first(len); + print_message(buffer); + if (len == 0) continue; + int const msg = buffer[0]; + if (msg != 20) continue; + int const ext_msg = buffer[1]; + if (ext_msg != 0) continue; + + int pos = 0; + ec.clear(); + bdecode_node e = bdecode(buffer.subspan(2), ec, &pos); + if (ec) + { + log("failed to parse extension handshake: %s at pos %d" + , ec.message().c_str(), pos); + } + TEST_CHECK(!ec); + + log("extension handshake: %s", print_entry(e).c_str()); + bdecode_node m = e.dict_find_dict("m"); + TEST_CHECK(m); + if (!m) return; + bdecode_node dont_have = m.dict_find_int("lt_donthave"); + TEST_CHECK(dont_have); + if (!dont_have) return; + + lt_dont_have = int(dont_have.int_value()); + } + print_session_log(*ses); + + char* ptr = recv_buffer; + write_uint32(6, ptr); + write_uint8(20, ptr); + write_uint8(lt_dont_have, ptr); + write_uint32(3, ptr); + + boost::asio::write(s, boost::asio::buffer(recv_buffer, 10) + , boost::asio::transfer_all(), ec); + if (ec) TEST_ERROR(ec.message()); + + print_session_log(*ses); + + std::this_thread::sleep_for(lt::milliseconds(1000)); + + print_session_log(*ses); + + th.get_peer_info(pi); + + TEST_EQUAL(pi.size(), 1); + if (pi.size() != 1) return; + + TEST_CHECK(!(pi[0].flags & peer_info::seed)); + TEST_EQUAL(pi[0].pieces.count(), pi[0].pieces.size() - 1); + TEST_EQUAL(pi[0].pieces[piece_index_t(3)], false); + TEST_EQUAL(pi[0].pieces[piece_index_t(2)], true); + TEST_EQUAL(pi[0].pieces[piece_index_t(1)], true); + TEST_EQUAL(pi[0].pieces[piece_index_t(0)], true); + + print_session_log(*ses); +} + +TORRENT_TEST(extension_handshake) +{ + using namespace lt::detail; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + entry extensions; + send_extension_handshake(s, extensions); + + extensions = read_extension_handshake(s, recv_buffer); + + std::cout << extensions << '\n'; + + // these extensions are built-in + TEST_CHECK(extensions["m"]["lt_donthave"].integer() != 0); +#ifndef TORRENT_DISABLE_SHARE_MODE + TEST_CHECK(extensions["m"]["share_mode"].integer() != 0); +#endif + TEST_CHECK(extensions["m"]["upload_only"].integer() != 0); + TEST_CHECK(extensions["m"]["ut_holepunch"].integer() != 0); + + // these require extensions to be enabled +#ifndef TORRENT_DISABLE_EXTENSIONS + TEST_CHECK(extensions["m"]["ut_metadata"].integer() != 0); + TEST_CHECK(extensions["m"]["ut_pex"].integer() != 0); +#endif +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +// TEST metadata extension messages and edge cases + +// this tests sending a request for a metadata piece that's too high. This is +// pos +TORRENT_TEST(invalid_metadata_request) +{ + using namespace lt::detail; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + std::shared_ptr ti = setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_all(s); + print_session_log(*ses); + + entry extensions; + extensions["m"]["ut_metadata"] = 1; + send_extension_handshake(s, extensions); + + extensions = read_extension_handshake(s, recv_buffer); + + int ut_metadata = int(extensions["m"]["ut_metadata"].integer()); + + log("ut_metadata: %d", ut_metadata); + + // 0 = request + // 1 = piece + // 2 = dont-have + // first send an invalid request + send_ut_metadata_msg(s, ut_metadata, 0, 1); + + // then send a valid one. If we get a response to the second one, + // we assume we were not disconnected because of the invalid one + send_ut_metadata_msg(s, ut_metadata, 0, 0); + + entry ut_metadata_msg = read_ut_metadata_msg(s, recv_buffer); + + // the first response should be "dont-have" + TEST_EQUAL(ut_metadata_msg["msg_type"].integer(), 2); + TEST_EQUAL(ut_metadata_msg["piece"].integer(), 1); + + ut_metadata_msg = read_ut_metadata_msg(s, recv_buffer); + + // the second response should be the payload + TEST_EQUAL(ut_metadata_msg["msg_type"].integer(), 1); + TEST_EQUAL(ut_metadata_msg["piece"].integer(), 0); + + print_session_log(*ses); +} +#endif // TORRENT_DISABLE_EXTENSIONS + + +TORRENT_TEST(invalid_request) +{ + std::cout << "\n === test request ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + send_have_none(s); + + peer_request req; + req.piece = piece_index_t(124134235); + req.start = 0; + req.length = 0x4000; + send_request(s, req); +} + +namespace { + +void have_all_test(bool const incoming) +{ + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses, incoming, false, false, torrent_flags::seed_mode); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + print_session_log(*ses); + + // expect to receive a have-all (not a bitfield) + // since we advertised support for FAST extensions + for (;;) + { + int const len = read_message(s, recv_buffer); + if (len == -1) + { + TEST_ERROR("failed to receive have-all despite advertising support for FAST"); + break; + } + auto const buffer = span(recv_buffer).first(len); + print_message(buffer); + int const msg = buffer[0]; + if (msg == 0xe) // have-all + { + // success! + break; + } + if (msg == 5) // bitfield + { + TEST_ERROR("received bitfield from seed despite advertising support for FAST"); + break; + } + } +} + +} // anonymous namespace + +TORRENT_TEST(outgoing_have_all) +{ + std::cout << "\n === test outgoing have-all ===\n" << std::endl; + have_all_test(false); +} + +TORRENT_TEST(incoming_have_all) +{ + std::cout << "\n === test incoming have-all ===\n" << std::endl; + have_all_test(true); +} + +TORRENT_TEST(dht_port_no_support) +{ + std::cout << "\n === test DHT port (without advertising support) ===\n" << std::endl; + + sha1_hash ih; + std::shared_ptr ses; + io_service ios; + tcp::socket s(ios); + setup_peer(s, ih, ses, true, true, true); + + char recv_buffer[1000]; + do_handshake(s, ih, recv_buffer); + send_dht_port(s, 6881); + print_session_log(*ses); + + s.close(); + std::this_thread::sleep_for(lt::milliseconds(500)); + print_session_log(*ses); +} +// TODO: test sending invalid requests (out of bound piece index, offsets and +// sizes) diff --git a/test/test_fence.cpp b/test/test_fence.cpp new file mode 100644 index 0000000..3476f3b --- /dev/null +++ b/test/test_fence.cpp @@ -0,0 +1,243 @@ +/* + +Copyright (c) 2003-2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/storage.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/performance_counters.hpp" +#include "test.hpp" + +#include + +using namespace lt; + +using lt::aux::disk_job_fence; + +TORRENT_TEST(empty_fence) +{ + disk_job_fence fence; + counters cnt; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret_int = 0; + bool ret = false; + // add a fence job + ret_int = fence.raise_fence(&test_job[5], &test_job[6], cnt); + // since we don't have any outstanding jobs + // we need to post this job + TEST_CHECK(ret_int == disk_job_fence::fence_post_fence); + + ret = fence.is_blocked(&test_job[7]); + TEST_CHECK(ret == true); + ret = fence.is_blocked(&test_job[8]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to post the blocked jobs + TEST_CHECK(jobs.size() == 2); + TEST_CHECK(jobs.first() == &test_job[7]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[7], jobs); + fence.job_complete(&test_job[8], jobs); +} + +TORRENT_TEST(job_fence) +{ + counters cnt; + disk_job_fence fence; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret_int = 0; + bool ret = false; + TEST_CHECK(fence.num_outstanding_jobs() == 0); + ret = fence.is_blocked(&test_job[0]); + TEST_CHECK(ret == false); + TEST_CHECK(fence.num_outstanding_jobs() == 1); + ret = fence.is_blocked(&test_job[1]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[2]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[3]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[4]); + TEST_CHECK(ret == false); + + TEST_CHECK(fence.num_outstanding_jobs() == 5); + TEST_CHECK(fence.num_blocked() == 0); + + // add a fence job + ret_int = fence.raise_fence(&test_job[5], &test_job[6], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret_int == disk_job_fence::fence_post_flush); + + ret = fence.is_blocked(&test_job[7]); + TEST_CHECK(ret == true); + ret = fence.is_blocked(&test_job[8]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + fence.job_complete(&test_job[3], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[2], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[4], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[1], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[0], jobs); + TEST_EQUAL(jobs.size(), 0); + + // the flush job completes + fence.job_complete(&test_job[6], jobs); + + // this was the last job. Now we should be + // able to run the fence job + TEST_EQUAL(jobs.size(), 1); + + TEST_CHECK(jobs.first() == &test_job[5]); + jobs.pop_front(); + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to post the blocked jobs + TEST_EQUAL(jobs.size(), 2); + TEST_CHECK(jobs.first() == &test_job[7]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[7], jobs); + fence.job_complete(&test_job[8], jobs); +} + +TORRENT_TEST(double_fence) +{ + counters cnt; + disk_job_fence fence; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret_int = 0; + bool ret = false; + TEST_CHECK(fence.num_outstanding_jobs() == 0); + ret = fence.is_blocked(&test_job[0]); + TEST_CHECK(ret == false); + TEST_CHECK(fence.num_outstanding_jobs() == 1); + ret = fence.is_blocked(&test_job[1]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[2]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[3]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[4]); + TEST_CHECK(ret == false); + + TEST_CHECK(fence.num_outstanding_jobs() == 5); + TEST_CHECK(fence.num_blocked() == 0); + + // add two fence jobs + ret_int = fence.raise_fence(&test_job[5], &test_job[6], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret_int == disk_job_fence::fence_post_flush); + + ret_int = fence.raise_fence(&test_job[7], &test_job[8], cnt); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret_int == disk_job_fence::fence_post_none); + std::printf("ret: %d\n", ret_int); + + ret = fence.is_blocked(&test_job[9]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + fence.job_complete(&test_job[3], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[2], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[4], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[1], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[0], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[6], jobs); + // this was the last job. Now we should be + // able to run the fence job + TEST_CHECK(jobs.size() == 1); + + TEST_CHECK(jobs.first() == &test_job[5]); + jobs.pop_front(); + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to run the next fence job + // first we get the flush job + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[8]); + jobs.pop_front(); + + fence.job_complete(&test_job[8], jobs); + + // then the fence itself + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[7]); + jobs.pop_front(); + + fence.job_complete(&test_job[7], jobs); + + // and now we can run the remaining blocked job + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[9]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[9], jobs); +} + diff --git a/test/test_ffs.cpp b/test/test_ffs.cpp new file mode 100644 index 0000000..ab23c13 --- /dev/null +++ b/test/test_ffs.cpp @@ -0,0 +1,138 @@ +/* + +Copyright (c) 2015, Arvid Norberg, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/span.hpp" +#include "libtorrent/hex.hpp" // from_hex +#include "libtorrent/aux_/ffs.hpp" +#include "libtorrent/aux_/byteswap.hpp" + +using namespace lt; + +static void to_binary(char const* s, std::uint32_t* buf) +{ + aux::from_hex({s, 40}, reinterpret_cast(&buf[0])); +} + +TORRENT_TEST(count_leading_zeros) +{ + std::vector> const tests = { + { "ffffffffffffffffffffffffffffffffffffffff", 0 }, + { "0000000000000000000000000000000000000000", 160 }, + { "fff0000000000000000000000000000000000000", 0 }, + { "7ff0000000000000000000000000000000000000", 1 }, + { "3ff0000000000000000000000000000000000000", 2 }, + { "1ff0000000000000000000000000000000000000", 3 }, + { "0ff0000000000000000000000000000000000000", 4 }, + { "07f0000000000000000000000000000000000000", 5 }, + { "03f0000000000000000000000000000000000000", 6 }, + { "01f0000000000000000000000000000000000000", 7 }, + { "00f0000000000000000000000000000000000000", 8 }, + { "0070000000000000000000000000000000000000", 9 }, + { "0030000000000000000000000000000000000000", 10 }, + { "0010000000000000000000000000000000000000", 11 }, + { "0000000ffff00000000000000000000000000000", 28 }, + { "00000007fff00000000000000000000000000000", 29 }, + { "00000003fff00000000000000000000000000000", 30 }, + { "00000001fff00000000000000000000000000000", 31 }, + { "00000000fff00000000000000000000000000000", 32 }, + { "000000007ff00000000000000000000000000000", 33 }, + { "000000003ff00000000000000000000000000000", 34 }, + { "000000001ff00000000000000000000000000000", 35 }, + }; + + for (auto const& t : tests) + { + std::printf("%s\n", t.first); + std::uint32_t buf[5]; + to_binary(t.first, buf); + TEST_EQUAL(aux::count_leading_zeros_sw({buf, 5}), t.second); +#if TORRENT_HAS_BUILTIN_CLZ || defined _MSC_VER + TEST_EQUAL(aux::count_leading_zeros_hw({buf, 5}), t.second); +#endif + TEST_EQUAL(aux::count_leading_zeros({buf, 5}), t.second); + } +} + +TORRENT_TEST(count_trailing_ones_u32) +{ + std::uint32_t v = 0; + TEST_EQUAL(aux::count_trailing_ones_sw(v), 0); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(v), 0); +#endif + TEST_EQUAL(aux::count_trailing_ones(v), 0); + + v = 0xffffffff; + TEST_EQUAL(aux::count_trailing_ones_sw(v), 32); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(v), 32); +#endif + TEST_EQUAL(aux::count_trailing_ones(v), 32); + + v = aux::host_to_network(0xff00ff00); + TEST_EQUAL(aux::count_trailing_ones_sw(v), 0); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(v), 0); +#endif + TEST_EQUAL(aux::count_trailing_ones(v), 0); + + v = aux::host_to_network(0xff0fff00); + TEST_EQUAL(aux::count_trailing_ones_sw(v), 0); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(v), 0); +#endif + TEST_EQUAL(aux::count_trailing_ones(v), 0); + + v = aux::host_to_network(0xf0ff00ff); + TEST_EQUAL(aux::count_trailing_ones_sw(v), 8); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(v), 8); +#endif + TEST_EQUAL(aux::count_trailing_ones(v), 8); + + v = aux::host_to_network(0xf0ff0fff); + TEST_EQUAL(aux::count_trailing_ones_sw(v), 12); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(v), 12); +#endif + TEST_EQUAL(aux::count_trailing_ones(v), 12); + + std::uint32_t const arr[2] = { + aux::host_to_network(0xf0ff0fff) + , 0xffffffff}; + TEST_EQUAL(aux::count_trailing_ones_sw(arr), 44); +#if TORRENT_HAS_BUILTIN_CTZ || defined _MSC_VER + TEST_EQUAL(aux::count_trailing_ones_hw(arr), 44); +#endif + TEST_EQUAL(aux::count_trailing_ones(arr), 44); +} diff --git a/test/test_file.cpp b/test/test_file.cpp new file mode 100644 index 0000000..5965fb9 --- /dev/null +++ b/test/test_file.cpp @@ -0,0 +1,693 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/file.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/numeric_cast.hpp" +#include "libtorrent/string_view.hpp" +#include "test.hpp" +#include +#include +#include +#include + +using namespace lt; + +namespace { + +int touch_file(std::string const& filename, int size) +{ + using namespace lt; + + std::vector v; + v.resize(aux::numeric_cast(size)); + for (int i = 0; i < size; ++i) + v[std::size_t(i)] = char(i & 255); + + file f; + error_code ec; + if (!f.open(filename, open_mode::write_only, ec)) return -1; + TEST_EQUAL(ec, error_code()); + if (ec) return -1; + iovec_t b = {v}; + std::int64_t written = f.writev(0, b, ec); + if (written != int(v.size())) return -3; + if (ec) return -3; + TEST_EQUAL(ec, error_code()); + return 0; +} + +} // anonymous namespace + +TORRENT_TEST(create_directory) +{ + error_code ec; + create_directory("__foobar__", ec); + if (ec) std::printf("ERROR: create_directory: (%d) %s\n" + , ec.value(), ec.message().c_str()); + TEST_CHECK(!ec); + + file_status st; + stat_file("__foobar__", &st, ec); + if (ec) std::printf("ERROR: stat_file: (%d) %s\n" + , ec.value(), ec.message().c_str()); + TEST_CHECK(!ec); + + TEST_CHECK(st.mode & file_status::directory); + + remove("__foobar__", ec); + if (ec) std::printf("ERROR: remove: (%d) %s\n" + , ec.value(), ec.message().c_str()); + TEST_CHECK(!ec); +} + +TORRENT_TEST(file_status) +{ + error_code ec; + + // test that the modification timestamps + touch_file("__test_timestamp__", 10); + + file_status st1; + stat_file("__test_timestamp__", &st1, ec); + TEST_CHECK(!ec); + + // sleep for 3 seconds and then make sure the difference in timestamp is + // between 2-4 seconds after touching it again + std::this_thread::sleep_for(lt::milliseconds(3000)); + + touch_file("__test_timestamp__", 10); + + file_status st2; + stat_file("__test_timestamp__", &st2, ec); + TEST_CHECK(!ec); + + int diff = int(st2.mtime - st1.mtime); + std::printf("timestamp difference: %d seconds. expected approx. 3 seconds\n" + , diff); + + TEST_CHECK(diff >= 2 && diff <= 4); +} + +TORRENT_TEST(directory) +{ + error_code ec; + + create_directory("file_test_dir", ec); + if (ec) std::printf("create_directory: %s\n", ec.message().c_str()); + TEST_CHECK(!ec); + + std::string cwd = current_working_directory(); + + touch_file(combine_path("file_test_dir", "abc"), 10); + touch_file(combine_path("file_test_dir", "def"), 100); + touch_file(combine_path("file_test_dir", "ghi"), 1000); + + std::set files; + for (directory i("file_test_dir", ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + TEST_CHECK(files.count(f) == 0); + files.insert(f); + std::printf(" %s\n", f.c_str()); + } + + TEST_CHECK(files.count("abc") == 1); + TEST_CHECK(files.count("def") == 1); + TEST_CHECK(files.count("ghi") == 1); + TEST_CHECK(files.count("..") == 1); + TEST_CHECK(files.count(".") == 1); + files.clear(); + + recursive_copy("file_test_dir", "file_test_dir2", ec); + + for (directory i("file_test_dir2", ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + TEST_CHECK(files.count(f) == 0); + files.insert(f); + std::printf(" %s\n", f.c_str()); + } + + remove_all("file_test_dir", ec); + if (ec) std::printf("remove_all: %s\n", ec.message().c_str()); + remove_all("file_test_dir2", ec); + if (ec) std::printf("remove_all: %s\n", ec.message().c_str()); +} + +// test path functions +TORRENT_TEST(paths) +{ + TEST_EQUAL(combine_path("test1/", "test2"), "test1/test2"); + TEST_EQUAL(combine_path("test1", "."), "test1"); + TEST_EQUAL(combine_path(".", "test1"), "test1"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(combine_path("test1\\", "test2"), "test1\\test2"); + TEST_EQUAL(combine_path("test1", "test2"), "test1\\test2"); +#else + TEST_EQUAL(combine_path("test1", "test2"), "test1/test2"); +#endif + + TEST_EQUAL(extension("blah"), ""); + TEST_EQUAL(extension("blah.exe"), ".exe"); + TEST_EQUAL(extension("blah.foo.bar"), ".bar"); + TEST_EQUAL(extension("blah.foo."), "."); + TEST_EQUAL(extension("blah.foo/bar"), ""); + + TEST_EQUAL(remove_extension("blah"), "blah"); + TEST_EQUAL(remove_extension("blah.exe"), "blah"); + TEST_EQUAL(remove_extension("blah.foo.bar"), "blah.foo"); + TEST_EQUAL(remove_extension("blah.foo."), "blah.foo"); + +#ifdef TORRENT_WINDOWS + TEST_EQUAL(is_root_path("c:\\blah"), false); + TEST_EQUAL(is_root_path("c:\\"), true); + TEST_EQUAL(is_root_path("\\\\"), true); + TEST_EQUAL(is_root_path("\\\\foobar"), true); + TEST_EQUAL(is_root_path("\\\\foobar\\"), true); + TEST_EQUAL(is_root_path("\\\\foobar/"), true); + TEST_EQUAL(is_root_path("\\\\foo/bar"), false); + TEST_EQUAL(is_root_path("\\\\foo\\bar\\"), false); +#else + TEST_EQUAL(is_root_path("/blah"), false); + TEST_EQUAL(is_root_path("/"), true); +#endif + +#ifdef TORRENT_WINDOWS + TEST_CHECK(compare_path("c:\\blah\\", "c:\\blah")); + TEST_CHECK(compare_path("c:\\blah", "c:\\blah")); + TEST_CHECK(compare_path("c:\\blah/", "c:\\blah")); + TEST_CHECK(compare_path("c:\\blah", "c:\\blah\\")); + TEST_CHECK(compare_path("c:\\blah", "c:\\blah")); + TEST_CHECK(compare_path("c:\\blah", "c:\\blah/")); + + TEST_CHECK(!compare_path("c:\\bla", "c:\\blah/")); + TEST_CHECK(!compare_path("c:\\bla", "c:\\blah")); + TEST_CHECK(!compare_path("c:\\blah", "c:\\bla")); + TEST_CHECK(!compare_path("c:\\blah\\sdf", "c:\\blah")); +#else + TEST_CHECK(compare_path("/blah", "/blah")); + TEST_CHECK(compare_path("/blah/", "/blah")); + TEST_CHECK(compare_path("/blah", "/blah")); + TEST_CHECK(compare_path("/blah", "/blah/")); + + TEST_CHECK(!compare_path("/bla", "/blah/")); + TEST_CHECK(!compare_path("/bla", "/blah")); + TEST_CHECK(!compare_path("/blah", "/bla")); + TEST_CHECK(!compare_path("/blah/sdf", "/blah")); +#endif + + // if has_parent_path() returns false + // parent_path() should return the empty string + TEST_EQUAL(parent_path("blah"), ""); + TEST_EQUAL(has_parent_path("blah"), false); + TEST_EQUAL(parent_path("/blah/foo/bar"), "/blah/foo/"); + TEST_EQUAL(has_parent_path("/blah/foo/bar"), true); + TEST_EQUAL(parent_path("/blah/foo/bar/"), "/blah/foo/"); + TEST_EQUAL(has_parent_path("/blah/foo/bar/"), true); + TEST_EQUAL(parent_path("/a"), "/"); + TEST_EQUAL(has_parent_path("/a"), true); + TEST_EQUAL(parent_path("/"), ""); + TEST_EQUAL(has_parent_path("/"), false); + TEST_EQUAL(parent_path(""), ""); + TEST_EQUAL(has_parent_path(""), false); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(parent_path("\\\\"), ""); + TEST_EQUAL(has_parent_path("\\\\"), false); + TEST_EQUAL(parent_path("c:\\"), ""); + TEST_EQUAL(has_parent_path("c:\\"), false); + TEST_EQUAL(parent_path("c:\\a"), "c:\\"); + TEST_EQUAL(has_parent_path("c:\\a"), true); + TEST_EQUAL(has_parent_path("\\\\a"), false); + TEST_EQUAL(has_parent_path("\\\\foobar/"), false); + TEST_EQUAL(has_parent_path("\\\\foobar\\"), false); + TEST_EQUAL(has_parent_path("\\\\foo/bar\\"), true); +#endif + +#ifdef TORRENT_WINDOWS + TEST_EQUAL(is_complete("c:\\"), true); + TEST_EQUAL(is_complete("c:\\foo\\bar"), true); + TEST_EQUAL(is_complete("\\\\foo\\bar"), true); + TEST_EQUAL(is_complete("foo/bar"), false); + TEST_EQUAL(is_complete("\\\\"), true); +#else + TEST_EQUAL(is_complete("/foo/bar"), true); + TEST_EQUAL(is_complete("foo/bar"), false); + TEST_EQUAL(is_complete("/"), true); + TEST_EQUAL(is_complete(""), false); +#endif + + TEST_EQUAL(complete("."), current_working_directory()); +} + +TORRENT_TEST(filename) +{ +#ifdef TORRENT_WINDOWS + TEST_EQUAL(filename("blah"), "blah"); + TEST_EQUAL(filename("\\blah\\foo\\bar"), "bar"); + TEST_EQUAL(filename("\\blah\\foo\\bar\\"), "bar"); + TEST_EQUAL(filename("blah\\"), "blah"); +#endif + TEST_EQUAL(filename("blah"), "blah"); + TEST_EQUAL(filename("/blah/foo/bar"), "bar"); + TEST_EQUAL(filename("/blah/foo/bar/"), "bar"); + TEST_EQUAL(filename("blah/"), "blah"); +} + +TORRENT_TEST(split_path) +{ + using r = std::pair; + +#ifdef TORRENT_WINDOWS + TEST_CHECK(lsplit_path("\\b\\c\\d") == r("b", "c\\d")); + TEST_CHECK(lsplit_path("a\\b\\c\\d") == r("a", "b\\c\\d")); + TEST_CHECK(lsplit_path("a") == r("a", "")); + TEST_CHECK(lsplit_path("") == r("", "")); + + TEST_CHECK(lsplit_path("a\\b/c\\d") == r("a", "b/c\\d")); + TEST_CHECK(lsplit_path("a/b\\c\\d") == r("a", "b\\c\\d")); + + TEST_CHECK(rsplit_path("a\\b\\c\\d\\") == r("a\\b\\c", "d")); + TEST_CHECK(rsplit_path("\\a\\b\\c\\d") == r("\\a\\b\\c", "d")); + TEST_CHECK(rsplit_path("\\a") == r("", "a")); + TEST_CHECK(rsplit_path("a") == r("", "a")); + TEST_CHECK(rsplit_path("") == r("", "")); + + TEST_CHECK(rsplit_path("a\\b/c\\d\\") == r("a\\b/c", "d")); + TEST_CHECK(rsplit_path("a\\b\\c/d\\") == r("a\\b\\c", "d")); +#endif + TEST_CHECK(lsplit_path("/b/c/d") == r("b", "c/d")); + TEST_CHECK(lsplit_path("a/b/c/d") == r("a", "b/c/d")); + TEST_CHECK(lsplit_path("a") == r("a", "")); + TEST_CHECK(lsplit_path("") == r("", "")); + + TEST_CHECK(rsplit_path("a/b/c/d/") == r("a/b/c", "d")); + TEST_CHECK(rsplit_path("/a/b/c/d") == r("/a/b/c", "d")); + TEST_CHECK(rsplit_path("/a") == r("", "a")); + TEST_CHECK(rsplit_path("a") == r("", "a")); + TEST_CHECK(rsplit_path("") == r("", "")); +} + +TORRENT_TEST(split_path_pos) +{ + using r = std::pair; + +#ifdef TORRENT_WINDOWS + TEST_CHECK(lsplit_path("\\b\\c\\d", 0) == r("b", "c\\d")); + TEST_CHECK(lsplit_path("\\b\\c\\d", 1) == r("b", "c\\d")); + TEST_CHECK(lsplit_path("\\b\\c\\d", 2) == r("b", "c\\d")); + TEST_CHECK(lsplit_path("\\b\\c\\d", 3) == r("b\\c", "d")); + TEST_CHECK(lsplit_path("\\b\\c\\d", 4) == r("b\\c", "d")); + TEST_CHECK(lsplit_path("\\b\\c\\d", 5) == r("b\\c\\d", "")); + TEST_CHECK(lsplit_path("\\b\\c\\d", 6) == r("b\\c\\d", "")); + + TEST_CHECK(lsplit_path("b\\c\\d", 0) == r("b", "c\\d")); + TEST_CHECK(lsplit_path("b\\c\\d", 1) == r("b", "c\\d")); + TEST_CHECK(lsplit_path("b\\c\\d", 2) == r("b\\c", "d")); + TEST_CHECK(lsplit_path("b\\c\\d", 3) == r("b\\c", "d")); + TEST_CHECK(lsplit_path("b\\c\\d", 4) == r("b\\c\\d", "")); + TEST_CHECK(lsplit_path("b\\c\\d", 5) == r("b\\c\\d", "")); +#endif + TEST_CHECK(lsplit_path("/b/c/d", 0) == r("b", "c/d")); + TEST_CHECK(lsplit_path("/b/c/d", 1) == r("b", "c/d")); + TEST_CHECK(lsplit_path("/b/c/d", 2) == r("b", "c/d")); + TEST_CHECK(lsplit_path("/b/c/d", 3) == r("b/c", "d")); + TEST_CHECK(lsplit_path("/b/c/d", 4) == r("b/c", "d")); + TEST_CHECK(lsplit_path("/b/c/d", 5) == r("b/c/d", "")); + TEST_CHECK(lsplit_path("/b/c/d", 6) == r("b/c/d", "")); + + TEST_CHECK(lsplit_path("b/c/d", 0) == r("b", "c/d")); + TEST_CHECK(lsplit_path("b/c/d", 1) == r("b", "c/d")); + TEST_CHECK(lsplit_path("b/c/d", 2) == r("b/c", "d")); + TEST_CHECK(lsplit_path("b/c/d", 3) == r("b/c", "d")); + TEST_CHECK(lsplit_path("b/c/d", 4) == r("b/c/d", "")); + TEST_CHECK(lsplit_path("b/c/d", 5) == r("b/c/d", "")); +} + +// file class +TORRENT_TEST(file) +{ + error_code ec; + file f; + std::string test_file_name = "test_file"; + TEST_CHECK(f.open(test_file_name, open_mode::read_write, ec)); + if (ec) + std::printf("open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + if (ec) std::printf("%s\n", ec.message().c_str()); + char test[] = "test"; + int const test_word_size = int(sizeof(test)) - 1; + iovec_t b = {test, test_word_size}; + TEST_EQUAL(f.writev(0, b, ec), test_word_size); + if (ec) + std::printf("writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_CHECK(!ec); + char test_buf[test_word_size + 1] = {0}; + b = { test_buf, test_word_size }; + TEST_EQUAL(f.readv(0, b, ec), test_word_size); + if (ec) + std::printf("readv failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(test_buf == "test"_sv); + f.close(); + + TEST_CHECK(f.open(test_file_name, open_mode::read_only, ec)); + if (ec) + std::printf("open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + if (ec) std::printf("%s\n", ec.message().c_str()); + + char test_buf2[test_word_size + 1] = {0}; + std::memset(test_buf, 0, sizeof(test_buf)); + iovec_t two_buffers[2] { + {test_buf, test_word_size}, + {test_buf2, test_word_size} + }; + + TEST_EQUAL(f.readv(0, two_buffers, ec), test_word_size); + if (ec) + std::printf("readv failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(test_buf == "test"_sv); + f.close(); +} + +TORRENT_TEST(hard_link) +{ + // try to create a hard link to see what happens + // first create a regular file to then add another link to. + + // create a file, write some stuff to it, create a hard link to that file, + // read that file and assert we get the same stuff we wrote to the first file + error_code ec; + file f; + TEST_CHECK(f.open("original_file", open_mode::read_write, ec)); + if (ec) + std::printf("open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + + char str[] = "abcdefghijklmnopqrstuvwxyz"; + iovec_t b = { str, 26 }; + TEST_EQUAL(f.writev(0, b, ec), 26); + if (ec) + std::printf("writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + f.close(); + + hard_link("original_file", "second_link", ec); + + if (ec) + std::printf("hard_link failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + + + TEST_CHECK(f.open("second_link", open_mode::read_write, ec)); + if (ec) + std::printf("open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + + char test_buf[27] = {}; + b = { test_buf, 27 }; + TEST_EQUAL(f.readv(0, b, ec), 26); + if (ec) + std::printf("readv failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(test_buf == "abcdefghijklmnopqrstuvwxyz"_sv); + f.close(); + + remove("original_file", ec); + if (ec) + std::printf("remove failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + + remove("second_link", ec); + if (ec) + std::printf("remove failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); +} + +TORRENT_TEST(coalesce_buffer) +{ + error_code ec; + file f; + TEST_CHECK(f.open("test_file", open_mode::read_write, ec)); + if (ec) + std::printf("open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_EQUAL(ec, error_code()); + if (ec) std::printf("%s\n", ec.message().c_str()); + char test[] = "test"; + char foobar[] = "foobar"; + iovec_t b[2] = {{test, 4}, {foobar, 6}}; + TEST_EQUAL(f.writev(0, b, ec, open_mode::coalesce_buffers), 4 + 6); + if (ec) + std::printf("writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); + TEST_CHECK(!ec); + char test_buf1[5] = {0}; + char test_buf2[7] = {0}; + b[0] = { test_buf1, 4 }; + b[1] = { test_buf2, 6 }; + TEST_EQUAL(f.readv(0, b, ec), 4 + 6); + if (ec) + { + std::printf("readv failed: [%s] %s\n" + , ec.category().name(), ec.message().c_str()); + } + TEST_EQUAL(ec, error_code()); + TEST_CHECK(test_buf1 == "test"_sv); + TEST_CHECK(test_buf2 == "foobar"_sv); + f.close(); +} + +TORRENT_TEST(stat_file) +{ + file_status st; + error_code ec; + stat_file("no_such_file_or_directory.file", &st, ec); + TEST_CHECK(ec); + TEST_EQUAL(ec, boost::system::errc::no_such_file_or_directory); +} + +TORRENT_TEST(relative_path) +{ +#ifdef TORRENT_WINDOWS +#define S "\\" +#else +#define S "/" +#endif + TEST_EQUAL(lexically_relative("A" S "B" S "C", "A" S "C" S "B") + , ".." S ".." S "C" S "B"); + + TEST_EQUAL(lexically_relative("A" S "B" S "C" S, "A" S "C" S "B") + , ".." S ".." S "C" S "B"); + + TEST_EQUAL(lexically_relative("A" S "B" S "C" S, "A" S "C" S "B" S) + , ".." S ".." S "C" S "B"); + + TEST_EQUAL(lexically_relative("A" S "B" S "C", "A" S "B" S "B") + , ".." S "B"); + + TEST_EQUAL(lexically_relative("A" S "B" S "C", "A" S "B" S "C") + , ""); + + TEST_EQUAL(lexically_relative("A" S "B", "A" S "B") + , ""); + + TEST_EQUAL(lexically_relative("A" S "B", "A" S "B" S "C") + , "C"); + + TEST_EQUAL(lexically_relative("A" S, "A" S) + , ""); + + TEST_EQUAL(lexically_relative("", "A" S "B" S "C") + , "A" S "B" S "C"); + + TEST_EQUAL(lexically_relative("A" S "B" S "C", "") + , ".." S ".." S ".." S); + + TEST_EQUAL(lexically_relative("", ""), ""); +} + +// UNC tests +#if TORRENT_USE_UNC_PATHS + +namespace { +std::tuple current_directory_caps() +{ +#ifdef TORRENT_WINDOWS + DWORD maximum_component_length = FILENAME_MAX; + DWORD file_system_flags = 0; + if (GetVolumeInformation(nullptr + , nullptr, 0, nullptr + , &maximum_component_length, &file_system_flags, nullptr, 0) == 0) + { + error_code ec(GetLastError(), system_category()); + std::printf("GetVolumeInformation: [%s : %d] %s\n" + , ec.category().name(), ec.value(), ec.message().c_str()); + // ignore errors, this is best-effort + } + bool const support_hard_links = ((file_system_flags & FILE_SUPPORTS_HARD_LINKS) != 0); + return std::make_tuple(int(maximum_component_length), support_hard_links); +#else + return std::make_tuple(255, true); +#endif +} +} // anonymous namespace + +TORRENT_TEST(unc_tests) +{ + using lt::canonicalize_path; + TEST_EQUAL(canonicalize_path("c:\\a\\..\\b"), "c:\\b"); + TEST_EQUAL(canonicalize_path("a\\..\\b"), "b"); + TEST_EQUAL(canonicalize_path("a\\..\\.\\b"), "b"); + TEST_EQUAL(canonicalize_path("\\.\\a"), "\\a"); + TEST_EQUAL(canonicalize_path("\\\\bla\\.\\a"), "\\\\bla\\a"); + TEST_EQUAL(canonicalize_path("c:\\bla\\a"), "c:\\bla\\a"); + + error_code ec; + + std::vector special_names + { + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", + "COM9", "LPT1", "LPT2", "LPT3", + "LPT4", "LPT5", "LPT6", "LPT7", + "LPT8", "LPT9" + }; + + for (std::string special_name : special_names) + { + touch_file(special_name, 10); + TEST_CHECK(lt::exists(special_name)); + lt::remove(special_name, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!lt::exists(special_name)); + } + + int maximum_component_length; + bool support_hard_links; + std::tie(maximum_component_length, support_hard_links) = current_directory_caps(); + + std::cout << "max file path component length: " << maximum_component_length << "\n" + << "support hard links: " << (support_hard_links?"yes":"no") << "\n"; + + std::string long_dir_name; + maximum_component_length -= 12; + long_dir_name.resize(size_t(maximum_component_length)); + for (int i = 0; i < maximum_component_length; ++i) + long_dir_name[i] = static_cast((i % 26) + 'A'); + + std::string long_file_name1 = combine_path(long_dir_name, long_dir_name); + long_file_name1.back() = '1'; + std::string long_file_name2 = long_file_name1; + long_file_name2.back() = '2'; + + lt::create_directory(long_dir_name, ec); + TEST_EQUAL(ec, error_code()); + if (ec) + { + std::cout << "create_directory \"" << long_dir_name << "\" failed: " << ec.message() << "\n"; + std::wcout << convert_to_native_path_string(long_dir_name) << L"\n"; + } + TEST_CHECK(lt::exists(long_dir_name)); + TEST_CHECK(lt::is_directory(long_dir_name, ec)); + TEST_EQUAL(ec, error_code()); + if (ec) + { + std::cout << "is_directory \"" << long_dir_name << "\" failed " << ec.message() << "\n"; + std::wcout << convert_to_native_path_string(long_dir_name) << L"\n"; + } + + touch_file(long_file_name1, 10); + TEST_CHECK(lt::exists(long_file_name1)); + + lt::rename(long_file_name1, long_file_name2, ec); + TEST_EQUAL(ec, error_code()); + if (ec) + { + std::cout << "rename \"" << long_file_name1 << "\" failed " << ec.message() << "\n"; + std::wcout << convert_to_native_path_string(long_file_name1) << L"\n"; + } + TEST_CHECK(!lt::exists(long_file_name1)); + TEST_CHECK(lt::exists(long_file_name2)); + + lt::copy_file(long_file_name2, long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + if (ec) + { + std::cout << "copy_file \"" << long_file_name2 << "\" failed " << ec.message() << "\n"; + std::wcout << convert_to_native_path_string(long_file_name2) << L"\n"; + } + TEST_CHECK(lt::exists(long_file_name1)); + + std::set files; + + for (lt::directory i(long_dir_name, ec); !i.done(); i.next(ec)) + { + std::string f = i.file(); + files.insert(f); + } + + TEST_EQUAL(files.size(), 4); + + lt::remove(long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + if (ec) + { + std::cout << "remove \"" << long_file_name1 << "\" failed " << ec.message() << "\n"; + std::wcout << convert_to_native_path_string(long_file_name1) << L"\n"; + } + TEST_CHECK(!lt::exists(long_file_name1)); + + if (support_hard_links) + { + lt::hard_link(long_file_name2, long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(lt::exists(long_file_name1)); + + lt::remove(long_file_name1, ec); + TEST_EQUAL(ec, error_code()); + TEST_CHECK(!lt::exists(long_file_name1)); + } +} + +TORRENT_TEST(unc_paths) +{ + std::string const reserved_name = "con"; + error_code ec; + { + file f; + TEST_CHECK(f.open(reserved_name, open_mode::read_write, ec) && !ec); + } + remove(reserved_name, ec); + TEST_CHECK(!ec); +} + +#endif diff --git a/test/test_file_progress.cpp b/test/test_file_progress.cpp new file mode 100644 index 0000000..df5f97d --- /dev/null +++ b/test/test_file_progress.cpp @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test_utils.hpp" +#include "libtorrent/aux_/file_progress.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/piece_picker.hpp" + +using namespace lt; + +TORRENT_TEST(init) +{ + // test the init function to make sure it assigns + // the correct number of bytes across the files + const int piece_size = 256; + + file_storage fs; + fs.add_file("torrent/1", 0); + fs.add_file("torrent/2", 10); + fs.add_file("torrent/3", 20); + fs.add_file("torrent/4", 30); + fs.add_file("torrent/5", 40); + fs.add_file("torrent/6", 100000); + fs.add_file("torrent/7", 30); + fs.set_piece_length(piece_size); + fs.set_num_pieces((int(fs.total_size()) + piece_size - 1) / piece_size); + + for (auto const idx : fs.piece_range()) + { + piece_picker picker(4, fs.total_size() % 4, fs.num_pieces()); + picker.we_have(idx); + + aux::file_progress fp; + fp.init(picker, fs); + + aux::vector vec; + fp.export_progress(vec); + + std::int64_t sum = 0; + for (file_index_t i(0); i < vec.end_index(); ++i) + sum += vec[i]; + + TEST_EQUAL(sum, fs.piece_size(idx)); + } +} + +TORRENT_TEST(init2) +{ + // test the init function to make sure it assigns + // the correct number of bytes across the files + const int piece_size = 256; + + file_storage fs; + fs.add_file("torrent/1", 100000); + fs.add_file("torrent/2", 10); + fs.set_piece_length(piece_size); + fs.set_num_pieces((int(fs.total_size()) + piece_size - 1) / piece_size); + + for (auto const idx : fs.piece_range()) + { + piece_picker picker(4, fs.total_size() % 4, fs.num_pieces()); + picker.we_have(idx); + + aux::vector vec; + aux::file_progress fp; + + fp.init(picker, fs); + fp.export_progress(vec); + + std::int64_t sum = 0; + for (file_index_t i(0); i < vec.end_index(); ++i) + sum += vec[i]; + + TEST_EQUAL(sum, fs.piece_size(idx)); + } +} + +TORRENT_TEST(update_simple_sequential) +{ + int const piece_size = 256; + + file_storage fs; + fs.add_file("torrent/1", 100000); + fs.add_file("torrent/2", 100); + fs.add_file("torrent/3", 45000); + fs.set_piece_length(piece_size); + fs.set_num_pieces((int(fs.total_size()) + piece_size - 1) / piece_size); + + piece_picker picker(4, fs.total_size() % 4, fs.num_pieces()); + + aux::file_progress fp; + + fp.init(picker, fs); + + int count = 0; + + for (auto const idx : fs.piece_range()) + { + fp.update(fs, idx, [&](file_index_t const file_index) + { + count++; + + aux::vector vec; + fp.export_progress(vec); + + TEST_EQUAL(vec[file_index], fs.file_size(file_index)); + }); + } + + TEST_EQUAL(count, fs.num_files()); +} diff --git a/test/test_file_storage.cpp b/test/test_file_storage.cpp new file mode 100644 index 0000000..ea86aa0 --- /dev/null +++ b/test/test_file_storage.cpp @@ -0,0 +1,780 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "test.hpp" +#include "setup_transfer.hpp" + +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/path.hpp" + +using namespace lt; + +namespace { + +void setup_test_storage(file_storage& st) +{ + st.add_file(combine_path("test", "a"), 10000); + st.add_file(combine_path("test", "b"), 20000); + st.add_file(combine_path("test", combine_path("c", "a")), 30000); + st.add_file(combine_path("test", combine_path("c", "b")), 40000); + + st.set_piece_length(0x4000); + st.set_num_pieces((int(st.total_size()) + st.piece_length() - 1) / 0x4000); + + TEST_EQUAL(st.file_name(file_index_t{0}), "a"); + TEST_EQUAL(st.file_name(file_index_t{1}), "b"); + TEST_EQUAL(st.file_name(file_index_t{2}), "a"); + TEST_EQUAL(st.file_name(file_index_t{3}), "b"); + TEST_EQUAL(st.name(), "test"); + + TEST_EQUAL(st.file_path(file_index_t{0}), combine_path("test", "a")); + TEST_EQUAL(st.file_path(file_index_t{1}), combine_path("test", "b")); + TEST_EQUAL(st.file_path(file_index_t{2}), combine_path("test", combine_path("c", "a"))); + TEST_EQUAL(st.file_path(file_index_t{3}), combine_path("test", combine_path("c", "b"))); + + TEST_EQUAL(st.file_size(file_index_t{0}), 10000); + TEST_EQUAL(st.file_size(file_index_t{1}), 20000); + TEST_EQUAL(st.file_size(file_index_t{2}), 30000); + TEST_EQUAL(st.file_size(file_index_t{3}), 40000); + + TEST_EQUAL(st.file_offset(file_index_t{0}), 0); + TEST_EQUAL(st.file_offset(file_index_t{1}), 10000); + TEST_EQUAL(st.file_offset(file_index_t{2}), 30000); + TEST_EQUAL(st.file_offset(file_index_t{3}), 60000); + + TEST_EQUAL(st.total_size(), 100000); + TEST_EQUAL(st.piece_length(), 0x4000); + std::printf("%d\n", st.num_pieces()); + TEST_EQUAL(st.num_pieces(), (100000 + 0x3fff) / 0x4000); +} + +} // anonymous namespace + +TORRENT_TEST(coalesce_path) +{ + file_storage st; + st.add_file(combine_path("test", "a"), 10000); + TEST_EQUAL(st.paths().size(), 1); + TEST_EQUAL(st.paths()[0], ""); + st.add_file(combine_path("test", "b"), 20000); + TEST_EQUAL(st.paths().size(), 1); + TEST_EQUAL(st.paths()[0], ""); + st.add_file(combine_path("test", combine_path("c", "a")), 30000); + TEST_EQUAL(st.paths().size(), 2); + TEST_EQUAL(st.paths()[0], ""); + TEST_EQUAL(st.paths()[1], "c"); + + // make sure that two files with the same path shares the path entry + st.add_file(combine_path("test", combine_path("c", "b")), 40000); + TEST_EQUAL(st.paths().size(), 2); + TEST_EQUAL(st.paths()[0], ""); + TEST_EQUAL(st.paths()[1], "c"); + + // cause pad files to be created, to make sure the pad files also share the + // same path entries + st.optimize(0, 1024, true); + + TEST_EQUAL(st.paths().size(), 3); + TEST_EQUAL(st.paths()[0], ""); + TEST_EQUAL(st.paths()[1], "c"); + TEST_EQUAL(st.paths()[2], ".pad"); +} + +TORRENT_TEST(rename_file) +{ + // test rename_file + file_storage st; + setup_test_storage(st); + + st.rename_file(file_index_t{0}, combine_path("test", combine_path("c", "d"))); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), combine_path(".", combine_path("test" + , combine_path("c", "d")))); + TEST_EQUAL(st.file_path(file_index_t{0}, ""), combine_path("test" + , combine_path("c", "d"))); + + // files with absolute paths should ignore the save_path argument + // passed in to file_path() +#ifdef TORRENT_WINDOWS + st.rename_file(file_index_t{0}, "c:\\tmp\\a"); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), "c:\\tmp\\a"); +#else + st.rename_file(file_index_t{0}, "/tmp/a"); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), "/tmp/a"); +#endif + + st.rename_file(file_index_t{0}, combine_path("test__", "a")); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), combine_path(".", combine_path("test__" + , "a"))); +} + +TORRENT_TEST(set_name) +{ + // test set_name. Make sure the name of the torrent is not encoded + // in the paths of each individual file. When changing the name of the + // torrent, the path of the files should change too + file_storage st; + setup_test_storage(st); + + st.set_name("test_2"); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), combine_path(".", combine_path("test_2" + , "a"))); +} + +TORRENT_TEST(rename_file2) +{ + // test rename_file + file_storage st; + st.add_file("a", 10000); + TEST_EQUAL(st.file_path(file_index_t{0}, ""), "a"); + + st.rename_file(file_index_t{0}, combine_path("test", combine_path("c", "d"))); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), combine_path(".", combine_path("test", combine_path("c", "d")))); + TEST_EQUAL(st.file_path(file_index_t{0}, ""), combine_path("test", combine_path("c", "d"))); + +#ifdef TORRENT_WINDOWS + st.rename_file(file_index_t{0}, "c:\\tmp\\a"); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), "c:\\tmp\\a"); + TEST_EQUAL(st.file_path(file_index_t{0}, "c:\\test-1\\test2"), "c:\\tmp\\a"); +#else + st.rename_file(file_index_t{0}, "/tmp/a"); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), "/tmp/a"); + TEST_EQUAL(st.file_path(file_index_t{0}, "/usr/local/temp"), "/tmp/a"); +#endif + + st.rename_file(file_index_t{0}, combine_path("tmp", "a")); + TEST_EQUAL(st.file_path(file_index_t{0}, "."), combine_path("tmp", "a")); +} + +TORRENT_TEST(pointer_offset) +{ + // test applying pointer offset + file_storage st; + char const filename[] = "test1fooba"; + + st.add_file_borrow({filename, 5}, combine_path("test-torrent-1", "test1") + , 10); + + // test filename_ptr and filename_len + TEST_EQUAL(st.file_name_ptr(file_index_t{0}), filename); + TEST_EQUAL(st.file_name_len(file_index_t{0}), 5); + TEST_EQUAL(st.file_name(file_index_t{0}), string_view(filename, 5)); + + TEST_EQUAL(st.file_path(file_index_t{0}, ""), combine_path("test-torrent-1", "test1")); + TEST_EQUAL(st.file_path(file_index_t{0}, "tmp"), combine_path("tmp" + , combine_path("test-torrent-1", "test1"))); + + // apply a pointer offset of 5 bytes. The name of the file should + // change to "fooba". + + st.apply_pointer_offset(5); + + TEST_EQUAL(st.file_path(file_index_t{0}, ""), combine_path("test-torrent-1", "fooba")); + TEST_EQUAL(st.file_path(file_index_t{0}, "tmp"), combine_path("tmp" + , combine_path("test-torrent-1", "fooba"))); + + // test filename_ptr and filename_len + TEST_EQUAL(st.file_name_ptr(file_index_t{0}), filename + 5); + TEST_EQUAL(st.file_name_len(file_index_t{0}), 5); +} + +TORRENT_TEST(invalid_path1) +{ + file_storage st; +#ifdef TORRENT_WINDOWS + st.add_file_borrow({}, R"(+\\\()", 10); +#else + st.add_file_borrow({}, "+///(", 10); +#endif + + TEST_EQUAL(st.file_name(file_index_t{0}), "("); + TEST_EQUAL(st.file_path(file_index_t{0}, ""), combine_path("+", "(")); +} + +TORRENT_TEST(invalid_path2) +{ + file_storage st; +#ifdef TORRENT_WINDOWS + st.add_file_borrow({}, R"(+\\\+\\()", 10); +#else + st.add_file_borrow({}, "+///+//(", 10); +#endif + + TEST_EQUAL(st.file_name(file_index_t{0}), "("); + TEST_EQUAL(st.file_path(file_index_t{0}, ""), combine_path("+", combine_path("+", "("))); +} + +TORRENT_TEST(map_file) +{ + // test map_file + file_storage fs; + fs.set_piece_length(512); + fs.add_file(combine_path("temp_storage", "test1.tmp"), 17); + fs.add_file(combine_path("temp_storage", "test2.tmp"), 612); + fs.add_file(combine_path("temp_storage", "test3.tmp"), 0); + fs.add_file(combine_path("temp_storage", "test4.tmp"), 0); + fs.add_file(combine_path("temp_storage", "test5.tmp"), 3253); + // size: 3882 + fs.add_file(combine_path("temp_storage", "test6.tmp"), 841); + // size: 4723 + + peer_request rq = fs.map_file(file_index_t{0}, 0, 10); + TEST_EQUAL(rq.piece, piece_index_t{0}); + TEST_EQUAL(rq.start, 0); + TEST_EQUAL(rq.length, 10); + rq = fs.map_file(file_index_t{5}, 0, 10); + TEST_EQUAL(rq.piece, piece_index_t{7}); + TEST_EQUAL(rq.start, 298); + TEST_EQUAL(rq.length, 10); + rq = fs.map_file(file_index_t{5}, 0, 1000); + TEST_EQUAL(rq.piece, piece_index_t{7}); + TEST_EQUAL(rq.start, 298); + TEST_EQUAL(rq.length, 841); +} + +TORRENT_TEST(file_path_hash) +{ + // test file_path_hash and path_hash. Make sure we can detect a path + // whose name collides with + file_storage fs; + fs.set_piece_length(512); + fs.add_file(combine_path("temp_storage", "Foo"), 17); + fs.add_file(combine_path("temp_storage", "foo"), 612); + + std::printf("path: %s\n", fs.file_path(file_index_t(0)).c_str()); + std::printf("file: %s\n", fs.file_path(file_index_t(1)).c_str()); + std::uint32_t file_hash0 = fs.file_path_hash(file_index_t(0), "a"); + std::uint32_t file_hash1 = fs.file_path_hash(file_index_t(1), "a"); + TEST_EQUAL(file_hash0, file_hash1); +} + +// make sure that files whose size is a multiple by the alignment are picked +// first, since that lets us keep placing files aligned +TORRENT_TEST(optimize_aligned_sizes) +{ + file_storage fs; + fs.set_piece_length(512); + fs.add_file(combine_path("s", "1"), 1); + fs.add_file(combine_path("s", "2"), 40000); + fs.add_file(combine_path("s", "3"), 1024); + + fs.optimize(512, 512, false); + + // since the size of file 3 is a multiple of the alignment (512), it should + // be prioritized, to minimize the amount of padding. + // after that, we want to pick the largest file (2), and since file 1 is + // smaller than the pad-file limit (512) we won't pad it. Since tail_padding + // is false, we won't pad the tail of the torrent either + + TEST_EQUAL(fs.num_files(), 3); + + TEST_EQUAL(fs.file_size(file_index_t(0)), 1024); + TEST_EQUAL(fs.file_name(file_index_t(0)), "3"); + TEST_EQUAL(fs.pad_file_at(file_index_t(0)), false); + + TEST_EQUAL(fs.file_size(file_index_t(1)), 40000); + TEST_EQUAL(fs.file_name(file_index_t(1)), "2"); + TEST_EQUAL(fs.pad_file_at(file_index_t(1)), false); + + TEST_EQUAL(fs.file_size(file_index_t(2)), 1); + TEST_EQUAL(fs.file_name(file_index_t(2)), "1"); + TEST_EQUAL(fs.pad_file_at(file_index_t(2)), false); +} + +// make sure we pad the end of the torrent when tail_padding is specified +TORRENT_TEST(optimize_tail_padding) +{ + file_storage fs; + fs.set_piece_length(512); + fs.add_file(combine_path("s", "1"), 700); + + fs.optimize(512, 512, true); + + // since the size of file 3 is a multiple of the alignment (512), it should + // be prioritized, to minimize the amount of padding. + // after that, we want to pick the largest file (2), and since file 1 is + // smaller than the pad-file limit (512) we won't pad it. Since tail_padding + // is false, we won't pad the tail of the torrent either + + TEST_EQUAL(fs.num_files(), 2); + + TEST_EQUAL(fs.file_size(file_index_t(0)), 700); + TEST_EQUAL(fs.file_name(file_index_t(0)), "1"); + TEST_EQUAL(fs.pad_file_at(file_index_t(0)), false); + + TEST_EQUAL(fs.file_size(file_index_t(1)), 1024 - 700); + TEST_EQUAL(fs.pad_file_at(file_index_t(1)), true); +} + + +// make sure we fill in padding with small files +TORRENT_TEST(optimize_pad_fillers) +{ + file_storage fs; + fs.set_piece_length(512); + fs.add_file(combine_path("s", "1"), 1); + fs.add_file(combine_path("s", "2"), 1000); + fs.add_file(combine_path("s", "3"), 1001); + + fs.optimize(512, 512, false); + + // first we pick the largest file, then we need to add padding, since file 1 + // is smaller than the pad file limit, it won't be aligned anyway, so we + // place that as part of the padding + + TEST_EQUAL(fs.num_files(), 4); + + TEST_EQUAL(fs.file_size(file_index_t(0)), 1001); + TEST_EQUAL(fs.file_name(file_index_t(0)), "3"); + TEST_EQUAL(fs.pad_file_at(file_index_t(0)), false); + + TEST_EQUAL(fs.file_size(file_index_t(1)), 1); + TEST_EQUAL(fs.file_name(file_index_t(1)), "1"); + TEST_EQUAL(fs.pad_file_at(file_index_t(1)), false); + + TEST_EQUAL(fs.file_size(file_index_t(2)), 1024 - (1001 + 1)); + TEST_EQUAL(fs.pad_file_at(file_index_t(2)), true); + + TEST_EQUAL(fs.file_size(file_index_t(3)), 1000); + TEST_EQUAL(fs.file_name(file_index_t(3)), "2"); + TEST_EQUAL(fs.pad_file_at(file_index_t(3)), false); +} + +TORRENT_TEST(piece_range_exclusive) +{ + int const piece_size = 16; + file_storage fs; + fs.set_piece_length(piece_size); + fs.add_file(combine_path("temp_storage", "0"), piece_size); + fs.add_file(combine_path("temp_storage", "1"), piece_size * 4 + 1); + fs.add_file(combine_path("temp_storage", "2"), piece_size * 4 - 1); + fs.set_num_pieces(int((fs.total_size() + piece_size - 1) / piece_size)); + // +---+---+---+---+---+---+---+---+---+ + // pieces | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | + // +---+---+---+---+---+---+---+---+---+ + // files | 0 | 1 | 2 | + // +---+----------------+--------------+ + + TEST_CHECK(aux::file_piece_range_exclusive(fs, file_index_t(0)) == std::make_tuple(piece_index_t(0), piece_index_t(1))); + TEST_CHECK(aux::file_piece_range_exclusive(fs, file_index_t(1)) == std::make_tuple(piece_index_t(1), piece_index_t(5))); + TEST_CHECK(aux::file_piece_range_exclusive(fs, file_index_t(2)) == std::make_tuple(piece_index_t(6), piece_index_t(9))); +} + +TORRENT_TEST(piece_range_inclusive) +{ + int const piece_size = 16; + file_storage fs; + fs.set_piece_length(piece_size); + fs.add_file(combine_path("temp_storage", "0"), piece_size); + fs.add_file(combine_path("temp_storage", "1"), piece_size * 4 + 1); + fs.add_file(combine_path("temp_storage", "2"), piece_size * 4 - 1); + fs.set_num_pieces(int((fs.total_size() + piece_size - 1) / piece_size)); + // +---+---+---+---+---+---+---+---+---+ + // pieces | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | + // +---+---+---+---+---+---+---+---+---+ + // files | 0 | 1 | 2 | + // +---+----------------+--------------+ + + TEST_CHECK(aux::file_piece_range_inclusive(fs, file_index_t(0)) == std::make_tuple(piece_index_t(0), piece_index_t(1))); + TEST_CHECK(aux::file_piece_range_inclusive(fs, file_index_t(1)) == std::make_tuple(piece_index_t(1), piece_index_t(6))); + TEST_CHECK(aux::file_piece_range_inclusive(fs, file_index_t(2)) == std::make_tuple(piece_index_t(5), piece_index_t(9))); +} + +TORRENT_TEST(piece_range) +{ + int const piece_size = 0x4000; + file_storage fs; + fs.set_piece_length(piece_size); + fs.add_file(combine_path("temp_storage", "0"), piece_size * 3); + fs.add_file(combine_path("temp_storage", "1"), piece_size * 3 + 0x30); + fs.set_num_pieces(int((fs.total_size() + piece_size - 1) / piece_size)); + // +---+---+---+---+---+---+---+ + // pieces | 0 | 1 | 2 | 3 | 4 | 5 | 6 | + // +---+---+---+---+---+---+---+ + // files | 0 | 1 | + // +---+-------+------------+ + + TEST_CHECK(aux::file_piece_range_inclusive(fs, file_index_t(0)) == std::make_tuple(piece_index_t(0), piece_index_t(3))); + TEST_CHECK(aux::file_piece_range_inclusive(fs, file_index_t(1)) == std::make_tuple(piece_index_t(3), piece_index_t(7))); + + TEST_CHECK(aux::file_piece_range_exclusive(fs, file_index_t(0)) == std::make_tuple(piece_index_t(0), piece_index_t(3))); + TEST_CHECK(aux::file_piece_range_exclusive(fs, file_index_t(1)) == std::make_tuple(piece_index_t(3), piece_index_t(7))); +} + +namespace { + +void test_optimize(std::vector file_sizes + , int const alignment + , int const pad_file_limit + , bool const tail_padding + , std::vector const expected_order) +{ + file_storage fs; + int i = 0; + for (int s : file_sizes) + { + fs.add_file(combine_path("test", std::to_string(i++)), s); + } + fs.optimize(pad_file_limit, alignment, tail_padding); + + TEST_EQUAL(fs.num_files(), int(expected_order.size())); + if (fs.num_files() != int(expected_order.size())) return; + + std::cout << "{ "; + for (auto const idx : fs.file_range()) + { + if (fs.file_flags(idx) & file_storage::flag_pad_file) std::cout << "*"; + std::cout << fs.file_size(idx) << " "; + } + std::cout << "}\n"; + + file_index_t idx{0}; + int num_pad_files = 0; + for (int expect : expected_order) + { + if (expect == -1) + { + TEST_CHECK(fs.file_flags(idx) & file_storage::flag_pad_file); + TEST_EQUAL(fs.file_name(idx), std::to_string(num_pad_files++)); + } + else + { + TEST_EQUAL(fs.file_name(idx), std::to_string(expect)); + TEST_EQUAL(fs.file_size(idx), file_sizes[std::size_t(expect)]); + } + ++idx; + } +} + +} // anonymous namespace + +TORRENT_TEST(optimize_order_large_first) +{ + test_optimize({1000, 3000, 10000}, 1024, 1024, false, {2, -1, 1, 0}); +} + +TORRENT_TEST(optimize_tail_padding2) +{ + // when tail padding is enabled, a pad file is added at the end + test_optimize({2000}, 1024, 1024, true, {0, -1}); +} + +TORRENT_TEST(optimize_tail_padding3) +{ + // when tail padding is enabled, a pad file is added at the end, even if the + // file is smaller than the alignment, as long as pad_file_limit is 0 *(which + // means files are aligned unconditionally) + test_optimize({1000}, 1024, 0, true, {0, -1}); +} + +TORRENT_TEST(optimize_tail_padding_small_files) +{ + // files smaller than the pad file limit are not tail-padded + test_optimize({1000, 1, 2}, 1024, 50, true, {0, -1, 2, 1}); +} + +TORRENT_TEST(optimize_tail_padding_small_files2) +{ + // files larger than the pad file limit are not tail-padded + test_optimize({1000, 1, 2}, 1024, 0, true, {0, -1, 2, -1, 1, -1}); +} + +TORRENT_TEST(optimize_prioritize_aligned_size) +{ + // file 0 of size 1024 will be chosen over the larger file, since it won't + // affect the alignment of the next file + test_optimize({1024, 3000, 10}, 1024, 1024, false, {0, 1, 2}); +} + +TORRENT_TEST(optimize_fill_with_small_files) +{ + // fill in space that otherwise would just be a pad file with other small + // files. + test_optimize({2000, 5000, 48, 120}, 1024, 1024, false, {1, 3, 0, 2}); +} + +TORRENT_TEST(optimize_pad_all) +{ + // when pad_size_limit is 0, every file is padded to alignment, regardless of + // how big it is + // the empty file is first, since it doesn't affect alignment of the next + // file + test_optimize({48, 1, 0, 5000}, 1024, 0, false, {2, 3, -1, 0, -1, 1}); +} + +TORRENT_TEST(optimize_pad_all_with_tail) +{ + // when pad_size_limit is 0, every file is padded to alignment, regardless of + // how big it is, also with tail-padding enabled + test_optimize({48, 1, 0, 5000}, 1024, 0, true, {2, 3, -1, 0, -1, 1, -1}); +} + +TORRENT_TEST(piece_size_last_piece) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("0", 100); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + TEST_EQUAL(fs.piece_size(piece_index_t{0}), 100); +} + +TORRENT_TEST(piece_size_middle_piece) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("0", 2000); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + TEST_EQUAL(fs.piece_size(piece_index_t{0}), 1024); + TEST_EQUAL(fs.piece_size(piece_index_t{1}), 2000 - 1024); +} + +TORRENT_TEST(file_index_at_offset) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("test/0", 1); + fs.add_file("test/1", 2); + fs.add_file("test/2", 3); + fs.add_file("test/3", 4); + fs.add_file("test/4", 5); + std::int64_t offset = 0; + for (int f : {0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4}) + { + TEST_EQUAL(fs.file_index_at_offset(offset++), file_index_t{f}); + } +} + +TORRENT_TEST(map_block_start) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("test/0", 1); + fs.add_file("test/1", 2); + fs.add_file("test/2", 3); + fs.add_file("test/3", 4); + fs.add_file("test/4", 5); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + int len = 0; + for (int f : {0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5}) + { + std::vector const map = fs.map_block(piece_index_t{0}, 0, len); + TEST_EQUAL(int(map.size()), f); + file_index_t file_index{0}; + std::int64_t actual_len = 0; + for (auto file : map) + { + TEST_EQUAL(file.file_index, file_index++); + TEST_EQUAL(file.offset, 0); + actual_len += file.size; + } + TEST_EQUAL(actual_len, len); + ++len; + } +} + +TORRENT_TEST(map_block_mid) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("test/0", 1); + fs.add_file("test/1", 2); + fs.add_file("test/2", 3); + fs.add_file("test/3", 4); + fs.add_file("test/4", 5); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + int offset = 0; + for (int f : {0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4}) + { + std::vector const map = fs.map_block(piece_index_t{0}, offset, 1); + TEST_EQUAL(int(map.size()), 1); + auto const& file = map[0]; + TEST_EQUAL(file.file_index, file_index_t{f}); + TEST_CHECK(file.offset <= offset); + TEST_EQUAL(file.size, 1); + ++offset; + } +} + +#ifdef TORRENT_WINDOWS +#define SEP "\\" +#else +#define SEP "/" +#endif + +TORRENT_TEST(sanitize_symlinks) +{ + file_storage fs; + fs.set_piece_length(1024); + + // invalid +#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2) + fs.add_file("test/0", 0, file_storage::flag_symlink, 0, "C:\\invalid\\target\\path"); +#else + fs.add_file("test/0", 0, file_storage::flag_symlink, 0, "/invalid/target/path"); +#endif + + // there is no file with this name, so this is invalid + fs.add_file("test/1", 0, file_storage::flag_symlink, 0, "ZZ"); + + // there is no file with this name, so this is invalid + fs.add_file("test/2", 0, file_storage::flag_symlink, 0, "B" SEP "B" SEP "ZZ"); + + // this should be OK + fs.add_file("test/3", 0, file_storage::flag_symlink, 0, "0"); + + // this should be OK + fs.add_file("test/4", 0, file_storage::flag_symlink, 0, "A"); + + // this is advanced, but OK + fs.add_file("test/5", 0, file_storage::flag_symlink, 0, "4" SEP "B"); + + // this is advanced, but OK + fs.add_file("test/6", 0, file_storage::flag_symlink, 0, "5" SEP "C"); + + // this is not OK + fs.add_file("test/7", 0, file_storage::flag_symlink, 0, "4" SEP "B" SEP "C" SEP "ZZ"); + + // this is the only actual content + fs.add_file("test/A" SEP "B" SEP "C", 10000); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + + fs.sanitize_symlinks(); + + // these were all invalid symlinks, so they're made to point to themselves + TEST_EQUAL(fs.symlink(file_index_t{0}), "test" SEP "0"); + TEST_EQUAL(fs.symlink(file_index_t{1}), "test" SEP "1"); + TEST_EQUAL(fs.symlink(file_index_t{2}), "test" SEP "2"); + + // ok + TEST_EQUAL(fs.symlink(file_index_t{3}), "test" SEP "0"); + TEST_EQUAL(fs.symlink(file_index_t{4}), "test" SEP "A"); + TEST_EQUAL(fs.symlink(file_index_t{5}), "test" SEP "4" SEP "B"); + TEST_EQUAL(fs.symlink(file_index_t{6}), "test" SEP "5" SEP "C"); + + // does not point to a valid file + TEST_EQUAL(fs.symlink(file_index_t{7}), "test" SEP "7"); +} + +TORRENT_TEST(sanitize_symlinks_single_file) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("test", 1); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + + fs.sanitize_symlinks(); + + TEST_EQUAL(fs.file_path(file_index_t{0}), "test"); +} + +TORRENT_TEST(sanitize_symlinks_cascade) +{ + file_storage fs; + fs.set_piece_length(1024); + + fs.add_file("test/0", 0, file_storage::flag_symlink, 0, "1" SEP "ZZ"); + fs.add_file("test/1", 0, file_storage::flag_symlink, 0, "2"); + fs.add_file("test/2", 0, file_storage::flag_symlink, 0, "3"); + fs.add_file("test/3", 0, file_storage::flag_symlink, 0, "4"); + fs.add_file("test/4", 0, file_storage::flag_symlink, 0, "5"); + fs.add_file("test/5", 0, file_storage::flag_symlink, 0, "6"); + fs.add_file("test/6", 0, file_storage::flag_symlink, 0, "7"); + fs.add_file("test/7", 0, file_storage::flag_symlink, 0, "A"); + fs.add_file("test/no-exist", 0, file_storage::flag_symlink, 0, "1" SEP "ZZZ"); + + // this is the only actual content + fs.add_file("test/A" SEP "ZZ", 10000); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + + fs.sanitize_symlinks(); + + TEST_EQUAL(fs.symlink(file_index_t{0}), "test" SEP "1" SEP "ZZ"); + TEST_EQUAL(fs.symlink(file_index_t{1}), "test" SEP "2"); + TEST_EQUAL(fs.symlink(file_index_t{2}), "test" SEP "3"); + TEST_EQUAL(fs.symlink(file_index_t{3}), "test" SEP "4"); + TEST_EQUAL(fs.symlink(file_index_t{4}), "test" SEP "5"); + TEST_EQUAL(fs.symlink(file_index_t{5}), "test" SEP "6"); + TEST_EQUAL(fs.symlink(file_index_t{6}), "test" SEP "7"); + TEST_EQUAL(fs.symlink(file_index_t{7}), "test" SEP "A"); + TEST_EQUAL(fs.symlink(file_index_t{8}), "test" SEP "no-exist"); +} + +TORRENT_TEST(sanitize_symlinks_circular) +{ + file_storage fs; + fs.set_piece_length(1024); + + fs.add_file("test/0", 0, file_storage::flag_symlink, 0, "1"); + fs.add_file("test/1", 0, file_storage::flag_symlink, 0, "0"); + + // when this is resolved, we end up in an infinite loop. Make sure we can + // handle that + fs.add_file("test/2", 0, file_storage::flag_symlink, 0, "0/ZZ"); + + // this is the only actual content + fs.add_file("test/A" SEP "ZZ", 10000); + fs.set_num_pieces(int((fs.total_size() + 1023) / 1024)); + + fs.sanitize_symlinks(); + + TEST_EQUAL(fs.symlink(file_index_t{0}), "test" SEP "1"); + TEST_EQUAL(fs.symlink(file_index_t{1}), "test" SEP "0"); + + // this was invalid, so it points to itself + TEST_EQUAL(fs.symlink(file_index_t{2}), "test" SEP "2"); +} + +TORRENT_TEST(query_symlinks) +{ + file_storage fs; + fs.set_piece_length(1024); + fs.add_file("test/0", 0, file_storage::flag_symlink, 0, "0"); + fs.add_file("test/1", 0, file_storage::flag_symlink, 0, "1"); + fs.add_file("test/2", 0, file_storage::flag_symlink, 0, "2"); + fs.add_file("test/3", 0, file_storage::flag_symlink, 0, "3"); + + auto const& ret1 = fs.symlink(file_index_t{0}); + auto const& ret2 = fs.symlink(file_index_t{1}); + auto const& ret3 = fs.symlink(file_index_t{2}); + auto const& ret4 = fs.symlink(file_index_t{3}); + + TEST_CHECK(ret1 != ret2); + TEST_CHECK(ret1 != ret3); + TEST_CHECK(ret1 != ret4); + TEST_CHECK(ret2 != ret3); + TEST_CHECK(ret2 != ret4); + TEST_CHECK(ret3 != ret4); +} + +// TODO: test file attributes +// TODO: test symlinks +// TODO: test reorder_file (make sure internal_file_entry::swap() is used) diff --git a/test/test_flags.cpp b/test/test_flags.cpp new file mode 100644 index 0000000..829fc8b --- /dev/null +++ b/test/test_flags.cpp @@ -0,0 +1,201 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/path.hpp" +#include "settings.hpp" + +using namespace libtorrent; +namespace lt = libtorrent; + +namespace { + +std::string file(std::string name) +{ + return combine_path(parent_path(current_working_directory()) + , combine_path("test_torrents", name)); +} + +void test_add_and_get_flags(torrent_flags_t const flags) +{ + session ses(settings()); + add_torrent_params p; + p.save_path = "."; + error_code ec; + p.ti = std::make_shared(file("base.torrent"), + std::ref(ec)); + TEST_CHECK(!ec); + p.flags = flags; + const torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); + TEST_EQUAL(h.flags() & flags, flags); +} + +void test_set_after_add(torrent_flags_t const flags) +{ + session ses(settings()); + add_torrent_params p; + p.save_path = "."; + error_code ec; + p.ti = std::make_shared(file("base.torrent"), + std::ref(ec)); + TEST_CHECK(!ec); + p.flags = torrent_flags::all & ~flags; + const torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); + TEST_EQUAL(h.flags() & flags, torrent_flags_t{}); + h.set_flags(flags); + TEST_EQUAL(h.flags() & flags, flags); +} + +void test_unset_after_add(torrent_flags_t const flags) +{ + session ses(settings()); + add_torrent_params p; + p.save_path = "."; + error_code ec; + p.ti = std::make_shared(file("base.torrent"), + std::ref(ec)); + TEST_CHECK(!ec); + p.flags = flags; + const torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); + TEST_EQUAL(h.flags() & flags, flags); + h.unset_flags(flags); + TEST_EQUAL(h.flags() & flags, torrent_flags_t{}); +} + +} // anonymous namespace + +TORRENT_TEST(flag_seed_mode) +{ + // seed-mode (can't be set after adding) + test_add_and_get_flags(torrent_flags::seed_mode); + test_unset_after_add(torrent_flags::seed_mode); +} + +TORRENT_TEST(flag_upload_mode) +{ + // upload-mode + test_add_and_get_flags(torrent_flags::upload_mode); + test_set_after_add(torrent_flags::upload_mode); + test_unset_after_add(torrent_flags::upload_mode); +} + +#ifndef TORRENT_DISABLE_SHARE_MODE +TORRENT_TEST(flag_share_mode) +{ + // share-mode + test_add_and_get_flags(torrent_flags::share_mode); + test_set_after_add(torrent_flags::share_mode); + test_unset_after_add(torrent_flags::share_mode); +} +#endif + +TORRENT_TEST(flag_apply_ip_filter) +{ + // apply-ip-filter + test_add_and_get_flags(torrent_flags::apply_ip_filter); + test_set_after_add(torrent_flags::apply_ip_filter); + test_unset_after_add(torrent_flags::apply_ip_filter); +} + +TORRENT_TEST(flag_paused) +{ + // paused + test_add_and_get_flags(torrent_flags::paused); + // TODO: change to a different test setup. currently always paused. + //test_set_after_add(torrent_flags::paused); + //test_unset_after_add(torrent_flags::paused); +} + +TORRENT_TEST(flag_auto_managed) +{ + // auto-managed + test_add_and_get_flags(torrent_flags::auto_managed); + test_set_after_add(torrent_flags::auto_managed); + test_unset_after_add(torrent_flags::auto_managed); +} + +#ifndef TORRENT_DISABLE_SUPERSEEDING +TORRENT_TEST(flag_super_seeding) +{ + // super-seeding + test_add_and_get_flags(torrent_flags::super_seeding); + test_set_after_add(torrent_flags::super_seeding); + test_unset_after_add(torrent_flags::super_seeding); +} +#endif + +TORRENT_TEST(flag_sequential_download) +{ + // sequential-download + test_add_and_get_flags(torrent_flags::sequential_download); + test_set_after_add(torrent_flags::sequential_download); + test_unset_after_add(torrent_flags::sequential_download); +} + +TORRENT_TEST(flag_stop_when_ready) +{ + // stop-when-ready + test_add_and_get_flags(torrent_flags::stop_when_ready); + // setting stop-when-ready when already stopped has no effect. + // TODO: change to a different test setup. currently always paused. + //test_set_after_add(torrent_flags::stop_when_ready); + test_unset_after_add(torrent_flags::stop_when_ready); +} + +TORRENT_TEST(flag_disable_dht) +{ + test_add_and_get_flags(torrent_flags::disable_dht); + test_set_after_add(torrent_flags::disable_dht); + test_unset_after_add(torrent_flags::disable_dht); +} + + +TORRENT_TEST(flag_disable_lsd) +{ + test_add_and_get_flags(torrent_flags::disable_lsd); + test_set_after_add(torrent_flags::disable_lsd); + test_unset_after_add(torrent_flags::disable_lsd); +} + +TORRENT_TEST(flag_disable_pex) +{ + test_add_and_get_flags(torrent_flags::disable_pex); + test_set_after_add(torrent_flags::disable_pex); + test_unset_after_add(torrent_flags::disable_pex); +} diff --git a/test/test_generate_peer_id.cpp b/test/test_generate_peer_id.cpp new file mode 100644 index 0000000..b32ca70 --- /dev/null +++ b/test/test_generate_peer_id.cpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/generate_peer_id.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/settings_pack.hpp" + +TORRENT_TEST(generate_peer_id) +{ + lt::aux::session_settings sett; + sett.set_str(lt::settings_pack::peer_fingerprint, "abc"); + lt::peer_id const id = lt::aux::generate_peer_id(sett); + + TEST_CHECK(std::equal(id.begin(), id.begin() + 3, "abc")); + TEST_CHECK(!lt::need_encoding(id.data(), int(id.size()))); +} + +TORRENT_TEST(generate_peer_id_truncate) +{ + lt::aux::session_settings sett; + sett.set_str(lt::settings_pack::peer_fingerprint, "abcdefghijklmnopqrstuvwxyz"); + lt::peer_id const id = lt::aux::generate_peer_id(sett); + + TEST_CHECK(std::equal(id.begin(), id.end(), "abcdefghijklmnopqrst")); +} diff --git a/test/test_gzip.cpp b/test/test_gzip.cpp new file mode 100644 index 0000000..bd303bc --- /dev/null +++ b/test/test_gzip.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/gzip.hpp" +#include "setup_transfer.hpp" // for load_file +#include "libtorrent/aux_/path.hpp" // for combine_path + +using namespace lt; + +TORRENT_TEST(zeroes) +{ + std::vector zipped; + error_code ec; + load_file(combine_path("..", "zeroes.gz"), zipped, ec, 1000000); + if (ec) std::printf("failed to open file: (%d) %s\n", ec.value() + , ec.message().c_str()); + TEST_CHECK(!ec); + + std::vector inflated; + inflate_gzip(zipped, inflated, 1000000, ec); + + if (ec) { + std::printf("failed to unzip: %s\n", ec.message().c_str()); + } + TEST_CHECK(!ec); + TEST_CHECK(!inflated.empty()); + for (std::size_t i = 0; i < inflated.size(); ++i) + TEST_EQUAL(inflated[i], 0); +} + +TORRENT_TEST(corrupt) +{ + std::vector zipped; + error_code ec; + load_file(combine_path("..", "corrupt.gz"), zipped, ec, 1000000); + if (ec) std::printf("failed to open file: (%d) %s\n", ec.value() + , ec.message().c_str()); + TEST_CHECK(!ec); + + std::vector inflated; + inflate_gzip(zipped, inflated, 1000000, ec); + + // we expect this to fail + TEST_CHECK(ec); +} + +TORRENT_TEST(invalid1) +{ + std::vector zipped; + error_code ec; + load_file(combine_path("..", "invalid1.gz"), zipped, ec, 1000000); + if (ec) std::printf("failed to open file: (%d) %s\n", ec.value() + , ec.message().c_str()); + TEST_CHECK(!ec); + + std::vector inflated; + inflate_gzip(zipped, inflated, 1000000, ec); + + // we expect this to fail + TEST_CHECK(ec); +} + +TORRENT_TEST(empty) +{ + std::vector empty; + std::vector inflated; + error_code ec; + inflate_gzip(empty, inflated, 1000000, ec); + TEST_CHECK(ec); +} diff --git a/test/test_hasher.cpp b/test/test_hasher.cpp new file mode 100644 index 0000000..9e3fa7b --- /dev/null +++ b/test/test_hasher.cpp @@ -0,0 +1,125 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/hasher.hpp" +#include "libtorrent/hex.hpp" + +#include "test.hpp" + +using namespace lt; + +namespace +{ +// test vectors from RFC 3174 +// http://www.faqs.org/rfcs/rfc3174.html + +char const* test_array[4] = +{ + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "a", + "0123456701234567012345670123456701234567012345670123456701234567" +}; + +long int repeat_count[4] = { 1, 1, 1000000, 10 }; + +char const* result_array[4] = +{ + "A9993E364706816ABA3E25717850C26C9CD0D89D", + "84983E441C3BD26EBAAE4AA1F95129E5E54670F1", + "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F", + "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452" +}; + +void test_vector(std::string s, std::string output, int const n = 1) +{ + hasher h; + for (int i = 0; i < n; i++) + h.update(s); + std::string const digest = h.final().to_string(); + std::string const digest_hex = aux::to_hex(digest); + + TEST_EQUAL(digest_hex, output); + + std::string output_hex = digest_hex; + aux::to_hex(digest, &output_hex[0]); + + TEST_EQUAL(output_hex, digest_hex); +} + +} + +TORRENT_TEST(hasher) +{ + for (int test = 0; test < 4; ++test) + { + hasher h; + for (int i = 0; i < repeat_count[test]; ++i) + h.update(test_array[test], int(std::strlen(test_array[test]))); + + sha1_hash result; + aux::from_hex({result_array[test], 40}, result.data()); + TEST_CHECK(result == h.final()); + } +} + +// http://www.di-mgt.com.au/sha_testvectors.html +TORRENT_TEST(hasher_test_vec1) +{ + test_vector( + "abc" + , "a9993e364706816aba3e25717850c26c9cd0d89d" + ); + + test_vector( + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + , "84983e441c3bd26ebaae4aa1f95129e5e54670f1" + ); + + test_vector( + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhi" + "jklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" + , "a49b2446a02c645bf419f995b67091253a04a259" + ); + + test_vector( + "a" + , "34aa973cd4c4daa4f61eeb2bdbad27316534016f" + , 1000000 + ); + + test_vector( + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno" + , "7789f0c9ef7bfc40d93311143dfbe69e2017f592" + , 16777216 + ); +} diff --git a/test/test_hasher512.cpp b/test/test_hasher512.cpp new file mode 100644 index 0000000..6bbe820 --- /dev/null +++ b/test/test_hasher512.cpp @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_DISABLE_DHT + +#include "libtorrent/hasher512.hpp" +#include "libtorrent/hex.hpp" + +#include "test.hpp" + +using namespace lt; + +namespace +{ + void test_vector(std::string s, std::string output, int const n = 1) + { + hasher512 h; + for (int i = 0; i < n; i++) + h.update(s); + std::string digest = h.final().to_string(); + TEST_EQUAL(aux::to_hex(digest), output); + } +} + +// http://www.di-mgt.com.au/sha_testvectors.html +TORRENT_TEST(hasher512_test_vec1) +{ + test_vector( + "abc" + , "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a" + "2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" + ); + + test_vector( + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + , "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c335" + "96fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" + ); + + test_vector( + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhi" + "jklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" + , "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018" + "501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909" + ); + + test_vector( + "a" + , "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973eb" + "de0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b" + , 1000000 + ); + + test_vector( + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno" + , "b47c933421ea2db149ad6e10fce6c7f93d0752380180ffd7f4629a712134831d" + "77be6091b819ed352c2967a2e2d4fa5050723c9630691f1a05a7281dbe6c1086" + , 16777216 + ); +} + +#endif // TORRENT_DISABLE_DHT diff --git a/test/test_heterogeneous_queue.cpp b/test/test_heterogeneous_queue.cpp new file mode 100644 index 0000000..71032c6 --- /dev/null +++ b/test/test_heterogeneous_queue.cpp @@ -0,0 +1,338 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/heterogeneous_queue.hpp" + +namespace { + +struct A +{ + int a; + explicit A(int a_) : a(a_) {} + A(A&&) noexcept = default; + virtual int type() = 0; + virtual ~A() = default; +}; + +struct B : A +{ + int b; + explicit B(int a_, int b_) : A(a_), b(b_) {} + B(B&&) noexcept = default; + int type() override { return 1; } +}; + +struct C : A +{ + char c[100]; + explicit C(int a_, int c_) : A(a_) + { + memset(c, c_, sizeof(c)); + } + C(C&&) noexcept = default; + int type() override { return 2; } +}; + +struct D +{ + static int instances; + D() { ++instances; } + D(D const&) { ++instances; } + D(D&&) noexcept { ++instances; } + + ~D() { --instances; } +}; + +struct E +{ + explicit E(char const* msg) : string_member(msg) {} + E(E&&) noexcept = default; + std::string string_member; +}; + +int D::instances = 0; + +struct F +{ + explicit F(int f_) + : self(this) + , f(f_) + , constructed(true) + , destructed(false) + , gutted(false) + {} + + F(F const& f_) + : self(this), f(f_.f) + , constructed(f_.constructed) + , destructed(f_.destructed) + , gutted(false) + { + TEST_EQUAL(f_.constructed, true); + TEST_EQUAL(f_.destructed, false); + TEST_EQUAL(f_.gutted, false); + } + + F(F&& f_) noexcept + : self(this) + , f(f_.f) + , constructed(f_.constructed) + , destructed(f_.destructed) + , gutted(f_.gutted) + { + TEST_EQUAL(f_.constructed, true); + TEST_EQUAL(f_.destructed, false); + TEST_EQUAL(f_.gutted, false); + f_.gutted = true; + } + + ~F() + { + TEST_EQUAL(constructed, true); + TEST_EQUAL(destructed, false); + TEST_EQUAL(self, this); + destructed = true; + constructed = false; + } + + void check_invariant() + { + TEST_EQUAL(constructed, true); + TEST_EQUAL(destructed, false); + TEST_EQUAL(gutted, false); + TEST_EQUAL(self, this); + } + + F* self; + int f; + bool constructed; + bool destructed; + bool gutted; +private: + // non-copyable + F& operator=(F const& f); +}; + +struct G : A +{ + G(int base, int v) : A(base), g(v) {} + G(G&&) noexcept = default; + int type() override { return 3; } + std::int64_t g; +}; + +} // anonymous namespace + +// test emplace_back of heterogeneous types +// and retrieval of their pointers +TORRENT_TEST(emplace_back) +{ + using namespace lt; + + heterogeneous_queue q; + q.emplace_back(0, 1); + TEST_EQUAL(q.size(), 1); + q.emplace_back(2, 3); + TEST_EQUAL(q.size(), 2); + q.emplace_back(4, 5); + TEST_EQUAL(q.size(), 3); + q.emplace_back(6, 7); + TEST_EQUAL(q.size(), 4); + q.emplace_back(8, 9); + TEST_EQUAL(q.size(), 5); + q.emplace_back(10, 11); + TEST_EQUAL(q.size(), 6); + + std::vector ptrs; + q.get_pointers(ptrs); + + TEST_EQUAL(int(ptrs.size()), q.size()); + TEST_EQUAL(ptrs[0]->type(), 1); + TEST_EQUAL(ptrs[1]->type(), 1); + TEST_EQUAL(ptrs[2]->type(), 1); + TEST_EQUAL(ptrs[3]->type(), 2); + TEST_EQUAL(ptrs[4]->type(), 2); + TEST_EQUAL(ptrs[5]->type(), 2); + + TEST_EQUAL(static_cast(ptrs[0])->a, 0); + TEST_EQUAL(static_cast(ptrs[0])->b, 1); + + TEST_EQUAL(static_cast(ptrs[1])->a, 2); + TEST_EQUAL(static_cast(ptrs[1])->b, 3); + + TEST_EQUAL(static_cast(ptrs[2])->a, 4); + TEST_EQUAL(static_cast(ptrs[2])->b, 5); + + TEST_EQUAL(static_cast(ptrs[3])->a, 6); + TEST_EQUAL(static_cast(ptrs[3])->c[0], 7); + + TEST_EQUAL(static_cast(ptrs[4])->a, 8); + TEST_EQUAL(static_cast(ptrs[4])->c[0], 9); + + TEST_EQUAL(static_cast(ptrs[5])->a, 10); + TEST_EQUAL(static_cast(ptrs[5])->c[0], 11); +} + +TORRENT_TEST(emplace_back_over_aligned) +{ + using namespace lt; + + heterogeneous_queue q; + q.emplace_back(1, 2); + q.emplace_back(3, 4); + q.emplace_back(5, 6); + + std::vector ptrs; + q.get_pointers(ptrs); + + TEST_EQUAL(int(ptrs.size()), q.size()); + TEST_EQUAL(ptrs.size(), 3); + TEST_EQUAL(ptrs[0]->type(), 3); + TEST_EQUAL(static_cast(ptrs[0])->a, 1); + TEST_EQUAL(static_cast(ptrs[0])->g, 2); + TEST_EQUAL(ptrs[1]->type(), 3); + TEST_EQUAL(static_cast(ptrs[1])->a, 3); + TEST_EQUAL(static_cast(ptrs[1])->g, 4); + TEST_EQUAL(ptrs[2]->type(), 1); + TEST_EQUAL(static_cast(ptrs[2])->a, 5); + TEST_EQUAL(static_cast(ptrs[2])->b, 6); +} + +// test swap +TORRENT_TEST(swap) +{ + using namespace lt; + + heterogeneous_queue q1; + heterogeneous_queue q2; + + q1.emplace_back(0, 1); + q1.emplace_back(2, 3); + q1.emplace_back(4, 5); + TEST_EQUAL(q1.size(), 3); + + q2.emplace_back(6, 7); + q2.emplace_back(8, 9); + TEST_EQUAL(q2.size(), 2); + + std::vector ptrs; + q1.get_pointers(ptrs); + TEST_EQUAL(int(ptrs.size()), q1.size()); + + TEST_EQUAL(ptrs[0]->type(), 1); + TEST_EQUAL(ptrs[1]->type(), 1); + TEST_EQUAL(ptrs[2]->type(), 1); + + q2.get_pointers(ptrs); + TEST_EQUAL(int(ptrs.size()), q2.size()); + + TEST_EQUAL(ptrs[0]->type(), 2); + TEST_EQUAL(ptrs[1]->type(), 2); + + q1.swap(q2); + + q1.get_pointers(ptrs); + TEST_EQUAL(q1.size(), 2); + TEST_EQUAL(int(ptrs.size()), q1.size()); + + TEST_EQUAL(ptrs[0]->type(), 2); + TEST_EQUAL(ptrs[1]->type(), 2); + + q2.get_pointers(ptrs); + TEST_EQUAL(q2.size(), 3); + TEST_EQUAL(int(ptrs.size()), q2.size()); + + TEST_EQUAL(ptrs[0]->type(), 1); + TEST_EQUAL(ptrs[1]->type(), 1); + TEST_EQUAL(ptrs[2]->type(), 1); +} + +// test destruction +TORRENT_TEST(destruction) +{ + using namespace lt; + + heterogeneous_queue q; + TEST_EQUAL(D::instances, 0); + + q.emplace_back(); + TEST_EQUAL(D::instances, 1); + q.emplace_back(); + TEST_EQUAL(D::instances, 2); + q.emplace_back(); + TEST_EQUAL(D::instances, 3); + q.emplace_back(); + TEST_EQUAL(D::instances, 4); + + q.clear(); + + TEST_EQUAL(D::instances, 0); +} + +// test copy/move +TORRENT_TEST(copy_move) +{ + using namespace lt; + + heterogeneous_queue q; + + // make sure the queue has to grow at some point, to exercise its + // copy/move of elements + for (int i = 0; i < 1000; ++i) + q.emplace_back(i); + + std::vector ptrs; + q.get_pointers(ptrs); + + TEST_EQUAL(int(ptrs.size()), 1000); + + for (std::size_t i = 0; i < ptrs.size(); ++i) + { + ptrs[i]->check_invariant(); + TEST_EQUAL(ptrs[i]->f, int(i)); + } + + // destroy all objects, asserting that their invariant still holds + q.clear(); +} + +TORRENT_TEST(nontrivial) +{ + using namespace lt; + + heterogeneous_queue q; + for (int i = 0; i < 10000; ++i) + { + q.emplace_back("testing to allocate non-trivial objects"); + } +} diff --git a/test/test_http_connection.cpp b/test/test_http_connection.cpp new file mode 100644 index 0000000..b16d614 --- /dev/null +++ b/test/test_http_connection.cpp @@ -0,0 +1,260 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" + +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "libtorrent/http_connection.hpp" +#include "libtorrent/resolver.hpp" +#include "libtorrent/random.hpp" + +#include +#include +#include + +using namespace lt; + +namespace { + +io_service ios; +resolver res(ios); + +int connect_handler_called = 0; +int handler_called = 0; +int data_size = 0; +int http_status = 0; +error_code g_error_code; +char data_buffer[4000]; + +void print_http_header(http_parser const& p) +{ + std::cout << time_now_string() << " < " << p.status_code() << " " << p.message() << std::endl; + + for (auto const& i : p.headers()) + { + std::cout << time_now_string() << " < " << i.first << ": " << i.second << std::endl; + } +} + +void http_connect_handler_test(http_connection& c) +{ + ++connect_handler_called; + TEST_CHECK(c.socket().is_open()); + error_code ec; + std::cout << time_now_string() << " connected to: " + << print_endpoint(c.socket().remote_endpoint(ec)) << std::endl; +// this is not necessarily true when using a proxy and proxying hostnames +// TEST_CHECK(c.socket().remote_endpoint(ec).address() == address::from_string("127.0.0.1", ec)); +} + +void http_handler_test(error_code const& ec, http_parser const& parser + , span data, http_connection&) +{ + ++handler_called; + data_size = int(data.size()); + g_error_code = ec; + TORRENT_ASSERT(data.empty() || parser.finished()); + + if (parser.header_finished()) + { + http_status = parser.status_code(); + if (http_status == 200) + { + TEST_CHECK(span(data_buffer, data.size()) == data); + } + } + print_http_header(parser); +} + +void reset_globals() +{ + connect_handler_called = 0; + handler_called = 0; + data_size = 0; + http_status = 0; + g_error_code = error_code(); +} + +void run_test(std::string const& url, int size, int status, int connected + , boost::optional ec, aux::proxy_settings const& ps + , std::string const& auth = std::string()) +{ + reset_globals(); + + std::cout << " ===== TESTING: " << url << " =====" << std::endl; + + std::cout << time_now_string() + << " expecting: size: " << size + << " status: " << status + << " connected: " << connected + << " error: " << (ec?ec->message():"no error") << std::endl; + +#ifdef TORRENT_USE_OPENSSL + ssl::context ssl_ctx(ssl::context::sslv23_client); + ssl_ctx.set_verify_mode(ssl::context::verify_none); +#endif + + std::shared_ptr h = std::make_shared(ios + , res, &::http_handler_test, true, 1024*1024, &::http_connect_handler_test + , http_filter_handler() +#ifdef TORRENT_USE_OPENSSL + , &ssl_ctx +#endif + ); + h->get(url, seconds(5), 0, &ps, 5, "test/user-agent", boost::none, resolver_flags{}, auth); + ios.reset(); + error_code e; + ios.run(e); + if (e) std::cout << time_now_string() << " run failed: " << e.message() << std::endl; + + std::string const n = time_now_string(); + std::cout << n << " connect_handler_called: " << connect_handler_called << std::endl; + std::cout << n << " handler_called: " << handler_called << std::endl; + std::cout << n << " status: " << http_status << std::endl; + std::cout << n << " size: " << data_size << std::endl; + std::cout << n << " expected-size: " << size << std::endl; + std::cout << n << " error_code: " << g_error_code.message() << std::endl; + TEST_CHECK(connect_handler_called == connected); + TEST_CHECK(handler_called == 1); + TEST_CHECK(data_size == size || size == -1); + TEST_CHECK(!ec || g_error_code == *ec); + TEST_CHECK(http_status == status || status == -1); +} + +void write_test_file() +{ + aux::random_bytes(data_buffer); + error_code ec; + file test_file("test_file", open_mode::write_only, ec); + TEST_CHECK(!ec); + if (ec) std::printf("file error: %s\n", ec.message().c_str()); + iovec_t b = { data_buffer, 3216}; + test_file.writev(0, b, ec); + TEST_CHECK(!ec); + if (ec) std::printf("file error: %s\n", ec.message().c_str()); + test_file.close(); +} + +enum suite_flags_t +{ + flag_chunked_encoding = 1, + flag_keepalive = 2 +}; + +void run_suite(std::string const& protocol + , settings_pack::proxy_type_t proxy_type + , int flags = flag_keepalive) +{ + write_test_file(); + + // starting the web server will also generate test_file.gz (from test_file) + // so it has to happen after we write test_file + int port = start_web_server(protocol == "https" + , (flags & flag_chunked_encoding) != 0 + , (flags & flag_keepalive) != 0); + + aux::proxy_settings ps; + ps.hostname = "127.0.0.1"; + ps.username = "testuser"; + ps.password = "testpass"; + ps.type = proxy_type; + + if (ps.type != settings_pack::none) + ps.port = aux::numeric_cast(start_proxy(ps.type)); + + using err = boost::optional; + + char url[256]; + std::snprintf(url, sizeof(url), "%s://127.0.0.1:%d/", protocol.c_str(), port); + std::string url_base(url); + + run_test(url_base + "relative/redirect", 3216, 200, 2, error_code(), ps); + run_test(url_base + "redirect", 3216, 200, 2, error_code(), ps); + // the actual error code for an abort caused by too many + // redirects is a bit unpredictable. under SSL for instance, + // it will be an ungraceful shutdown + run_test(url_base + "infinite_redirect", 0, 301, 6, err(), ps); + run_test(url_base + "test_file", 3216, 200, 1, error_code(), ps); + run_test(url_base + "test_file.gz", 3216, 200, 1, error_code(), ps); + run_test(url_base + "non-existing-file", -1, 404, 1, err(), ps); + run_test(url_base + "password_protected", 3216, 200, 1, error_code(), ps + , "testuser:testpass"); + + // try a very long path + std::string path; + for (int i = 0; i < 6000; ++i) + { + path += static_cast(i % 26) + 'a'; + } + run_test(url_base + path, 0, 404, 1, err(), ps); + + // only run the tests to handle NX_DOMAIN if we have a proper internet + // connection that doesn't inject false DNS responses (like Comcast does) + hostent* h = gethostbyname("non-existent-domain.se"); + std::printf("gethostbyname(\"non-existent-domain.se\") = %p. h_errno = %d\n" + , static_cast(h), h_errno); + if (h == nullptr && h_errno == HOST_NOT_FOUND) + { + // if we have a proxy, we'll be able to connect to it, we will just get an + // error from the proxy saying it failed to connect to the final target + if (protocol == "http" && (ps.type == settings_pack::http || ps.type == settings_pack::http_pw)) + run_test(protocol + "://non-existent-domain.se/non-existing-file", -1, -1, 1, err(), ps); + else + run_test(protocol + "://non-existent-domain.se/non-existing-file", -1, -1, 0, err(), ps); + } + if (ps.type != settings_pack::none) + stop_proxy(ps.port); + stop_web_server(); +} + +} // anonymous namespace + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(no_proxy_ssl) { run_suite("https", settings_pack::none); } +TORRENT_TEST(http_ssl) { run_suite("https", settings_pack::http); } +TORRENT_TEST(http_pw_ssl) { run_suite("https", settings_pack::http_pw); } +TORRENT_TEST(socks5_proxy_ssl) { run_suite("https", settings_pack::socks5); } +TORRENT_TEST(socks5_pw_proxy_ssl) { run_suite("https", settings_pack::socks5_pw); } +#endif // USE_OPENSSL + +TORRENT_TEST(http_proxy) { run_suite("http", settings_pack::http); } +TORRENT_TEST(http__pwproxy) { run_suite("http", settings_pack::http_pw); } +TORRENT_TEST(socks5_proxy) { run_suite("http", settings_pack::socks5); } +TORRENT_TEST(socks5_pw_proxy) { run_suite("http", settings_pack::socks5_pw); } + +TORRENT_TEST(no_keepalive) +{ + run_suite("http", settings_pack::none, 0); +} diff --git a/test/test_http_parser.cpp b/test/test_http_parser.cpp new file mode 100644 index 0000000..8756fc4 --- /dev/null +++ b/test/test_http_parser.cpp @@ -0,0 +1,758 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/http_parser.hpp" +#include "libtorrent/parse_url.hpp" +#include "libtorrent/string_view.hpp" + +#include + +using namespace lt; + +namespace { + +std::tuple feed_bytes(http_parser& parser, string_view str) +{ + std::tuple ret(0, 0, false); + std::tuple prev(0, 0, false); + for (int chunks = 1; chunks < 70; ++chunks) + { + ret = std::make_tuple(0, 0, false); + parser.reset(); + string_view recv_buf; + for (;;) + { + int const chunk_size = std::min(chunks, int(str.size() - recv_buf.size())); + if (chunk_size == 0) break; + recv_buf = str.substr(0, recv_buf.size() + std::size_t(chunk_size)); + int payload, protocol; + bool error = false; + std::tie(payload, protocol) = parser.incoming(recv_buf, error); + std::get<0>(ret) += payload; + std::get<1>(ret) += protocol; + +#ifdef _MSC_VER +#pragma warning(push, 1) +// this ia a buggy diagnostic on msvc +// warning C4834: discarding return value of function with 'nodiscard' attribute +#pragma warning( disable : 4834 ) +#endif + std::get<2>(ret) |= error; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + TORRENT_ASSERT(payload + protocol == chunk_size || std::get<2>(ret)); + } + TEST_CHECK(prev == std::make_tuple(0, 0, false) || ret == prev || std::get<2>(ret)); + if (!std::get<2>(ret)) + { + TEST_EQUAL(std::get<0>(ret) + std::get<1>(ret), int(str.size())); + } + + prev = ret; + } + return ret; +} + +} // anonymous namespace + +TORRENT_TEST(http_parser) +{ + // HTTP request parser + http_parser parser; + std::tuple received; + + received = feed_bytes(parser + , "HTTP/1.1 200 OK\r\n" + "Content-Length: 4\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "test"); + + TEST_CHECK(received == std::make_tuple(4, 64, false)); + TEST_CHECK(parser.finished()); + span body = parser.get_body(); + TEST_CHECK(std::equal(body.begin(), body.end(), "test")); + TEST_CHECK(parser.header("content-type") == "text/plain"); + TEST_CHECK(atoi(parser.header("content-length").c_str()) == 4); + TEST_CHECK(*parser.header_duration("content-length") == lt::seconds32(4)); + TEST_CHECK(parser.header_duration("content-length-x") == boost::none); + + parser.reset(); + + TEST_CHECK(!parser.finished()); + + char const upnp_response[] = + "HTTP/1.1 200 OK\r\n" + "ST:upnp:rootdevice\r\n" + "USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice\r\n" + "Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc\r\n" + "Server: Custom/1.0 UPnP/1.0 Proc/Ver\r\n" + "EXT:\r\n" + "Cache-Control:max-age=180\r\n" + "DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n"; + + received = feed_bytes(parser, upnp_response); + + TEST_CHECK(received == std::make_tuple(0, int(strlen(upnp_response)), false)); + TEST_CHECK(parser.get_body().empty()); + TEST_CHECK(parser.header("st") == "upnp:rootdevice"); + TEST_CHECK(parser.header("location") + == "http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc"); + TEST_CHECK(parser.header("ext") == ""); + TEST_CHECK(parser.header("date") == "Fri, 02 Jan 1970 08:10:38 GMT"); + TEST_EQUAL(parser.connection_close(), false); + + // test connection close + parser.reset(); + + TEST_CHECK(!parser.finished()); + + char const http1_response[] = + "HTTP/1.0 200 OK\r\n" + "Cache-Control: max-age=180\r\n" + "DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n"; + + received = feed_bytes(parser, http1_response); + + TEST_CHECK(received == std::make_tuple(0, int(strlen(http1_response)), false)); + TEST_CHECK(parser.get_body().empty()); + TEST_CHECK(parser.header("date") == "Fri, 02 Jan 1970 08:10:38 GMT"); + TEST_EQUAL(parser.connection_close(), true); + + parser.reset(); + + TEST_CHECK(!parser.finished()); + + char const close_response[] = + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n"; + + received = feed_bytes(parser, close_response); + + TEST_CHECK(received == std::make_tuple(0, int(strlen(close_response)), false)); + TEST_CHECK(parser.get_body().empty()); + TEST_CHECK(parser.header("date") == "Fri, 02 Jan 1970 08:10:38 GMT"); + TEST_EQUAL(parser.connection_close(), true); + + parser.reset(); + + TEST_CHECK(!parser.finished()); + + char const keep_alive_response[] = + "HTTP/1.1 200 OK\r\n" + "Connection: keep-alive\r\n" + "DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n"; + + received = feed_bytes(parser, keep_alive_response); + + TEST_CHECK(received == std::make_tuple(0, int(strlen(keep_alive_response)), false)); + TEST_CHECK(parser.get_body().empty()); + TEST_CHECK(parser.header("date") == "Fri, 02 Jan 1970 08:10:38 GMT"); + TEST_EQUAL(parser.connection_close(), false); + + parser.reset(); + TEST_CHECK(!parser.finished()); + + char const upnp_notify[] = + "NOTIFY * HTTP/1.1\r\n" + "Host:239.255.255.250:1900\r\n" + "NT:urn:schemas-upnp-org:device:MediaServer:1\r\n" + "NTS:ssdp:alive\r\n" + "Location:http://10.0.1.15:2353/upnphost/udhisapi.dll?" + "content=uuid:c17f2c31-d19b-4912-af94-651945c8a84e\r\n" + "USN:uuid:c17f0c32-d1db-4be8-ae94-25f94583026e::urn:schemas-upnp-org:device:MediaServer:1\r\n" + "Cache-Control:max-age=900\r\n" + "Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0\r\n"; + + received = feed_bytes(parser, upnp_notify); + + TEST_CHECK(received == std::make_tuple(0, int(strlen(upnp_notify)), false)); + TEST_CHECK(parser.method() == "notify"); + TEST_CHECK(parser.path() == "*"); + + parser.reset(); + TEST_CHECK(!parser.finished()); + + char const bt_lsd[] = "BT-SEARCH * HTTP/1.1\r\n" + "Host: 239.192.152.143:6771\r\n" + "Port: 6881\r\n" + "Infohash: 12345678901234567890\r\n" + "\r\n"; + + received = feed_bytes(parser, bt_lsd); + + TEST_CHECK(received == std::make_tuple(0, int(strlen(bt_lsd)), false)); + TEST_CHECK(parser.method() == "bt-search"); + TEST_CHECK(parser.path() == "*"); + TEST_CHECK(atoi(parser.header("port").c_str()) == 6881); + TEST_CHECK(parser.header("infohash") == "12345678901234567890"); + + TEST_CHECK(parser.finished()); + + parser.reset(); + TEST_CHECK(!parser.finished()); + + // test chunked encoding + char const chunked_test[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 20\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "4\r\n" + "test\r\n" + "10\r\n" + "0123456789abcdef\r\n" + "0\r\n" + "Test-header: foobar\r\n" + "\r\n"; + + received = feed_bytes(parser, chunked_test); + + std::printf("payload: %d protocol: %d\n", std::get<0>(received), std::get<1>(received)); + TEST_CHECK(received == std::make_tuple(20, int(strlen(chunked_test)) - 20, false)); + TEST_CHECK(parser.finished()); + TEST_CHECK(std::equal(parser.get_body().begin(), parser.get_body().end() + , "4\r\ntest\r\n10\r\n0123456789abcdef")); + TEST_CHECK(parser.header("test-header") == "foobar"); + TEST_CHECK(parser.header("content-type") == "text/plain"); + TEST_CHECK(atoi(parser.header("content-length").c_str()) == 20); + TEST_CHECK(parser.chunked_encoding()); + using chunk_range = std::pair; + std::vector cmp; + cmp.push_back(chunk_range(96, 100)); + cmp.push_back(chunk_range(106, 122)); + TEST_CHECK(cmp == parser.chunks()); + + // make sure we support trackers with incorrect line endings + char const tracker_response[] = + "HTTP/1.1 200 OK\n" + "content-length: 5\n" + "content-type: test/plain\n" + "\n" + "\ntest"; + + received = feed_bytes(parser, tracker_response); + + TEST_CHECK(received == std::make_tuple(5, int(strlen(tracker_response) - 5), false)); + TEST_CHECK(parser.get_body().size() == 5); + + parser.reset(); + + // make sure we support content-range responses + // and that we're case insensitive + char const web_seed_response[] = + "HTTP/1.1 206 OK\n" + "contEnt-rAngE: bYTes 0-4\n" + "conTent-TyPe: test/plain\n" + "\n" + "\ntest"; + + received = feed_bytes(parser, web_seed_response); + + TEST_CHECK(received == std::make_tuple(5, int(strlen(web_seed_response) - 5), false)); + TEST_CHECK(parser.content_range() == (std::pair(0, 4))); + TEST_CHECK(parser.content_length() == 5); + + parser.reset(); + + // test invalid content range + char const invalid_range_response[] = + "HTTP/1.1 206 OK\n" + "content-range: bytes 4-0\n" + "content-type: test/plain\n" + "\n" + "\ntest"; + + received = feed_bytes(parser, invalid_range_response); + + TEST_CHECK(std::get<2>(received) == true); + + parser.reset(); + + // test invalid status line + char const invalid_status_response[] = + "HTTP/1.1 206\n" + "content-range: bytes 4-0\n" + "content-type: test/plain\n" + "\n" + "\ntest"; + + received = feed_bytes(parser, invalid_status_response); + + TEST_CHECK(std::get<2>(received) == true); + + parser.reset(); + + // test invalid status line 2 + char const invalid_status_response2[] = + "HTTP/1.1\n" + "content-range: bytes 4-0\n" + "content-type: test/plain\n" + "\n" + "\ntest"; + + received = feed_bytes(parser, invalid_status_response2); + + TEST_CHECK(std::get<2>(received) == true); + + parser.reset(); + + // make sure we support content-range responses + // and that we're case insensitive + char const one_hundred_response[] = + "HTTP/1.1 100 Continue\n" + "\r\n" + "HTTP/1.1 200 OK\n" + "Content-Length: 4\r\n" + "Content-Type: test/plain\r\n" + "\r\n" + "test"; + + received = feed_bytes(parser, one_hundred_response); + + TEST_CHECK(received == std::make_tuple(4, int(strlen(one_hundred_response) - 4), false)); + TEST_EQUAL(parser.content_length(), 4); + + { + // test chunked encoding parser + char const chunk_header1[] = "f;this is a comment\r\n"; + std::int64_t chunk_size; + int header_size; + bool ret = parser.parse_chunk_header(span(chunk_header1, 10) + , &chunk_size, &header_size); + TEST_EQUAL(ret, false); + ret = parser.parse_chunk_header(span(chunk_header1, sizeof(chunk_header1)) + , &chunk_size, &header_size); + TEST_EQUAL(ret, true); + TEST_EQUAL(chunk_size, 15); + TEST_EQUAL(header_size, sizeof(chunk_header1) - 1); + + char const chunk_header2[] = + "0;this is a comment\r\n" + "test1: foo\r\n" + "test2: bar\r\n" + "\r\n"; + + ret = parser.parse_chunk_header(span(chunk_header2, sizeof(chunk_header2)) + , &chunk_size, &header_size); + TEST_EQUAL(ret, true); + TEST_EQUAL(chunk_size, 0); + TEST_EQUAL(header_size, sizeof(chunk_header2) - 1); + + TEST_EQUAL(parser.headers().find("test1")->second, "foo"); + TEST_EQUAL(parser.headers().find("test2")->second, "bar"); + } + + // test url parsing + + error_code ec; + TEST_CHECK(parse_url_components("http://foo:bar@host.com:80/path/to/file", ec) + == std::make_tuple("http", "foo:bar", "host.com", 80, "/path/to/file")); + + TEST_CHECK(parse_url_components("http://host.com/path/to/file", ec) + == std::make_tuple("http", "", "host.com", -1, "/path/to/file")); + + TEST_CHECK(parse_url_components("ftp://host.com:21/path/to/file", ec) + == std::make_tuple("ftp", "", "host.com", 21, "/path/to/file")); + + TEST_CHECK(parse_url_components("http://host.com/path?foo:bar@foo:", ec) + == std::make_tuple("http", "", "host.com", -1, "/path?foo:bar@foo:")); + + TEST_CHECK(parse_url_components("http://192.168.0.1/path/to/file", ec) + == std::make_tuple("http", "", "192.168.0.1", -1, "/path/to/file")); + + TEST_CHECK(parse_url_components("http://[2001:ff00::1]:42/path/to/file", ec) + == std::make_tuple("http", "", "2001:ff00::1", 42, "/path/to/file")); + + // leading spaces are supposed to be stripped + TEST_CHECK(parse_url_components(" \thttp://[2001:ff00::1]:42/path/to/file", ec) + == std::make_tuple("http", "", "2001:ff00::1", 42, "/path/to/file")); + + parse_url_components("http://[2001:ff00::1:42/path/to/file", ec); + TEST_CHECK(ec == error_code(errors::expected_close_bracket_in_address)); + + parse_url_components("http:/", ec); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + ec.clear(); + + parse_url_components("http:", ec); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + ec.clear(); + + // test split_url + + TEST_CHECK(split_url("http://foo:bar@host.com:80/path/to/file", ec) + == std::make_tuple("http://foo:bar@host.com:80", "/path/to/file")); + + TEST_CHECK(split_url("http://host.com/path/to/file", ec) + == std::make_tuple("http://host.com", "/path/to/file")); + + TEST_CHECK(split_url("ftp://host.com:21/path/to/file", ec) + == std::make_tuple("ftp://host.com:21", "/path/to/file")); + + TEST_CHECK(split_url("http://host.com/path?foo:bar@foo:", ec) + == std::make_tuple("http://host.com", "/path?foo:bar@foo:")); + + TEST_CHECK(split_url("http://192.168.0.1/path/to/file", ec) + == std::make_tuple("http://192.168.0.1", "/path/to/file")); + + TEST_CHECK(split_url("http://[2001:ff00::1]:42/path/to/file", ec) + == std::make_tuple("http://[2001:ff00::1]:42", "/path/to/file")); + + TEST_CHECK(split_url("http://[2001:ff00::1]:42", ec) + == std::make_tuple("http://[2001:ff00::1]:42", "")); + + TEST_CHECK(split_url("bla://[2001:ff00::1]:42/path/to/file", ec) + == std::make_tuple("bla://[2001:ff00::1]:42", "/path/to/file")); + + ec.clear(); + TEST_CHECK(split_url("foo:/[2001:ff00::1]:42/path/to/file", ec) + == std::make_tuple("foo:/[2001:ff00::1]:42/path/to/file", "")); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + + ec.clear(); + TEST_CHECK(split_url("foo:/", ec) + == std::make_tuple("foo:/", "")); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + + ec.clear(); + TEST_CHECK(split_url("//[2001:ff00::1]:42/path/to/file", ec) + == std::make_tuple("//[2001:ff00::1]:42/path/to/file", "")); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + + ec.clear(); + TEST_CHECK(split_url("//host.com/path?foo:bar@foo:", ec) + == std::make_tuple("//host.com/path?foo:bar@foo:", "")); + TEST_CHECK(ec == error_code(errors::unsupported_url_protocol)); + + // test resolve_redirect_location + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "a") + , "http://example.com/a/a"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "c/d/e/") + , "http://example.com/a/c/d/e/"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "../a") + , "http://example.com/a/../a"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "/c") + , "http://example.com/c"); + + TEST_EQUAL(resolve_redirect_location("http://example.com/a/b", "http://test.com/d") + , "http://test.com/d"); + + TEST_EQUAL(resolve_redirect_location("my-custom-scheme://example.com/a/b", "http://test.com/d") + , "http://test.com/d"); + + TEST_EQUAL(resolve_redirect_location("http://example.com", "/d") + , "http://example.com/d"); + + TEST_EQUAL(resolve_redirect_location("http://example.com", "d") + , "http://example.com/d"); + + TEST_EQUAL(resolve_redirect_location("my-custom-scheme://example.com/a/b", "/d") + , "my-custom-scheme://example.com/d"); + + TEST_EQUAL(resolve_redirect_location("my-custom-scheme://example.com/a/b", "c/d") + , "my-custom-scheme://example.com/a/c/d"); + + // if the referrer is invalid, just respond the verbatim location + + TEST_EQUAL(resolve_redirect_location("example.com/a/b", "/c/d") + , "/c/d"); + + // is_ok_status + + TEST_EQUAL(is_ok_status(200), true); + TEST_EQUAL(is_ok_status(206), true); + TEST_EQUAL(is_ok_status(299), false); + TEST_EQUAL(is_ok_status(300), true); + TEST_EQUAL(is_ok_status(399), true); + TEST_EQUAL(is_ok_status(400), false); + TEST_EQUAL(is_ok_status(299), false); + + // is_redirect + + TEST_EQUAL(is_redirect(299), false); + TEST_EQUAL(is_redirect(100), false); + TEST_EQUAL(is_redirect(300), true); + TEST_EQUAL(is_redirect(399), true); + TEST_EQUAL(is_redirect(400), false); +} + +TORRENT_TEST(chunked_encoding) +{ + char const chunked_input[] = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "4\r\ntest\r\n4\r\n1234\r\n10\r\n0123456789abcdef\r\n" + "0\r\n\r\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_EQUAL(strlen(chunked_input), 24 + 94); + TEST_CHECK(received == std::make_tuple(24, 94, false)); + TEST_CHECK(parser.finished()); + + char mutable_buffer[100]; + span body = parser.get_body(); + std::copy(body.begin(), body.end(), mutable_buffer); + body = parser.collapse_chunk_headers({mutable_buffer, body.size()}); + + TEST_CHECK(body == span("test12340123456789abcdef", 24)); +} + +TORRENT_TEST(chunked_encoding_overflow) +{ + char const chunked_input[] = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "7FFFFFFFFFFFFFBF\r\n"; + + http_parser parser; + int payload; + int protocol; + bool error = false; + std::tie(payload, protocol) = parser.incoming(chunked_input, error); + + // it should have encountered an error + TEST_CHECK(error == true); +} + +TORRENT_TEST(invalid_content_length) +{ + char const chunked_input[] = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Length: -45345\r\n" + "\r\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(invalid_chunked) +{ + char const chunked_input[] = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "-53465234545\r\n" + "foobar"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(invalid_content_range_start) +{ + char const chunked_input[] = + "HTTP/1.1 206 OK\n" + "Content-Range: bYTes -3-4\n" + "\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(invalid_content_range_end) +{ + char const chunked_input[] = + "HTTP/1.1 206 OK\n" + "Content-Range: bYTes 3--434\n" + "\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(overflow_content_length) +{ + char const* chunked_input = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 9999999999999999999999999999\r\n" + "\r\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(overflow_content_range_end) +{ + char const* chunked_input = + "HTTP/1.1 206 OK\n" + "Content-Range: bytes 0-999999999999999999999999\n" + "\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(overflow_content_range_begin) +{ + char const* chunked_input = + "HTTP/1.1 206 OK\n" + "Content-Range: bytes 999999999999999999999999-0\n" + "\n"; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, chunked_input); + + TEST_CHECK(std::get<2>(received) == true); +} + +TORRENT_TEST(missing_chunked_header) +{ + char const input[] = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "\n"; + + // make the inpout not be null terminated. If the parser reads off the end, + // address sanitizer will trigger + char chunked_input[sizeof(input)-1]; + std::memcpy(chunked_input, input, sizeof(chunked_input)); + + http_parser parser; + std::tuple const received + = feed_bytes(parser, {chunked_input, sizeof(chunked_input)}); + + TEST_CHECK(std::get<2>(received) == false); +} + +TORRENT_TEST(invalid_chunk_1) +{ + std::uint8_t const invalid_chunked_input[] = { + 0x48, 0x6f, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, // HoTP/1.1 200 OK + 0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0d, // Cont-Length: 20 + 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x2d, 0x4c, 0x65, // Contente: tn + 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x30, // Transfer-Encoding: chunked + 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x65, 0x3a, 0x20, 0x74, 0x6e, 0x0d, 0x0a, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, + 0x67, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x65, 0x64, 0x0d, 0x0a, 0x0d, 0x0d, 0x0a, 0x0d, + 0x0a, 0x0a, 0x2d, 0x38, 0x39, 0x61, 0x62, 0x63, + 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x0d, + 0x0a, 0xd6, 0x0d, 0x0a, 0x54, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0x64, + 0x65, 0x66, 0x0d, 0x0a, 0xd6, 0x0d, 0x0a, 0x54, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0x65, 0x73, 0x74, 0x2d, 0x68, + 0x65, 0x61, 0x64, 0x79, 0x72, 0x3a, 0x20, 0x66, + 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x0d, 0x0a, 0x0d, + 0x0a, 0x00 + }; + + http_parser parser; + std::tuple const received + = feed_bytes(parser, {reinterpret_cast(invalid_chunked_input), sizeof(invalid_chunked_input)}); + + TEST_CHECK(std::get<2>(received) == false); +} + +TORRENT_TEST(invalid_chunk_2) +{ + std::uint8_t const invalid_chunked_input[] = { + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, + 0x20, 0x32, 0x30, 0x30, 0x20, 0x4f, 0x4b, 0x0a, // HTTP/1.1 20x00, OK. + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, // Transfer-Encodin + 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, // g: chunked.Date: + 0x67, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, // Sat, 0x31, Aug 200 + 0x65, 0x64, 0x0a, 0x44, 0x61, 0x74, 0x65, 0x3a, // 2 00:24:0x08, GMT.C + 0x20, 0x53, 0x61, 0x74, 0x2c, 0x20, 0x33, 0x31, // onnection: close + 0x20, 0x41, 0x75, 0x67, 0x20, 0x32, 0x30, 0x30, + 0x32, 0x20, 0x30, 0x30, 0x3a, 0x32, 0x34, 0x3a, + 0x30, 0x38, 0x20, 0x47, 0x4d, 0x54, 0x0a, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x3a, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65, + 0x0a, 0x0a, 0x31, 0x0a, 0x72, 0x0a, 0x30, 0x0a, + 0x0a + }; + + http_parser parser; + feed_bytes(parser, {reinterpret_cast(invalid_chunked_input), sizeof(invalid_chunked_input)}); +} + +TORRENT_TEST(invalid_chunk_3) +{ + std::uint8_t const invalid_chunked_input[] = { + 0x48, 0xff, 0xff, 0xff, 0xfd, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x20, 0x32, 0x30, 0x30, // H....TTP/1.1 200 + 0x20, 0x4f, 0x4b, 0x0a, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x45, 0x6e, 0x63, // OK.Transfer-Enc + 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x65, 0x64, 0x0a, 0x44, // oding: chunked.D + 0x61, 0x74, 0x65, 0x3a, 0x20, 0x53, 0x61, 0x74, 0x2c, 0x20, 0x33, 0x31, 0x20, 0x41, 0x75, 0x67, // ate: Sat, 0x31, Aug + 0x20, 0x32, 0x30, 0x30, 0x32, 0x20, 0x30, 0x30, 0x3a, 0x32, 0x34, 0x3a, 0x30, 0x38, 0x20, 0x47, // 2002 0x00,:0x24,:0x08, G + 0x4d, 0x54, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x63, // MT.Connection: c + 0x6c, 0x6f, 0x73, 0x65, 0x0a, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, // lose..0000000000 + 0x36, 0x34, 0x0a, 0x6c, 0x72, 0x79, 0x6d, 0x68, 0x74, 0x67, 0x6b, 0x7a, 0x71, 0x74, 0x71, 0x71, // 0x64,.lrymhtgkzqtqq + 0x62, 0x75, 0x71, 0x69, 0x66, 0x76, 0x69, 0x74, 0x6b, 0x70, 0x69, 0x63, 0x6f, 0x64, 0x67, 0x77, // buqifvitkpicodgw + 0x6d, 0x69, 0x6a, 0x64, 0x67, 0x76, 0x6b, 0x63, 0x65, 0x78, 0x64, 0x75, 0x71, 0x74, 0x6e, 0x74, // mijdgvkcexduqtnt + 0x6e, 0x66, 0x62, 0x75, 0x64, 0x6d, 0x6e, 0x6e, 0x62, 0x78, 0x72, 0x72, 0x63, 0x78, 0x6e, 0x70, // nfbudmnnbxrrcxnp + 0x66, 0x79, 0x73, 0x6f, 0x74, 0x66, 0x71, 0x7a, 0x63, 0x74, 0x77, 0x75, 0x6d, 0x6a, 0x6e, 0x63, // fysotfqzctwumjnc + 0x6f, 0x71, 0x77, 0x72, 0x63, 0x6d, 0x67, 0x64, 0x6c, 0x78, 0x77, 0x6f, 0x78, 0x6c, 0x64, 0x65, // oqwrcmgdlxwoxlde + 0x6a, 0x76, 0x73, 0x66, 0x63, 0x6b, 0x65, 0x0a, 0x30, 0x0a, 0x0a, 0x0a, // jvsfcke.0... + }; + + http_parser parser; + feed_bytes(parser, {reinterpret_cast(invalid_chunked_input), sizeof(invalid_chunked_input)}); +} diff --git a/test/test_identify_client.cpp b/test/test_identify_client.cpp new file mode 100644 index 0000000..ec3327a --- /dev/null +++ b/test/test_identify_client.cpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/identify_client.hpp" + +using namespace lt; + +TORRENT_TEST(identify_client) +{ + TEST_EQUAL(aux::identify_client_impl(peer_id("-AZ123B-............")), "Azureus 1.2.3.11"); + TEST_EQUAL(aux::identify_client_impl(peer_id("-AZ1230-............")), "Azureus 1.2.3"); + TEST_EQUAL(aux::identify_client_impl(peer_id("S123--..............")), "Shadow 1.2.3"); + TEST_EQUAL(aux::identify_client_impl(peer_id("S\x1\x2\x3....\0...........")), "Shadow 1.2.3"); + TEST_EQUAL(aux::identify_client_impl(peer_id("M1-2-3--............")), "Mainline 1.2.3"); + TEST_EQUAL(aux::identify_client_impl(peer_id("\0\0\0\0\0\0\0\0\0\0\0\0........")), "Generic"); + TEST_EQUAL(aux::identify_client_impl(peer_id("-xx1230-............")), "xx 1.2.3"); +} + diff --git a/test/test_io.cpp b/test/test_io.cpp new file mode 100644 index 0000000..869c10b --- /dev/null +++ b/test/test_io.cpp @@ -0,0 +1,292 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/aux_/io.hpp" +#include "libtorrent/span.hpp" + +using namespace lt::detail; +using namespace lt::aux; +using lt::span; + +TORRENT_TEST(write_uint8) +{ + std::array buf; + buf.fill(0x55); + char* ptr = buf.data(); + write_uint8(0x10, ptr); + TEST_CHECK(ptr == buf.data() + 1); + TEST_CHECK(buf[0] == 0x10); + TEST_CHECK(buf[1] == 0x55); +} + +TORRENT_TEST(write_uint16) +{ + std::array buf; + buf.fill(0x55); + char* ptr = buf.data(); + write_uint16(0x2010, ptr); + TEST_CHECK(ptr == buf.data() + 2); + TEST_CHECK(buf[0] == 0x20); + TEST_CHECK(buf[1] == 0x10); + TEST_CHECK(buf[2] == 0x55); +} + +TORRENT_TEST(write_uint32) +{ + std::array buf; + buf.fill(0x55); + char* ptr = buf.data(); + write_uint32(0x40302010, ptr); + TEST_CHECK(ptr == buf.data() + 4); + TEST_CHECK(buf[0] == 0x40); + TEST_CHECK(buf[1] == 0x30); + TEST_CHECK(buf[2] == 0x20); + TEST_CHECK(buf[3] == 0x10); + TEST_CHECK(buf[4] == 0x55); +} + +TORRENT_TEST(write_int32) +{ + std::array buf; + buf.fill(0x55); + char* ptr = buf.data(); + write_int32(0x40302010, ptr); + TEST_CHECK(ptr == buf.data() + 4); + TEST_CHECK(buf[0] == 0x40); + TEST_CHECK(buf[1] == 0x30); + TEST_CHECK(buf[2] == 0x20); + TEST_CHECK(buf[3] == 0x10); + TEST_CHECK(buf[4] == 0x55); +} + +TORRENT_TEST(write_uint64) +{ + std::array buf; + buf.fill(0x55); + std::uint8_t* ptr = buf.data(); + write_uint64(0x8070605040302010ull, ptr); + TEST_CHECK(ptr == buf.data() + 8); + TEST_CHECK(buf[0] == 0x80); + TEST_CHECK(buf[1] == 0x70); + TEST_CHECK(buf[2] == 0x60); + TEST_CHECK(buf[3] == 0x50); + TEST_CHECK(buf[4] == 0x40); + TEST_CHECK(buf[5] == 0x30); + TEST_CHECK(buf[6] == 0x20); + TEST_CHECK(buf[7] == 0x10); + TEST_CHECK(buf[8] == 0x55); +} + +TORRENT_TEST(read_uint8) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x10; + char const* ptr = buf.data(); + TEST_CHECK(read_uint8(ptr) == 0x10); + TEST_CHECK(ptr == buf.data() + 1); +} + +TORRENT_TEST(read_uint16) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x20; + buf[1] = 0x10; + char const* ptr = buf.data(); + TEST_CHECK(read_uint16(ptr) == 0x2010); + TEST_CHECK(ptr == buf.data() + 2); +} + +TORRENT_TEST(read_uint32) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x40; + buf[1] = 0x30; + buf[2] = 0x20; + buf[3] = 0x10; + char const* ptr = buf.data(); + TEST_CHECK(read_uint32(ptr) == 0x40302010); + TEST_CHECK(ptr == buf.data() + 4); +} + +TORRENT_TEST(read_uint64) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x80; + buf[1] = 0x70; + buf[2] = 0x60; + buf[3] = 0x50; + buf[4] = 0x40; + buf[5] = 0x30; + buf[6] = 0x20; + buf[7] = 0x10; + std::uint8_t const* ptr = buf.data(); + TEST_CHECK(read_uint64(ptr) == 0x8070605040302010ull); + TEST_CHECK(ptr == buf.data() + 8); +} + +TORRENT_TEST(read_int32) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x40; + buf[1] = 0x30; + buf[2] = 0x20; + buf[3] = 0x10; + std::uint8_t const* ptr = buf.data(); + TEST_CHECK(read_int32(ptr) == 0x40302010); + TEST_CHECK(ptr == buf.data() + 4); +} + +TORRENT_TEST(write_uint8_span) +{ + std::array buf; + buf.fill(0x55); + span view(buf); + write_uint8(0x10, view); + TEST_CHECK(view == span(buf).subspan(1)); + TEST_CHECK(buf[0] == 0x10); + TEST_CHECK(buf[1] == 0x55); +} + +TORRENT_TEST(write_uint16_span) +{ + std::array buf; + buf.fill(0x55); + span view(buf); + write_uint16(0x2010, view); + TEST_CHECK(view == span(buf).subspan(2)); + TEST_CHECK(buf[0] == 0x20); + TEST_CHECK(buf[1] == 0x10); + TEST_CHECK(buf[2] == 0x55); +} + +TORRENT_TEST(write_uint32_span) +{ + std::array buf; + buf.fill(0x55); + span view(buf); + write_uint32(0x40302010, view); + TEST_CHECK(view == span(buf).subspan(4)); + TEST_CHECK(buf[0] == 0x40); + TEST_CHECK(buf[1] == 0x30); + TEST_CHECK(buf[2] == 0x20); + TEST_CHECK(buf[3] == 0x10); + TEST_CHECK(buf[4] == 0x55); +} + +TORRENT_TEST(write_uint64_span) +{ + std::array buf; + buf.fill(0x55); + span view(buf); + write_uint64(0x8070605040302010ull, view); + TEST_CHECK(view == span(buf).subspan(8)); + TEST_CHECK(buf[0] == 0x80); + TEST_CHECK(buf[1] == 0x70); + TEST_CHECK(buf[2] == 0x60); + TEST_CHECK(buf[3] == 0x50); + TEST_CHECK(buf[4] == 0x40); + TEST_CHECK(buf[5] == 0x30); + TEST_CHECK(buf[6] == 0x20); + TEST_CHECK(buf[7] == 0x10); + TEST_CHECK(buf[8] == 0x55); +} + +TORRENT_TEST(read_uint8_span) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x10; + span view(buf); + TEST_CHECK(read_uint8(view) == 0x10); + TEST_CHECK(view == span(buf).subspan(1)); +} + +TORRENT_TEST(read_uint16_span) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x20; + buf[1] = 0x10; + span view(buf); + TEST_CHECK(read_uint16(view) == 0x2010); + TEST_CHECK(view == span(buf).subspan(2)); +} + +TORRENT_TEST(read_uint32_span) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x40; + buf[1] = 0x30; + buf[2] = 0x20; + buf[3] = 0x10; + span view(buf); + TEST_CHECK(read_uint32(view) == 0x40302010); + TEST_CHECK(view == span(buf).subspan(4)); +} + +TORRENT_TEST(read_int32_span) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x40; + buf[1] = 0x30; + buf[2] = 0x20; + buf[3] = 0x10; + span view(buf); + TEST_CHECK(read_int32(view) == 0x40302010); + TEST_CHECK(view == span(buf).subspan(4)); +} + +TORRENT_TEST(read_uint64_span) +{ + std::array buf; + buf.fill(0x55); + buf[0] = 0x80; + buf[1] = 0x70; + buf[2] = 0x60; + buf[3] = 0x50; + buf[4] = 0x40; + buf[5] = 0x30; + buf[6] = 0x20; + buf[7] = 0x10; + span view(buf); + TEST_CHECK(read_uint64(view) == 0x8070605040302010ull); + TEST_CHECK(view == span(buf).subspan(8)); +} diff --git a/test/test_ip_filter.cpp b/test/test_ip_filter.cpp new file mode 100644 index 0000000..a143cb1 --- /dev/null +++ b/test/test_ip_filter.cpp @@ -0,0 +1,261 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/ip_filter.hpp" +#include "setup_transfer.hpp" // for addr() +#include + +#include "test.hpp" +#include "settings.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/session.hpp" + +/* + +Currently this test only tests that the filter can handle +IPv4 addresses. Maybe it should be extended to IPv6 as well, +but the actual code is just a template, so it is probably +pretty safe to assume that as long as it works for IPv4 it +also works for IPv6. + +*/ + +using namespace lt; + +template +bool compare(ip_range const& lhs + , ip_range const& rhs) +{ + return lhs.first == rhs.first + && lhs.last == rhs.last + && lhs.flags == rhs.flags; +} + +template +void test_rules_invariant(std::vector> const& r, ip_filter const& f) +{ + TEST_CHECK(!r.empty()); + if (r.empty()) return; + + if (sizeof(r.front().first) == sizeof(address_v4)) + { + TEST_CHECK(r.front().first == addr("0.0.0.0")); + TEST_CHECK(r.back().last == addr("255.255.255.255")); + } + else + { + TEST_CHECK(r.front().first == addr("::0")); + TEST_CHECK(r.back().last == addr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + } + + for (auto i(r.begin()), j(std::next(r.begin())) + , end(r.end()); j != end; ++j, ++i) + { + TEST_EQUAL(f.access(i->last), i->flags); + TEST_EQUAL(f.access(j->first), j->flags); + TEST_CHECK(detail::plus_one(i->last.to_bytes()) == j->first.to_bytes()); + } +} + +TORRENT_TEST(session_get_ip_filter) +{ + session ses(settings()); + ip_filter const& ipf = ses.get_ip_filter(); + TEST_EQUAL(std::get<0>(ipf.export_filter()).size(), 1); +} + +TORRENT_TEST(ip_filter) +{ + std::vector> range; + + // **** test joining of ranges at the end **** + ip_range expected1[] = + { + {addr4("0.0.0.0"), addr4("0.255.255.255"), 0} + , {addr4("1.0.0.0"), addr4("3.0.0.0"), ip_filter::blocked} + , {addr4("3.0.0.1"), addr4("255.255.255.255"), 0} + }; + + { + ip_filter f; + f.add_rule(addr("1.0.0.0"), addr("2.0.0.0"), ip_filter::blocked); + f.add_rule(addr("2.0.0.1"), addr("3.0.0.0"), ip_filter::blocked); + + range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + // **** test joining of ranges at the start **** + + { + ip_filter f; + f.add_rule(addr("2.0.0.1"), addr("3.0.0.0"), ip_filter::blocked); + f.add_rule(addr("1.0.0.0"), addr("2.0.0.0"), ip_filter::blocked); + + range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + + // **** test joining of overlapping ranges at the start **** + + { + ip_filter f; + f.add_rule(addr("2.0.0.1"), addr("3.0.0.0"), ip_filter::blocked); + f.add_rule(addr("1.0.0.0"), addr("2.4.0.0"), ip_filter::blocked); + + range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + + // **** test joining of overlapping ranges at the end **** + + { + ip_filter f; + f.add_rule(addr("1.0.0.0"), addr("2.4.0.0"), ip_filter::blocked); + f.add_rule(addr("2.0.0.1"), addr("3.0.0.0"), ip_filter::blocked); + + range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + TEST_CHECK(std::equal(range.begin(), range.end(), expected1, &compare)); + + } + + + // **** test joining of multiple overlapping ranges 1 **** + + { + ip_filter f; + f.add_rule(addr("1.0.0.0"), addr("2.0.0.0"), ip_filter::blocked); + f.add_rule(addr("3.0.0.0"), addr("4.0.0.0"), ip_filter::blocked); + f.add_rule(addr("5.0.0.0"), addr("6.0.0.0"), ip_filter::blocked); + f.add_rule(addr("7.0.0.0"), addr("8.0.0.0"), ip_filter::blocked); + + f.add_rule(addr("1.0.1.0"), addr("9.0.0.0"), ip_filter::blocked); + + range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + ip_range expected[] = + { + {addr4("0.0.0.0"), addr4("0.255.255.255"), 0} + , {addr4("1.0.0.0"), addr4("9.0.0.0"), ip_filter::blocked} + , {addr4("9.0.0.1"), addr4("255.255.255.255"), 0} + }; + + TEST_CHECK(std::equal(range.begin(), range.end(), expected, &compare)); + + } + + // **** test joining of multiple overlapping ranges 2 **** + + { + ip_filter f; + f.add_rule(addr("1.0.0.0"), addr("2.0.0.0"), ip_filter::blocked); + f.add_rule(addr("3.0.0.0"), addr("4.0.0.0"), ip_filter::blocked); + f.add_rule(addr("5.0.0.0"), addr("6.0.0.0"), ip_filter::blocked); + f.add_rule(addr("7.0.0.0"), addr("8.0.0.0"), ip_filter::blocked); + + f.add_rule(addr("0.0.1.0"), addr("7.0.4.0"), ip_filter::blocked); + + range = std::get<0>(f.export_filter()); + test_rules_invariant(range, f); + + TEST_CHECK(range.size() == 3); + ip_range expected[] = + { + {addr4("0.0.0.0"), addr4("0.0.0.255"), 0} + , {addr4("0.0.1.0"), addr4("8.0.0.0"), ip_filter::blocked} + , {addr4("8.0.0.1"), addr4("255.255.255.255"), 0} + }; + + TEST_CHECK(std::equal(range.begin(), range.end(), expected, &compare)); + + } + + // **** test IPv6 **** + + ip_range expected2[] = + { + {addr6("::0"), addr6("0:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 0} + , {addr6("1::"), addr6("3::"), ip_filter::blocked} + , {addr6("3::1"), addr6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 0} + }; + + { + ip_filter f; + f.add_rule(addr("2::1"), addr("3::"), ip_filter::blocked); + f.add_rule(addr("1::"), addr("2::"), ip_filter::blocked); + + std::vector> rangev6; + rangev6 = std::get<1>(f.export_filter()); + test_rules_invariant(rangev6, f); + + TEST_EQUAL(rangev6.size(), 3); + TEST_CHECK(std::equal(rangev6.begin(), rangev6.end(), expected2, &compare)); + } + + port_filter pf; + + // default constructed port filter should allow any port + TEST_CHECK(pf.access(0) == 0); + TEST_CHECK(pf.access(65535) == 0); + TEST_CHECK(pf.access(6881) == 0); + + // block port 100 - 300 + pf.add_rule(100, 300, port_filter::blocked); + + TEST_CHECK(pf.access(0) == 0); + TEST_CHECK(pf.access(99) == 0); + TEST_CHECK(pf.access(100) == port_filter::blocked); + TEST_CHECK(pf.access(150) == port_filter::blocked); + TEST_CHECK(pf.access(300) == port_filter::blocked); + TEST_CHECK(pf.access(301) == 0); + TEST_CHECK(pf.access(6881) == 0); + TEST_CHECK(pf.access(65535) == 0); +} diff --git a/test/test_ip_voter.cpp b/test/test_ip_voter.cpp new file mode 100644 index 0000000..a18334f --- /dev/null +++ b/test/test_ip_voter.cpp @@ -0,0 +1,222 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6() +#include "setup_transfer.hpp" // for rand_v4 + +using namespace lt; + +namespace { + +bool cast_vote(ip_voter& ipv, address ext_ip, address voter) +{ + bool new_ip = ipv.cast_vote(ext_ip, aux::session_interface::source_dht, voter); + std::printf("%15s -> %-15s\n" + , print_address(voter).c_str() + , print_address(ext_ip).c_str()); + if (new_ip) + { + std::printf(" \x1b[1mnew external IP: %s\x1b[0m\n" + , print_address(ipv.external_address()).c_str()); + } + return new_ip; +} + +} // anonymous namespace + +// test the case where every time we get a new IP. Make sure +// we don't flap +TORRENT_TEST(test_random) +{ + init_rand_address(); + + ip_voter ipv; + + address_v4 addr1(address_v4::from_string("51.41.61.132")); + + bool new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(new_ip); + TEST_CHECK(ipv.external_address() == addr1); + for (int i = 0; i < 1000; ++i) + { + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + TEST_CHECK(!new_ip); + } + TEST_CHECK(ipv.external_address() == addr1); +} + +TORRENT_TEST(two_ips) +{ + init_rand_address(); + + ip_voter ipv; + + address_v4 addr1(address_v4::from_string("51.1.1.1")); + address_v4 addr2(address_v4::from_string("53.3.3.3")); + + // addr1 is the first address we see, which is the one we pick. Even though + // we'll have as many votes for addr2, we shouldn't flap, since addr2 never + // gets an overwhelming majority. + bool new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(new_ip); + for (int i = 0; i < 1000; ++i) + { + new_ip = cast_vote(ipv, addr2, rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(!new_ip); + + TEST_CHECK(ipv.external_address() == addr1); + } +} + +TORRENT_TEST(one_ip) +{ + init_rand_address(); + + ip_voter ipv; + + address_v4 start_addr(address_v4::from_string("93.12.63.174")); + address_v4 addr1(address_v4::from_string("51.1.1.1")); + address_v4 addr2(address_v4::from_string("53.3.3.3")); + + bool new_ip = cast_vote(ipv, start_addr, rand_v4()); + TEST_CHECK(new_ip); + TEST_CHECK(ipv.external_address() != addr1); + TEST_CHECK(ipv.external_address() == start_addr); + for (int i = 0; i < 30; ++i) + { + new_ip = cast_vote(ipv, addr2, rand_v4()); + if (new_ip) break; + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + if (new_ip) break; + new_ip = cast_vote(ipv, addr1, rand_v4()); + if (new_ip) break; + new_ip = cast_vote(ipv, addr1, rand_v4()); + if (new_ip) break; + + } + + TEST_CHECK(ipv.external_address() == addr1); + + for (int i = 0; i < 500; ++i) + { + new_ip = cast_vote(ipv, addr2, rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, rand_v4(), rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(!new_ip); + new_ip = cast_vote(ipv, addr1, rand_v4()); + TEST_CHECK(!new_ip); + } + + TEST_CHECK(ipv.external_address() == addr1); +} + +TORRENT_TEST(ip_voter_1) +{ + init_rand_address(); + + // test external ip voting + ip_voter ipv1; + + // test a single malicious node + // adds 50 legitimate responses from different peers + // and 50 malicious responses from the same peer + error_code ec; + address real_external = address_v4::from_string("5.5.5.5", ec); + TEST_CHECK(!ec); + address malicious = address_v4::from_string("4.4.4.4", ec); + TEST_CHECK(!ec); + for (int i = 0; i < 50; ++i) + { + ipv1.cast_vote(real_external, aux::session_interface::source_dht, rand_v4()); + ipv1.cast_vote(rand_v4(), aux::session_interface::source_dht, malicious); + } + TEST_CHECK(ipv1.external_address() == real_external); +} + +TORRENT_TEST(ip_voter_2) +{ + init_rand_address(); + + ip_voter ipv2,ipv6; + + // test a single malicious node + // adds 50 legitimate responses from different peers + // and 50 consistent malicious responses from the same peer + error_code ec; + address malicious = address_v4::from_string("4.4.4.4", ec); + TEST_CHECK(!ec); + address real_external1 = address_v4::from_string("5.5.5.5", ec); + TEST_CHECK(!ec); + address malicious_external = address_v4::from_string("3.3.3.3", ec); + TEST_CHECK(!ec); + + address malicious2; + address real_external2; + address malicious_external2; + if (supports_ipv6()) + { + malicious2 = address_v6::from_string("2f90::", ec); + TEST_CHECK(!ec); + real_external2 = address_v6::from_string("2f80::", ec); + TEST_CHECK(!ec); + malicious_external2 = address_v6::from_string("2f70::", ec); + TEST_CHECK(!ec); + } + + for (int i = 0; i < 50; ++i) + { + ipv2.cast_vote(real_external1, aux::session_interface::source_dht, rand_v4()); + ipv2.cast_vote(malicious_external, aux::session_interface::source_dht, malicious); + if (supports_ipv6()) + { + ipv6.cast_vote(real_external2, aux::session_interface::source_dht, rand_v6()); + ipv6.cast_vote(malicious_external2, aux::session_interface::source_dht, malicious2); + } + } + TEST_CHECK(ipv2.external_address() == real_external1); + if (supports_ipv6()) + TEST_CHECK(ipv6.external_address() == real_external2); +} + diff --git a/test/test_linked_list.cpp b/test/test_linked_list.cpp new file mode 100644 index 0000000..783e11e --- /dev/null +++ b/test/test_linked_list.cpp @@ -0,0 +1,201 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/linked_list.hpp" + +using namespace lt; + +namespace { + +struct test_node : list_node +{ + explicit test_node(int v) : val(v) {} + int val; +}; + +void compare(linked_list const& list, int* array, int size) +{ + TEST_EQUAL(list.size(), size); + + int idx = 0; + for (test_node const* i = list.front(); i != nullptr; i = i->next, ++idx) + { + TEST_EQUAL(i->val, array[idx]); + } +} + +} // anonymous namespace + +TORRENT_TEST(push_back) +{ + test_node n0(0); + test_node n1(1); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + + int expected[] = { 0, 1 }; + compare(list, expected, 2); +} + +TORRENT_TEST(push_front) +{ + test_node n0(0); + test_node n1(1); + + linked_list list; + + list.push_back(&n1); + list.push_front(&n0); + + int expected[] = { 0, 1 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_begin) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list.erase(&n0); + + int expected[] = { 1, 2 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_end) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list.erase(&n2); + + int expected[] = { 0, 1 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_middle) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list.erase(&n1); + + int expected[] = { 0, 2 }; + compare(list, expected, 2); +} + +TORRENT_TEST(erase_last) +{ + test_node n0(0); + + linked_list list; + + list.push_back(&n0); + + list.erase(&n0); + + int expected[] = { -1 }; + compare(list, expected, 0); + + TEST_CHECK(list.empty()); +} + +TORRENT_TEST(iterate_forward) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list_iterator it = list.iterate(); + TEST_EQUAL(it.get(), &n0); + it.next(); + TEST_EQUAL(it.get(), &n1); + it.next(); + TEST_EQUAL(it.get(), &n2); + it.next(); + TEST_EQUAL(it.get(), static_cast(nullptr)); +} + +TORRENT_TEST(iterate_backward) +{ + test_node n0(0); + test_node n1(1); + test_node n2(2); + + linked_list list; + + list.push_back(&n0); + list.push_back(&n1); + list.push_back(&n2); + + list_iterator it = list.iterate(); + it.next(); + it.next(); + TEST_EQUAL(it.get(), &n2); + it.prev(); + TEST_EQUAL(it.get(), &n1); + it.prev(); + TEST_EQUAL(it.get(), &n0); + it.prev(); + TEST_EQUAL(it.get(), static_cast(nullptr)); +} diff --git a/test/test_listen_socket.cpp b/test/test_listen_socket.cpp new file mode 100644 index 0000000..ebea07d --- /dev/null +++ b/test/test_listen_socket.cpp @@ -0,0 +1,533 @@ +/* + +Copyright (c) 2016, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/string_util.hpp" + +using namespace lt; + +namespace +{ + using tp = aux::transport; + + void test_equal(aux::listen_socket_t const& s, address addr, int port + , std::string dev, tp ssl) + { + TEST_CHECK(s.ssl == ssl); + TEST_EQUAL(s.local_endpoint.address(), addr); + TEST_EQUAL(s.original_port, port); + TEST_EQUAL(s.device, dev); + } + + void test_equal(aux::listen_endpoint_t const& e1, address addr, int port + , std::string dev, tp ssl) + { + TEST_CHECK(e1.ssl == ssl); + TEST_EQUAL(e1.port, port); + TEST_EQUAL(e1.addr, addr); + TEST_EQUAL(e1.device, dev); + } + + ip_interface ifc(char const* ip, char const* device, char const* netmask = nullptr) + { + ip_interface ipi; + ipi.interface_address = address::from_string(ip); + if (netmask) ipi.netmask = address::from_string(netmask); + std::strncpy(ipi.name, device, sizeof(ipi.name) - 1); + return ipi; + } + + ip_interface ifc(char const* ip, char const* device, interface_flags const flags + , char const* netmask = nullptr) + { + ip_interface ipi; + ipi.interface_address = address::from_string(ip); + if (netmask) ipi.netmask = address::from_string(netmask); + std::strncpy(ipi.name, device, sizeof(ipi.name) - 1); + ipi.flags = flags; + return ipi; + } + + ip_route rt(char const* ip, char const* device, char const* gateway) + { + ip_route ret; + ret.destination = address::from_string(ip); + ret.gateway = address::from_string(gateway); + std::strncpy(ret.name, device, sizeof(ret.name) - 1); + ret.name[sizeof(ret.name) - 1] = '\0'; + return ret; + } + + aux::listen_endpoint_t ep(char const* ip, int port + , tp ssl, aux::listen_socket_flags_t const flags) + { + return aux::listen_endpoint_t(address::from_string(ip), port, std::string{} + , ssl, flags); + } + + aux::listen_endpoint_t ep(char const* ip, int port + , tp ssl = tp::plaintext + , std::string device = {}) + { + return aux::listen_endpoint_t(address::from_string(ip), port, device, ssl + , aux::listen_socket_t::accept_incoming); + } + + aux::listen_endpoint_t ep(char const* ip, int port + , std::string device + , tp ssl = tp::plaintext) + { + return aux::listen_endpoint_t(address::from_string(ip), port, device, ssl + , aux::listen_socket_t::accept_incoming); + } + + aux::listen_endpoint_t ep(char const* ip, int port + , std::string device + , aux::listen_socket_flags_t const flags) + { + return aux::listen_endpoint_t(address::from_string(ip), port, device + , tp::plaintext, flags); + } + + aux::listen_endpoint_t ep(char const* ip, int port + , aux::listen_socket_flags_t const flags) + { + return aux::listen_endpoint_t(address::from_string(ip), port, std::string{} + , tp::plaintext, flags); + } + + std::shared_ptr sock(char const* ip, int const port + , int const original_port, char const* device = "") + { + auto s = std::make_shared(); + s->local_endpoint = tcp::endpoint(address::from_string(ip) + , aux::numeric_cast(port)); + s->original_port = original_port; + s->device = device; + return s; + } + + std::shared_ptr sock(char const* ip, int const port, char const* dev) + { return sock(ip, port, port, dev); } + + std::shared_ptr sock(char const* ip, int const port) + { return sock(ip, port, port); } + +} // anonymous namespace + +TORRENT_TEST(partition_listen_sockets_wildcard2specific) +{ + std::vector> sockets = { + sock("0.0.0.0", 6881), sock("4.4.4.4", 6881) + }; + + // remove the wildcard socket and replace it with a specific IP + std::vector eps = { + ep("4.4.4.4", 6881), ep("4.4.4.5", 6881) + }; + + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_EQUAL(eps.size(), 1); + TEST_EQUAL(std::distance(sockets.begin(), remove_iter), 1); + TEST_EQUAL(std::distance(remove_iter, sockets.end()), 1); + test_equal(*sockets.front(), address_v4::from_string("4.4.4.4"), 6881, "", tp::plaintext); + test_equal(*sockets.back(), address_v4(), 6881, "", tp::plaintext); + test_equal(eps.front(), address_v4::from_string("4.4.4.5"), 6881, "", tp::plaintext); +} + +TORRENT_TEST(partition_listen_sockets_port_change) +{ + std::vector> sockets = { + sock("4.4.4.4", 6881), sock("4.4.4.5", 6881) + }; + + // change the ports + std::vector eps = { + ep("4.4.4.4", 6882), ep("4.4.4.5", 6882) + }; + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_CHECK(sockets.begin() == remove_iter); + TEST_EQUAL(eps.size(), 2); +} + +TORRENT_TEST(partition_listen_sockets_device_bound) +{ + std::vector> sockets = { + sock("4.4.4.5", 6881), sock("0.0.0.0", 6881) + }; + + // replace the wildcard socket with a pair of device bound sockets + std::vector eps = { + ep("4.4.4.5", 6881) + , ep("4.4.4.6", 6881, "eth1") + , ep("4.4.4.7", 6881, "eth1") + }; + + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_EQUAL(std::distance(sockets.begin(), remove_iter), 1); + TEST_EQUAL(std::distance(remove_iter, sockets.end()), 1); + test_equal(*sockets.front(), address_v4::from_string("4.4.4.5"), 6881, "", tp::plaintext); + test_equal(*sockets.back(), address_v4(), 6881, "", tp::plaintext); + TEST_EQUAL(eps.size(), 2); +} + +TORRENT_TEST(partition_listen_sockets_device_ip_change) +{ + std::vector> sockets = { + sock("10.10.10.10", 6881, "enp3s0") + , sock("4.4.4.4", 6881, "enp3s0") + }; + + // change the IP of a device bound socket + std::vector eps = { + ep("10.10.10.10", 6881, "enp3s0") + , ep("4.4.4.5", 6881, "enp3s0") + }; + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_EQUAL(std::distance(sockets.begin(), remove_iter), 1); + TEST_EQUAL(std::distance(remove_iter, sockets.end()), 1); + test_equal(*sockets.front(), address_v4::from_string("10.10.10.10"), 6881, "enp3s0", tp::plaintext); + test_equal(*sockets.back(), address_v4::from_string("4.4.4.4"), 6881, "enp3s0", tp::plaintext); + TEST_EQUAL(eps.size(), 1); + test_equal(eps.front(), address_v4::from_string("4.4.4.5"), 6881, "enp3s0", tp::plaintext); +} + +TORRENT_TEST(partition_listen_sockets_original_port) +{ + std::vector> sockets = { + sock("10.10.10.10", 6883, 6881), sock("4.4.4.4", 6883, 6881) + }; + + // make sure all sockets are kept when the actual port is different from the original + std::vector eps = { + ep("10.10.10.10", 6881) + , ep("4.4.4.4", 6881) + }; + + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_CHECK(remove_iter == sockets.end()); + TEST_CHECK(eps.empty()); +} + +TORRENT_TEST(partition_listen_sockets_ssl) +{ + std::vector> sockets = { + sock("10.10.10.10", 6881), sock("4.4.4.4", 6881) + }; + + // add ssl sockets + std::vector eps = { + ep("10.10.10.10", 6881) + , ep("4.4.4.4", 6881) + , ep("10.10.10.10", 6881, tp::ssl) + , ep("4.4.4.4", 6881, tp::ssl) + }; + + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_CHECK(remove_iter == sockets.end()); + TEST_EQUAL(eps.size(), 2); +} + +TORRENT_TEST(partition_listen_sockets_op_ports) +{ + std::vector> sockets = { + sock("10.10.10.10", 6881, 0), sock("4.4.4.4", 6881) + }; + + // replace OS assigned ports with explicit ports + std::vector eps ={ + ep("10.10.10.10", 6882), + ep("4.4.4.4", 6882), + }; + auto remove_iter = aux::partition_listen_sockets(eps, sockets); + TEST_CHECK(remove_iter == sockets.begin()); + TEST_EQUAL(eps.size(), 2); +} + +TORRENT_TEST(expand_devices) +{ + std::vector const ifs = { + ifc("127.0.0.1", "lo", "255.0.0.0") + , ifc("192.168.1.2", "eth0", "255.255.255.0") + , ifc("24.172.48.90", "eth1", "255.255.255.0") + , ifc("::1", "lo", "ffff:ffff:ffff:ffff::") + , ifc("fe80::d250:99ff:fe0c:9b74", "eth0", "ffff:ffff:ffff:ffff::") + , ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0", "ffff:ffff:ffff:ffff::") + }; + + std::vector eps = { + { + address::from_string("127.0.0.1"), + 6881, // port + "", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{} }, + { + address::from_string("192.168.1.2"), + 6881, // port + "", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{} } + }; + + expand_devices(ifs, eps); + + TEST_CHECK((eps == std::vector{ + { + address::from_string("127.0.0.1"), + 6881, // port + "lo", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{}, + address::from_string("255.0.0.0") }, + { + address::from_string("192.168.1.2"), + 6881, // port + "eth0", // device + aux::transport::plaintext, + aux::listen_socket_flags_t{}, + address::from_string("255.255.255.0") }, + })); +} + +TORRENT_TEST(expand_unspecified) +{ + // this causes us to only expand IPv6 addresses on eth0 + std::vector const routes = { + rt("0.0.0.0", "eth0", "1.2.3.4"), + rt("::", "eth0", "1234:5678::1"), + }; + + std::vector const ifs = { + ifc("127.0.0.1", "lo") + , ifc("192.168.1.2", "eth0") + , ifc("24.172.48.90", "eth1") + , ifc("::1", "lo") + , ifc("fe80::d250:99ff:fe0c:9b74", "eth0") + , ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth0") + }; + + aux::listen_socket_flags_t const global = aux::listen_socket_t::accept_incoming + | aux::listen_socket_t::was_expanded; + aux::listen_socket_flags_t const local = aux::listen_socket_t::accept_incoming + | aux::listen_socket_t::was_expanded + | aux::listen_socket_t::local_network; + + auto v4_nossl = ep("0.0.0.0", 6881); + auto v4_ssl = ep("0.0.0.0", 6882, tp::ssl); + auto v4_loopb_nossl= ep("127.0.0.1", 6881, local); + auto v4_loopb_ssl = ep("127.0.0.1", 6882, tp::ssl, local); + auto v4_g1_nossl = ep("192.168.1.2", 6881, global); + auto v4_g1_ssl = ep("192.168.1.2", 6882, tp::ssl, global); + auto v4_g2_nossl = ep("24.172.48.90", 6881, global); + auto v4_g2_ssl = ep("24.172.48.90", 6882, tp::ssl, global); + auto v6_unsp_nossl = ep("::", 6883, global); + auto v6_unsp_ssl = ep("::", 6884, tp::ssl, global); + auto v6_ll_nossl = ep("fe80::d250:99ff:fe0c:9b74", 6883, local); + auto v6_ll_ssl = ep("fe80::d250:99ff:fe0c:9b74", 6884, tp::ssl, local); + auto v6_g_nossl = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6883, global); + auto v6_g_ssl = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6884, tp::ssl, global); + auto v6_loopb_ssl = ep("::1", 6884, tp::ssl, local); + auto v6_loopb_nossl= ep("::1", 6883, local); + + std::vector eps = { + v4_nossl, v4_ssl, v6_unsp_nossl, v6_unsp_ssl + }; + + aux::expand_unspecified_address(ifs, routes, eps); + + TEST_EQUAL(eps.size(), 12); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_g1_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_g1_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_g2_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_g2_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_loopb_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_loopb_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_loopb_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_loopb_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_unsp_nossl) == 0); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_unsp_ssl) == 0); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_nossl) == 0); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_ssl) == 0); + + // test that a user configured endpoint is not duplicated + auto v6_g_nossl_dev = ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 6883, "eth0"); + + eps.clear(); + eps.push_back(v6_unsp_nossl); + eps.push_back(v6_g_nossl_dev); + + aux::expand_unspecified_address(ifs, routes, eps); + + TEST_EQUAL(eps.size(), 3); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl) == 0); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_loopb_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl_dev) == 1); +} + +using eps_t = std::vector; + +auto const global = aux::listen_socket_t::accept_incoming + | aux::listen_socket_t::was_expanded; +auto const local = global | aux::listen_socket_t::local_network; + +TORRENT_TEST(expand_unspecified_no_default) +{ + // even though this route isn't a default route, it's a route for a global + // internet address + std::vector const routes = { + rt("128.0.0.0", "eth0", "128.0.0.0"), + }; + + std::vector const ifs = { ifc("192.168.1.2", "eth0", "255.255.0.0") }; + eps_t eps = { ep("0.0.0.0", 6881) }; + + aux::expand_unspecified_address(ifs, routes, eps); + + TEST_CHECK(eps == eps_t{ ep("192.168.1.2", 6881, global) }); +} + +namespace { + +void test_expand_unspecified_if_flags(interface_flags const flags + , eps_t const& expected) +{ + // even though this route isn't a default route, it's a route for a global + // internet address + std::vector const routes; + + std::vector const ifs = { ifc("192.168.1.2", "eth0", flags) }; + eps_t eps = { ep("0.0.0.0", 6881) }; + aux::expand_unspecified_address(ifs, routes, eps); + TEST_CHECK((eps == expected)); +} + +void test_expand_unspecified_if_address(char const* address, eps_t const& expected) +{ + std::vector const routes; + std::vector const ifs = { ifc(address, "eth0", "255.255.0.0") }; + eps_t eps = { ep("0.0.0.0", 6881) }; + + aux::expand_unspecified_address(ifs, routes, eps); + + TEST_CHECK(eps == expected); +} + +} + +TORRENT_TEST(expand_unspecified_ppp) +{ + test_expand_unspecified_if_flags(if_flags::up | if_flags::pointopoint, eps_t{ ep("192.168.1.2", 6881, global) }); + test_expand_unspecified_if_flags(if_flags::up, eps_t{ ep("192.168.1.2", 6881, local) }); +} + +TORRENT_TEST(expand_unspecified_down_if) +{ + test_expand_unspecified_if_flags({}, eps_t{}); + test_expand_unspecified_if_flags(if_flags::up, eps_t{ ep("192.168.1.2", 6881, local) }); +} + +TORRENT_TEST(expand_unspecified_if_loopback) +{ + test_expand_unspecified_if_flags(if_flags::up | if_flags::loopback, eps_t{ ep("192.168.1.2", 6881, local) }); +} + +TORRENT_TEST(expand_unspecified_global_address) +{ + test_expand_unspecified_if_address("1.2.3.4", eps_t{ ep("1.2.3.4", 6881, global)}); +} + +TORRENT_TEST(expand_unspecified_link_local) +{ + test_expand_unspecified_if_address("169.254.1.2", eps_t{ ep("169.254.1.2", 6881, local)}); +} + +TORRENT_TEST(expand_unspecified_loopback) +{ + test_expand_unspecified_if_address("127.1.1.1", eps_t{ ep("127.1.1.1", 6881, local)}); +} + +namespace { +std::vector to_endpoint(listen_interface_t const& iface + , span const ifs) +{ + std::vector ret; + interface_to_endpoints(iface, aux::listen_socket_t::accept_incoming, ifs, ret); + return ret; +} + +using eps = std::vector; + +listen_interface_t ift(char const* dev, int const port, bool const ssl = false + , bool const local = false) +{ + return {std::string(dev), port, ssl, local}; +} +} +using ls = aux::listen_socket_t; + +TORRENT_TEST(interface_to_endpoint) +{ + TEST_CHECK(to_endpoint(ift("10.0.1.1", 6881), {}) == eps{ep("10.0.1.1", 6881)}); + + + std::vector const ifs = { + // this is a global IPv4 address, not a private network + ifc("185.0.1.2", "eth0") + , ifc("192.168.2.2", "eth1") + , ifc("fe80::d250:99ff:fe0c:9b74", "eth0") + // this is a global IPv6 address, not a private network + , ifc("2601:646:c600:a3:d250:99ff:fe0c:9b74", "eth1") + }; + + TEST_CHECK((to_endpoint(ift("eth0", 1234), ifs) + == eps{ep("185.0.1.2", 1234, "eth0", ls::was_expanded | ls::accept_incoming) + , ep("fe80::d250:99ff:fe0c:9b74", 1234, "eth0", ls::was_expanded | ls::accept_incoming | ls::local_network)})); + + TEST_CHECK((to_endpoint(ift("eth1", 1234), ifs) + == eps{ep("192.168.2.2", 1234, "eth1", ls::was_expanded | ls::accept_incoming) + , ep("2601:646:c600:a3:d250:99ff:fe0c:9b74", 1234, "eth1", ls::was_expanded | ls::accept_incoming)})); + + std::vector const ifs2 = { + ifc("10.0.1.1", "eth0") + }; + + TEST_CHECK((to_endpoint(ift("eth0", 1234), ifs2) + == eps{ep("10.0.1.1", 1234, "eth0", ls::was_expanded | ls::accept_incoming)})); +} + diff --git a/test/test_lsd.cpp b/test/test_lsd.cpp new file mode 100644 index 0000000..18444e2 --- /dev/null +++ b/test/test_lsd.cpp @@ -0,0 +1,126 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/aux_/path.hpp" +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include + +namespace { + +void test_lsd() +{ + using namespace lt; + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert_category::error + | alert_category::session_log + | alert_category::torrent_log + | alert_category::peer_log + | alert_category::ip_block + | alert_category::status); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, true); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48100"); +#if TORRENT_ABI_VERSION == 1 + pack.set_bool(settings_pack::rate_limit_utp, true); +#endif + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49100"); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + using std::ignore; + std::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, nullptr, true, false, false, "_lsd" + , 16 * 1024, nullptr, false, nullptr, false); + + for (int i = 0; i < 30; ++i) + { + print_alerts(ses1, "ses1"); + print_alerts(ses2, "ses2"); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + print_ses_rate(float(i), &st1, &st2); + + if (st2.is_seeding /*&& st3.is_seeding*/) break; + std::this_thread::sleep_for(lt::milliseconds(1000)); + } + + TEST_CHECK(tor2.status().is_seeding); + + if (tor2.status().is_seeding) std::cout << "done\n"; + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +} // anonymous namespace + +TORRENT_TEST(lsd) +{ + using namespace lt; + + // in case the previous run was terminated + error_code ec; + remove_all("./tmp1_lsd", ec); + remove_all("./tmp2_lsd", ec); + remove_all("./tmp3_lsd", ec); + + test_lsd(); + + remove_all("./tmp1_lsd", ec); + remove_all("./tmp2_lsd", ec); + remove_all("./tmp3_lsd", ec); +} + + + diff --git a/test/test_magnet.cpp b/test/test_magnet.cpp new file mode 100644 index 0000000..2b40411 --- /dev/null +++ b/test/test_magnet.cpp @@ -0,0 +1,609 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/magnet_uri.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" // for announce_entry +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "settings.hpp" + +using namespace lt; + +#if TORRENT_ABI_VERSION == 1 +namespace { +void test_remove_url(std::string url) +{ + lt::session s(settings()); + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.url = url; + p.save_path = "."; + torrent_handle h = s.add_torrent(p); + std::vector handles = s.get_torrents(); + TEST_EQUAL(handles.size(), 1); + + TEST_NOTHROW(s.remove_torrent(h)); + + handles = s.get_torrents(); + TEST_EQUAL(handles.size(), 0); +} +} // anonymous namespace + +TORRENT_TEST(remove_url) +{ + test_remove_url("magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567"); +} + +TORRENT_TEST(remove_url2) +{ + test_remove_url("http://non-existent.com/test.torrent"); +} +#endif + +TORRENT_TEST(magnet) +{ + session_proxy p1; + session_proxy p2; + + // test session state load/restore + settings_pack pack = settings(); + pack.set_str(settings_pack::user_agent, "test"); + pack.set_int(settings_pack::tracker_receive_timeout, 1234); + pack.set_int(settings_pack::file_pool_size, 543); + pack.set_int(settings_pack::urlseed_wait_retry, 74); + pack.set_int(settings_pack::initial_picker_threshold, 351); + pack.set_bool(settings_pack::coalesce_writes, true); + pack.set_bool(settings_pack::close_redundant_connections, false); + pack.set_int(settings_pack::auto_scrape_interval, 235); + pack.set_int(settings_pack::auto_scrape_min_interval, 62); + std::unique_ptr s(new lt::session(pack)); + + TEST_EQUAL(pack.get_str(settings_pack::user_agent), "test"); + TEST_EQUAL(pack.get_int(settings_pack::tracker_receive_timeout), 1234); + +#ifndef TORRENT_DISABLE_DHT + dht::dht_settings dhts; + dhts.max_peers_reply = 70; + s->set_dht_settings(dhts); +#endif +/* +#ifndef TORRENT_DISABLE_DHT + dht_settings dht_sett; + s->set_dht_settings(dht_sett); +#endif +*/ + entry session_state; + s->save_state(session_state); + + // test magnet link parsing + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&tr=http://1" + "&tr=http://2" + "&tr=http://3" + "&tr=http://3" + "&dn=foo" + "&dht=127.0.0.1:43"); + + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.save_path = "."; + + error_code ec; + torrent_handle t = s->add_torrent(p, ec); + TEST_CHECK(!ec); + if (ec) std::printf("%s\n", ec.message().c_str()); + + std::vector trackers = t.trackers(); + TEST_EQUAL(trackers.size(), 3); + std::set trackers_set; + for (std::vector::iterator i = trackers.begin() + , end(trackers.end()); i != end; ++i) + trackers_set.insert(i->url); + + TEST_CHECK(trackers_set.count("http://1") == 1); + TEST_CHECK(trackers_set.count("http://2") == 1); + TEST_CHECK(trackers_set.count("http://3") == 1); + + p = parse_magnet_uri("magnet:" + "?tr=http://1" + "&tr=http://2" + "&dn=foo" + "&dht=127.0.0.1:43" + "&xt=urn:ed2k:a0a9277894123b27945224fbac8366c9" + "&xt=urn:btih:c352cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.save_path = "."; + torrent_handle t2 = s->add_torrent(p, ec); + TEST_CHECK(!ec); + if (ec) std::printf("%s\n", ec.message().c_str()); + + trackers = t2.trackers(); + TEST_EQUAL(trackers.size(), 2); + TEST_EQUAL(trackers[0].tier, 0); + TEST_EQUAL(trackers[1].tier, 1); + + p = parse_magnet_uri("magnet:" + "?tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80" + "&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80" + "&tr=udp%3A%2F%2Ftracker.ccc.de%3A80" + "&xt=urn:btih:a38d02c287893842a32825aa866e00828a318f07" + "&dn=Ubuntu+11.04+%28Final%29"); + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.save_path = "."; + torrent_handle t3 = s->add_torrent(p, ec); + TEST_CHECK(!ec); + if (ec) std::printf("%s\n", ec.message().c_str()); + + trackers = t3.trackers(); + TEST_EQUAL(trackers.size(), 3); + if (trackers.size() > 0) + { + TEST_EQUAL(trackers[0].url, "udp://tracker.openbittorrent.com:80"); + TEST_EQUAL(trackers[0].tier, 0); + std::printf("1: %s\n", trackers[0].url.c_str()); + } + if (trackers.size() > 1) + { + TEST_EQUAL(trackers[1].url, "udp://tracker.publicbt.com:80"); + TEST_EQUAL(trackers[1].tier, 1); + std::printf("2: %s\n", trackers[1].url.c_str()); + } + if (trackers.size() > 2) + { + TEST_EQUAL(trackers[2].url, "udp://tracker.ccc.de:80"); + TEST_EQUAL(trackers[2].tier, 2); + std::printf("3: %s\n", trackers[2].url.c_str()); + } + + sha1_hash const ih = t.info_hash(); + TEST_EQUAL(aux::to_hex(ih), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + + p1 = s->abort(); + s.reset(new lt::session(settings())); + + std::vector buf; + bencode(std::back_inserter(buf), session_state); + bdecode_node session_state2; + int ret = bdecode(&buf[0], &buf[0] + buf.size(), session_state2, ec); + TEST_CHECK(ret == 0); + + std::printf("session_state\n%s\n", print_entry(session_state2).c_str()); + + // make sure settings that haven't been changed from their defaults are not saved + TEST_CHECK(!session_state2.dict_find("settings") + .dict_find("optimistic_disk_retry")); + + s->load_state(session_state2); + +#define CMP_SET(x) std::printf(#x ": %d %d\n"\ + , s->get_settings().get_int(settings_pack:: x)\ + , pack.get_int(settings_pack:: x)); \ + TEST_EQUAL(s->get_settings().get_int(settings_pack:: x), pack.get_int(settings_pack:: x)) + + CMP_SET(tracker_receive_timeout); + CMP_SET(file_pool_size); + CMP_SET(urlseed_wait_retry); + CMP_SET(initial_picker_threshold); + CMP_SET(auto_scrape_interval); + CMP_SET(auto_scrape_min_interval); + p2 = s->abort(); +} + +TORRENT_TEST(parse_escaped_hash_parameter) +{ + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn%3Abtih%3Acdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_EQUAL(aux::to_hex(p.info_hash), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); +} + +TORRENT_TEST(parse_escaped_hash_parameter_in_hex) +{ + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc%64"); + TEST_EQUAL(aux::to_hex(p.info_hash), "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); +} + +TORRENT_TEST(parse_invalid_escaped_hash_parameter) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn%%3A", ec); + TEST_EQUAL(ec, error_code(errors::invalid_escaped_string)); +} + +TORRENT_TEST(throwing_overload) +{ + TEST_THROW(parse_magnet_uri("magnet:?xt=urn%%3A")); +} + +TORRENT_TEST(parse_missing_hash) +{ + // parse_magnet_uri + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?dn=foo&dht=127.0.0.1:43", ec); + TEST_EQUAL(ec, error_code(errors::missing_info_hash_in_uri)); +} + +TORRENT_TEST(parse_base32_hash) +{ + // parse_magnet_uri + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:MFRGCYTBMJQWEYLCMFRGCYTBMJQWEYLC"); + TEST_EQUAL(p.info_hash, sha1_hash("abababababababababab")); +} + +TORRENT_TEST(parse_web_seeds) +{ + // parse_magnet_uri + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&ws=http://foo.com/bar&ws=http://bar.com/foo"); + TEST_EQUAL(p.url_seeds.size(), 2); + TEST_EQUAL(p.url_seeds[0], "http://foo.com/bar"); + TEST_EQUAL(p.url_seeds[1], "http://bar.com/foo"); +} + +TORRENT_TEST(parse_missing_hash2) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=blah&dn=foo&dht=127.0.0.1:43", ec); + TEST_EQUAL(ec, error_code(errors::missing_info_hash_in_uri)); +} + +TORRENT_TEST(parse_short_hash) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abababab", ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); +} + +TORRENT_TEST(parse_long_hash) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:ababababababababababab", ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); +} + +TORRENT_TEST(parse_space_hash) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih: abababababababababab", ec); + TEST_EQUAL(ec, error_code(errors::invalid_info_hash)); +} + +TORRENT_TEST(parse_peer) +{ + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&x.pe=127.0.0.1:43&x.pe=&x.pe=:100&x.pe=[::1]:45"); + TEST_EQUAL(p.peers.size(), 2); + TEST_EQUAL(p.peers[0], ep("127.0.0.1", 43)); + TEST_EQUAL(p.peers[1], ep("::1", 45)); +} + +#ifndef TORRENT_DISABLE_DHT +TORRENT_TEST(parse_dht_node) +{ + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&dht=127.0.0.1:43&dht=10.0.0.1:1337"); + + TEST_EQUAL(p.dht_nodes.size(), 2); + TEST_EQUAL(p.dht_nodes[0].first, "127.0.0.1"); + TEST_EQUAL(p.dht_nodes[0].second, 43); + + TEST_EQUAL(p.dht_nodes[1].first, "10.0.0.1"); + TEST_EQUAL(p.dht_nodes[1].second, 1337); +} +#endif + +TORRENT_TEST(make_magnet_uri) +{ + // make_magnet_uri + entry info; + info["pieces"] = "aaaaaaaaaaaaaaaaaaaa"; + info["name"] = "slightly shorter name, it's kind of sad that people started " + "the trend of incorrectly encoding the regular name field and then adding " + "another one with correct encoding"; + info["name.utf-8"] = "this is a long ass name in order to try to make " + "make_magnet_uri overflow and hopefully crash. Although, by the time " + "you read this that particular bug should have been fixed"; + info["piece length"] = 16 * 1024; + info["length"] = 3245; + entry torrent; + torrent["info"] = info; + entry::list_type& al1 = torrent["announce-list"].list(); + al1.push_back(entry::list_type()); + entry::list_type& al = al1.back().list(); + al.push_back(entry("http://bigtorrent.org:2710/announce")); + al.push_back(entry("http://bt.careland.com.cn:6969/announce")); + al.push_back(entry("http://bt.e-burg.org:2710/announce")); + al.push_back(entry("http://bttrack.9you.com/announce")); + al.push_back(entry("http://coppersurfer.tk:6969/announce")); + al.push_back(entry("http://erdgeist.org/arts/software/opentracker/announce")); + al.push_back(entry("http://exodus.desync.com/announce")); + al.push_back(entry("http://fr33dom.h33t.com:3310/announce")); + al.push_back(entry("http://genesis.1337x.org:1337/announce")); + al.push_back(entry("http://inferno.demonoid.me:3390/announce")); + al.push_back(entry("http://inferno.demonoid.ph:3390/announce")); + al.push_back(entry("http://ipv6.tracker.harry.lu/announce")); + al.push_back(entry("http://lnxroot.com:6969/announce")); + al.push_back(entry("http://nemesis.1337x.org/announce")); + al.push_back(entry("http://puto.me:6969/announce")); + al.push_back(entry("http://sline.net:2710/announce")); + al.push_back(entry("http://tracker.beeimg.com:6969/announce")); + al.push_back(entry("http://tracker.ccc.de/announce")); + al.push_back(entry("http://tracker.coppersurfer.tk/announce")); + al.push_back(entry("http://tracker.coppersurfer.tk:6969/announce")); + al.push_back(entry("http://tracker.cpleft.com:2710/announce")); + al.push_back(entry("http://tracker.istole.it/announce")); + al.push_back(entry("http://tracker.kamyu.net/announce")); + al.push_back(entry("http://tracker.novalayer.org:6969/announce")); + al.push_back(entry("http://tracker.torrent.to:2710/announce")); + al.push_back(entry("http://tracker.torrentbay.to:6969/announce")); + al.push_back(entry("udp://tracker.openbittorrent.com:80")); + al.push_back(entry("udp://tracker.publicbt.com:80")); + + std::vector buf; + bencode(std::back_inserter(buf), torrent); + buf.push_back('\0'); + std::printf("%s\n", &buf[0]); + error_code ec; + torrent_info ti(buf, ec, from_span); + + TEST_EQUAL(al.size(), ti.trackers().size()); + + std::string magnet = make_magnet_uri(ti); + std::printf("%s len: %d\n", magnet.c_str(), int(magnet.size())); +} + +TORRENT_TEST(make_magnet_uri2) +{ + // make_magnet_uri + entry info; + info["pieces"] = "aaaaaaaaaaaaaaaaaaaa"; + info["name"] = "test"; + info["name.utf-8"] = "test"; + info["piece length"] = 16 * 1024; + info["length"] = 3245; + entry torrent; + torrent["info"] = info; + + torrent["url-list"] = "http://foo.com/bar"; + + std::vector buf; + bencode(std::back_inserter(buf), torrent); + buf.push_back('\0'); + std::printf("%s\n", &buf[0]); + error_code ec; + torrent_info ti(buf, ec, from_span); + + std::string magnet = make_magnet_uri(ti); + std::printf("%s len: %d\n", magnet.c_str(), int(magnet.size())); + TEST_CHECK(magnet.find("&ws=http%3a%2f%2ffoo.com%2fbar") != std::string::npos); +} + +TORRENT_TEST(trailing_whitespace) +{ + session ses(settings()); + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", ec); + p.save_path = "."; + // invalid hash + TEST_CHECK(ec); + TEST_THROW(ses.add_torrent(p)); + + ec.clear(); + p = parse_magnet_uri("magnet:?xt=urn:btih:abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + p.save_path = "."; + // now it's valid, because there's no trailing whitespace + torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); +} + +// These tests don't work because we don't hand out an incomplete torrent_info +// object. To make them work we would either have to set the correct metadata in +// the test, or change the behavior to make `h.torrent_file()` return the +// internal torrent_info object unconditionally +/* +TORRENT_TEST(preserve_trackers) +{ + session ses(settings()); + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&tr=https://test.com/announce", ec); + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); + TEST_CHECK(h.torrent_file()->trackers().size() == 1); + TEST_CHECK(h.torrent_file()->trackers().at(0).url == "https://test.com/announce"); +} + +TORRENT_TEST(preserve_web_seeds) +{ + session ses(settings()); + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&ws=https://test.com/test", ec); + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); + TEST_CHECK(h.torrent_file()->web_seeds().size() == 1); + TEST_CHECK(h.torrent_file()->web_seeds().at(0).url == "https://test.com/test"); +} + +TORRENT_TEST(preserve_dht_nodes) +{ + session ses(settings()); + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&dht=test:1234", ec); + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + TEST_CHECK(h.is_valid()); + TEST_CHECK(h.torrent_file()->nodes().size() == 1); + TEST_CHECK(h.torrent_file()->nodes().at(0).first == "test"); + TEST_CHECK(h.torrent_file()->nodes().at(0).second == 1234); +} +*/ +TORRENT_TEST(invalid_tracker_escaping) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?tr=udp%3A%2F%2Ftracker.openjnt.com%\xf7" + "A80&tr=udp%3A%2F%2Ftracker.pub.ciltbcom%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&xt=urn:btih:a38d02c287893842a39737aa866e00828aA80&xt=urn:buntu+11.04+%28Final%29" + , ec); + TEST_CHECK(ec); +} + +TORRENT_TEST(invalid_web_seed_escaping) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?ws=udp%3A%2F%2Ftracker.openjnt.com%\xf7" "A80", ec); + TEST_CHECK(ec); +} + +TORRENT_TEST(invalid_trackers) +{ + error_code ec; + add_torrent_params p = parse_magnet_uri("magnet:?tr=", ec); + TEST_CHECK(p.trackers.empty()); +} + + +namespace { + +auto const yes = default_priority; +auto const no = dont_download; + +void test_select_only(string_view uri, std::vector expected) +{ + add_torrent_params p = parse_magnet_uri(uri); + TEST_CHECK(p.file_priorities == expected); +} + +} // anonymous namespace + +TORRENT_TEST(parse_magnet_select_only) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=0,2,4,6-8" + , {yes, no, yes, no, yes, no, yes, yes, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_overlap_range) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=0,2-4,3-5&dht=10.0.0.1:1337" + , {yes, no, yes, yes, yes, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_multiple) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=2-4&dht=10.0.0.1:1337&so=1" + , {no, yes, yes, yes, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_inverted_range) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=7-4,100000000&dht=10.0.0.1:1337&so=10" + , {no, no, no, no, no, no, no, no, no, no, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_index_bounds) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=100000000&dht=10.0.0.1:1337&so=10" + , {no, no, no, no, no, no, no, no, no, no, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_range1) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=-4&so=1", {no, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_range2) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=3-&so=1", {no, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_index_character) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=a&so=1", {no, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_index_value) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=100000000&so=1", {no, yes}); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_no_value) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=&dht=10.0.0.1:1337&so=", {}); +} + +TORRENT_TEST(parse_magnet_select_only_invalid_no_values) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=&dht=10.0.0.1:1337&so=,,1", {no, yes}); +} + + +TORRENT_TEST(parse_magnet_select_only_invalid_quotes) +{ + test_select_only("magnet:?xt=urn:btih:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + "&dn=foo&so=\"1,2\"", {}); +} + +TORRENT_TEST(magnet_tr_x_uri) +{ + add_torrent_params p = parse_magnet_uri("magnet:" + "?tr.0=udp://1" + "&tr.1=http://2" + "&tr=http://3" + "&xt=urn:btih:c352cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_CHECK((p.trackers == std::vector{ + "udp://1", "http://2", "http://3"})); + + TEST_CHECK((p.tracker_tiers == std::vector{0, 1, 2 })); + + p = parse_magnet_uri("magnet:" + "?tr.a=udp://1" + "&tr.1=http://2" + "&xt=urn:btih:c352cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"); + TEST_CHECK((p.trackers == std::vector{"http://2" })); + TEST_CHECK((p.tracker_tiers == std::vector{0})); +} diff --git a/test/test_merkle.cpp b/test/test_merkle.cpp new file mode 100644 index 0000000..1b635ed --- /dev/null +++ b/test/test_merkle.cpp @@ -0,0 +1,107 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/merkle.hpp" + +TORRENT_TEST(merkle) +{ + using namespace lt; + + // test merkle_*() functions + + // this is the structure: + // 0 + // 1 2 + // 3 4 5 6 + // 7 8 9 10 11 12 13 14 + // num_leafs = 8 + + TEST_EQUAL(merkle_num_leafs(1), 1); + TEST_EQUAL(merkle_num_leafs(2), 2); + TEST_EQUAL(merkle_num_leafs(3), 4); + TEST_EQUAL(merkle_num_leafs(4), 4); + TEST_EQUAL(merkle_num_leafs(5), 8); + TEST_EQUAL(merkle_num_leafs(6), 8); + TEST_EQUAL(merkle_num_leafs(7), 8); + TEST_EQUAL(merkle_num_leafs(8), 8); + TEST_EQUAL(merkle_num_leafs(9), 16); + TEST_EQUAL(merkle_num_leafs(10), 16); + TEST_EQUAL(merkle_num_leafs(11), 16); + TEST_EQUAL(merkle_num_leafs(12), 16); + TEST_EQUAL(merkle_num_leafs(13), 16); + TEST_EQUAL(merkle_num_leafs(14), 16); + TEST_EQUAL(merkle_num_leafs(15), 16); + TEST_EQUAL(merkle_num_leafs(16), 16); + TEST_EQUAL(merkle_num_leafs(17), 32); + TEST_EQUAL(merkle_num_leafs(18), 32); + + // parents + TEST_EQUAL(merkle_get_parent(1), 0); + TEST_EQUAL(merkle_get_parent(2), 0); + TEST_EQUAL(merkle_get_parent(3), 1); + TEST_EQUAL(merkle_get_parent(4), 1); + TEST_EQUAL(merkle_get_parent(5), 2); + TEST_EQUAL(merkle_get_parent(6), 2); + TEST_EQUAL(merkle_get_parent(7), 3); + TEST_EQUAL(merkle_get_parent(8), 3); + TEST_EQUAL(merkle_get_parent(9), 4); + TEST_EQUAL(merkle_get_parent(10), 4); + TEST_EQUAL(merkle_get_parent(11), 5); + TEST_EQUAL(merkle_get_parent(12), 5); + TEST_EQUAL(merkle_get_parent(13), 6); + TEST_EQUAL(merkle_get_parent(14), 6); + + // siblings + TEST_EQUAL(merkle_get_sibling(1), 2); + TEST_EQUAL(merkle_get_sibling(2), 1); + TEST_EQUAL(merkle_get_sibling(3), 4); + TEST_EQUAL(merkle_get_sibling(4), 3); + TEST_EQUAL(merkle_get_sibling(5), 6); + TEST_EQUAL(merkle_get_sibling(6), 5); + TEST_EQUAL(merkle_get_sibling(7), 8); + TEST_EQUAL(merkle_get_sibling(8), 7); + TEST_EQUAL(merkle_get_sibling(9), 10); + TEST_EQUAL(merkle_get_sibling(10), 9); + TEST_EQUAL(merkle_get_sibling(11), 12); + TEST_EQUAL(merkle_get_sibling(12), 11); + TEST_EQUAL(merkle_get_sibling(13), 14); + TEST_EQUAL(merkle_get_sibling(14), 13); + + // total number of nodes given the number of leafs + TEST_EQUAL(merkle_num_nodes(1), 1); + TEST_EQUAL(merkle_num_nodes(2), 3); + TEST_EQUAL(merkle_num_nodes(4), 7); + TEST_EQUAL(merkle_num_nodes(8), 15); + TEST_EQUAL(merkle_num_nodes(16), 31); +} + diff --git a/test/test_packet_buffer.cpp b/test/test_packet_buffer.cpp new file mode 100644 index 0000000..6fa7cb4 --- /dev/null +++ b/test/test_packet_buffer.cpp @@ -0,0 +1,185 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/packet_buffer.hpp" +#include "libtorrent/packet_pool.hpp" + +using lt::packet_buffer; +using lt::packet_ptr; +using lt::packet_pool; +using lt::packet; + +namespace { + +packet_ptr make_pkt(packet_pool& pool, int const val) +{ + packet_ptr ret = pool.acquire(20); + *reinterpret_cast(ret->buf) = std::uint8_t(val); + return ret; +} + +int get_val(packet* pkt) +{ + TORRENT_ASSERT(pkt != nullptr); + return *reinterpret_cast(pkt->buf); +} + +} // anonymous namespace + +// test packet_buffer +TORRENT_TEST(insert) +{ + packet_pool pool; + packet_buffer pb; + + TEST_EQUAL(pb.capacity(), 0); + TEST_EQUAL(pb.size(), 0); + TEST_EQUAL(pb.span(), 0); + + pb.insert(123, make_pkt(pool, 123)); + TEST_CHECK(pb.at(123 + 16) == nullptr); + + TEST_EQUAL(get_val(pb.at(123)), 123); + TEST_CHECK(pb.capacity() > 0); + TEST_EQUAL(pb.size(), 1); + TEST_EQUAL(pb.span(), 1); + TEST_EQUAL(pb.cursor(), 123); + + pb.insert(125, make_pkt(pool, 125)); + + TEST_EQUAL(get_val(pb.at(125)), 125); + TEST_EQUAL(pb.size(), 2); + TEST_EQUAL(pb.span(), 3); + TEST_EQUAL(pb.cursor(), 123); + + pb.insert(500, make_pkt(pool, 4)); + TEST_EQUAL(pb.size(), 3); + TEST_EQUAL(pb.span(), 501 - 123); + TEST_EQUAL(pb.capacity(), 512); + + pb.insert(500, make_pkt(pool, 5)); + TEST_EQUAL(pb.size(), 3); + TEST_EQUAL(get_val(pb.insert(500, make_pkt(pool, 4)).get()), 5); + TEST_EQUAL(pb.size(), 3); + + TEST_EQUAL(get_val(pb.remove(123).get()), 123); + TEST_EQUAL(pb.size(), 2); + TEST_EQUAL(pb.span(), 501 - 125); + TEST_EQUAL(pb.cursor(), 125); + TEST_EQUAL(get_val(pb.remove(125).get()), 125); + TEST_EQUAL(pb.size(), 1); + TEST_EQUAL(pb.span(), 1); + TEST_EQUAL(pb.cursor(), 500); + + TEST_EQUAL(get_val(pb.remove(500).get()), 4); + TEST_EQUAL(pb.size(), 0); + TEST_EQUAL(pb.span(), 0); + + for (int i = 0; i < 0xff; ++i) + { + int index = (i + 0xfff0) & 0xffff; + pb.insert(packet_buffer::index_type(index), make_pkt(pool, index + 1)); + std::printf("insert: %u (mask: %x)\n", index, int(pb.capacity() - 1)); + TEST_EQUAL(pb.capacity(), 512); + if (i >= 14) + { + index = (index - 14) & 0xffff; + std::printf("remove: %u\n", index); + TEST_EQUAL(get_val(pb.remove(packet_buffer::index_type(index)).get()), std::uint8_t(index + 1)); + TEST_EQUAL(pb.size(), 14); + } + } +} + +TORRENT_TEST(wrap) +{ + // test wrapping the indices + packet_pool pool; + packet_buffer pb; + + TEST_EQUAL(pb.size(), 0); + + pb.insert(0xfffe, make_pkt(pool, 1)); + TEST_EQUAL(get_val(pb.at(0xfffe)), 1); + + pb.insert(2, make_pkt(pool, 2)); + TEST_EQUAL(get_val(pb.at(2)), 2); + + pb.remove(0xfffe); + TEST_CHECK(pb.at(0xfffe) == nullptr); + TEST_EQUAL(get_val(pb.at(2)), 2); +} + +TORRENT_TEST(wrap2) +{ + // test wrapping the indices + packet_pool pool; + packet_buffer pb; + + TEST_EQUAL(pb.size(), 0); + + pb.insert(0xfff3, make_pkt(pool, 1)); + TEST_EQUAL(get_val(pb.at(0xfff3)), 1); + + auto const new_index = packet_buffer::index_type((0xfff3 + pb.capacity()) & 0xffff); + pb.insert(new_index, make_pkt(pool, 2)); + TEST_EQUAL(get_val(pb.at(new_index)), 2); + + packet_ptr old = pb.remove(0xfff3); + TEST_CHECK(get_val(old.get()) == 1); + TEST_CHECK(pb.at(0xfff3) == nullptr); + TEST_EQUAL(get_val(pb.at(new_index)), 2); +} + +TORRENT_TEST(reverse_wrap) +{ + // test wrapping the indices backwards + packet_pool pool; + packet_buffer pb; + + TEST_EQUAL(pb.size(), 0); + + pb.insert(0xfff3, make_pkt(pool, 1)); + TEST_EQUAL(get_val(pb.at(0xfff3)), 1); + + auto const new_index = packet_buffer::index_type((0xfff3 + pb.capacity()) & 0xffff); + pb.insert(new_index, make_pkt(pool, 2)); + TEST_EQUAL(get_val(pb.at(new_index)), 2); + + packet_ptr old = pb.remove(0xfff3); + TEST_CHECK(get_val(old.get()) == 1); + TEST_CHECK(pb.at(0xfff3) == nullptr); + TEST_EQUAL(get_val(pb.at(new_index)), 2); + + pb.insert(0xffff, make_pkt(pool, 3)); +} diff --git a/test/test_part_file.cpp b/test/test_part_file.cpp new file mode 100644 index 0000000..8843a07 --- /dev/null +++ b/test/test_part_file.cpp @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "test.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/error_code.hpp" + +using namespace lt; + +TORRENT_TEST(part_file) +{ + error_code ec; + std::string cwd = complete("."); + + remove_all(combine_path(cwd, "partfile_test_dir"), ec); + if (ec) std::printf("remove_all: %s\n", ec.message().c_str()); + remove_all(combine_path(cwd, "partfile_test_dir2"), ec); + if (ec) std::printf("remove_all: %s\n", ec.message().c_str()); + + int piece_size = 16 * 0x4000; + std::array buf; + + { + create_directory(combine_path(cwd, "partfile_test_dir"), ec); + if (ec) std::printf("create_directory: %s\n", ec.message().c_str()); + create_directory(combine_path(cwd, "partfile_test_dir2"), ec); + if (ec) std::printf("create_directory: %s\n", ec.message().c_str()); + + part_file pf(combine_path(cwd, "partfile_test_dir"), "partfile.parts", 100, piece_size); + pf.flush_metadata(ec); + if (ec) std::printf("flush_metadata: %s\n", ec.message().c_str()); + + // since we don't have anything in the part file, it will have + // not have been created yet + + TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts"))); + + // write something to the metadata file + for (int i = 0; i < 1024; ++i) buf[std::size_t(i)] = char(i & 0xff); + + iovec_t v = buf; + pf.writev(v, piece_index_t(10), 0, ec); + if (ec) std::printf("part_file::writev: %s\n", ec.message().c_str()); + + pf.flush_metadata(ec); + if (ec) std::printf("flush_metadata: %s\n", ec.message().c_str()); + + // now wwe should have created the partfile + TEST_CHECK(exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts"))); + + pf.move_partfile(combine_path(cwd, "partfile_test_dir2"), ec); + TEST_CHECK(!ec); + if (ec) std::printf("move_partfile: %s\n", ec.message().c_str()); + + TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts"))); + TEST_CHECK(exists(combine_path(combine_path(cwd, "partfile_test_dir2"), "partfile.parts"))); + + buf.fill(0); + + pf.readv(v, piece_index_t(10), 0, ec); + if (ec) std::printf("part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[std::size_t(i)] == char(i)); + } + + { + // load the part file back in + part_file pf(combine_path(cwd, "partfile_test_dir2"), "partfile.parts", 100, piece_size); + + buf.fill(0); + + iovec_t v = buf; + pf.readv(v, piece_index_t(10), 0, ec); + if (ec) std::printf("part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[std::size_t(i)] == static_cast(i)); + + // test exporting the piece to a file + + std::string output_filename = combine_path(combine_path(cwd, "partfile_test_dir") + , "part_file_test_export"); + + pf.export_file([](std::int64_t file_offset, span buf_data) + { + for (char i : buf_data) + { + // make sure we got the bytes we expected + TEST_CHECK(i == static_cast(file_offset)); + ++file_offset; + } + }, 10 * piece_size, 1024, ec); + if (ec) std::printf("export_file: %s\n", ec.message().c_str()); + + pf.free_piece(piece_index_t(10)); + + pf.flush_metadata(ec); + if (ec) std::printf("flush_metadata: %s\n", ec.message().c_str()); + + // we just removed the last piece. The partfile does not + // contain anything anymore, it should have deleted itself + TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir2"), "partfile.parts"), ec)); + TEST_CHECK(!ec); + if (ec) std::printf("exists: %s\n", ec.message().c_str()); + } +} diff --git a/test/test_pe_crypto.cpp b/test/test_pe_crypto.cpp new file mode 100644 index 0000000..4aee4a8 --- /dev/null +++ b/test/test_pe_crypto.cpp @@ -0,0 +1,160 @@ +/* + +Copyright (c) 2007, Un Shyam +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "libtorrent/hasher.hpp" +#include "libtorrent/pe_crypto.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/span.hpp" + +#include "test.hpp" + +#if !defined TORRENT_DISABLE_ENCRYPTION + +namespace { + +void test_enc_handler(lt::crypto_plugin& a, lt::crypto_plugin& b) +{ + int const repcount = 128; + for (int rep = 0; rep < repcount; ++rep) + { + std::ptrdiff_t const buf_len = lt::random(512 * 1024); + std::vector buf(static_cast(buf_len)); + std::vector cmp_buf(static_cast(buf_len)); + + lt::aux::random_bytes(buf); + std::copy(buf.begin(), buf.end(), cmp_buf.begin()); + + using namespace lt::aux; + + { + lt::span iovec(buf.data(), buf_len); + int next_barrier; + lt::span> iovec_out; + std::tie(next_barrier, iovec_out) = a.encrypt(iovec); + TEST_CHECK(buf != cmp_buf); + TEST_EQUAL(iovec_out.size(), 0); + TEST_EQUAL(next_barrier, int(buf_len)); + } + + { + int consume = 0; + int produce = 0; + int packet_size = 0; + lt::span iovec(buf.data(), buf_len); + std::tie(consume, produce, packet_size) = b.decrypt(iovec); + TEST_CHECK(buf == cmp_buf); + TEST_EQUAL(consume, 0); + TEST_EQUAL(produce, int(buf_len)); + TEST_EQUAL(packet_size, 0); + } + + { + lt::span iovec(buf.data(), buf_len); + int next_barrier; + lt::span> iovec_out; + std::tie(next_barrier, iovec_out) = b.encrypt(iovec); + TEST_EQUAL(iovec_out.size(), 0); + TEST_CHECK(buf != cmp_buf); + TEST_EQUAL(next_barrier, int(buf_len)); + + int consume = 0; + int produce = 0; + int packet_size = 0; + lt::span iovec2(buf.data(), buf_len); + std::tie(consume, produce, packet_size) = a.decrypt(iovec2); + TEST_CHECK(buf == cmp_buf); + TEST_EQUAL(consume, 0); + TEST_EQUAL(produce, int(buf_len)); + TEST_EQUAL(packet_size, 0); + } + } +} + +} // anonymous namespace + +TORRENT_TEST(diffie_hellman) +{ + using namespace lt; + + const int repcount = 128; + + for (int rep = 0; rep < repcount; ++rep) + { + dh_key_exchange DH1, DH2; + + DH1.compute_secret(DH2.get_local_key()); + DH2.compute_secret(DH1.get_local_key()); + + TEST_EQUAL(DH1.get_secret(), DH2.get_secret()); + if (!DH1.get_secret() != DH2.get_secret()) + { + std::printf("DH1 local: "); + std::cout << DH1.get_local_key() << std::endl; + + std::printf("DH2 local: "); + std::cout << DH2.get_local_key() << std::endl; + + std::printf("DH1 shared_secret: "); + std::cout << DH1.get_secret() << std::endl; + + std::printf("DH2 shared_secret: "); + std::cout << DH2.get_secret() << std::endl; + } + } +} + +TORRENT_TEST(rc4) +{ + using namespace lt; + + sha1_hash test1_key = hasher("test1_key",8).final(); + sha1_hash test2_key = hasher("test2_key",8).final(); + + std::printf("testing RC4 handler\n"); + rc4_handler rc41; + rc41.set_incoming_key(test2_key); + rc41.set_outgoing_key(test1_key); + rc4_handler rc42; + rc42.set_incoming_key(test1_key); + rc42.set_outgoing_key(test2_key); + test_enc_handler(rc41, rc42); +} + +#else +TORRENT_TEST(disabled) +{ + std::printf("PE test not run because it's disabled\n"); +} +#endif diff --git a/test/test_peer_classes.cpp b/test/test_peer_classes.cpp new file mode 100644 index 0000000..f2ae517 --- /dev/null +++ b/test/test_peer_classes.cpp @@ -0,0 +1,150 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/session.hpp" + +using namespace lt; + +namespace { +std::string class_name(peer_class_t id, peer_class_pool const& p) +{ + peer_class const* c = p.at(id); + TEST_CHECK(c != nullptr); + if (c == nullptr) return ""; + peer_class_info i; + c->get_info(&i); + return i.label; +} +} // anonymous namespace + +TORRENT_TEST(peer_class) +{ + peer_class_pool pool; + + peer_class_t id1 = pool.new_peer_class("test1"); + peer_class_t id2 = pool.new_peer_class("test2"); + + // make sure there's no leak + for (int i = 0; i < 1000; ++i) + { + peer_class_t tmp = pool.new_peer_class("temp"); + pool.decref(tmp); + } + + peer_class_t id3 = pool.new_peer_class("test3"); + + TEST_CHECK(id3 == next(id2)); + + // make sure refcounting works + TEST_EQUAL(class_name(id3, pool), "test3"); + pool.incref(id3); + TEST_EQUAL(class_name(id3, pool), "test3"); + pool.decref(id3); + TEST_EQUAL(class_name(id3, pool), "test3"); + pool.decref(id3); + // it should have been deleted now + TEST_CHECK(pool.at(id3) == nullptr); + + // test setting and retrieving upload and download rates + pool.at(id2)->set_upload_limit(1000); + pool.at(id2)->set_download_limit(2000); + + peer_class_info cls; + pool.at(id2)->get_info(&cls); + TEST_EQUAL(cls.upload_limit, 1000); + TEST_EQUAL(cls.download_limit, 2000); + + // test peer_class_type_filter + peer_class_type_filter filter; + + for (int i = 0; i < 5; ++i) + { + TEST_CHECK(filter.apply(static_cast(i) + , 0xffffffff) == 0xffffffff); + } + + filter.disallow(static_cast(0), peer_class_t{0}); + TEST_CHECK(filter.apply(static_cast(0) + , 0xffffffff) == 0xfffffffe); + TEST_CHECK(filter.apply(static_cast(1) + , 0xffffffff) == 0xffffffff); + filter.allow(static_cast(0), peer_class_t{0}); + TEST_CHECK(filter.apply(static_cast(0) + , 0xffffffff) == 0xffffffff); + + TEST_CHECK(filter.apply(static_cast(0), 0) == 0); + filter.add(static_cast(0), peer_class_t{0}); + TEST_CHECK(filter.apply(static_cast(0), 0) == 1); + filter.remove(static_cast(0), peer_class_t{0}); + TEST_CHECK(filter.apply(static_cast(0), 0) == 0); + + pool.decref(id2); + pool.decref(id1); + TEST_CHECK(pool.at(id2) == nullptr); + TEST_CHECK(pool.at(id1) == nullptr); +} + +TORRENT_TEST(session_peer_class_filter) +{ + using namespace libtorrent; + session ses; + peer_class_t my_class = ses.create_peer_class("200.1.x.x IP range"); + + ip_filter f; + f.add_rule(address_v4::from_string("200.1.1.0") + , address_v4::from_string("200.1.255.255") + , 1 << static_cast(my_class)); + ses.set_peer_class_filter(f); + + TEST_CHECK(std::get<0>(ses.get_peer_class_filter().export_filter()) + == std::get<0>(f.export_filter())); +} + +TORRENT_TEST(session_peer_class_type_filter) +{ + using namespace libtorrent; + session ses; + peer_class_t my_class = ses.create_peer_class("all utp sockets"); + + peer_class_type_filter f; + f.add(peer_class_type_filter::utp_socket, my_class); + f.disallow(peer_class_type_filter::utp_socket, session::global_peer_class_id); + ses.set_peer_class_type_filter(f); + + TEST_CHECK(ses.get_peer_class_type_filter() == f); +} diff --git a/test/test_peer_list.cpp b/test/test_peer_list.cpp new file mode 100644 index 0000000..f617732 --- /dev/null +++ b/test/test_peer_list.cpp @@ -0,0 +1,976 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_list.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/socket_io.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" +#include +#include // for shared_ptr +#include + +using namespace lt; + +namespace { + +struct mock_torrent; + +struct mock_peer_connection + : peer_connection_interface + , std::enable_shared_from_this +{ + mock_peer_connection(mock_torrent* tor, bool out, tcp::endpoint const& remote) + : m_choked(false) + , m_outgoing(out) + , m_tp(nullptr) + , m_remote(remote) + , m_local(ep("127.0.0.1", 8080)) + , m_our_id(nullptr) + , m_disconnect_called(false) + , m_torrent(*tor) + { + aux::random_bytes(m_id); + } + + virtual ~mock_peer_connection() = default; + +#if !defined TORRENT_DISABLE_LOGGING + bool should_log(peer_log_alert::direction_t) const noexcept override + { return true; } + + void peer_log(peer_log_alert::direction_t, char const* /*event*/ + , char const* fmt, ...) const noexcept override + { + va_list v; + va_start(v, fmt); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + std::vprintf(fmt, v); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + va_end(v); + } +#endif + + bool was_disconnected() const { return m_disconnect_called; } + void set_local_ep(tcp::endpoint const& ep) { m_local = ep; } + + lt::stat m_stat; + bool m_choked; + bool m_outgoing; + torrent_peer* m_tp; + tcp::endpoint m_remote; + tcp::endpoint m_local; + peer_id m_id; + peer_id m_our_id; + bool m_disconnect_called; + mock_torrent& m_torrent; + + void get_peer_info(peer_info&) const override {} + tcp::endpoint const& remote() const override { return m_remote; } + tcp::endpoint local_endpoint() const override { return m_local; } + void disconnect(error_code const& ec + , operation_t op, disconnect_severity_t error = peer_connection_interface::normal) override; + peer_id const& pid() const override { return m_id; } + peer_id our_pid() const override { return m_our_id; } + void set_holepunch_mode() override {} + torrent_peer* peer_info_struct() const override { return m_tp; } + void set_peer_info(torrent_peer* pi) override { m_tp = pi; } + bool is_outgoing() const override { return m_outgoing; } + void add_stat(std::int64_t downloaded, std::int64_t uploaded) override + { m_stat.add_stat(downloaded, uploaded); } + bool fast_reconnect() const override { return true; } + bool is_choked() const override { return m_choked; } + bool failed() const override { return false; } + lt::stat const& statistics() const override { return m_stat; } +}; + +struct mock_torrent +{ + explicit mock_torrent(torrent_state* st) : m_p(nullptr), m_state(st) {} + virtual ~mock_torrent() = default; + + bool connect_to_peer(torrent_peer* peerinfo) + { + TORRENT_ASSERT(peerinfo->connection == nullptr); + if (peerinfo->connection) return false; + auto c = std::make_shared(this, true, peerinfo->ip()); + c->set_peer_info(peerinfo); + + m_connections.push_back(c); + m_p->set_connection(peerinfo, c.get()); + return true; + } + + peer_list* m_p; + torrent_state* m_state; + std::vector> m_connections; +}; + +void mock_peer_connection::disconnect(error_code const& + , operation_t, disconnect_severity_t) +{ + m_torrent.m_p->connection_closed(*this, 0, m_torrent.m_state); + auto const i = std::find(m_torrent.m_connections.begin(), m_torrent.m_connections.end() + , std::static_pointer_cast(shared_from_this())); + if (i != m_torrent.m_connections.end()) m_torrent.m_connections.erase(i); + + m_tp = nullptr; + m_disconnect_called = true; +} + +bool has_peer(peer_list const& p, tcp::endpoint const& ep) +{ + auto const its = p.find_peers(ep.address()); + return its.first != its.second; +} + +torrent_state init_state() +{ + torrent_state st; + st.is_finished = false; + st.is_paused = false; + st.max_peerlist_size = 1000; + st.allow_multiple_connections_per_ip = false; + st.port = 9999; + return st; +} + +torrent_peer* add_peer(peer_list& p, torrent_state& st, tcp::endpoint const& ep) +{ + int cc = p.num_connect_candidates(); + torrent_peer* peer = p.add_peer(ep, {}, {}, &st); + if (peer) + { + TEST_EQUAL(p.num_connect_candidates(), cc + 1); + TEST_EQUAL(peer->port, ep.port()); + } + st.erased.clear(); + return peer; +} + +void connect_peer(peer_list& p, mock_torrent& t, torrent_state& st) +{ + torrent_peer* tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + if (!tp) return; + t.connect_to_peer(tp); + st.erased.clear(); + TEST_CHECK(tp->connection); +} + +static torrent_peer_allocator allocator; + +} // anonymous namespace + +// test multiple peers with the same IP +// when disallowing it +TORRENT_TEST(multiple_ips_disallowed) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + TEST_EQUAL(p.num_connect_candidates(), 0); + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); + + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(peer1, peer2); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); +} + +// test multiple peers with the same IP +// when allowing it +TORRENT_TEST(multiple_ips_allowed) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = true; + peer_list p(allocator); + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + TEST_EQUAL(p.num_peers(), 1); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); + TEST_EQUAL(p.num_peers(), 2); + TEST_CHECK(peer1 != peer2); + TEST_EQUAL(p.num_connect_candidates(), 2); + st.erased.clear(); +} + +// test adding two peers with the same IP, but different ports, to +// make sure they can be connected at the same time +// with allow_multiple_connections_per_ip enabled +TORRENT_TEST(multiple_ips_allowed2) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = true; + peer_list p(allocator); + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + + TEST_EQUAL(p.num_peers(), 1); + torrent_peer* tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + st.erased.clear(); + + // we only have one peer, we can't + // connect another one + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp == nullptr); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); + TEST_EQUAL(p.num_peers(), 2); + TEST_CHECK(peer1 != peer2); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); +} + +// test adding two peers with the same IP, but different ports, to +// make sure they can not be connected at the same time +// with allow_multiple_connections_per_ip disabled +TORRENT_TEST(multiple_ips_disallowed2) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), {}, {}, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + TEST_EQUAL(peer1->port, 3000); + st.erased.clear(); + + TEST_EQUAL(p.num_peers(), 1); + torrent_peer* tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + st.erased.clear(); + + // we only have one peer, we can't + // connect another one + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp == nullptr); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), {}, {}, &st); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(peer2->port, 9020); + TEST_CHECK(peer1 == peer2); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); +} + +// test incoming connection +// and update_peer_port +TORRENT_TEST(update_peer_port) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + TEST_EQUAL(p.num_connect_candidates(), 0); + auto c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); + p.new_connection(*c, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + st.erased.clear(); + + p.update_peer_port(4000, c->peer_info_struct(), peer_info::incoming, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(c->peer_info_struct()->port, 4000); + st.erased.clear(); +} + +// test incoming connection +// and update_peer_port, causing collission +TORRENT_TEST(update_peer_port_collide) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = true; + peer_list p(allocator); + t.m_p = &p; + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.1", 4000), {}, {}, &st); + TEST_CHECK(peer2); + + TEST_EQUAL(p.num_connect_candidates(), 1); + auto c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); + p.new_connection(*c, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + // at this point we have two peers, because we think they have different + // ports + TEST_EQUAL(p.num_peers(), 2); + st.erased.clear(); + + // this peer will end up having the same port as the existing peer in the list + p.update_peer_port(4000, c->peer_info_struct(), peer_info::incoming, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + // the expected behavior is to replace that one + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(c->peer_info_struct()->port, 4000); + st.erased.clear(); +} + +namespace { +std::shared_ptr shared_from_this(lt::peer_connection_interface* p) +{ + return std::static_pointer_cast( + static_cast(p)->shared_from_this()); +} +} // anonymous namespace + +// test ip filter +TORRENT_TEST(ip_filter) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // add peer 1 + torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.2", 3000)); + torrent_peer* peer2 = add_peer(p, st, ep("11.0.0.2", 9020)); + + TEST_CHECK(peer1 != peer2); + + connect_peer(p, t, st); + connect_peer(p, t, st); + + auto con1 = shared_from_this(peer1->connection); + TEST_EQUAL(con1->was_disconnected(), false); + auto con2 = shared_from_this(peer2->connection); + TEST_EQUAL(con2->was_disconnected(), false); + + // now, filter one of the IPs and make sure the peer is removed + ip_filter filter; + filter.add_rule(addr4("11.0.0.0"), addr4("255.255.255.255"), 1); + std::vector
    banned; + p.apply_ip_filter(filter, &st, banned); + // we just erased a peer, because it was filtered by the ip filter + TEST_EQUAL(st.erased.size(), 1); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(banned.size(), 1); + TEST_EQUAL(banned[0], addr4("11.0.0.2")); + TEST_EQUAL(con2->was_disconnected(), true); + TEST_EQUAL(con1->was_disconnected(), false); +} + +// test port filter +TORRENT_TEST(port_filter) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // add peer 1 + torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.2", 3000)); + torrent_peer* peer2 = add_peer(p, st, ep("11.0.0.2", 9020)); + + TEST_CHECK(peer1 != peer2); + + connect_peer(p, t, st); + connect_peer(p, t, st); + + auto con1 = shared_from_this(peer1->connection); + TEST_EQUAL(con1->was_disconnected(), false); + auto con2 = shared_from_this(peer2->connection); + TEST_EQUAL(con2->was_disconnected(), false); + + // now, filter one of the IPs and make sure the peer is removed + port_filter filter; + filter.add_rule(9000, 10000, 1); + std::vector
    banned; + p.apply_port_filter(filter, &st, banned); + // we just erased a peer, because it was filtered by the ip filter + TEST_EQUAL(st.erased.size(), 1); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(banned.size(), 1); + TEST_EQUAL(banned[0], addr4("11.0.0.2")); + TEST_EQUAL(con2->was_disconnected(), true); + TEST_EQUAL(con1->was_disconnected(), false); +} + +// test banning peers +TORRENT_TEST(ban_peers) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 4000)); + + TEST_EQUAL(p.num_connect_candidates(), 1); + auto c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); + p.new_connection(*c, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + st.erased.clear(); + + // now, ban the peer + bool ok = p.ban_peer(c->peer_info_struct()); + TEST_EQUAL(ok, true); + TEST_EQUAL(peer1->banned, true); + // we still have it in the list + TEST_EQUAL(p.num_peers(), 1); + // it's just not a connect candidate, nor allowed to receive incoming connections + TEST_EQUAL(p.num_connect_candidates(), 0); + + p.connection_closed(*c, 0, &st); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); + + c = std::make_shared(&t, true, ep("10.0.0.1", 8080)); + ok = p.new_connection(*c, 0, &st); + // since it's banned, we should not allow this incoming connection + TEST_EQUAL(ok, false); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); +} + +// test erase_peers when we fill up the peer list +TORRENT_TEST(erase_peers) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.max_peerlist_size = 100; + st.allow_multiple_connections_per_ip = true; + peer_list p(allocator); + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + TEST_EQUAL(st.erased.size(), 0); + tcp::endpoint ep = rand_tcp_ep(); + torrent_peer* peer = add_peer(p, st, ep); + TEST_CHECK(peer); + if (peer == nullptr || st.erased.size() > 0) + { + std::printf("unexpected rejection of peer: %s | %d in list. " + "added peer %p, erased %d peers\n" + , print_endpoint(ep).c_str(), p.num_peers(), static_cast(peer) + , int(st.erased.size())); + } + } + TEST_EQUAL(p.num_peers(), 100); + + // trigger the eviction of one peer + torrent_peer* peer = p.add_peer(rand_tcp_ep(), {}, {}, &st); + // we either removed an existing peer, or rejected this one + // either is valid behavior when the list is full + TEST_CHECK(st.erased.size() == 1 || peer == nullptr); +} + +// test set_ip_filter +TORRENT_TEST(set_ip_filter) +{ + torrent_state st = init_state(); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + p.add_peer(tcp::endpoint( + address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), 353), {}, {}, &st); + TEST_EQUAL(st.erased.size(), 0); + st.erased.clear(); + } + TEST_EQUAL(p.num_peers(), 100); + TEST_EQUAL(p.num_connect_candidates(), 100); + + // trigger the removal of one peer + ip_filter filter; + filter.add_rule(addr4("10.13.0.0"), addr4("10.13.255.255"), ip_filter::blocked); + p.apply_ip_filter(filter, &st, banned); + TEST_EQUAL(st.erased.size(), 1); + TEST_EQUAL(st.erased[0]->address(), addr4("10.13.0.0")); + TEST_EQUAL(p.num_peers(), 99); + TEST_EQUAL(p.num_connect_candidates(), 99); +} + +// test set_port_filter +TORRENT_TEST(set_port_filter) +{ + torrent_state st = init_state(); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + p.add_peer(tcp::endpoint( + address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), std::uint16_t(i + 10)), {}, {}, &st); + TEST_EQUAL(st.erased.size(), 0); + st.erased.clear(); + } + TEST_EQUAL(p.num_peers(), 100); + TEST_EQUAL(p.num_connect_candidates(), 100); + + // trigger the removal of one peer + port_filter filter; + filter.add_rule(13, 13, port_filter::blocked); + p.apply_port_filter(filter, &st, banned); + TEST_EQUAL(st.erased.size(), 1); + TEST_EQUAL(st.erased[0]->address(), addr4("10.13.0.0")); + TEST_EQUAL(st.erased[0]->port, 13); + TEST_EQUAL(p.num_peers(), 99); + TEST_EQUAL(p.num_connect_candidates(), 99); +} + +// test set_max_failcount +TORRENT_TEST(set_max_failcount) +{ + torrent_state st = init_state(); + + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + torrent_peer* peer = p.add_peer(tcp::endpoint( + address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), std::uint16_t(i + 10)), {}, {}, &st); + TEST_EQUAL(st.erased.size(), 0); + st.erased.clear(); + // every other peer has a failcount of 1 + if (i % 2) p.inc_failcount(peer); + } + TEST_EQUAL(p.num_peers(), 100); + TEST_EQUAL(p.num_connect_candidates(), 100); + + // set the max failcount to 1 and observe how half the peers no longer + // are connect candidates + st.max_failcount = 1; + p.set_max_failcount(&st); + + TEST_EQUAL(p.num_connect_candidates(), 50); + TEST_EQUAL(p.num_peers(), 100); +} + +// test set_seed +TORRENT_TEST(set_seed) +{ + torrent_state st = init_state(); + + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + torrent_peer* peer = p.add_peer(tcp::endpoint( + address_v4(std::uint32_t((10 << 24) + ((i + 10) << 16))), std::uint16_t(i + 10)), {}, {}, &st); + TEST_EQUAL(st.erased.size(), 0); + st.erased.clear(); + // make every other peer a seed + if (i % 2) p.set_seed(peer, true); + } + TEST_EQUAL(p.num_peers(), 100); + TEST_EQUAL(p.num_connect_candidates(), 100); + + // now, the torrent completes and we're no longer interested in + // connecting to seeds. Make sure half the peers are no longer + // considered connect candidates + st.is_finished = true; + + // this will make the peer_list recalculate the connect candidates + std::vector peers; + p.connect_one_peer(1, &st); + + TEST_EQUAL(p.num_connect_candidates(), 50); + TEST_EQUAL(p.num_peers(), 100); +} + +// test has_peer +TORRENT_TEST(has_peer) +{ + torrent_state st = init_state(); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + + torrent_peer* peer1 = add_peer(p, st, ep("10.10.0.1", 10)); + torrent_peer* peer2 = add_peer(p, st, ep("10.10.0.2", 11)); + + TEST_EQUAL(p.num_peers(), 2); + TEST_EQUAL(p.num_connect_candidates(), 2); + + TEST_EQUAL(p.has_peer(peer1), true); + TEST_EQUAL(p.has_peer(peer2), true); + + ip_filter filter; + filter.add_rule(addr4("10.10.0.1"), addr4("10.10.0.1"), ip_filter::blocked); + p.apply_ip_filter(filter, &st, banned); + TEST_EQUAL(st.erased.size(), 1); + st.erased.clear(); + + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(p.num_connect_candidates(), 1); + + TEST_EQUAL(p.has_peer(peer1), false); + TEST_EQUAL(p.has_peer(peer2), true); +} + +// test connect_candidates torrent_finish +TORRENT_TEST(connect_candidates_finish) +{ + torrent_state st = init_state(); + std::vector
    banned; + + mock_torrent t(&st); + peer_list p(allocator); + t.m_p = &p; + + torrent_peer* peer1 = add_peer(p, st, ep("10.10.0.1", 10)); + TEST_CHECK(peer1); + p.set_seed(peer1, true); + torrent_peer* peer2 = add_peer(p, st, ep("10.10.0.2", 11)); + TEST_CHECK(peer2); + p.set_seed(peer2, true); + torrent_peer* peer3 = add_peer(p, st, ep("10.10.0.3", 11)); + TEST_CHECK(peer3); + p.set_seed(peer3, true); + torrent_peer* peer4 = add_peer(p, st, ep("10.10.0.4", 11)); + TEST_CHECK(peer4); + torrent_peer* peer5 = add_peer(p, st, ep("10.10.0.5", 11)); + TEST_CHECK(peer5); + + TEST_EQUAL(p.num_peers(), 5); + TEST_EQUAL(p.num_connect_candidates(), 5); + + st.is_finished = true; + // we're finished downloading now, only the non-seeds are + // connect candidates + + // connect to one of them + connect_peer(p, t, st); + + TEST_EQUAL(p.num_peers(), 5); + // and there should be one left + TEST_EQUAL(p.num_connect_candidates(), 1); +} + +// test self-connection +TORRENT_TEST(self_connection) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // add and connect peer + torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 3000)); + connect_peer(p, t, st); + + auto con_out = shared_from_this(peer->connection); + con_out->set_local_ep(ep("10.0.0.2", 8080)); + + auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 8080)); + con_in->set_local_ep(ep("10.0.0.2", 3000)); + + p.new_connection(*con_in, 0, &st); + + // from the peer_list's point of view, this looks like we made one + // outgoing connection and received an incoming one. Since they share + // the exact same endpoints (IP ports) but just swapped source and + // destination, the peer list is supposed to figure out that we connected + // to ourself and disconnect it + TEST_EQUAL(con_out->was_disconnected(), true); + TEST_EQUAL(con_in->was_disconnected(), true); +} + +// test double connection (both incoming) +TORRENT_TEST(double_connection) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // we are 10.0.0.1 and the other peer is 10.0.0.2 + + // first incoming connection + auto con1 = std::make_shared(&t, false, ep("10.0.0.2", 7528)); + con1->set_local_ep(ep("10.0.0.1", 8080)); + + p.new_connection(*con1, 0, &st); + + // and the incoming connection + auto con2 = std::make_shared(&t, false, ep("10.0.0.2", 3561)); + con2->set_local_ep(ep("10.0.0.1", 8080)); + + p.new_connection(*con2, 0, &st); + + // the second incoming connection should be closed + TEST_EQUAL(con1->was_disconnected(), false); + TEST_EQUAL(con2->was_disconnected(), true); +} + +// test double connection (we loose) +TORRENT_TEST(double_connection_loose) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // we are 10.0.0.1 and the other peer is 10.0.0.2 + + // our outgoing connection + torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 3000)); + connect_peer(p, t, st); + + auto con_out = shared_from_this(peer->connection); + con_out->set_local_ep(ep("10.0.0.1", 3163)); + + // and the incoming connection + auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 3561)); + con_in->set_local_ep(ep("10.0.0.1", 8080)); + + p.new_connection(*con_in, 0, &st); + + // the rules are documented in peer_list.cpp + TEST_EQUAL(con_out->was_disconnected(), true); + TEST_EQUAL(con_in->was_disconnected(), false); +} + +// test double connection with identical ports (random) +TORRENT_TEST(double_connection_random) +{ + int in = 0; + int out = 0; + for (int i = 0; i < 30; ++i) + { + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // we are 10.0.0.1 and the other peer is 10.0.0.2 + + // our outgoing connection + torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 3000)); + connect_peer(p, t, st); + + auto con_out = static_cast(peer->connection)->shared_from_this(); + con_out->set_local_ep(ep("10.0.0.1", 3000)); + + // and the incoming connection + auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 3000)); + con_in->set_local_ep(ep("10.0.0.1", 3000)); + + p.new_connection(*con_in, 0, &st); + + // the rules are documented in peer_list.cpp + out += con_out->was_disconnected(); + in += con_in->was_disconnected(); + } + // we should have gone different ways randomly + TEST_CHECK(out > 0); + TEST_CHECK(in > 0); +} + +// test double connection (we win) +TORRENT_TEST(double_connection_win) +{ + torrent_state st = init_state(); + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + // we are 10.0.0.1 and the other peer is 10.0.0.2 + + // our outgoing connection + torrent_peer* peer = add_peer(p, st, ep("10.0.0.2", 8080)); + connect_peer(p, t, st); + + auto con_out = shared_from_this(peer->connection); + con_out->set_local_ep(ep("10.0.0.1", 3163)); + + //and the incoming connection + auto con_in = std::make_shared(&t, false, ep("10.0.0.2", 3561)); + con_in->set_local_ep(ep("10.0.0.1", 3000)); + + p.new_connection(*con_in, 0, &st); + + // the rules are documented in peer_list.cpp + TEST_EQUAL(con_out->was_disconnected(), false); + TEST_EQUAL(con_in->was_disconnected(), true); +} + +// test incoming connection when we are at the list size limit +TORRENT_TEST(incoming_size_limit) +{ + torrent_state st = init_state(); + st.max_peerlist_size = 5; + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 8080)); + TEST_CHECK(peer1); + TEST_EQUAL(p.num_peers(), 1); + torrent_peer* peer2 = add_peer(p, st, ep("10.0.0.2", 8080)); + TEST_CHECK(peer2); + TEST_EQUAL(p.num_peers(), 2); + torrent_peer* peer3 = add_peer(p, st, ep("10.0.0.3", 8080)); + TEST_CHECK(peer3); + TEST_EQUAL(p.num_peers(), 3); + torrent_peer* peer4 = add_peer(p, st, ep("10.0.0.4", 8080)); + TEST_CHECK(peer4); + TEST_EQUAL(p.num_peers(), 4); + torrent_peer* peer5 = add_peer(p, st, ep("10.0.0.5", 8080)); + TEST_CHECK(peer5); + TEST_EQUAL(p.num_peers(), 5); + + auto con_in = std::make_shared(&t, false, ep("10.0.1.2", 3561)); + con_in->set_local_ep(ep("10.0.2.1", 3000)); + + // since we're already at 5 peers in the peer list, this call should + // erase one of the existing ones. + p.new_connection(*con_in, 0, &st); + + TEST_EQUAL(con_in->was_disconnected(), false); + TEST_EQUAL(p.num_peers(), 5); + + // one of the previous ones should have been removed + TEST_EQUAL(has_peer(p, ep("10.0.0.1", 8080)) + + has_peer(p, ep("10.0.0.2", 8080)) + + has_peer(p, ep("10.0.0.3", 8080)) + + has_peer(p, ep("10.0.0.4", 8080)) + + has_peer(p, ep("10.0.0.5", 8080)) + , 4); +} + +// test new peer when we are at the list size limit +TORRENT_TEST(new_peer_size_limit) +{ + torrent_state st = init_state(); + st.max_peerlist_size = 5; + mock_torrent t(&st); + st.allow_multiple_connections_per_ip = false; + peer_list p(allocator); + t.m_p = &p; + + torrent_peer* peer1 = add_peer(p, st, ep("10.0.0.1", 8080)); + TEST_CHECK(peer1); + TEST_EQUAL(p.num_peers(), 1); + torrent_peer* peer2 = add_peer(p, st, ep("10.0.0.2", 8080)); + TEST_CHECK(peer2); + TEST_EQUAL(p.num_peers(), 2); + torrent_peer* peer3 = add_peer(p, st, ep("10.0.0.3", 8080)); + TEST_CHECK(peer3); + TEST_EQUAL(p.num_peers(), 3); + torrent_peer* peer4 = add_peer(p, st, ep("10.0.0.4", 8080)); + TEST_CHECK(peer4); + TEST_EQUAL(p.num_peers(), 4); + torrent_peer* peer5 = add_peer(p, st, ep("10.0.0.5", 8080)); + TEST_CHECK(peer5); + TEST_EQUAL(p.num_peers(), 5); + torrent_peer* peer6 = p.add_peer(ep("10.0.0.6", 8080), {}, {}, &st); + TEST_CHECK(peer6 == nullptr); + TEST_EQUAL(p.num_peers(), 5); + + // one of the connection should have been removed + TEST_EQUAL(has_peer(p, ep("10.0.0.1", 8080)) + + has_peer(p, ep("10.0.0.2", 8080)) + + has_peer(p, ep("10.0.0.3", 8080)) + + has_peer(p, ep("10.0.0.4", 8080)) + + has_peer(p, ep("10.0.0.5", 8080)) + + has_peer(p, ep("10.0.0.6", 8080)) + , 5); +} + +// TODO: test erasing peers +// TODO: test update_peer_port with allow_multiple_connections_per_ip and without +// TODO: test add i2p peers +// TODO: test allow_i2p_mixed +// TODO: test insert_peer failing with all error conditions +// TODO: test IPv6 +// TODO: test connect_to_peer() failing +// TODO: test connection_closed +// TODO: connect candidates recalculation when incrementing failcount diff --git a/test/test_peer_priority.cpp b/test/test_peer_priority.cpp new file mode 100644 index 0000000..fd404e9 --- /dev/null +++ b/test/test_peer_priority.cpp @@ -0,0 +1,99 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/peer_list.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6() +#include "setup_transfer.hpp" +#include "libtorrent/aux_/disable_warnings_push.hpp" +#include +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include "test.hpp" + +using namespace lt; + +namespace { +std::uint32_t hash_buffer(char const* buf, int len) +{ + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_block(buf, buf + len); + return crc.checksum(); +} +} // anonymous namespace + +TORRENT_TEST(peer_priority) +{ + // when the IP is the same, we hash the ports, sorted + std::uint32_t p = peer_priority( + ep("230.12.123.3", 0x4d2), ep("230.12.123.3", 0x12c)); + TEST_EQUAL(p, hash_buffer("\x01\x2c\x04\xd2", 4)); + + // when we're in the same /24, we just hash the IPs + p = peer_priority(ep("230.12.123.1", 0x4d2), ep("230.12.123.3", 0x12c)); + TEST_EQUAL(p, hash_buffer("\xe6\x0c\x7b\x01\xe6\x0c\x7b\x03", 8)); + + // when we're in the same /16, we just hash the IPs masked by + // 0xffffff55 + p = peer_priority(ep("230.12.23.1", 0x4d2), ep("230.12.123.3", 0x12c)); + TEST_EQUAL(p, hash_buffer("\xe6\x0c\x17\x01\xe6\x0c\x7b\x01", 8)); + + // when we're in different /16, we just hash the IPs masked by + // 0xffff5555 + p = peer_priority(ep("230.120.23.1", 0x4d2), ep("230.12.123.3", 0x12c)); + TEST_EQUAL(p, hash_buffer("\xe6\x0c\x51\x01\xe6\x78\x15\x01", 8)); + + // test vectors from BEP 40 + TEST_EQUAL(peer_priority(ep("123.213.32.10", 0), ep("98.76.54.32", 0)) + , 0xec2d7224); + + TEST_EQUAL(peer_priority( + ep("123.213.32.10", 0), ep("123.213.32.234", 0)) + , 0x99568189); + + if (supports_ipv6()) + { + // IPv6 has a twice as wide mask, and we only care about the top 64 bits + // when the IPs are the same, just hash the ports + p = peer_priority( + ep("ffff:ffff:ffff:ffff::1", 0x4d2), ep("ffff:ffff:ffff:ffff::1", 0x12c)); + TEST_EQUAL(p, hash_buffer("\x01\x2c\x04\xd2", 4)); + + // these IPs don't belong to the same /32, so apply the full mask + // 0xffffffff55555555 + p = peer_priority( + ep("ffff:ffff:ffff:ffff::1", 0x4d2), ep("ffff:0fff:ffff:ffff::1", 0x12c)); + TEST_EQUAL(p, hash_buffer( + "\xff\xff\x0f\xff\x55\x55\x55\x55\x00\x00\x00\x00\x00\x00\x00\x01" + "\xff\xff\xff\xff\x55\x55\x55\x55\x00\x00\x00\x00\x00\x00\x00\x01", 32)); + } +} diff --git a/test/test_pex.cpp b/test/test_pex.cpp new file mode 100644 index 0000000..1522eda --- /dev/null +++ b/test/test_pex.cpp @@ -0,0 +1,169 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/extensions/ut_pex.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/aux_/path.hpp" +#include + +#include "setup_transfer.hpp" +#include "settings.hpp" +#include + +namespace { + +void test_pex() +{ + using namespace lt; + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + session_proxy p3; + + // this is to avoid everything finish from a single peer + // immediately. To make the swarm actually connect all + // three peers before finishing. + settings_pack pack = settings(); + pack.set_int(settings_pack::download_rate_limit, 2000); + pack.set_int(settings_pack::upload_rate_limit, 2000); + pack.set_int(settings_pack::max_retry_port_bind, 800); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48200"); + + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); +#if TORRENT_ABI_VERSION == 1 + pack.set_bool(settings_pack::rate_limit_utp, true); +#endif + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49200"); + + lt::session ses3(pack); + + // make the peer connecting the two worthless to transfer + // data, to force peer 3 to connect directly to peer 1 through pex + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:50200"); + lt::session ses2(pack); + + ses1.add_extension(create_ut_pex_plugin); + ses2.add_extension(create_ut_pex_plugin); + + torrent_handle tor1; + torrent_handle tor2; + torrent_handle tor3; + + std::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true, false, false, "_pex"); + + ses2.apply_settings(pack); + + std::this_thread::sleep_for(lt::milliseconds(100)); + + // in this test, ses1 is a seed, ses2 is connected to ses1 and ses3. + // the expected behavior is that ses2 will introduce ses1 and ses3 to each other + error_code ec; + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec), ses1.listen_port())); + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec), ses3.listen_port())); + + torrent_status st1; + torrent_status st2; + torrent_status st3; + for (int i = 0; i < 610; ++i) + { + print_alerts(ses1, "ses1"); + print_alerts(ses2, "ses2"); + print_alerts(ses3, "ses3"); + + st1 = tor1.status(); + st2 = tor2.status(); + st3 = tor3.status(); + + print_ses_rate(i / 10.f, &st1, &st2, &st3); + + // this is the success condition + if (st1.num_peers == 2 && st2.num_peers == 2 && st3.num_peers == 2) + break; + + // this suggests that we failed. If session 3 completes without + // actually connecting to session 1, everything was transferred + // through session 2 + if (st3.state == torrent_status::seeding) break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + TEST_CHECK(st1.num_peers == 2 && st2.num_peers == 2 && st3.num_peers == 2); + + if (!tor2.status().is_seeding && tor3.status().is_seeding) std::cout << "done\n"; + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); + p3 = ses3.abort(); +} +} // anonymous namespace +#endif // TORRENT_DISABLE_EXTENSIONS + +TORRENT_TEST(pex) +{ +#ifndef TORRENT_DISABLE_EXTENSIONS + using namespace lt; + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_pex", ec); + remove_all("tmp2_pex", ec); + remove_all("tmp3_pex", ec); + + test_pex(); + + remove_all("tmp1_pex", ec); + remove_all("tmp2_pex", ec); + remove_all("tmp3_pex", ec); +#endif // TORRENT_DISABLE_EXTENSIONS +} + + diff --git a/test/test_piece_picker.cpp b/test/test_piece_picker.cpp new file mode 100644 index 0000000..6b90d54 --- /dev/null +++ b/test/test_piece_picker.cpp @@ -0,0 +1,2368 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/piece_picker.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/bitfield.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/units.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "test.hpp" +#include "test_utils.hpp" + +using namespace lt; +using namespace std::placeholders; + +namespace { + +const int blocks_per_piece = 4; + +typed_bitfield string2vec(char const* have_str) +{ + const int num_pieces = int(strlen(have_str)); + typed_bitfield have(num_pieces, false); + for (piece_index_t i(0); i < have.end_index(); ++i) + if (have_str[static_cast(i)] != ' ') have.set_bit(i); + return have; +} + +tcp::endpoint endp; +ipv4_peer tmp0(endp, false, {}); +ipv4_peer tmp1(endp, false, {}); +ipv4_peer tmp2(endp, false, {}); +ipv4_peer tmp3(endp, false, {}); +ipv4_peer tmp4(endp, false, {}); +ipv4_peer tmp5(endp, false, {}); +ipv4_peer tmp6(endp, false, {}); +ipv4_peer tmp7(endp, false, {}); +ipv4_peer tmp8(endp, false, {}); +ipv4_peer tmp9(endp, false, {}); +ipv4_peer peer_struct(endp, true, {}); +ipv4_peer* tmp_peer = &tmp1; + +static std::vector const empty_vector; + +#if TORRENT_USE_ASSERTS +namespace { // TODO: remove the nested namespace + static struct initializer + { + initializer() + { + tmp0.in_use = true; + tmp1.in_use = true; + tmp2.in_use = true; + tmp3.in_use = true; + tmp4.in_use = true; + tmp5.in_use = true; + tmp6.in_use = true; + tmp7.in_use = true; + tmp8.in_use = true; + tmp9.in_use = true; + peer_struct.in_use = true; + } + + } initializer_dummy; +} +#endif + +// availability is a string where each character is the +// availability of that piece, '1', '2' etc. +// have_str is a string where each character represents a +// piece, ' ' means we don't have the piece and any other +// character means we have it +std::shared_ptr setup_picker( + char const* availability + , char const* have_str + , char const* priority + , char const* partial + , int num_blocks_per_piece = blocks_per_piece) +{ + const int num_pieces = int(strlen(availability)); + TORRENT_ASSERT(int(strlen(have_str)) == num_pieces); + + std::shared_ptr p = std::make_shared(num_blocks_per_piece, num_blocks_per_piece, num_pieces); + + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + const int avail = availability[static_cast(i)] - '0'; + assert(avail >= 0); + + static const torrent_peer* peers[10] = { &tmp0, &tmp1, &tmp2 + , &tmp3, &tmp4, &tmp5, &tmp6, &tmp7, &tmp8, &tmp9 }; + TORRENT_ASSERT(avail < 10); + for (int j = 0; j < avail; ++j) p->inc_refcount(i, peers[j]); + } + + auto have = string2vec(have_str); + + for (piece_index_t i(0); i < have.end_index(); ++i) + { + int const idx = static_cast(i); + if (partial[idx] == 0) break; + + if (partial[idx] == ' ') continue; + + int blocks = 0; + if (partial[idx] >= '0' && partial[idx] <= '9') + blocks = partial[idx] - '0'; + else + blocks = partial[idx] - 'a' + 10; + + int counter = 0; + for (int j = 0; j < 4; ++j) + { + TEST_CHECK(!p->is_finished(piece_block(i, j))); + if ((blocks & (1 << j)) == 0) continue; + ++counter; + bool ret = p->mark_as_downloading(piece_block(i, j), tmp_peer); + TEST_CHECK(ret == true); + TEST_CHECK(p->is_requested(piece_block(i, j)) == ((blocks & (1 << j)) != 0)); + p->mark_as_writing(piece_block(i, j), tmp_peer); + TEST_CHECK(!p->is_finished(piece_block(i, j))); + // trying to mark a block as requested after it has been completed + // should fail (return false) + ret = p->mark_as_downloading(piece_block(i, j), tmp_peer); + TEST_CHECK(ret == false); + p->mark_as_finished(piece_block(i, j), tmp_peer); + + TEST_CHECK(p->is_downloaded(piece_block(i, j)) == ((blocks & (1 << j)) != 0)); + TEST_CHECK(p->is_finished(piece_block(i, j)) == ((blocks & (1 << j)) != 0)); + } + + piece_picker::downloading_piece st; + p->piece_info(i, st); + TEST_EQUAL(int(st.writing), 0); + TEST_EQUAL(int(st.requested), 0); + TEST_EQUAL(int(st.index), idx); + + TEST_EQUAL(st.finished, counter); + TEST_EQUAL(st.finished + st.requested + st.writing, counter); + + TEST_CHECK(p->is_piece_finished(i) == (counter == 4)); + } + + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + int const idx = static_cast(i); + if (priority[idx] == 0) break; + download_priority_t const prio((priority[idx] - '0') & 0xff); + TEST_CHECK(prio >= dont_download); + p->set_piece_priority(i, prio); + + TEST_CHECK(p->piece_priority(i) == prio); + } + + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + if (!have[i]) continue; + p->we_have(i); + for (int j = 0; j < num_blocks_per_piece; ++j) + TEST_CHECK(p->is_finished(piece_block(i, j))); + } + + aux::vector availability_vec; + p->get_availability(availability_vec); + for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i) + { + const int avail = availability[static_cast(i)] - '0'; + assert(avail >= 0); + TEST_CHECK(avail == availability_vec[i]); + } + +#if TORRENT_USE_INVARIANT_CHECKS + p->check_invariant(); +#endif + return p; +} + +bool verify_pick(std::shared_ptr p + , std::vector const& picked, bool allow_multi_blocks = false) +{ +#if TORRENT_USE_INVARIANT_CHECKS + p->check_invariant(); +#endif + if (!allow_multi_blocks) + { + for (std::vector::const_iterator i = picked.begin() + , end(picked.end()); i != end; ++i) + { + if (p->num_peers(*i) > 0) return false; + } + } + + // make sure there are no duplicated + std::set blocks; + std::copy(picked.begin(), picked.end() + , std::insert_iterator>(blocks, blocks.end())); + std::cout << " verify: " << picked.size() << " " << blocks.size() << std::endl; + return picked.size() == blocks.size(); +} + +void print_availability(std::shared_ptr const& p) +{ + aux::vector avail; + p->get_availability(avail); + std::printf("[ "); + for (auto i : avail) + std::printf("%d ", i); + std::printf("]\n"); +} + +bool verify_availability(std::shared_ptr const& p, char const* a) +{ + aux::vector avail; + p->get_availability(avail); + for (auto i = avail.begin(), end(avail.end()); i != end; ++i, ++a) + { + if (*a - '0' != *i) return false; + } + return true; +} + +void print_pick(std::vector const& picked) +{ + for (auto const& p : picked) + { + std::cout << "(" << p.piece_index << ", " << p.block_index << ") "; + } + std::cout << std::endl; +} + +std::vector pick_pieces(std::shared_ptr const& p + , char const* availability + , int num_blocks + , int prefer_contiguous_blocks + , torrent_peer* peer_struct_arg + , picker_options_t const options = piece_picker::rarest_first + , std::vector const& suggested_pieces = empty_vector) +{ + std::vector picked; + counters pc; + p->pick_pieces(string2vec(availability), picked + , num_blocks, prefer_contiguous_blocks, peer_struct_arg + , options, suggested_pieces, 20, pc); + print_pick(picked); + TEST_CHECK(verify_pick(p, picked)); + return picked; +} + +piece_index_t test_pick(std::shared_ptr const& p + , picker_options_t const options = piece_picker::rarest_first) +{ + std::vector picked = pick_pieces(p, "*******", 1, 0, nullptr + , options, empty_vector); + if (picked.size() != 1) return piece_index_t(-1); + return picked[0].piece_index; +} + +picker_options_t const options = piece_picker::rarest_first; +counters pc; + +} // anonymous namespace + +TORRENT_TEST(piece_block) +{ + piece_index_t const zero(0); + piece_index_t const one(1); + + TEST_CHECK(piece_block(zero, 0) != piece_block(zero, 1)); + TEST_CHECK(piece_block(zero, 0) != piece_block(one, 0)); + TEST_CHECK(!(piece_block(zero, 0) != piece_block(zero, 0))); + + TEST_CHECK(!(piece_block(zero, 0) == piece_block(zero, 1))); + TEST_CHECK(!(piece_block(zero, 0) == piece_block(one, 0))); + TEST_CHECK(piece_block(zero, 0) == piece_block(zero, 0)); + + TEST_CHECK(!(piece_block(zero, 1) < piece_block(zero, 0))); + TEST_CHECK(!(piece_block(one, 0) < piece_block(zero, 0))); + TEST_CHECK(piece_block(zero, 0) < piece_block(zero, 1)); + TEST_CHECK(piece_block(zero, 0) < piece_block(one, 0)); + TEST_CHECK(!(piece_block(zero, 0) < piece_block(zero, 0))); + TEST_CHECK(!(piece_block(one, 0) < piece_block(one, 0))); + TEST_CHECK(!(piece_block(zero, 1) < piece_block(zero, 1))); +} + +TORRENT_TEST(abort_download) +{ + // test abort_download + auto p = setup_picker("1111111", " ", "7110000", ""); + auto picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) != picked.end()); + + p->abort_download({piece_index_t(0), 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) != picked.end()); + + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == true); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) == picked.end()); + + p->abort_download({piece_index_t(0), 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) != picked.end()); + + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1); + p->mark_as_downloading({piece_index_t(0), 1}, &tmp1); + p->abort_download({piece_index_t(0), 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) != picked.end()); + + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1); + p->mark_as_writing({piece_index_t(0), 0}, &tmp1); + p->write_failed({piece_index_t(0), 0}); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(1), 0)) != picked.end() + || std::find(picked.begin(), picked.end(), piece_block(piece_index_t(2), 0)) != picked.end()); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) == picked.end()); + p->restore_piece(piece_index_t(0)); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) != picked.end()); + + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1); + p->mark_as_writing({piece_index_t(0), 0}, &tmp1); + p->mark_as_finished({piece_index_t(0), 0}, &tmp1); + p->abort_download({piece_index_t(0), 0}, tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) == picked.end()); +} + +TORRENT_TEST(abort_download2) +{ + auto p = setup_picker("1111111", " ", "7110000", ""); + piece_picker::downloading_piece st; + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1); + p->mark_as_finished({piece_index_t(0), 1}, nullptr); + p->piece_info(piece_index_t(0), st); + TEST_EQUAL(st.requested, 1); + TEST_EQUAL(st.finished, 1); + p->abort_download({piece_index_t(0), 0}, tmp_peer); + p->piece_info(piece_index_t(0), st); + TEST_EQUAL(st.requested, 0); + TEST_EQUAL(st.finished, 1); + auto picked = pick_pieces(p, "*******", blocks_per_piece, 0, nullptr + , options, empty_vector); + TEST_CHECK(p->is_requested({piece_index_t(0), 0}) == false); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(0), 0)) != picked.end()); +} + +TORRENT_TEST(get_downloaders) +{ + auto p = setup_picker("1111111", " ", "7110000", ""); + + p->mark_as_downloading({piece_index_t(0), 2}, &tmp1); + p->mark_as_writing({piece_index_t(0), 2}, &tmp1); + p->abort_download({piece_index_t(0), 2}, &tmp1); + p->mark_as_downloading({piece_index_t(0), 2}, &tmp2); + p->mark_as_writing({piece_index_t(0), 2}, &tmp2); + + std::vector d; + p->get_downloaders(d, piece_index_t(0)); + TEST_EQUAL(d.size(), 4); + TEST_CHECK(d[0] == nullptr); + TEST_CHECK(d[1] == nullptr); + TEST_CHECK(d[2] == &tmp2); + TEST_CHECK(d[3] == nullptr); + + p->mark_as_downloading({piece_index_t(0), 3}, &tmp1); + p->abort_download({piece_index_t(0), 3}, &tmp1); + p->mark_as_downloading({piece_index_t(0), 3}, &tmp2); + p->mark_as_writing({piece_index_t(0), 3}, &tmp2); + + p->get_downloaders(d, piece_index_t(0)); + + TEST_EQUAL(d.size(), 4); + TEST_CHECK(d[0] == nullptr); + TEST_CHECK(d[1] == nullptr); + TEST_CHECK(d[2] == &tmp2); + TEST_CHECK(d[3] == &tmp2); + + // if we ask for downloaders for a piece that's not + // curently being downloaded, we get zeroes back + p->get_downloaders(d, piece_index_t(1)); + + TEST_EQUAL(d.size(), 4); + TEST_CHECK(d[0] == nullptr); + TEST_CHECK(d[1] == nullptr); + TEST_CHECK(d[2] == nullptr); + TEST_CHECK(d[3] == nullptr); + +// ======================================================== + + p = setup_picker("2222", " ", "", ""); + + for (piece_index_t i(0); i < piece_index_t(4); ++i) + for (int k = 0; k < blocks_per_piece; ++k) + p->mark_as_downloading(piece_block(i, k), &tmp1); + + p->mark_as_downloading({piece_index_t(0), 0}, &tmp2); + + std::printf("num_peers: %d\n", p->num_peers({piece_index_t(0), 0})); + TEST_EQUAL(p->num_peers({piece_index_t(0), 0}), 2); + + p->abort_download({piece_index_t(0), 0}, &tmp1); + + std::printf("num_peers: %d\n", p->num_peers({piece_index_t(0), 0})); + TEST_EQUAL(p->num_peers({piece_index_t(0), 0}), 1); +} + +TORRENT_TEST(pick_lowest_availability) +{ + // make sure the block that is picked is from piece 1, since it + // it is the piece with the lowest availability + auto p = setup_picker("2223333", "* * * ", "", ""); + TEST_CHECK(test_pick(p) == piece_index_t(1)); +} + +TORRENT_TEST(random_pick_at_same_priority) +{ + // make sure pieces with equal priority and availability + // are picked at random + std::map random_prio_pieces; + for (int i = 0; i < 100; ++i) + { + auto p = setup_picker("1111112", " ", "", ""); + ++random_prio_pieces[test_pick(p)]; + } + TEST_CHECK(random_prio_pieces.size() == 6); + for (auto i = random_prio_pieces.begin() + , end(random_prio_pieces.end()); i != end; ++i) + std::cout << static_cast(i->first) << ": " << i->second << " "; + std::cout << std::endl; +} + +TORRENT_TEST(pick_highest_priority) +{ + // make sure the block that is picked is from piece 5, since it + // has the highest priority among the available pieces + auto p = setup_picker("1111111", " ", "1111121", ""); + TEST_CHECK(test_pick(p) == piece_index_t(5)); + + p = setup_picker("1111111", " ", "1171121", ""); + TEST_CHECK(test_pick(p) == piece_index_t(2)); + + p = setup_picker("1111111", " ", "1131521", ""); + TEST_CHECK(test_pick(p) == piece_index_t(4)); +} + +TORRENT_TEST(reverse_rarest_first) +{ + auto p = setup_picker("4179253", " ", "", ""); + auto picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, &peer_struct + , piece_picker::rarest_first | piece_picker::reverse, empty_vector); + int expected_common_pieces[] = {3, 2, 5, 0, 6, 4, 1}; + for (int i = 0; i < int(picked.size()); ++i) + { + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t( + expected_common_pieces[i / blocks_per_piece]) + , i % blocks_per_piece)); + } + + // piece 3 should NOT be prioritized since it's a partial, and not + // reversed. Reversed partials are considered reversed + p = setup_picker("1122111", " ", "3333333", " 1 "); + TEST_CHECK(test_pick(p, piece_picker::rarest_first | piece_picker::reverse) + == piece_index_t(2)); +} + +TORRENT_TEST(pick_whole_pieces) +{ + // make sure the 4 blocks are picked from the same piece if + // whole pieces are preferred. Priority and availability is more + // important. Piece 1 has the lowest availability even though + // it is not a whole piece + auto p = setup_picker("2212222", " ", "1111111", "1023460"); + auto picked = pick_pieces(p, "****** ", 1, blocks_per_piece + , &peer_struct, options, empty_vector); + TEST_EQUAL(int(picked.size()), 3); + for (int i = 0; i < blocks_per_piece && i < int(picked.size()); ++i) + TEST_EQUAL(picked[std::size_t(i)].piece_index, piece_index_t(2)); + + p = setup_picker("1111111", " ", "1111111", ""); + picked = pick_pieces(p, "****** ", 1, blocks_per_piece + , &peer_struct, options, empty_vector); + TEST_EQUAL(int(picked.size()), blocks_per_piece); + for (int i = 0; i < blocks_per_piece && i < int(picked.size()); ++i) + TEST_EQUAL(picked[std::size_t(i)].block_index, i); + + p = setup_picker("2221222", " ", "", ""); + picked = pick_pieces(p, "*******", 1, 7 * blocks_per_piece + , &peer_struct, options, empty_vector); + TEST_EQUAL(int(picked.size()), 7 * blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(i / blocks_per_piece) + , i % blocks_per_piece)); +} + +TORRENT_TEST(distributed_copies) +{ + // test the distributed copies function. It should include ourself + // in the availability. i.e. piece 0 has availability 2. + // there are 2 pieces with availability 2 and 5 with availability 3 + auto p = setup_picker("1233333", "* ", "", ""); + auto dc = p->distributed_copies(); + TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); +} + +TORRENT_TEST(filtered_pieces) +{ + // make sure filtered pieces are ignored + auto p = setup_picker("1111111", " ", "0010000", ""); + TEST_CHECK(test_pick(p, piece_picker::rarest_first) == piece_index_t(2)); + TEST_CHECK(test_pick(p, piece_picker::rarest_first | piece_picker::reverse) == piece_index_t(2)); + TEST_CHECK(test_pick(p, piece_picker::sequential) == piece_index_t(2)); + TEST_CHECK(test_pick(p, piece_picker::sequential | piece_picker::reverse) == piece_index_t(2)); +} + +TORRENT_TEST(we_dont_have) +{ + // make sure we_dont_have works + auto p = setup_picker("1111111", "*******", "0100000", ""); + TEST_CHECK(p->have_piece(piece_index_t(1))); + TEST_CHECK(p->have_piece(piece_index_t(2))); + p->we_dont_have(piece_index_t(1)); + p->we_dont_have(piece_index_t(2)); + TEST_CHECK(!p->have_piece(piece_index_t(1))); + TEST_CHECK(!p->have_piece(piece_index_t(2))); + auto picked = pick_pieces(p, "*** ** ", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front().piece_index == piece_index_t(1)); +} + +TORRENT_TEST(dec_refcount_split_seed) +{ + // make sure we can split m_seed when removing a refcount + auto p = setup_picker("0000000", " ", "0000000", ""); + p->inc_refcount_all(nullptr); + + aux::vector avail; + p->get_availability(avail); + TEST_EQUAL(avail.size(), 7); + TEST_CHECK(avail[piece_index_t(0)] != 0); + TEST_CHECK(avail[piece_index_t(1)] != 0); + TEST_CHECK(avail[piece_index_t(2)] != 0); + TEST_CHECK(avail[piece_index_t(3)] != 0); + TEST_CHECK(avail[piece_index_t(4)] != 0); + + p->dec_refcount(piece_index_t(3), nullptr); + + p->get_availability(avail); + TEST_EQUAL(avail.size(), 7); + + TEST_CHECK(avail[piece_index_t(0)] != 0); + TEST_CHECK(avail[piece_index_t(1)] != 0); + TEST_CHECK(avail[piece_index_t(2)] != 0); + TEST_CHECK(avail[piece_index_t(3)] == 0); + TEST_CHECK(avail[piece_index_t(4)] != 0); +} + +TORRENT_TEST(resize) +{ + // make sure init preserves priorities + auto p = setup_picker("1111111", " ", "1111111", ""); + + TEST_EQUAL(p->want().num_pieces, 7); + TEST_EQUAL(p->have_want().num_pieces, 0); + TEST_EQUAL(p->have().num_pieces, 0); + + p->set_piece_priority(piece_index_t(0), dont_download); + TEST_EQUAL(p->want().num_pieces, 6); + TEST_EQUAL(p->have_want().num_pieces, 0); + TEST_EQUAL(p->have().num_pieces, 0); + + p->we_have(piece_index_t(0)); + + TEST_EQUAL(p->want().num_pieces, 6); + TEST_EQUAL(p->have_want().num_pieces, 0); + TEST_EQUAL(p->have().num_pieces, 1); + + p->resize(blocks_per_piece, blocks_per_piece, blocks_per_piece * 7); + TEST_EQUAL(p->piece_priority(piece_index_t(0)), dont_download); + TEST_EQUAL(p->want().num_pieces, blocks_per_piece * 7 - 1); + TEST_EQUAL(p->have_want().num_pieces, 0); + TEST_EQUAL(p->have().num_pieces, 0); +} + +TORRENT_TEST(we_have_all) +{ + auto p = setup_picker("0123111", " ** * ", "1234567", " 1234"); + + p->we_have_all(); + + TEST_EQUAL(p->want().num_pieces, 7); + TEST_EQUAL(p->want().pad_blocks, 0); + TEST_EQUAL(p->want().last_piece, true); + + TEST_EQUAL(p->have_want().num_pieces, 7); + TEST_EQUAL(p->have_want().pad_blocks, 0); + TEST_EQUAL(p->have_want().last_piece, true); + + TEST_EQUAL(p->have().num_pieces, 7); + TEST_EQUAL(p->have().pad_blocks, 0); + TEST_EQUAL(p->have().last_piece, true); +} + +TORRENT_TEST(dont_pick_requested_blocks) +{ + // make sure requested blocks aren't picked + auto p = setup_picker("1111111", " ", "", ""); + auto picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + piece_block first = picked.front(); + p->mark_as_downloading(picked.front(), &peer_struct); + TEST_CHECK(p->num_peers(picked.front()) == 1); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() != first); +} + +TORRENT_TEST(downloading_piece_priority) +{ + // make sure downloading pieces have higher priority + auto p = setup_picker("1111111", " ", "", ""); + auto picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + auto first = picked.front(); + p->mark_as_downloading(picked.front(), &peer_struct); + TEST_CHECK(p->num_peers(picked.front()) == 1); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() != first); + TEST_CHECK(picked.front().piece_index == first.piece_index); +} + +TORRENT_TEST(partial_piece_order_rarest_first) +{ + // when we're prioritizing partial pieces, make sure to first pick the + // rarest of them. The blocks in this test are: + // 0: [ ] avail: 1 + // 1: [x ] avail: 1 + // 2: [xx ] avail: 1 + // 3: [xxx ] avail: 2 + // 4: [ ] avail: 1 + // 5: [ ] avail: 1 + // 6: [xxxx] avail: 1 + // piece 6 does not have any blocks left to pick, even though piece 3 only + // has a single block left before it completes, it is less rare than piece + // 2. Piece 2 is the best pick in this case. + auto p = setup_picker("1112111", " ", "", "013700f"); + auto picked = pick_pieces(p, "*******", 1, 0, nullptr + , options | piece_picker::prioritize_partials, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(piece_index_t(2), 2) + || picked.front() == piece_block(piece_index_t(2), 3)); +} + +TORRENT_TEST(partial_piece_order_most_complete) +{ + // as a tie breaker, make sure downloading pieces closer to completion have + // higher priority. piece 3 is only 1 block from being completed, and should + // be picked + auto p = setup_picker("1111111", " ", "", "013700f"); + auto picked = pick_pieces(p, "*******", 1, 0, nullptr + , options | piece_picker::prioritize_partials, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(piece_index_t(3), 3)); +} + +TORRENT_TEST(partial_piece_order_sequential) +{ + // if we don't use rarest first when we prioritize partials, but instead use + // sequential order, make sure we pick the right one + auto p = setup_picker("1111111", " ", "", "013700f"); + auto picked = pick_pieces(p, "*******", 1, 0, nullptr + , piece_picker::sequential | piece_picker::prioritize_partials, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(piece_index_t(1), 1) + || picked.front() == piece_block(piece_index_t(1), 2) + || picked.front() == piece_block(piece_index_t(1), 3)); +} + +TORRENT_TEST(random_picking_downloading_piece) +{ + // make sure the random piece picker can still pick partial pieces + auto p = setup_picker("1111111", " ", "", "013700f"); + auto picked = pick_pieces(p, " *** *", 1, 0, nullptr + , {}, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(piece_index_t(1), 1) + || picked.front() == piece_block(piece_index_t(2), 2) + || picked.front() == piece_block(piece_index_t(3), 3)); +} + +TORRENT_TEST(random_picking_downloading_piece_prefer_contiguous) +{ + // make sure the random piece picker can still pick partial pieces + // even when prefer_contiguous_blocks is set + auto p = setup_picker("1111111", " ", "", "013700f"); + auto picked = pick_pieces(p, " *** *", 1, 4, nullptr + , {}, empty_vector); + TEST_CHECK(int(picked.size()) > 0); + TEST_CHECK(picked.front() == piece_block(piece_index_t(1), 1) + || picked.front() == piece_block(piece_index_t(2), 2) + || picked.front() == piece_block(piece_index_t(3), 3)); +} + +TORRENT_TEST(sequential_download) +{ + // test sequential download + auto p = setup_picker("7654321", " ", "", ""); + auto picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, nullptr + , piece_picker::sequential, empty_vector); + TEST_CHECK(int(picked.size()) == 7 * blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(i / blocks_per_piece) + , i % blocks_per_piece)); +} + +TORRENT_TEST(reverse_sequential_download) +{ + // test reverse sequential download + auto p = setup_picker("7654321", " ", "", ""); + auto picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, nullptr + , piece_picker::sequential | piece_picker::reverse, empty_vector); + TEST_CHECK(int(picked.size()) == 7 * blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(6 - (i / blocks_per_piece)) + , i % blocks_per_piece)); +} + +TORRENT_TEST(priority_sequential_download) +{ + // test priority sequential download + auto p = setup_picker("7654321", " ", "1117071", ""); + auto picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, nullptr + , piece_picker::sequential, empty_vector); + + // the piece with priority 0 was not picked, everything else should + // be picked + TEST_EQUAL(int(picked.size()), 6 * blocks_per_piece); + + // the first two pieces picked should be 3 and 5 since those have priority 7 + for (int i = 0; i < 2 * blocks_per_piece; ++i) + TEST_CHECK(picked[std::size_t(i)].piece_index == piece_index_t(3) || picked[std::size_t(i)].piece_index == piece_index_t(5)); + + int expected[] = {-1, -1, 0, 1, 2, 6}; + for (int i = 2 * blocks_per_piece; i < int(picked.size()); ++i) + TEST_EQUAL(picked[std::size_t(i)].piece_index, piece_index_t(expected[i / blocks_per_piece])); +} + +TORRENT_TEST(cursors_sweep_up_we_have) +{ + // sweep up, we_have() + auto p = setup_picker("7654321", " ", "", ""); + for (piece_index_t i(0); i < piece_index_t(7); ++i) + { + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->we_have(i); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); +} + +TORRENT_TEST(cursors_sweep_up_set_piece_priority) +{ + // sweep up, set_piece_priority() + auto p = setup_picker("7654321", " ", "", ""); + for (piece_index_t i(0); i < piece_index_t(7); ++i) + { + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->set_piece_priority(i, dont_download); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); +} + +TORRENT_TEST(cursors_sweep_down_we_have) +{ + // sweep down, we_have() + auto p = setup_picker("7654321", " ", "", ""); + for (piece_index_t i(6); i >= piece_index_t(0); --i) + { + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), next(i)); + p->we_have(i); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); +} + +TORRENT_TEST(cursors_sweep_down_set_piece_priority) +{ + // sweep down, set_piece_priority() + auto p = setup_picker("7654321", " ", "", ""); + for (piece_index_t i(6); i >= piece_index_t(0); --i) + { + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), next(i)); + p->set_piece_priority(i, dont_download); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); +} + +TORRENT_TEST(cursors_sweep_in_set_priority) +{ + // sweep in, set_piece_priority() + auto p = setup_picker("7654321", " ", "", ""); + for (piece_index_t left(0), right(6); left <= piece_index_t(3) + && right >= piece_index_t(3); ++left, --right) + { + TEST_EQUAL(p->cursor(), left); + TEST_EQUAL(p->reverse_cursor(), next(right)); + p->set_piece_priority(left, dont_download); + p->set_piece_priority(right, dont_download); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); +} + +TORRENT_TEST(cursors_sweep_in_we_have) +{ + // sweep in, we_have() + auto p = setup_picker("7654321", " ", "", ""); + for (piece_index_t left(0), right(6); left <= piece_index_t(3) + && right >= piece_index_t(3); ++left, --right) + { + TEST_EQUAL(p->cursor(), left); + TEST_EQUAL(p->reverse_cursor(), next(right)); + p->we_have(left); + p->we_have(right); + } + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); +} + +TORRENT_TEST(cursors_sweep_up_we_dont_have) +{ + auto p = setup_picker("7654321", "*******", "", ""); + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); + for (piece_index_t i(0); i < piece_index_t(7); ++i) + { + p->we_dont_have(i); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), next(i)); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); +} + +TORRENT_TEST(cursors_sweep_down_we_dont_have) +{ + auto p = setup_picker("7654321", "*******", "", ""); + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); + for (piece_index_t i(6); i >= piece_index_t(0); --i) + { + p->we_dont_have(i); + TEST_EQUAL(p->cursor(), i); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); +} + +TORRENT_TEST(cursors_sweep_out_we_dont_have) +{ + auto p = setup_picker("7654321", "*******", "", ""); + TEST_CHECK(p->is_finished()); + TEST_CHECK(p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); + for (piece_index_t left(3), right(3); + left >= piece_index_t(0) && right < piece_index_t(7); + --left, ++right) + { + p->we_dont_have(left); + p->we_dont_have(right); + TEST_EQUAL(p->cursor(), left); + TEST_EQUAL(p->reverse_cursor(), next(right)); + } + TEST_CHECK(!p->is_finished()); + TEST_CHECK(!p->is_seeding()); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); +} + +TORRENT_TEST(cursors) +{ + // test cursors + auto p = setup_picker("7654321", " ", "", ""); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->we_have(piece_index_t(1)); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->we_have(piece_index_t(0)); + TEST_EQUAL(p->cursor(), piece_index_t(2)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->we_have(piece_index_t(5)); + TEST_EQUAL(p->cursor(), piece_index_t(2)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->we_have(piece_index_t(6)); + TEST_EQUAL(p->cursor(), piece_index_t(2)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(5)); + p->we_have(piece_index_t(4)); + p->we_have(piece_index_t(3)); + p->we_have(piece_index_t(2)); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); + + p = setup_picker("7654321", " ", "", ""); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->set_piece_priority(piece_index_t(1), dont_download); + TEST_EQUAL(p->cursor(), piece_index_t(0)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->set_piece_priority(piece_index_t(0), dont_download); + TEST_EQUAL(p->cursor(), piece_index_t(2)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->set_piece_priority(piece_index_t(5), dont_download); + TEST_EQUAL(p->cursor(), piece_index_t(2)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(7)); + p->set_piece_priority(piece_index_t(6), dont_download); + TEST_EQUAL(p->cursor(), piece_index_t(2)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(5)); + p->set_piece_priority(piece_index_t(4), dont_download); + p->set_piece_priority(piece_index_t(3), dont_download); + p->set_piece_priority(piece_index_t(2), dont_download); + TEST_EQUAL(p->cursor(), piece_index_t(7)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(0)); + p->set_piece_priority(piece_index_t(3), low_priority); + TEST_EQUAL(p->cursor(), piece_index_t(3)); + TEST_EQUAL(p->reverse_cursor(), piece_index_t(4)); +} + +TORRENT_TEST(piece_priorities) +{ + // test piece priorities + auto p = setup_picker("5555555", " ", "7654321", ""); + TEST_EQUAL(p->want().num_pieces, 7); + TEST_EQUAL(p->have_want().num_pieces, 0); + p->set_piece_priority(piece_index_t(0), dont_download); + TEST_EQUAL(p->want().num_pieces, 6); + TEST_EQUAL(p->have_want().num_pieces, 0); + p->mark_as_finished({piece_index_t(0), 0}, nullptr); + p->we_have(piece_index_t(0)); + TEST_EQUAL(p->want().num_pieces, 6); + TEST_EQUAL(p->have_want().num_pieces, 0); + TEST_EQUAL(p->have().num_pieces, 1); + + p->we_dont_have(piece_index_t(0)); + p->set_piece_priority(piece_index_t(0), top_priority); + + auto picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, nullptr + , options, empty_vector); + TEST_CHECK(int(picked.size()) == 7 * blocks_per_piece); + + for (int i = 0; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(i / blocks_per_piece), i % blocks_per_piece)); + + // test changing priority on a piece we have + p->we_have(piece_index_t(0)); + p->set_piece_priority(piece_index_t(0), dont_download); + p->set_piece_priority(piece_index_t(0), low_priority); + p->set_piece_priority(piece_index_t(0), dont_download); + + std::vector prios; + p->piece_priorities(prios); + TEST_CHECK(prios.size() == 7); + download_priority_t prio_comp[] = {0_pri, 6_pri, 5_pri, 4_pri, 3_pri, 2_pri, 1_pri}; + TEST_CHECK(std::equal(prios.begin(), prios.end(), prio_comp)); +} + +TORRENT_TEST(restore_piece) +{ + // test restore_piece + auto p = setup_picker("1234567", " ", "", ""); + p->mark_as_finished({piece_index_t(0), 0}, nullptr); + p->mark_as_finished({piece_index_t(0), 1}, nullptr); + p->mark_as_finished({piece_index_t(0), 2}, nullptr); + p->mark_as_finished({piece_index_t(0), 3}, nullptr); + + auto picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == piece_index_t(1)); + + p->restore_piece(piece_index_t(0)); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == piece_index_t(0)); + + p->mark_as_finished({piece_index_t(0), 0}, nullptr); + p->mark_as_finished({piece_index_t(0), 1}, nullptr); + p->mark_as_finished({piece_index_t(0), 2}, nullptr); + p->mark_as_finished({piece_index_t(0), 3}, nullptr); + p->set_piece_priority(piece_index_t(0), dont_download); + + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == piece_index_t(1)); + + p->restore_piece(piece_index_t(0)); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == piece_index_t(1)); + + p->set_piece_priority(piece_index_t(0), top_priority); + picked = pick_pieces(p, "*******", 1, 0, nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 1); + TEST_CHECK(picked.front().piece_index == piece_index_t(0)); +} + +TORRENT_TEST(random_pick) +{ + // test random mode + auto p = setup_picker("1234567", " ", "1111122", ""); + std::set random_pieces; + for (int i = 0; i < 100; ++i) + random_pieces.insert(test_pick(p, {})); + TEST_CHECK(random_pieces.size() == 7); + + random_pieces.clear(); + for (int i = 0; i < 7; ++i) + { + piece_index_t const piece = test_pick(p, {}); + p->we_have(piece); + random_pieces.insert(piece); + } + TEST_CHECK(random_pieces.size() == 7); +} + +TORRENT_TEST(picking_downloading_blocks) +{ + // make sure the piece picker will pick pieces that + // are already requested from other peers if it has to + auto p = setup_picker("1111111", " ", "", ""); + p->mark_as_downloading({piece_index_t(2), 2}, &tmp1); + p->mark_as_downloading({piece_index_t(1), 2}, &tmp1); + + std::vector picked; + picked.clear(); + p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, nullptr + , piece_picker::prioritize_partials, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // don't pick both busy pieces, if there are already other blocks picked + TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 2); + + picked.clear(); + p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, nullptr + , piece_picker::prioritize_partials + | piece_picker::rarest_first, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // don't pick both busy pieces, if there are already other blocks picked + TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 2); + + picked.clear(); + p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, nullptr + , piece_picker::rarest_first, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // don't pick both busy pieces, if there are already other blocks picked + TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 2); + + // make sure we still pick from a partial piece even when prefering whole pieces + picked.clear(); + p->pick_pieces(string2vec(" * "), picked, 1, blocks_per_piece, nullptr + , piece_picker::rarest_first + | piece_picker::align_expanded_pieces, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); + TEST_CHECK(picked.size() >= 1 && picked[0].piece_index == piece_index_t(1)); + + // don't pick locked pieces + picked.clear(); + p->lock_piece(piece_index_t(1)); + p->pick_pieces(string2vec(" ** "), picked, 7, 0, nullptr + , piece_picker::rarest_first, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 3); + TEST_CHECK(picked.size() >= 1 && picked[0].piece_index == piece_index_t(2)); + + p->restore_piece(piece_index_t(1)); + p->mark_as_downloading({piece_index_t(2), 0}, &tmp1); + p->mark_as_downloading({piece_index_t(2), 1}, &tmp1); + p->mark_as_downloading({piece_index_t(2), 3}, &tmp1); + p->mark_as_downloading({piece_index_t(1), 0}, &tmp1); + p->mark_as_downloading({piece_index_t(1), 1}, &tmp1); + p->mark_as_downloading({piece_index_t(1), 2}, &tmp1); + p->mark_as_downloading({piece_index_t(1), 3}, &tmp1); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2, 0, nullptr + , piece_picker::rarest_first, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2 * blocks_per_piece, 0, nullptr + , piece_picker::prioritize_partials, empty_vector, 0 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2 * blocks_per_piece, 0, nullptr + , piece_picker::prioritize_partials, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); +} + +TORRENT_TEST(clear_peer) +{ + // test clear_peer + auto p = setup_picker("1123333", " ", "", ""); + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1); + p->mark_as_downloading({piece_index_t(0), 1}, &tmp2); + p->mark_as_downloading({piece_index_t(0), 2}, &tmp3); + p->mark_as_downloading({piece_index_t(1), 1}, &tmp1); + p->mark_as_downloading({piece_index_t(2), 1}, &tmp2); + p->mark_as_downloading({piece_index_t(3), 1}, &tmp3); + + std::vector dls; + torrent_peer* expected_dls1[] = {&tmp1, &tmp2, &tmp3, nullptr}; + torrent_peer* expected_dls2[] = {nullptr, &tmp1, nullptr, nullptr}; + torrent_peer* expected_dls3[] = {nullptr, &tmp2, nullptr, nullptr}; + torrent_peer* expected_dls4[] = {nullptr, &tmp3, nullptr, nullptr}; + torrent_peer* expected_dls5[] = {&tmp1, nullptr, &tmp3, nullptr}; + p->get_downloaders(dls, piece_index_t(0)); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls1)); + p->get_downloaders(dls, piece_index_t(1)); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls2)); + p->get_downloaders(dls, piece_index_t(2)); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls3)); + p->get_downloaders(dls, piece_index_t(3)); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls4)); + + p->clear_peer(&tmp2); + p->get_downloaders(dls, piece_index_t(0)); + TEST_CHECK(std::equal(dls.begin(), dls.end(), expected_dls5)); +} + +TORRENT_TEST(have_all_have_none) +{ + // test have_all and have_none + auto p = setup_picker("0123333", "* ", "", ""); + auto dc = p->distributed_copies(); + std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; + TEST_CHECK(dc == std::make_pair(1, 5000 / 7)); + p->inc_refcount_all(&tmp8); + dc = p->distributed_copies(); + TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); + p->dec_refcount_all(&tmp8); + dc = p->distributed_copies(); + std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; + TEST_CHECK(dc == std::make_pair(1, 5000 / 7)); + p->inc_refcount(piece_index_t(0), &tmp0); + p->dec_refcount_all(&tmp0); + dc = p->distributed_copies(); + std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; + TEST_CHECK(dc == std::make_pair(0, 6000 / 7)); + TEST_CHECK(test_pick(p) == piece_index_t(2)); +} + +TORRENT_TEST(have_all_have_none_seq_download) +{ + // test have_all and have_none + auto p = setup_picker("0123333", "* ", "", ""); + auto dc = p->distributed_copies(); + std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; + TEST_CHECK(dc == std::make_pair(1, 5000 / 7)); + p->inc_refcount_all(&tmp8); + dc = p->distributed_copies(); + std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; + TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); + TEST_CHECK(test_pick(p) == piece_index_t(1)); +} + +TORRENT_TEST(inc_ref_dec_ref) +{ + // test inc_ref and dec_ref + auto p = setup_picker("1233333", " * ", "", ""); + TEST_CHECK(test_pick(p) == piece_index_t(0)); + + p->dec_refcount(piece_index_t(0), &tmp0); + TEST_CHECK(test_pick(p) == piece_index_t(1)); + + p->dec_refcount(piece_index_t(4), &tmp0); + p->dec_refcount(piece_index_t(4), &tmp1); + TEST_CHECK(test_pick(p) == piece_index_t(4)); + + // decrease refcount on something that's not in the piece list + p->dec_refcount(piece_index_t(5), &tmp0); + p->inc_refcount(piece_index_t(5), &tmp0); + + typed_bitfield bits = string2vec("* "); + TEST_EQUAL(bits.get_bit(piece_index_t(0)), true); + TEST_EQUAL(bits.get_bit(piece_index_t(1)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(2)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(3)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(4)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(5)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(6)), false); + p->inc_refcount(bits, &tmp0); + bits = string2vec(" * "); + + TEST_EQUAL(bits.get_bit(piece_index_t(0)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(1)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(2)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(3)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(4)), true); + TEST_EQUAL(bits.get_bit(piece_index_t(5)), false); + TEST_EQUAL(bits.get_bit(piece_index_t(6)), false); + p->dec_refcount(bits, &tmp2); + TEST_EQUAL(test_pick(p), piece_index_t(0)); +} + +TORRENT_TEST(prefer_cnotiguous_blocks) +{ + // test prefer_contiguous_blocks + auto p = setup_picker("1111111", " ", "", ""); + auto picked = pick_pieces(p, "*******", 1, 3 * blocks_per_piece + , nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 3 * blocks_per_piece); + piece_block b = picked.front(); + for (std::size_t i = 1; i < picked.size(); ++i) + { + TEST_CHECK(static_cast(picked[i].piece_index) * blocks_per_piece + picked[i].block_index + == static_cast(b.piece_index) * blocks_per_piece + b.block_index + 1); + b = picked[i]; + } + + picked = pick_pieces(p, "*******", 1, 3 * blocks_per_piece + , nullptr, options, empty_vector); + TEST_CHECK(int(picked.size()) >= 3 * blocks_per_piece); + b = picked.front(); + for (std::size_t i = 1; i < picked.size(); ++i) + { + TEST_CHECK(static_cast(picked[i].piece_index) * blocks_per_piece + picked[i].block_index + == static_cast(b.piece_index) * blocks_per_piece + b.block_index + 1); + b = picked[i]; + } + + // make sure pieces that don't match the 'whole pieces' requirement + // are picked if there's no other choice + p = setup_picker("1111111", " ", "", ""); + p->mark_as_downloading({piece_index_t(2), 2}, &tmp1); + picked = pick_pieces(p, "*******", 7 * blocks_per_piece - 1, blocks_per_piece + , nullptr, options, empty_vector); + TEST_CHECK(picked.size() == 7 * blocks_per_piece - 1); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(piece_index_t(2), 2)) == picked.end()); +} + +TORRENT_TEST(prefer_aligned_whole_pieces) +{ + auto p = setup_picker("2222221222222222", " ", "", ""); + auto picked = pick_pieces(p, "****************", 1, 4 * blocks_per_piece, nullptr + , options | piece_picker::align_expanded_pieces, empty_vector); + + // the piece picker should pick piece 5, and then align it to even 4 pieces + // i.e. it should have picked pieces: 4,5,6,7 + print_pick(picked); + TEST_EQUAL(picked.size(), 4 * blocks_per_piece); + + std::set picked_pieces; + for (auto idx : picked) picked_pieces.insert(idx.piece_index); + + TEST_CHECK(picked_pieces.size() == 4); + piece_index_t expected_pieces[] = {piece_index_t(4),piece_index_t(5),piece_index_t(6),piece_index_t(7)}; + TEST_CHECK(std::equal(picked_pieces.begin(), picked_pieces.end(), expected_pieces)); +} + +TORRENT_TEST(parole_mode) +{ + // test parole mode + auto p = setup_picker("3333133", " ", "", ""); + p->mark_as_finished({piece_index_t(0), 0}, nullptr); + auto picked = pick_pieces(p, "*******", 1, blocks_per_piece, nullptr + + , options | piece_picker::on_parole | piece_picker::prioritize_partials, empty_vector); + TEST_EQUAL(int(picked.size()), blocks_per_piece - 1); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(0), i + 1)); + + // make sure that the partial piece is not picked by a + // peer that is has not downloaded/requested the other blocks + picked = pick_pieces(p, "*******", 1, blocks_per_piece + , &peer_struct + , options | piece_picker::on_parole | piece_picker::prioritize_partials, empty_vector); + TEST_EQUAL(int(picked.size()), blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(4), i)); +} + +TORRENT_TEST(suggested_pieces) +{ + // test suggested pieces + auto p = setup_picker("1111222233334444", " ", "", ""); + int v[] = {1, 5}; + const std::vector suggested_pieces(v, v + 2); + + auto picked = pick_pieces(p, "****************", 1, blocks_per_piece + , nullptr, options, suggested_pieces); + TEST_CHECK(int(picked.size()) >= blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(1), i)); + p->set_piece_priority(piece_index_t(0), dont_download); + p->set_piece_priority(piece_index_t(1), dont_download); + p->set_piece_priority(piece_index_t(2), dont_download); + p->set_piece_priority(piece_index_t(3), dont_download); + + picked = pick_pieces(p, "****************", 1, blocks_per_piece + , nullptr, options, suggested_pieces); + TEST_CHECK(int(picked.size()) >= blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(5), i)); + + p = setup_picker("1111222233334444", "**** ", "", ""); + picked = pick_pieces(p, "****************", 1, blocks_per_piece + , nullptr, options, suggested_pieces); + TEST_CHECK(int(picked.size()) >= blocks_per_piece); + for (int i = 1; i < int(picked.size()); ++i) + TEST_CHECK(picked[std::size_t(i)] == piece_block(piece_index_t(5), i)); +} + +TORRENT_TEST(bitfield_optimization) +{ + // test bitfield optimization + // we have less than half of the pieces + auto p = setup_picker("2122222211221222", " ", "", ""); + // make sure it's not dirty + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + print_availability(p); + p->dec_refcount(string2vec("** ** ** * "), &tmp0); + print_availability(p); + TEST_CHECK(verify_availability(p, "1022112200220222")); + // make sure it's not dirty + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + p->inc_refcount(string2vec(" ** ** * * "), &tmp8); + print_availability(p); + TEST_CHECK(verify_availability(p, "1132123201220322")); +} + +TORRENT_TEST(seed_optimization) +{ + // test seed optimizaton + auto p = setup_picker("0000000000000000", " ", "", ""); + + // make sure it's not dirty + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + + p->inc_refcount_all(&tmp0); + print_availability(p); + TEST_CHECK(verify_availability(p, "1111111111111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + p->dec_refcount(string2vec(" **** ** "), &tmp0); + print_availability(p); + TEST_CHECK(verify_availability(p, "1100001100111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + p->inc_refcount(string2vec(" **** ** "), &tmp0); + TEST_CHECK(verify_availability(p, "1111111111111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + p->dec_refcount_all(&tmp0); + TEST_CHECK(verify_availability(p, "0000000000000000")); + + p->inc_refcount_all(&tmp1); + print_availability(p); + TEST_CHECK(verify_availability(p, "1111111111111111")); + + pick_pieces(p, "****************", 1, blocks_per_piece, nullptr); + p->dec_refcount(piece_index_t(3), &tmp1); + print_availability(p); + TEST_CHECK(verify_availability(p, "1110111111111111")); + + p->inc_refcount(string2vec("****************"), &tmp2); + print_availability(p); + TEST_CHECK(verify_availability(p, "2221222222222222")); + + p->inc_refcount(string2vec("* * * * * * * * "), &tmp3); + print_availability(p); + TEST_CHECK(verify_availability(p, "3231323232323232")); + + p->dec_refcount(string2vec("****************"), &tmp2); + print_availability(p); + TEST_CHECK(verify_availability(p, "2120212121212121")); + + p->dec_refcount(string2vec("* * * * * * * * "), &tmp3); + print_availability(p); + TEST_CHECK(verify_availability(p, "1110111111111111")); +} + +TORRENT_TEST(reversed_peers) +{ + // test reversed peers + auto p = setup_picker("3333333", " *****", "", ""); + + // a reversed peer picked a block from piece 0 + // This should make the piece reversed + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1 + , piece_picker::reverse); + + TEST_EQUAL(test_pick(p, piece_picker::rarest_first), piece_index_t(1)); + + // make sure another reversed peer pick the same piece + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::reverse), piece_index_t(0)); +} + +TORRENT_TEST(reversed_piece_upgrade) +{ + // test reversed pieces upgrading to normal pieces + auto p = setup_picker("3333333", " *****", "", ""); + + // make piece 0 partial and reversed + p->mark_as_downloading({piece_index_t(0), 1}, &tmp1 + , piece_picker::reverse); + TEST_EQUAL(test_pick(p), piece_index_t(1)); + + // now have a regular peer pick the reversed block. It should now + // have turned into a regular one and be prioritized + p->mark_as_downloading({piece_index_t(0), 2}, &tmp1); + TEST_EQUAL(test_pick(p), piece_index_t(0)); +} + +TORRENT_TEST(reversed_piece_downgrade) +{ +// test pieces downgrading to reversed pieces +// now make sure a piece can be demoted to reversed if there are no +// other outstanding requests + + auto p = setup_picker("3333333", " ", "", ""); + + // make piece 0 partial and not reversed + p->mark_as_finished({piece_index_t(0), 1}, &tmp1); + + // a reversed peer picked a block from piece 0 + // This should make the piece reversed + p->mark_as_downloading({piece_index_t(0), 0}, &tmp1 + , piece_picker::reverse); + + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::reverse), piece_index_t(0)); +} + +TORRENT_TEST(piece_stats) +{ + auto p = setup_picker("3456789", "* ", "", "0300000"); + + piece_picker::piece_stats_t stat = p->piece_stats(piece_index_t(0)); + TEST_EQUAL(stat.peer_count, 3); + TEST_EQUAL(stat.have, 1); + TEST_EQUAL(stat.downloading, 0); + + stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.peer_count, 4); + TEST_EQUAL(stat.have, 0); + TEST_EQUAL(stat.downloading, 1); +} + +TORRENT_TEST(piece_passed) +{ + auto p = setup_picker("1111111", "* ", "", "0300000"); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + + p->piece_passed(piece_index_t(1)); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->have().num_pieces, 1); + + p->we_have(piece_index_t(1)); + TEST_EQUAL(p->have().num_pieces, 2); + + p->mark_as_finished({piece_index_t(2), 0}, &tmp1); + p->piece_passed(piece_index_t(2)); + TEST_EQUAL(p->num_passed(), 3); + // just because the hash check passed doesn't mean + // we "have" the piece. We need to write it to disk first + TEST_EQUAL(p->have().num_pieces, 2); + + // piece 2 already passed the hash check, as soon as we've + // written all the blocks to disk, we should have that piece too + p->mark_as_finished({piece_index_t(2), 1}, &tmp1); + p->mark_as_finished({piece_index_t(2), 2}, &tmp1); + p->mark_as_finished({piece_index_t(2), 3}, &tmp1); + TEST_EQUAL(p->have().num_pieces, 3); + TEST_EQUAL(p->have_piece(piece_index_t(2)), true); +} + +TORRENT_TEST(piece_passed_causing_we_have) +{ + auto p = setup_picker("1111111", "* ", "", "0700000"); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + + p->mark_as_finished({piece_index_t(1), 3}, &tmp1); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + + p->piece_passed(piece_index_t(1)); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->have().num_pieces, 2); +} + +TORRENT_TEST(break_one_seed) +{ + auto p = setup_picker("0000000", "* ", "", "0700000"); + p->inc_refcount_all(&tmp1); + p->inc_refcount_all(&tmp2); + p->inc_refcount_all(&tmp3); + + TEST_EQUAL(p->piece_stats(piece_index_t(0)).peer_count, 3); + + p->dec_refcount(piece_index_t(0), &tmp1); + + TEST_EQUAL(p->piece_stats(piece_index_t(0)).peer_count, 2); + TEST_EQUAL(p->piece_stats(piece_index_t(1)).peer_count, 3); + TEST_EQUAL(p->piece_stats(piece_index_t(2)).peer_count, 3); + TEST_EQUAL(p->piece_stats(piece_index_t(3)).peer_count, 3); +} + +TORRENT_TEST(we_dont_have2) +{ + auto p = setup_picker("1111111", "* * ", "1101111", ""); + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->has_piece_passed(piece_index_t(2)), true); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->have().num_pieces, 2); + TEST_EQUAL(p->have_want().num_pieces, 1); + TEST_EQUAL(p->want().num_pieces, 6); + + p->we_dont_have(piece_index_t(0)); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), false); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->has_piece_passed(piece_index_t(2)), true); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + TEST_EQUAL(p->have_want().num_pieces, 0); + + p = setup_picker("1111111", "* * ", "1101111", ""); + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->has_piece_passed(piece_index_t(2)), true); + TEST_EQUAL(p->num_passed(), 2); + TEST_EQUAL(p->have().num_pieces, 2); + TEST_EQUAL(p->have_want().num_pieces, 1); + TEST_EQUAL(p->want().num_pieces, 6); + + p->we_dont_have(piece_index_t(2)); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->has_piece_passed(piece_index_t(2)), false); + TEST_EQUAL(p->num_passed(), 1); + TEST_EQUAL(p->have().num_pieces, 1); + TEST_EQUAL(p->have_want().num_pieces, 1); + TEST_EQUAL(p->want().num_pieces, 6); +} + +TORRENT_TEST(dont_have_but_passed_hash_check) +{ + auto p = setup_picker("1111111", "* * ", "1101111", "0200000"); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->have_piece(piece_index_t(0)), true); + TEST_EQUAL(p->have_piece(piece_index_t(1)), false); + + p->piece_passed(piece_index_t(1)); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), true); + TEST_EQUAL(p->have_piece(piece_index_t(1)), false); + + p->we_dont_have(piece_index_t(1)); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->have_piece(piece_index_t(1)), false); +} + +TORRENT_TEST(write_failed) +{ + auto p = setup_picker("1111111", "* * ", "1101111", "0200000"); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->have_piece(piece_index_t(1)), false); + + p->piece_passed(piece_index_t(1)); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), true); + TEST_EQUAL(p->have_piece(piece_index_t(1)), false); + + p->mark_as_writing({piece_index_t(1), 0}, &tmp1); + p->write_failed({piece_index_t(1), 0}); + + TEST_EQUAL(p->has_piece_passed(piece_index_t(0)), true); + TEST_EQUAL(p->has_piece_passed(piece_index_t(1)), false); + TEST_EQUAL(p->have_piece(piece_index_t(1)), false); + + // make sure write_failed() and lock_piece() actually + // locks the piece, and that it won't be picked. + // also make sure restore_piece() unlocks it and makes + // it available for picking again. + + auto picked = pick_pieces(p, " * ", 1, blocks_per_piece, nullptr); + TEST_EQUAL(picked.size(), 0); + + p->restore_piece(piece_index_t(1)); + + picked = pick_pieces(p, " * ", 1, blocks_per_piece, nullptr); + TEST_EQUAL(picked.size(), blocks_per_piece); + + // locking pieces only works on partial pieces + p->mark_as_writing({piece_index_t(1), 0}, &tmp1); + p->lock_piece(piece_index_t(1)); + + picked = pick_pieces(p, " * ", 1, blocks_per_piece, nullptr); + TEST_EQUAL(picked.size(), 0); +} + +TORRENT_TEST(write_failed_clear_piece) +{ + auto p = setup_picker("1111111", "* * ", "1101111", ""); + + auto stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.downloading, 0); + + p->mark_as_writing({piece_index_t(1), 0}, &tmp1); + + stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.downloading, 1); + + p->write_failed({piece_index_t(1), 0}); + + stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.downloading, 0); +} + +TORRENT_TEST(mark_as_canceled) +{ + auto p = setup_picker("1111111", "* * ", "1101111", ""); + + auto stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.downloading, 0); + + p->mark_as_writing({piece_index_t(1), 0}, &tmp1); + + stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.downloading, 1); + + p->mark_as_canceled({piece_index_t(1), 0}, &tmp1); + stat = p->piece_stats(piece_index_t(1)); + TEST_EQUAL(stat.downloading, 0); +} + +TORRENT_TEST(get_download_queue) +{ + auto picker = setup_picker("1111111", " ", "1101111", "0327000"); + + auto const downloads = picker->get_download_queue(); + + // the download queue should have piece 1, 2 and 3 in it + TEST_EQUAL(downloads.size(), 3); + + TEST_EQUAL(std::count_if(downloads.begin(), downloads.end() + , [](piece_picker::downloading_piece const& p) { return p.index == piece_index_t(1); }), 1); + TEST_EQUAL(std::count_if(downloads.begin(), downloads.end() + , [](piece_picker::downloading_piece const& p) { return p.index == piece_index_t(2); }), 1); + TEST_EQUAL(std::count_if(downloads.begin(), downloads.end() + , [](piece_picker::downloading_piece const& p) { return p.index == piece_index_t(3); }), 1); +} + +TORRENT_TEST(get_download_queue_size) +{ + auto p = setup_picker("1111111", " ", "1111111", "0327ff0"); + + TEST_EQUAL(p->get_download_queue_size(), 5); + + p->set_piece_priority(piece_index_t(1), dont_download); + + int partial; + int full; + int finished; + int zero_prio; + p->get_download_queue_sizes(&partial, &full, &finished, &zero_prio); + + TEST_EQUAL(partial, 2); + TEST_EQUAL(full, 0); + TEST_EQUAL(finished, 2); + TEST_EQUAL(zero_prio, 1); +} + +TORRENT_TEST(time_critical_mode) +{ + auto p = setup_picker("1111111", " ", "1654741", "0352000"); + + // rarest-first + auto picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::rarest_first | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); + + // reverse rarest-first + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::reverse | piece_picker::rarest_first + | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); + + // sequential + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::sequential | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); + + // reverse sequential + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::reverse | piece_picker::sequential + | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); + + // just critical + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); + + // prioritize_partials + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::prioritize_partials | piece_picker::time_critical_mode, empty_vector); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); + + // even when a non-critical piece is suggested should we ignore it + int v[] = {1, 5}; + const std::vector suggested_pieces(v, v + 2); + + picked = pick_pieces(p, "*******", 7 * blocks_per_piece, 0, tmp_peer + , piece_picker::rarest_first | piece_picker::time_critical_mode + , suggested_pieces); + TEST_EQUAL(picked.size(), blocks_per_piece); + for (int i = 0; i < int(picked.size()); ++i) + TEST_EQUAL(picked[0].piece_index, piece_index_t(4)); +} + +TORRENT_TEST(reprioritize_downloading) +{ + auto p = setup_picker("1111111", " ", "", ""); + bool ret; + + ret = p->mark_as_downloading({piece_index_t(0), 0}, tmp_peer); + TEST_EQUAL(ret, true); + p->mark_as_finished({piece_index_t(0), 1}, tmp_peer); + ret = p->mark_as_writing({piece_index_t(0), 2}, tmp_peer); + TEST_EQUAL(ret, true); + + // make sure we pick the partial piece (i.e. piece 0) + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::prioritize_partials), piece_index_t(0)); + + // set the priority of the piece to 0 (while downloading it) + ret = p->set_piece_priority(piece_index_t(0), dont_download); + TEST_EQUAL(ret, true); + + // make sure we _DON'T_ pick the partial piece, since it has priority zero + piece_index_t const picked_piece = test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials); + TEST_NE(picked_piece, piece_index_t(-1)); + TEST_NE(picked_piece, piece_index_t(0)); + + // set the priority of the piece back to 1. It should now be the best pick + // again (since it's partial) + ret = p->set_piece_priority(piece_index_t(0), low_priority); + TEST_EQUAL(ret, true); + + // make sure we pick the partial piece + TEST_EQUAL(test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials), piece_index_t(0)); +} + +TORRENT_TEST(reprioritize_fully_downloading) +{ + auto p = setup_picker("1111111", " ", "", ""); + bool ret; + + for (int i = 0; i < blocks_per_piece; ++i) + { + ret = p->mark_as_downloading(piece_block(piece_index_t(0), i), tmp_peer); + TEST_EQUAL(ret, true); + } + + // make sure we _DON'T_ pick the downloading piece + { + piece_index_t const picked_piece = test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials); + TEST_NE(picked_piece, piece_index_t(-1)); + TEST_NE(picked_piece, piece_index_t(0)); + } + + // set the priority of the piece to 0 (while downloading it) + ret = p->set_piece_priority(piece_index_t(0), dont_download); + TEST_EQUAL(ret, true); + + // make sure we still _DON'T_ pick the downloading piece + { + piece_index_t const picked_piece = test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials); + TEST_NE(picked_piece, piece_index_t(-1)); + TEST_NE(picked_piece, piece_index_t(0)); + } + + // set the priority of the piece back to 1. It should now be the best pick + // again (since it's partial) + ret = p->set_piece_priority(piece_index_t(0), low_priority); + TEST_EQUAL(ret, true); + + // make sure we still _DON'T_ pick the downloading piece + { + piece_index_t const picked_piece = test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials); + TEST_NE(picked_piece, piece_index_t(-1)); + TEST_NE(picked_piece, piece_index_t(0)); + } +} + +TORRENT_TEST(download_filtered_piece) +{ + auto p = setup_picker("1111111", " ", "", ""); + bool ret; + + // set the priority of the piece to 0 + ret = p->set_piece_priority(piece_index_t(0), dont_download); + TEST_EQUAL(ret, true); + + // make sure we _DON'T_ pick piece 0 + { + piece_index_t const picked_piece = test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials); + TEST_NE(picked_piece, piece_index_t(-1)); + TEST_NE(picked_piece, piece_index_t(0)); + } + + // then mark it for downloading + ret = p->mark_as_downloading({piece_index_t(0), 0}, tmp_peer); + TEST_EQUAL(ret, true); + p->mark_as_finished({piece_index_t(0), 1}, tmp_peer); + ret = p->mark_as_writing({piece_index_t(0), 2}, tmp_peer); + TEST_EQUAL(ret, true); + + { + // we still should not pick it + piece_index_t const picked_piece = test_pick(p, piece_picker::rarest_first + | piece_picker::prioritize_partials); + TEST_NE(picked_piece, piece_index_t(-1)); + TEST_NE(picked_piece, piece_index_t(0)); + } + + // set the priority of the piece back to 1. It should now be the best pick + // again (since it's partial) + ret = p->set_piece_priority(piece_index_t(0), low_priority); + TEST_EQUAL(ret, true); + + // make sure we pick piece 0 + TEST_EQUAL(test_pick(p, piece_picker::rarest_first | piece_picker::prioritize_partials), piece_index_t(0)); +} + +TORRENT_TEST(mark_as_pad) +{ + auto p = setup_picker("1111111", " ", "4444444", ""); + piece_block const bl(piece_index_t{2}, 0); + p->mark_as_pad(bl); + + bool ret = p->mark_as_downloading({piece_index_t{2}, 1}, tmp_peer); + TEST_EQUAL(ret, true); + + auto dl = p->get_download_queue(); + + TEST_EQUAL(dl.size(), 1); + TEST_EQUAL(dl[0].finished, 1); + TEST_EQUAL(dl[0].writing, 0); + TEST_EQUAL(dl[0].requested, 1); + TEST_EQUAL(dl[0].index, piece_index_t{2}); + + auto blocks = p->blocks_for_piece(dl[0]); + TEST_EQUAL(blocks[0].state, piece_picker::block_info::state_finished); + TEST_EQUAL(blocks[1].state, piece_picker::block_info::state_requested); + TEST_EQUAL(blocks[2].state, piece_picker::block_info::state_none); + TEST_EQUAL(blocks[3].state, piece_picker::block_info::state_none); +} + +TORRENT_TEST(mark_as_pad_downloading) +{ + auto p = setup_picker("1111111", " ", "4444444", ""); + piece_block const bl(piece_index_t{2}, 0); + p->mark_as_pad(bl); + + bool ret = p->mark_as_downloading({piece_index_t{2}, 0}, tmp_peer); + TEST_EQUAL(ret, false); + + auto dl = p->get_download_queue(); + + TEST_EQUAL(dl.size(), 1); + TEST_EQUAL(dl[0].finished, 1); + TEST_EQUAL(dl[0].writing, 0); + TEST_EQUAL(dl[0].requested, 0); + TEST_EQUAL(dl[0].index, piece_index_t{2}); + + auto blocks = p->blocks_for_piece(dl[0]); + TEST_EQUAL(blocks[0].state, piece_picker::block_info::state_finished); + TEST_EQUAL(blocks[1].state, piece_picker::block_info::state_none); + TEST_EQUAL(blocks[2].state, piece_picker::block_info::state_none); + TEST_EQUAL(blocks[3].state, piece_picker::block_info::state_none); +} + +TORRENT_TEST(mark_as_pad_seeding) +{ + auto p = setup_picker("1", " ", "4", ""); + p->mark_as_pad({piece_index_t{0}, 0}); + p->mark_as_pad({piece_index_t{0}, 1}); + p->mark_as_pad({piece_index_t{0}, 2}); + + TEST_CHECK(!p->is_seeding()); + + p->mark_as_finished({piece_index_t{0}, 3}, tmp_peer); + + TEST_CHECK(!p->is_seeding()); + p->piece_passed(piece_index_t{0}); + TEST_CHECK(p->is_seeding()); +} + +TORRENT_TEST(mark_as_pad_whole_piece_seeding) +{ + auto p = setup_picker("11", " ", "44", ""); + p->mark_as_pad({piece_index_t{0}, 0}); + p->mark_as_pad({piece_index_t{0}, 1}); + p->mark_as_pad({piece_index_t{0}, 2}); + p->mark_as_pad({piece_index_t{0}, 3}); + TEST_CHECK(p->have_piece(piece_index_t{0})); + + TEST_CHECK(!p->is_seeding()); + + p->mark_as_finished({piece_index_t{1}, 0}, nullptr); + p->mark_as_finished({piece_index_t{1}, 1}, nullptr); + p->mark_as_finished({piece_index_t{1}, 2}, nullptr); + p->mark_as_finished({piece_index_t{1}, 3}, nullptr); + + TEST_CHECK(!p->is_seeding()); + p->piece_passed(piece_index_t{1}); + TEST_CHECK(p->is_seeding()); +} + +TORRENT_TEST(pad_blocks_in_piece) +{ + auto p = setup_picker("11", " ", "44", ""); + p->mark_as_pad({piece_index_t{0}, 0}); + p->mark_as_pad({piece_index_t{0}, 1}); + p->mark_as_pad({piece_index_t{0}, 2}); + + TEST_EQUAL(p->pad_blocks_in_piece(piece_index_t{0}), 3); + TEST_EQUAL(p->pad_blocks_in_piece(piece_index_t{1}), 0); +} + +TORRENT_TEST(pad_blocks_in_last_piece) +{ + auto p = setup_picker("11", " ", "44", ""); + p->mark_as_pad({piece_index_t{1}, 0}); + p->mark_as_pad({piece_index_t{1}, 1}); + p->mark_as_pad({piece_index_t{1}, 2}); + + TEST_EQUAL(p->pad_blocks_in_piece(piece_index_t{1}), 3); + TEST_EQUAL(p->pad_blocks_in_piece(piece_index_t{0}), 0); +} + +namespace { +void validate_piece_count(piece_count const& c) +{ + // it's an impossible combination to have 0 pieces, but still have one of them be the last piece + TEST_CHECK(!(c.num_pieces == 0 && c.last_piece == true)); + + // if we have 0 pieces, we can't have any pad blocks either + TEST_CHECK(!(c.num_pieces == 0 && c.pad_blocks > 0)); + + // if we have all pieces, we must also have the last one + TEST_CHECK(!(c.num_pieces == 4 && c.last_piece == false)); +} + +void validate_all_pieces(piece_count const& c) +{ + TEST_EQUAL(c.last_piece, true); + TEST_EQUAL(c.num_pieces, 4); + TEST_EQUAL(c.pad_blocks, 3); +} + +void validate_no_pieces(piece_count const& c) +{ + TEST_EQUAL(c.last_piece, false); + TEST_EQUAL(c.num_pieces, 0); + TEST_EQUAL(c.pad_blocks, 0); +} +} + +TORRENT_TEST(pad_blocks_all_filtered) +{ + auto p = setup_picker("1111", " ", "0000", ""); + p->mark_as_pad({piece_index_t{1}, 0}); + p->mark_as_pad({piece_index_t{1}, 1}); + p->mark_as_pad({piece_index_t{2}, 0}); + + validate_piece_count(p->all_pieces()); + validate_piece_count(p->have()); + validate_piece_count(p->have_want()); + validate_piece_count(p->want()); + + validate_all_pieces(p->all_pieces()); + validate_no_pieces(p->have()); + validate_no_pieces(p->have_want()); + validate_no_pieces(p->want()); +} + +TORRENT_TEST(pad_blocks_all_wanted) +{ + auto p = setup_picker("1111", " ", "4444", ""); + p->mark_as_pad({piece_index_t{1}, 0}); + p->mark_as_pad({piece_index_t{1}, 1}); + p->mark_as_pad({piece_index_t{2}, 0}); + + validate_piece_count(p->all_pieces()); + validate_piece_count(p->have()); + validate_piece_count(p->have_want()); + validate_piece_count(p->want()); + + validate_all_pieces(p->all_pieces()); + validate_all_pieces(p->want()); + validate_no_pieces(p->have()); + validate_no_pieces(p->have_want()); +} + +TORRENT_TEST(pad_blocks_some_wanted) +{ + auto p = setup_picker("1111", " ", "0404", ""); + p->mark_as_pad({piece_index_t{1}, 0}); + p->mark_as_pad({piece_index_t{1}, 1}); + p->mark_as_pad({piece_index_t{2}, 0}); + + validate_piece_count(p->all_pieces()); + validate_piece_count(p->have()); + validate_piece_count(p->have_want()); + validate_piece_count(p->want()); + + validate_all_pieces(p->all_pieces()); + validate_no_pieces(p->have()); + validate_no_pieces(p->have_want()); + + TEST_EQUAL(p->want().num_pieces, 2); + TEST_EQUAL(p->want().last_piece, true); + TEST_EQUAL(p->want().pad_blocks, 2); +} + +namespace { + +std::vector full_piece(int const p, int const blocks) +{ + std::vector ret; + for (int i = 0;i < blocks; ++i) + ret.push_back(piece_block(piece_index_t{p}, i)); + return ret; +} +void mark_downloading(std::shared_ptr const& p, std::vector const blocks + , torrent_peer* const peer, picker_options_t const opts) +{ + for (auto const& b : blocks) + p->mark_as_downloading(b, peer, opts); +} +} + +TORRENT_TEST(piece_extent_affinity) +{ + int const blocks = 64; + // these are 2 extents. the first 4 pieces and the last 4 pieces + auto const have_none = " "; + auto const have_all = "********"; + + auto p = setup_picker("33133233", have_none, "", "", blocks); + + std::vector picked = pick_pieces(p, have_all, blocks, 0, &tmp0 + , options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(2, blocks)); + mark_downloading(p, full_piece(2, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + + // without the piece_extent_affinity, we would pick piece 5, because of + // availability + picked = pick_pieces(p, have_all, blocks, 0, &tmp1); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(5, blocks)); + mark_downloading(p, full_piece(5, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + + // with piece_extent_affinity, we would pick piece 0, because it's the same + // extent as the piece we just picked + picked = pick_pieces(p, have_all, blocks, 0, &tmp2, options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(0, blocks)); + mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + + // then we should pick piece 1 + picked = pick_pieces(p, have_all, blocks, 0, &tmp3, options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(1, blocks)); + mark_downloading(p, full_piece(1, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + + // then we should pick piece 3. The last piece of the extent + picked = pick_pieces(p, have_all, blocks, 0, &tmp4, options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(3, blocks)); + mark_downloading(p, full_piece(3, blocks), &tmp0, options | piece_picker::piece_extent_affinity); +} + +TORRENT_TEST(piece_extent_affinity_priority) +{ + int const blocks = 64; + auto const have_none = " "; + auto const have_all = "********"; + + auto p = setup_picker("33333233", have_none, "43444444", "", blocks); + // we pick piece 2. Since piece 1 has a different priority this should not + // create an affinity for the extent + mark_downloading(p, full_piece(2, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + + // so next piece to be picked will *not* be the extent, but piece 5, which + // has the lowest availability + + std::vector picked = pick_pieces(p, have_all, blocks, 0, &tmp1 + , options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(5, blocks)); +} + +TORRENT_TEST(piece_extent_affinity_large_pieces) +{ + int const blocks = 256; + auto const have_none = " "; + auto const have_all = "********"; + + auto p = setup_picker("33333233", have_none, "", "", blocks); + // we pick piece 2. Since the pieces are so large (4 MiB), there is no + // affinity for piece extents. + mark_downloading(p, full_piece(2, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + + // so next piece to be picked will *not* be the extent, but piece 5, which + // has the next lowest availability + std::vector picked = pick_pieces(p, have_all, blocks, 0, &tmp1 + , options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(5, blocks)); +} + +TORRENT_TEST(piece_extent_affinity_active_limit) +{ + // an extent is two pieces wide, 6 extents total. + // make ure we limit the number of extents to 5 + int const blocks = 128; + auto const have_none = " "; + + auto p = setup_picker("333333333333", have_none, "444444444455", "", blocks); + // open up the first 5 extents + mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(2, blocks), &tmp1, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(4, blocks), &tmp2, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(6, blocks), &tmp3, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(8, blocks), &tmp4, options | piece_picker::piece_extent_affinity); + + // this should not open up another extent. We should still have a bias + // towards pieces 1, 3, 5, 7 and 9. + mark_downloading(p, full_piece(10, blocks), &tmp5, options | piece_picker::piece_extent_affinity); + + // a peer that only has piece 0, 1, 10, 11, will always pick 1, never 11, + // even though 10 and 11 have higher priority + + std::vector picked = pick_pieces(p, "** **", blocks, 0, &tmp1 + , options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(1, blocks)); +} + +TORRENT_TEST(piece_extent_affinity_clear_done) +{ + // an extent is two pieces wide, 7 extents total. + // make sure we remove an active extent when we have all the pieces, and + // allow a new extent to be added + int const blocks = 128; + auto const have_none = " "; + + auto p = setup_picker("33333333333333", have_none, "44444444444455", "", blocks); + // open up the first 5 extents + mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(2, blocks), &tmp1, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(4, blocks), &tmp2, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(6, blocks), &tmp3, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(8, blocks), &tmp4, options | piece_picker::piece_extent_affinity); + + // now all 5 extents are in use, if we finish a whole extent, it should be + // removed from the list + p->we_have(piece_index_t{0}); + p->we_have(piece_index_t{1}); + + // we need to invoke the piece picker once to detect and reap this full + // extent + pick_pieces(p, "**************", blocks, 0, &tmp1, options | piece_picker::piece_extent_affinity); + + // this *should* open up another extent. We should still have a bias + // towards pieces 1, 3, 5, 7 and 9. + mark_downloading(p, full_piece(10, blocks), &tmp5, options | piece_picker::piece_extent_affinity); + + // a peer that only has piece 10, 11, 12, 13 will always pick 11, since it's + // part of an extent that was just opened, never 12 or 13 even though they + // have higher priority + std::vector picked = pick_pieces(p, " ****", blocks, 0, &tmp1 + , options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(11, blocks)); +} + +TORRENT_TEST(piece_extent_affinity_no_duplicates) +{ + // an extent is 8 pieces wide, 3 extents total. + // make sure that downloading pieces from the same extent don't create + // multiple entries in the recent-extent list, but they all use a single + // entry + int const blocks = 32; + auto const have_none = " "; + + auto p = setup_picker("333333333333333333333333", have_none + , "444444444444444444444455", "", blocks); + // download 5 pieces from the first extent + mark_downloading(p, full_piece(0, blocks), &tmp0, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(2, blocks), &tmp1, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(4, blocks), &tmp2, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(6, blocks), &tmp3, options | piece_picker::piece_extent_affinity); + mark_downloading(p, full_piece(1, blocks), &tmp4, options | piece_picker::piece_extent_affinity); + + // since all these belong to the same extent (0), there should be a single + // entry in the recent extent list. Make sure that it's possible to open up a + // second extent, to show that all 5 entries weren't used up by 5 duplicates + // of 0. + // opens up extent 1 + mark_downloading(p, full_piece(8, blocks), &tmp5, options | piece_picker::piece_extent_affinity); + + // now, from a peer that doesn't have anything from the first extent, still + // pick from the second extent even though the last two pieces have higher + // priority. + std::vector picked = pick_pieces(p, " ****************", blocks, 0, &tmp1 + , options | piece_picker::piece_extent_affinity); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(picked == full_piece(9, blocks)); +} + +//TODO: 2 test picking with partial pieces and other peers present so that both +// backup_pieces and backup_pieces2 are used diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp new file mode 100644 index 0000000..8295e83 --- /dev/null +++ b/test/test_primitives.cpp @@ -0,0 +1,189 @@ +/* + +Copyright (c) 2008-2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/entry.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/hex.hpp" // from_hex +#include "libtorrent/fingerprint.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" + +using namespace lt; + +TORRENT_TEST(retry_interval) +{ + // make sure the retry interval keeps growing + // on failing announces + announce_entry ae("dummy"); + ae.endpoints.emplace_back(aux::listen_socket_handle(), false); + int last = 0; + auto const tracker_backoff = 250; + for (int i = 0; i < 10; ++i) + { + ae.endpoints.front().failed(tracker_backoff, seconds32(5)); + int const delay = static_cast(total_seconds(ae.endpoints.front().next_announce - clock_type::now())); + TEST_CHECK(delay > last); + last = delay; + std::printf("%d, ", delay); + } + std::printf("\n"); +} + +TORRENT_TEST(error_code) +{ + TEST_CHECK(error_code(errors::http_error).message() == "HTTP error"); + TEST_CHECK(error_code(errors::missing_file_sizes).message() + == "missing or invalid 'file sizes' entry"); +#if TORRENT_ABI_VERSION == 1 + TEST_CHECK(error_code(errors::unsupported_protocol_version).message() + == "unsupported protocol version"); +#endif + TEST_CHECK(error_code(errors::no_i2p_router).message() == "no i2p router is set up"); + TEST_CHECK(error_code(errors::http_parse_error).message() == "Invalid HTTP header"); + TEST_CHECK(error_code(errors::error_code_max).message() == "Unknown error"); + + TEST_CHECK(error_code(errors::unauthorized, http_category()).message() + == "401 Unauthorized"); + TEST_CHECK(error_code(errors::service_unavailable, http_category()).message() + == "503 Service Unavailable"); +} + +#if defined __GNUC__ && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation=" +#endif + +TORRENT_TEST(snprintf) +{ + char msg[10]; + std::snprintf(msg, sizeof(msg), "too %s format string", "long"); + TEST_CHECK(strcmp(msg, "too long ") == 0); +} + +#if defined __GNUC__ && __GNUC__ >= 7 +#pragma GCC diagnostic pop +#endif + +TORRENT_TEST(address_to_from_string) +{ + if (!supports_ipv6()) return; + + error_code ec; + // make sure the assumption we use in peer list hold + std::multimap peers; + std::multimap::iterator i; + peers.insert(std::make_pair(address::from_string("::1", ec), 0)); + peers.insert(std::make_pair(address::from_string("::2", ec), 3)); + peers.insert(std::make_pair(address::from_string("::3", ec), 5)); + i = peers.find(address::from_string("::2", ec)); + TEST_CHECK(i != peers.end()); + if (i != peers.end()) + { + TEST_CHECK(i->first == address::from_string("::2", ec)); + TEST_CHECK(i->second == 3); + } +} + +TORRENT_TEST(address_endpoint_io) +{ + // test print_endpoint, print_address + TEST_EQUAL(print_endpoint(ep("127.0.0.1", 23)), "127.0.0.1:23"); + TEST_EQUAL(print_address(addr4("241.124.23.5")), "241.124.23.5"); + + TEST_EQUAL(print_endpoint(ep("ff::1", 1214)), "[ff::1]:1214"); + TEST_EQUAL(print_address(addr6("2001:ff::1")), "2001:ff::1"); + + // test address_to_bytes + TEST_EQUAL(address_to_bytes(addr4("10.11.12.13")), "\x0a\x0b\x0c\x0d"); + TEST_EQUAL(address_to_bytes(addr4("16.5.127.1")), "\x10\x05\x7f\x01"); + + // test endpoint_to_bytes + TEST_EQUAL(endpoint_to_bytes(uep("10.11.12.13", 8080)), "\x0a\x0b\x0c\x0d\x1f\x90"); + TEST_EQUAL(endpoint_to_bytes(uep("16.5.127.1", 12345)), "\x10\x05\x7f\x01\x30\x39"); +} + +TORRENT_TEST(gen_fingerprint) +{ + TEST_EQUAL(generate_fingerprint("AB", 1, 2, 3, 4), "-AB1234-"); + TEST_EQUAL(generate_fingerprint("AB", 1, 2), "-AB1200-"); + TEST_EQUAL(generate_fingerprint("..", 1, 10), "-..1A00-"); + TEST_EQUAL(generate_fingerprint("CZ", 1, 15), "-CZ1F00-"); + TEST_EQUAL(generate_fingerprint("CZ", 1, 15, 16, 17), "-CZ1FGH-"); +} + +TORRENT_TEST(printf_int64) +{ + char buffer[100]; + std::int64_t val = 345678901234567ll; + std::snprintf(buffer, sizeof(buffer), "%" PRId64 " %s", val, "end"); + TEST_EQUAL(buffer, std::string("345678901234567 end")); +} + +TORRENT_TEST(printf_uint64) +{ + char buffer[100]; + std::uint64_t val = 18446744073709551615ull; + std::snprintf(buffer, sizeof(buffer), "%" PRIu64 " %s", val, "end"); + TEST_EQUAL(buffer, std::string("18446744073709551615 end")); +} + +#if defined __GNUC__ && __GNUC__ >= 7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation=" +#endif + +TORRENT_TEST(printf_trunc) +{ + char buffer[4]; + int val = 184; + std::snprintf(buffer, sizeof(buffer), "%d %s", val, "end"); + TEST_EQUAL(buffer, std::string("184")); +} + +#if defined __GNUC__ && __GNUC__ >= 7 +#pragma GCC diagnostic pop +#endif + + +TORRENT_TEST(error_condition) +{ +#ifdef TORRENT_WINDOWS + error_code ec(ERROR_FILE_NOT_FOUND, system_category()); +#else + error_code ec(ENOENT, system_category()); +#endif + TEST_CHECK(ec == boost::system::errc::no_such_file_or_directory); +} + diff --git a/test/test_priority.cpp b/test/test_priority.cpp new file mode 100644 index 0000000..1f34332 --- /dev/null +++ b/test/test_priority.cpp @@ -0,0 +1,618 @@ +/* + +Copyright (c) 2008-2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include +#include + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include +#include + +using namespace lt; +using std::ignore; + +namespace { + +int peer_disconnects = 0; + +bool on_alert(alert const* a) +{ + auto const* const pd = alert_cast(a); + if (pd && pd->error != make_error_code(errors::self_connection)) + ++peer_disconnects; + else if (alert_cast(a)) + ++peer_disconnects; + + return false; +} + +void cleanup() +{ + error_code ec; + remove_all("tmp1_priority", ec); + remove_all("tmp2_priority", ec); + remove_all("tmp1_priority_moved", ec); + remove_all("tmp2_priority_moved", ec); +} + +void test_transfer(settings_pack const& sett, bool test_deprecated = false) +{ + // this allows shutting down the sessions in parallel + std::vector sp; + + cleanup(); + + lt::settings_pack pack = sett; + + // we need a short reconnect time since we + // finish the torrent and then restart it + // immediately to complete the second half. + // using a reconnect time > 0 will just add + // to the time it will take to complete the test + pack.set_int(settings_pack::min_reconnect_time, 0); + + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + pack.set_int(settings_pack::unchoke_slots_limit, 8); + + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); +#if TORRENT_ABI_VERSION == 1 + pack.set_bool(settings_pack::rate_limit_utp, true); +#endif + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + error_code ec; + create_directory("tmp1_priority", ec); + std::ofstream file("tmp1_priority/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + std::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, nullptr + , true, false, true, "_priority", 8 * 1024, &t, false, nullptr); + + int const num_pieces = tor2.torrent_file()->num_pieces(); + aux::vector priorities(std::size_t(num_pieces), 1_pri); + // set half of the pieces to priority 0 + std::fill(priorities.begin(), priorities.begin() + (num_pieces / 2), 0_pri); + tor2.prioritize_pieces(priorities); + std::cout << "setting priorities: "; + for (auto p : priorities) std::cout << int(static_cast(p)) << " "; + std::cout << '\n'; + + for (int i = 0; i < 200; ++i) + { + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + if (i % 10 == 0) + { + print_ses_rate(i / 10.f, &st1, &st2); + } + + // st2 is finished when we have downloaded half of the pieces + if (st2.is_finished) break; + + if (st2.state != torrent_status::downloading) + { + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + std::cout << "st2 state: " << state_str[st2.state] << std::endl; + } + + TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_resume_data + || st1.state == torrent_status::checking_files); + TEST_CHECK(st2.state == torrent_status::downloading + || st2.state == torrent_status::checking_resume_data); + + if (peer_disconnects >= 2) + { + std::printf("too many disconnects (%d), exiting\n", peer_disconnects); + break; + } + + // if nothing is being transferred after 3 seconds, we're failing the test + if (st1.upload_payload_rate == 0 && i > 30) + { + std::cout << "no upload in " << (i / 10) << " seconds, failing\n"; + break; + } + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + TEST_CHECK(!tor2.status().is_seeding); + TEST_CHECK(tor2.status().is_finished); + + if (tor2.status().is_finished) + std::cout << "torrent is finished (50% complete)" << std::endl; + else return; + + std::vector priorities2 = tor2.get_piece_priorities(); + std::copy(priorities2.begin(), priorities2.end() + , std::ostream_iterator(std::cout, ", ")); + std::cout << std::endl; + TEST_CHECK(std::equal(priorities.begin(), priorities.end(), priorities2.begin())); + + std::cout << "force recheck" << std::endl; + tor2.force_recheck(); + + priorities2 = tor2.get_piece_priorities(); + std::copy(priorities2.begin(), priorities2.end() + , std::ostream_iterator(std::cout, ", ")); + std::cout << std::endl; + TEST_CHECK(std::equal(priorities.begin(), priorities.end(), priorities2.begin())); + + peer_disconnects = 0; + + // when we're done checking, we're likely to be put in downloading state + // for a split second before transitioning to finished. This loop waits + // for the finished state + torrent_status st2; + for (int i = 0; i < 50; ++i) + { + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + st2 = tor2.status(); + if (i % 10 == 0) + { + std::cout << int(st2.progress * 100) << "% " << std::endl; + } + if (st2.state == torrent_status::finished) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + TEST_EQUAL(st2.state, torrent_status::finished); + + if (st2.state != torrent_status::finished) + return; + + std::cout << "recheck complete" << std::endl; + + priorities2 = tor2.get_piece_priorities(); + std::copy(priorities2.begin(), priorities2.end(), std::ostream_iterator(std::cout, ", ")); + std::cout << std::endl; + TEST_CHECK(std::equal(priorities.begin(), priorities.end(), priorities2.begin())); + + tor2.pause(); + wait_for_alert(ses2, torrent_paused_alert::alert_type, "ses2"); + + std::printf("save resume data\n"); + tor2.save_resume_data(); + + std::vector resume_data; + + time_point start = clock_type::now(); + while (true) + { + ses2.wait_for_alert(seconds(10)); + std::vector alerts; + ses2.pop_alerts(&alerts); + if (alerts.empty()) break; + for (std::vector::iterator i = alerts.begin() + , end(alerts.end()); i != end; ++i) + { + alert* a = *i; + std::cout << "ses2: " << a->message() << std::endl; + if (alert_cast(a)) + { + resume_data = write_resume_data_buf(alert_cast(a)->params); + std::printf("saved resume data\n"); + goto done; + } + else if (alert_cast(a)) + { + std::printf("save resume failed\n"); + goto done; + } + if (total_seconds(clock_type::now() - start) > 10) + goto done; + } + } +done: + TEST_CHECK(resume_data.size()); + + if (resume_data.empty()) + return; + + std::printf("%s\n", &resume_data[0]); + + ses2.remove_torrent(tor2); + + std::cout << "removed" << std::endl; + + std::this_thread::sleep_for(lt::milliseconds(100)); + + std::cout << "re-adding" << std::endl; + add_torrent_params p; + TORRENT_UNUSED(test_deprecated); +#if TORRENT_ABI_VERSION == 1 + if (test_deprecated) + { + p.resume_data = resume_data; + } + else +#endif + { + error_code ec1; + p = read_resume_data(resume_data, ec1); + TEST_CHECK(!ec1); + } + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.ti = t; + p.save_path = "tmp2_priority"; + + tor2 = ses2.add_torrent(p, ec); + tor2.prioritize_pieces(priorities); + std::cout << "resetting priorities" << std::endl; + tor2.resume(); + + // wait for torrent 2 to settle in back to finished state (it will + // start as checking) + torrent_status st1; + for (int i = 0; i < 5; ++i) + { + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + st1 = tor1.status(); + st2 = tor2.status(); + + TEST_CHECK(st1.state == torrent_status::seeding); + + if (st2.is_finished) break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + // torrent 2 should not be seeding yet, it should + // just be 50% finished + TEST_CHECK(!st2.is_seeding); + TEST_CHECK(st2.is_finished); + + std::fill(priorities.begin(), priorities.end(), 1_pri); + tor2.prioritize_pieces(priorities); + std::cout << "setting priorities to 1" << std::endl; + TEST_EQUAL(tor2.status().is_finished, false); + + std::copy(priorities.begin(), priorities.end() + , std::ostream_iterator(std::cout, ", ")); + std::cout << std::endl; + + // drain alerts + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + peer_disconnects = 0; + + // this loop makes sure ses2 reconnects to the peer now that it's + // in download mode again. If this fails, the reconnect logic may + // not work or be inefficient + st1 = tor1.status(); + st2 = tor2.status(); + for (int i = 0; i < 130; ++i) + { + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + st1 = tor1.status(); + st2 = tor2.status(); + + if (i % 10 == 0) + print_ses_rate(i / 10.f, &st1, &st2); + + if (st2.is_seeding) break; + + TEST_EQUAL(st1.state, torrent_status::seeding); + TEST_EQUAL(st2.state, torrent_status::downloading); + + if (peer_disconnects >= 2) + { + std::printf("too many disconnects (%d), exiting\n", peer_disconnects); + break; + } + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + st2 = tor2.status(); + if (!st2.is_seeding) + std::printf("ses2 failed to reconnect to ses1!\n"); + TEST_CHECK(st2.is_seeding); + + sp.push_back(ses1.abort()); + sp.push_back(ses2.abort()); +} + +} // anonymous namespace + +TORRENT_TEST(priority) +{ + using namespace lt; + settings_pack p = settings(); + test_transfer(p); + cleanup(); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(priority_deprecated) +{ + using namespace lt; + settings_pack p = settings(); + test_transfer(p, true); + cleanup(); +} +#endif + +// test to set piece and file priority on a torrent that doesn't have metadata +// yet +TORRENT_TEST(no_metadata_prioritize_files) +{ + lt::session ses(settings()); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.info_hash = sha1_hash("abababababababababab"); + addp.save_path = "."; + torrent_handle h = ses.add_torrent(addp); + + std::vector prios(3); + prios[0] = lt::dont_download; + + h.prioritize_files(prios); + // TODO 2: this should wait for an alert instead of just sleeping + std::this_thread::sleep_for(lt::milliseconds(100)); + TEST_CHECK(h.get_file_priorities() == prios); + + prios[0] = lt::low_priority; + h.prioritize_files(prios); + std::this_thread::sleep_for(lt::milliseconds(100)); + TEST_CHECK(h.get_file_priorities() == prios); + + ses.remove_torrent(h); +} + +TORRENT_TEST(no_metadata_file_prio) +{ + lt::session ses(settings()); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.info_hash = sha1_hash("abababababababababab"); + addp.save_path = "."; + torrent_handle h = ses.add_torrent(addp); + + h.file_priority(file_index_t(0), 0_pri); + // TODO 2: this should wait for an alert instead of just sleeping + std::this_thread::sleep_for(lt::milliseconds(100)); + TEST_EQUAL(h.file_priority(file_index_t(0)), 0_pri); + h.file_priority(file_index_t(0), 1_pri); + std::this_thread::sleep_for(lt::milliseconds(100)); + TEST_EQUAL(h.file_priority(file_index_t(0)), 1_pri); + + ses.remove_torrent(h); +} + +TORRENT_TEST(no_metadata_piece_prio) +{ + lt::session ses(settings()); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.info_hash = sha1_hash("abababababababababab"); + addp.save_path = "."; + torrent_handle h = ses.add_torrent(addp); + + // you can't set piece priorities before the metadata has been downloaded + h.piece_priority(piece_index_t(2), 0_pri); + TEST_EQUAL(h.piece_priority(piece_index_t(2)), 4_pri); + h.piece_priority(piece_index_t(2), 1_pri); + TEST_EQUAL(h.piece_priority(piece_index_t(2)), 4_pri); + + ses.remove_torrent(h); +} + +TORRENT_TEST(file_priority_multiple_calls) +{ + settings_pack pack = settings(); + lt::session ses(pack); + + auto t = ::generate_torrent(true); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.save_path = "."; + addp.ti = t; + torrent_handle h = ses.add_torrent(addp); + + for (file_index_t const i : t->files().file_range()) + h.file_priority(i, lt::low_priority); + + std::vector const expected( + std::size_t(t->files().num_files()), lt::low_priority); + for (int i = 0; i < 10; ++i) + { + auto const p = h.get_file_priorities(); + if (p == expected) return; + std::this_thread::sleep_for(milliseconds(500)); + } + TEST_CHECK(false); +} + +TORRENT_TEST(export_file_while_seed) +{ + settings_pack pack = settings(); + lt::session ses(pack); + + error_code ec; + create_directory("tmp2_priority", ec); + std::ofstream file("tmp2_priority/temporary"); + auto t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.save_path = "."; + addp.ti = t; + torrent_handle h = ses.add_torrent(addp); + + // write to the partfile + h.file_priority(file_index_t{0}, lt::dont_download); + + std::vector piece(16 * 1024); + for (std::size_t i = 0; i < piece.size(); ++i) + piece[i] = char((i % 26) + 'A'); + + // prevent race conditions of adding pieces while checking + lt::torrent_status st = h.status(); + for (int i = 0; i < 40; ++i) + { + print_alerts(ses, "ses", true, true); + st = h.status(); + if (st.state != torrent_status::checking_files + && st.state != torrent_status::checking_resume_data) + break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + TEST_CHECK(st.state != torrent_status::checking_files + && st.state != torrent_status::checking_files); + TEST_CHECK(st.num_pieces == 0); + + for (piece_index_t i : t->piece_range()) + h.add_piece(i, piece.data()); + + TEST_CHECK(!exists("temporary")); + + for (int i = 0; i < 10; ++i) + { + if (h.status().is_seeding) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + TEST_EQUAL(h.status().is_seeding, true); + + // this should cause the file to be exported + h.file_priority(file_index_t{0}, lt::low_priority); + + for (int i = 0; i < 10; ++i) + { + if (h.file_priority(file_index_t{0}) == lt::low_priority) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + TEST_CHECK(exists("temporary")); +} + +TORRENT_TEST(test_piece_priority_after_resume) +{ + auto const new_prio = lt::low_priority; + + add_torrent_params p; + auto ti = generate_torrent(); + { + auto const prio = top_priority; + + p.save_path = "."; + p.ti = ti; + p.file_priorities.resize(1, prio); + + lt::session ses(settings()); + torrent_handle h = ses.add_torrent(p); + + TEST_EQUAL(h.piece_priority(piece_index_t{0}), prio); + + using prio_vec = std::vector>; + h.prioritize_pieces(prio_vec{{piece_index_t{0}, new_prio}}); + TEST_EQUAL(h.piece_priority(piece_index_t{0}), new_prio); + + ses.pause(); + h.save_resume_data(); + + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + save_resume_data_alert const* rd = alert_cast(a); + + p = rd->params; + } + { + p.save_path = "."; + p.ti = ti; + + lt::session ses(settings()); + torrent_handle h = ses.add_torrent(p); + + TEST_EQUAL(h.piece_priority(piece_index_t{0}), new_prio); + } +} diff --git a/test/test_privacy.cpp b/test/test_privacy.cpp new file mode 100644 index 0000000..7dd3790 --- /dev/null +++ b/test/test_privacy.cpp @@ -0,0 +1,343 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "dht_server.hpp" +#include "peer_server.hpp" +#include "udp_tracker.hpp" +#include "test_utils.hpp" +#include "settings.hpp" + +#include "libtorrent/alert.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/flags.hpp" + +#include + +using namespace lt; + +namespace { + +char const* proxy_name[] = { + "none", + "socks4", + "socks5", + "socks5_pw", + "http", + "http_pw", + "i2p_proxy" +}; + +using flags_t = flags::bitfield_flag; + +constexpr flags_t expect_http_connection = 1_bit; +constexpr flags_t expect_udp_connection = 2_bit; +//constexpr flags_t expect_http_reject = 3_bit; +//constexpr flags_t expect_udp_reject = 4_bit; +constexpr flags_t expect_dht_msg = 5_bit; +constexpr flags_t expect_peer_connection = 6_bit; + +constexpr flags_t dont_proxy_peers = 10_bit; +constexpr flags_t dont_proxy_trackers = 11_bit; + +session_proxy test_proxy(settings_pack::proxy_type_t proxy_type, flags_t flags) +{ +#ifdef TORRENT_DISABLE_DHT + // if DHT is disabled, we won't get any requests to it + flags &= ~expect_dht_msg; +#endif + std::printf("\n=== TEST == proxy: %s \n\n", proxy_name[proxy_type]); + int const http_port = start_web_server(); + int const udp_port = start_udp_tracker(); + int const dht_port = start_dht(); + int const peer_port = start_peer(); + + int const prev_udp_announces = num_udp_announces(); + + settings_pack sett = settings(); + sett.set_int(settings_pack::stop_tracker_timeout, 2); + sett.set_int(settings_pack::tracker_completion_timeout, 2); + sett.set_int(settings_pack::tracker_receive_timeout, 2); + sett.set_bool(settings_pack::announce_to_all_trackers, true); + sett.set_bool(settings_pack::announce_to_all_tiers, true); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_dht, true); + + // since multiple sessions may exist simultaneously (because of the + // pipelining of the tests) they actually need to use different ports + static int listen_port = 10000 + int(lt::random(50000)); + char iface[200]; + std::snprintf(iface, sizeof(iface), "127.0.0.1:%d", listen_port); + listen_port += lt::random(10) + 1; + sett.set_str(settings_pack::listen_interfaces, iface); + + // if we don't do this, the peer connection test + // will be delayed by several seconds, by first + // trying uTP + sett.set_bool(settings_pack::enable_outgoing_utp, false); + + // in non-anonymous mode we circumvent/ignore the proxy if it fails + // wheras in anonymous mode, we just fail + sett.set_str(settings_pack::proxy_hostname, "non-existing.com"); + sett.set_int(settings_pack::proxy_type, proxy_type); + sett.set_bool(settings_pack::proxy_peer_connections, !(flags & dont_proxy_peers)); + sett.set_bool(settings_pack::proxy_tracker_connections, !(flags & dont_proxy_trackers)); + sett.set_int(settings_pack::proxy_port, 4444); + + std::unique_ptr s(new lt::session(sett)); + + error_code ec; + remove_all("tmp1_privacy", ec); + create_directory("tmp1_privacy", ec); + std::ofstream file(combine_path("tmp1_privacy", "temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char http_tracker_url[200]; + std::snprintf(http_tracker_url, sizeof(http_tracker_url) + , "http://127.0.0.1:%d/announce", http_port); + t->add_tracker(http_tracker_url, 0); + std::printf("http tracker: %s\n", http_tracker_url); + + char udp_tracker_url[200]; + std::snprintf(udp_tracker_url, sizeof(udp_tracker_url) + , "udp://127.0.0.1:%d/announce", udp_port); + t->add_tracker(udp_tracker_url, 1); + std::printf("udp tracker: %s\n", udp_tracker_url); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + + // we don't want to waste time checking the torrent, just go straight into + // seeding it, announcing to trackers and connecting to peers + addp.flags |= torrent_flags::seed_mode; + + addp.ti = t; + addp.save_path = "tmp1_privacy"; + addp.dht_nodes.push_back(std::pair("127.0.0.1", dht_port)); + torrent_handle h = s->add_torrent(addp); + + std::printf("connect_peer: 127.0.0.1:%d\n", peer_port); + h.connect_peer({address_v4::from_string("127.0.0.1"), std::uint16_t(peer_port)}); + + std::vector accepted_trackers; + + int const timeout = 30; + std::size_t const expected_trackers + = ((flags & expect_http_connection) ? 2 : 0) + + ((flags & expect_udp_connection) ? 2 : 0); + + for (int i = 0; i < timeout; ++i) + { + print_alerts(*s, "s", false, false + , [&](lt::alert const* a) + { + if (auto const* ta = alert_cast(a)) + { + std::printf("accepted tracker: %s\n", ta->tracker_url()); + accepted_trackers.push_back(ta->tracker_url()); + } + return false; + }); + std::this_thread::sleep_for(lt::milliseconds(100)); + + if (num_udp_announces() >= prev_udp_announces + 1 + && num_peer_hits() > 0 + && accepted_trackers.size() >= expected_trackers) + { + break; + } + } + + // we should have announced to the tracker by now + TEST_EQUAL(num_udp_announces(), prev_udp_announces + + ((flags & expect_udp_connection) ? 1 : 0)); + + if (flags & expect_dht_msg) + { + TEST_CHECK(num_dht_hits() > 0); + } + else + { + TEST_EQUAL(num_dht_hits(), 0); + } + + if (flags & expect_peer_connection) + { + TEST_CHECK(num_peer_hits() > 0); + } + else + { + TEST_EQUAL(num_peer_hits(), 0); + } + + if (flags & expect_http_connection) + { + std::printf("expecting: %s\n", http_tracker_url); + TEST_CHECK(std::find(accepted_trackers.begin(), accepted_trackers.end() + , http_tracker_url) != accepted_trackers.end()); + } + else + { + std::printf("NOT expecting: %s\n", http_tracker_url); + TEST_CHECK(std::find(accepted_trackers.begin(), accepted_trackers.end() + , http_tracker_url) == accepted_trackers.end()); + } + + if (flags & expect_udp_connection) + { + std::printf("expecting: %s\n", udp_tracker_url); + TEST_CHECK(std::find(accepted_trackers.begin(), accepted_trackers.end() + , udp_tracker_url) != accepted_trackers.end()); + } + else + { + std::printf("NOT expecting: %s\n", udp_tracker_url); + TEST_CHECK(std::find(accepted_trackers.begin(), accepted_trackers.end() + , udp_tracker_url) == accepted_trackers.end()); + } + + std::printf("%s: ~session\n", time_now_string()); + session_proxy pr = s->abort(); + s.reset(); + + stop_peer(); + stop_dht(); + stop_udp_tracker(); + stop_web_server(); + return pr; +} + +} // anonymous namespace + +// not using anonymous mode +// UDP fails open if we can't connect to the proxy +// or if the proxy doesn't support UDP + +TORRENT_TEST(no_proxy) +{ + test_proxy(settings_pack::none, expect_udp_connection + | expect_http_connection | expect_dht_msg | expect_peer_connection); +} + +// since we don't actually have a proxy in this test, make sure libtorrent +// doesn't send any outgoing packets to either tracker or the peer +TORRENT_TEST(socks4) +{ + test_proxy(settings_pack::socks4, {}); +} + +TORRENT_TEST(socks5) +{ + test_proxy(settings_pack::socks5, {}); +} + +TORRENT_TEST(socks5_pw) +{ + test_proxy(settings_pack::socks5_pw, {}); +} + +TORRENT_TEST(http) +{ + test_proxy(settings_pack::http, {}); +} + +TORRENT_TEST(http_pw) +{ + test_proxy(settings_pack::http_pw, {}); +} + +// if we configure trackers to not be proxied, they should be let through +TORRENT_TEST(socks4_tracker) +{ + test_proxy(settings_pack::socks4, dont_proxy_trackers | expect_http_connection | expect_udp_connection); +} + +TORRENT_TEST(socks5_tracker) +{ + test_proxy(settings_pack::socks5, dont_proxy_trackers | expect_http_connection | expect_udp_connection); +} + +TORRENT_TEST(socks5_pw_tracker) +{ + test_proxy(settings_pack::socks5_pw, dont_proxy_trackers | expect_http_connection | expect_udp_connection); +} + +TORRENT_TEST(http_tracker) +{ + test_proxy(settings_pack::http, dont_proxy_trackers | expect_http_connection | expect_udp_connection); +} + +TORRENT_TEST(http_pw_tracker) +{ + test_proxy(settings_pack::http_pw, dont_proxy_trackers | expect_http_connection | expect_udp_connection); +} + +// if we configure peers to not be proxied, they should be let through +TORRENT_TEST(socks4_peer) +{ + test_proxy(settings_pack::socks4, dont_proxy_peers | expect_peer_connection); +} + +TORRENT_TEST(socks5_peer) +{ + test_proxy(settings_pack::socks5, dont_proxy_peers | expect_peer_connection); +} + +TORRENT_TEST(socks5_pw_peer) +{ + test_proxy(settings_pack::socks5_pw, dont_proxy_peers | expect_peer_connection); +} + +TORRENT_TEST(http_peer) +{ + test_proxy(settings_pack::http, dont_proxy_peers | expect_peer_connection); +} + +TORRENT_TEST(http_pw_peer) +{ + test_proxy(settings_pack::http_pw, dont_proxy_peers | expect_peer_connection); +} + +#if TORRENT_USE_I2P +TORRENT_TEST(i2p) +{ + test_proxy(settings_pack::i2p_proxy, {}); +} +#endif diff --git a/test/test_read_piece.cpp b/test/test_read_piece.cpp new file mode 100644 index 0000000..77b3d82 --- /dev/null +++ b/test/test_read_piece.cpp @@ -0,0 +1,158 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/aux_/path.hpp" + +namespace { + +enum flags_t +{ + seed_mode = 1, + time_critical = 2 +}; + +void test_read_piece(int flags) +{ + using namespace lt; + + std::printf("==== TEST READ PIECE =====\n"); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_read_piece", ec); + if (ec) std::printf("ERROR: removing tmp1_read_piece: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + create_directory("tmp1_read_piece", ec); + if (ec) std::printf("ERROR: creating directory tmp1_read_piece: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + create_directory(combine_path("tmp1_read_piece", "test_torrent"), ec); + if (ec) std::printf("ERROR: creating directory test_torrent: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + file_storage fs; + std::srand(10); + int piece_size = 0x4000; + + static std::array const file_sizes{{ 100000, 10000 }}; + + create_random_files(combine_path("tmp1_read_piece", "test_torrent"), file_sizes); + + add_files(fs, combine_path("tmp1_read_piece", "test_torrent")); + lt::create_torrent t(fs, piece_size, 0x4000); + + // calculate the hash for all pieces + set_piece_hashes(t, "tmp1_read_piece", ec); + if (ec) std::printf("ERROR: set_piece_hashes: (%d) %s\n" + , ec.value(), ec.message().c_str()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, ec, from_span); + + std::printf("generated torrent: %s tmp1_read_piece/test_torrent\n" + , aux::to_hex(ti->info_hash()).c_str()); + + settings_pack sett = settings(); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48000"); + lt::session ses(sett); + + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.save_path = "tmp1_read_piece"; + p.ti = ti; + if (flags & flags_t::seed_mode) + p.flags |= torrent_flags::seed_mode; + torrent_handle tor1 = ses.add_torrent(p, ec); + if (ec) std::printf("ERROR: add_torrent: (%d) %s\n" + , ec.value(), ec.message().c_str()); + TEST_CHECK(!ec); + TEST_CHECK(tor1.is_valid()); + + alert const* a = wait_for_alert(ses, torrent_finished_alert::alert_type, "ses"); + TEST_CHECK(a); + + TEST_CHECK(tor1.status().is_seeding); + + if (flags & time_critical) + { + tor1.set_piece_deadline(piece_index_t(1), 0, torrent_handle::alert_when_available); + } + else + { + tor1.read_piece(piece_index_t(1)); + } + + a = wait_for_alert(ses, read_piece_alert::alert_type, "ses"); + + TEST_CHECK(a); + if (a) + { + read_piece_alert const* rp = alert_cast(a); + TEST_CHECK(rp); + if (rp) + { + TEST_EQUAL(rp->piece, piece_index_t(1)); + } + } + + remove_all("tmp1_read_piece", ec); + if (ec) std::printf("ERROR: removing tmp1_read_piece: (%d) %s\n" + , ec.value(), ec.message().c_str()); +} + +} // anonymous namespace + +TORRENT_TEST(read_piece) +{ + test_read_piece(0); +} + +TORRENT_TEST(seed_mode) +{ + test_read_piece(seed_mode); +} + +TORRENT_TEST(time_critical) +{ + test_read_piece(time_critical); +} diff --git a/test/test_read_resume.cpp b/test/test_read_resume.cpp new file mode 100644 index 0000000..6410215 --- /dev/null +++ b/test/test_read_resume.cpp @@ -0,0 +1,325 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "test_utils.hpp" + +#include + +#include "libtorrent/entry.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" + +using namespace lt; + +TORRENT_TEST(read_resume) +{ + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = "abcdefghijklmnopqrst"; + rd["pieces"] = "\x01\x01\x01\x01\x01\x01"; + + rd["total_uploaded"] = 1337; + rd["total_downloaded"] = 1338; + rd["active_time"] = 1339; + rd["seeding_time"] = 1340; + rd["upload_rate_limit"] = 1343; + rd["download_rate_limit"] = 1344; + rd["max_connections"] = 1345; + rd["max_uploads"] = 1346; + rd["seed_mode"] = 0; + rd["super_seeding"] = 0; + rd["added_time"] = 1347; + rd["completed_time"] = 1348; + rd["finished_time"] = 1352; + + rd["piece_priority"] = "\x01\x02\x03\x04\x05\x06"; + rd["auto_managed"] = 0; + rd["sequential_download"] = 0; + rd["paused"] = 0; + + std::vector resume_data; + bencode(std::back_inserter(resume_data), rd); + + add_torrent_params atp = read_resume_data(resume_data); + + TEST_EQUAL(atp.info_hash, sha1_hash("abcdefghijklmnopqrst")); + TEST_EQUAL(atp.have_pieces.size(), 6); + TEST_EQUAL(atp.have_pieces.count(), 6); + + TEST_EQUAL(atp.total_uploaded, 1337); + TEST_EQUAL(atp.total_downloaded, 1338); + TEST_EQUAL(atp.active_time, 1339); + TEST_EQUAL(atp.seeding_time, 1340); + TEST_EQUAL(atp.upload_limit, 1343); + TEST_EQUAL(atp.download_limit, 1344); + TEST_EQUAL(atp.max_connections, 1345); + TEST_EQUAL(atp.max_uploads, 1346); + + torrent_flags_t const flags_mask + = torrent_flags::seed_mode + | torrent_flags::super_seeding + | torrent_flags::auto_managed + | torrent_flags::paused + | torrent_flags::sequential_download; + + TEST_CHECK(!(atp.flags & flags_mask)); + TEST_EQUAL(atp.added_time, 1347); + TEST_EQUAL(atp.completed_time, 1348); + TEST_EQUAL(atp.finished_time, 1352); + + TEST_EQUAL(atp.piece_priorities.size(), 6); + TEST_EQUAL(atp.piece_priorities[0], 1_pri); + TEST_EQUAL(atp.piece_priorities[1], 2_pri); + TEST_EQUAL(atp.piece_priorities[2], 3_pri); + TEST_EQUAL(atp.piece_priorities[3], 4_pri); + TEST_EQUAL(atp.piece_priorities[4], 5_pri); + TEST_EQUAL(atp.piece_priorities[5], 6_pri); +} + +TORRENT_TEST(read_resume_missing_info_hash) +{ + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + // missing info-hash + + std::vector resume_data; + bencode(std::back_inserter(resume_data), rd); + + error_code ec; + add_torrent_params atp = read_resume_data(resume_data, ec); + TEST_EQUAL(ec, error_code(errors::missing_info_hash)); +} + +TORRENT_TEST(read_resume_missing_file_format) +{ + entry rd; + + // missing file-format + rd["file-version"] = 1; + rd["info-hash"] = "abcdefghijklmnopqrst"; + + std::vector resume_data; + bencode(std::back_inserter(resume_data), rd); + + error_code ec; + add_torrent_params atp = read_resume_data(resume_data, ec); + TEST_EQUAL(ec, error_code(errors::invalid_file_tag)); +} + +TORRENT_TEST(read_resume_mismatching_torrent) +{ + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = "abcdefghijklmnopqrst"; + entry& info = rd["info"]; + info["piece length"] = 16384 * 16; + info["name"] = "test"; + + + std::vector resume_data; + bencode(std::back_inserter(resume_data), rd); + + // the info-hash field does not match the torrent in the "info" field, so it + // will be ignored + add_torrent_params atp = read_resume_data(resume_data); + TEST_CHECK(!atp.ti); +} + +namespace { +std::shared_ptr generate_torrent() +{ + file_storage fs; + fs.add_file("test_resume/tmp1", 128 * 1024 * 8); + fs.add_file("test_resume/tmp2", 128 * 1024); + fs.add_file("test_resume/tmp3", 128 * 1024); + lt::create_torrent t(fs, 128 * 1024, 6); + + t.add_tracker("http://torrent_file_tracker.com/announce"); + t.add_url_seed("http://torrent_file_url_seed.com/"); + + int num = t.num_pieces(); + TEST_CHECK(num > 0); + for (auto const i : fs.piece_range()) + { + sha1_hash ph; + aux::random_bytes(ph); + t.set_hash(i, ph); + } + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + return std::make_shared(buf, from_span); +} +} // anonymous namespace + +TORRENT_TEST(read_resume_torrent) +{ + std::shared_ptr ti = generate_torrent(); + + entry rd; + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["info"] = bdecode({ti->metadata().get(), ti->metadata_size()}); + + std::vector resume_data; + bencode(std::back_inserter(resume_data), rd); + + // the info-hash field does not match the torrent in the "info" field, so it + // will be ignored + add_torrent_params atp = read_resume_data(resume_data); + TEST_CHECK(atp.ti); + + TEST_EQUAL(atp.ti->info_hash(), ti->info_hash()); + TEST_EQUAL(atp.ti->name(), ti->name()); +} + +namespace { + +void test_roundtrip(add_torrent_params const& input) +{ + auto b = write_resume_data_buf(input); + error_code ec; + auto output = read_resume_data(b, ec); + TEST_CHECK(write_resume_data_buf(output) == b); +} + +template +lt::typed_bitfield bits() +{ + lt::typed_bitfield b; + b.resize(19); + b.set_bit(T(2)); + b.set_bit(T(6)); + b.set_bit(T(12)); + return b; +} + +lt::bitfield bits() +{ + lt::bitfield b; + b.resize(19); + b.set_bit(2); + b.set_bit(6); + b.set_bit(12); + return b; +} + +template +std::vector vec() +{ + std::vector ret; + ret.resize(10); + ret[0] = T(1); + ret[1] = T(2); + ret[5] = T(3); + ret[7] = T(4); + return ret; +} +} + +TORRENT_TEST(round_trip_have_pieces) +{ + add_torrent_params atp; + atp.have_pieces = bits(); + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_verified_pieces) +{ + add_torrent_params atp; + atp.verified_pieces = bits(); + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_prios) +{ + add_torrent_params atp; + atp.piece_priorities = vec(); + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_unfinished) +{ + add_torrent_params atp; + atp.unfinished_pieces = std::map{{piece_index_t{42}, bits()}}; + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_trackers) +{ + add_torrent_params atp; + atp.flags |= torrent_flags::override_trackers; + test_roundtrip(atp); +} + +TORRENT_TEST(round_trip_flags) +{ + torrent_flags_t const flags[] = { + torrent_flags::seed_mode, + torrent_flags::upload_mode, + torrent_flags::share_mode, + torrent_flags::apply_ip_filter, + torrent_flags::paused, + torrent_flags::auto_managed, + torrent_flags::duplicate_is_error, + torrent_flags::update_subscribe, + torrent_flags::super_seeding, + torrent_flags::sequential_download, + torrent_flags::stop_when_ready, + torrent_flags::override_trackers, + torrent_flags::override_web_seeds, + torrent_flags::need_save_resume, + torrent_flags::disable_dht, + torrent_flags::disable_lsd, + torrent_flags::disable_pex, + }; + + for (auto const& f : flags) + { + add_torrent_params atp; + atp.flags = f; + test_roundtrip(atp); + } +} diff --git a/test/test_receive_buffer.cpp b/test/test_receive_buffer.cpp new file mode 100644 index 0000000..5a48d75 --- /dev/null +++ b/test/test_receive_buffer.cpp @@ -0,0 +1,256 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "test.hpp" +#include "libtorrent/receive_buffer.hpp" + +using namespace lt; + +TORRENT_TEST(recv_buffer_init) +{ + receive_buffer b; + + b.cut(0, 10); + + TEST_EQUAL(b.packet_size(), 10); + TEST_EQUAL(b.packet_bytes_remaining(), 10); + TEST_EQUAL(b.packet_finished(), false); + TEST_EQUAL(b.pos(), 0); + TEST_EQUAL(b.capacity(), 0); +} + +TORRENT_TEST(recv_buffer_pos_at_end_false) +{ + receive_buffer b; + + b.cut(0, 1000); + // allocate some space to receive into + b.reserve(1000); + + b.received(1000); + b.advance_pos(999); + + TEST_EQUAL(b.pos_at_end(), false); +} + +TORRENT_TEST(recv_buffer_pos_at_end_true) +{ + receive_buffer b; + b.cut(0, 1000); + b.reserve(1000); + b.reserve(1000); + b.received(1000); + b.advance_pos(1000); + TEST_EQUAL(b.pos_at_end(), true); +} + +TORRENT_TEST(recv_buffer_packet_finished) +{ + receive_buffer b; + // packet_size = 10 + b.cut(0, 10); + b.reserve(1000); + b.reserve(1000); + b.received(1000); + + for (int i = 0; i < 10; ++i) + { + TEST_EQUAL(b.packet_finished(), false); + b.advance_pos(1); + } + TEST_EQUAL(b.packet_finished(), true); +} + +TORRENT_TEST(recv_buffer_grow_floor) +{ + receive_buffer b; + b.reset(1337); + b.grow(100000); + + // the exact size depends on the OS allocator. Technically there's no upper + // bound, but it's likely withint some reasonable size + TEST_CHECK(b.capacity() >= 1337); + TEST_CHECK(b.capacity() < 1337 + 1000); +} + +TORRENT_TEST(recv_buffer_grow) +{ + receive_buffer b; + b.reserve(200); + b.grow(100000); + // grow by 50% + TEST_CHECK(b.capacity() >= 300); + TEST_CHECK(b.capacity() < 300 + 500); +} + +TORRENT_TEST(recv_buffer_grow_limit) +{ + receive_buffer b; + b.reserve(2000); + b.grow(2100); + // grow by 50%, but capped by 2100 bytes + TEST_CHECK(b.capacity() >= 2100); + TEST_CHECK(b.capacity() < 2100 + 500); + printf("capacity: %d\n", b.capacity()); +} + +TORRENT_TEST(recv_buffer_reserve_minimum_grow) +{ + receive_buffer b; + b.reset(1337); + b.reserve(20); + + // we only asked for 20 more bytes, but since the message size was set to + // 1337, that's the minimum size to grow to + TEST_CHECK(b.capacity() >= 1337); + TEST_CHECK(b.capacity() < 1337 + 1000); +} + +TORRENT_TEST(recv_buffer_reserve_grow) +{ + receive_buffer b; + b.reserve(20); + + TEST_CHECK(b.capacity() >= 20); + TEST_CHECK(b.capacity() < 20 + 500); +} + +TORRENT_TEST(recv_buffer_reserve) +{ + receive_buffer b; + auto range1 = b.reserve(100); + + int const capacity = b.capacity(); + + b.reset(20); + b.received(20); + + TEST_EQUAL(b.capacity(), capacity); + + auto range2 = b.reserve(50); + + TEST_EQUAL(b.capacity(), capacity); + TEST_EQUAL(range1.begin() + 20, range2.begin()); + TEST_CHECK(range1.size() >= 20); + TEST_CHECK(range2.size() >= 50); +} + +TORRENT_TEST(receive_buffer_normalize) +{ + receive_buffer b; + b.reset(16000); + + // receive one large packet, to allocate a large receive buffer + for (int i = 0; i < 16; ++i) + { + b.reserve(1000); + b.received(1000); + b.normalize(); + } + + TEST_CHECK(b.capacity() >= 16000); + int const start_capacity = b.capacity(); + + // then receive lots of small packets. We should eventually re-allocate down + // to a smaller buffer + for (int i = 0; i < 15; ++i) + { + b.reset(160); + b.reserve(160); + b.received(160); + b.normalize(); + printf("capacity: %d watermark: %d\n", b.capacity(), b.watermark()); + } + + TEST_CHECK(b.capacity() <= start_capacity / 2); + printf("capacity: %d\n", b.capacity()); +} + +TORRENT_TEST(receive_buffer_max_receive) +{ + receive_buffer b; + b.reset(2000); + b.reserve(2000); + b.received(2000); + b.normalize(); + + b.reset(20); + int const max_receive = b.max_receive(); + TEST_CHECK(max_receive >= 2000); + b.received(20); + TEST_EQUAL(b.max_receive(), max_receive - 20); +} + +TORRENT_TEST(receive_buffer_watermark) +{ + receive_buffer b; + b.reset(0x4000); + b.reserve(33500000); + b.received(33500000); + b.normalize(); + + TEST_EQUAL(b.watermark(), 33500000); +} + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + +TORRENT_TEST(recv_buffer_mutable_buffers) +{ + receive_buffer b; + b.reserve(1100); + b.cut(0, 100); // packet size = 100 + b.received(1100); + int packet_transferred = b.advance_pos(1100); + // this is just the first packet + TEST_EQUAL(packet_transferred, 100); + // the next packet is 1000, and we're done with the first 100 bytes now + b.cut(100, 1000); // packet size = 1000 + packet_transferred = b.advance_pos(999); + TEST_EQUAL(packet_transferred, 999); + span vec = b.mutable_buffer(999); + + // previous packet + // | + // v buffer + // - - - ----------------------- + // ^ + // | + // m_recv_start + + // |----------------------| 1000 packet size + // |---------------------| 999 buffer + + TEST_EQUAL(vec.size(), 999); +} + +#endif diff --git a/test/test_recheck.cpp b/test/test_recheck.cpp new file mode 100644 index 0000000..8c89395 --- /dev/null +++ b/test/test_recheck.cpp @@ -0,0 +1,118 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/error_code.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +#include +#include + +using namespace lt; + +namespace { + +void wait_for_complete(lt::session& ses, torrent_handle h) +{ + int last_progress = 0; + clock_type::time_point last_change = clock_type::now(); + for (int i = 0; i < 200; ++i) + { + print_alerts(ses, "ses1"); + torrent_status st = h.status(); + std::printf("%f s - %f %%\n" + , total_milliseconds(clock_type::now() - last_change) / 1000.0 + , st.progress_ppm / 10000.0); + if (st.progress_ppm == 1000000) return; + if (st.progress_ppm != last_progress) + { + last_progress = st.progress_ppm; + last_change = clock_type::now(); + } + if (clock_type::now() - last_change > seconds(20)) break; + std::this_thread::sleep_for(lt::seconds(1)); + } + TEST_ERROR("torrent did not finish"); +} + +} // anonymous namespace + +TORRENT_TEST(recheck) +{ + error_code ec; + settings_pack sett = settings(); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48675"); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_dht, false); + lt::session ses1(sett); + create_directory("tmp1_recheck", ec); + if (ec) std::printf("create_directory: %s\n", ec.message().c_str()); + std::ofstream file("tmp1_recheck/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary", 4 * 1024 * 1024 + , 7, false); + file.close(); + + add_torrent_params param; + param.flags &= ~torrent_flags::paused; + param.flags &= ~torrent_flags::auto_managed; + param.ti = t; + param.save_path = "tmp1_recheck"; + param.flags |= torrent_flags::seed_mode; + torrent_handle tor1 = ses1.add_torrent(param, ec); + if (ec) std::printf("add_torrent: %s\n", ec.message().c_str()); + + wait_for_listen(ses1, "ses1"); + + tor1.force_recheck(); + + torrent_status st1 = tor1.status(); + TEST_CHECK(st1.progress_ppm <= 1000000); + wait_for_complete(ses1, tor1); + + tor1.force_recheck(); + + st1 = tor1.status(); + TEST_CHECK(st1.progress_ppm <= 1000000); + wait_for_complete(ses1, tor1); +} diff --git a/test/test_remap_files.cpp b/test/test_remap_files.cpp new file mode 100644 index 0000000..b839c6c --- /dev/null +++ b/test/test_remap_files.cpp @@ -0,0 +1,216 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/aux_/path.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "test.hpp" +#include "settings.hpp" + +#include +#include +#include +#include + +using namespace lt; +using std::ignore; + +namespace { + +bool all_of(std::vector const& v) +{ + return std::all_of(v.begin(), v.end(), [](bool val){ return val; }); +} + +void test_remap_files(storage_mode_t storage_mode = storage_mode_sparse) +{ + using namespace lt; + + // create a torrent with 2 files, remap them into 3 files and make sure + // the file priorities don't break things + static std::array const file_sizes{{100000, 100000}}; + int const piece_size = 0x8000; + auto t = make_torrent(file_sizes, piece_size); + + static std::array const remap_file_sizes + {{10000, 10000, int(t->total_size() - 20000)}}; + + file_storage fs = make_file_storage(remap_file_sizes, piece_size, "multifile-"); + + t->remap_files(fs); + + auto const alert_mask = alert_category::all + & ~alert_category::stats; + + session_proxy p1; + + settings_pack sett = settings(); + sett.set_int(settings_pack::alert_mask, alert_mask); + lt::session ses(sett); + + add_torrent_params params; + params.save_path = "."; + params.storage_mode = storage_mode; + params.flags &= ~torrent_flags::paused; + params.flags &= ~torrent_flags::auto_managed; + params.ti = t; + + torrent_handle tor1 = ses.add_torrent(params); + + // prevent race conditions of adding pieces while checking + lt::torrent_status st = tor1.status(); + for (int i = 0; i < 40; ++i) + { + st = tor1.status(); + if (st.state != torrent_status::checking_files + && st.state != torrent_status::checking_resume_data) + break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + TEST_CHECK(st.state != torrent_status::checking_files + && st.state != torrent_status::checking_files); + TEST_CHECK(st.num_pieces == 0); + + // write pieces + for (auto const i : fs.piece_range()) + { + std::vector const piece = generate_piece(i, fs.piece_size(i)); + tor1.add_piece(i, piece.data()); + } + + // read pieces + for (auto const i : fs.piece_range()) + { + tor1.read_piece(i); + } + + // wait for all alerts to come back and verify the data against the expected + // piece data + aux::vector pieces(std::size_t(fs.num_pieces()), false); + aux::vector passed(std::size_t(fs.num_pieces()), false); + aux::vector files(std::size_t(fs.num_files()), false); + + while (!all_of(pieces) || !all_of(passed) || !all_of(files)) + { + alert* a = ses.wait_for_alert(lt::seconds(5)); + if (a == nullptr) break; + + std::vector alerts; + ses.pop_alerts(&alerts); + + for (alert* i : alerts) + { + printf("%s\n", i->message().c_str()); + + read_piece_alert* rp = alert_cast(i); + if (rp) + { + auto const idx = rp->piece; + TEST_EQUAL(t->piece_size(idx), rp->size); + + std::vector const piece = generate_piece(idx, t->piece_size(idx)); + TEST_CHECK(std::memcmp(rp->buffer.get(), piece.data(), std::size_t(rp->size)) == 0); + TEST_CHECK(pieces[idx] == false); + pieces[idx] = true; + } + + file_completed_alert* fc = alert_cast(i); + if (fc) + { + auto const idx = fc->index; + TEST_CHECK(files[idx] == false); + files[idx] = true; + } + + piece_finished_alert* pf = alert_cast(i); + if (pf) + { + auto const idx = pf->piece_index; + TEST_CHECK(passed[idx] == false); + passed[idx] = true; + } + } + } + + TEST_CHECK(all_of(pieces)); + TEST_CHECK(all_of(files)); + TEST_CHECK(all_of(passed)); + + // just because we can read them back throught libtorrent, doesn't mean the + // files have hit disk yet (because of the cache). Retry a few times to try + // to pick up the files + for (file_index_t i(0); i < file_index_t(int(remap_file_sizes.size())); ++i) + { + std::string name = fs.file_path(i); + for (int j = 0; j < 10 && !exists(name); ++j) + { + std::this_thread::sleep_for(lt::milliseconds(500)); + print_alerts(ses, "ses"); + } + + std::printf("%s\n", name.c_str()); + TEST_CHECK(exists(name)); + } + + print_alerts(ses, "ses"); + + st = tor1.status(); + TEST_EQUAL(st.is_seeding, true); + + std::printf("\ntesting force recheck\n\n"); + + // test force rechecking a seeding torrent with remapped files + tor1.force_recheck(); + + for (int i = 0; i < 50; ++i) + { + torrent_status st1 = tor1.status(); + if (st1.is_seeding) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + print_alerts(ses, "ses"); + } + + print_alerts(ses, "ses"); + st = tor1.status(); + TEST_CHECK(st.is_seeding); +} + +} // anonymous namespace + +TORRENT_TEST(remap_files) +{ + test_remap_files(); +} diff --git a/test/test_remove_torrent.cpp b/test/test_remove_torrent.cpp new file mode 100644 index 0000000..fb4f502 --- /dev/null +++ b/test/test_remove_torrent.cpp @@ -0,0 +1,209 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_status.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/aux_/path.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; +using std::ignore; + +namespace { + +enum test_case { + complete_download, + partial_download, + mid_download +}; + +void test_remove_torrent(remove_flags_t const remove_options + , test_case const test = complete_download) +{ + // this allows shutting down the sessions in parallel + std::vector sp; + settings_pack pack = settings(); + + // we do this to force pieces to be evicted into the ghost lists + pack.set_int(settings_pack::cache_size, 10); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + int const num_pieces = (test == mid_download) ? 500 : 100; + + error_code ec; + remove_all("tmp1_remove", ec); + remove_all("tmp2_remove", ec); + create_directory("tmp1_remove", ec); + std::ofstream file("tmp1_remove/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary" + , 16 * 1024, num_pieces, false); + file.close(); + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + // test using piece sizes smaller than 16kB + std::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, nullptr + , true, false, true, "_remove", 8 * 1024, &t, false, nullptr); + + if (test == partial_download) + { + std::vector priorities(std::size_t(num_pieces), low_priority); + // set half of the pieces to priority 0 + std::fill(priorities.begin(), priorities.begin() + (num_pieces / 2), dont_download); + tor2.prioritize_pieces(priorities); + } + else if (test == mid_download) + { + tor1.set_upload_limit(static_cast(t->total_size())); + tor2.set_download_limit(static_cast(t->total_size())); + } + + torrent_status st1; + torrent_status st2; + + for (int i = 0; i < 200; ++i) + { + print_alerts(ses1, "ses1", true, true); + print_alerts(ses2, "ses2", true, true); + + st1 = tor1.status(); + st2 = tor2.status(); + + if (test == mid_download && st2.num_pieces > num_pieces / 2) + { + TEST_CHECK(st2.is_finished == false); + break; + } + if (st2.is_finished) break; + + TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_resume_data + || st1.state == torrent_status::checking_files); + TEST_CHECK(st2.state == torrent_status::downloading + || st2.state == torrent_status::checking_resume_data); + + // if nothing is being transferred after 3 seconds, we're failing the test + if (st1.total_payload_upload == 0 && i > 30) + { + TEST_ERROR("no transfer"); + return; + } + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + TEST_CHECK(st1.num_pieces > 0); + TEST_CHECK(st2.num_pieces > 0); + + ses2.remove_torrent(tor2, remove_options); + ses1.remove_torrent(tor1, remove_options); + + std::cerr << "removed" << std::endl; + + for (int i = 0; tor2.is_valid() || tor1.is_valid(); ++i) + { + std::this_thread::sleep_for(lt::milliseconds(100)); + if (++i > 400) + { + std::cerr << "torrent handle(s) still valid: " + << (tor1.is_valid() ? "tor1 " : "") + << (tor2.is_valid() ? "tor2 " : "") + << std::endl; + + TEST_ERROR("handle did not become invalid"); + return; + } + } + + if (remove_options & session::delete_files) + { + TEST_CHECK(!exists("tmp1_remove/temporary")); + TEST_CHECK(!exists("tmp2_remove/temporary")); + } + + sp.push_back(ses1.abort()); + sp.push_back(ses2.abort()); +} + +} // anonymous namespace + +TORRENT_TEST(remove_torrent) +{ + test_remove_torrent({}); +} + +TORRENT_TEST(remove_torrent_and_files) +{ + test_remove_torrent(session::delete_files); +} + +TORRENT_TEST(remove_torrent_partial) +{ + test_remove_torrent({}, partial_download); +} + +TORRENT_TEST(remove_torrent_and_files_partial) +{ + test_remove_torrent(session::delete_files, partial_download); +} + +TORRENT_TEST(remove_torrent_mid_download) +{ + test_remove_torrent({}, mid_download); +} + +TORRENT_TEST(remove_torrent_and_files_mid_download) +{ + test_remove_torrent(session::delete_files, mid_download); +} + + diff --git a/test/test_resolve_links.cpp b/test/test_resolve_links.cpp new file mode 100644 index 0000000..da718d2 --- /dev/null +++ b/test/test_resolve_links.cpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/resolve_links.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path +#include "libtorrent/hex.hpp" // to_hex +#include "libtorrent/create_torrent.hpp" + +#include + +using namespace lt; +using namespace std::placeholders; + +struct test_torrent_t +{ + char const* filename1; + char const* filename2; + std::string::size_type expected_matches; +}; + +static test_torrent_t test_torrents[] = { + // no match because shared file in test2 and test3 is not padded/aligned + { "test2", "test1_pad_files", 0}, + { "test3", "test1_pad_files", 0}, + + // in this case, test1 happens to have the shared file as the first one, + // which makes it padded, however, the tail of it isn't padded, so it + // still overlaps with the next file + { "test1", "test1_pad_files", 0}, + + // test2 and test3 don't have the shared file aligned + { "test2", "test1_pad_files", 0}, + { "test3", "test1_pad_files", 0}, + { "test2", "test1_single", 0}, + + // these are all padded. The first small file will accidentally also + // match, even though it's not tail padded, the following file is identical + { "test2_pad_files", "test1_pad_files", 2}, + { "test3_pad_files", "test1_pad_files", 2}, + { "test3_pad_files", "test2_pad_files", 2}, + { "test1_pad_files", "test2_pad_files", 2}, + { "test1_pad_files", "test3_pad_files", 2}, + { "test2_pad_files", "test3_pad_files", 2}, + + // one might expect this to work, but since the tail of the single file + // torrent is not padded, the last piece hash won't match + { "test1_pad_files", "test1_single", 0}, + + // if it's padded on the other hand, it will work + { "test1_pad_files", "test1_single_padded", 1}, + + // TODO: test files with different piece size (negative test) +}; + +// TODO: it would be nice to test resolving of more than just 2 files as well. +// like 3 single file torrents merged into one, resolving all 3 files. + +TORRENT_TEST(resolve_links) +{ + std::string path = combine_path(parent_path(current_working_directory()) + , "mutable_test_torrents"); + + for (int i = 0; i < int(sizeof(test_torrents)/sizeof(test_torrents[0])); ++i) + { + test_torrent_t const& e = test_torrents[i]; + + std::string p = combine_path(path, e.filename1) + ".torrent"; + std::printf("loading %s\n", p.c_str()); + std::shared_ptr ti1 = std::make_shared(p); + + p = combine_path(path, e.filename2) + ".torrent"; + std::printf("loading %s\n", p.c_str()); + std::shared_ptr ti2 = std::make_shared(p); + + std::printf("resolving\n"); + resolve_links l(ti1); + l.match(ti2, "."); + + aux::vector const& links = l.get_links(); + + auto const num_matches = std::size_t(std::count_if(links.begin(), links.end() + , std::bind(&resolve_links::link_t::ti, _1))); + + // some debug output in case the test fails + if (num_matches > e.expected_matches) + { + file_storage const& fs = ti1->files(); + for (file_index_t idx{0}; idx != links.end_index(); ++idx) + { + TORRENT_ASSERT(idx < file_index_t{fs.num_files()}); + std::printf("%*s --> %s : %d\n" + , int(fs.file_name(idx).size()) + , fs.file_name(idx).data() + , links[idx].ti + ? aux::to_hex(links[idx].ti->info_hash()).c_str() + : "", static_cast(links[idx].file_idx)); + } + } + + TEST_EQUAL(num_matches, e.expected_matches); + + } +} + +// this ensure that internally there is a range lookup +// since the zero-hash piece is in the second place +TORRENT_TEST(range_lookup_duplicated_files) +{ + file_storage fs1; + file_storage fs2; + + fs1.add_file("test_resolve_links_dir/tmp1", 1024); + fs1.add_file("test_resolve_links_dir/tmp2", 1024); + fs2.add_file("test_resolve_links_dir/tmp1", 1024); + fs2.add_file("test_resolve_links_dir/tmp2", 1024); + + lt::create_torrent t1(fs1, 1024); + lt::create_torrent t2(fs2, 1024); + + t1.set_hash(piece_index_t{0}, sha1_hash::max()); + + std::vector tmp1; + std::vector tmp2; + bencode(std::back_inserter(tmp1), t1.generate()); + bencode(std::back_inserter(tmp2), t2.generate()); + auto ti1 = std::make_shared(tmp1, from_span); + auto ti2 = std::make_shared(tmp2, from_span); + + std::printf("resolving\n"); + resolve_links l(ti1); + l.match(ti2, "."); + + aux::vector const& links = l.get_links(); + + auto const num_matches = std::count_if(links.begin(), links.end() + , std::bind(&resolve_links::link_t::ti, _1)); + + TEST_EQUAL(num_matches, 1); +} + +#else +TORRENT_TEST(empty) +{ + TEST_CHECK(true); +} +#endif // TORRENT_DISABLE_MUTABLE_TORRENTS diff --git a/test/test_resume.cpp b/test/test_resume.cpp new file mode 100644 index 0000000..d14f8b8 --- /dev/null +++ b/test/test_resume.cpp @@ -0,0 +1,1578 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/add_torrent_params.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/file.hpp" +#include "setup_transfer.hpp" + +#include "test.hpp" +#include "test_utils.hpp" +#include "settings.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +#include + +#ifdef TORRENT_WINDOWS +#define SEP "\\" +#else +#define SEP "/" +#endif +using namespace lt; + +namespace { + +torrent_flags_t const flags_mask + = torrent_flags::sequential_download + | torrent_flags::paused + | torrent_flags::auto_managed + | torrent_flags::seed_mode + | torrent_flags::super_seeding + | torrent_flags::share_mode + | torrent_flags::upload_mode + | torrent_flags::apply_ip_filter; + +std::vector generate_resume_data(torrent_info* ti + , char const* file_priorities = "") +{ + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = std::max(1, ti->piece_length() / 0x4000); + rd["pieces"] = std::string(std::size_t(ti->num_pieces()), '\x01'); + + rd["total_uploaded"] = 1337; + rd["total_downloaded"] = 1338; + rd["active_time"] = 1339; + rd["seeding_time"] = 1340; + rd["upload_rate_limit"] = 1343; + rd["download_rate_limit"] = 1344; + rd["max_connections"] = 1345; + rd["max_uploads"] = 1346; + rd["seed_mode"] = 0; + rd["super_seeding"] = 0; + rd["added_time"] = 1347; + rd["completed_time"] = 1348; + rd["last_download"] = 2; + rd["last_upload"] = 3; + rd["finished_time"] = 1352; + if (file_priorities && file_priorities[0]) + { + entry::list_type& file_prio = rd["file_priority"].list(); + for (int i = 0; file_priorities[i]; ++i) + file_prio.push_back(entry(file_priorities[i] - '0')); + } + + rd["piece_priority"] = std::string(std::size_t(ti->num_pieces()), '\x01'); + rd["auto_managed"] = 0; + rd["sequential_download"] = 0; + rd["paused"] = 0; + entry::list_type& trackers = rd["trackers"].list(); + trackers.push_back(entry(entry::list_t)); + trackers.back().list().push_back(entry("http://resume_data_tracker.com/announce")); + entry::list_type& url_list = rd["url-list"].list(); + url_list.push_back(entry("http://resume_data_url_seed.com")); + + entry::list_type& httpseeds = rd["httpseeds"].list(); + httpseeds.push_back(entry("http://resume_data_http_seed.com")); + +#ifdef TORRENT_WINDOWS + rd["save_path"] = "c:\\resume_data save_path"; +#else + rd["save_path"] = "/resume_data save_path"; +#endif + + std::vector ret; + bencode(back_inserter(ret), rd); + + std::printf("%s\n", rd.to_string().c_str()); + + return ret; +} + +torrent_handle test_resume_flags(lt::session& ses + , torrent_flags_t const flags = torrent_flags_t{} + , char const* file_priorities = "1111", char const* resume_file_prio = "" + , bool const test_deprecated = false) +{ + std::shared_ptr ti = generate_torrent( + bool(flags & torrent_flags::seed_mode)); + + add_torrent_params p; + std::vector rd = generate_resume_data(ti.get(), resume_file_prio); + TORRENT_UNUSED(test_deprecated); +#if TORRENT_ABI_VERSION == 1 + if (test_deprecated) + { + p.resume_data.swap(rd); + + p.trackers.push_back("http://add_torrent_params_tracker.com/announce"); + p.url_seeds.push_back("http://add_torrent_params_url_seed.com"); + + p.max_uploads = 1; + p.max_connections = 2; + p.upload_limit = 3; + p.download_limit = 4; + } + else +#endif + { + p = read_resume_data(rd); + } + + p.ti = ti; + p.flags = flags; +#ifdef TORRENT_WINDOWS + p.save_path = "c:\\add_torrent_params save_path"; +#else + p.save_path = "/add_torrent_params save_path"; +#endif + + if (file_priorities[0]) + { + aux::vector priorities_vector; + for (int i = 0; file_priorities[i]; ++i) + priorities_vector.push_back(download_priority_t(aux::numeric_cast(file_priorities[i] - '0'))); + + p.file_priorities = priorities_vector; + } + + torrent_handle h = ses.add_torrent(p); + torrent_status s = h.status(); + TEST_EQUAL(s.info_hash, ti->info_hash()); + return h; +} + +void default_tests(torrent_status const& s) +{ + // allow some slack in the time stamps since they are reported as + // relative times. If the computer is busy while running the unit test + // or running under valgrind it may take several seconds +#if TORRENT_ABI_VERSION == 1 + TEST_CHECK(s.active_time >= 1339); + TEST_CHECK(s.active_time < 1339 + 10); + + auto const now = duration_cast(clock_type::now().time_since_epoch()).count(); + TEST_CHECK(s.time_since_download >= now - 2); + TEST_CHECK(s.time_since_upload >= now - 3); + + TEST_CHECK(s.time_since_download < now - 2 + 10); + TEST_CHECK(s.time_since_upload < now - 3 + 10); +#endif + + using lt::seconds; + TEST_CHECK(s.finished_duration< seconds(1352 + 2)); + TEST_CHECK(s.seeding_duration < seconds(1340 + 2)); + TEST_CHECK(s.active_duration >= seconds(1339)); + TEST_CHECK(s.active_duration < seconds(1339 + 10)); + + TEST_CHECK(s.added_time < 1347 + 2); + TEST_CHECK(s.added_time >= 1347); + TEST_CHECK(s.completed_time < 1348 + 2); + TEST_CHECK(s.completed_time >= 1348); +} + +void test_piece_priorities(bool test_deprecated = false) +{ + lt::session ses(settings()); + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + + h.piece_priority(piece_index_t(0), 0_pri); + h.piece_priority(piece_index_t(ti->num_pieces()-1), 0_pri); + + h.save_resume_data(); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + auto const prios = ra->params.piece_priorities; + TEST_EQUAL(int(prios.size()), ti->num_pieces()); + TEST_EQUAL(prios[0], 0_pri); + TEST_EQUAL(prios[1], 4_pri); + TEST_EQUAL(prios[std::size_t(ti->num_pieces() - 1)], 0_pri); + + std::vector resume_data = write_resume_data_buf(ra->params); + TORRENT_UNUSED(test_deprecated); +#if TORRENT_ABI_VERSION == 1 + if (test_deprecated) + { + p.resume_data = resume_data; + } + else +#endif + { + p = read_resume_data(resume_data); + p.ti = ti; + p.save_path = "."; + } + } + + ses.remove_torrent(h); + + // now, make sure the piece priorities are loaded correctly + h = ses.add_torrent(p); + + TEST_EQUAL(h.piece_priority(piece_index_t(0)), 0_pri); + TEST_EQUAL(h.piece_priority(piece_index_t(1)), 4_pri); + TEST_EQUAL(h.piece_priority(piece_index_t(ti->num_pieces()-1)), 0_pri); +} + +} // anonymous namespace + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(piece_priorities_deprecated) +{ + test_piece_priorities(true); +} +#endif + +TORRENT_TEST(piece_priorities) +{ + test_piece_priorities(); +} + +TORRENT_TEST(test_non_metadata) +{ + lt::session ses(settings()); + // this test torrent contain a tracker: + // http://torrent_file_tracker.com/announce + + // and a URL seed: + // http://torrent_file_url_seed.com + + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + + h.replace_trackers(std::vector{announce_entry{"http://torrent_file_tracker2.com/announce"}}); + h.remove_url_seed("http://torrent_file_url_seed.com/"); + h.add_url_seed("http://torrent.com/"); + + TEST_EQUAL(ti->comment(), "test comment"); + TEST_EQUAL(ti->creator(), "libtorrent test"); + auto const creation_date = ti->creation_date(); + + h.save_resume_data(torrent_handle::save_info_dict); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + auto const& atp = ra->params; + TEST_CHECK(atp.trackers == std::vector{"http://torrent_file_tracker2.com/announce"}); + TEST_CHECK(atp.url_seeds == std::vector{"http://torrent.com/"}); + TEST_CHECK(atp.ti); + TEST_EQUAL(atp.ti->comment(), "test comment"); + TEST_EQUAL(atp.ti->creator(), "libtorrent test"); + TEST_EQUAL(atp.ti->creation_date(), creation_date); + + std::vector resume_data = write_resume_data_buf(atp); + p = read_resume_data(resume_data); + p.ti = ti; + p.save_path = "."; + } + + ses.remove_torrent(h); + + // now, make sure the fields are restored correctly + h = ses.add_torrent(p); + + TEST_EQUAL(h.trackers().size(), 1); + TEST_CHECK(h.trackers().at(0).url == "http://torrent_file_tracker2.com/announce"); + TEST_CHECK(h.url_seeds() == std::set{"http://torrent.com/"}); + auto t = h.status().torrent_file.lock(); + TEST_EQUAL(ti->comment(), "test comment"); + TEST_EQUAL(ti->creator(), "libtorrent test"); + TEST_EQUAL(ti->creation_date(), creation_date); +} + +TORRENT_TEST(test_remove_trackers) +{ + lt::session ses(settings()); + // this test torrent contain a tracker: + // http://torrent_file_tracker.com/announce + + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + + h.replace_trackers(std::vector{}); + + h.save_resume_data(torrent_handle::save_info_dict); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + auto const& atp = ra->params; + TEST_EQUAL(atp.trackers.size(), 0); + + std::vector resume_data = write_resume_data_buf(atp); + p = read_resume_data(resume_data); + p.ti = ti; + p.save_path = "."; + } + + ses.remove_torrent(h); + + // now, make sure the fields are restored correctly + h = ses.add_torrent(p); + + TEST_EQUAL(h.trackers().size(), 0); +} + +TORRENT_TEST(test_remove_web_seed) +{ + lt::session ses(settings()); + // this test torrent contain a URL seed: + // http://torrent_file_url_seed.com + + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + torrent_handle h = ses.add_torrent(p); + + h.remove_url_seed("http://torrent_file_url_seed.com/"); + + h.save_resume_data(torrent_handle::save_info_dict); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + auto const& atp = ra->params; + TEST_CHECK(atp.url_seeds.size() == 0); + + std::vector resume_data = write_resume_data_buf(atp); + p = read_resume_data(resume_data); + p.ti = ti; + p.save_path = "."; + } + + ses.remove_torrent(h); + + // now, make sure the fields are restored correctly + h = ses.add_torrent(p); + + TEST_EQUAL(h.url_seeds().size(), 0); +} + +TORRENT_TEST(piece_slots) +{ + // make sure the "pieces" field is correctly accepted from resume data + std::shared_ptr ti = generate_torrent(); + + error_code ec; + create_directories("add_torrent_params_test" SEP "test_resume", ec); + { + std::vector a(128 * 1024 * 8); + std::vector b(128 * 1024); + std::ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp1").write(a.data(), std::streamsize(a.size())); + std::ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp2").write(b.data(), std::streamsize(b.size())); + std::ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp3").write(b.data(), std::streamsize(b.size())); + } + + add_torrent_params p; + p.ti = ti; + p.save_path = "add_torrent_params_test"; + + p.have_pieces.resize(2); + p.have_pieces.set_bit(piece_index_t{0}); + p.have_pieces.set_bit(piece_index_t{1}); + + lt::session ses(settings()); + torrent_handle h = ses.add_torrent(p); + + wait_for_alert(ses, torrent_checked_alert::alert_type, "piece_slots"); + + torrent_status s = h.status(); + print_alerts(ses, "ses"); + TEST_EQUAL(s.info_hash, ti->info_hash()); + TEST_EQUAL(s.pieces.size(), ti->num_pieces()); + TEST_CHECK(s.pieces.size() >= 4); + TEST_EQUAL(s.pieces[piece_index_t{0}], true); + TEST_EQUAL(s.pieces[piece_index_t{1}], true); + TEST_EQUAL(s.pieces[piece_index_t{2}], false); + TEST_EQUAL(s.pieces[piece_index_t{3}], false); + + // now save resume data and make sure the pieces are preserved correctly + h.save_resume_data(); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + auto const& pieces = ra->params.have_pieces; + TEST_EQUAL(int(pieces.size()), ti->num_pieces()); + + TEST_EQUAL(pieces[piece_index_t{0}], true); + TEST_EQUAL(pieces[piece_index_t{1}], true); + TEST_EQUAL(pieces[piece_index_t{2}], false); + TEST_EQUAL(pieces[piece_index_t{3}], false); + } +} + +namespace { + +void test_piece_slots_seed(settings_pack const& sett) +{ + // make sure the "pieces" field is correctly accepted from resume data + std::shared_ptr ti = generate_torrent(); + + error_code ec; + create_directories(combine_path("add_torrent_params_test", "test_resume"), ec); + { + std::vector a(128 * 1024 * 8); + std::vector b(128 * 1024); + std::ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp1").write(a.data(), std::streamsize(a.size())); + std::ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp2").write(b.data(), std::streamsize(b.size())); + std::ofstream("add_torrent_params_test" SEP "test_resume" SEP "tmp3").write(b.data(), std::streamsize(b.size())); + } + + add_torrent_params p; + p.ti = ti; + p.save_path = "add_torrent_params_test"; + + p.have_pieces.resize(ti->num_pieces(), true); + + lt::session ses(sett); + torrent_handle h = ses.add_torrent(p); + + wait_for_alert(ses, torrent_checked_alert::alert_type, "piece_slots"); + + torrent_status s = h.status(); + print_alerts(ses, "ses"); + TEST_EQUAL(s.info_hash, ti->info_hash()); + TEST_EQUAL(s.pieces.size(), ti->num_pieces()); + for (auto const i : ti->piece_range()) + { + TEST_EQUAL(s.pieces[i], true); + } + + TEST_EQUAL(s.is_seeding, true); + + // now save resume data and make sure it reflects that we're a seed + h.save_resume_data(); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + auto const& pieces = ra->params.have_pieces; + TEST_EQUAL(int(pieces.size()), ti->num_pieces()); + + for (auto const i : ti->piece_range()) + { + TEST_EQUAL(pieces[i], true); + } + } +} + +} // anonymous namespace + +TORRENT_TEST(piece_slots_seed) +{ + test_piece_slots_seed(settings()); +} + +TORRENT_TEST(piece_slots_seed_suggest_cache) +{ + settings_pack sett = settings(); + sett.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + test_piece_slots_seed(sett); +} + +// TODO: test what happens when loading a resume file with both piece priorities +// and file priorities (file prio should take precedence) + +// TODO: make sure a resume file only ever contain file priorities OR piece +// priorities. Never both. + +// TODO: generally save + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(file_priorities_default_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses + , {}, "", "", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 4); + TEST_EQUAL(file_priorities[1], 4); + TEST_EQUAL(file_priorities[2], 4); +} + +// As long as the add_torrent_params priorities are empty, the file_priorities +// from the resume data should take effect +TORRENT_TEST(file_priorities_in_resume_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "", "123").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1); + TEST_EQUAL(file_priorities[1], 2); + TEST_EQUAL(file_priorities[2], 3); +} + +// if both resume data and add_torrent_params has file_priorities, the +// add_torrent_params one take precedence +TORRENT_TEST(file_priorities_in_resume_and_params_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "456", "123").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 4); + TEST_EQUAL(file_priorities[1], 5); + TEST_EQUAL(file_priorities[2], 6); +} + +// if we set flag_override_resume_data, it should no affect file priorities +TORRENT_TEST(file_priorities_override_resume_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses + , add_torrent_params::flag_override_resume_data, "", "123").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1); + TEST_EQUAL(file_priorities[1], 2); + TEST_EQUAL(file_priorities[2], 3); +} + +#ifndef TORRENT_DISABLE_SHARE_MODE +TORRENT_TEST(file_priorities_resume_share_mode_deprecated) +{ + // in share mode file priorities should always be 0 + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, + torrent_flags::share_mode, "", "123", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 0); + TEST_EQUAL(file_priorities[1], 0); + TEST_EQUAL(file_priorities[2], 0); +} + +TORRENT_TEST(file_priorities_share_mode_deprecated) +{ + // in share mode file priorities should always be 0 + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, + torrent_flags::share_mode, "123", "", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 0); + TEST_EQUAL(file_priorities[1], 0); + TEST_EQUAL(file_priorities[2], 0); +} +#endif + +TORRENT_TEST(resume_save_load_deprecated) +{ + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, {}, "123", "", true); + + h.save_resume_data(); + + save_resume_data_alert const* a = alert_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type + , "resume_save_load")); + + TEST_CHECK(a); + if (a == nullptr) return; + + auto const l = a->params.file_priorities; + + TEST_EQUAL(l.size(), 3); + TEST_EQUAL(l[0], 1); + TEST_EQUAL(l[1], 2); + TEST_EQUAL(l[2], 3); +} + +TORRENT_TEST(resume_save_load_resume_deprecated) +{ + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, {}, "", "123", true); + + h.save_resume_data(); + + save_resume_data_alert const* a = alert_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type + , "resume_save_load")); + + TEST_CHECK(a); + if (a == nullptr) return; + + auto const l = a->params.file_priorities; + + TEST_EQUAL(l.size(), 3); + TEST_EQUAL(l[0], 1); + TEST_EQUAL(l[1], 2); + TEST_EQUAL(l[2], 3); +} + +TORRENT_TEST(file_priorities_resume_override_deprecated) +{ + // make sure that an empty file_priorities vector in add_torrent_params won't + // override the resume data file priorities, even when override resume data + // flag is set. + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, + torrent_flags::override_resume_data, "", "123", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1); + TEST_EQUAL(file_priorities[1], 2); + TEST_EQUAL(file_priorities[2], 3); +} + +TORRENT_TEST(file_priorities_resume_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "", "123", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1); + TEST_EQUAL(file_priorities[1], 2); + TEST_EQUAL(file_priorities[2], 3); +} + +TORRENT_TEST(file_priorities1_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "010", "", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 0); + TEST_EQUAL(file_priorities[1], 1); + TEST_EQUAL(file_priorities[2], 0); + +//#error save resume data and assert the file priorities are preserved +} + +TORRENT_TEST(file_priorities2_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "123", "", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1); + TEST_EQUAL(file_priorities[1], 2); + TEST_EQUAL(file_priorities[2], 3); +} + +TORRENT_TEST(file_priorities3_deprecated) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "4321", "", true).get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 4); + TEST_EQUAL(file_priorities[1], 3); + TEST_EQUAL(file_priorities[2], 2); +} + +TORRENT_TEST(plain_deprecated) +{ + lt::session ses(settings()); + + torrent_status s = test_resume_flags(ses, {}, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags_t{}); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(use_resume_save_path_deprecated) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , torrent_flags::use_resume_save_path, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\resume_data save_path"); +#else + TEST_EQUAL(s.save_path, "/resume_data save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags_t{}); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(override_resume_data_deprecated) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , torrent_flags::override_resume_data + | torrent_flags::paused, "", "", true).status(); + + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::paused); + TEST_EQUAL(s.connections_limit, 2); + TEST_EQUAL(s.uploads_limit, 1); +} + +TORRENT_TEST(seed_mode_deprecated) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses, torrent_flags::override_resume_data + | torrent_flags::seed_mode, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::seed_mode); + TEST_EQUAL(s.connections_limit, 2); + TEST_EQUAL(s.uploads_limit, 1); +} + +TORRENT_TEST(upload_mode_deprecated) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , torrent_flags::upload_mode, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::upload_mode); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +#ifndef TORRENT_DISABLE_SHARE_MODE +TORRENT_TEST(share_mode_deprecated) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , torrent_flags::override_resume_data + | torrent_flags::share_mode, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::share_mode); + TEST_EQUAL(s.connections_limit, 2); + TEST_EQUAL(s.uploads_limit, 1); +} +#endif + +TORRENT_TEST(auto_managed_deprecated) +{ + lt::session ses(settings()); + // resume data overrides the auto-managed flag + torrent_status s = test_resume_flags(ses + , torrent_flags::auto_managed, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags_t{}); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(paused_deprecated) +{ + lt::session ses(settings()); + // resume data overrides the paused flag + torrent_status s = test_resume_flags(ses, torrent_flags::paused, "", "", true).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags_t{}); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); + + // TODO: test all other resume flags here too. This would require returning + // more than just the torrent_status from test_resume_flags. Also http seeds + // and trackers for instance +} + +TORRENT_TEST(url_seed_resume_data_deprecated) +{ + // merge url seeds with resume data + std::printf("flags: merge_resume_http_seeds\n"); + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, + torrent_flags::merge_resume_http_seeds, "", "", true); + std::set us = h.url_seeds(); + std::set ws = h.http_seeds(); + + TEST_EQUAL(us.size(), 3); + TEST_EQUAL(std::count(us.begin(), us.end() + , "http://add_torrent_params_url_seed.com/"), 1); + TEST_EQUAL(std::count(us.begin(), us.end() + , "http://torrent_file_url_seed.com/"), 1); + TEST_EQUAL(std::count(us.begin(), us.end() + , "http://resume_data_url_seed.com/"), 1); + + TEST_EQUAL(ws.size(), 1); + TEST_EQUAL(std::count(ws.begin(), ws.end() + , "http://resume_data_http_seed.com"), 1); +} + +TORRENT_TEST(resume_override_torrent_deprecated) +{ + // resume data overrides the .torrent_file + std::printf("flags: no merge_resume_http_seed\n"); + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, + torrent_flags::merge_resume_trackers, "", "", true); + std::set us = h.url_seeds(); + std::set ws = h.http_seeds(); + + TEST_EQUAL(ws.size(), 1); + TEST_EQUAL(std::count(ws.begin(), ws.end() + , "http://resume_data_http_seed.com"), 1); + + TEST_EQUAL(us.size(), 1); + TEST_EQUAL(std::count(us.begin(), us.end() + , "http://resume_data_url_seed.com/"), 1); +} +#endif + +TORRENT_TEST(file_priorities_default) +{ + lt::session ses(settings()); + std::vector file_priorities + = test_resume_flags(ses, {}, "", "").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 4_pri); + TEST_EQUAL(file_priorities[1], 4_pri); + TEST_EQUAL(file_priorities[2], 4_pri); +} + +#ifndef TORRENT_DISABLE_SHARE_MODE +TORRENT_TEST(file_priorities_resume_share_mode) +{ + // in share mode file priorities should always be 0 + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses + , torrent_flags::share_mode, "", "123").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 0_pri); + TEST_EQUAL(file_priorities[1], 0_pri); + TEST_EQUAL(file_priorities[2], 0_pri); +} + +TORRENT_TEST(file_priorities_share_mode) +{ + // in share mode file priorities should always be 0 + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses + , torrent_flags::share_mode, "123", "").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 0_pri); + TEST_EQUAL(file_priorities[1], 0_pri); + TEST_EQUAL(file_priorities[2], 0_pri); +} +#endif + +namespace { + +void test_zero_file_prio(bool test_deprecated = false, bool mix_prios = false) +{ + std::printf("test_file_prio\n"); + + lt::session ses(settings()); + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = std::max(1, ti->piece_length() / 0x4000); + + // set file priorities to 0 + rd["file_priority"] = entry::list_type(100, entry(0)); + + rd["pieces"] = std::string(std::size_t(ti->num_pieces()), '\x01'); + + // but set the piece priorities to 1. these take precedence + if (mix_prios) + rd["piece_priority"] = std::string(std::size_t(ti->num_pieces()), '\x01'); + + std::vector resume_data; + bencode(back_inserter(resume_data), rd); + TORRENT_UNUSED(test_deprecated); +#if TORRENT_ABI_VERSION == 1 + if (test_deprecated) + { + p.resume_data = resume_data; + } + else +#endif + { + p = read_resume_data(resume_data); + p.ti = ti; + p.save_path = "."; + } + + torrent_handle h = ses.add_torrent(p); + + torrent_status s = h.status(); + if (mix_prios) + { + TEST_EQUAL(s.total_wanted, ti->total_size()); + } + else + { + TEST_EQUAL(s.total_wanted, 0); + } +} + +} // anonymous namespace + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(zero_file_prio_deprecated) +{ + test_zero_file_prio(true); +} + +TORRENT_TEST(mixing_file_and_piece_prio_deprecated) +{ + test_zero_file_prio(true, true); +} + +TORRENT_TEST(backwards_compatible_resume_info_dict) +{ + // make sure the "info" dictionary is picked up correctly from the + // resume data in backwards compatible mode + + std::shared_ptr ti = generate_torrent(); + entry rd; + rd["file-format"] = "libtorrent resume file"; + rd["name"] = ti->name(); + rd["info-hash"] = ti->info_hash(); + auto metainfo = ti->metadata(); + rd["info"] = bdecode({metainfo.get(), ti->metadata_size()}); + std::vector resume_data; + bencode(back_inserter(resume_data), rd); + + add_torrent_params atp; + atp.resume_data = std::move(resume_data); + atp.save_path = "."; + + session ses; + torrent_handle h = ses.add_torrent(atp); + auto torrent = h.torrent_file(); + TEST_CHECK(torrent->info_hash() == ti->info_hash()); + torrent_status s = h.status(); +} +#endif + +TORRENT_TEST(resume_info_dict) +{ + // make sure the "info" dictionary is picked up correctly from the + // resume data + + std::shared_ptr ti = generate_torrent(); + entry rd; + rd["file-format"] = "libtorrent resume file"; + rd["name"] = ti->name(); + rd["info-hash"] = ti->info_hash(); + auto metainfo = ti->metadata(); + rd["info"] = bdecode({metainfo.get(), ti->metadata_size()}); + std::vector resume_data; + bencode(back_inserter(resume_data), rd); + + error_code ec; + add_torrent_params atp = read_resume_data(resume_data, ec); + TEST_CHECK(atp.ti->info_hash() == ti->info_hash()); +} + +TORRENT_TEST(zero_file_prio) +{ + test_zero_file_prio(); +} + +TORRENT_TEST(mixing_file_and_piece_prio) +{ + test_zero_file_prio(false, true); +} + +using test_mode_t = flags::bitfield_flag; + +namespace test_mode { + constexpr test_mode_t file_prio = 0_bit; + constexpr test_mode_t pieces_have = 1_bit; + constexpr test_mode_t piece_prio = 2_bit; + constexpr test_mode_t all_files_zero = 3_bit; +#if TORRENT_ABI_VERSION == 1 + constexpr test_mode_t deprecated = 4_bit; +#endif + constexpr test_mode_t missing_files = 5_bit; + constexpr test_mode_t pieces_have_all = 6_bit; + constexpr test_mode_t missing_all_files = 7_bit; + constexpr test_mode_t extended_files = 8_bit; +} + +namespace { + +void test_seed_mode(test_mode_t const flags) +{ + lt::session ses(settings()); + std::shared_ptr ti = generate_torrent(true); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + + if (flags & test_mode::missing_files) + { + TEST_CHECK(::remove(combine_path("test_resume", "tmp2").c_str()) == 0); + } + + if (flags & test_mode::missing_all_files) + { + lt::error_code ec; + lt::remove_all("test_resume", ec); + TEST_CHECK(!ec); + } + + if (flags & test_mode::extended_files) + { + error_code ec; + file("test_resume" SEP "tmp2", open_mode::read_write, ec).set_size(128 * 1024 + 10, ec); + TEST_CHECK(!ec); + } + + entry rd; + + rd["file-format"] = "libtorrent resume file"; + rd["file-version"] = 1; + rd["info-hash"] = ti->info_hash().to_string(); + rd["blocks per piece"] = std::max(1, ti->piece_length() / 0x4000); + + if (flags & test_mode::file_prio) + { + // this should take it out of seed_mode + entry::list_type& file_prio = rd["file_priority"].list(); + file_prio.push_back(entry(0)); + if (flags & test_mode::all_files_zero) + { + for (int i = 0; i < 100; ++i) + { + file_prio.push_back(entry(0)); + } + } + } + + if (flags & test_mode::pieces_have) + { + std::string pieces(std::size_t(ti->num_pieces()), '\x01'); + pieces[0] = '\0'; + rd["pieces"] = pieces; + } + + if (flags & test_mode::pieces_have_all) + { + rd["pieces"] = std::string(std::size_t(ti->num_pieces()), '\x01'); + } + + if (flags & test_mode::piece_prio) + { + std::string pieces_prio(std::size_t(ti->num_pieces()), '\x01'); + pieces_prio[0] = '\0'; + rd["piece_priority"] = pieces_prio; + } + + rd["seed_mode"] = 1; + + std::vector resume_data; + bencode(back_inserter(resume_data), rd); + +#if TORRENT_ABI_VERSION == 1 + if (flags & test_mode::deprecated) + { + p.resume_data = resume_data; + } + else +#endif + { + error_code ec; + p = read_resume_data(resume_data, ec); + TEST_CHECK(!ec); + p.ti = ti; + p.save_path = "."; + } + + torrent_handle h = ses.add_torrent(p); + + // if we have all pieces, any zero-priority file is not checked against the + // disk. But if we have any zero-priority file, the seed-mode flag is cleared + if ((flags & (test_mode::piece_prio + | test_mode::missing_files + | test_mode::missing_all_files + | test_mode::pieces_have)) + || (flags & (test_mode::pieces_have_all | test_mode::file_prio)) == test_mode::file_prio + ) + { + std::vector alerts; + bool done = false; + auto const start_time = lt::clock_type::now(); + while (!done) + { + ses.wait_for_alert(seconds(1)); + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + std::printf("%s\n", a->message().c_str()); + if (auto const* sca = alert_cast(a)) + { + TEST_CHECK(sca->state != torrent_status::seeding); + if (sca->state == torrent_status::downloading) done = true; + } + else if (alert_cast(a)) + { + // the torrent is not finished! + TEST_CHECK(false); + } + } + if (lt::clock_type::now() - start_time > seconds(5)) break; + } + TEST_CHECK(done); + torrent_status const s = h.status(); + TEST_CHECK(!(s.flags & torrent_flags::seed_mode)); + } + else + { + std::vector alerts; + bool done = false; + bool finished = false; + auto const start_time = lt::clock_type::now(); + while (!done || !finished) + { + ses.wait_for_alert(seconds(1)); + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + std::printf("%s\n", a->message().c_str()); + if (auto const* sca = alert_cast(a)) + { + TEST_CHECK(sca->state != torrent_status::checking_files); + if (sca->state == torrent_status::seeding) done = true; + } + else if (alert_cast(a)) + { + finished = true; + } + } + if (lt::clock_type::now() - start_time > seconds(5)) break; + } + TEST_CHECK(done); + TEST_CHECK(finished); + torrent_status const s = h.status(); + if (flags & test_mode::file_prio) + TEST_CHECK(!(s.flags & torrent_flags::seed_mode)); + else + TEST_CHECK(s.flags & torrent_flags::seed_mode); + } +} + +} // anonymous namespace + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(seed_mode_file_prio_deprecated) +{ + test_seed_mode(test_mode::file_prio | test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_piece_prio_deprecated) +{ + test_seed_mode(test_mode::pieces_have | test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_piece_have_deprecated) +{ + test_seed_mode(test_mode::piece_prio | test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_preserve_deprecated) +{ + test_seed_mode(test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_missing_files_deprecated) +{ + test_seed_mode(test_mode::missing_files | test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_missing_all_files_deprecated) +{ + test_seed_mode(test_mode::missing_all_files | test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_missing_files_with_pieces_deprecated) +{ + test_seed_mode(test_mode::missing_files | test_mode::pieces_have | test_mode::deprecated); +} + +TORRENT_TEST(seed_mode_missing_files_with_all_pieces_deprecated) +{ + test_seed_mode(test_mode::missing_files | test_mode::pieces_have_all | test_mode::deprecated); +} +#endif + +TORRENT_TEST(seed_mode_file_prio) +{ + test_seed_mode(test_mode::file_prio); +} + +TORRENT_TEST(seed_mode_extended_files) +{ + test_seed_mode(test_mode::extended_files); +} + +TORRENT_TEST(seed_mode_have_file_prio) +{ + test_seed_mode(test_mode::pieces_have_all | test_mode::file_prio); +} + +TORRENT_TEST(seed_mode_piece_prio) +{ + test_seed_mode(test_mode::pieces_have); +} + +TORRENT_TEST(seed_mode_piece_have) +{ + test_seed_mode(test_mode::piece_prio); +} + +TORRENT_TEST(seed_mode_preserve) +{ + test_seed_mode(test_mode_t{}); +} + +TORRENT_TEST(seed_mode_missing_files) +{ + test_seed_mode(test_mode::missing_files); +} + +TORRENT_TEST(seed_mode_missing_all_files) +{ + test_seed_mode(test_mode::missing_all_files); +} + +TORRENT_TEST(seed_mode_missing_files_with_pieces) +{ + test_seed_mode(test_mode::missing_files | test_mode::pieces_have); +} + +TORRENT_TEST(seed_mode_missing_files_with_all_pieces) +{ + test_seed_mode(test_mode::missing_files | test_mode::pieces_have_all); +} + +TORRENT_TEST(seed_mode_load_peers) +{ + lt::session ses(settings()); + std::shared_ptr ti = generate_torrent(); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + p.flags |= torrent_flags::seed_mode; + p.peers.push_back(tcp::endpoint(address::from_string("1.2.3.4"), 12345)); + + torrent_handle h = ses.add_torrent(p); + + wait_for_alert(ses, torrent_checked_alert::alert_type, "seed_mode_load_peers"); + + h.save_resume_data(); + + save_resume_data_alert const* a = alert_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type + , "seed_mode_load_peers")); + + TEST_CHECK(a); + if (a == nullptr) return; + + auto const& peers = a->params.peers; + TEST_EQUAL(peers.size(), 1); + TEST_CHECK(peers[0] == tcp::endpoint(address::from_string("1.2.3.4"), 12345)); +} + +TORRENT_TEST(resume_save_load) +{ + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, {}, "123", ""); + + h.save_resume_data(); + + save_resume_data_alert const* a = alert_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type + , "resume_save_load")); + + TEST_CHECK(a); + if (a == nullptr) return; + + auto const l = a->params.file_priorities; + + TEST_EQUAL(l.size(), 3); + TEST_EQUAL(l[0], 1_pri); + TEST_EQUAL(l[1], 2_pri); + TEST_EQUAL(l[2], 3_pri); +} + +TORRENT_TEST(resume_save_load_resume) +{ + lt::session ses(settings()); + torrent_handle h = test_resume_flags(ses, {}, "", "123"); + + h.save_resume_data(); + + save_resume_data_alert const* a = alert_cast( + wait_for_alert(ses, save_resume_data_alert::alert_type + , "resume_save_load")); + + TEST_CHECK(a); + if (a == nullptr) return; + + auto const l = a->params.file_priorities; + + TEST_EQUAL(l.size(), 3); + TEST_EQUAL(l[0], 1_pri); + TEST_EQUAL(l[1], 2_pri); + TEST_EQUAL(l[2], 3_pri); +} + +TORRENT_TEST(file_priorities_resume) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "", "123").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1_pri); + TEST_EQUAL(file_priorities[1], 2_pri); + TEST_EQUAL(file_priorities[2], 3_pri); +} + +TORRENT_TEST(file_priorities1) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "010").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 0_pri); + TEST_EQUAL(file_priorities[1], 1_pri); + TEST_EQUAL(file_priorities[2], 0_pri); + +//#error save resume data and assert the file priorities are preserved +} + +TORRENT_TEST(file_priorities2) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "123").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 1_pri); + TEST_EQUAL(file_priorities[1], 2_pri); + TEST_EQUAL(file_priorities[2], 3_pri); +} + +TORRENT_TEST(file_priorities3) +{ + lt::session ses(settings()); + std::vector file_priorities = test_resume_flags(ses, {}, "4321").get_file_priorities(); + + TEST_EQUAL(file_priorities.size(), 3); + TEST_EQUAL(file_priorities[0], 4_pri); + TEST_EQUAL(file_priorities[1], 3_pri); + TEST_EQUAL(file_priorities[2], 2_pri); +} + +TORRENT_TEST(plain) +{ + lt::session ses(settings()); + + torrent_status s = test_resume_flags(ses).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags_t{}); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(seed_mode) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , torrent_flags::seed_mode).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::seed_mode); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(upload_mode) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses, torrent_flags::upload_mode).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::upload_mode); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +#ifndef TORRENT_DISABLE_SHARE_MODE +TORRENT_TEST(share_mode) +{ + lt::session ses(settings()); + torrent_status s = test_resume_flags(ses + , torrent_flags::share_mode).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::share_mode); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} +#endif + +TORRENT_TEST(auto_managed) +{ + lt::session ses(settings()); + // resume data overrides the auto-managed flag + torrent_status s = test_resume_flags(ses, torrent_flags::auto_managed).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::auto_managed); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); +} + +TORRENT_TEST(paused) +{ + lt::session ses(settings()); + // resume data overrides the paused flag + torrent_status s = test_resume_flags(ses, torrent_flags::paused).status(); + default_tests(s); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(s.save_path, "c:\\add_torrent_params save_path"); +#else + TEST_EQUAL(s.save_path, "/add_torrent_params save_path"); +#endif + TEST_EQUAL(s.flags & flags_mask, torrent_flags::paused); + TEST_EQUAL(s.connections_limit, 1345); + TEST_EQUAL(s.uploads_limit, 1346); + + // TODO: test all other resume flags here too. This would require returning + // more than just the torrent_status from test_resume_flags. Also http seeds + // and trackers for instance +} diff --git a/test/test_session.cpp b/test/test_session.cpp new file mode 100644 index 0000000..efa49d7 --- /dev/null +++ b/test/test_session.cpp @@ -0,0 +1,626 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/session_stats.hpp" +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/session_stats.hpp" +#include "settings.hpp" + +#include + +using namespace std::placeholders; +using namespace lt; + +TORRENT_TEST(session) +{ + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p); + + settings_pack sett = settings(); + sett.set_int(settings_pack::num_optimistic_unchoke_slots, 10); + sett.set_int(settings_pack::unchoke_slots_limit, 10); + sett.set_int(settings_pack::resolver_cache_timeout, 1000); + + ses.apply_settings(sett); + + // verify that we get the appropriate performance warning + + alert const* a; + for (;;) + { + a = wait_for_alert(ses, performance_alert::alert_type, "ses1"); + + if (a == nullptr) break; + TEST_EQUAL(a->type(), performance_alert::alert_type); + + if (alert_cast(a)->warning_code + == performance_alert::too_many_optimistic_unchoke_slots) + break; + } + + TEST_CHECK(a); + + sett.set_int(settings_pack::unchoke_slots_limit, 0); + ses.apply_settings(sett); + TEST_CHECK(ses.get_settings().get_int(settings_pack::unchoke_slots_limit) == 0); + + sett.set_int(settings_pack::unchoke_slots_limit, -1); + ses.apply_settings(sett); + TEST_CHECK(ses.get_settings().get_int(settings_pack::unchoke_slots_limit) == -1); + + sett.set_int(settings_pack::unchoke_slots_limit, 8); + ses.apply_settings(sett); + TEST_CHECK(ses.get_settings().get_int(settings_pack::unchoke_slots_limit) == 8); + + TEST_EQUAL(ses.get_settings().get_int(settings_pack::resolver_cache_timeout), 1000); + sett.set_int(settings_pack::resolver_cache_timeout, 1001); + ses.apply_settings(sett); + TEST_EQUAL(ses.get_settings().get_int(settings_pack::resolver_cache_timeout), 1001); + + // make sure the destructor waits properly + // for the asynchronous call to set the alert + // mask completes, before it goes on to destruct + // the session object +} + +TORRENT_TEST(async_add_torrent_duplicate_error) +{ + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p); + + add_torrent_params atp; + atp.info_hash.assign("abababababababababab"); + atp.save_path = "."; + ses.async_add_torrent(atp); + + auto* a = alert_cast(wait_for_alert(ses, add_torrent_alert::alert_type, "ses")); + TEST_CHECK(a); + if (a == nullptr) return; + + atp.flags |= torrent_flags::duplicate_is_error; + ses.async_add_torrent(atp); + a = alert_cast(wait_for_alert(ses, add_torrent_alert::alert_type, "ses")); + TEST_CHECK(a); + if (a == nullptr) return; + TEST_CHECK(!a->handle.is_valid()); + TEST_CHECK(a->error); +} + +TORRENT_TEST(async_add_torrent_duplicate) +{ + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p); + + add_torrent_params atp; + atp.info_hash.assign("abababababababababab"); + atp.save_path = "."; + ses.async_add_torrent(atp); + + auto* a = alert_cast(wait_for_alert(ses, add_torrent_alert::alert_type, "ses")); + TEST_CHECK(a); + if (a == nullptr) return; + torrent_handle h = a->handle; + TEST_CHECK(!a->error); + + atp.flags &= ~torrent_flags::duplicate_is_error; + ses.async_add_torrent(atp); + a = alert_cast(wait_for_alert(ses, add_torrent_alert::alert_type, "ses")); + TEST_CHECK(a); + if (a == nullptr) return; + TEST_CHECK(a->handle == h); + TEST_CHECK(!a->error); +} + +TORRENT_TEST(async_add_torrent_duplicate_back_to_back) +{ + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p); + + add_torrent_params atp; + atp.info_hash.assign("abababababababababab"); + atp.save_path = "."; + atp.flags |= torrent_flags::paused; + atp.flags &= ~torrent_flags::apply_ip_filter; + atp.flags &= ~torrent_flags::auto_managed; + ses.async_add_torrent(atp); + + atp.flags &= ~torrent_flags::duplicate_is_error; + ses.async_add_torrent(atp); + + auto* a = alert_cast(wait_for_alert(ses + , add_torrent_alert::alert_type, "ses", pop_alerts::cache_alerts)); + TEST_CHECK(a); + if (a == nullptr) return; + torrent_handle h = a->handle; + TEST_CHECK(!a->error); + + a = alert_cast(wait_for_alert(ses + , add_torrent_alert::alert_type, "ses", pop_alerts::cache_alerts)); + TEST_CHECK(a); + if (a == nullptr) return; + TEST_CHECK(a->handle == h); + TEST_CHECK(!a->error); + + torrent_status st = h.status(); + TEST_CHECK(st.flags & torrent_flags::paused); + TEST_CHECK(!(st.flags & torrent_flags::apply_ip_filter)); + TEST_CHECK(!(st.flags & torrent_flags::auto_managed)); +} + +TORRENT_TEST(load_empty_file) +{ + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p); + + add_torrent_params atp; + error_code ignore_errors; + atp.ti = std::make_shared("", std::ref(ignore_errors), from_span); + atp.save_path = "."; + error_code ec; + torrent_handle h = ses.add_torrent(std::move(atp), ec); + + TEST_CHECK(!h.is_valid()); + TEST_CHECK(ec == error_code(errors::no_metadata)); +} + +TORRENT_TEST(session_stats) +{ + std::vector stats = session_stats_metrics(); + std::sort(stats.begin(), stats.end() + , [](stats_metric const& lhs, stats_metric const& rhs) + { return lhs.value_index < rhs.value_index; }); + + TEST_EQUAL(stats.size(), lt::counters::num_counters); + // make sure every stat index is represented in the stats_metric vector + for (int i = 0; i < int(stats.size()); ++i) + { + TEST_EQUAL(stats[std::size_t(i)].value_index, i); + } + + TEST_EQUAL(lt::find_metric_idx("peer.incoming_connections") + , lt::counters::incoming_connections); +} + +TORRENT_TEST(paused_session) +{ + lt::session s(settings()); + s.pause(); + + lt::add_torrent_params ps; + std::ofstream file("temporary"); + ps.ti = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + ps.flags = lt::torrent_flags::paused; + ps.save_path = "."; + + torrent_handle h = s.add_torrent(std::move(ps)); + + std::this_thread::sleep_for(lt::milliseconds(2000)); + h.resume(); + std::this_thread::sleep_for(lt::milliseconds(1000)); + + TEST_CHECK(!(h.flags() & torrent_flags::paused)); +} + +TORRENT_TEST(get_cache_info) +{ + lt::session s(settings()); + lt::cache_status ret; + s.get_cache_info(&ret); + + TEST_CHECK(ret.pieces.empty()); +#if TORRENT_ABI_VERSION == 1 + TEST_EQUAL(ret.blocks_written, 0); + TEST_EQUAL(ret.writes, 0); + TEST_EQUAL(ret.blocks_read, 0); + TEST_EQUAL(ret.blocks_read_hit, 0); + TEST_EQUAL(ret.reads, 0); + TEST_EQUAL(ret.queued_bytes, 0); + TEST_EQUAL(ret.cache_size, 0); + TEST_EQUAL(ret.write_cache_size, 0); + TEST_EQUAL(ret.read_cache_size, 0); + TEST_EQUAL(ret.pinned_blocks, 0); + TEST_EQUAL(ret.total_used_buffers, 0); + TEST_EQUAL(ret.average_read_time, 0); + TEST_EQUAL(ret.average_write_time, 0); + TEST_EQUAL(ret.average_hash_time, 0); + TEST_EQUAL(ret.average_job_time, 0); + TEST_EQUAL(ret.cumulative_job_time, 0); + TEST_EQUAL(ret.cumulative_read_time, 0); + TEST_EQUAL(ret.cumulative_write_time, 0); + TEST_EQUAL(ret.cumulative_hash_time, 0); + TEST_EQUAL(ret.total_read_back, 0); + TEST_EQUAL(ret.read_queue_size, 0); + TEST_EQUAL(ret.blocked_jobs, 0); + TEST_EQUAL(ret.queued_jobs, 0); + TEST_EQUAL(ret.peak_queued, 0); + TEST_EQUAL(ret.pending_jobs, 0); + TEST_EQUAL(ret.num_jobs, 0); + TEST_EQUAL(ret.num_read_jobs, 0); + TEST_EQUAL(ret.num_write_jobs, 0); + TEST_EQUAL(ret.arc_mru_size, 0); + TEST_EQUAL(ret.arc_mru_ghost_size, 0); + TEST_EQUAL(ret.arc_mfu_size, 0); + TEST_EQUAL(ret.arc_mfu_ghost_size, 0); + TEST_EQUAL(ret.arc_write_size, 0); + TEST_EQUAL(ret.arc_volatile_size, 0); + TEST_EQUAL(ret.num_writing_threads, 0); +#endif +} + +template +void test_save_restore(Set setup, Save s, Default d, Load l) +{ + entry st; + { + settings_pack p = settings(); + setup(p); + lt::session ses(p); + s(ses, st); + } + + { + settings_pack p = settings(); + d(p); + lt::session ses(p); + // the loading function takes a bdecode_node, so we have to transform the + // entry + std::printf("%s\n", st.to_string().c_str()); + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node state; + error_code ec; + int ret = bdecode(buf.data(), buf.data() + buf.size() + , state, ec, nullptr, 100, 1000); + TEST_EQUAL(ret, 0); + if (ec) + { + std::printf("bdecode: %s\n", ec.message().c_str()); + std::printf("%s\n", std::string(buf.data(), buf.size()).c_str()); + } + TEST_CHECK(!ec); + l(ses, state); + } +} + +TORRENT_TEST(save_restore_state) +{ + test_save_restore( + [](settings_pack& p) { + // set the cache size + p.set_int(settings_pack::request_queue_time, 1337); + }, + [](lt::session& ses, entry& st) { + ses.save_state(st); + }, + [](settings_pack& p) { + p.set_int(settings_pack::request_queue_time, 90); + }, + [](lt::session& ses, bdecode_node& st) { + ses.load_state(st); + // make sure we loaded the cache size correctly + settings_pack sett = ses.get_settings(); + TEST_EQUAL(sett.get_int(settings_pack::request_queue_time), 1337); + }); +} + +TORRENT_TEST(save_restore_state_save_filter) +{ + test_save_restore( + [](settings_pack& p) { + // set the cache size + p.set_int(settings_pack::request_queue_time, 1337); + }, + [](lt::session& ses, entry& st) { + // save everything _but_ the settings + ses.save_state(st, ~session::save_settings); + }, + [](settings_pack& p) { + p.set_int(settings_pack::request_queue_time, 90); + }, + [](lt::session& ses, bdecode_node& st) { + ses.load_state(st); + // make sure whatever we loaded did not include the cache size + settings_pack sett = ses.get_settings(); + TEST_EQUAL(sett.get_int(settings_pack::request_queue_time), 90); + }); +} + +TORRENT_TEST(save_restore_state_load_filter) +{ + test_save_restore( + [](settings_pack& p) { + // set the cache size + p.set_int(settings_pack::request_queue_time, 1337); + }, + [](lt::session& ses, entry& st) { + // save everything + ses.save_state(st); + }, + [](settings_pack& p) { + p.set_int(settings_pack::request_queue_time, 90); + }, + [](lt::session& ses, bdecode_node& st) { + // load everything _but_ the settings + ses.load_state(st, ~session::save_settings); + settings_pack sett = ses.get_settings(); + TEST_EQUAL(sett.get_int(settings_pack::request_queue_time), 90); + }); +} + +TORRENT_TEST(session_shutdown) +{ + lt::settings_pack pack; + lt::session ses(pack); +} + +// make sure we don't restore peer_id from session state +TORRENT_TEST(save_state_peer_id) +{ + lt::settings_pack pack; + pack.set_str(settings_pack::peer_fingerprint, "AAA"); + lt::session ses(pack); + TEST_EQUAL(ses.get_settings().get_str(settings_pack::peer_fingerprint), "AAA"); + + lt::entry st; + ses.save_state(st); + + pack.set_str(settings_pack::peer_fingerprint, "foobar"); + ses.apply_settings(pack); + + TEST_EQUAL(ses.get_settings().get_str(settings_pack::peer_fingerprint), "foobar"); + + std::vector buf; + bencode(std::back_inserter(buf), st); + bdecode_node state; + error_code ec; + int ret = bdecode(buf.data(), buf.data() + buf.size() + , state, ec, nullptr, 100, 1000); + TEST_EQUAL(ret, 0); + ses.load_state(state); + + TEST_EQUAL(ses.get_settings().get_str(settings_pack::peer_fingerprint), "foobar"); +} + +TORRENT_TEST(pop_alert_clear) +{ + session s; + + // make sure the vector is cleared if there are no alerts to be popped + std::vector alerts(100); + + for (int i = 0; i < 10; ++i) + { + alerts.resize(100); + s.pop_alerts(&alerts); + if (alerts.empty()) break; + } + TEST_CHECK(alerts.empty()); +} + +#if !defined TORRENT_DISABLE_LOGGING + +#if !defined TORRENT_DISABLE_DHT + +auto const count_dht_inits = [](session& ses) +{ + int count = 0; + int num = 200; // this number is adjusted per version, an estimate + time_point const end_time = clock_type::now() + seconds(15); + while (true) + { + time_point const now = clock_type::now(); + if (now > end_time) return count; + + ses.wait_for_alert(end_time - now); + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + std::printf("%d: [%s] %s\n", num, a->what(), a->message().c_str()); + if (a->type() == log_alert::alert_type) + { + std::string const msg = a->message(); + if (msg.find("starting DHT, running: ") != std::string::npos) + count++; + } + num--; + } + if (num <= 0) return count; + } +}; + +TORRENT_TEST(init_dht_default_bootstrap) +{ + settings_pack p = settings(); + p.set_bool(settings_pack::enable_dht, true); + p.set_int(settings_pack::alert_mask, alert_category::all); + // default value + p.set_str(settings_pack::dht_bootstrap_nodes, "dht.libtorrent.org:25401"); + + lt::session s(p); + + int const count = count_dht_inits(s); + TEST_EQUAL(count, 1); +} + +TORRENT_TEST(init_dht_invalid_bootstrap) +{ + settings_pack p = settings(); + p.set_bool(settings_pack::enable_dht, true); + p.set_int(settings_pack::alert_mask, alert_category::all); + // no default value + p.set_str(settings_pack::dht_bootstrap_nodes, "test.libtorrent.org:25401:8888"); + + lt::session s(p); + + int const count = count_dht_inits(s); + TEST_EQUAL(count, 1); +} + +TORRENT_TEST(init_dht_empty_bootstrap) +{ + settings_pack p = settings(); + p.set_bool(settings_pack::enable_dht, true); + p.set_int(settings_pack::alert_mask, alert_category::all); + // empty value + p.set_str(settings_pack::dht_bootstrap_nodes, ""); + + lt::session s(p); + + int const count = count_dht_inits(s); + TEST_EQUAL(count, 1); +} + +TORRENT_TEST(dht_upload_rate_overflow_pack) +{ + settings_pack p = settings(); + // make sure this doesn't cause an overflow + p.set_int(settings_pack::dht_upload_rate_limit, std::numeric_limits::max()); + p.set_int(settings_pack::alert_mask, alert_category_t(std::uint32_t(p.get_int(settings_pack::alert_mask))) + | alert_category::dht_log); + p.set_bool(settings_pack::enable_dht, true); + lt::session s(p); + + p = s.get_settings(); + TEST_EQUAL(p.get_int(settings_pack::dht_upload_rate_limit), std::numeric_limits::max() / 3); + + int const count = count_dht_inits(s); + TEST_EQUAL(count, 1); +} + +TORRENT_TEST(dht_upload_rate_overflow) +{ + settings_pack p = settings(); + p.set_bool(settings_pack::enable_dht, true); + p.set_int(settings_pack::alert_mask, alert_category_t(std::uint32_t(p.get_int(settings_pack::alert_mask))) + | alert_category::dht_log); + lt::session s(p); + + // make sure this doesn't cause an overflow + dht::dht_settings sett; + sett.upload_rate_limit = std::numeric_limits::max(); + s.set_dht_settings(sett); + + p = s.get_settings(); + TEST_EQUAL(p.get_int(settings_pack::dht_upload_rate_limit), std::numeric_limits::max() / 3); + + int const count = count_dht_inits(s); + TEST_EQUAL(count, 1); +} + +#endif // TORRENT_DISABLE_DHT + +TORRENT_TEST(reopen_network_sockets) +{ + auto count_alerts = [](session& ses, int const listen, int const portmap) + { + int count_listen = 0; + int count_portmap = 0; + int num = 50; // this number is adjusted per version, an estimate + time_point const end_time = clock_type::now() + seconds(1); + while (true) + { + time_point const now = clock_type::now(); + if (now > end_time) + break; + + ses.wait_for_alert(end_time - now); + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + std::printf("%d: [%s] %s\n", num, a->what(), a->message().c_str()); + std::string const msg = a->message(); + if (msg.find("successfully listening on") != std::string::npos) + count_listen++; + // upnp + if (msg.find("adding port map:") != std::string::npos) + count_portmap++; + // natpmp + if (msg.find("add-mapping: proto:") != std::string::npos) + count_portmap++; + num--; + } + if (num <= 0) + break; + } + + std::printf("count_listen: %d, count_portmap: %d\n", count_listen, count_portmap); + return count_listen == listen && count_portmap == portmap; + }; + + settings_pack p = settings(); + p.set_int(settings_pack::alert_mask, alert_category::all); + p.set_str(settings_pack::listen_interfaces, "127.0.0.1:6881l"); + + p.set_bool(settings_pack::enable_upnp, true); + p.set_bool(settings_pack::enable_natpmp, true); + + lt::session s(p); + + // NAT-PMP nad UPnP will be disabled when we only listen on loopback + TEST_CHECK(count_alerts(s, 2, 0)); + + // this is a bit of a pointless test now, since neither UPnP nor NAT-PMP are + // enabled for loopback + s.reopen_network_sockets(session_handle::reopen_map_ports); + + TEST_CHECK(count_alerts(s, 0, 0)); + + s.reopen_network_sockets({}); + + TEST_CHECK(count_alerts(s, 0, 0)); + + p.set_bool(settings_pack::enable_upnp, false); + p.set_bool(settings_pack::enable_natpmp, false); + s.apply_settings(p); + + s.reopen_network_sockets(session_handle::reopen_map_ports); + + TEST_CHECK(count_alerts(s, 0, 0)); +} +#endif + diff --git a/test/test_session_params.cpp b/test/test_session_params.cpp new file mode 100644 index 0000000..5ce83e1 --- /dev/null +++ b/test/test_session_params.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2016, Alden Torres +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bdecode.hpp" +#include "libtorrent/hex.hpp" +#include "setup_transfer.hpp" // for addr6 +#include "settings.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" + +using namespace lt; +using dht::dht_storage_interface; +using dht::dht_state; + +namespace +{ +#ifndef TORRENT_DISABLE_DHT + bool g_storage_constructor_invoked = false; + + std::unique_ptr dht_custom_storage_constructor( + dht::dht_settings const& settings) + { + g_storage_constructor_invoked = true; + return dht_default_storage_constructor(settings); + } +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS + bool g_plugin_added_invoked = false; + + struct custom_plugin : plugin + { + void added(session_handle const& h) override + { + TORRENT_UNUSED(h); + g_plugin_added_invoked = true; + } + }; +#endif +} + +TORRENT_TEST(default_plugins) +{ + session_params p1; +#ifndef TORRENT_DISABLE_EXTENSIONS + TEST_EQUAL(int(p1.extensions.size()), 3); +#else + TEST_EQUAL(int(p1.extensions.size()), 0); +#endif + + std::vector> exts; + session_params p2(settings_pack(), exts); + TEST_EQUAL(int(p2.extensions.size()), 0); +} + +#ifndef TORRENT_DISABLE_DHT +TORRENT_TEST(custom_dht_storage) +{ + g_storage_constructor_invoked = false; + settings_pack p = settings(); + p.set_bool(settings_pack::enable_dht, true); + session_params params(p); + params.dht_storage_constructor = dht_custom_storage_constructor; + lt::session ses(params); + + TEST_CHECK(ses.is_dht_running() == true); + TEST_EQUAL(g_storage_constructor_invoked, true); +} + +TORRENT_TEST(dht_state) +{ + settings_pack p = settings(); + p.set_bool(settings_pack::enable_dht, true); + + dht::dht_settings sett; + sett.max_dht_items = 10000; + sett.max_peers = 20000; + + dht_state s; + s.nids.emplace_back(addr4("0.0.0.0"), to_hash("0000000000000000000000000000000000000001")); + s.nodes.push_back(uep("1.1.1.1", 1)); + s.nodes.push_back(uep("2.2.2.2", 2)); + // not important that IPv6 is disabled here + s.nids.emplace_back(addr6("::"), to_hash("0000000000000000000000000000000000000002")); + + session_params params(p); + params.dht_settings = sett; + params.dht_state = s; + + params.settings.set_str(settings_pack::listen_interfaces, "127.0.0.1:6881"); + + lt::session ses1(params); + TEST_CHECK(ses1.is_dht_running() == true); + entry e; + ses1.save_state(e); + + std::vector tmp; + bencode(std::back_inserter(tmp), e); + + bdecode_node n; + error_code ec; + int r = bdecode(&tmp[0], &tmp[0] + tmp.size(), n, ec); + TEST_CHECK(!r); + + session_params params1 = read_session_params(n); + TEST_EQUAL(params1.dht_settings.max_dht_items, 10000); + TEST_EQUAL(params1.dht_settings.max_peers, 20000); + + TEST_EQUAL(params1.dht_state.nids.size(), 1); + + if (params1.dht_state.nids.size() >= 1) { + // not a chance the nid will be the fake initial ones + TEST_CHECK(params1.dht_state.nids[0].second != s.nids[0].second); + } +} +#endif + +#ifndef TORRENT_DISABLE_EXTENSIONS +TORRENT_TEST(add_plugin) +{ + g_plugin_added_invoked = false; + session_params params(settings()); + params.extensions.push_back(std::make_shared()); + lt::session ses(params); + + TEST_EQUAL(g_plugin_added_invoked, true); +} +#endif diff --git a/test/test_settings_pack.cpp b/test/test_settings_pack.cpp new file mode 100644 index 0000000..8a2e7a5 --- /dev/null +++ b/test/test_settings_pack.cpp @@ -0,0 +1,262 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/entry.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/bdecode.hpp" +#include + +using namespace lt; +using namespace lt::aux; + +TORRENT_TEST(default_settings) +{ + aux::session_settings sett; + + entry e; + save_settings_to_dict(sett, e.dict()); + // all default values are supposed to be skipped + // by save_settings + TEST_EQUAL(e.dict().size(), 0); + +#if TORRENT_USE_IOSTREAM + if (e.dict().size() > 0) + std::cout << e << std::endl; +#endif +} + +TORRENT_TEST(default_settings2) +{ + aux::session_settings sett; + + settings_pack def = default_settings(); + + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + TEST_EQUAL(sett.get_str(settings_pack::string_type_base + i) + , def.get_str(settings_pack::string_type_base + i)); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + TEST_EQUAL(sett.get_int(settings_pack::int_type_base + i) + , def.get_int(settings_pack::int_type_base + i)); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + TEST_EQUAL(sett.get_bool(settings_pack::bool_type_base + i) + , def.get_bool(settings_pack::bool_type_base + i)); + } +} + +TORRENT_TEST(apply_pack) +{ + aux::session_settings sett; + settings_pack sp; + sp.set_int(settings_pack::max_out_request_queue, 1337); + + TEST_CHECK(sett.get_int(settings_pack::max_out_request_queue) != 1337); + + apply_pack(&sp, sett); + + TEST_EQUAL(sett.get_int(settings_pack::max_out_request_queue), 1337); + entry e; + save_settings_to_dict(sett, e.dict()); + TEST_EQUAL(e.dict().size(), 1); + + std::string out; + bencode(std::back_inserter(out), e); + TEST_EQUAL(out, "d21:max_out_request_queuei1337ee"); +} + +TORRENT_TEST(sparse_pack) +{ + settings_pack pack; + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false); + + pack.set_bool(settings_pack::send_redundant_have, true); + + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), true); + TEST_EQUAL(pack.has_val(settings_pack::user_agent), false); + TEST_EQUAL(pack.get_bool(settings_pack::send_redundant_have), true); +} + +TORRENT_TEST(test_name) +{ +#define TEST_NAME(n) \ + TEST_EQUAL(setting_by_name(#n), settings_pack:: n); \ + TEST_EQUAL(name_for_setting(settings_pack:: n), std::string(#n)) + +#if TORRENT_ABI_VERSION == 1 + TEST_NAME(contiguous_recv_buffer); +#endif + TEST_NAME(choking_algorithm); + TEST_NAME(seeding_piece_quota); +#if TORRENT_ABI_VERSION == 1 + TEST_NAME(half_open_limit); + TEST_NAME(mmap_cache); +#endif + TEST_NAME(peer_turnover_interval); + TEST_NAME(peer_fingerprint); + TEST_NAME(proxy_tracker_connections); + TEST_NAME(cache_size_volatile); + TEST_NAME(predictive_piece_announce); + TEST_NAME(max_metadata_size); + TEST_NAME(num_optimistic_unchoke_slots); +} + +TORRENT_TEST(clear) +{ + settings_pack pack; + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false); + + pack.set_bool(settings_pack::send_redundant_have, true); + + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), true); + TEST_EQUAL(pack.has_val(settings_pack::user_agent), false); + TEST_EQUAL(pack.get_bool(settings_pack::send_redundant_have), true); + + pack.clear(); + + TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false); + TEST_EQUAL(pack.has_val(settings_pack::user_agent), false); +} + +TORRENT_TEST(clear_single_int) +{ + settings_pack sp; + sp.set_int(settings_pack::max_out_request_queue, 1337); + + TEST_EQUAL(sp.get_int(settings_pack::max_out_request_queue), 1337); + + sp.clear(settings_pack::max_out_request_queue); + + TEST_EQUAL(sp.get_int(settings_pack::max_out_request_queue), 0); +} + +TORRENT_TEST(clear_single_bool) +{ + settings_pack sp; + sp.set_bool(settings_pack::send_redundant_have, true); + + TEST_EQUAL(sp.get_bool(settings_pack::send_redundant_have), true); + + sp.clear(settings_pack::send_redundant_have); + + TEST_EQUAL(sp.get_bool(settings_pack::send_redundant_have), false); +} + +TORRENT_TEST(clear_single_string) +{ + settings_pack sp; + sp.set_str(settings_pack::user_agent, "foobar"); + + TEST_EQUAL(sp.get_str(settings_pack::user_agent), "foobar"); + + sp.clear(settings_pack::user_agent); + + TEST_EQUAL(sp.get_str(settings_pack::user_agent), std::string()); +} + +TORRENT_TEST(duplicates) +{ + settings_pack p; + p.set_str(settings_pack::peer_fingerprint, "abc"); + p.set_str(settings_pack::peer_fingerprint, "cde"); + p.set_str(settings_pack::peer_fingerprint, "efg"); + p.set_str(settings_pack::peer_fingerprint, "hij"); + + TEST_EQUAL(p.get_str(settings_pack::peer_fingerprint), "hij"); +} + +TORRENT_TEST(load_pack_from_dict) +{ + aux::session_settings p1; + p1.set_str(settings_pack::peer_fingerprint, "abc"); + p1.set_int(settings_pack::max_out_request_queue, 1337); + p1.set_bool(settings_pack::send_redundant_have, false); + + entry e; + save_settings_to_dict(p1, e.dict()); + + std::string s; + bencode(std::back_inserter(s), e); + + bdecode_node n; + error_code ec; + int ret = bdecode(s.data(), s.data() + int(s.size()), n, ec); + TEST_EQUAL(ret, 0); + TEST_CHECK(!ec); + + settings_pack p2 = load_pack_from_dict(n); + TEST_EQUAL(p2.get_str(settings_pack::peer_fingerprint), "abc"); + TEST_EQUAL(p2.get_int(settings_pack::max_out_request_queue), 1337); + TEST_EQUAL(p2.get_bool(settings_pack::send_redundant_have), false); +} + +TORRENT_TEST(settings_pack_abi) +{ + // make sure enum values are preserved across libtorrent versions + // for ABI compatibility + // These values are only allowed to change across major versions + + TEST_EQUAL(settings_pack::string_type_base, 0x0000); + TEST_EQUAL(settings_pack::int_type_base, 0x4000); + TEST_EQUAL(settings_pack::bool_type_base, 0x8000); + TEST_EQUAL(settings_pack::type_mask, 0xc000); + + // strings + TEST_EQUAL(settings_pack::outgoing_interfaces, settings_pack::string_type_base + 4); + TEST_EQUAL(settings_pack::dht_bootstrap_nodes, settings_pack::string_type_base + 11); + + // bool + TEST_EQUAL(settings_pack::use_dht_as_fallback, settings_pack::bool_type_base + 4); + TEST_EQUAL(settings_pack::use_read_cache, settings_pack::bool_type_base + 7); + TEST_EQUAL(settings_pack::proxy_tracker_connections, settings_pack::bool_type_base + 67); + + // ints + TEST_EQUAL(settings_pack::tracker_completion_timeout, settings_pack::int_type_base + 0); + TEST_EQUAL(settings_pack::tracker_receive_timeout, settings_pack::int_type_base + 1); + TEST_EQUAL(settings_pack::stop_tracker_timeout, settings_pack::int_type_base + 2); + TEST_EQUAL(settings_pack::max_suggest_pieces, settings_pack::int_type_base + 66); + TEST_EQUAL(settings_pack::connections_slack, settings_pack::int_type_base + 86); + TEST_EQUAL(settings_pack::aio_threads, settings_pack::int_type_base + 104); + TEST_EQUAL(settings_pack::max_http_recv_buffer_size, settings_pack::int_type_base + 115); + TEST_EQUAL(settings_pack::web_seed_name_lookup_retry, settings_pack::int_type_base + 128); + TEST_EQUAL(settings_pack::close_file_interval, settings_pack::int_type_base + 129); + TEST_EQUAL(settings_pack::max_web_seed_connections, settings_pack::int_type_base + 131); + TEST_EQUAL(settings_pack::resolver_cache_timeout, settings_pack::int_type_base + 132); +} diff --git a/test/test_sha1_hash.cpp b/test/test_sha1_hash.cpp new file mode 100644 index 0000000..c6b4a42 --- /dev/null +++ b/test/test_sha1_hash.cpp @@ -0,0 +1,146 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/hex.hpp" // from_hex + +using namespace lt; + +static sha1_hash to_hash(char const* s) +{ + sha1_hash ret; + aux::from_hex({s, 40}, ret.data()); + return ret; +} + +TORRENT_TEST(sha1_hash) +{ + sha1_hash h1(nullptr); + sha1_hash h2(nullptr); + TEST_CHECK(h1 == h2); + TEST_CHECK(!(h1 != h2)); + TEST_CHECK(!(h1 < h2)); + TEST_CHECK(!(h1 < h2)); + TEST_CHECK(h1.is_all_zeros()); + + h1 = to_hash("0123456789012345678901234567890123456789"); + h2 = to_hash("0113456789012345678901234567890123456789"); + + TEST_CHECK(h2 < h1); + TEST_CHECK(h2 == h2); + TEST_CHECK(h1 == h1); + h2.clear(); + TEST_CHECK(h2.is_all_zeros()); + + h2 = to_hash("ffffffffff0000000000ffffffffff0000000000"); + h1 = to_hash("fffff00000fffff00000fffff00000fffff00000"); + h1 &= h2; + TEST_CHECK(h1 == to_hash("fffff000000000000000fffff000000000000000")); + + h2 = to_hash("ffffffffff0000000000ffffffffff0000000000"); + h1 = to_hash("fffff00000fffff00000fffff00000fffff00000"); + h1 |= h2; + TEST_CHECK(h1 == to_hash("fffffffffffffff00000fffffffffffffff00000")); + + h2 = to_hash("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); + h1 ^= h2; + TEST_CHECK(h1 == to_hash("f0f0f0f0f0f0f0ff0f0ff0f0f0f0f0f0f0ff0f0f")); + TEST_CHECK(h1 != h2); + + h2 = sha1_hash(" "); + TEST_CHECK(h2 == to_hash("2020202020202020202020202020202020202020")); + + h1 = to_hash("ffffffffff0000000000ffffffffff0000000000"); + h1 <<= 12; + TEST_CHECK(h1 == to_hash("fffffff0000000000ffffffffff0000000000000")); + h1 >>= 12; + TEST_CHECK(h1 == to_hash("000fffffff0000000000ffffffffff0000000000")); + + h1 = to_hash("7000000000000000000000000000000000000000"); + h1 <<= 1; + TEST_CHECK(h1 == to_hash("e000000000000000000000000000000000000000")); + + h1 = to_hash("0000000000000000000000000000000000000007"); + h1 <<= 1; + TEST_CHECK(h1 == to_hash("000000000000000000000000000000000000000e")); + + h1 = to_hash("0000000000000000000000000000000000000007"); + h1 >>= 1; + TEST_CHECK(h1 == to_hash("0000000000000000000000000000000000000003")); + + h1 = to_hash("7000000000000000000000000000000000000000"); + h1 >>= 1; + TEST_CHECK(h1 == to_hash("3800000000000000000000000000000000000000")); + + h1 = to_hash("7000000000000000000000000000000000000000"); + h1 >>= 32; + TEST_CHECK(h1 == to_hash("0000000070000000000000000000000000000000")); + h1 >>= 33; + TEST_CHECK(h1 == to_hash("0000000000000000380000000000000000000000")); + h1 <<= 33; + TEST_CHECK(h1 == to_hash("0000000070000000000000000000000000000000")); +} + +TORRENT_TEST(count_leading_zeroes) +{ + std::vector> const tests = { + { "ffffffffffffffffffffffffffffffffffffffff", 0 }, + { "0000000000000000000000000000000000000000", 160 }, + { "fff0000000000000000000000000000000000000", 0 }, + { "7ff0000000000000000000000000000000000000", 1 }, + { "3ff0000000000000000000000000000000000000", 2 }, + { "1ff0000000000000000000000000000000000000", 3 }, + { "0ff0000000000000000000000000000000000000", 4 }, + { "07f0000000000000000000000000000000000000", 5 }, + { "03f0000000000000000000000000000000000000", 6 }, + { "01f0000000000000000000000000000000000000", 7 }, + { "00f0000000000000000000000000000000000000", 8 }, + { "0070000000000000000000000000000000000000", 9 }, + { "0030000000000000000000000000000000000000", 10 }, + { "0010000000000000000000000000000000000000", 11 }, + { "0000000ffff00000000000000000000000000000", 28 }, + { "00000007fff00000000000000000000000000000", 29 }, + { "00000003fff00000000000000000000000000000", 30 }, + { "00000001fff00000000000000000000000000000", 31 }, + { "00000000fff00000000000000000000000000000", 32 }, + { "000000007ff00000000000000000000000000000", 33 }, + { "000000003ff00000000000000000000000000000", 34 }, + { "000000001ff00000000000000000000000000000", 35 }, + }; + + for (auto const& t : tests) + { + std::printf("%s\n", t.first); + TEST_EQUAL(to_hash(t.first).count_leading_zeroes(), t.second); + } +} diff --git a/test/test_sliding_average.cpp b/test/test_sliding_average.cpp new file mode 100644 index 0000000..cbbd206 --- /dev/null +++ b/test/test_sliding_average.cpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/sliding_average.hpp" + +namespace { + +// normal distributed samples. mean=60 stddev=10 +int samples[] = { +49, 51, 60, 46, 65, 53, 76, 59, 57, 54, 56, 51, 45, 80, 53, 62, +69, 67, 66, 56, 56, 61, 52, 61, 61, 62, 59, 53, 48, 68, 47, 47, +63, 51, 53, 54, 46, 65, 64, 64, 45, 68, 64, 66, 53, 42, 57, 58, +57, 47, 55, 59, 64, 61, 37, 67, 55, 52, 60, 60, 44, 57, 50, 77, +56, 54, 49, 68, 66, 64, 47, 60, 46, 47, 81, 74, 65, 62, 44, 75, +65, 43, 58, 59, 53, 67, 49, 51, 33, 47, 49, 50, 54, 48, 55, 80, +67, 51, 66, 52, 48, 57, 30, 51, 72, 65, 78, 56, 74, 68, 49, 66, +63, 57, 61, 62, 64, 62, 61, 52, 67, 64, 59, 61, 69, 60, 54, 69 }; + +} // anonymous namespace + +using namespace lt; + +// make sure we react quickly for the first few samples +TORRENT_TEST(reaction_time) +{ + sliding_average avg; + + avg.add_sample(-10); + avg.add_sample(10); + + TEST_EQUAL(avg.mean(), 0); +} + +TORRENT_TEST(reaction_time2) +{ + sliding_average avg; + + avg.add_sample(10); + avg.add_sample(20); + + TEST_EQUAL(avg.mean(), 15); +} + +// make sure we converge +TORRENT_TEST(converge) +{ + sliding_average avg; + avg.add_sample(100); + for (int i = 0; i < 20; ++i) + avg.add_sample(10); + TEST_CHECK(abs(avg.mean() - 10) <= 3); +} + +TORRENT_TEST(converge2) +{ + sliding_average avg; + avg.add_sample(-100); + for (int i = 0; i < 20; ++i) + avg.add_sample(-10); + TEST_CHECK(abs(avg.mean() + 10) <= 3); +} + +// test with a more realistic input +TORRENT_TEST(random_converge) +{ + sliding_average avg; + for (int i = 0; i < int(sizeof(samples)/sizeof(samples[0])); ++i) + avg.add_sample(samples[i]); + TEST_CHECK(abs(avg.mean() - 60) <= 3); +} + +TORRENT_TEST(sliding_average) +{ + sliding_average avg; + TEST_EQUAL(avg.mean(), 0); + TEST_EQUAL(avg.avg_deviation(), 0); + avg.add_sample(500); + TEST_EQUAL(avg.mean(), 500); + TEST_EQUAL(avg.avg_deviation(), 0); + avg.add_sample(501); + TEST_EQUAL(avg.avg_deviation(), 1); + avg.add_sample(0); + avg.add_sample(0); + std::printf("avg: %d dev: %d\n", avg.mean(), avg.avg_deviation()); + TEST_CHECK(abs(avg.mean() - 250) < 50); + TEST_CHECK(abs(avg.avg_deviation() - 250) < 80); +} diff --git a/test/test_socket_io.cpp b/test/test_socket_io.cpp new file mode 100644 index 0000000..0e5e976 --- /dev/null +++ b/test/test_socket_io.cpp @@ -0,0 +1,217 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/socket.hpp" + +#include + +using namespace lt; +using namespace lt::detail; + +TORRENT_TEST(address_to_bytes) +{ + // test address_to_bytes + TEST_EQUAL(address_to_bytes(addr4("10.11.12.13")), "\x0a\x0b\x0c\x0d"); + TEST_EQUAL(address_to_bytes(addr4("16.5.127.1")), "\x10\x05\x7f\x01"); + + // test endpoint_to_bytes + TEST_EQUAL(endpoint_to_bytes(uep("10.11.12.13", 8080)), "\x0a\x0b\x0c\x0d\x1f\x90"); + TEST_EQUAL(endpoint_to_bytes(uep("16.5.127.1", 12345)), "\x10\x05\x7f\x01\x30\x39"); +} + +TORRENT_TEST(read_v4_address) +{ + std::string buf; + write_address(addr4("16.5.128.1"), std::back_inserter(buf)); + TEST_EQUAL(buf, "\x10\x05\x80\x01"); + address addr = read_v4_address(buf.begin()); + TEST_EQUAL(addr, addr4("16.5.128.1")); + + buf.clear(); + write_endpoint(uep("16.5.128.1", 1337) + , std::back_inserter(buf)); + TEST_EQUAL(buf, "\x10\x05\x80\x01\x05\x39"); + udp::endpoint ep4 = read_v4_endpoint(buf.begin()); + TEST_EQUAL(ep4, uep("16.5.128.1", 1337)); +} + +TORRENT_TEST(read_v6_endpoint) +{ + std::string buf; + write_address(addr6("1000::ffff"), std::back_inserter(buf)); + TEST_CHECK(std::equal(buf.begin(), buf.end(), "\x10\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff")); + address addr = read_v6_address(buf.begin()); + TEST_EQUAL(addr, addr6("1000::ffff")); + + buf.clear(); + write_endpoint(uep("1000::ffff", 1337) + , std::back_inserter(buf)); + TEST_CHECK(std::equal(buf.begin(), buf.end() + , "\x10\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff\x05\x39")); + TEST_EQUAL(buf.size(), 18); + udp::endpoint ep6 = read_v6_endpoint(buf.begin()); + TEST_EQUAL(ep6, uep("1000::ffff", 1337)); +} + +TORRENT_TEST(read_endpoint_list) +{ + char const eplist[] = "l6:\x10\x05\x80\x01\x05\x39" + "18:\x10\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xff\x05\x39" "e"; + bdecode_node e; + error_code ec; + bdecode(eplist, eplist + sizeof(eplist)-1, e, ec); + TEST_CHECK(!ec); + std::vector list = read_endpoint_list(e); + + TEST_EQUAL(list.size(), 2); + TEST_EQUAL(list[1], uep("1000::ffff", 1337)); + TEST_EQUAL(list[0], uep("16.5.128.1", 1337)); +} + +TORRENT_TEST(parse_invalid_ipv4_endpoint) +{ + error_code ec; + tcp::endpoint endp; + + endp = parse_endpoint("", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("\n\t ", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1-4", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1:-4", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1:66000", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1:abc", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1", ec); + TEST_CHECK(ec); + ec.clear(); + +#ifndef TORRENT_WINDOWS + // it appears windows silently accepts truncated IP addresses + endp = parse_endpoint("127.0.0:123", ec); + TEST_CHECK(ec); + ec.clear(); +#endif + + endp = parse_endpoint("127.0.0.1:", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("127.0.0.1X", ec); + TEST_CHECK(ec); + ec.clear(); +} + +TORRENT_TEST(parse_valid_ip4_endpoint) +{ + error_code ec; + TEST_EQUAL(parse_endpoint("127.0.0.1:4", ec), ep("127.0.0.1", 4)); + TEST_CHECK(!ec); + ec.clear(); + + TEST_EQUAL(parse_endpoint("\t 127.0.0.1:4 \n", ec), ep("127.0.0.1", 4)); + TEST_CHECK(!ec); + ec.clear(); + + TEST_EQUAL(parse_endpoint("127.0.0.1:23", ec), ep("127.0.0.1", 23)); + TEST_CHECK(!ec); + ec.clear(); +} + +TORRENT_TEST(parse_invalid_ipv6_endpoint) +{ + error_code ec; + tcp::endpoint endp; + + endp = parse_endpoint("[::1]-4", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("[::1]", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("[::1]:", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("[::1]X", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("[::1", ec); + TEST_CHECK(ec == errors::expected_close_bracket_in_address); + ec.clear(); + + parse_endpoint("[ff::1:5", ec); + TEST_EQUAL(ec, error_code(errors::expected_close_bracket_in_address)); + ec.clear(); + + endp = parse_endpoint("[abcd]:123", ec); + TEST_CHECK(ec); + ec.clear(); + + endp = parse_endpoint("[ff::1]", ec); + TEST_EQUAL(ec, error_code(errors::invalid_port)); + ec.clear(); +} + +TORRENT_TEST(parse_valid_ipv6_endpoint) +{ + error_code ec; + TEST_EQUAL(parse_endpoint("[::1]:4", ec), ep("::1", 4)); + TEST_CHECK(!ec); + ec.clear(); + + TEST_EQUAL(parse_endpoint(" \t[ff::1]:1214 \r", ec), ep("ff::1", 1214)); + TEST_CHECK(!ec); + ec.clear(); +} + diff --git a/test/test_span.cpp b/test/test_span.cpp new file mode 100644 index 0000000..a7a0420 --- /dev/null +++ b/test/test_span.cpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/span.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" + +#include +#include + +using namespace lt; + +namespace { + +span f(span x) { return x; } +span> g(span> x) { return x; } + +} // anonymous namespace + +TORRENT_TEST(span_vector) +{ + std::vector v1 = {1,2,3,4}; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_vector_assignment) +{ + std::vector v1 = {1,2,3,4}; + span a; + a = v1; + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_assignment) +{ + char v1[] = {1,2,3,4}; + span a2(v1); + span a; + a = a2; + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +namespace { + +void do_span_temp_vector(span a) +{ + std::vector v1 = {1,2,3,4}; + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +} // anonymous namespace + +TORRENT_TEST(span_temp_vector) +{ + do_span_temp_vector(std::vector{1,2,3,4}); +} + +TORRENT_TEST(span_std_array) +{ + std::array v1{{1,2,3,4}}; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_const_std_array) +{ + std::array v1{{1,2,3,4}}; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_array) +{ + char v1[] = {1,2,3,4}; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_string) +{ + std::string v1 = "test"; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_const_array) +{ + char const v1[] = {1,2,3,4}; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 4); +} + +TORRENT_TEST(span_single_element) +{ + char const v1 = 1; + span a(v1); + TEST_CHECK(a == f(v1)); + TEST_CHECK(a.size() == 1); +} + +TORRENT_TEST(span_of_spans) +{ + std::vector v1 = {1,2,3,4}; + span s1(v1); + span> a(s1); + TEST_CHECK(a == g(s1)); + TEST_CHECK(a.size() == 1); + TEST_CHECK(a[0].size() == 4); +} + diff --git a/test/test_ssl.cpp b/test/test_ssl.cpp new file mode 100644 index 0000000..3c707e7 --- /dev/null +++ b/test/test_ssl.cpp @@ -0,0 +1,643 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/session_status.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/hex.hpp" // for to_hex +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/openssl.hpp" + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" + +#include "libtorrent/aux_/disable_warnings_push.hpp" + +#include + +#ifdef TORRENT_USE_OPENSSL +#include + +#include "libtorrent/aux_/disable_warnings_pop.hpp" + +#include +#include +#include +#include + +using namespace std::placeholders; +using namespace lt; +using std::ignore; + +namespace { + +struct test_config_t +{ + char const* name; + bool use_ssl_ports; + bool seed_has_cert; + bool downloader_has_cert; + bool downloader_has_ssl_listen_port; + bool expected_to_complete; + int peer_errors; + int ssl_disconnects; +}; + +test_config_t const test_config[] = +{ + // name sslport sd-cert dl-cert dl-port expect peer-error ssl-disconn + {"nobody has a cert (connect to regular port)", false, false, false, true, false, 0, 1}, + {"nobody has a cert (connect to ssl port)", true, false, false, true, false, 1, 1}, + {"seed has a cert, but not downloader (connect to regular port)", false, true, false, true, false, 0, 1}, + {"seed has a cert, but not downloader (connect to ssl port)", true, true, false, true, false, 1, 1}, + {"downloader has a cert, but not seed (connect to regular port)", false, false, true, true, false, 0, 1}, + {"downloader has a cert, but not seed (connect to ssl port)", true, false, true, true, false, 1, 1}, + {"both downloader and seed has a cert (connect to regular port)", false, true, true, true, false, 0, 1}, + {"both downloader and seed has a cert (connect to ssl port)", true, true, true, true, true, 0, 0}, + // there is a disconnect (or failed connection attempt), that's not a peer + // error though, so both counters stay 0 + {"both downloader and seed has a cert (downloader has no SSL port)", true, true, true, false, false, 0, 0}, +}; + +int peer_disconnects = 0; +int peer_errors = 0; +int ssl_peer_disconnects = 0; + +bool on_alert(alert const* a) +{ + if (peer_disconnected_alert const* e = alert_cast(a)) + { + ++peer_disconnects; + string_view const cat = e->error.category().name(); + if (cat == boost::asio::error::get_ssl_category().name() +#if BOOST_VERSION >= 106400 + || cat == boost::asio::ssl::error::get_stream_category().name() +#endif + ) + ++ssl_peer_disconnects; + + std::printf("--- peer_errors: %d ssl_disconnects: %d\n" + , peer_errors, ssl_peer_disconnects); + } + + if (peer_error_alert const* e = alert_cast(a)) + { + ++peer_disconnects; + ++peer_errors; + + string_view const cat = e->error.category().name(); + if (cat == boost::asio::error::get_ssl_category().name() +#if BOOST_VERSION >= 106400 + || cat == boost::asio::ssl::error::get_stream_category().name() +#endif + ) + ++ssl_peer_disconnects; + + std::printf("--- peer_errors: %d ssl_disconnects: %d\n" + , peer_errors, ssl_peer_disconnects); + } + return false; +} + +void test_ssl(int const test_idx, bool const use_utp) +{ + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + + test_config_t const& test = test_config[test_idx]; + + std::printf("\n%s TEST: %s Protocol: %s\n\n", time_now_string() + , test.name, use_utp ? "uTP": "TCP"); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_ssl", ec); + remove_all("tmp2_ssl", ec); + + int port = 1024 + rand() % 50000; + settings_pack sett = settings(); + sett.set_int(settings_pack::max_retry_port_bind, 100); + + char listen_iface[100]; + std::snprintf(listen_iface, sizeof(listen_iface), "0.0.0.0:%ds", port); + sett.set_str(settings_pack::listen_interfaces, listen_iface); + sett.set_bool(settings_pack::enable_incoming_utp, use_utp); + sett.set_bool(settings_pack::enable_outgoing_utp, use_utp); + sett.set_bool(settings_pack::enable_incoming_tcp, !use_utp); + sett.set_bool(settings_pack::enable_outgoing_tcp, !use_utp); + sett.set_bool(settings_pack::enable_dht, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + // if a peer fails once, don't try it again + sett.set_int(settings_pack::max_failcount, 1); + + lt::session ses1(sett, {}); + + // this +20 is here to use a different port as ses1 + port += 20; + + // the +20 below is the port we use for non-SSL connections + if (test.downloader_has_ssl_listen_port) + std::snprintf(listen_iface, sizeof(listen_iface), "0.0.0.0:%d,0.0.0.0:%ds", port + 20, port); + else + std::snprintf(listen_iface, sizeof(listen_iface), "0.0.0.0:%d", port + 20); + + sett.set_str(settings_pack::listen_interfaces, listen_iface); + + lt::session ses2(sett, {}); + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + torrent_handle tor1; + torrent_handle tor2; + + create_directory("tmp1_ssl", ec); + std::ofstream file("tmp1_ssl/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary" + , 16 * 1024, 13, false, combine_path("..", combine_path("ssl", "root_ca_cert.pem"))); + file.close(); + + add_torrent_params addp; + addp.save_path = "tmp1_ssl"; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + + peer_disconnects = 0; + ssl_peer_disconnects = 0; + peer_errors = 0; + + std::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, nullptr + , true, false, false, "_ssl", 16 * 1024, &t, false, &addp, true); + + if (test.seed_has_cert) + { + tor1.set_ssl_certificate( + combine_path("..", combine_path("ssl", "peer_certificate.pem")) + , combine_path("..", combine_path("ssl", "peer_private_key.pem")) + , combine_path("..", combine_path("ssl", "dhparams.pem")) + , "test"); + } + + if (test.downloader_has_cert) + { + tor2.set_ssl_certificate( + combine_path("..", combine_path("ssl", "peer_certificate.pem")) + , combine_path("..", combine_path("ssl", "peer_private_key.pem")) + , combine_path("..", combine_path("ssl", "dhparams.pem")) + , "test"); + } + + // make sure they've taken effect + if (test.downloader_has_cert || test.seed_has_cert) + { + // this will cause a round-trip to the main thread, and make sure the + // previous async. calls have completed + ses1.listen_port(); + ses2.listen_port(); + } + + wait_for_alert(ses1, torrent_finished_alert::alert_type, "ses1"); + wait_for_downloading(ses2, "ses2"); + + // connect the peers after setting the certificates + if (test.use_ssl_ports == false) port += 20; + std::printf("\n\n%s: ses1: connecting peer port: %d\n\n\n" + , time_now_string(), port); + tor1.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec) + , std::uint16_t(port))); + + const int timeout = 40; + for (int i = 0; i < timeout; ++i) + { + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + if (i % 10 == 0) + { + std::cout << time_now_string() << " " + << "\033[32m" << int(st1.download_payload_rate / 1000.f) << "kB/s " + << "\033[33m" << int(st1.upload_payload_rate / 1000.f) << "kB/s " + << "\033[0m" << int(st1.progress * 100) << "% " + << st1.num_peers + << ": " + << "\033[32m" << int(st2.download_payload_rate / 1000.f) << "kB/s " + << "\033[31m" << int(st2.upload_payload_rate / 1000.f) << "kB/s " + << "\033[0m" << int(st2.progress * 100) << "% " + << st2.num_peers + << " cc: " << st2.connect_candidates + << std::endl; + } + + if (st2.is_finished) break; + + if (peer_disconnects >= 2) + { + std::printf("too many disconnects (%d), breaking\n", peer_disconnects); + break; + } + + if (st2.state != torrent_status::downloading) + { + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + std::cout << "st2 state: " << state_str[st2.state] << std::endl; + } + + TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_files); + TEST_CHECK(st2.state == torrent_status::downloading + || st2.state == torrent_status::checking_resume_data); + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + std::printf("peer_errors: %d expected_errors: %d\n" + , peer_errors, test.peer_errors); + + std::printf("ssl_disconnects: %d expected: %d\n", ssl_peer_disconnects, test.ssl_disconnects); + if (!use_utp) + { + TEST_EQUAL(ssl_peer_disconnects > 0, test.ssl_disconnects > 0); + TEST_EQUAL(peer_errors > 0, test.peer_errors > 0); + } + + char const* now = time_now_string(); + std::printf("%s: EXPECT: %s\n", now, test.expected_to_complete ? "SUCCEESS" : "FAILURE"); + std::printf("%s: RESULT: %s\n", now, tor2.status().is_seeding ? "SUCCEESS" : "FAILURE"); + TEST_EQUAL(tor2.status().is_seeding, test.expected_to_complete); + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +std::string password_callback(std::size_t /*length*/, boost::asio::ssl::context::password_purpose p + , std::string pw) +{ + if (p != boost::asio::ssl::context::for_reading) return ""; + return pw; +} + +struct attack_t +{ + // flags controlling the connection attempt + std::uint32_t flags; + // whether or not we expect to be able to connect + bool expect; +}; + +enum attack_flags_t +{ + valid_certificate = 1, + invalid_certificate = 2, + valid_sni_hash = 4, + invalid_sni_hash = 8, + valid_bittorrent_hash = 16, +}; + +attack_t const attacks[] = +{ + // positive test + { valid_certificate | valid_sni_hash | valid_bittorrent_hash, true}, + + // SNI + { valid_certificate | invalid_sni_hash | valid_bittorrent_hash, false}, + { valid_certificate | valid_bittorrent_hash, false}, + + // certificate + { valid_sni_hash | valid_bittorrent_hash, false}, + { invalid_certificate | valid_sni_hash | valid_bittorrent_hash, false}, + + // bittorrent hash + { valid_certificate | valid_sni_hash, false}, +}; + +const int num_attacks = sizeof(attacks)/sizeof(attacks[0]); + +bool try_connect(lt::session& ses1, int port + , std::shared_ptr const& t, std::uint32_t flags) +{ + using boost::asio::ssl::context; + + std::printf("\nMALICIOUS PEER TEST: "); + if (flags & invalid_certificate) std::printf("invalid-certificate "); + else if (flags & valid_certificate) std::printf("valid-certificate "); + else std::printf("no-certificate "); + + if (flags & invalid_sni_hash) std::printf("invalid-SNI-hash "); + else if (flags & valid_sni_hash) std::printf("valid-SNI-hash "); + else std::printf("no-SNI-hash "); + + if (flags & valid_bittorrent_hash) std::printf("valid-bittorrent-hash "); + else std::printf("invalid-bittorrent-hash "); + + std::printf(" port: %d\n", port); + + error_code ec; + boost::asio::io_service ios; + + // create the SSL context for this torrent. We need to + // inject the root certificate, and no other, to + // verify other peers against + context ctx(context::sslv23); + + ctx.set_options(context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::single_dh_use); + + // we're a malicious peer, we don't have any interest + // in verifying peers + ctx.set_verify_mode(context::verify_none, ec); + if (ec) + { + std::printf("Failed to set SSL verify mode: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + + std::string certificate = combine_path("..", combine_path("ssl", "peer_certificate.pem")); + std::string private_key = combine_path("..", combine_path("ssl", "peer_private_key.pem")); + std::string dh_params = combine_path("..", combine_path("ssl", "dhparams.pem")); + + if (flags & invalid_certificate) + { + certificate = combine_path("..", combine_path("ssl", "invalid_peer_certificate.pem")); + private_key = combine_path("..", combine_path("ssl", "invalid_peer_private_key.pem")); + } + + // TODO: test using a signed certificate with the wrong info-hash in DN + + if (flags & (valid_certificate | invalid_certificate)) + { + std::printf("set_password_callback\n"); + ctx.set_password_callback(std::bind(&password_callback, _1, _2, "test"), ec); + if (ec) + { + std::printf("Failed to set certificate password callback: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + std::printf("use_certificate_file \"%s\"\n", certificate.c_str()); + ctx.use_certificate_file(certificate, context::pem, ec); + if (ec) + { + std::printf("Failed to set certificate file: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + std::printf("use_private_key_file \"%s\"\n", private_key.c_str()); + ctx.use_private_key_file(private_key, context::pem, ec); + if (ec) + { + std::printf("Failed to set private key: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + std::printf("use_tmp_dh_file \"%s\"\n", dh_params.c_str()); + ctx.use_tmp_dh_file(dh_params, ec); + if (ec) + { + std::printf("Failed to set DH params: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + } + + boost::asio::ssl::stream ssl_sock(ios, ctx); + + std::printf("connecting 127.0.0.1:%d\n", port); + ssl_sock.lowest_layer().connect(tcp::endpoint( + address_v4::from_string("127.0.0.1"), std::uint16_t(port)), ec); + print_alerts(ses1, "ses1", true, true, &on_alert); + + if (ec) + { + std::printf("Failed to connect: %s\n" + , ec.message().c_str()); + TEST_CHECK(!ec); + return false; + } + + if (flags & valid_sni_hash) + { + std::string name = aux::to_hex(t->info_hash()); + std::printf("SNI: %s\n", name.c_str()); + aux::openssl_set_tlsext_hostname(ssl_sock.native_handle(), name.c_str()); + } + else if (flags & invalid_sni_hash) + { + char const hex_alphabet[] = "0123456789abcdef"; + std::string name; + name.reserve(40); + for (int i = 0; i < 40; ++i) + name += hex_alphabet[rand() % 16]; + + std::printf("SNI: %s\n", name.c_str()); + aux::openssl_set_tlsext_hostname(ssl_sock.native_handle(), name.c_str()); + } + + std::printf("SSL handshake\n"); + ssl_sock.handshake(boost::asio::ssl::stream_base::client, ec); + + print_alerts(ses1, "ses1", true, true, &on_alert); + if (ec) + { + std::printf("Failed SSL handshake: %s\n" + , ec.message().c_str()); + return false; + } + + char handshake[] = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + + // fill in the info-hash + if (flags & valid_bittorrent_hash) + { + std::memcpy(handshake + 28, &t->info_hash()[0], 20); + } + else + { + // TODO: also test using a hash that refers to a valid torrent + // but that differs from the SNI hash + std::generate(handshake + 28, handshake + 48, &rand); + } + + // fill in the peer-id + std::generate(handshake + 48, handshake + 68, &rand); + + std::printf("bittorrent handshake\n"); + boost::asio::write(ssl_sock, boost::asio::buffer(handshake, (sizeof(handshake) - 1)), ec); + print_alerts(ses1, "ses1", true, true, &on_alert); + if (ec) + { + std::printf("failed to write bittorrent handshake: %s\n" + , ec.message().c_str()); + return false; + } + + char buf[68]; + std::printf("read bittorrent handshake\n"); + boost::asio::read(ssl_sock, boost::asio::buffer(buf, sizeof(buf)), ec); + print_alerts(ses1, "ses1", true, true, &on_alert); + if (ec) + { + std::printf("failed to read bittorrent handshake: %s\n" + , ec.message().c_str()); + return false; + } + + if (memcmp(buf, "\x13" "BitTorrent protocol", 20) != 0) + { + std::printf("invalid bittorrent handshake\n"); + return false; + } + + if (memcmp(buf + 28, &t->info_hash()[0], 20) != 0) + { + std::printf("invalid info-hash in bittorrent handshake\n"); + return false; + } + + std::printf("successfully connected over SSL and shook hand over bittorrent\n"); + + return true; +} + +void test_malicious_peer() +{ + error_code ec; + remove_all("tmp3_ssl", ec); + + // set up session + int port = 1024 + rand() % 50000; + settings_pack sett = settings(); + sett.set_int(settings_pack::max_retry_port_bind, 100); + + char listen_iface[100]; + std::snprintf(listen_iface, sizeof(listen_iface), "0.0.0.0:%ds", port); + sett.set_str(settings_pack::listen_interfaces, listen_iface); + sett.set_bool(settings_pack::enable_dht, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + + lt::session ses1(sett, {}); + wait_for_listen(ses1, "ses1"); + + // create torrent + create_directory("tmp3_ssl", ec); + std::ofstream file("tmp3_ssl/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary" + , 16 * 1024, 13, false, combine_path("..", combine_path("ssl", "root_ca_cert.pem"))); + file.close(); + + TEST_CHECK(!t->ssl_cert().empty()); + + add_torrent_params addp; + addp.save_path = "tmp3_ssl"; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.ti = t; + + torrent_handle tor1 = ses1.add_torrent(addp, ec); + + tor1.set_ssl_certificate( + combine_path("..", combine_path("ssl", "peer_certificate.pem")) + , combine_path("..", combine_path("ssl", "peer_private_key.pem")) + , combine_path("..", combine_path("ssl", "dhparams.pem")) + , "test"); + + alert const* a = wait_for_alert(ses1 + , torrent_finished_alert::alert_type, "ses1"); + TEST_CHECK(a); + if (a) + { + TEST_EQUAL(a->type(), torrent_finished_alert::alert_type); + } + + for (int i = 0; i < num_attacks; ++i) + { + bool const success = try_connect(ses1, port, t, attacks[i].flags); + TEST_EQUAL(success, attacks[i].expect); + } +} + +} // anonymous namespace + +TORRENT_TEST(malicious_peer) +{ + test_malicious_peer(); +} + +TORRENT_TEST(utp_config0) { test_ssl(0, true); } +TORRENT_TEST(utp_config1) { test_ssl(1, true); } +TORRENT_TEST(utp_config2) { test_ssl(2, true); } +TORRENT_TEST(utp_config3) { test_ssl(3, true); } +TORRENT_TEST(utp_config4) { test_ssl(4, true); } +TORRENT_TEST(utp_config5) { test_ssl(5, true); } +TORRENT_TEST(utp_config6) { test_ssl(6, true); } +TORRENT_TEST(utp_config7) { test_ssl(7, true); } +TORRENT_TEST(utp_config8) { test_ssl(8, true); } + +TORRENT_TEST(tcp_config0) { test_ssl(0, false); } +TORRENT_TEST(tcp_config1) { test_ssl(1, false); } +TORRENT_TEST(tcp_config2) { test_ssl(2, false); } +TORRENT_TEST(tcp_config3) { test_ssl(3, false); } +TORRENT_TEST(tcp_config4) { test_ssl(4, false); } +TORRENT_TEST(tcp_config5) { test_ssl(5, false); } +TORRENT_TEST(tcp_config6) { test_ssl(6, false); } +TORRENT_TEST(tcp_config7) { test_ssl(7, false); } +TORRENT_TEST(tcp_config8) { test_ssl(8, false); } +#else +TORRENT_TEST(disabled) {} +#endif // TORRENT_USE_OPENSSL + diff --git a/test/test_stack_allocator.cpp b/test/test_stack_allocator.cpp new file mode 100644 index 0000000..f0a474f --- /dev/null +++ b/test/test_stack_allocator.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/stack_allocator.hpp" +#include "libtorrent/string_view.hpp" +#include // for va_list, va_start, va_end + +using lt::aux::stack_allocator; +using lt::aux::allocation_slot; +using namespace lt::literals; + +TORRENT_TEST(copy_string) +{ + stack_allocator a; + allocation_slot const idx1 = a.copy_string("testing"); + + // attempt to trigger a reallocation + a.allocate(100000); + + allocation_slot const idx2 = a.copy_string(std::string("foobar")); + + TEST_CHECK(a.ptr(idx1) == "testing"_sv); + TEST_CHECK(a.ptr(idx2) == "foobar"_sv); +} + +TORRENT_TEST(copy_buffer) +{ + stack_allocator a; + allocation_slot const idx1 = a.copy_buffer(lt::span("testing")); + + // attempt to trigger a reallocation + a.allocate(100000); + + TEST_CHECK(a.ptr(idx1) == "testing"_sv); + + // attempt zero size allocation + allocation_slot const idx2 = a.copy_buffer({}); + + // attempt to get a pointer after zero allocation + char* ptr = a.ptr(idx2); + TEST_CHECK(ptr == nullptr); +} + +TORRENT_TEST(allocate) +{ + stack_allocator a; + allocation_slot const idx1 = a.allocate(100); + char* ptr = a.ptr(idx1); + for (int i = 0; i < 100; ++i) + ptr[i] = char(i % 256); + + // attempt to trigger a reallocation + a.allocate(100000); + + ptr = a.ptr(idx1); + for (int i = 0; i < 100; ++i) + TEST_CHECK(ptr[i] == char(i % 256)); + + // attempt zero size allocation + allocation_slot const idx2 = a.allocate(0); + + // attempt to get a pointer after zero allocation + ptr = a.ptr(idx2); + TEST_CHECK(ptr == nullptr); +} + +TORRENT_TEST(swap) +{ + stack_allocator a1; + stack_allocator a2; + + allocation_slot const idx1 = a1.copy_string("testing"); + allocation_slot const idx2 = a2.copy_string("foobar"); + + a1.swap(a2); + + TEST_CHECK(a1.ptr(idx2) == "foobar"_sv); + TEST_CHECK(a2.ptr(idx1) == "testing"_sv); +} + +namespace { + +TORRENT_FORMAT(2,3) +allocation_slot format_string_helper(stack_allocator& stack, char const* fmt, ...) +{ + va_list v; + va_start(v, fmt); + auto const ret = stack.format_string(fmt, v); + va_end(v); + return ret; +} + +} + +TORRENT_TEST(format_string_long) +{ + stack_allocator a; + std::string long_string; + for (int i = 0; i < 1024; ++i) long_string += "foobar-"; + auto const idx = format_string_helper(a, "%s", long_string.c_str()); + + TEST_EQUAL(a.ptr(idx), long_string); +} + +TORRENT_TEST(format_string) +{ + stack_allocator a; + auto const idx = format_string_helper(a, "%d", 10); + + TEST_EQUAL(a.ptr(idx), "10"_sv); +} diff --git a/test/test_stat_cache.cpp b/test/test_stat_cache.cpp new file mode 100644 index 0000000..2b6d4f6 --- /dev/null +++ b/test/test_stat_cache.cpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/error_code.hpp" +#include "test.hpp" + +using namespace lt; + +TORRENT_TEST(stat_cache) +{ + error_code ec; + + stat_cache sc; + + file_storage fs; + for (int i = 0; i < 20; ++i) + { + char buf[50]; + std::snprintf(buf, sizeof(buf), "test_torrent/test-%d", i); + fs.add_file(buf, (i + 1) * 10); + } + + std::string save_path = "."; + + sc.reserve(10); + + sc.set_error(file_index_t(3), error_code(boost::system::errc::permission_denied, generic_category())); + ec.clear(); + TEST_EQUAL(sc.get_filesize(file_index_t(3), fs, save_path, ec), stat_cache::file_error); + TEST_EQUAL(ec, error_code(boost::system::errc::permission_denied, generic_category())); + + sc.set_error(file_index_t(3), error_code(boost::system::errc::no_such_file_or_directory, generic_category())); + ec.clear(); + TEST_EQUAL(sc.get_filesize(file_index_t(3), fs, save_path, ec), stat_cache::file_error); + TEST_EQUAL(ec, error_code(boost::system::errc::no_such_file_or_directory, generic_category())); + + ec.clear(); + sc.set_cache(file_index_t(3), 101); + TEST_EQUAL(sc.get_filesize(file_index_t(3), fs, save_path, ec), 101); + TEST_CHECK(!ec); + + sc.set_error(file_index_t(11), error_code(boost::system::errc::broken_pipe, generic_category())); + ec.clear(); + TEST_EQUAL(sc.get_filesize(file_index_t(11), fs, save_path, ec), stat_cache::file_error); + TEST_EQUAL(ec, error_code(boost::system::errc::broken_pipe, generic_category())); + + ec.clear(); + sc.set_error(file_index_t(13), error_code(boost::system::errc::no_such_file_or_directory, generic_category())); + TEST_EQUAL(sc.get_filesize(file_index_t(13), fs, save_path, ec), stat_cache::file_error); + TEST_EQUAL(ec, error_code(boost::system::errc::no_such_file_or_directory, generic_category())); + + ec.clear(); + sc.set_cache(file_index_t(15), 1000); + TEST_CHECK(sc.get_filesize(file_index_t(15), fs, save_path, ec) == 1000); + TEST_CHECK(!ec); +} + diff --git a/test/test_storage.cpp b/test/test_storage.cpp new file mode 100644 index 0000000..aab25f6 --- /dev/null +++ b/test/test_storage.cpp @@ -0,0 +1,1541 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "test_utils.hpp" +#include "settings.hpp" + +#include "libtorrent/storage.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/read_resume_data.hpp" +#include "libtorrent/write_resume_data.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/aux_/storage_utils.hpp" +#include "libtorrent/random.hpp" + +#include +#include // for bind + +#include +#include + +#include + +using namespace std::placeholders; +using namespace lt; + +namespace { + +constexpr int piece_size = 16 * 1024 * 16; +constexpr int half = piece_size / 2; + +void on_check_resume_data(status_t const status, storage_error const& error, bool* done) +{ + std::cout << time_now_string() << " on_check_resume_data ret: " + << static_cast(status); + switch (status) + { + case status_t::no_error: + std::cout << time_now_string() << " success" << std::endl; + break; + case status_t::fatal_disk_error: + std::cout << time_now_string() << " disk error: " << error.ec.message() + << " file: " << error.file() << std::endl; + break; + case status_t::need_full_check: + std::cout << time_now_string() << " need full check" << std::endl; + break; + case status_t::file_exist: + std::cout << time_now_string() << " file exist" << std::endl; + break; + } + std::cout << std::endl; + *done = true; +} + +void on_piece_checked(piece_index_t, sha1_hash const& + , storage_error const& error, bool* done) +{ + std::cout << time_now_string() << " on_piece_checked err: " + << error.ec.message() << '\n'; + *done = true; +} + +void print_error(char const* call, int ret, storage_error const& ec) +{ + std::printf("%s: %s() returned: %d error: \"%s\" in file: %d operation: %s\n" + , time_now_string(), call, ret, ec.ec.message().c_str() + , static_cast(ec.file()), operation_name(ec.operation)); +} + +void run_until(io_service& ios, bool const& done) +{ + while (!done) + { + ios.reset(); + error_code ec; + ios.run_one(ec); + if (ec) + { + std::cout << "run_one: " << ec.message().c_str() << std::endl; + return; + } + std::cout << time_now_string() << " done: " << done << std::endl; + } +} + +void nop() {} + +std::shared_ptr setup_torrent_info(file_storage& fs + , std::vector& buf) +{ + fs.add_file(combine_path("temp_storage", "test1.tmp"), 8); + fs.add_file(combine_path("temp_storage", combine_path("folder1", "test2.tmp")), 8); + fs.add_file(combine_path("temp_storage", combine_path("folder2", "test3.tmp")), 0); + fs.add_file(combine_path("temp_storage", combine_path("_folder3", "test4.tmp")), 0); + fs.add_file(combine_path("temp_storage", combine_path("_folder3", combine_path("subfolder", "test5.tmp"))), 8); + lt::create_torrent t(fs, 4, -1, {}); + + char buf_[4] = {0, 0, 0, 0}; + sha1_hash h = hasher(buf_).final(); + for (piece_index_t i(0); i < piece_index_t(6); ++i) t.set_hash(i, h); + + bencode(std::back_inserter(buf), t.generate()); + error_code ec; + + auto info = std::make_shared(buf, ec, from_span); + + if (ec) + { + std::printf("torrent_info constructor failed: %s\n" + , ec.message().c_str()); + } + + return info; +} + +std::shared_ptr setup_torrent(file_storage& fs + , file_pool& fp + , std::vector& buf + , std::string const& test_path + , aux::session_settings& set) +{ + std::shared_ptr info = setup_torrent_info(fs, buf); + + aux::vector priorities; + sha1_hash info_hash; + storage_params p{ + fs, + nullptr, + test_path, + storage_mode_allocate, + priorities, + info_hash + }; + std::shared_ptr s(new default_storage(p, fp)); + s->m_settings = &set; + + // allocate the files and create the directories + storage_error se; + s->initialize(se); + if (se) + { + TEST_ERROR(se.ec.message().c_str()); + std::printf("default_storage::initialize %s: %d\n" + , se.ec.message().c_str(), static_cast(se.file())); + } + + return s; +} + +std::vector new_piece(std::size_t const size) +{ + std::vector ret(size); + aux::random_bytes(ret); + return ret; +} + +void run_storage_tests(std::shared_ptr info + , file_storage& fs + , std::string const& test_path + , lt::storage_mode_t storage_mode + , bool unbuffered) +{ + TORRENT_ASSERT(fs.num_files() > 0); + { + error_code ec; + create_directory(combine_path(test_path, "temp_storage"), ec); + if (ec) std::cout << "create_directory '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + remove_all(combine_path(test_path, "temp_storage2"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage2") + << "': " << ec.message() << std::endl; + remove_all(combine_path(test_path, "part0"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "part0") + << "': " << ec.message() << std::endl; + } + int num_pieces = fs.num_pieces(); + TEST_CHECK(info->num_pieces() == num_pieces); + + std::vector piece0 = new_piece(piece_size); + std::vector piece1 = new_piece(piece_size); + std::vector piece2 = new_piece(piece_size); + + aux::session_settings set; + set.set_int(settings_pack::disk_io_write_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + set.set_int(settings_pack::disk_io_read_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + + char* piece = static_cast(malloc(piece_size)); + + { // avoid having two storages use the same files + file_pool fp; + boost::asio::io_service ios; + disk_buffer_pool dp(ios, std::bind(&nop)); + + aux::vector priorities; + sha1_hash info_hash; + storage_params p{ + fs, + nullptr, + test_path, + storage_mode, + priorities, + info_hash + }; + std::unique_ptr s(new default_storage(p, fp)); + s->m_settings = &set; + + storage_error ec; + s->initialize(ec); + TEST_CHECK(!ec); + if (ec) print_error("initialize", 0, ec); + + int ret = 0; + + // write piece 1 (in slot 0) + iovec_t iov = { piece1.data(), half}; + ret = s->writev(iov, piece_index_t(0), 0, open_mode::read_write, ec); + if (ret != half) print_error("writev", ret, ec); + + iov = { piece1.data() + half, half }; + ret = s->writev(iov, piece_index_t(0), half, open_mode::read_write, ec); + if (ret != half) print_error("writev", ret, ec); + + // test unaligned read (where the bytes are aligned) + iov = { piece + 3, piece_size - 9}; + ret = s->readv(iov, piece_index_t(0), 3, open_mode::read_write, ec); + if (ret != piece_size - 9) print_error("readv",ret, ec); + TEST_CHECK(std::equal(piece+3, piece + piece_size-9, piece1.data()+3)); + + // test unaligned read (where the bytes are not aligned) + iov = { piece, piece_size - 9}; + ret = s->readv(iov, piece_index_t(0), 3, open_mode::read_write, ec); + TEST_CHECK(ret == piece_size - 9); + if (ret != piece_size - 9) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size-9, piece1.data()+3)); + + // verify piece 1 + iov = { piece, piece_size }; + ret = s->readv(iov, piece_index_t(0), 0, open_mode::read_write, ec); + TEST_CHECK(ret == piece_size); + if (ret != piece_size) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size, piece1.data())); + + // do the same with piece 0 and 2 (in slot 1 and 2) + iov = { piece0.data(), piece_size }; + ret = s->writev(iov, piece_index_t(1), 0, open_mode::read_write, ec); + if (ret != piece_size) print_error("writev", ret, ec); + + iov = { piece2.data(), piece_size }; + ret = s->writev(iov, piece_index_t(2), 0, open_mode::read_write, ec); + if (ret != piece_size) print_error("writev", ret, ec); + + // verify piece 0 and 2 + iov = { piece, piece_size }; + ret = s->readv(iov, piece_index_t(1), 0, open_mode::read_write, ec); + if (ret != piece_size) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size, piece0.data())); + + iov = { piece, piece_size }; + ret = s->readv(iov, piece_index_t(2), 0, open_mode::read_write, ec); + if (ret != piece_size) print_error("readv", ret, ec); + TEST_CHECK(std::equal(piece, piece + piece_size, piece2.data())); + + s->release_files(ec); + } + + free(piece); +} + +void test_remove(std::string const& test_path, bool unbuffered) +{ + error_code ec; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); + + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(ios, std::bind(&nop)); + + aux::session_settings set; + set.set_int(settings_pack::disk_io_write_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + set.set_int(settings_pack::disk_io_read_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + + std::shared_ptr s = setup_torrent(fs, fp, buf, test_path, set); + + // directories are not created up-front, unless they contain + // an empty file (all of which are created up-front, along with + // all required directories) + // files are created on first write + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + + // this directory and file is created up-front because it's an empty file + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder2", "test3.tmp"))))); + + // this isn't + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + + iovec_t b = {&buf[0], 4}; + storage_error se; + s->writev(b, piece_index_t(2), 0, open_mode::read_write, se); + + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + file_status st; + stat_file(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))), &st, ec); + TEST_EQUAL(st.file_size, 8); + + s->writev(b, piece_index_t(4), 0, open_mode::read_write, se); + + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + stat_file(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "test5.tmp"))), &st, ec); + TEST_EQUAL(st.file_size, 8); + + s->delete_files(session::delete_files, se); + if (se) print_error("delete_files", 0, se); + + if (se) + { + TEST_ERROR(se.ec.message().c_str()); + std::printf("default_storage::delete_files %s: %d\n" + , se.ec.message().c_str(), static_cast(se.file())); + } + + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); +} + +void test_rename(std::string const& test_path) +{ + error_code ec; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); + + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(ios, std::bind(&nop)); + aux::session_settings set; + + std::shared_ptr s = setup_torrent(fs, fp, buf, test_path + , set); + + // directories are not created up-front, unless they contain + // an empty file + std::string first_file = fs.file_path(file_index_t(0)); + for (auto const i : fs.file_range()) + { + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , fs.file_path(i))))); + } + + storage_error se; + s->rename_file(file_index_t(0), "new_filename", se); + if (se.ec) + { + std::printf("default_storage::rename_file failed: %s\n" + , se.ec.message().c_str()); + } + TEST_CHECK(!se.ec); + + TEST_EQUAL(s->files().file_path(file_index_t(0)), "new_filename"); +} + +struct test_error_storage : default_storage +{ + explicit test_error_storage(storage_params const& params, file_pool& fp) + : default_storage(params, fp) {} + + bool has_any_file(storage_error& ec) override + { + ec.ec = make_error_code(boost::system::errc::permission_denied); + ec.operation = operation_t::file_stat; + return false; + } +}; + +storage_interface* error_storage_constructor( + storage_params const& params, file_pool& fp) +{ + return new test_error_storage(params, fp); +} + +void test_check_files(std::string const& test_path + , lt::storage_mode_t storage_mode) +{ + std::shared_ptr info; + + error_code ec; + constexpr int piece_size_check = 16 * 1024; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + file_storage fs; + fs.add_file("temp_storage/test1.tmp", piece_size_check); + fs.add_file("temp_storage/test2.tmp", piece_size_check * 2); + fs.add_file("temp_storage/test3.tmp", piece_size_check); + + std::vector piece0 = new_piece(piece_size_check); + std::vector piece2 = new_piece(piece_size_check); + + lt::create_torrent t(fs, piece_size_check, -1, {}); + t.set_hash(piece_index_t(0), hasher(piece0).final()); + t.set_hash(piece_index_t(1), {}); + t.set_hash(piece_index_t(2), {}); + t.set_hash(piece_index_t(3), hasher(piece2).final()); + + create_directory(combine_path(test_path, "temp_storage"), ec); + if (ec) std::cout << "create_directory: " << ec.message() << std::endl; + + std::ofstream f; + f.open(combine_path(test_path, combine_path("temp_storage", "test1.tmp")).c_str() + , std::ios::trunc | std::ios::binary); + f.write(piece0.data(), piece_size_check); + f.close(); + f.open(combine_path(test_path, combine_path("temp_storage", "test3.tmp")).c_str() + , std::ios::trunc | std::ios::binary); + f.write(piece2.data(), piece_size_check); + f.close(); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = std::make_shared(buf, ec, from_span); + + file_pool fp; + boost::asio::io_service ios; + counters cnt; + + aux::session_settings sett; + sett.set_int(settings_pack::aio_threads, 1); + disk_io_thread io(ios, sett, cnt); + + disk_buffer_pool dp(ios, std::bind(&nop)); + + aux::vector priorities( + std::size_t(info->num_files()), download_priority_t{}); + sha1_hash info_hash; + storage_params p{ + fs, + nullptr, + test_path, + storage_mode, + priorities, + info_hash + }; + + auto st = io.new_torrent(error_storage_constructor, std::move(p) + , std::shared_ptr()); + + bool done = false; + add_torrent_params frd; + aux::vector links; + io.async_check_files(st, &frd, links + , std::bind(&on_check_resume_data, _1, _2, &done)); + io.submit_jobs(); + ios.reset(); + run_until(ios, done); + + for (auto const i : info->piece_range()) + { + done = false; + io.async_hash(st, i, disk_interface::sequential_access | disk_interface::volatile_read + , std::bind(&on_piece_checked, _1, _2, _3, &done)); + io.submit_jobs(); + ios.reset(); + run_until(ios, done); + } + + io.abort(true); +} + +// TODO: 2 split this test up into smaller parts +void run_test(bool unbuffered) +{ + std::string test_path = current_working_directory(); + std::cout << "\n=== " << test_path << " ===\n" << std::endl; + + std::shared_ptr info; + + std::vector piece0 = new_piece(piece_size); + std::vector piece1 = new_piece(piece_size); + std::vector piece2 = new_piece(piece_size); + std::vector piece3 = new_piece(piece_size); + + { + error_code ec; + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + file_storage fs; + fs.add_file("temp_storage/test1.tmp", 17); + fs.add_file("temp_storage/test2.tmp", 612); + fs.add_file("temp_storage/test3.tmp", 0); + fs.add_file("temp_storage/test4.tmp", 0); + fs.add_file("temp_storage/test5.tmp", 3253); + fs.add_file("temp_storage/test6.tmp", 841); + int const last_file_size = 4 * int(piece_size) - int(fs.total_size()); + fs.add_file("temp_storage/test7.tmp", last_file_size); + + // File layout + // +-+--+++-------+-------+----------------------------------------------------------------------------------------+ + // |1| 2||| file5 | file6 | file7 | + // +-+--+++-------+-------+----------------------------------------------------------------------------------------+ + // | | | | | + // | piece 0 | piece 1 | piece 2 | piece 3 | + + lt::create_torrent t(fs, piece_size, -1, {}); + TEST_CHECK(t.num_pieces() == 4); + t.set_hash(piece_index_t(0), hasher(piece0).final()); + t.set_hash(piece_index_t(1), hasher(piece1).final()); + t.set_hash(piece_index_t(2), hasher(piece2).final()); + t.set_hash(piece_index_t(3), hasher(piece3).final()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = std::make_shared(buf, from_span); + std::cout << "=== test 1 === " << (unbuffered?"unbuffered":"buffered") << std::endl; + + // run_storage_tests writes piece 0, 1 and 2. not 3 + run_storage_tests(info, fs, test_path, storage_mode_sparse, unbuffered); + + // make sure the files have the correct size + std::string base = combine_path(test_path, "temp_storage"); + std::printf("base = \"%s\"\n", base.c_str()); + TEST_EQUAL(file_size(combine_path(base, "test1.tmp")), 17); + TEST_EQUAL(file_size(combine_path(base, "test2.tmp")), 612); + + // these files should have been allocated as 0 size + TEST_CHECK(exists(combine_path(base, "test3.tmp"))); + TEST_CHECK(exists(combine_path(base, "test4.tmp"))); + TEST_CHECK(file_size(combine_path(base, "test3.tmp")) == 0); + TEST_CHECK(file_size(combine_path(base, "test4.tmp")) == 0); + + TEST_EQUAL(file_size(combine_path(base, "test5.tmp")), 3253); + TEST_EQUAL(file_size(combine_path(base, "test6.tmp")), 841); + std::printf("file: %d expected: %d last_file_size: %d, piece_size: %d\n" + , int(file_size(combine_path(base, "test7.tmp"))) + , last_file_size - int(piece_size), last_file_size, int(piece_size)); + TEST_EQUAL(file_size(combine_path(base, "test7.tmp")), last_file_size - int(piece_size)); + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + } + +// ============================================== + + { + error_code ec; + file_storage fs; + fs.add_file(combine_path("temp_storage", "test1.tmp"), 3 * piece_size); + lt::create_torrent t(fs, piece_size, -1, {}); + TEST_CHECK(fs.file_path(file_index_t(0)) == combine_path("temp_storage", "test1.tmp")); + t.set_hash(piece_index_t(0), hasher(piece0).final()); + t.set_hash(piece_index_t(1), hasher(piece1).final()); + t.set_hash(piece_index_t(2), hasher(piece2).final()); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + info = std::make_shared(buf, ec, from_span); + + std::cout << "=== test 3 ===" << std::endl; + + run_storage_tests(info, fs, test_path, storage_mode_sparse, unbuffered); + + TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), piece_size * 3); + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + +// ============================================== + + std::cout << "=== test 4 ===" << std::endl; + + run_storage_tests(info, fs, test_path, storage_mode_allocate, unbuffered); + + std::cout << file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))) << std::endl; + TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), 3 * piece_size); + + remove_all(combine_path(test_path, "temp_storage"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "temp_storage") + << "': " << ec.message() << std::endl; + + } + +// ============================================== + + std::cout << "=== test 5 ===" << std::endl; + test_remove(test_path, unbuffered); + +// ============================================== + + std::cout << "=== test 6 ===" << std::endl; + test_check_files(test_path, storage_mode_sparse); + + std::cout << "=== test 7 ===" << std::endl; + test_rename(test_path); +} + +void test_fastresume(bool const test_deprecated) +{ + std::string test_path = current_working_directory(); + error_code ec; + std::cout << "\n\n=== test fastresume ===" << std::endl; + remove_all(combine_path(test_path, "tmp1"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; + create_directory(combine_path(test_path, "tmp1"), ec); + if (ec) std::cout << "create_directory '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; + std::ofstream file(combine_path(test_path, "tmp1/temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file); + file.close(); + TEST_CHECK(exists(combine_path(test_path, "tmp1/temporary"))); + if (!exists(combine_path(test_path, "tmp1/temporary"))) + return; + + entry resume; + { + settings_pack pack = settings(); + lt::session ses(pack); + + add_torrent_params p; + p.ti = std::make_shared(std::cref(*t)); + p.save_path = combine_path(test_path, "tmp1"); + p.storage_mode = storage_mode_sparse; + error_code ignore; + torrent_handle h = ses.add_torrent(std::move(p), ignore); + TEST_CHECK(exists(combine_path(p.save_path, "temporary"))); + if (!exists(combine_path(p.save_path, "temporary"))) + return; + + torrent_status s; + for (int i = 0; i < 50; ++i) + { + print_alerts(ses, "ses"); + s = h.status(); + if (s.progress == 1.0f) + { + std::cout << "progress: 1.0f" << std::endl; + break; + } + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + // the whole point of the test is to have a resume + // data which expects the file to exist in full. If + // we failed to do that, we might as well abort + TEST_EQUAL(s.progress, 1.0f); + if (s.progress != 1.0f) + return; + + h.save_resume_data(); + alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type); + TEST_CHECK(ra); + if (ra) resume = write_resume_data(alert_cast(ra)->params); + ses.remove_torrent(h, lt::session::delete_files); + alert const* da = wait_for_alert(ses, torrent_deleted_alert::alert_type); + TEST_CHECK(da); + } + TEST_CHECK(!exists(combine_path(test_path, combine_path("tmp1", "temporary")))); + if (exists(combine_path(test_path, combine_path("tmp1", "temporary")))) + return; + + std::cout << resume.to_string() << "\n"; + + // make sure the fast resume check fails! since we removed the file + { + settings_pack pack = settings(); + lt::session ses(pack); + + std::vector resume_data; + bencode(std::back_inserter(resume_data), resume); + + add_torrent_params p; + TORRENT_UNUSED(test_deprecated); +#if TORRENT_ABI_VERSION == 1 + if (test_deprecated) + { + p.resume_data = resume_data; + } + else +#endif + { + p = read_resume_data(resume_data); + } + + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.ti = std::make_shared(std::cref(*t)); + p.save_path = combine_path(test_path, "tmp1"); + p.storage_mode = storage_mode_sparse; + torrent_handle h = ses.add_torrent(std::move(p), ec); + + std::printf("expecting fastresume to be rejected becase the files were removed"); + alert const* a = wait_for_alert(ses, fastresume_rejected_alert::alert_type + , "ses"); + // we expect the fast resume to be rejected because the files were removed + TEST_CHECK(alert_cast(a) != nullptr); + } + remove_all(combine_path(test_path, "tmp1"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "tmp1") + << "': " << ec.message() << std::endl; +} + +} // anonymous namespace + +TORRENT_TEST(fastresume) +{ + test_fastresume(false); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(fastresume_deprecated) +{ + test_fastresume(true); +} +#endif + +namespace { + +bool got_file_rename_alert(alert const* a) +{ + return alert_cast(a) + || alert_cast(a); +} + +} // anonymous namespace + +TORRENT_TEST(rename_file) +{ + std::vector buf; + file_storage fs; + std::shared_ptr info = setup_torrent_info(fs, buf); + + settings_pack pack = settings(); + pack.set_bool(settings_pack::disable_hash_checks, true); + lt::session ses(pack); + + add_torrent_params p; + p.ti = info; + p.save_path = "."; + error_code ec; + torrent_handle h = ses.add_torrent(std::move(p), ec); + + // prevent race conditions of adding pieces while checking + lt::torrent_status st = h.status(); + for (int i = 0; i < 40; ++i) + { + print_alerts(ses, "ses", true, true); + st = h.status(); + if (st.state != torrent_status::checking_files + && st.state != torrent_status::checking_resume_data) + break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + // make it a seed + std::vector tmp(std::size_t(info->piece_length())); + for (auto const i : fs.piece_range()) + h.add_piece(i, &tmp[0]); + + // wait for the files to have been written + + for (int i = 0; i < info->num_pieces(); ++i) + { + alert const* pf = wait_for_alert(ses, piece_finished_alert::alert_type + , "ses", pop_alerts::cache_alerts); + TEST_CHECK(pf); + } + + // now rename them. This is the test + for (auto const i : fs.file_range()) + { + std::string name = fs.file_path(i); + h.rename_file(i, "temp_storage__" + name.substr(12)); + } + + // wait for the files to have been renamed + for (int i = 0; i < info->num_files(); ++i) + { + alert const* fra = wait_for_alert(ses, file_renamed_alert::alert_type + , "ses", pop_alerts::cache_alerts); + TEST_CHECK(fra); + } + + TEST_CHECK(exists(info->name() + "__")); + + h.save_resume_data(); + alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type); + TEST_CHECK(ra); + if (!ra) return; + add_torrent_params resume = alert_cast(ra)->params; + + auto const files = resume.renamed_files; + for (auto const& i : files) + { + TEST_EQUAL(i.second.substr(0, 14), "temp_storage__"); + } +} + +namespace { + +void test_rename_file_fastresume(bool test_deprecated) +{ + std::string test_path = current_working_directory(); + error_code ec; + std::cout << "\n\n=== test rename file in fastresume ===" << std::endl; + remove_all(combine_path(test_path, "tmp2"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "tmp2") + << "': " << ec.message() << std::endl; + create_directory(combine_path(test_path, "tmp2"), ec); + if (ec) std::cout << "create_directory: " << ec.message() << std::endl; + std::ofstream file(combine_path(test_path, "tmp2/temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file); + file.close(); + TEST_CHECK(exists(combine_path(test_path, "tmp2/temporary"))); + + add_torrent_params resume; + { + settings_pack pack = settings(); + lt::session ses(pack); + + add_torrent_params p; + p.ti = std::make_shared(std::cref(*t)); + p.save_path = combine_path(test_path, "tmp2"); + p.storage_mode = storage_mode_sparse; + torrent_handle h = ses.add_torrent(std::move(p), ec); + + h.rename_file(file_index_t(0), "testing_renamed_files"); + std::cout << "renaming file" << std::endl; + bool renamed = false; + for (int i = 0; i < 30; ++i) + { + if (print_alerts(ses, "ses", true, true, &got_file_rename_alert)) renamed = true; + torrent_status s = h.status(); + if (s.state == torrent_status::seeding && renamed) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + std::cout << "stop loop" << std::endl; + torrent_status s = h.status(); + TEST_CHECK(s.state == torrent_status::seeding); + + h.save_resume_data(); + alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type); + TEST_CHECK(ra); + if (ra) resume = alert_cast(ra)->params; + ses.remove_torrent(h); + } + TEST_CHECK(!exists(combine_path(test_path, "tmp2/temporary"))); + TEST_CHECK(exists(combine_path(test_path, "tmp2/testing_renamed_files"))); + TEST_CHECK(!resume.renamed_files.empty()); + + entry resume_ent = write_resume_data(resume); + + std::cout << resume_ent.to_string() << "\n"; + + // make sure the fast resume check succeeds, even though we renamed the file + { + settings_pack pack = settings(); + lt::session ses(pack); + + add_torrent_params p; + std::vector resume_data; + bencode(std::back_inserter(resume_data), resume_ent); + TORRENT_UNUSED(test_deprecated); +#if TORRENT_ABI_VERSION == 1 + if (test_deprecated) + { + p.resume_data = resume_data; + } + else +#endif + { + p = read_resume_data(resume_data); + } + p.ti = std::make_shared(std::cref(*t)); + p.save_path = combine_path(test_path, "tmp2"); + p.storage_mode = storage_mode_sparse; + torrent_handle h = ses.add_torrent(std::move(p), ec); + + torrent_status stat; + for (int i = 0; i < 50; ++i) + { + stat = h.status(); + print_alerts(ses, "ses"); + if (stat.state == torrent_status::seeding) + break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + TEST_CHECK(stat.state == torrent_status::seeding); + + h.save_resume_data(); + alert const* ra = wait_for_alert(ses, save_resume_data_alert::alert_type); + TEST_CHECK(ra); + if (ra) resume = alert_cast(ra)->params; + ses.remove_torrent(h); + } + TEST_CHECK(!resume.renamed_files.empty()); + + resume_ent = write_resume_data(resume); + std::cout << resume_ent.to_string() << "\n"; + + remove_all(combine_path(test_path, "tmp2"), ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cout << "remove_all '" << combine_path(test_path, "tmp2") + << "': " << ec.message() << std::endl; +} + +} // anonymous namespace + +TORRENT_TEST(rename_file_fastresume) +{ + test_rename_file_fastresume(false); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(rename_file_fastresume_deprecated) +{ + test_rename_file_fastresume(true); +} +#endif + +namespace { + +void alloc_iov(iovec_t* iov, int num_bufs) +{ + for (int i = 0; i < num_bufs; ++i) + { + iov[i] = { new char[static_cast(num_bufs * (i + 1))] + , num_bufs * (i + 1) }; + } +} + +// TODO: this should take a span of iovec_ts +void fill_pattern(iovec_t* iov, int num_bufs) +{ + int counter = 0; + for (int i = 0; i < num_bufs; ++i) + { + for (char& v : iov[i]) + { + v = char(counter & 0xff); + ++counter; + } + } +} + +bool check_pattern(std::vector const& buf, int counter) +{ + unsigned char const* p = reinterpret_cast(buf.data()); + for (int k = 0; k < int(buf.size()); ++k) + { + if (p[k] != (counter & 0xff)) return false; + ++counter; + } + return true; +} + +// TODO: this should take a span +void free_iov(iovec_t* iov, int num_bufs) +{ + for (int i = 0; i < num_bufs; ++i) + { + delete[] iov[i].data(); + iov[i] = { nullptr, 0 }; + } +} + +} // anonymous namespace + +TORRENT_TEST(iovec_copy_bufs) +{ + iovec_t iov1[10]; + iovec_t iov2[10]; + + alloc_iov(iov1, 10); + fill_pattern(iov1, 10); + + TEST_CHECK(bufs_size({iov1, 10}) >= 106); + + // copy exactly 106 bytes from iov1 to iov2 + int num_bufs = aux::copy_bufs(iov1, 106, iov2); + + // verify that the first 100 bytes is pattern 1 + // and that the remaining bytes are pattern 2 + + int counter = 0; + for (int i = 0; i < num_bufs; ++i) + { + for (char v : iov2[i]) + { + TEST_EQUAL(int(v), (counter & 0xff)); + ++counter; + } + } + TEST_EQUAL(counter, 106); + + free_iov(iov1, 10); +} + +TORRENT_TEST(iovec_clear_bufs) +{ + iovec_t iov[10]; + alloc_iov(iov, 10); + fill_pattern(iov, 10); + + lt::aux::clear_bufs({iov, 10}); + for (int i = 0; i < 10; ++i) + { + for (char v : iov[i]) + { + TEST_EQUAL(int(v), 0); + } + } + free_iov(iov, 10); +} + +TORRENT_TEST(iovec_bufs_size) +{ + iovec_t iov[10]; + + for (int i = 1; i < 10; ++i) + { + alloc_iov(iov, i); + + int expected_size = 0; + for (int k = 0; k < i; ++k) expected_size += i * (k + 1); + TEST_EQUAL(bufs_size({iov, i}), expected_size); + + free_iov(iov, i); + } +} + +TORRENT_TEST(iovec_advance_bufs) +{ + iovec_t iov1[10]; + iovec_t iov2[10]; + alloc_iov(iov1, 10); + fill_pattern(iov1, 10); + + memcpy(iov2, iov1, sizeof(iov1)); + + span iov = iov2; + + // advance iov 13 bytes. Make sure what's left fits pattern 1 shifted + // 13 bytes + iov = aux::advance_bufs(iov, 13); + + // make sure what's in + int counter = 13; + for (auto buf : iov) + { + for (char v : buf) + { + TEST_EQUAL(v, static_cast(counter)); + ++counter; + } + } + + free_iov(iov1, 10); +} + +TORRENT_TEST(unbuffered) { run_test(true); } +TORRENT_TEST(buffered) { run_test(false); } + +namespace { + +file_storage make_fs() +{ + file_storage fs; + fs.add_file(combine_path("readwritev", "1"), 3); + fs.add_file(combine_path("readwritev", "2"), 9); + fs.add_file(combine_path("readwritev", "3"), 81); + fs.add_file(combine_path("readwritev", "4"), 6561); + fs.set_piece_length(0x1000); + fs.set_num_pieces(int((fs.total_size() + 0xfff) / 0x1000)); + return fs; +} + +struct test_fileop +{ + explicit test_fileop(int stripe_size) : m_stripe_size(stripe_size) {} + + int operator()(file_index_t const file_index, std::int64_t const file_offset + , span bufs, storage_error&) + { + std::size_t offset = size_t(file_offset); + if (file_index >= m_file_data.end_index()) + { + m_file_data.resize(static_cast(file_index) + 1); + } + + std::size_t const write_size = std::size_t(std::min(m_stripe_size, bufs_size(bufs))); + + std::vector& file = m_file_data[file_index]; + + if (offset + write_size > file.size()) + { + file.resize(offset + write_size); + } + + int left = int(write_size); + while (left > 0) + { + std::size_t const copy_size = std::size_t(std::min(left, int(bufs.front().size()))); + std::memcpy(&file[offset], bufs.front().data(), copy_size); + bufs = bufs.subspan(1); + offset += copy_size; + left -= int(copy_size); + } + return int(write_size); + } + + int m_stripe_size; + aux::vector, file_index_t> m_file_data; +}; + +struct test_read_fileop +{ + // EOF after size bytes read + explicit test_read_fileop(int size) : m_size(size), m_counter(0) {} + + int operator()(file_index_t, std::int64_t /*file_offset*/ + , span bufs, storage_error&) + { + int local_size = std::min(m_size, bufs_size(bufs)); + const int read = local_size; + while (local_size > 0) + { + int const len = std::min(int(bufs.front().size()), local_size); + auto local_buf = bufs.front().first(len); + for (char& v : local_buf) + { + v = char(m_counter & 0xff); + ++m_counter; + } + local_size -= len; + m_size -= len; + bufs = bufs.subspan(1); + } + return read; + } + + int m_size; + int m_counter; +}; + +struct test_error_fileop +{ + // EOF after size bytes read + explicit test_error_fileop(file_index_t error_file) + : m_error_file(error_file) {} + + int operator()(file_index_t const file_index, std::int64_t /*file_offset*/ + , span bufs, storage_error& ec) + { + if (m_error_file == file_index) + { + ec.file(file_index); + ec.ec.assign(boost::system::errc::permission_denied + , boost::system::generic_category()); + ec.operation = operation_t::file_read; + return -1; + } + return bufs_size(bufs); + } + + file_index_t m_error_file; +}; + +int count_bufs(iovec_t const* bufs, int bytes) +{ + int size = 0; + int count = 1; + if (bytes == 0) return 0; + for (iovec_t const* i = bufs;; ++i, ++count) + { + size += int(i->size()); + if (size >= bytes) return count; + } +} + +} // anonymous namespace + +TORRENT_TEST(readwritev_stripe_1) +{ + const int num_bufs = 30; + iovec_t iov[num_bufs]; + + alloc_iov(iov, num_bufs); + fill_pattern(iov, num_bufs); + + file_storage fs = make_fs(); + test_fileop fop(1); + storage_error ec; + + TEST_CHECK(bufs_size({iov, num_bufs}) >= fs.total_size()); + + iovec_t iov2[num_bufs]; + aux::copy_bufs(iov, int(fs.total_size()), iov2); + int num_bufs2 = count_bufs(iov2, int(fs.total_size())); + TEST_CHECK(num_bufs2 <= num_bufs); + + int ret = readwritev(fs, {iov2, num_bufs2}, piece_index_t(0), 0, ec + , std::ref(fop)); + + TEST_EQUAL(ret, fs.total_size()); + TEST_EQUAL(fop.m_file_data.size(), 4); + TEST_EQUAL(fop.m_file_data[file_index_t(0)].size(), 3); + TEST_EQUAL(fop.m_file_data[file_index_t(1)].size(), 9); + TEST_EQUAL(fop.m_file_data[file_index_t(2)].size(), 81); + TEST_EQUAL(fop.m_file_data[file_index_t(3)].size(), 6561); + + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(0)], 0)); + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(1)], 3)); + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(2)], 3 + 9)); + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(3)], 3 + 9 + 81)); + + free_iov(iov, num_bufs); +} + +TORRENT_TEST(readwritev_single_buffer) +{ + file_storage fs = make_fs(); + test_fileop fop(10000000); + storage_error ec; + + std::vector buf(size_t(fs.total_size())); + iovec_t iov = { &buf[0], int(buf.size()) }; + fill_pattern(&iov, 1); + + int ret = readwritev(fs, iov, piece_index_t(0), 0, ec, std::ref(fop)); + + TEST_EQUAL(ret, fs.total_size()); + TEST_EQUAL(fop.m_file_data.size(), 4); + TEST_EQUAL(fop.m_file_data[file_index_t(0)].size(), 3); + TEST_EQUAL(fop.m_file_data[file_index_t(1)].size(), 9); + TEST_EQUAL(fop.m_file_data[file_index_t(2)].size(), 81); + TEST_EQUAL(fop.m_file_data[file_index_t(3)].size(), 6561); + + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(0)], 0)); + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(1)], 3)); + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(2)], 3 + 9)); + TEST_CHECK(check_pattern(fop.m_file_data[file_index_t(3)], 3 + 9 + 81)); +} + +TORRENT_TEST(readwritev_read) +{ + file_storage fs = make_fs(); + test_read_fileop fop(10000000); + storage_error ec; + + std::vector buf(size_t(fs.total_size())); + iovec_t iov = { &buf[0], int(buf.size()) }; + + // read everything + int ret = readwritev(fs, iov, piece_index_t(0), 0, ec, std::ref(fop)); + + TEST_EQUAL(ret, fs.total_size()); + TEST_CHECK(check_pattern(buf, 0)); +} + +TORRENT_TEST(readwritev_read_short) +{ + file_storage fs = make_fs(); + test_read_fileop fop(100); + storage_error ec; + + std::vector buf(size_t(fs.total_size())); + iovec_t iov = { buf.data(), static_cast(fs.total_size()) }; + + // read everything + int ret = readwritev(fs, iov, piece_index_t(0), 0, ec, std::ref(fop)); + + TEST_EQUAL(static_cast(ec.file()), 3); + + TEST_EQUAL(ret, 100); + buf.resize(100); + TEST_CHECK(check_pattern(buf, 0)); +} + +TORRENT_TEST(readwritev_error) +{ + file_storage fs = make_fs(); + test_error_fileop fop(file_index_t(2)); + storage_error ec; + + std::vector buf(size_t(fs.total_size())); + iovec_t iov = { buf.data(), static_cast(fs.total_size()) }; + + // read everything + int ret = readwritev(fs, iov, piece_index_t(0), 0, ec, std::ref(fop)); + + TEST_EQUAL(ret, -1); + TEST_EQUAL(static_cast(ec.file()), 2); + TEST_CHECK(ec.operation == operation_t::file_read); + TEST_EQUAL(ec.ec, boost::system::errc::permission_denied); + std::printf("error: %s\n", ec.ec.message().c_str()); +} + +TORRENT_TEST(readwritev_zero_size_files) +{ + file_storage fs; + fs.add_file(combine_path("readwritev", "1"), 3); + fs.add_file(combine_path("readwritev", "2"), 0); + fs.add_file(combine_path("readwritev", "3"), 81); + fs.add_file(combine_path("readwritev", "4"), 0); + fs.add_file(combine_path("readwritev", "5"), 6561); + fs.set_piece_length(0x1000); + fs.set_num_pieces(int((fs.total_size() + 0xfff) / 0x1000)); + test_read_fileop fop(10000000); + storage_error ec; + + std::vector buf(size_t(fs.total_size())); + iovec_t iov = { buf.data(), static_cast(fs.total_size()) }; + + // read everything + int ret = readwritev(fs, iov, piece_index_t(0), 0, ec, std::ref(fop)); + + TEST_EQUAL(ret, fs.total_size()); + TEST_CHECK(check_pattern(buf, 0)); +} + +namespace { + +void delete_dirs(std::string path) +{ + error_code ec; + remove_all(path, ec); + if (ec && ec != boost::system::errc::no_such_file_or_directory) + { + std::printf("remove_all \"%s\": %s\n" + , path.c_str(), ec.message().c_str()); + } + TEST_CHECK(!exists(path)); +} + +} // anonymous namespace + +TORRENT_TEST(move_storage_to_self) +{ + // call move_storage with the path to the exising storage. should be a no-op + std::string const save_path = current_working_directory(); + std::string const test_path = combine_path(save_path, "temp_storage"); + delete_dirs(test_path); + + aux::session_settings set; + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(ios, std::bind(&nop)); + std::shared_ptr s = setup_torrent(fs, fp, buf, save_path, set); + + iovec_t const b = {&buf[0], 4}; + storage_error se; + s->writev(b, piece_index_t(1), 0, open_mode::read_write, se); + + TEST_CHECK(exists(combine_path(test_path, combine_path("folder2", "test3.tmp")))); + TEST_CHECK(exists(combine_path(test_path, combine_path("_folder3", "test4.tmp")))); + + s->move_storage(save_path, move_flags_t::always_replace_files, se); + TEST_EQUAL(se.ec, boost::system::errc::success); + + TEST_CHECK(exists(test_path)); + + TEST_CHECK(exists(combine_path(test_path, combine_path("folder2", "test3.tmp")))); + TEST_CHECK(exists(combine_path(test_path, combine_path("_folder3", "test4.tmp")))); +} + +TORRENT_TEST(move_storage_into_self) +{ + std::string const save_path = current_working_directory(); + delete_dirs(combine_path(save_path, "temp_storage")); + + aux::session_settings set; + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(ios, std::bind(&nop)); + std::shared_ptr s = setup_torrent(fs, fp, buf, save_path, set); + + iovec_t const b = {&buf[0], 4}; + storage_error se; + s->writev(b, piece_index_t(2), 0, open_mode::read_write, se); + + std::string const test_path = combine_path(save_path, combine_path("temp_storage", "folder1")); + s->move_storage(test_path, move_flags_t::always_replace_files, se); + TEST_EQUAL(se.ec, boost::system::errc::success); + + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + + // these directories and files are created up-front because they are empty files + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder2", "test3.tmp"))))); + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "test4.tmp"))))); +} + +TORRENT_TEST(storage_paths_string_pooling) +{ + file_storage file_storage; + file_storage.add_file(combine_path("test_storage", "root.txt"), 0x4000); + file_storage.add_file(combine_path("test_storage", combine_path("sub", "test1.txt")), 0x4000); + file_storage.add_file(combine_path("test_storage", combine_path("sub", "test2.txt")), 0x4000); + file_storage.add_file(combine_path("test_storage", combine_path("sub", "test3.txt")), 0x4000); + + // "sub" paths should point to same string item, so paths.size() must not grow + TEST_CHECK(file_storage.paths().size() <= 2); +} + +TORRENT_TEST(dont_move_intermingled_files) +{ + std::string const save_path = combine_path(current_working_directory(), "save_path_1"); + delete_dirs(combine_path(save_path, "temp_storage")); + + std::string test_path = combine_path(current_working_directory(), "save_path_2"); + delete_dirs(combine_path(test_path, "temp_storage")); + + aux::session_settings set; + file_storage fs; + std::vector buf; + file_pool fp; + io_service ios; + disk_buffer_pool dp(ios, std::bind(&nop)); + std::shared_ptr s = setup_torrent(fs, fp, buf, save_path, set); + + iovec_t b = {&buf[0], 4}; + storage_error se; + s->writev(b, piece_index_t(2), 0, open_mode::read_write, se); + + error_code ec; + create_directory(combine_path(save_path, combine_path("temp_storage" + , combine_path("_folder3", "alien_folder1"))), ec); + TEST_EQUAL(ec, boost::system::errc::success); + file f; + f.open(combine_path(save_path, combine_path("temp_storage", "alien1.tmp")) + , open_mode::write_only, ec); + f.close(); + TEST_EQUAL(ec, boost::system::errc::success); + f.open(combine_path(save_path, combine_path("temp_storage" + , combine_path("folder1", "alien2.tmp"))), open_mode::write_only, ec); + f.close(); + TEST_EQUAL(ec, boost::system::errc::success); + + s->move_storage(test_path, move_flags_t::always_replace_files, se); + TEST_EQUAL(se.ec, boost::system::errc::success); + + // torrent files moved to new place + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "test2.tmp"))))); + // these directories and files are created up-front because they are empty files + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder2", "test3.tmp"))))); + TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "test4.tmp"))))); + + // intermingled files and directories are still in old place + TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage" + , "alien1.tmp")))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , "alien1.tmp")))); + TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage" + , combine_path("folder1", "alien2.tmp"))))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("folder1", "alien2.tmp"))))); + TEST_CHECK(exists(combine_path(save_path, combine_path("temp_storage" + , combine_path("_folder3", "alien_folder1"))))); + TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" + , combine_path("_folder3", "alien_folder1"))))); +} diff --git a/test/test_string.cpp b/test/test_string.cpp new file mode 100644 index 0000000..f0f4465 --- /dev/null +++ b/test/test_string.cpp @@ -0,0 +1,564 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/aux_/escape_string.hpp" +#include "libtorrent/hex.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/aux_/string_ptr.hpp" +#include +#include // for strcmp +#include "libtorrent/aux_/escape_string.hpp" // for trim + +using namespace lt; + +TORRENT_TEST(maybe_url_encode) +{ + // test maybe_url_encode + TEST_EQUAL(maybe_url_encode("http://test:test@abc.com/abc<>abc"), "http://test:test@abc.com/abc%3c%3eabc"); + TEST_EQUAL(maybe_url_encode("http://abc.com/foo bar"), "http://abc.com/foo%20bar"); + TEST_EQUAL(maybe_url_encode("http://abc.com:80/foo bar"), "http://abc.com:80/foo%20bar"); + TEST_EQUAL(maybe_url_encode("http://abc.com:8080/foo bar"), "http://abc.com:8080/foo%20bar"); + TEST_EQUAL(maybe_url_encode("abc"), "abc"); + TEST_EQUAL(maybe_url_encode("http://abc.com/abc"), "http://abc.com/abc"); +} + +TORRENT_TEST(hex) +{ + static char const str[] = "0123456789012345678901234567890123456789"; + char bin[20]; + TEST_CHECK(aux::from_hex({str, 40}, bin)); + char hex[41]; + aux::to_hex(bin, hex); + TEST_CHECK(std::strcmp(hex, str) == 0); + + TEST_CHECK(aux::to_hex({"\x55\x73",2}) == "5573"); + TEST_CHECK(aux::to_hex({"\xaB\xd0",2}) == "abd0"); + + static char const hex_chars[] = "0123456789abcdefABCDEF"; + + for (int i = 1; i < 255; ++i) + { + bool const hex_loop = std::strchr(hex_chars, i) != nullptr; + char const c = char(i); + TEST_EQUAL(aux::is_hex(c), hex_loop); + } + + TEST_EQUAL(aux::hex_to_int('0'), 0); + TEST_EQUAL(aux::hex_to_int('7'), 7); + TEST_EQUAL(aux::hex_to_int('a'), 10); + TEST_EQUAL(aux::hex_to_int('f'), 15); + TEST_EQUAL(aux::hex_to_int('b'), 11); + TEST_EQUAL(aux::hex_to_int('t'), -1); + TEST_EQUAL(aux::hex_to_int('g'), -1); +} + +TORRENT_TEST(is_space) +{ + TEST_CHECK(!is_space('C')); + TEST_CHECK(!is_space('\b')); + TEST_CHECK(!is_space('8')); + TEST_CHECK(!is_space('=')); + TEST_CHECK(is_space(' ')); + TEST_CHECK(is_space('\t')); + TEST_CHECK(is_space('\n')); + TEST_CHECK(is_space('\r')); + TEST_CHECK(is_space('\f')); + TEST_CHECK(is_space('\v')); +} + +TORRENT_TEST(to_lower) +{ + TEST_CHECK(to_lower('C') == 'c'); + TEST_CHECK(to_lower('c') == 'c'); + TEST_CHECK(to_lower('-') == '-'); + TEST_CHECK(to_lower('&') == '&'); +} + +TORRENT_TEST(string_equal_no_case) +{ + TEST_CHECK(string_equal_no_case("foobar", "FoobAR")); + TEST_CHECK(string_equal_no_case("foobar", "foobar")); + TEST_CHECK(!string_equal_no_case("foobar", "foobar ")); + TEST_CHECK(!string_equal_no_case("foobar", "F00")); + TEST_CHECK(!string_equal_no_case("foobar", "foo")); + TEST_CHECK(!string_equal_no_case("foo", "foobar")); + + TEST_CHECK(string_begins_no_case("foobar", "FoobAR --")); + TEST_CHECK(string_begins_no_case("foo", "foobar")); + TEST_CHECK(!string_begins_no_case("foobar", "F00")); + TEST_CHECK(!string_begins_no_case("foobar", "foo")); + + TEST_CHECK(string_ends_with("foobar", "bar")); + TEST_CHECK(string_ends_with("name.txt", ".txt")); + TEST_CHECK(string_ends_with("name.a.b", ".a.b")); + TEST_CHECK(!string_ends_with("-- FoobAR", "foobar")); + TEST_CHECK(!string_ends_with("foobar", "F00")); + TEST_CHECK(!string_ends_with("foobar", "foo")); + TEST_CHECK(!string_ends_with("foo", "foobar")); +} + +TORRENT_TEST(to_string) +{ + TEST_CHECK(to_string(345).data() == std::string("345")); + TEST_CHECK(to_string(-345).data() == std::string("-345")); + TEST_CHECK(to_string(std::numeric_limits::max()).data() == std::string("9223372036854775807")); + TEST_CHECK(to_string(std::numeric_limits::min()).data() == std::string("-9223372036854775808")); + + TEST_CHECK(to_string(0).data() == std::string("0")); + TEST_CHECK(to_string(10).data() == std::string("10")); + TEST_CHECK(to_string(100).data() == std::string("100")); + TEST_CHECK(to_string(1000).data() == std::string("1000")); + TEST_CHECK(to_string(10000).data() == std::string("10000")); + TEST_CHECK(to_string(100000).data() == std::string("100000")); + TEST_CHECK(to_string(1000000).data() == std::string("1000000")); + TEST_CHECK(to_string(10000000).data() == std::string("10000000")); + TEST_CHECK(to_string(100000000).data() == std::string("100000000")); + TEST_CHECK(to_string(1000000000).data() == std::string("1000000000")); + TEST_CHECK(to_string(10000000000).data() == std::string("10000000000")); + TEST_CHECK(to_string(100000000000).data() == std::string("100000000000")); + TEST_CHECK(to_string(1000000000000).data() == std::string("1000000000000")); + TEST_CHECK(to_string(10000000000000).data() == std::string("10000000000000")); + TEST_CHECK(to_string(100000000000000).data() == std::string("100000000000000")); + TEST_CHECK(to_string(1000000000000000).data() == std::string("1000000000000000")); + + TEST_CHECK(to_string(9).data() == std::string("9")); + TEST_CHECK(to_string(99).data() == std::string("99")); + TEST_CHECK(to_string(999).data() == std::string("999")); + TEST_CHECK(to_string(9999).data() == std::string("9999")); + TEST_CHECK(to_string(99999).data() == std::string("99999")); + TEST_CHECK(to_string(999999).data() == std::string("999999")); + TEST_CHECK(to_string(9999999).data() == std::string("9999999")); + TEST_CHECK(to_string(99999999).data() == std::string("99999999")); + TEST_CHECK(to_string(999999999).data() == std::string("999999999")); + TEST_CHECK(to_string(9999999999).data() == std::string("9999999999")); + TEST_CHECK(to_string(99999999999).data() == std::string("99999999999")); + TEST_CHECK(to_string(999999999999).data() == std::string("999999999999")); + TEST_CHECK(to_string(9999999999999).data() == std::string("9999999999999")); + TEST_CHECK(to_string(99999999999999).data() == std::string("99999999999999")); + TEST_CHECK(to_string(999999999999999).data() == std::string("999999999999999")); + TEST_CHECK(to_string(9999999999999999).data() == std::string("9999999999999999")); + TEST_CHECK(to_string(99999999999999999).data() == std::string("99999999999999999")); + TEST_CHECK(to_string(999999999999999999).data() == std::string("999999999999999999")); + + TEST_CHECK(to_string(-10).data() == std::string("-10")); + TEST_CHECK(to_string(-100).data() == std::string("-100")); + TEST_CHECK(to_string(-1000).data() == std::string("-1000")); + TEST_CHECK(to_string(-10000).data() == std::string("-10000")); + TEST_CHECK(to_string(-100000).data() == std::string("-100000")); + TEST_CHECK(to_string(-1000000).data() == std::string("-1000000")); + TEST_CHECK(to_string(-10000000).data() == std::string("-10000000")); + TEST_CHECK(to_string(-100000000).data() == std::string("-100000000")); + TEST_CHECK(to_string(-1000000000).data() == std::string("-1000000000")); + TEST_CHECK(to_string(-10000000000).data() == std::string("-10000000000")); + TEST_CHECK(to_string(-100000000000).data() == std::string("-100000000000")); + TEST_CHECK(to_string(-1000000000000).data() == std::string("-1000000000000")); + TEST_CHECK(to_string(-10000000000000).data() == std::string("-10000000000000")); + TEST_CHECK(to_string(-100000000000000).data() == std::string("-100000000000000")); + TEST_CHECK(to_string(-1000000000000000).data() == std::string("-1000000000000000")); + + TEST_CHECK(to_string(-9).data() == std::string("-9")); + TEST_CHECK(to_string(-99).data() == std::string("-99")); + TEST_CHECK(to_string(-999).data() == std::string("-999")); + TEST_CHECK(to_string(-9999).data() == std::string("-9999")); + TEST_CHECK(to_string(-99999).data() == std::string("-99999")); + TEST_CHECK(to_string(-999999).data() == std::string("-999999")); + TEST_CHECK(to_string(-9999999).data() == std::string("-9999999")); + TEST_CHECK(to_string(-99999999).data() == std::string("-99999999")); + TEST_CHECK(to_string(-999999999).data() == std::string("-999999999")); + TEST_CHECK(to_string(-9999999999).data() == std::string("-9999999999")); + TEST_CHECK(to_string(-99999999999).data() == std::string("-99999999999")); + TEST_CHECK(to_string(-999999999999).data() == std::string("-999999999999")); + TEST_CHECK(to_string(-9999999999999).data() == std::string("-9999999999999")); + TEST_CHECK(to_string(-99999999999999).data() == std::string("-99999999999999")); + TEST_CHECK(to_string(-999999999999999).data() == std::string("-999999999999999")); + TEST_CHECK(to_string(-9999999999999999).data() == std::string("-9999999999999999")); + TEST_CHECK(to_string(-99999999999999999).data() == std::string("-99999999999999999")); + TEST_CHECK(to_string(-999999999999999999).data() == std::string("-999999999999999999")); +} + +TORRENT_TEST(base64) +{ + // base64 test vectors from http://www.faqs.org/rfcs/rfc4648.html + TEST_CHECK(base64encode("") == ""); + TEST_CHECK(base64encode("f") == "Zg=="); + TEST_CHECK(base64encode("fo") == "Zm8="); + TEST_CHECK(base64encode("foo") == "Zm9v"); + TEST_CHECK(base64encode("foob") == "Zm9vYg=="); + TEST_CHECK(base64encode("fooba") == "Zm9vYmE="); + TEST_CHECK(base64encode("foobar") == "Zm9vYmFy"); +} + +TORRENT_TEST(base32) +{ + // base32 test vectors from http://www.faqs.org/rfcs/rfc4648.html + +#if TORRENT_USE_I2P + TEST_CHECK(base32encode("") == ""); + TEST_CHECK(base32encode("f") == "MY======"); + TEST_CHECK(base32encode("fo") == "MZXQ===="); + TEST_CHECK(base32encode("foo") == "MZXW6==="); + TEST_CHECK(base32encode("foob") == "MZXW6YQ="); + TEST_CHECK(base32encode("fooba") == "MZXW6YTB"); + TEST_CHECK(base32encode("foobar") == "MZXW6YTBOI======"); + + // base32 for i2p + TEST_CHECK(base32encode("fo", string::no_padding) == "MZXQ"); + TEST_CHECK(base32encode("foob", string::i2p) == "mzxw6yq"); + TEST_CHECK(base32encode("foobar", string::lowercase) == "mzxw6ytboi======"); + + std::string test; + for (int i = 0; i < 255; ++i) + test += char(i); + + TEST_CHECK(base32decode(base32encode(test)) == test); +#endif // TORRENT_USE_I2P + + TEST_CHECK(base32decode("") == ""); + TEST_CHECK(base32decode("MY======") == "f"); + TEST_CHECK(base32decode("MZXQ====") == "fo"); + TEST_CHECK(base32decode("MZXW6===") == "foo"); + TEST_CHECK(base32decode("MZXW6YQ=") == "foob"); + TEST_CHECK(base32decode("MZXW6YTB") == "fooba"); + TEST_CHECK(base32decode("MZXW6YTBOI======") == "foobar"); + + TEST_CHECK(base32decode("MY") == "f"); + TEST_CHECK(base32decode("MZXW6YQ") == "foob"); + TEST_CHECK(base32decode("MZXW6YTBOI") == "foobar"); + TEST_CHECK(base32decode("mZXw6yTBO1======") == "foobar"); + + // make sure invalid encoding returns the empty string + TEST_CHECK(base32decode("mZXw6yTBO1{#&*()=") == ""); +} + +TORRENT_TEST(escape_string) +{ + // escape_string + char const* test_string = "!@#$%^&*()-_=+/,. %?"; + TEST_EQUAL(escape_string(test_string) + , "!%40%23%24%25%5e%26*()-_%3d%2b%2f%2c.%20%25%3f"); + + // escape_path + TEST_EQUAL(escape_path(test_string) + , "!%40%23%24%25%5e%26*()-_%3d%2b/%2c.%20%25%3f"); + + error_code ec; + TEST_CHECK(unescape_string(escape_path(test_string), ec) == test_string); + TEST_CHECK(!ec); + if (ec) std::printf("%s\n", ec.message().c_str()); + + // need_encoding + char const* test_string2 = "!@$&()-_/,.%?"; + TEST_CHECK(need_encoding(test_string, int(strlen(test_string))) == true); + TEST_CHECK(need_encoding(test_string2, int(strlen(test_string2))) == false); + TEST_CHECK(need_encoding("\n", 1) == true); + + // maybe_url_encode + TEST_EQUAL(maybe_url_encode("http://bla.com/\n"), "http://bla.com/%0a"); + TEST_EQUAL(maybe_url_encode("http://bla.com/foo%20bar"), "http://bla.com/foo%20bar"); + TEST_EQUAL(maybe_url_encode("http://bla.com/foo%20bar?k=v&k2=v2") + , "http://bla.com/foo%20bar?k=v&k2=v2"); + TEST_EQUAL(maybe_url_encode("?&"), "?&"); + + // unescape_string + TEST_CHECK(unescape_string(escape_string(test_string), ec) + == test_string); + std::cout << unescape_string(escape_string(test_string), ec) << std::endl; + // prematurely terminated string + unescape_string("%", ec); + TEST_CHECK(ec == error_code(errors::invalid_escaped_string)); + unescape_string("%0", ec); + TEST_CHECK(ec == error_code(errors::invalid_escaped_string)); + + // invalid hex character + unescape_string("%GE", ec); + TEST_CHECK(ec == error_code(errors::invalid_escaped_string)); + unescape_string("%eg", ec); + TEST_CHECK(ec == error_code(errors::invalid_escaped_string)); + ec.clear(); + + TEST_CHECK(unescape_string("123+abc", ec) == "123 abc"); +} + +TORRENT_TEST(read_until) +{ + char const* test_string1 = "abcdesdf sdgf"; + char const* tmp1 = test_string1; + TEST_CHECK(read_until(tmp1, 'd', test_string1 + strlen(test_string1)) == "abc"); + + tmp1 = test_string1; + TEST_CHECK(read_until(tmp1, '[', test_string1 + strlen(test_string1)) + == "abcdesdf sdgf"); +} + +TORRENT_TEST(path) +{ + std::string path = "a\\b\\c"; + convert_path_to_posix(path); + TEST_EQUAL(path, "a/b/c"); + +#if TORRENT_ABI_VERSION == 1 + // resolve_file_url + +#ifdef TORRENT_WINDOWS + std::string p = "c:/blah/foo/bar\\"; + convert_path_to_windows(p); + TEST_EQUAL(p, "c:\\blah\\foo\\bar\\"); + TEST_EQUAL(resolve_file_url("file:///c:/blah/foo/bar"), "c:\\blah\\foo\\bar"); + TEST_EQUAL(resolve_file_url("file:///c:/b%3fah/foo/bar"), "c:\\b?ah\\foo\\bar"); + TEST_EQUAL(resolve_file_url("file://\\c:\\b%3fah\\foo\\bar"), "c:\\b?ah\\foo\\bar"); +#else + TEST_EQUAL(resolve_file_url("file:///c/blah/foo/bar"), "/c/blah/foo/bar"); + TEST_EQUAL(resolve_file_url("file:///c/b%3fah/foo/bar"), "/c/b?ah/foo/bar"); +#endif +#endif +} + +namespace { + +void test_parse_interface(char const* input + , std::vector const expected + , std::vector const expected_e + , string_view const output) +{ + std::printf("parse interface: %s\n", input); + std::vector err; + auto const list = parse_listen_interfaces(input, err); + TEST_EQUAL(list.size(), expected.size()); + TEST_CHECK(list == expected); + TEST_CHECK(err == expected_e); +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING + TEST_EQUAL(print_listen_interfaces(list), output); + std::cout << "RESULT: " << print_listen_interfaces(list) << '\n'; +#endif + for (auto const& e : err) + std::cout << "ERR: \"" << e << "\"\n"; +} + +} // anonymous namespace + +TORRENT_TEST(parse_list) +{ + std::vector list; + parse_comma_separated_string(" a,b, c, d ,e \t,foobar\n\r,[::1]", list); + TEST_CHECK((list == std::vector{"a", "b", "c", "d", "e", "foobar", "[::1]"})); +} + +TORRENT_TEST(parse_interface) +{ + test_parse_interface(" a:4,b:35, c : 1000s, d: 351 ,e \t:42,foobar:1337s\n\r,[2001::1]:6881" + , {{"a", 4, false, false}, {"b", 35, false, false} + , {"c", 1000, true, false} + , {"d", 351, false, false} + , {"e", 42, false, false} + , {"foobar", 1337, true, false} + , {"2001::1", 6881, false, false}} + , {} + , "a:4,b:35,c:1000s,d:351,e:42,foobar:1337s,[2001::1]:6881"); + + // IPv6 address + test_parse_interface("[2001:ffff::1]:6882s" + , {{"2001:ffff::1", 6882, true, false}} + , {} + , "[2001:ffff::1]:6882s"); + + // IPv4 address + test_parse_interface("127.0.0.1:6882" + , {{"127.0.0.1", 6882, false, false}} + , {} + , "127.0.0.1:6882"); + + // maximum padding + test_parse_interface(" nic\r\n:\t 12\r s " + , {{"nic", 12, true, false}} + , {} + , "nic:12s"); + + // negative tests + test_parse_interface("nic:99999999999999999999999", {}, {"nic:99999999999999999999999"}, ""); + test_parse_interface("nic: -3", {}, {"nic: -3"}, ""); + test_parse_interface("nic: ", {}, {"nic:"}, ""); + test_parse_interface("nic :", {}, {"nic :"}, ""); + test_parse_interface("nic ", {}, {"nic"}, ""); + test_parse_interface("nic s", {}, {"nic s"}, ""); + + // parse interface with port 0 + test_parse_interface("127.0.0.1:0", {{"127.0.0.1", 0, false, false}} + , {}, "127.0.0.1:0"); + + // SSL flag + test_parse_interface("127.0.0.1:1234s", {{"127.0.0.1", 1234, true, false}} + , {}, "127.0.0.1:1234s"); + // local flag + test_parse_interface("127.0.0.1:1234l", {{"127.0.0.1", 1234, false, true}} + , {}, "127.0.0.1:1234l"); + + // both + test_parse_interface("127.0.0.1:1234ls", {{"127.0.0.1", 1234, true, true}} + , {}, "127.0.0.1:1234sl"); + + // IPv6 error + test_parse_interface("[aaaa::1", {}, {"[aaaa::1"}, ""); + test_parse_interface("[aaaa::1]", {}, {"[aaaa::1]"}, ""); + test_parse_interface("[aaaa::1]:", {}, {"[aaaa::1]:"}, ""); + test_parse_interface("[aaaa::1]:s", {}, {"[aaaa::1]:s"}, ""); + test_parse_interface("[aaaa::1] :6881", {}, {"[aaaa::1] :6881"}, ""); + test_parse_interface("[aaaa::1]:6881", {{"aaaa::1", 6881, false, false}}, {}, "[aaaa::1]:6881"); + + // unterminated [ + test_parse_interface("[aaaa::1,foobar:0", {{"foobar", 0, false, false}}, {"[aaaa::1"}, "foobar:0"); + + // multiple errors + test_parse_interface("foo:,bar", {}, {"foo:", "bar"}, ""); + + // quoted elements + test_parse_interface("\"abc,.\",bar", {}, {"abc,.", "bar"}, ""); + + // silent error + test_parse_interface("\"", {}, {"\""}, ""); + + // multiple errors and one correct + test_parse_interface("foo,bar,0.0.0.0:6881", {{"0.0.0.0", 6881, false, false}}, {"foo", "bar"}, "0.0.0.0:6881"); +} + +TORRENT_TEST(split_string) +{ + TEST_CHECK(split_string("a b"_sv, ' ') == std::make_pair("a"_sv, "b"_sv)); + TEST_CHECK(split_string("\"a b\" c"_sv, ' ') == std::make_pair("\"a b\""_sv, "c"_sv)); + TEST_CHECK(split_string("\"a b\"foobar c"_sv, ' ') == std::make_pair("\"a b\"foobar"_sv, "c"_sv)); + TEST_CHECK(split_string("a\nb foobar"_sv, ' ') == std::make_pair("a\nb"_sv, "foobar"_sv)); + TEST_CHECK(split_string("a b\"foo\"bar"_sv, '"') == std::make_pair("a b"_sv, "foo\"bar"_sv)); + TEST_CHECK(split_string("a"_sv, ' ') == std::make_pair("a"_sv, ""_sv)); + TEST_CHECK(split_string("\"a b"_sv, ' ') == std::make_pair("\"a b"_sv, ""_sv)); + TEST_CHECK(split_string(""_sv, ' ') == std::make_pair(""_sv, ""_sv)); +} + +TORRENT_TEST(convert_from_native) +{ + TEST_EQUAL(std::string("foobar"), convert_from_native(convert_to_native("foobar"))); + TEST_EQUAL(std::string("foobar") + , convert_from_native(convert_to_native("foo")) + + convert_from_native(convert_to_native("bar"))); + + TEST_EQUAL(convert_to_native("foobar") + , convert_to_native("foo") + convert_to_native("bar")); +} + +TORRENT_TEST(trim) +{ + TEST_EQUAL(trim(""), ""); + TEST_EQUAL(trim("\t "), ""); + TEST_EQUAL(trim(" a"), "a"); + TEST_EQUAL(trim(" a "), "a"); + TEST_EQUAL(trim("\t \na \t\r"), "a"); + TEST_EQUAL(trim(" \t \ta"), "a"); + TEST_EQUAL(trim("a "), "a"); + TEST_EQUAL(trim("a \t"), "a"); + TEST_EQUAL(trim("a \t\n \tb"), "a \t\n \tb"); +} + +#if TORRENT_USE_I2P +TORRENT_TEST(i2p_url) +{ + TEST_CHECK(is_i2p_url("http://a.i2p/a")); + TEST_CHECK(!is_i2p_url("http://a.I2P/a")); + TEST_CHECK(!is_i2p_url("http://c.i3p")); + TEST_CHECK(!is_i2p_url("http://i2p/foo bar")); +} +#endif + +TORRENT_TEST(string_ptr_zero_termination) +{ + char str[] = {'f', 'o', 'o', 'b', 'a', 'r'}; + aux::string_ptr p(string_view(str, sizeof(str))); + + // make sure it's zero-terminated now + TEST_CHECK(strlen(*p) == 6); + TEST_CHECK((*p)[6] == '\0'); + TEST_CHECK(*p == string_view("foobar")); +} + +TORRENT_TEST(string_ptr_move_construct) +{ + aux::string_ptr p1("test"); + TEST_CHECK(*p1 == string_view("test")); + + aux::string_ptr p2(std::move(p1)); + + TEST_CHECK(*p2 == string_view("test")); + + // moved-from state is empty + TEST_CHECK(*p1 == nullptr); +} + +TORRENT_TEST(string_ptr_move_assign) +{ + aux::string_ptr p1("test"); + TEST_CHECK(*p1 == string_view("test")); + + aux::string_ptr p2("foobar"); + + p1 = std::move(p2); + + TEST_CHECK(*p1 == string_view("foobar")); + + // moved-from state is empty + TEST_CHECK(*p2 == nullptr); +} + +TORRENT_TEST(find_first_of) +{ + string_view test("01234567891"); + TEST_EQUAL(find_first_of(test, '1', 0), 1); + TEST_EQUAL(find_first_of(test, '1', 1), 1); + TEST_EQUAL(find_first_of(test, '1', 2), 10); + TEST_EQUAL(find_first_of(test, '1', 3), 10); + + TEST_EQUAL(find_first_of(test, "61", 0), 1); + TEST_EQUAL(find_first_of(test, "61", 1), 1); + TEST_EQUAL(find_first_of(test, "61", 2), 6); + TEST_EQUAL(find_first_of(test, "61", 3), 6); + TEST_EQUAL(find_first_of(test, "61", 4), 6); +} + +TORRENT_TEST(strip_string) +{ + TEST_EQUAL(strip_string(" ab"), "ab"); + TEST_EQUAL(strip_string(" ab "), "ab"); + TEST_EQUAL(strip_string(" "), ""); + TEST_EQUAL(strip_string(""), ""); + TEST_EQUAL(strip_string("a b"), "a b"); + TEST_EQUAL(strip_string(" a b "), "a b"); + TEST_EQUAL(strip_string(" \t \t ab\t\t\t"), "ab"); +} + diff --git a/test/test_tailqueue.cpp b/test/test_tailqueue.cpp new file mode 100644 index 0000000..eb47d45 --- /dev/null +++ b/test/test_tailqueue.cpp @@ -0,0 +1,169 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/tailqueue.hpp" + +using namespace lt; + +namespace { + +struct test_node : tailqueue_node +{ + explicit test_node(char n) : name(n) {} + char name; +}; + +void check_chain(tailqueue& chain, char const* expected) +{ + tailqueue_iterator i = chain.iterate(); + + while (i.get()) + { + TEST_EQUAL(static_cast(i.get())->name, *expected); + i.next(); + ++expected; + } + if (!chain.empty()) + { + TEST_CHECK(chain.last() == nullptr || chain.last()->next == nullptr); + } + TEST_EQUAL(expected[0], 0); +} + +void free_chain(tailqueue& q) +{ + test_node* chain = static_cast(q.get_all()); + while(chain) + { + test_node* del = static_cast(chain); + chain = static_cast(chain->next); + delete del; + } +} + +void build_chain(tailqueue& q, char const* str) +{ + free_chain(q); + + char const* expected = str; + + while(*str) + { + q.push_back(new test_node(*str)); + ++str; + } + check_chain(q, expected); +} + +} // anonymous namespace + +TORRENT_TEST(tailqueue) +{ + tailqueue t1; + tailqueue t2; + + // test prepend + build_chain(t1, "abcdef"); + build_chain(t2, "12345"); + + t1.prepend(t2); + check_chain(t1, "12345abcdef"); + check_chain(t2, ""); + + // test append + build_chain(t1, "abcdef"); + build_chain(t2, "12345"); + + t1.append(t2); + check_chain(t1, "abcdef12345"); + check_chain(t2, ""); + + // test swap + build_chain(t1, "abcdef"); + build_chain(t2, "12345"); + + t1.swap(t2); + + check_chain(t1, "12345"); + check_chain(t2, "abcdef"); + + // test pop_front + build_chain(t1, "abcdef"); + + delete t1.pop_front(); + + check_chain(t1, "bcdef"); + + // test push_back + + build_chain(t1, "abcdef"); + t1.push_back(new test_node('1')); + check_chain(t1, "abcdef1"); + + // test push_front + + build_chain(t1, "abcdef"); + t1.push_front(new test_node('1')); + check_chain(t1, "1abcdef"); + + // test size + + build_chain(t1, "abcdef"); + TEST_EQUAL(t1.size(), 6); + + // test empty + + free_chain(t1); + TEST_EQUAL(t1.empty(), true); + build_chain(t1, "abcdef"); + TEST_EQUAL(t1.empty(), false); + + // test get_all + build_chain(t1, "abcdef"); + test_node* n = t1.get_all(); + TEST_EQUAL(t1.empty(), true); + TEST_EQUAL(t1.size(), 0); + + char const* expected = "abcdef"; + while (n) + { + test_node* del = n; + TEST_EQUAL(n->name, *expected); + n = n->next; + ++expected; + delete del; + } + + free_chain(t1); + free_chain(t2); +} diff --git a/test/test_threads.cpp b/test/test_threads.cpp new file mode 100644 index 0000000..438ed9e --- /dev/null +++ b/test/test_threads.cpp @@ -0,0 +1,128 @@ +/* + +Copyright (c) 2010, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include + +#include "test.hpp" +#include "libtorrent/time.hpp" + +using namespace lt; + +namespace { + +void fun(std::condition_variable* s, std::mutex* m, int* waiting, int i) +{ + std::printf("thread %d waiting\n", i); + std::unique_lock l(*m); + *waiting += 1; + s->wait(l); + std::printf("thread %d done\n", i); +} + +void increment(std::condition_variable* s, std::mutex* m, int* waiting, std::atomic* c) +{ + std::unique_lock l(*m); + *waiting += 1; + s->wait(l); + l.unlock(); + for (int i = 0; i < 1000000; ++i) + ++*c; +} + +void decrement(std::condition_variable* s, std::mutex* m, int* waiting, std::atomic* c) +{ + std::unique_lock l(*m); + *waiting += 1; + s->wait(l); + l.unlock(); + for (int i = 0; i < 1000000; ++i) + --*c; +} + +} // anonymous namespace + +TORRENT_TEST(threads) +{ + std::condition_variable cond; + std::mutex m; + std::vector threads; + int waiting = 0; + for (int i = 0; i < 20; ++i) + { + threads.emplace_back(&fun, &cond, &m, &waiting, i); + } + + // make sure all threads are waiting on the condition_variable + std::unique_lock l(m); + while (waiting < 20) + { + l.unlock(); + std::this_thread::sleep_for(lt::milliseconds(10)); + l.lock(); + } + + cond.notify_all(); + l.unlock(); + + for (auto& t : threads) t.join(); + threads.clear(); + + waiting = 0; + std::atomic c(0); + for (int i = 0; i < 3; ++i) + { + threads.emplace_back(&increment, &cond, &m, &waiting, &c); + threads.emplace_back(&decrement, &cond, &m, &waiting, &c); + } + + // make sure all threads are waiting on the condition_variable + l.lock(); + while (waiting < 6) + { + l.unlock(); + std::this_thread::sleep_for(lt::milliseconds(10)); + l.lock(); + } + + cond.notify_all(); + l.unlock(); + + for (auto& t : threads) t.join(); + + TEST_CHECK(c == 0); +} + diff --git a/test/test_time.cpp b/test/test_time.cpp new file mode 100644 index 0000000..a3d2ae1 --- /dev/null +++ b/test/test_time.cpp @@ -0,0 +1,104 @@ +/* + +Copyright (c) 2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +#include "libtorrent/time.hpp" + +#include +#include +#include +#include + +using namespace lt; + +namespace { + +void check_timer_loop(std::mutex& m, time_point& last, std::condition_variable& cv) +{ + std::unique_lock l(m); + cv.wait(l); + l.unlock(); + + for (int i = 0; i < 10000; ++i) + { + std::lock_guard ll(m); + time_point now = clock_type::now(); + TEST_CHECK(now >= last); + last = now; + } +} + +} // anonymous namespace + +TORRENT_TEST(time) +{ + // make sure the time classes have correct semantics + + TEST_EQUAL(total_milliseconds(milliseconds(100)), 100); + TEST_EQUAL(total_milliseconds(milliseconds(1)), 1); + TEST_EQUAL(total_milliseconds(seconds(1)), 1000); + TEST_EQUAL(total_seconds(minutes(1)), 60); + TEST_EQUAL(total_seconds(hours(1)), 3600); + + // make sure it doesn't wrap at 32 bit arithmetic + TEST_EQUAL(total_seconds(seconds(281474976)), 281474976); + TEST_EQUAL(total_milliseconds(milliseconds(281474976)), 281474976); + + // make sure the timer is monotonic + + time_point now = clock_type::now(); + time_point last = now; + for (int i = 0; i < 1000; ++i) + { + now = clock_type::now(); + TEST_CHECK(now >= last); + last = now; + } + + std::mutex m; + std::condition_variable cv; + std::thread t1(&check_timer_loop, std::ref(m), std::ref(last), std::ref(cv)); + std::thread t2(&check_timer_loop, std::ref(m), std::ref(last), std::ref(cv)); + std::thread t3(&check_timer_loop, std::ref(m), std::ref(last), std::ref(cv)); + std::thread t4(&check_timer_loop, std::ref(m), std::ref(last), std::ref(cv)); + + std::this_thread::sleep_for(lt::milliseconds(100)); + + cv.notify_all(); + + t1.join(); + t2.join(); + t3.join(); + t4.join(); +} + diff --git a/test/test_time_critical.cpp b/test/test_time_critical.cpp new file mode 100644 index 0000000..318a49f --- /dev/null +++ b/test/test_time_critical.cpp @@ -0,0 +1,41 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "swarm_suite.hpp" + +TORRENT_TEST(time_crititcal) +{ + // with time critical pieces + test_swarm(test_flags::time_critical); +} + + diff --git a/test/test_timestamp_history.cpp b/test/test_timestamp_history.cpp new file mode 100644 index 0000000..9daebc8 --- /dev/null +++ b/test/test_timestamp_history.cpp @@ -0,0 +1,57 @@ +/* + +Copyright (c) 2008-2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/timestamp_history.hpp" + +TORRENT_TEST(timestamp_history) +{ + using namespace lt; + + timestamp_history h; + TEST_EQUAL(h.add_sample(0x32, false), 0); + TEST_EQUAL(h.base(), 0x32); + TEST_EQUAL(h.add_sample(0x33, false), 0x1); + TEST_EQUAL(h.base(), 0x32); + TEST_EQUAL(h.add_sample(0x3433, false), 0x3401); + TEST_EQUAL(h.base(), 0x32); + TEST_EQUAL(h.add_sample(0x30, false), 0); + TEST_EQUAL(h.base(), 0x30); + + // test that wrapping of the timestamp is properly handled + h.add_sample(0xfffffff3, false); + TEST_EQUAL(h.base(), 0xfffffff3); + + // TODO: test the case where we have > 120 samples (and have the base delay actually be updated) + // TODO: test the case where a sample is lower than the history entry but not lower than the base +} + diff --git a/test/test_torrent.cpp b/test/test_torrent.cpp new file mode 100644 index 0000000..af965ab --- /dev/null +++ b/test/test_torrent.cpp @@ -0,0 +1,858 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/path.hpp" // for combine_path, current_working_directory +#include "libtorrent/magnet_uri.hpp" +#include "settings.hpp" +#include +#include + +#include "test.hpp" +#include "test_utils.hpp" +#include "setup_transfer.hpp" + +using namespace lt; + +namespace { + +bool wait_priority(torrent_handle const& h, aux::vector const& prio) +{ + for (int i = 0; i < 10; ++i) + { + if (h.get_file_priorities() == prio) return true; + +#ifdef NDEBUG + std::this_thread::sleep_for(lt::milliseconds(100)); +#else + std::this_thread::sleep_for(lt::milliseconds(300)); +#endif + } + + return h.get_file_priorities() == prio; +} + +bool prioritize_files(torrent_handle const& h, aux::vector const& prio) +{ + h.prioritize_files(prio); + return wait_priority(h, prio); +} + +void test_running_torrent(std::shared_ptr info, std::int64_t file_size) +{ + settings_pack pack = settings(); + pack.set_int(settings_pack::alert_mask, alert_category::piece_progress | alert_category::storage); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack); + + aux::vector zeroes; + zeroes.resize(1000, 0_pri); + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.ti = info; + p.save_path = "."; + + // make sure we correctly handle the case where we pass in + // more values than there are files + p.file_priorities = zeroes; + + error_code ec; + torrent_handle h = ses.add_torrent(p, ec); + if (ec) + { + std::printf("add_torrent: %s\n", ec.message().c_str()); + return; + } + + aux::vector ones(std::size_t(info->num_files()), 1_pri); + TEST_CHECK(prioritize_files(h, ones)); + + torrent_status st = h.status(); + + TEST_EQUAL(st.total_wanted, file_size); // we want the single file + TEST_EQUAL(st.total_wanted_done, 0); + + aux::vector prio(std::size_t(info->num_files()), 1_pri); + prio[file_index_t(0)] = 0_pri; + TEST_CHECK(prioritize_files(h, prio)); + st = h.status(); + + st = h.status(); + TEST_EQUAL(st.total_wanted, 0); // we don't want anything + TEST_EQUAL(st.total_wanted_done, 0); + TEST_EQUAL(int(h.get_file_priorities().size()), info->num_files()); + + if (info->num_files() > 1) + { + prio[file_index_t{1}] = 0_pri; + TEST_CHECK(prioritize_files(h, prio)); + + st = h.status(); + TEST_EQUAL(st.total_wanted, file_size); + TEST_EQUAL(st.total_wanted_done, 0); + } + + if (info->num_pieces() > 0) + { + h.piece_priority(piece_index_t(0), 1_pri); + st = h.status(); + TEST_CHECK(st.pieces.size() > 0 && st.pieces[piece_index_t(0)] == false); + std::vector piece(std::size_t(info->piece_length())); + for (int i = 0; i < int(piece.size()); ++i) + piece[std::size_t(i)] = (i % 26) + 'A'; + h.add_piece(piece_index_t(0), &piece[0], torrent_handle::overwrite_existing); + + // wait until the piece is done writing and hashing + wait_for_alert(ses, piece_finished_alert::alert_type, "piece_finished_alert"); + st = h.status(); + TEST_CHECK(st.pieces.size() > 0); + + std::cout << "reading piece 0" << std::endl; + h.read_piece(piece_index_t(0)); + alert const* a = wait_for_alert(ses, read_piece_alert::alert_type, "read_piece"); + TEST_CHECK(a); + read_piece_alert const* rpa = alert_cast(a); + TEST_CHECK(rpa); + if (rpa) + { + std::cout << "SUCCEEDED!" << std::endl; + TEST_CHECK(std::memcmp(&piece[0], rpa->buffer.get() + , std::size_t(info->piece_size(piece_index_t(0)))) == 0); + TEST_CHECK(rpa->size == info->piece_size(piece_index_t(0))); + TEST_CHECK(rpa->piece == piece_index_t(0)); + TEST_CHECK(hasher(piece).final() == info->hash_for_piece(piece_index_t(0))); + } + } + + TEST_CHECK(h.get_file_priorities() == prio); +} + +void test_large_piece_size(int const size) +{ + entry torrent; + entry& info = torrent["info"]; + info["pieces"] = "aaaaaaaaaaaaaaaaaaaa"; + info["name"] = "test"; + info["piece length"] = size; + info["length"] = size; + + std::vector buf; + bencode(std::back_inserter(buf), torrent); + add_torrent_params atp; + atp.ti = std::make_shared(buf, from_span); + atp.save_path = "."; + + lt::session ses; + auto h = ses.add_torrent(atp); + TEST_CHECK(h.status().errc == error_code(lt::errors::invalid_piece_size)); + h.clear_error(); + TEST_CHECK(h.status().errc == error_code(lt::errors::invalid_piece_size)); +} + +} // anonymous namespace + +TORRENT_TEST(long_names) +{ + entry torrent; + entry& info = torrent["info"]; + info["pieces"] = "aaaaaaaaaaaaaaaaaaaa"; + info["name"] = "slightly shorter name, it's kind of sad that people started " + "the trend of incorrectly encoding the regular name field and then adding " + "another one with correct encoding"; + info["name.utf-8"] = "this is a long ass name in order to try to make " + "make_magnet_uri overflow and hopefully crash. Although, by the time you " + "read this that particular bug should have been fixed"; + info["piece length"] = 16 * 1024; + info["length"] = 3245; + + std::vector buf; + bencode(std::back_inserter(buf), torrent); + auto ti = std::make_shared(buf, from_span); +} + +TORRENT_TEST(large_piece_size) +{ + test_large_piece_size(32768 * 16 * 1024); + test_large_piece_size(65536 * 16 * 1024); + test_large_piece_size(65537 * 16 * 1024); +} + +TORRENT_TEST(total_wanted) +{ + file_storage fs; + + fs.add_file("test_torrent_dir4/tmp1", 1024); + fs.add_file("test_torrent_dir4/tmp2", 1024); + fs.add_file("test_torrent_dir4/tmp3", 1024); + fs.add_file("test_torrent_dir4/tmp4", 1024); + + lt::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + + settings_pack pack = settings(); + pack.set_int(settings_pack::alert_mask, alert_category::storage); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack); + + add_torrent_params p; + p.ti = info; + p.save_path = "."; + + // we just want 1 out of 4 files, 1024 out of 4096 bytes + p.file_priorities.resize(4, 0_pri); + p.file_priorities[1] = 1_pri; + + torrent_handle h = ses.add_torrent(std::move(p)); + + torrent_status st = h.status(); + TEST_EQUAL(st.total_wanted, 1024); + TEST_EQUAL(st.total_wanted_done, 0); + + // make sure that selecting and unseleting a file quickly still end up with + // the last set priority + h.file_priority(file_index_t{1}, default_priority); + h.file_priority(file_index_t{1}, dont_download); + TEST_CHECK(wait_priority(h, aux::vector(static_cast(fs.num_files())))); + TEST_EQUAL(h.status({}).total_wanted, 0); +} + +TORRENT_TEST(added_peers) +{ + file_storage fs; + + fs.add_file("test_torrent_dir4/tmp1", 1024); + + lt::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + + settings_pack pack = settings(); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack); + + add_torrent_params p = parse_magnet_uri( + "magnet:?xt=urn:btih:abababababababababababababababababababab&x.pe=127.0.0.1:48081&x.pe=127.0.0.2:48082"); + p.ti = info; + p.info_hash.clear(); + p.save_path = "."; + + torrent_handle h = ses.add_torrent(std::move(p)); + + h.save_resume_data(); + alert const* a = wait_for_alert(ses, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) TEST_EQUAL(ra->params.peers.size(), 2); +} + +TORRENT_TEST(mismatching_info_hash) +{ + file_storage fs; + fs.add_file("test_torrent_dir4/tmp1", 1024); + lt::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + + add_torrent_params p; + p.ti = std::move(info); + + // this info-hash is definitely different from the one in `info`, this + // should trigger a failure + p.info_hash = lt::sha1_hash("01010101010101010101"); + p.save_path = "."; + + lt::session ses(settings()); + error_code ec; + torrent_handle h = ses.add_torrent(std::move(p), ec); + TEST_CHECK(ec == lt::errors::mismatching_info_hash); + TEST_CHECK(!h.is_valid()); +} + +TORRENT_TEST(exceed_file_prio) +{ + file_storage fs; + fs.add_file("test_torrent_dir4/tmp1", 1024); + lt::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + + add_torrent_params p; + p.ti = std::move(info); + + p.file_priorities.resize(9999, lt::low_priority); + p.save_path = "."; + + lt::session ses(settings()); + error_code ec; + torrent_handle h = ses.add_torrent(std::move(p)); + auto const prios = h.get_file_priorities(); + TEST_CHECK(prios.size() == 1); +} + +TORRENT_TEST(exceed_piece_prio) +{ + file_storage fs; + fs.add_file("test_torrent_dir4/tmp1", 1024); + lt::create_torrent t(fs, 1024); + std::vector tmp; + bencode(std::back_inserter(tmp), t.generate()); + auto info = std::make_shared(tmp, from_span); + std::size_t const num_pieces = std::size_t(info->num_pieces()); + + add_torrent_params p; + p.ti = std::move(info); + + p.piece_priorities.resize(9999, lt::low_priority); + p.save_path = "."; + + lt::session ses(settings()); + error_code ec; + torrent_handle h = ses.add_torrent(std::move(p)); + auto const prios = h.get_piece_priorities(); + TEST_CHECK(prios.size() == num_pieces); +} + +TORRENT_TEST(exceed_piece_prio_magnet) +{ + add_torrent_params p; + p.info_hash = sha1_hash("abababababababababab"); + p.piece_priorities.resize(9999, lt::low_priority); + p.save_path = "."; + + lt::session ses(settings()); + error_code ec; + torrent_handle h = ses.add_torrent(std::move(p)); + auto const prios = h.get_piece_priorities(); + TEST_CHECK(prios.empty()); +} + +TORRENT_TEST(torrent) +{ +/* { + remove("test_torrent_dir2/tmp1"); + remove("test_torrent_dir2/tmp2"); + remove("test_torrent_dir2/tmp3"); + file_storage fs; + std::int64_t file_size = 256 * 1024; + fs.add_file("test_torrent_dir2/tmp1", file_size); + fs.add_file("test_torrent_dir2/tmp2", file_size); + fs.add_file("test_torrent_dir2/tmp3", file_size); + lt::create_torrent t(fs, 128 * 1024); + t.add_tracker("http://non-existing.com/announce"); + + std::vector piece(128 * 1024); + for (int i = 0; i < int(piece.size()); ++i) + piece[i] = (i % 26) + 'A'; + + // calculate the hash for all pieces + sha1_hash ph = hasher(piece).final(); + int num = t.num_pieces(); + TEST_CHECK(t.num_pieces() > 0); + for (int i = 0; i < num; ++i) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator> out(tmp); + bencode(out, t.generate()); + error_code ec; + auto info = std::make_shared(tmp, std::ref(ec), from_span); + TEST_CHECK(info->num_pieces() > 0); + + test_running_torrent(info, file_size); + } +*/ + { + file_storage fs; + + fs.add_file("test_torrent_dir2/tmp1", 1024); + lt::create_torrent t(fs, 1024, 6); + + std::vector piece(1024); + for (int i = 0; i < int(piece.size()); ++i) + piece[std::size_t(i)] = (i % 26) + 'A'; + + // calculate the hash for all pieces + sha1_hash const ph = hasher(piece).final(); + TEST_CHECK(t.num_pieces() > 0); + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator> out(tmp); + bencode(out, t.generate()); + auto info = std::make_shared(tmp, from_span); + test_running_torrent(info, 1024); + } +} + +#ifndef TORRENT_DISABLE_EXTENSIONS +struct test_plugin : lt::torrent_plugin {}; + +struct plugin_creator +{ + explicit plugin_creator(int& c) : m_called(c) {} + + std::shared_ptr + operator()(torrent_handle const&, void*) + { + ++m_called; + return std::make_shared(); + } + + int& m_called; +}; + +TORRENT_TEST(duplicate_is_not_error) +{ + file_storage fs; + + fs.add_file("test_torrent_dir2/tmp1", 1024); + lt::create_torrent t(fs, 128 * 1024, 6); + + std::vector piece(128 * 1024); + for (int i = 0; i < int(piece.size()); ++i) + piece[std::size_t(i)] = (i % 26) + 'A'; + + // calculate the hash for all pieces + sha1_hash ph = hasher(piece).final(); + TEST_CHECK(t.num_pieces() > 0); + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator> out(tmp); + bencode(out, t.generate()); + + int called = 0; + plugin_creator creator(called); + + add_torrent_params p; + p.ti = std::make_shared(tmp, from_span); + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + p.flags &= ~torrent_flags::duplicate_is_error; + p.save_path = "."; + p.extensions.push_back(creator); + + lt::session ses(settings()); + ses.async_add_torrent(p); + ses.async_add_torrent(std::move(p)); + + wait_for_downloading(ses, "ses"); + + // we should only have added the plugin once + TEST_EQUAL(called, 1); +} +#endif + +TORRENT_TEST(torrent_total_size_zero) +{ + file_storage fs; + + fs.add_file("test_torrent_dir2/tmp1", 0); + TEST_CHECK(fs.num_files() == 1); + TEST_CHECK(fs.total_size() == 0); + + error_code ec; + lt::create_torrent t1(fs); + set_piece_hashes(t1, ".", ec); + TEST_CHECK(ec); + + fs.add_file("test_torrent_dir2/tmp2", 0); + TEST_CHECK(fs.num_files() == 2); + TEST_CHECK(fs.total_size() == 0); + + ec.clear(); + lt::create_torrent t2(fs); + set_piece_hashes(t2, ".", ec); + TEST_CHECK(ec); +} + +TORRENT_TEST(rename_file) +{ + file_storage fs; + + fs.add_file("test3/tmp1", 20); + fs.add_file("test3/tmp2", 20); + lt::create_torrent t(fs, 128 * 1024, 6); + + std::vector tmp; + std::back_insert_iterator> out(tmp); + bencode(out, t.generate()); + auto info = std::make_shared(tmp, from_span); + + TEST_EQUAL(info->files().file_path(file_index_t(0)), combine_path("test3","tmp1")); + + // move "test3/tmp1" -> "tmp1" + info->rename_file(file_index_t(0), "tmp1"); + + TEST_EQUAL(info->files().file_path(file_index_t(0)), "tmp1"); +} + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(async_load_deprecated) +{ + settings_pack pack = settings(); + lt::session ses(pack); + + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + std::string dir = parent_path(current_working_directory()); + + p.url = "file://" + combine_path(combine_path(dir, "test_torrents"), "base.torrent"); + p.save_path = "."; + ses.async_add_torrent(std::move(p)); + + alert const* a = wait_for_alert(ses, add_torrent_alert::alert_type); + TEST_CHECK(a); + if (a == nullptr) return; + auto const* ta = alert_cast(a); + TEST_CHECK(ta); + if (ta == nullptr) return; + TEST_CHECK(!ta->error); + TEST_CHECK(ta->params.ti->name() == "temp"); +} +#endif + +TORRENT_TEST(torrent_status) +{ + TEST_EQUAL(static_cast(torrent_status::error_file_none), -1); +#if TORRENT_ABI_VERSION == 1 + TEST_EQUAL(static_cast(torrent_status::error_file_url), -2); + TEST_EQUAL(static_cast(torrent_status::error_file_metadata), -4); +#endif + TEST_EQUAL(static_cast(torrent_status::error_file_ssl_ctx), -3); + TEST_EQUAL(static_cast(torrent_status::error_file_exception), -5); +} + +namespace { + +void test_queue(add_torrent_params) +{ + lt::settings_pack pack = settings(); + // we're not testing the hash check, just accept the data we write + pack.set_bool(settings_pack::disable_hash_checks, true); + lt::session ses(pack); + + std::vector torrents; + for(int i = 0; i < 6; i++) + { + file_storage fs; + std::stringstream file_path; + file_path << "test_torrent_dir4/queue" << i; + fs.add_file(file_path.str(), 1024); + lt::create_torrent t(fs, 128 * 1024, 6); + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto ti = std::make_shared(buf, from_span); + add_torrent_params p; + p.ti = ti; + p.save_path = "."; + torrents.push_back(ses.add_torrent(std::move(p))); + } + + print_alerts(ses, "ses"); + + std::vector pieces( + std::size_t(torrents[5].torrent_file()->num_pieces()), 0_pri); + torrents[5].prioritize_pieces(pieces); + torrent_handle finished = torrents[5]; + + wait_for_alert(ses, torrent_finished_alert::alert_type, "ses"); + + // add_torrent should be ordered + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{4}); + + // test top and bottom + torrents[2].queue_position_top(); + torrents[1].queue_position_bottom(); + + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{4}); + + // test set pos + torrents[0].queue_position_set(queue_position_t{0}); + torrents[1].queue_position_set(queue_position_t{1}); + // torrent 2 should be get moved down by 0 and 1 to pos 2 + + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{4}); + + //test strange up and down commands + torrents[0].queue_position_up(); + torrents[4].queue_position_down(); + + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{4}); + + torrents[1].queue_position_up(); + torrents[3].queue_position_down(); + finished.queue_position_up(); + + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{4}); + + torrents[1].queue_position_down(); + torrents[3].queue_position_up(); + finished.queue_position_down(); + + + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{4}); + + // test set pos on not existing pos + torrents[3].queue_position_set(queue_position_t{10}); + finished.queue_position_set(queue_position_t{10}); + + TEST_EQUAL(finished.queue_position(), no_pos); + TEST_EQUAL(torrents[0].queue_position(), queue_position_t{0}); + TEST_EQUAL(torrents[1].queue_position(), queue_position_t{1}); + TEST_EQUAL(torrents[2].queue_position(), queue_position_t{2}); + TEST_EQUAL(torrents[4].queue_position(), queue_position_t{3}); + TEST_EQUAL(torrents[3].queue_position(), queue_position_t{4}); +} + +} // anonymous namespace + +TORRENT_TEST(queue) +{ + test_queue(add_torrent_params()); +} + +TORRENT_TEST(queue_paused) +{ + add_torrent_params p; + p.flags |= torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + test_queue(p); +} + +TORRENT_TEST(test_move_storage_no_metadata) +{ + lt::session ses(settings()); + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abababababababababababababababababababab"); + p.save_path = "save_path"; + torrent_handle h = ses.add_torrent(p); + + TEST_EQUAL(h.status().save_path, complete("save_path")); + + h.move_storage("save_path_1"); + + TEST_EQUAL(h.status().save_path, complete("save_path_1")); +} + +TORRENT_TEST(test_have_piece_no_metadata) +{ + lt::session ses(settings()); + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abababababababababababababababababababab"); + p.save_path = "save_path"; + torrent_handle h = ses.add_torrent(p); + + TEST_EQUAL(h.have_piece(piece_index_t{-1}), false); + TEST_EQUAL(h.have_piece(piece_index_t{0}), false); + TEST_EQUAL(h.have_piece(piece_index_t{100}), false); +} + +TORRENT_TEST(test_have_piece_out_of_range) +{ + lt::session ses(settings()); + + add_torrent_params p; + static std::array const file_sizes{{100000, 100000}}; + int const piece_size = 0x8000; + p.ti = make_torrent(file_sizes, piece_size); + p.save_path = "save_path"; + p.flags |= torrent_flags::seed_mode; + torrent_handle h = ses.add_torrent(p); + + TEST_EQUAL(h.have_piece(piece_index_t{-1}), false); + TEST_EQUAL(h.have_piece(piece_index_t{0}), true); + TEST_EQUAL(h.have_piece(piece_index_t{100}), false); +} + +TORRENT_TEST(test_read_piece_no_metadata) +{ + lt::session ses(settings()); + add_torrent_params p = parse_magnet_uri("magnet:?xt=urn:btih:abababababababababababababababababababab"); + p.save_path = "save_path"; + torrent_handle h = ses.add_torrent(p); + + h.read_piece(piece_index_t{-1}); + + alert const* a = wait_for_alert(ses, read_piece_alert::alert_type, "read_piece_alert"); + TEST_CHECK(a); + if (auto* rp = alert_cast(a)) + { + TEST_CHECK(rp->error == error_code(lt::errors::no_metadata, lt::libtorrent_category())); + } +} + +TORRENT_TEST(test_read_piece_out_of_range) +{ + lt::session ses(settings()); + + add_torrent_params p; + static std::array const file_sizes{{100000, 100000}}; + int const piece_size = 0x8000; + p.ti = make_torrent(file_sizes, piece_size); + p.save_path = "save_path"; + p.flags |= torrent_flags::seed_mode; + torrent_handle h = ses.add_torrent(p); + + h.read_piece(piece_index_t{-1}); + + alert const* a = wait_for_alert(ses, read_piece_alert::alert_type, "read_piece_alert"); + TEST_CHECK(a); + if (auto* rp = alert_cast(a)) + { + TEST_CHECK(rp->error == error_code(lt::errors::invalid_piece_index + , lt::libtorrent_category())); + } +} + +namespace { +int const piece_size = 0x4000 * 128; + +file_storage test_fs() +{ + file_storage fs; + fs.set_piece_length(piece_size); + fs.add_file("temp", 99999999999); + fs.set_num_pieces(int((fs.total_size() + piece_size - 1) / piece_size)); + return fs; +} +} + +TORRENT_TEST(test_calc_bytes_pieces) +{ + auto const fs = test_fs(); + TEST_EQUAL(calc_bytes(fs, piece_count{2, 0, false}), 2 * piece_size); +} + +TORRENT_TEST(test_calc_bytes_pieces_last) +{ + auto const fs = test_fs(); + TEST_EQUAL(calc_bytes(fs, piece_count{2, 0, true}), piece_size + fs.total_size() % piece_size); +} + +TORRENT_TEST(test_calc_bytes_no_pieces) +{ + auto const fs = test_fs(); + TEST_EQUAL(calc_bytes(fs, piece_count{0, 0, false}), 0); +} + +TORRENT_TEST(test_calc_bytes_all_pieces) +{ + auto const fs = test_fs(); + TEST_EQUAL(calc_bytes(fs, piece_count{fs.num_pieces(), 0, true}), fs.total_size()); +} + +TORRENT_TEST(test_calc_bytes_all_pieces_one_pad) +{ + auto const fs = test_fs(); + TEST_EQUAL(calc_bytes(fs, piece_count{fs.num_pieces(), 1, true}), fs.total_size() - 0x4000); +} + +TORRENT_TEST(test_calc_bytes_all_pieces_two_pad) +{ + auto const fs = test_fs(); + TEST_EQUAL(calc_bytes(fs, piece_count{fs.num_pieces(), 2, true}), fs.total_size() - 2 * 0x4000); +} + +#if TORRENT_HAS_SYMLINK +TORRENT_TEST(symlinks_restore) +{ + // downloading test torrent with symlinks + std::string const work_dir = current_working_directory(); + lt::add_torrent_params p; + p.ti = std::make_shared(combine_path( + combine_path(parent_path(work_dir), "test_torrents"), "symlink2.torrent")); + p.flags &= ~lt::torrent_flags::paused; + p.save_path = work_dir; + settings_pack pack = settings(); + pack.set_int(libtorrent::settings_pack::alert_mask, libtorrent::alert_category::status | libtorrent::alert_category::error); + lt::session ses(std::move(pack)); + ses.add_torrent(p); + + wait_for_alert(ses, torrent_checked_alert::alert_type, "torrent_checked_alert"); + + std::string const f = combine_path(combine_path(work_dir, "Some.framework"), "SDL2"); + TEST_CHECK(aux::get_file_attributes(f) & file_storage::flag_symlink); + TEST_CHECK(aux::get_symlink_path(f) == "Versions/A/SDL2"); +} +#endif diff --git a/test/test_torrent_info.cpp b/test/test_torrent_info.cpp new file mode 100644 index 0000000..4b8f514 --- /dev/null +++ b/test/test_torrent_info.cpp @@ -0,0 +1,1135 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/file_storage.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/announce_entry.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix +#include "libtorrent/hex.hpp" // to_hex + +#include + +using namespace lt; + +#ifndef TORRENT_DISABLE_MUTABLE_TORRENTS +TORRENT_TEST(mutable_torrents) +{ + file_storage fs; + + fs.add_file("test/temporary.txt", 0x4000); + + lt::create_torrent t(fs, 0x4000); + + // calculate the hash for all pieces + sha1_hash ph; + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + t.add_collection("collection1"); + t.add_collection("collection2"); + + t.add_similar_torrent(sha1_hash("abababababababababab")); + t.add_similar_torrent(sha1_hash("babababababababababa")); + + std::vector tmp; + std::back_insert_iterator> out(tmp); + + entry tor = t.generate(); + bencode(out, tor); + + torrent_info ti(tmp, from_span); + + std::vector similar; + similar.push_back(sha1_hash("abababababababababab")); + similar.push_back(sha1_hash("babababababababababa")); + + std::vector collections; + collections.push_back("collection1"); + collections.push_back("collection2"); + + TEST_CHECK(similar == ti.similar_torrents()); + TEST_CHECK(collections == ti.collections()); +} +#endif + +namespace { + +struct test_torrent_t +{ + char const* file; +}; + +using namespace lt; + +static test_torrent_t test_torrents[] = +{ + { "base.torrent" }, + { "empty_path.torrent" }, + { "parent_path.torrent" }, + { "hidden_parent_path.torrent" }, + { "single_multi_file.torrent" }, + { "slash_path.torrent" }, + { "slash_path2.torrent" }, + { "slash_path3.torrent" }, + { "backslash_path.torrent" }, + { "url_list.torrent" }, + { "url_list2.torrent" }, + { "url_list3.torrent" }, + { "httpseed.torrent" }, + { "empty_httpseed.torrent" }, + { "long_name.torrent" }, + { "whitespace_url.torrent" }, + { "duplicate_files.torrent" }, + { "pad_file.torrent" }, + { "creation_date.torrent" }, + { "no_creation_date.torrent" }, + { "url_seed.torrent" }, + { "url_seed_multi.torrent" }, + { "url_seed_multi_single_file.torrent" }, + { "url_seed_multi_space.torrent" }, + { "url_seed_multi_space_nolist.torrent" }, + { "root_hash.torrent" }, + { "empty_path_multi.torrent" }, + { "duplicate_web_seeds.torrent" }, + { "invalid_name2.torrent" }, + { "invalid_name3.torrent" }, + { "symlink1.torrent" }, + { "symlink2.torrent" }, + { "unordered.torrent" }, + { "symlink_zero_size.torrent" }, + { "pad_file_no_path.torrent" }, + { "large.torrent" }, + { "absolute_filename.torrent" }, + { "invalid_filename.torrent" }, + { "invalid_filename2.torrent" }, + { "overlapping_symlinks.torrent" }, +}; + +struct test_failing_torrent_t +{ + char const* file; + error_code error; // the expected error +}; + +test_failing_torrent_t test_error_torrents[] = +{ + { "missing_piece_len.torrent", errors::torrent_missing_piece_length }, + { "invalid_piece_len.torrent", errors::torrent_missing_piece_length }, + { "negative_piece_len.torrent", errors::torrent_missing_piece_length }, + { "no_name.torrent", errors::torrent_missing_name }, + { "bad_name.torrent", errors::torrent_missing_name }, + { "invalid_name.torrent", errors::torrent_missing_name }, + { "invalid_info.torrent", errors::torrent_missing_info }, + { "string.torrent", errors::torrent_is_no_dict }, + { "negative_size.torrent", errors::torrent_invalid_length }, + { "negative_file_size.torrent", errors::torrent_invalid_length }, + { "invalid_path_list.torrent", errors::torrent_invalid_name}, + { "missing_path_list.torrent", errors::torrent_missing_name }, + { "invalid_pieces.torrent", errors::torrent_missing_pieces }, + { "unaligned_pieces.torrent", errors::torrent_invalid_hashes }, + { "invalid_root_hash.torrent", errors::torrent_invalid_hashes }, + { "invalid_root_hash2.torrent", errors::torrent_missing_pieces }, + { "invalid_merkle.torrent", errors::no_files_in_torrent}, + { "invalid_file_size.torrent", errors::torrent_invalid_length }, + { "invalid_symlink.torrent", errors::torrent_invalid_name }, + { "many_pieces.torrent", errors::too_many_pieces_in_torrent }, + { "no_files.torrent", errors::no_files_in_torrent}, +}; + +} // anonymous namespace + +// TODO: test remap_files +// TODO: merkle torrents. specifically torrent_info::add_merkle_nodes and torrent with "root hash" +// TODO: torrent with 'p' (padfile) attribute +// TODO: torrent with 'h' (hidden) attribute +// TODO: torrent with 'x' (executable) attribute +// TODO: torrent with 'l' (symlink) attribute +// TODO: creating a merkle torrent (torrent_info::build_merkle_list) +// TODO: torrent with multiple trackers in multiple tiers, making sure we +// shuffle them (how do you test shuffling?, load it multiple times and make +// sure it's in different order at least once) +// TODO: torrents with a zero-length name +// TODO: torrents with a merkle tree and add_merkle_nodes +// TODO: torrent with a non-dictionary info-section +// TODO: torrents with DHT nodes +// TODO: torrent with url-list as a single string +// TODO: torrent with http seed as a single string +// TODO: torrent with a comment +// TODO: torrent with an SSL cert +// TODO: torrent with attributes (executable and hidden) +// TODO: torrent_info::add_tracker +// TODO: torrent_info constructor that takes an invalid bencoded buffer +// TODO: verify_encoding with a string that triggers character replacement + +TORRENT_TEST(url_list_and_httpseeds) +{ + entry info; + info["pieces"] = "aaaaaaaaaaaaaaaaaaaa"; + info["name.utf-8"] = "test1"; + info["name"] = "test__"; + info["piece length"] = 16 * 1024; + info["length"] = 3245; + entry::list_type l; + l.push_back(entry("http://foo.com/bar1")); + l.push_back(entry("http://foo.com/bar1")); + l.push_back(entry("http://foo.com/bar2")); + entry const e(l); + entry torrent; + torrent["url-list"] = e; + torrent["httpseeds"] = e; + torrent["info"] = info; + std::vector buf; + bencode(std::back_inserter(buf), torrent); + torrent_info ti(buf, from_span); + TEST_EQUAL(ti.web_seeds().size(), 4); +} + +TORRENT_TEST(add_url_seed) +{ + torrent_info ti(sha1_hash(" ")); + TEST_EQUAL(ti.web_seeds().size(), 0); + + ti.add_url_seed("http://test.com"); + + TEST_EQUAL(ti.web_seeds().size(), 1); + web_seed_entry we = ti.web_seeds()[0]; + TEST_EQUAL(we.type, web_seed_entry::url_seed); + TEST_EQUAL(we.url, "http://test.com"); +} + +TORRENT_TEST(add_http_seed) +{ + torrent_info ti(sha1_hash(" ")); + TEST_EQUAL(ti.web_seeds().size(), 0); + + ti.add_http_seed("http://test.com"); + + TEST_EQUAL(ti.web_seeds().size(), 1); + web_seed_entry we = ti.web_seeds()[0]; + TEST_EQUAL(we.type, web_seed_entry::http_seed); + TEST_EQUAL(we.url, "http://test.com"); +} + +TORRENT_TEST(set_web_seeds) +{ + torrent_info ti(sha1_hash(" ")); + TEST_EQUAL(ti.web_seeds().size(), 0); + + std::vector seeds; + web_seed_entry e1("http://test1.com", web_seed_entry::url_seed); + seeds.push_back(e1); + web_seed_entry e2("http://test2com", web_seed_entry::http_seed); + seeds.push_back(e2); + + ti.set_web_seeds(seeds); + + TEST_EQUAL(ti.web_seeds().size(), 2); + TEST_CHECK(ti.web_seeds() == seeds); +} + +#ifdef TORRENT_WINDOWS +#define SEPARATOR "\\" +#else +#define SEPARATOR "/" +#endif + +TORRENT_TEST(sanitize_path_truncate) +{ + std::string path; + sanitize_append_path_element(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_"); + sanitize_append_path_element(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcde.test"); + TEST_EQUAL(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_" SEPARATOR + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_.test"); +} + +TORRENT_TEST(sanitize_path_truncate_utf) +{ + std::string path; + // msvc doesn't like unicode string literals, so we encode it as UTF-8 explicitly + sanitize_append_path_element(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi" "\xE2" "\x80" "\x94" "abcde.jpg"); + TEST_EQUAL(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi" "\xE2" "\x80" "\x94" ".jpg"); +} + +TORRENT_TEST(sanitize_path_trailing_dots) +{ + std::string path; + sanitize_append_path_element(path, "a"); + sanitize_append_path_element(path, "abc..."); + sanitize_append_path_element(path, "c"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "a" SEPARATOR "abc" SEPARATOR "c"); +#else + TEST_EQUAL(path, "a" SEPARATOR "abc..." SEPARATOR "c"); +#endif + + path.clear(); + sanitize_append_path_element(path, "abc..."); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc..."); +#endif + + path.clear(); + sanitize_append_path_element(path, "abc."); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc."); +#endif + + + path.clear(); + sanitize_append_path_element(path, "a. . ."); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "a"); +#else + TEST_EQUAL(path, "a. . ."); +#endif +} + +TORRENT_TEST(sanitize_path_trailing_spaces) +{ + std::string path; + sanitize_append_path_element(path, "a"); + sanitize_append_path_element(path, "abc "); + sanitize_append_path_element(path, "c"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "a" SEPARATOR "abc" SEPARATOR "c"); +#else + TEST_EQUAL(path, "a" SEPARATOR "abc " SEPARATOR "c"); +#endif + + path.clear(); + sanitize_append_path_element(path, "abc "); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc "); +#endif + + path.clear(); + sanitize_append_path_element(path, "abc "); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc "); +#endif +} + +TORRENT_TEST(sanitize_path) +{ + std::string path; + sanitize_append_path_element(path, "\0\0\xed\0\x80"); + TEST_EQUAL(path, "_"); + + path.clear(); + sanitize_append_path_element(path, "/a/"); + sanitize_append_path_element(path, "b"); + sanitize_append_path_element(path, "c"); + TEST_EQUAL(path, "a" SEPARATOR "b" SEPARATOR "c"); + + path.clear(); + sanitize_append_path_element(path, "a...b"); + TEST_EQUAL(path, "a...b"); + + path.clear(); + sanitize_append_path_element(path, "a"); + sanitize_append_path_element(path, ".."); + sanitize_append_path_element(path, "c"); + TEST_EQUAL(path, "a" SEPARATOR "c"); + + path.clear(); + sanitize_append_path_element(path, "a"); + sanitize_append_path_element(path, ".."); + TEST_EQUAL(path, "a"); + + path.clear(); + sanitize_append_path_element(path, "/.."); + sanitize_append_path_element(path, "."); + sanitize_append_path_element(path, "c"); + TEST_EQUAL(path, "c"); + + path.clear(); + sanitize_append_path_element(path, "dev:"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "dev_"); +#else + TEST_EQUAL(path, "dev:"); +#endif + + path.clear(); + sanitize_append_path_element(path, "c:"); + sanitize_append_path_element(path, "b"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "c_" SEPARATOR "b"); +#else + TEST_EQUAL(path, "c:" SEPARATOR "b"); +#endif + + path.clear(); + sanitize_append_path_element(path, "c:"); + sanitize_append_path_element(path, "."); + sanitize_append_path_element(path, "c"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "c_" SEPARATOR "c"); +#else + TEST_EQUAL(path, "c:" SEPARATOR "c"); +#endif + + path.clear(); + sanitize_append_path_element(path, "\\c"); + sanitize_append_path_element(path, "."); + sanitize_append_path_element(path, "c"); + TEST_EQUAL(path, "c" SEPARATOR "c"); + + path.clear(); + sanitize_append_path_element(path, "\b"); + TEST_EQUAL(path, "_"); + + path.clear(); + sanitize_append_path_element(path, "\b"); + sanitize_append_path_element(path, "filename"); + TEST_EQUAL(path, "_" SEPARATOR "filename"); + + path.clear(); + sanitize_append_path_element(path, "filename"); + sanitize_append_path_element(path, "\b"); + TEST_EQUAL(path, "filename" SEPARATOR "_"); + + path.clear(); + sanitize_append_path_element(path, "abc"); + sanitize_append_path_element(path, ""); + TEST_EQUAL(path, "abc" SEPARATOR "_"); + + path.clear(); + sanitize_append_path_element(path, "abc"); + sanitize_append_path_element(path, " "); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "abc"); +#else + TEST_EQUAL(path, "abc" SEPARATOR " "); +#endif + + path.clear(); + sanitize_append_path_element(path, ""); + sanitize_append_path_element(path, "abc"); + TEST_EQUAL(path, "_" SEPARATOR "abc"); + + path.clear(); + sanitize_append_path_element(path, "\b?filename=4"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "__filename=4"); +#else + TEST_EQUAL(path, "_?filename=4"); +#endif + + path.clear(); + sanitize_append_path_element(path, "filename=4"); + TEST_EQUAL(path, "filename=4"); + + // valid 2-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xc2\xa1"); + TEST_EQUAL(path, "filename\xc2\xa1"); + + // truncated 2-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xc2"); + TEST_EQUAL(path, "filename_"); + + // valid 3-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xe2\x9f\xb9"); + TEST_EQUAL(path, "filename\xe2\x9f\xb9"); + + // truncated 3-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xe2\x9f"); + TEST_EQUAL(path, "filename_"); + + // truncated 3-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xe2"); + TEST_EQUAL(path, "filename_"); + + // valid 4-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xf0\x9f\x92\x88"); + TEST_EQUAL(path, "filename\xf0\x9f\x92\x88"); + + // truncated 4-byte sequence + path.clear(); + sanitize_append_path_element(path, "filename\xf0\x9f\x92"); + TEST_EQUAL(path, "filename_"); + + // 5-byte utf-8 sequence (not allowed) + path.clear(); + sanitize_append_path_element(path, "filename\xf8\x9f\x9f\x9f\x9f" "foobar"); + TEST_EQUAL(path, "filename_foobar"); + + // redundant (overlong) 2-byte sequence + // ascii code 0x2e encoded with a leading 0 + path.clear(); + sanitize_append_path_element(path, "filename\xc0\xae"); + TEST_EQUAL(path, "filename_"); + + // redundant (overlong) 3-byte sequence + // ascii code 0x2e encoded with two leading 0s + path.clear(); + sanitize_append_path_element(path, "filename\xe0\x80\xae"); + TEST_EQUAL(path, "filename_"); + + // redundant (overlong) 4-byte sequence + // ascii code 0x2e encoded with three leading 0s + path.clear(); + sanitize_append_path_element(path, "filename\xf0\x80\x80\xae"); + TEST_EQUAL(path, "filename_"); + + // a filename where every character is filtered is not replaced by an understcore + path.clear(); + sanitize_append_path_element(path, "//\\"); + TEST_EQUAL(path, ""); + + // make sure suspicious unicode characters are filtered out + path.clear(); + // that's utf-8 for U+200e LEFT-TO-RIGHT MARK + sanitize_append_path_element(path, "foo\xe2\x80\x8e" "bar"); + TEST_EQUAL(path, "foobar"); + + // make sure suspicious unicode characters are filtered out + path.clear(); + // that's utf-8 for U+202b RIGHT-TO-LEFT EMBEDDING + sanitize_append_path_element(path, "foo\xe2\x80\xab" "bar"); + TEST_EQUAL(path, "foobar"); +} + +TORRENT_TEST(sanitize_path_zeroes) +{ + std::string path; + sanitize_append_path_element(path, "\0foo"); + TEST_EQUAL(path, "_"); + + path.clear(); + sanitize_append_path_element(path, "\0\0\0\0"); + TEST_EQUAL(path, "_"); +} + +TORRENT_TEST(sanitize_path_colon) +{ + std::string path; + sanitize_append_path_element(path, "foo:bar"); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "foo_bar"); +#else + TEST_EQUAL(path, "foo:bar"); +#endif +} + +TORRENT_TEST(verify_encoding) +{ + // verify_encoding + std::string test = "\b?filename=4"; + TEST_CHECK(verify_encoding(test)); + TEST_CHECK(test == "\b?filename=4"); + + test = "filename=4"; + TEST_CHECK(verify_encoding(test)); + TEST_CHECK(test == "filename=4"); + + // valid 2-byte sequence + test = "filename\xc2\xa1"; + TEST_CHECK(verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename\xc2\xa1"); + + // truncated 2-byte sequence + test = "filename\xc2"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // valid 3-byte sequence + test = "filename\xe2\x9f\xb9"; + TEST_CHECK(verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename\xe2\x9f\xb9"); + + // truncated 3-byte sequence + test = "filename\xe2\x9f"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // truncated 3-byte sequence + test = "filename\xe2"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // valid 4-byte sequence + test = "filename\xf0\x9f\x92\x88"; + TEST_CHECK(verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename\xf0\x9f\x92\x88"); + + // truncated 4-byte sequence + test = "filename\xf0\x9f\x92"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // 5-byte utf-8 sequence (not allowed) + test = "filename\xf8\x9f\x9f\x9f\x9f""foobar"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_foobar"); + + // redundant (overlong) 2-byte sequence + // ascii code 0x2e encoded with a leading 0 + test = "filename\xc0\xae"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // redundant (overlong) 3-byte sequence + // ascii code 0x2e encoded with two leading 0s + test = "filename\xe0\x80\xae"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // redundant (overlong) 4-byte sequence + // ascii code 0x2e encoded with three leading 0s + test = "filename\xf0\x80\x80\xae"; + TEST_CHECK(!verify_encoding(test)); + std::printf("%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); + + // missing byte header + test = "filename\xed\0\x80"; + TEST_CHECK(!verify_encoding(test)); + fprintf(stdout, "%s\n", test.c_str()); + TEST_CHECK(test == "filename_"); +} + +TORRENT_TEST(parse_torrents) +{ + // test torrent parsing + + entry info; + info["pieces"] = "aaaaaaaaaaaaaaaaaaaa"; + info["name.utf-8"] = "test1"; + info["name"] = "test__"; + info["piece length"] = 16 * 1024; + info["length"] = 3245; + entry torrent; + torrent["info"] = info; + + std::vector buf; + bencode(std::back_inserter(buf), torrent); + torrent_info ti1(buf, from_span); + std::cout << ti1.name() << std::endl; + TEST_CHECK(ti1.name() == "test1"); + +#ifdef TORRENT_WINDOWS + info["name.utf-8"] = "c:/test1/test2/test3"; +#else + info["name.utf-8"] = "/test1/test2/test3"; +#endif + torrent["info"] = info; + buf.clear(); + bencode(std::back_inserter(buf), torrent); + torrent_info ti2(buf, from_span); + std::cout << ti2.name() << std::endl; +#ifdef TORRENT_WINDOWS + TEST_EQUAL(ti2.name(), "c_test1test2test3"); +#else + TEST_EQUAL(ti2.name(), "test1test2test3"); +#endif + + info["name.utf-8"] = "test2/../test3/.././../../test4"; + torrent["info"] = info; + buf.clear(); + bencode(std::back_inserter(buf), torrent); + torrent_info ti3(buf, from_span); + std::cout << ti3.name() << std::endl; + TEST_EQUAL(ti3.name(), "test2..test3.......test4"); + + std::string root_dir = parent_path(current_working_directory()); + for (auto const t : test_torrents) + { + std::printf("loading %s\n", t.file); + std::string filename = combine_path(combine_path(root_dir, "test_torrents") + , t.file); + error_code ec; + auto ti = std::make_shared(filename, ec); + TEST_CHECK(!ec); + if (ec) std::printf(" loading(\"%s\") -> failed %s\n", filename.c_str() + , ec.message().c_str()); + + if (t.file == "whitespace_url.torrent"_sv) + { + // make sure we trimmed the url + TEST_CHECK(ti->trackers().size() > 0); + if (ti->trackers().size() > 0) + TEST_CHECK(ti->trackers()[0].url == "udp://test.com/announce"); + } + else if (t.file == "duplicate_files.torrent"_sv) + { + // make sure we disambiguated the files + TEST_EQUAL(ti->num_files(), 2); + TEST_CHECK(ti->files().file_path(file_index_t{0}) == combine_path(combine_path("temp", "foo"), "bar.txt")); + TEST_CHECK(ti->files().file_path(file_index_t{1}) == combine_path(combine_path("temp", "foo"), "bar.1.txt")); + } + else if (t.file == "pad_file.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(bool(ti->files().file_flags(file_index_t{0}) & file_storage::flag_pad_file), false); + TEST_EQUAL(bool(ti->files().file_flags(file_index_t{1}) & file_storage::flag_pad_file), true); + } + else if (t.file == "creation_date.torrent"_sv) + { + TEST_EQUAL(ti->creation_date(), 1234567); + } + else if (t.file == "duplicate_web_seeds.torrent"_sv) + { + TEST_EQUAL(ti->web_seeds().size(), 3); + } + else if (t.file == "no_creation_date.torrent"_sv) + { + TEST_CHECK(!ti->creation_date()); + } + else if (t.file == "url_seed.torrent"_sv) + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/file"); +#if TORRENT_ABI_VERSION == 1 + TEST_EQUAL(ti->http_seeds().size(), 0); + TEST_EQUAL(ti->url_seeds().size(), 1); + TEST_EQUAL(ti->url_seeds()[0], "http://test.com/file"); +#endif + } + else if (t.file == "url_seed_multi.torrent"_sv) + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/file/"); +#if TORRENT_ABI_VERSION == 1 + TEST_EQUAL(ti->http_seeds().size(), 0); + TEST_EQUAL(ti->url_seeds().size(), 1); + TEST_EQUAL(ti->url_seeds()[0], "http://test.com/file/"); +#endif + } + else if (t.file == "url_seed_multi_single_file.torrent"_sv) + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/file/temp/foo/bar.txt"); + } + else if (t.file == "url_seed_multi_space.torrent"_sv + || t.file == "url_seed_multi_space_nolist.torrent"_sv) + { + TEST_EQUAL(ti->web_seeds().size(), 1); + TEST_EQUAL(ti->web_seeds()[0].url, "http://test.com/test%20file/foo%20bar/"); +#if TORRENT_ABI_VERSION == 1 + TEST_EQUAL(ti->http_seeds().size(), 0); + TEST_EQUAL(ti->url_seeds().size(), 1); + TEST_EQUAL(ti->url_seeds()[0], "http://test.com/test%20file/foo%20bar/"); +#endif + } + else if (t.file == "invalid_name2.torrent"_sv) + { + // if, after all invalid characters are removed from the name, it ends up + // being empty, it's set to the info-hash. Some torrents also have an empty name + // in which case it's also set to the info-hash + TEST_EQUAL(ti->name(), "b61560c2918f463768cd122b6d2fdd47b77bdb35"); + } + else if (t.file == "invalid_name3.torrent"_sv) + { + // windows does not allow trailing spaces in filenames +#ifdef TORRENT_WINDOWS + TEST_EQUAL(ti->name(), "foobar"); +#else + TEST_EQUAL(ti->name(), "foobar "); +#endif + } + else if (t.file == "symlink1.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().symlink(file_index_t{1}), "temp" SEPARATOR "a" SEPARATOR "b" SEPARATOR "bar"); + } + else if (t.file == "symlink2.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 5); + TEST_EQUAL(ti->files().symlink(file_index_t{0}), "Some.framework" SEPARATOR "Versions" SEPARATOR "A" SEPARATOR "SDL2"); + TEST_EQUAL(ti->files().symlink(file_index_t{4}), "Some.framework" SEPARATOR "Versions" SEPARATOR "A"); + } + else if (t.file == "slash_path.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "temp" SEPARATOR "bar"); + } + else if (t.file == "slash_path2.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "temp" SEPARATOR "abc....def" SEPARATOR "bar"); + } + else if (t.file == "slash_path3.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 1); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), "temp....abc"); + } + else if (t.file == "symlink_zero_size.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().symlink(file_index_t(1)), "temp" SEPARATOR "a" SEPARATOR "b" SEPARATOR "bar"); + } + else if (t.file == "pad_file_no_path.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().file_path(file_index_t{1}), combine_path(".pad", "0")); + } + else if (t.file == "absolute_filename.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 2); + TEST_EQUAL(ti->files().file_path(file_index_t{0}), combine_path("temp", "abcde")); + TEST_EQUAL(ti->files().file_path(file_index_t{1}), combine_path("temp", "foobar")); + } + else if (t.file == "invalid_filename.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 2); + } + else if (t.file == "invalid_filename2.torrent"_sv) + { + TEST_EQUAL(ti->num_files(), 3); + } + else if (t.file == "overlapping_symlinks.torrent"_sv) + { + TEST_CHECK(ti->num_files() > 3); + TEST_EQUAL(ti->files().symlink(file_index_t{0}), "SDL2.framework" SEPARATOR "Versions" SEPARATOR "Current" SEPARATOR "Headers"); + TEST_EQUAL(ti->files().symlink(file_index_t{1}), "SDL2.framework" SEPARATOR "Versions" SEPARATOR "Current" SEPARATOR "Resources"); + TEST_EQUAL(ti->files().symlink(file_index_t{2}), "SDL2.framework" SEPARATOR "Versions" SEPARATOR "Current" SEPARATOR "SDL2"); + } + + file_storage const& fs = ti->files(); + for (file_index_t idx{0}; idx != file_index_t(fs.num_files()); ++idx) + { + piece_index_t const first = ti->map_file(idx, 0, 0).piece; + piece_index_t const last = ti->map_file(idx, std::max(fs.file_size(idx)-1, std::int64_t(0)), 0).piece; + file_flags_t const flags = fs.file_flags(idx); + sha1_hash const ih = fs.hash(idx); + std::printf(" %11" PRId64 " %c%c%c%c [ %4d, %4d ] %7u %s %s %s%s\n" + , fs.file_size(idx) + , (flags & file_storage::flag_pad_file)?'p':'-' + , (flags & file_storage::flag_executable)?'x':'-' + , (flags & file_storage::flag_hidden)?'h':'-' + , (flags & file_storage::flag_symlink)?'l':'-' + , static_cast(first), static_cast(last) + , std::uint32_t(fs.mtime(idx)) + , ih != sha1_hash(nullptr) ? aux::to_hex(ih).c_str() : "" + , fs.file_path(idx).c_str() + , flags & file_storage::flag_symlink ? "-> ": "" + , flags & file_storage::flag_symlink ? fs.symlink(idx).c_str() : ""); + } + } + + for (int i = 0; i < int(sizeof(test_error_torrents)/sizeof(test_error_torrents[0])); ++i) + { + error_code ec; + std::printf("loading %s\n", test_error_torrents[i].file); + auto ti = std::make_shared(combine_path( + combine_path(root_dir, "test_torrents"), test_error_torrents[i].file), ec); + std::printf("E: \"%s\"\nexpected: \"%s\"\n", ec.message().c_str() + , test_error_torrents[i].error.message().c_str()); + TEST_CHECK(ec.message() == test_error_torrents[i].error.message()); + TEST_EQUAL(ti->is_valid(), false); + } +} + +namespace { + +struct file_t +{ + std::string filename; + int size; + file_flags_t flags; + string_view expected_filename; +}; + +std::vector> const test_cases +{ + { + {"test/temporary.txt", 0x4000, {}, "test/temporary.txt"}, + {"test/Temporary.txt", 0x4000, {}, "test/Temporary.1.txt"}, + {"test/TeMPorArY.txT", 0x4000, {}, "test/TeMPorArY.2.txT"}, + // a file with the same name in a seprate directory is fine + {"test/test/TEMPORARY.TXT", 0x4000, {}, "test/test/TEMPORARY.TXT"}, + }, + { + {"test/b.exe", 0x4000, {}, "test/b.exe"}, + // duplicate of b.exe + {"test/B.ExE", 0x4000, {}, "test/B.1.ExE"}, + // duplicate of b.exe + {"test/B.exe", 0x4000, {}, "test/B.2.exe"}, + {"test/filler", 0x4000, {}, "test/filler"}, + }, + { + {"test/a/b/c/d/e/f/g/h/i/j/k/l/m", 0x4000, {}, "test/a/b/c/d/e/f/g/h/i/j/k/l/m"}, + {"test/a", 0x4000, {}, "test/a.1"}, + {"test/a/b", 0x4000, {}, "test/a/b.1"}, + {"test/a/b/c", 0x4000, {}, "test/a/b/c.1"}, + {"test/a/b/c/d", 0x4000, {}, "test/a/b/c/d.1"}, + {"test/a/b/c/d/e", 0x4000, {}, "test/a/b/c/d/e.1"}, + {"test/a/b/c/d/e/f", 0x4000, {}, "test/a/b/c/d/e/f.1"}, + {"test/a/b/c/d/e/f/g", 0x4000, {}, "test/a/b/c/d/e/f/g.1"}, + {"test/a/b/c/d/e/f/g/h", 0x4000, {}, "test/a/b/c/d/e/f/g/h.1"}, + {"test/a/b/c/d/e/f/g/h/i", 0x4000, {}, "test/a/b/c/d/e/f/g/h/i.1"}, + {"test/a/b/c/d/e/f/g/h/i/j", 0x4000, {}, "test/a/b/c/d/e/f/g/h/i/j.1"}, + }, + { + // it doesn't matter whether the file comes before the directory, + // directories take precedence + {"test/a", 0x4000, {}, "test/a.1"}, + {"test/a/b", 0x4000, {}, "test/a/b"}, + }, + { + {"test/A/tmp", 0x4000, {}, "test/A/tmp"}, + // a file may not have the same name as a directory + {"test/a", 0x4000, {}, "test/a.1"}, + // duplicate of directory a + {"test/A", 0x4000, {}, "test/A.2"}, + {"test/filler", 0x4000, {}, "test/filler"}, + }, + { + // a subset of this path collides with the next filename + {"test/long/path/name/that/collides", 0x4000, {}, "test/long/path/name/that/collides"}, + // so this file needs to be renamed, to not collide with the path name + {"test/long/path", 0x4000, {}, "test/long/path.1"}, + {"test/filler-1", 0x4000, {}, "test/filler-1"}, + {"test/filler-2", 0x4000, {}, "test/filler-2"}, + }, + { + // pad files are allowed to collide, as long as they have the same size + {"test/.pad/1234", 0x4000, file_storage::flag_pad_file, "test/.pad/1234"}, + {"test/filler-1", 0x4000, {}, "test/filler-1"}, + {"test/.pad/1234", 0x4000, file_storage::flag_pad_file, "test/.pad/1234"}, + {"test/filler-2", 0x4000, {}, "test/filler-2"}, + }, + { + // pad files of different sizes are NOT allowed to collide + {"test/.pad/1234", 0x8000, file_storage::flag_pad_file, "test/.pad/1234"}, + {"test/filler-1", 0x4000, {}, "test/filler-1"}, + {"test/.pad/1234", 0x4000, file_storage::flag_pad_file, "test/.pad/1234.1"}, + {"test/filler-2", 0x4000, {}, "test/filler-2"}, + }, + { + // pad files are NOT allowed to collide with normal files + {"test/.pad/1234", 0x4000, {}, "test/.pad/1234"}, + {"test/filler-1", 0x4000, {}, "test/filler-1"}, + {"test/.pad/1234", 0x4000, file_storage::flag_pad_file, "test/.pad/1234.1"}, + {"test/filler-2", 0x4000, {}, "test/filler-2"}, + }, + { + // normal files are NOT allowed to collide with pad files + {"test/.pad/1234", 0x4000, file_storage::flag_pad_file, "test/.pad/1234"}, + {"test/filler-1", 0x4000, {}, "test/filler-1"}, + {"test/.pad/1234", 0x4000, {}, "test/.pad/1234.1"}, + {"test/filler-2", 0x4000, {}, "test/filler-2"}, + }, + { + // pad files are NOT allowed to collide with directories + {"test/.pad/1234", 0x4000, file_storage::flag_pad_file, "test/.pad/1234.1"}, + {"test/filler-1", 0x4000, {}, "test/filler-1"}, + {"test/.pad/1234/filler-2", 0x4000, {}, "test/.pad/1234/filler-2"}, + }, +}; + +void test_resolve_duplicates(aux::vector const& test) +{ + file_storage fs; + for (auto const& f : test) fs.add_file(f.filename, f.size, f.flags); + + lt::create_torrent t(fs, 0x4000); + + // calculate the hash for all pieces + sha1_hash ph; + for (auto const i : fs.piece_range()) + t.set_hash(i, ph); + + std::vector tmp; + std::back_insert_iterator> out(tmp); + + entry tor = t.generate(); + bencode(out, tor); + + torrent_info ti(tmp, from_span); + for (auto const i : fs.file_range()) + { + std::string p = ti.files().file_path(i); + convert_path_to_posix(p); + std::printf("%s == %s\n", p.c_str(), test[i].expected_filename.to_string().c_str()); + + TEST_EQUAL(p, test[i].expected_filename); + } +} + +} // anonymous namespace + +TORRENT_TEST(resolve_duplicates) +{ + for (auto const& t : test_cases) + test_resolve_duplicates(t); +} + +TORRENT_TEST(empty_file) +{ + error_code ec; + auto ti = std::make_shared("", ec, from_span); + TEST_CHECK(ec); +} + +TORRENT_TEST(empty_file2) +{ + try + { + auto ti = std::make_shared("", from_span); + TEST_ERROR("expected exception thrown"); + } + catch (system_error const& e) + { + std::printf("Expected error: %s\n", e.code().message().c_str()); + } +} + +TORRENT_TEST(copy) +{ + using namespace lt; + + std::shared_ptr a = std::make_shared( + combine_path(parent_path(current_working_directory()) + , combine_path("test_torrents", "sample.torrent"))); + + aux::vector expected_files = + { + "sample/text_file2.txt", + "sample/.____padding_file/0", + "sample/text_file.txt", + }; + + aux::vector file_hashes = + { + sha1_hash(nullptr), + sha1_hash(nullptr), + sha1_hash("abababababababababab") + }; + + file_storage const& fs = a->files(); + for (auto const i : fs.file_range()) + { + std::string p = fs.file_path(i); + convert_path_to_posix(p); + TEST_EQUAL(p, expected_files[i]); + std::printf("%s\n", p.c_str()); + + TEST_EQUAL(a->files().hash(i), file_hashes[i]); + } + + // copy the torrent_info object + std::shared_ptr b = std::make_shared(*a); + + // clear out the buffer for a, just to make sure b doesn't have any + // references into it by mistake + int s = a->metadata_size(); + std::memset(a->metadata().get(), 0, std::size_t(s)); + + a.reset(); + + TEST_EQUAL(b->num_files(), 3); + + file_storage const& fs2 = b->files(); + for (auto const i : fs2.file_range()) + { + std::string p = fs2.file_path(i); + convert_path_to_posix(p); + TEST_EQUAL(p, expected_files[i]); + std::printf("%s\n", p.c_str()); + + TEST_EQUAL(fs2.hash(i), file_hashes[i]); + } +} + +struct A +{ + int val; +}; + +TORRENT_TEST(copy_ptr) +{ + copy_ptr a(new A{4}); + copy_ptr b(a); + + TEST_EQUAL(a->val, b->val); + TEST_CHECK(&*a != &*b); + a->val = 5; + TEST_EQUAL(b->val, 4); +} diff --git a/test/test_torrents/absolute_filename.torrent b/test/test_torrents/absolute_filename.torrent new file mode 100644 index 0000000..b5c8a7c --- /dev/null +++ b/test/test_torrents/absolute_filename.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl5:abcdeeed6:lengthi5e4:pathl7:/foobareee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/backslash_path.torrent b/test/test_torrents/backslash_path.torrent new file mode 100644 index 0000000..c3d61a5 --- /dev/null +++ b/test/test_torrents/backslash_path.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl1:\1:\3:bareee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/bad_name.torrent b/test/test_torrents/bad_name.torrent new file mode 100644 index 0000000000000000000000000000000000000000..2dbaf91f889da1cdf734c3183660ed565d0c82a6 GIT binary patch literal 375 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 literal 0 HcmV?d00001 diff --git a/test/test_torrents/long_name.torrent b/test/test_torrents/long_name.torrent new file mode 100644 index 0000000..6664c71 --- /dev/null +++ b/test/test_torrents/long_name.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi425e4:name300:abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0.2345678912:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/many_pieces.torrent b/test/test_torrents/many_pieces.torrent new file mode 100644 index 0000000..c3bcf85 --- /dev/null +++ b/test/test_torrents/many_pieces.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi1759218597889e4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/missing_path_list.torrent b/test/test_torrents/missing_path_list.torrent new file mode 100644 index 0000000..bc4a365 --- /dev/null +++ b/test/test_torrents/missing_path_list.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425eed6:lengthi425e4:pathl3:foo7:var.txteee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/missing_piece_len.torrent b/test/test_torrents/missing_piece_len.torrent new file mode 100644 index 0000000..3078970 --- /dev/null +++ b/test/test_torrents/missing_piece_len.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name4:temp6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/negative_file_size.torrent b/test/test_torrents/negative_file_size.torrent new file mode 100644 index 0000000..52eec05 --- /dev/null +++ b/test/test_torrents/negative_file_size.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld4:pathl3:foo7:bar.txte6:lengthi-45eed4:pathl3:foo7:var.txte6:lengthi24124eee4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/negative_piece_len.torrent b/test/test_torrents/negative_piece_len.torrent new file mode 100644 index 0000000..08e0864 --- /dev/null +++ b/test/test_torrents/negative_piece_len.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name4:temp12:piece lengthi-16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/negative_size.torrent b/test/test_torrents/negative_size.torrent new file mode 100644 index 0000000..539a2e0 --- /dev/null +++ b/test/test_torrents/negative_size.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod6:lengthi-425e4:name4:temp12:piece lengthi16384e6:pieces20:cdcdcdcdcdcdcdcdcdcdee diff --git a/test/test_torrents/no_creation_date.torrent b/test/test_torrents/no_creation_date.torrent new file mode 100644 index 0000000..21de7b3 --- /dev/null +++ b/test/test_torrents/no_creation_date.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee4:name4:temp12:piece lengthi16384e6:pieces20:01234567890123456789ee diff --git a/test/test_torrents/no_files.torrent b/test/test_torrents/no_files.torrent new file mode 100644 index 0000000..b27bab6 --- /dev/null +++ b/test/test_torrents/no_files.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesle4:name4:temp12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/no_name.torrent b/test/test_torrents/no_name.torrent new file mode 100644 index 0000000..a07828a --- /dev/null +++ b/test/test_torrents/no_name.torrent @@ -0,0 +1 @@ +d10:created by10:libtorrent13:creation datei1359599503e4:infod5:filesld6:lengthi425e4:pathl3:foo7:bar.txteed6:lengthi425e4:pathl3:foo7:var.txteee12:piece lengthi16384e6:pieces20:‚ž¼Œ&¾ÇJW›}ÜA4u,·¼‘‡ee diff --git a/test/test_torrents/overlapping_symlinks.torrent b/test/test_torrents/overlapping_symlinks.torrent new file mode 100644 index 0000000000000000000000000000000000000000..43fc68a4e59ca4672d86eae794e27cbf2b6020e2 GIT binary patch literal 6119 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 + +using namespace lt; + +// TODO: test scrape requests +// TODO: test parse peers6 +// TODO: test parse tracker-id +// TODO: test parse failure-reason +// TODO: test all failure paths, including +// invalid bencoding +// not a dictionary +// no files entry in scrape response +// no info-hash entry in scrape response +// malformed peers in peer list of dictionaries +// uneven number of bytes in peers and peers6 string responses + +TORRENT_TEST(parse_hostname_peers) +{ + char const response[] = "d5:peersld7:peer id20:aaaaaaaaaaaaaaaaaaaa" + "2:ip13:test_hostname4:porti1000eed" + "7:peer id20:bbbbabaababababababa2:ip12:another_host4:porti1001eeee"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 2); + if (resp.peers.size() == 2) + { + peer_entry const& e0 = resp.peers[0]; + peer_entry const& e1 = resp.peers[1]; + TEST_EQUAL(e0.hostname, "test_hostname"); + TEST_EQUAL(e0.port, 1000); + TEST_EQUAL(e0.pid, peer_id("aaaaaaaaaaaaaaaaaaaa")); + + TEST_EQUAL(e1.hostname, "another_host"); + TEST_EQUAL(e1.port, 1001); + TEST_EQUAL(e1.pid, peer_id("bbbbabaababababababa")); + } +} + +TORRENT_TEST(parse_peers4) +{ + char const response[] = "d5:peers12:\x01\x02\x03\x04\x30\x10" + "\x09\x08\x07\x06\x20\x10" "e"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers4.size(), 2); + if (resp.peers.size() == 2) + { + ipv4_peer_entry const& e0 = resp.peers4[0]; + ipv4_peer_entry const& e1 = resp.peers4[1]; + TEST_CHECK(e0.ip == addr4("1.2.3.4").to_bytes()); + TEST_EQUAL(e0.port, 0x3010); + + TEST_CHECK(e1.ip == addr4("9.8.7.6").to_bytes()); + TEST_EQUAL(e1.port, 0x2010); + } +} + +#if TORRENT_USE_I2P +TORRENT_TEST(parse_i2p_peers) +{ + // d8:completei8e10:incompletei4e8:intervali3600e5:peers352: ... + std::uint8_t const response[] = { 0x64, 0x38, 0x3a, 0x63, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x69, 0x38, 0x65, 0x31, 0x30, + 0x3a, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x69, 0x34, 0x65, 0x38, 0x3a, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x69, 0x33, 0x36, 0x30, 0x30, 0x65, + 0x35, 0x3a, 0x70, 0x65, 0x65, 0x72, 0x73, 0x33, 0x35, 0x32, + 0x3a, 0xb1, 0x84, 0xe0, 0x96, 0x1f, 0xdb, 0xf2, 0xc9, 0xb0, + 0x53, 0x9a, 0x31, 0xa5, 0x35, 0xcd, 0xe8, 0x59, 0xa0, 0x7c, + 0xcd, 0xf2, 0x7c, 0x81, 0x81, 0x02, 0x11, 0x7b, 0xb4, 0x2a, + 0xd1, 0x20, 0x87, 0xd6, 0x1b, 0x06, 0x4c, 0xbb, 0x4c, 0x4e, + 0x30, 0xf9, 0xa3, 0x5d, 0x58, 0xa0, 0xa5, 0x10, 0x48, 0xfa, + 0x9b, 0x3b, 0x10, 0x86, 0x43, 0x5c, 0x2e, 0xa2, 0xa6, 0x22, + 0x31, 0xd0, 0x63, 0x6a, 0xfb, 0x4f, 0x25, 0x5b, 0xe2, 0x29, + 0xbc, 0xcc, 0xa0, 0x1a, 0x0a, 0x30, 0x45, 0x32, 0xa1, 0xc8, + 0x49, 0xf7, 0x9e, 0x03, 0xfd, 0x34, 0x80, 0x9a, 0x5b, 0xe9, + 0x78, 0x04, 0x48, 0x4e, 0xbd, 0xc0, 0x5c, 0xdd, 0x4f, 0xf8, + 0xbd, 0xc8, 0x4c, 0x4b, 0xcc, 0xf6, 0x25, 0x1b, 0xb3, 0x4d, + 0xc0, 0x91, 0xb1, 0x4b, 0xb6, 0xbd, 0x95, 0xb7, 0x8e, 0x88, + 0x79, 0xa8, 0xaa, 0x83, 0xa5, 0x7e, 0xec, 0x17, 0x60, 0x8d, + 0x1d, 0xe2, 0xbe, 0x16, 0x35, 0x83, 0x25, 0xee, 0xe4, 0xd5, + 0xbe, 0x54, 0x7b, 0xc8, 0x00, 0xdc, 0x5d, 0x56, 0xc7, 0x29, + 0xd2, 0x1e, 0x6d, 0x7a, 0xfb, 0xfc, 0xef, 0x36, 0x05, 0x8a, + 0xd0, 0xa7, 0x05, 0x4c, 0x11, 0xd5, 0x50, 0xe6, 0x2d, 0x7b, + 0xe0, 0x7d, 0x84, 0xda, 0x47, 0x48, 0x9d, 0xf9, 0x77, 0xa2, + 0xc7, 0x78, 0x90, 0xa4, 0xb5, 0x05, 0xf4, 0x95, 0xea, 0x36, + 0x7b, 0x92, 0x8c, 0x5b, 0xf7, 0x8b, 0x18, 0x94, 0x2c, 0x2f, + 0x88, 0xcf, 0xf8, 0xec, 0x5c, 0x52, 0xa8, 0x98, 0x8f, 0xd1, + 0xd3, 0xf0, 0xd8, 0x63, 0x19, 0x73, 0x33, 0xd7, 0xeb, 0x1f, + 0x87, 0x1c, 0x9f, 0x5b, 0xce, 0xe4, 0xd0, 0x15, 0x4e, 0x38, + 0xb7, 0xe3, 0xbd, 0x93, 0x64, 0xe2, 0x15, 0x3d, 0xfc, 0x56, + 0x4f, 0xd4, 0x19, 0x62, 0xe0, 0xb7, 0x59, 0x24, 0xff, 0x7f, + 0x32, 0xdf, 0x56, 0xa5, 0x62, 0x42, 0x87, 0xa3, 0x04, 0xec, + 0x09, 0x0a, 0x5b, 0x90, 0x48, 0x57, 0xc3, 0x32, 0x5f, 0x87, + 0xeb, 0xfb, 0x08, 0x69, 0x6f, 0xa9, 0x46, 0x46, 0xa9, 0x54, + 0x67, 0xec, 0x7b, 0x15, 0xc9, 0x68, 0x6b, 0x01, 0xb8, 0x10, + 0x59, 0x53, 0x9c, 0xe6, 0x1b, 0x2e, 0x70, 0x72, 0x6e, 0x82, + 0x7b, 0x03, 0xbc, 0xf2, 0x26, 0x9b, 0xb3, 0x91, 0xaa, 0xf1, + 0xba, 0x62, 0x12, 0xbb, 0x74, 0x4b, 0x70, 0x44, 0x74, 0x19, + 0xb2, 0xa1, 0x68, 0xd2, 0x30, 0xd6, 0xa5, 0x1b, 0xd9, 0xea, + 0x4d, 0xdb, 0x81, 0x8e, 0x66, 0xbf, 0x4d, 0x6c, 0x32, 0x66, + 0xc2, 0x8a, 0x22, 0x6b, 0x47, 0xc1, 0xd1, 0x52, 0x61, 0x66, + 0xa0, 0x75, 0xab, 0x65 }; + error_code ec; + tracker_response resp = parse_tracker_response( + { reinterpret_cast(response), sizeof(response) } + , ec, tracker_request::i2p, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 11); + + if (resp.peers.size() == 11) + { + TEST_EQUAL(resp.peers[0].hostname + , "wgcobfq73pzmtmcttiy2knon5bm2a7gn6j6idaiccf53ikwrecdq.b32.i2p"); + TEST_EQUAL(resp.peers[10].hostname + , "ufunemgwuun5t2sn3oay4zv7jvwdezwcrirgwr6b2fjgczvaowvq.b32.i2p"); + } +} +#endif // TORRENT_USE_I2P + +TORRENT_TEST(parse_interval) +{ + char const response[] = "d8:intervali1042e12:min intervali10e5:peers0:e"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.peers4.size(), 0); + TEST_EQUAL(resp.interval.count(), 1042); + TEST_EQUAL(resp.min_interval.count(), 10); +} + +TORRENT_TEST(parse_warning) +{ + char const response[] = "d5:peers0:15:warning message12:test messagee"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.warning_message, "test message"); +} + +TORRENT_TEST(parse_failure_reason) +{ + char const response[] = "d5:peers0:14:failure reason12:test messagee"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, errors::tracker_failure); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.failure_reason, "test message"); +} + +TORRENT_TEST(parse_scrape_response) +{ + char const response[] = "d5:filesd20:aaaaaaaaaaaaaaaaaaaad" + "8:completei1e10:incompletei2e10:downloadedi3e11:downloadersi6eeee"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, true, sha1_hash("aaaaaaaaaaaaaaaaaaaa")); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.complete, 1); + TEST_EQUAL(resp.incomplete, 2); + TEST_EQUAL(resp.downloaded, 3); + TEST_EQUAL(resp.downloaders, 6); +} + +TORRENT_TEST(parse_scrape_response_with_zero) +{ + char const response[] = "d5:filesd20:aaa\0aaaaaaaaaaaaaaaad" + "8:completei4e10:incompletei5e10:downloadedi6eeee"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, true, sha1_hash("aaa\0aaaaaaaaaaaaaaaa")); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.complete, 4); + TEST_EQUAL(resp.incomplete, 5); + TEST_EQUAL(resp.downloaded, 6); + TEST_EQUAL(resp.downloaders, -1); +} + +TORRENT_TEST(parse_external_ip) +{ + char const response[] = "d5:peers0:11:external ip4:\x01\x02\x03\x04" "e"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.external_ip, addr4("1.2.3.4")); +} + +TORRENT_TEST(parse_external_ip6) +{ + char const response[] = "d5:peers0:11:external ip" + "16:\xf1\x02\x03\x04\0\0\0\0\0\0\0\0\0\0\xff\xff" "e"; + error_code ec; + tracker_response resp = parse_tracker_response(response + , ec, false, sha1_hash()); + + TEST_EQUAL(ec, error_code()); + TEST_EQUAL(resp.peers.size(), 0); + TEST_EQUAL(resp.external_ip, addr6("f102:0304::ffff")); +} + +namespace { +peer_entry extract_peer(char const* peer_field, error_code expected_ec, bool expected_ret) +{ + error_code ec; + peer_entry result; + bdecode_node n; + bdecode(peer_field, peer_field + strlen(peer_field) + , n, ec, nullptr, 1000, 1000); + TEST_CHECK(!ec); + bool ret = extract_peer_info(n, result, ec); + TEST_EQUAL(expected_ret, ret); + TEST_EQUAL(expected_ec, ec); + return result; +} +} // anonymous namespace + +TORRENT_TEST(extract_peer) +{ + peer_entry result = extract_peer("d7:peer id20:abababababababababab2:ip4:abcd4:porti1337ee" + , error_code(), true); + TEST_EQUAL(result.hostname, "abcd"); + TEST_EQUAL(result.pid, peer_id("abababababababababab")); + TEST_EQUAL(result.port, 1337); +} + +TORRENT_TEST(extract_peer_hostname) +{ + peer_entry result = extract_peer("d2:ip11:example.com4:porti1ee" + , error_code(), true); + TEST_EQUAL(result.hostname, "example.com"); + TEST_EQUAL(result.pid, peer_id::min()); + TEST_EQUAL(result.port, 1); +} + +TORRENT_TEST(extract_peer_not_a_dictionary) +{ + // not a dictionary + peer_entry result = extract_peer("2:ip11:example.com" + , errors::invalid_peer_dict, false); +} + +TORRENT_TEST(extract_peer_missing_ip) +{ + // missing IP + peer_entry result = extract_peer("d7:peer id20:abababababababababab4:porti1337ee" + , errors::invalid_tracker_response, false); +} + +TORRENT_TEST(extract_peer_missing_port) +{ + // missing port + peer_entry result = extract_peer("d7:peer id20:abababababababababab2:ip4:abcde" + , errors::invalid_tracker_response, false); +} + +namespace { + +bool connect_alert(lt::alert const* a, tcp::endpoint& ep) +{ + if (peer_connect_alert const* pc = alert_cast(a)) + { + ep = pc->endpoint; + return true; + } + return false; +} + +void test_udp_tracker(std::string const& iface, address tracker, tcp::endpoint const& expected_peer) +{ + using namespace std::placeholders; + + int const udp_port = start_udp_tracker(tracker); + + int prev_udp_announces = num_udp_announces(); + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + + std::unique_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp1_tracker", ec); + create_directory("tmp1_tracker", ec); + std::ofstream file(combine_path("tmp1_tracker", "temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char tracker_url[200]; + std::snprintf(tracker_url, sizeof(tracker_url), "udp://%s:%d/announce", iface.c_str(), udp_port); + t->add_tracker(tracker_url, 0); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.flags |= torrent_flags::seed_mode; + addp.ti = t; + addp.save_path = "tmp1_tracker"; + torrent_handle h = s->add_torrent(addp); + + tcp::endpoint peer_ep; + for (int i = 0; i < 50; ++i) + { + print_alerts(*s, "s", false, false, std::bind(&connect_alert, _1, std::ref(peer_ep))); + + if (num_udp_announces() == prev_udp_announces + 1) + break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + // we should have announced to the tracker by now + TEST_EQUAL(num_udp_announces(), prev_udp_announces + 1); + + // if we remove the torrent before it has received the response from the + // tracker, it won't announce again to stop. So, wait a bit before removing. + std::this_thread::sleep_for(lt::milliseconds(1000)); + + s->remove_torrent(h); + + for (int i = 0; i < 50; ++i) + { + print_alerts(*s, "s", true, false, std::bind(&connect_alert, _1, std::ref(peer_ep))); + if (num_udp_announces() == prev_udp_announces + 2) + break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + std::printf("peer_ep: %s expected: %s\n" + , lt::print_endpoint(peer_ep).c_str() + , lt::print_endpoint(expected_peer).c_str()); + TEST_CHECK(peer_ep == expected_peer); + std::printf("destructing session\n"); + + s.reset(); + std::printf("done\n"); + + // we should have announced the stopped event now + TEST_EQUAL(num_udp_announces(), prev_udp_announces + 2); + + stop_udp_tracker(); +} + +} // anonymous namespace + +TORRENT_TEST(udp_tracker_v4) +{ + // if the machine running the test doesn't have an actual IPv4 connection + // the test would fail with any other address than loopback (because it + // would be unreachable). This is true for some CI's, running containers + // without an internet connection + test_udp_tracker("127.0.0.1", address_v4::any(), ep("127.0.0.2", 1337)); +} + +TORRENT_TEST(udp_tracker_v6) +{ + if (supports_ipv6()) + { + // if the machine running the test doesn't have an actual IPv6 connection + // the test would fail with any other address than loopback (because it + // would be unreachable) + test_udp_tracker("[::1]", address_v6::any(), ep("::1", 1337)); + } +} + +TORRENT_TEST(http_peers) +{ + int const http_port = start_web_server(); + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:39775"); + + std::unique_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp2_tracker", ec); + create_directory("tmp2_tracker", ec); + std::ofstream file(combine_path("tmp2_tracker", "temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char tracker_url[200]; + std::snprintf(tracker_url, sizeof(tracker_url), "http://127.0.0.1:%d/announce" + , http_port); + t->add_tracker(tracker_url, 0); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.flags |= torrent_flags::seed_mode; + addp.ti = t; + addp.save_path = "tmp2_tracker"; + torrent_handle h = s->add_torrent(addp); + + lt::torrent_status status = h.status(); + TEST_CHECK(status.current_tracker.empty()); + + // wait to hit the tracker + wait_for_alert(*s, tracker_reply_alert::alert_type, "s"); + + status = h.status(); + TEST_CHECK(!status.current_tracker.empty()); + TEST_EQUAL(status.current_tracker, tracker_url); + + // we expect to have certain peers in our peer list now + // these peers are hard coded in web_server.py + h.save_resume_data(); + alert const* a = wait_for_alert(*s, save_resume_data_alert::alert_type); + + TEST_CHECK(a); + save_resume_data_alert const* ra = alert_cast(a); + TEST_CHECK(ra); + if (ra) + { + std::set expected_peers; + expected_peers.insert(ep("65.65.65.65", 16962)); + expected_peers.insert(ep("67.67.67.67", 17476)); + expected_peers.insert(ep("4545:4545:4545:4545:4545:4545:4545:4545", 17990)); + for (auto const& ip : ra->params.peers) + { + TEST_EQUAL(expected_peers.count(ip), 1); + } + } + + std::printf("destructing session\n"); + s.reset(); + std::printf("done\n"); + + std::printf("stop_web_server\n"); + stop_web_server(); + std::printf("done\n"); +} + +TORRENT_TEST(current_tracker) +{ + // use a invalid tracker port + int http_port = 39527; + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:39775"); + + std::unique_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp3_tracker", ec); + create_directory("tmp3_tracker", ec); + std::ofstream file(combine_path("tmp3_tracker", "temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char tracker_url[200]; + std::snprintf(tracker_url, sizeof(tracker_url), "http://127.0.0.1:%d/announce" + , http_port); + t->add_tracker(tracker_url, 0); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.flags |= torrent_flags::seed_mode; + addp.ti = t; + addp.save_path = "tmp3_tracker"; + torrent_handle h = s->add_torrent(addp); + + lt::torrent_status status = h.status(); + TEST_CHECK(status.current_tracker.empty()); + + // wait to hit the tracker announce + wait_for_alert(*s, tracker_announce_alert::alert_type, "s"); + + status = h.status(); + TEST_CHECK(status.current_tracker.empty()); + + // wait to hit the tracker error + wait_for_alert(*s, tracker_error_alert::alert_type, "s"); + + status = h.status(); + TEST_CHECK(status.current_tracker.empty()); + + std::printf("destructing session\n"); + s.reset(); + std::printf("done\n"); +} + +namespace { + +void test_proxy(bool proxy_trackers) +{ + int http_port = start_web_server(); + + settings_pack pack = settings(); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:39775"); + + pack.set_str(settings_pack::proxy_hostname, "non-existing.com"); + pack.set_int(settings_pack::proxy_type, settings_pack::socks5); + pack.set_int(settings_pack::proxy_port, 4444); + pack.set_bool(settings_pack::proxy_tracker_connections, proxy_trackers); + + std::unique_ptr s(new lt::session(pack)); + + error_code ec; + remove_all("tmp2_tracker", ec); + create_directory("tmp2_tracker", ec); + std::ofstream file(combine_path("tmp2_tracker", "temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + char tracker_url[200]; + // and this should not be announced to (since the one before it succeeded) + std::snprintf(tracker_url, sizeof(tracker_url), "http://127.0.0.1:%d/announce" + , http_port); + t->add_tracker(tracker_url, 0); + + add_torrent_params addp; + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + addp.flags |= torrent_flags::seed_mode; + addp.ti = t; + addp.save_path = "tmp2_tracker"; + torrent_handle h = s->add_torrent(addp); + + // wait to hit the tracker + const alert* a = wait_for_alert(*s, tracker_reply_alert::alert_type, "s"); + if (proxy_trackers) + { + TEST_CHECK(a == nullptr); + } + else + { + TEST_CHECK(a != nullptr); + } + + std::printf("destructing session\n"); + s.reset(); + std::printf("done\n"); + + std::printf("stop_web_server\n"); + stop_web_server(); + std::printf("done\n"); +} + +} // anonymous namespace + +TORRENT_TEST(tracker_proxy) +{ + std::printf("\n\nnot proxying tracker connections (expect to reach the tracker)\n\n"); + test_proxy(false); + + std::printf("\n\nproxying tracker connections through non-existent proxy " + "(do not expect to reach the tracker)\n\n"); + test_proxy(true); +} + +#ifndef TORRENT_DISABLE_LOGGING +namespace { +void test_stop_tracker_timeout(int const timeout) +{ + // trick the min interval so that the stopped anounce is permitted immediately + // after the initial announce + int port = start_web_server(false, false, true, -1); + + auto count_stopped_events = [](session& ses, int expected) + { + int count = 0; + int num = 70; // this number is adjusted per version, an estimate + time_point const end_time = clock_type::now() + seconds(15); + while (true) + { + time_point const now = clock_type::now(); + if (now > end_time) return count; + + ses.wait_for_alert(end_time - now); + std::vector alerts; + ses.pop_alerts(&alerts); + for (auto a : alerts) + { + std::printf("%d: [%s] %s\n", num, a->what(), a->message().c_str()); + if (a->type() == log_alert::alert_type) + { + std::string const msg = a->message(); + if (msg.find("&event=stopped") != std::string::npos) + { + count++; + --expected; + } + } + num--; + } + if (num <= 0 && expected <= 0) return count; + } + }; + + settings_pack p = settings(); + p.set_bool(settings_pack::announce_to_all_trackers, true); + p.set_bool(settings_pack::announce_to_all_tiers, true); + p.set_str(settings_pack::listen_interfaces, "127.0.0.1:6881"); + p.set_int(settings_pack::stop_tracker_timeout, timeout); + + lt::session s(p); + + error_code ec; + remove_all("tmp4_tracker", ec); + create_directory("tmp4_tracker", ec); + std::ofstream file(combine_path("tmp4_tracker", "temporary").c_str()); + std::shared_ptr t = ::create_torrent(&file, "temporary", 16 * 1024, 13, false); + file.close(); + + add_torrent_params tp; + tp.flags &= ~torrent_flags::paused; + tp.flags &= ~torrent_flags::auto_managed; + tp.flags |= torrent_flags::seed_mode; + tp.ti = t; + tp.save_path = "tmp4_tracker"; + torrent_handle h = s.add_torrent(tp); + + char tracker_url[200]; + std::snprintf(tracker_url, sizeof(tracker_url), "http://127.0.0.1:%d/announce", port); + announce_entry ae{tracker_url}; + h.add_tracker(ae); + + // make sure it announced a event=started properly + wait_for_alert(s, tracker_reply_alert::alert_type, "s"); + + s.remove_torrent(h); + + int const count = count_stopped_events(s, (timeout == 0) ? 0 : 1); + TEST_EQUAL(count, (timeout == 0) ? 0 : 1); +} +} // anonymous namespace + +TORRENT_TEST(stop_tracker_timeout) +{ + std::printf("\n\nexpect to get ONE request with &event=stopped\n\n"); + test_stop_tracker_timeout(1); +} + +TORRENT_TEST(stop_tracker_timeout_zero_timeout) +{ + std::printf("\n\nexpect to NOT get a request with &event=stopped\n\n"); + test_stop_tracker_timeout(0); +} +#endif diff --git a/test/test_transfer.cpp b/test/test_transfer.cpp new file mode 100644 index 0000000..ca3f4de --- /dev/null +++ b/test/test_transfer.cpp @@ -0,0 +1,537 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/torrent_info.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include "test_utils.hpp" + +#include +#include + +#include +#include + +using namespace lt; + +using std::ignore; + +namespace { + +int peer_disconnects = 0; + +bool on_alert(alert const* a) +{ + auto const* const pd = alert_cast(a); + if (pd && pd->error != make_error_code(errors::self_connection)) + ++peer_disconnects; + else if (alert_cast(a)) + ++peer_disconnects; + + return false; +} + +// simulate a full disk +struct test_storage : default_storage +{ + test_storage(storage_params const& params, file_pool& pool) + : default_storage(params, pool) + , m_written(0) + , m_limit(16 * 1024 * 2) + {} + + void set_file_priority(aux::vector& + , storage_error&) override {} + + void set_limit(int lim) + { + std::lock_guard l(m_mutex); + m_limit = lim; + } + + int writev( + span bufs + , piece_index_t piece_index + , int offset + , open_mode_t const flags + , storage_error& se) override + { + std::unique_lock l(m_mutex); + if (m_written >= m_limit) + { + std::cout << "storage written: " << m_written << " limit: " << m_limit << std::endl; + error_code ec; + ec = error_code(boost::system::errc::no_space_on_device, generic_category()); + se.ec = ec; + return 0; + } + + for (auto const& b : bufs) m_written += int(b.size()); + l.unlock(); + return default_storage::writev(bufs, piece_index, offset, flags, se); + } + + ~test_storage() override = default; + + int m_written; + int m_limit; + std::mutex m_mutex; +}; + +storage_interface* test_storage_constructor(storage_params const& params, file_pool& pool) +{ + return new test_storage(params, pool); +} + +using transfer_flags_t = lt::flags::bitfield_flag; + +constexpr transfer_flags_t disk_full = 1_bit; +constexpr transfer_flags_t delete_files = 2_bit; +constexpr transfer_flags_t move_storage = 3_bit; + +void test_transfer(int proxy_type, settings_pack const& sett + , transfer_flags_t flags = {} + , storage_mode_t storage_mode = storage_mode_sparse) +{ + char const* test_name[] = {"no", "SOCKS4", "SOCKS5", "SOCKS5 password", "HTTP", "HTTP password"}; + + std::printf("\n\n ==== TESTING %s proxy ==== disk-full: %s delete_files: %s move-storage: %s\n\n\n" + , test_name[proxy_type] + , (flags & disk_full) ? "true": "false" + , (flags & delete_files) ? "true": "false" + , (flags & move_storage) ? "true": "false" + ); + + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_transfer", ec); + remove_all("tmp2_transfer", ec); + remove_all("tmp1_transfer_moved", ec); + remove_all("tmp2_transfer_moved", ec); + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + + settings_pack pack = settings(); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); +#if TORRENT_ABI_VERSION == 1 + pack.set_bool(settings_pack::rate_limit_utp, true); +#endif + + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49075"); + lt::session ses2(pack); + + int proxy_port = 0; + if (proxy_type) + { + proxy_port = start_proxy(proxy_type); + + settings_pack pack_p; + pack_p.set_str(settings_pack::proxy_username, "testuser"); + pack_p.set_str(settings_pack::proxy_password, "testpass"); + pack_p.set_int(settings_pack::proxy_type, proxy_type); + pack_p.set_int(settings_pack::proxy_port, proxy_port); + + // test resetting the proxy in quick succession. + // specifically the udp_socket connecting to a new + // socks5 proxy while having one connection attempt + // in progress. + pack_p.set_str(settings_pack::proxy_hostname, "5.6.7.8"); + ses1.apply_settings(pack_p); + pack_p.set_str(settings_pack::proxy_hostname, "127.0.0.1"); + ses1.apply_settings(pack_p); + } + + pack = sett; + + // we need a short reconnect time since we + // finish the torrent and then restart it + // immediately to complete the second half. + // using a reconnect time > 0 will just add + // to the time it will take to complete the test + pack.set_int(settings_pack::min_reconnect_time, 0); + pack.set_int(settings_pack::stop_tracker_timeout, 1); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + + // make sure we announce to both http and udp trackers + pack.set_bool(settings_pack::prefer_udp_trackers, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_dht, false); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + + // TODO: these settings_pack tests belong in their own test + pack.set_int(settings_pack::unchoke_slots_limit, 0); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == 0); + + pack.set_int(settings_pack::unchoke_slots_limit, -1); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == -1); + + pack.set_int(settings_pack::unchoke_slots_limit, 8); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == 8); + + ses2.apply_settings(pack); + + torrent_handle tor1; + torrent_handle tor2; + + create_directory("tmp1_transfer", ec); + std::ofstream file("tmp1_transfer/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary", 32 * 1024, 13, false); + file.close(); + + TEST_CHECK(exists(combine_path("tmp1_transfer", "temporary"))); + + add_torrent_params addp(&test_storage_constructor); + addp.flags &= ~torrent_flags::paused; + addp.flags &= ~torrent_flags::auto_managed; + + add_torrent_params params; + params.storage_mode = storage_mode; + params.flags &= ~torrent_flags::paused; + params.flags &= ~torrent_flags::auto_managed; + + wait_for_listen(ses1, "ses1"); + wait_for_listen(ses2, "ses2"); + + peer_disconnects = 0; + + // test using piece sizes smaller than 16kB + std::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, nullptr + , true, false, true, "_transfer", 1024 * 1024, &t, false + , (flags & disk_full) ? &addp : ¶ms); + + int num_pieces = tor2.torrent_file()->num_pieces(); + std::vector priorities(std::size_t(num_pieces), 1); + + int upload_mode_timer = 0; + + lt::time_point const start_time = lt::clock_type::now(); + + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + + for (int i = 0; i < 20000; ++i) + { + if (lt::clock_type::now() - start_time > seconds(10)) + { + std::cout << "timeout\n"; + break; + } + // sleep a bit + ses2.wait_for_alert(lt::milliseconds(100)); + + torrent_status const st1 = tor1.status(); + torrent_status const st2 = tor2.status(); + + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + if (i % 10 == 0) + { + print_ses_rate(i / 10.f, &st1, &st2); + } + + std::cout << "st1-progress: " << (st1.progress * 100.f) << "% state: " << state_str[st1.state] << "\n"; + std::cout << "st2-progress: " << (st2.progress * 100.f) << "% state: " << state_str[st2.state] << "\n"; + if ((flags & move_storage) && st2.progress > 0.1f) + { + flags &= ~move_storage; + tor1.move_storage("tmp1_transfer_moved"); + tor2.move_storage("tmp2_transfer_moved"); + std::cout << "moving storage" << std::endl; + } + + if ((flags & delete_files) && st2.progress > 0.1f) + { + ses1.remove_torrent(tor1, session::delete_files); + std::cout << "deleting files" << std::endl; + + std::this_thread::sleep_for(lt::seconds(1)); + break; + } + + // wait 10 loops before we restart the torrent. This lets + // us catch all events that failed (and would put the torrent + // back into upload mode) before we restart it. + + // TODO: factor out the disk-full test into its own unit test + if (flags & disk_full + && !(tor2.flags() & torrent_flags::upload_mode) + && ++upload_mode_timer > 10) + { + flags &= ~disk_full; + static_cast(tor2.get_storage_impl())->set_limit(16 * 1024 * 1024); + + // if we reset the upload mode too soon, there may be more disk + // jobs failing right after, putting us back in upload mode. So, + // give the disk some time to fail all disk jobs before resetting + // upload mode to false + std::this_thread::sleep_for(lt::milliseconds(500)); + + // then we need to drain the alert queue, so the peer_disconnects + // counter doesn't get incremented by old alerts + print_alerts(ses1, "ses1", true, true, &on_alert); + print_alerts(ses2, "ses2", true, true, &on_alert); + + lt::error_code err = tor2.status().errc; + std::printf("error: \"%s\"\n", err.message().c_str()); + TEST_CHECK(!err); + tor2.unset_flags(torrent_flags::upload_mode); + + // at this point we probably disconnected the seed + // so we need to reconnect as well + std::printf("%s: reconnecting peer\n", time_now_string()); + error_code ec2; + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec2) + , ses1.listen_port())); + + TEST_CHECK(tor2.status().is_finished == false); + std::printf("disconnects: %d\n", peer_disconnects); + TEST_CHECK(peer_disconnects >= 2); + std::printf("%s: discovered disk full mode. Raise limit and disable upload-mode\n", time_now_string()); + peer_disconnects = 0; + continue; + } + + if (!(flags & disk_full) && st2.is_seeding) break; + + TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_files + || st1.state == torrent_status::checking_resume_data); + TEST_CHECK(st2.state == torrent_status::downloading + || st2.state == torrent_status::checking_resume_data + || ((flags & disk_full) && st2.errc)); + + if (!(flags & disk_full) && peer_disconnects >= 2) break; + + // if nothing is being transferred after 2 seconds, we're failing the test +// if (!(flags & disk_full) && st1.upload_payload_rate == 0 && i > 20) break; + } + + if (!(flags & delete_files)) + { + TEST_CHECK(tor2.status().is_seeding); + } + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); + + if (proxy_type) stop_proxy(proxy_port); +} + +void cleanup() +{ + error_code ec; + remove_all("tmp1_transfer", ec); + remove_all("tmp2_transfer", ec); + remove_all("tmp1_transfer_moved", ec); + remove_all("tmp2_transfer_moved", ec); +} + +} // anonymous namespace + +#if TORRENT_ABI_VERSION == 1 +TORRENT_TEST(no_contiguous_buffers) +{ + using namespace lt; + + // test no contiguous_recv_buffers + settings_pack p; + p.set_bool(settings_pack::contiguous_recv_buffer, false); + test_transfer(0, p); + + cleanup(); +} +#endif + + // test with all kinds of proxies +TORRENT_TEST(socks5_pw) +{ + using namespace lt; + test_transfer(settings_pack::socks5_pw, settings_pack()); + cleanup(); +} + +TORRENT_TEST(http) +{ + using namespace lt; + test_transfer(settings_pack::http, settings_pack()); + cleanup(); +} + +TORRENT_TEST(http_pw) +{ + using namespace lt; + test_transfer(settings_pack::http_pw, settings_pack()); + cleanup(); +} +/* +TORRENT_TEST(i2p) +{ + using namespace lt; + test_transfer(settings_pack::i2p_proxy, settings_pack()); + cleanup(); +} + +// this test is too flaky. Move it to a sim +TORRENT_TEST(disk_full) +{ + using namespace lt; + // test with a (simulated) full disk + test_transfer(0, settings_pack(), disk_full); + + cleanup(); +} +*/ + +TORRENT_TEST(move_storage) +{ + using namespace lt; + test_transfer(0, settings_pack(), move_storage); + cleanup(); +} + +TORRENT_TEST(delete_files) +{ + using namespace lt; + settings_pack p = settings_pack(); + p.set_int(settings_pack::aio_threads, 10); + test_transfer(0, p, delete_files); + cleanup(); +} + +TORRENT_TEST(allow_fast) +{ + using namespace lt; + // test allowed fast + settings_pack p; + p.set_int(settings_pack::allowed_fast_set_size, 2000); + test_transfer(0, p); + + cleanup(); +} + +TORRENT_TEST(coalesce_reads) +{ + using namespace lt; + // test allowed fast + settings_pack p; + p.set_int(settings_pack::read_cache_line_size, 16); + p.set_bool(settings_pack::coalesce_reads, true); + test_transfer(0, p); + + cleanup(); +} + +TORRENT_TEST(coalesce_writes) +{ + using namespace lt; + // test allowed fast + settings_pack p; + p.set_bool(settings_pack::coalesce_writes, true); + test_transfer(0, p); + + cleanup(); +} + +TORRENT_TEST(no_coalesce_reads) +{ + using namespace libtorrent; + settings_pack p; + p.set_int(settings_pack::read_cache_line_size, 16); + p.set_bool(settings_pack::coalesce_reads, false); + test_transfer(0, p); + + cleanup(); +} + +TORRENT_TEST(no_coalesce_writes) +{ + using namespace libtorrent; + settings_pack p; + p.set_bool(settings_pack::coalesce_writes, false); + test_transfer(0, p); + + cleanup(); +} + +TORRENT_TEST(allocate) +{ + using namespace lt; + // test storage_mode_allocate + std::printf("full allocation mode\n"); + test_transfer(0, settings_pack(), {}, storage_mode_allocate); + + cleanup(); +} + +TORRENT_TEST(suggest) +{ + using namespace lt; + settings_pack p; + p.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + test_transfer(0, p); + + cleanup(); +} diff --git a/test/test_upnp.cpp b/test/test_upnp.cpp new file mode 100644 index 0000000..ef6af7b --- /dev/null +++ b/test/test_upnp.cpp @@ -0,0 +1,342 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/upnp.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/socket_io.hpp" // print_endpoint +#include "libtorrent/http_parser.hpp" +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/aux_/path.hpp" +#include +#include +#include +#include + +using namespace lt; + +using lt::portmap_protocol; + +namespace { + +broadcast_socket* sock = nullptr; +int g_port = 0; + +char const* soap_add_response[] = { + "" + "" + "", + "" + "" + ""}; + +char const* soap_delete_response[] = { + "" + "" + "", + "" + "" + ""}; + +void incoming_msearch(udp::endpoint const& from, span buffer) +{ + http_parser p; + bool error = false; + p.incoming(buffer, error); + if (error || !p.header_finished()) + { + std::cout << "*** malformed HTTP from " + << print_endpoint(from) << std::endl; + return; + } + + if (p.method() != "m-search") return; + + std::cout << "< incoming m-search from " << from << std::endl; + + char const msg[] = "HTTP/1.1 200 OK\r\n" + "ST:upnp:rootdevice\r\n" + "USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice\r\n" + "Location: http://127.0.0.1:%d/upnp.xml\r\n" + "Server: Custom/1.0 UPnP/1.0 Proc/Ver\r\n" + "EXT:\r\n" + "Cache-Control:max-age=180\r\n" + "DATE: Fri, 02 Jan 1970 08:10:38 GMT\r\n\r\n"; + + TORRENT_ASSERT(g_port != 0); + char buf[sizeof(msg) + 30]; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + int const len = std::snprintf(buf, sizeof(buf), msg, g_port); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + error_code ec; + sock->send_to(buf, len, from, ec); + + std::cout << "> sending response to " << print_endpoint(from) << std::endl; + + if (ec) std::cout << "*** error sending " << ec.message() << std::endl; +} + +struct callback_info +{ + port_mapping_t mapping; + int port; + error_code ec; + bool operator==(callback_info const& e) + { return mapping == e.mapping && port == e.port && !ec == !e.ec; } +}; + +std::list callbacks; + + struct upnp_callback final : aux::portmap_callback + { + void on_port_mapping(port_mapping_t const mapping + , address const& ip, int port + , portmap_protocol const protocol, error_code const& err + , portmap_transport) override + { + callback_info info = {mapping, port, err}; + callbacks.push_back(info); + std::cout << "mapping: " << static_cast(mapping) + << ", port: " << port << ", IP: " << ip + << ", proto: " << static_cast(protocol) + << ", error: \"" << err.message() << "\"\n"; + } + #ifndef TORRENT_DISABLE_LOGGING + bool should_log_portmap(portmap_transport) const override + { + return true; + } + + void log_portmap(portmap_transport, char const* msg) const override + { + std::cout << "UPnP: " << msg << std::endl; + //TODO: store the log and verify that some key messages are there + } + #endif + }; + +ip_interface pick_upnp_interface() +{ + lt::io_service ios; + error_code ec; + std::vector const routes = enum_routes(ios, ec); + if (ec) + { + std::cerr << "failed to enumerate routes: " << ec.message() << '\n'; + TEST_CHECK(false); + return {}; + } + std::vector const ifs = enum_net_interfaces(ios, ec); + if (ec) + { + std::cerr << "failed to enumerate network interfaces: " << ec.message() << '\n'; + TEST_CHECK(false); + return {}; + } + int idx = 0; + auto const iface = std::find_if(ifs.begin(), ifs.end(), [&](ip_interface const& face) + { + std::cerr << " - " << idx << ' ' << face.interface_address.to_string() << ' ' << face.name << '\n'; + ++idx; + if (!face.interface_address.is_v4()) return false; + if (is_loopback(face.interface_address)) return false; + auto const route = std::find_if(routes.begin(), routes.end(), [&](ip_route const& r) + { return r.destination.is_unspecified() && string_view(face.name) == r.name; }); + if (route == routes.end()) return false; + return true; + }); + + if (iface == ifs.end()) + { + std::cerr << "could not find an IPv4 interface to run UPnP test over!\n"; + TEST_CHECK(false); + return {}; + } + std::cout << "starting upnp on: " << iface->interface_address.to_string() << ' ' << iface->name << '\n'; + return *iface; +} + +void run_upnp_test(char const* root_filename, char const* control_name, int igd_version) +{ + g_port = start_web_server(); + + std::vector buf; + error_code ec; + load_file(root_filename, buf, ec); + buf.push_back(0); + + FILE* xml_file = fopen("upnp.xml", "w+"); + if (xml_file == nullptr) + { + std::printf("failed to open file 'upnp.xml': %s\n", strerror(errno)); + TEST_CHECK(false); + return; + } +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + std::fprintf(xml_file, &buf[0], g_port); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + fclose(xml_file); + + std::ofstream xml(control_name, std::ios::trunc); + xml.write(soap_add_response[igd_version-1], sizeof(soap_add_response[igd_version-1])-1); + xml.close(); + + sock = new broadcast_socket(udp::endpoint(address_v4::from_string("239.255.255.250") + , 1900)); + + lt::io_service ios; + + sock->open(&incoming_msearch, ios, ec); + + aux::session_settings sett; + + // pick an appropriate interface to run this test on + auto const ipf = pick_upnp_interface(); + + upnp_callback cb; + auto upnp_handler = std::make_shared(ios, sett, cb + , ipf.interface_address.to_v4(), ipf.netmask.to_v4(), ipf.name); + upnp_handler->start(); + + for (int i = 0; i < 20; ++i) + { + ios.reset(); + ios.poll(ec); + if (ec) + { + std::printf("io_service::run(): %s\n", ec.message().c_str()); + ec.clear(); + break; + } + if (!upnp_handler->router_model().empty()) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + std::cout << "router: " << upnp_handler->router_model() << std::endl; + TEST_CHECK(!upnp_handler->router_model().empty()); + + auto const mapping1 = upnp_handler->add_mapping(portmap_protocol::tcp, 500, ep("127.0.0.1", 500)); + auto const mapping2 = upnp_handler->add_mapping(portmap_protocol::udp, 501, ep("127.0.0.1", 501)); + + for (int i = 0; i < 40; ++i) + { + ios.reset(); + ios.poll(ec); + if (ec) + { + std::printf("io_service::run(): %s\n", ec.message().c_str()); + ec.clear(); + break; + } + if (callbacks.size() >= 2) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + callback_info expected1 = {mapping1, 500, error_code()}; + callback_info expected2 = {mapping2, 501, error_code()}; + TEST_EQUAL(std::count(callbacks.begin(), callbacks.end(), expected1), 1); + TEST_EQUAL(std::count(callbacks.begin(), callbacks.end(), expected2), 1); + + xml.open(control_name, std::ios::trunc); + xml.write(soap_delete_response[igd_version-1], sizeof(soap_delete_response[igd_version-1])-1); + xml.close(); + + upnp_handler->close(); + sock->close(); + + for (int i = 0; i < 40; ++i) + { + ios.reset(); + ios.poll(ec); + if (ec) + { + std::printf("io_service::run(): %s\n", ec.message().c_str()); + ec.clear(); + break; + } + if (callbacks.size() >= 4) break; + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + // there should have been two DeleteMapping calls + TEST_EQUAL(callbacks.size(), 4); + + stop_web_server(); + + callbacks.clear(); + + delete sock; +} + +} // anonymous namespace + +TORRENT_TEST(upnp) +{ + run_upnp_test(combine_path("..", "root1.xml").c_str(), "wipconn", 1); + run_upnp_test(combine_path("..", "root2.xml").c_str(), "WANIPConnection", 1); + run_upnp_test(combine_path("..", "root3.xml").c_str(), "WANIPConnection_2", 2); +} + +TORRENT_TEST(upnp_max_mappings) +{ + // pick an appropriate interface to run this test on + lt::io_service ios; + + auto const ipf = pick_upnp_interface(); + aux::session_settings sett; + + upnp_callback cb; + auto upnp_handler = std::make_shared(ios, sett, cb + , ipf.interface_address.to_v4(), ipf.netmask.to_v4(), ipf.name); + + for (int i = 0; i < 50; ++i) + { + auto const mapping = upnp_handler->add_mapping(portmap_protocol::tcp + , 500 + i, ep("127.0.0.1", 500 + i)); + + TEST_CHECK(mapping != port_mapping_t{-1}); + } +} diff --git a/test/test_url_seed.cpp b/test/test_url_seed.cpp new file mode 100644 index 0000000..a4567cf --- /dev/null +++ b/test/test_url_seed.cpp @@ -0,0 +1,68 @@ +/* + +Copyright (c) 2008-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(url_seed_ssl_keepalive) +{ + run_http_suite(proxy, "https", 1, 0, 0, 1); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1, 0, 0, 0); +} +#endif + +TORRENT_TEST(url_seed_keepalive) +{ + run_http_suite(proxy, "http", 1, 0, 0, 1); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1, 0, 0, 0); +} + +TORRENT_TEST(url_seed_keepalive_rename) +{ + run_http_suite(proxy, "http", 1, 0, 0, 1, 1); +} + + diff --git a/test/test_utf8.cpp b/test/test_utf8.cpp new file mode 100644 index 0000000..a362184 --- /dev/null +++ b/test/test_utf8.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "libtorrent/utf8.hpp" +#include "setup_transfer.hpp" // for load_file +#include "libtorrent/aux_/path.hpp" // for combine_path + +#include + +using namespace lt; + +namespace { +void test_utf8_roundtrip(std::int32_t const codepoint) +{ + std::string utf8; + append_utf8_codepoint(utf8, codepoint); + + int len; + std::int32_t cp; + std::tie(cp, len) = parse_utf8_codepoint(utf8); + + TEST_EQUAL(len, int(utf8.size())); + TEST_EQUAL(cp, codepoint); +} + +void test_parse_utf8(lt::string_view utf8) +{ + int len; + std::int32_t cp; + std::tie(cp, len) = parse_utf8_codepoint(utf8); + TEST_EQUAL(len, int(utf8.size())); + + std::string out; + append_utf8_codepoint(out, cp); + TEST_EQUAL(out, utf8); +} + +void parse_error(lt::string_view utf8) +{ + int len; + std::int32_t cp; + std::tie(cp, len) = parse_utf8_codepoint(utf8); + TEST_EQUAL(cp, -1); + TEST_CHECK(len >= 1); + TEST_CHECK(len <= int(utf8.size())); +} +} + +TORRENT_TEST(parse_utf8_roundtrip) +{ + for (std::int32_t cp = 0; cp < 0xd800; ++cp) + test_utf8_roundtrip(cp); + + // skip surrogate codepoints, which are invalid. They won't round-trip + + for (std::int32_t cp = 0xe000; cp < 0xffff; ++cp) + test_utf8_roundtrip(cp); +} + +TORRENT_TEST(parse_utf8) +{ + test_parse_utf8("\x7f"); + test_parse_utf8("\xc3\xb0"); + test_parse_utf8("\xed\x9f\xbf"); + test_parse_utf8("\xee\x80\x80"); + test_parse_utf8("\xef\xbf\xbd"); + test_parse_utf8("\xf4\x8f\xbf\xbf"); + + // largest possible codepoint + test_parse_utf8("\xf4\x8f\xbf\xbf"); +} + +TORRENT_TEST(parse_utf8_fail) +{ + // Unexpected continuation bytes + parse_error("\x80"); + parse_error("\xbf"); + + // Impossible bytes + // The following two bytes cannot appear in a correct UTF-8 string + parse_error("\xff"); + parse_error("\xfe"); + parse_error("\xff\xff\xfe\xfe"); + + // Examples of an overlong ASCII character + parse_error("\xc0\xaf"); + parse_error("\xe0\x80\xaf"); + parse_error("\xf0\x80\x80\xaf"); + parse_error("\xf8\x80\x80\x80\xaf "); + parse_error("\xfc\x80\x80\x80\x80\xaf"); + + // Maximum overlong sequences + parse_error("\xc1\xbf"); + parse_error("\xe0\x9f\xbf"); + parse_error("\xf0\x8f\xbf\xbf"); + parse_error("\xf8\x87\xbf\xbf\xbf"); + parse_error("\xfc\x83\xbf\xbf\xbf\xbf"); + + // Overlong representation of the NUL character + parse_error("\xc0\x80"); + parse_error("\xe0\x80\x80"); + parse_error("\xf0\x80\x80\x80"); + parse_error("\xf8\x80\x80\x80\x80"); + parse_error("\xfc\x80\x80\x80\x80\x80"); + + // invalid continuation character + parse_error("\xc0\x7f"); + + // codepoint too high + parse_error("\xf5\x8f\xbf\xbf"); + parse_error("\xf4\xbf\xbf\xbf"); +} diff --git a/test/test_utils.cpp b/test/test_utils.cpp new file mode 100644 index 0000000..c387d6b --- /dev/null +++ b/test/test_utils.cpp @@ -0,0 +1,54 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test_utils.hpp" +#include "libtorrent/time.hpp" + +namespace libtorrent +{ + char const* time_now_string() + { + static const time_point start = clock_type::now(); + static char ret[200]; + int t = int(total_milliseconds(clock_type::now() - start)); + int h = t / 1000 / 60 / 60; + t -= h * 60 * 60 * 1000; + int m = t / 1000 / 60; + t -= m * 60 * 1000; + int s = t / 1000; + t -= s * 1000; + int ms = t; + std::snprintf(ret, sizeof(ret), "%02d:%02d:%02d.%03d", h, m, s, ms); + return ret; + } +} + diff --git a/test/test_utils.hpp b/test/test_utils.hpp new file mode 100644 index 0000000..16862b0 --- /dev/null +++ b/test/test_utils.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TEST_UTILS_HPP +#define TEST_UTILS_HPP + +#include "test.hpp" +#include "libtorrent/download_priority.hpp" + +namespace libtorrent +{ + EXPORT char const* time_now_string(); + +} + +inline lt::download_priority_t operator "" _pri(unsigned long long const p) +{ + return lt::download_priority_t(static_cast(p)); +} + +#endif + diff --git a/test/test_utp.cpp b/test/test_utp.cpp new file mode 100644 index 0000000..c35ec72 --- /dev/null +++ b/test/test_utp.cpp @@ -0,0 +1,156 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/aux_/path.hpp" +#include "libtorrent/utp_stream.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "settings.hpp" +#include + +using namespace lt; + +namespace { + +void test_transfer() +{ + // in case the previous run was terminated + error_code ec; + remove_all("tmp1_utp", ec); + remove_all("tmp2_utp", ec); + + // these are declared before the session objects + // so that they are destructed last. This enables + // the sessions to destruct in parallel + session_proxy p1; + session_proxy p2; + + settings_pack pack = settings(); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_incoming_tcp, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::prefer_udp_trackers, false); + pack.set_int(settings_pack::min_reconnect_time, 1); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48885"); + lt::session ses1(pack); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49885"); + lt::session ses2(pack); + + torrent_handle tor1; + torrent_handle tor2; + + ec.clear(); + create_directory("tmp1_utp", ec); + if (ec) + { + std::printf("ERROR: failed to create test directory \"tmp1_utp\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + std::ofstream file("tmp1_utp/temporary"); + std::shared_ptr t = ::create_torrent(&file, "temporary", 128 * 1024, 6, false); + file.close(); + + // for performance testing + add_torrent_params atp; + atp.flags &= ~torrent_flags::paused; + atp.flags &= ~torrent_flags::auto_managed; +// atp.storage = &disabled_storage_constructor; + + // test using piece sizes smaller than 16kB + std::tie(tor1, tor2, std::ignore) = setup_transfer(&ses1, &ses2, nullptr + , true, false, true, "_utp", 0, &t, false, &atp); + + const int timeout = 16; + + for (int i = 0; i < timeout; ++i) + { + print_alerts(ses1, "ses1", true, true); + print_alerts(ses2, "ses2", true, true); + + std::this_thread::sleep_for(lt::milliseconds(500)); + + torrent_status st1 = tor1.status(); + torrent_status st2 = tor2.status(); + + print_ses_rate(i / 2.f, &st1, &st2); + + if (st2.is_finished) break; + + TEST_CHECK(st1.state == torrent_status::seeding + || st1.state == torrent_status::checking_files); + TEST_CHECK(st2.state == torrent_status::downloading); + } + + TEST_CHECK(tor1.status().is_finished); + TEST_CHECK(tor2.status().is_finished); + + // this allows shutting down the sessions in parallel + p1 = ses1.abort(); + p2 = ses2.abort(); +} + +} // anonymous namespace + +TORRENT_TEST(utp) +{ + test_transfer(); + + error_code ec; + remove_all("tmp1_utp", ec); + remove_all("tmp2_utp", ec); +} + +TORRENT_TEST(compare_less_wrap) +{ + TEST_CHECK(compare_less_wrap(1, 2, 0xffff)); + TEST_CHECK(!compare_less_wrap(2, 1, 0xffff)); + TEST_CHECK(compare_less_wrap(100, 200, 0xffff)); + TEST_CHECK(!compare_less_wrap(200, 100, 0xffff)); + TEST_CHECK(compare_less_wrap(0xfff0, 0x000f, 0xffff)); // wrap + TEST_CHECK(!compare_less_wrap(0xfff0, 0xff00, 0xffff)); +} diff --git a/test/test_web_seed.cpp b/test/test_web_seed.cpp new file mode 100644 index 0000000..c25d476 --- /dev/null +++ b/test/test_web_seed.cpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2008-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_ssl) +{ + run_http_suite(proxy, "https", false); +} +#endif + +TORRENT_TEST(web_seed) +{ + run_http_suite(proxy, "http", false); +} + diff --git a/test/test_web_seed_ban.cpp b/test/test_web_seed_ban.cpp new file mode 100644 index 0000000..ad20367 --- /dev/null +++ b/test/test_web_seed_ban.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0, 0, 1); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1, 0, 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0, 0, 1); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1, 0, 1); +} + diff --git a/test/test_web_seed_chunked.cpp b/test/test_web_seed_chunked.cpp new file mode 100644 index 0000000..9ff41ee --- /dev/null +++ b/test/test_web_seed_chunked.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::none; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_ssl) +{ + run_http_suite(proxy, "https", 0, 1, 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1, 1, 0); +} +#endif + +TORRENT_TEST(web_seed) +{ + run_http_suite(proxy, "http", 0, 1, 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1, 1, 0); +} + diff --git a/test/test_web_seed_http.cpp b/test/test_web_seed_http.cpp new file mode 100644 index 0000000..72aa2ff --- /dev/null +++ b/test/test_web_seed_http.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::http; + +TORRENT_TEST(web_seed_http) +{ + run_http_suite(proxy, "http", false); +} + +TORRENT_TEST(url_seed_http) +{ + run_http_suite(proxy, "http", true); +} + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_https) +{ + run_http_suite(proxy, "https", false); +} + +TORRENT_TEST(url_seed_https) +{ + run_http_suite(proxy, "https", true); +} +#endif + diff --git a/test/test_web_seed_http_pw.cpp b/test/test_web_seed_http_pw.cpp new file mode 100644 index 0000000..e2f1dfb --- /dev/null +++ b/test/test_web_seed_http_pw.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::http_pw; + +TORRENT_TEST(web_seed_http_pw) +{ + run_http_suite(proxy, "http", false); +} + +TORRENT_TEST(url_seed_http_pw) +{ + run_http_suite(proxy, "http", true); +} + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(web_seed_http_pw_ssl) +{ + run_http_suite(proxy, "https", false); +} + +TORRENT_TEST(url_seed_http_pw_ssl) +{ + run_http_suite(proxy, "https", true); +} +#endif + diff --git a/test/test_web_seed_redirect.cpp b/test/test_web_seed_redirect.cpp new file mode 100644 index 0000000..505f287 --- /dev/null +++ b/test/test_web_seed_redirect.cpp @@ -0,0 +1,102 @@ +/* + +Copyright (c) 2008-2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" +#include "settings.hpp" +#include "libtorrent/random.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/torrent_info.hpp" + +using namespace lt; + +TORRENT_TEST(web_seed_redirect) +{ + using namespace lt; + + error_code ec; + + file_storage fs; + int piece_size = 0x4000; + + std::array random_data; + aux::random_bytes(random_data); + file f("test_file", open_mode::write_only, ec); + if (ec) + { + std::printf("failed to create file \"test_file\": (%d) %s\n" + , ec.value(), ec.message().c_str()); + TEST_ERROR("failed to create file"); + return; + } + iovec_t b = random_data; + f.writev(0, b, ec); + fs.add_file("test_file", 16000); + + int port = start_web_server(); + + // generate a torrent with pad files to make sure they + // are not requested web seeds + lt::create_torrent t(fs, piece_size, 0x4000); + + char tmp[512]; + std::snprintf(tmp, sizeof(tmp), "http://127.0.0.1:%d/redirect", port); + t.add_url_seed(tmp); + + // calculate the hash for all pieces + set_piece_hashes(t, ".", ec); + + if (ec) + { + std::printf("error creating hashes for test torrent: %s\n" + , ec.message().c_str()); + TEST_ERROR("failed to create hashes"); + return; + } + + std::vector buf; + bencode(std::back_inserter(buf), t.generate()); + auto torrent_file = std::make_shared(buf, ec, from_span); + + { + settings_pack p = settings(); + p.set_int(settings_pack::max_queued_disk_bytes, 256 * 1024); + lt::session ses(p); + + // disable keep-alive because otherwise the test will choke on seeing + // the disconnect (from the redirect) + test_transfer(ses, torrent_file, 0, "http", true, false, false, false); + } + + stop_web_server(); +} diff --git a/test/test_web_seed_socks4.cpp b/test/test_web_seed_socks4.cpp new file mode 100644 index 0000000..a9187ff --- /dev/null +++ b/test/test_web_seed_socks4.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::socks4; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1); +} + diff --git a/test/test_web_seed_socks5.cpp b/test/test_web_seed_socks5.cpp new file mode 100644 index 0000000..59d0f90 --- /dev/null +++ b/test/test_web_seed_socks5.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::socks5; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1); +} + diff --git a/test/test_web_seed_socks5_no_peers.cpp b/test/test_web_seed_socks5_no_peers.cpp new file mode 100644 index 0000000..083cd6a --- /dev/null +++ b/test/test_web_seed_socks5_no_peers.cpp @@ -0,0 +1,52 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::socks5; + +TORRENT_TEST(web_seed_socks5_no_peers_ssl) +{ +#ifdef TORRENT_USE_OPENSSL + run_http_suite(proxy, "https", false, false, false, false, false, false); +#endif +} + +TORRENT_TEST(web_seed_socks5_no_peers) +{ + run_http_suite(proxy, "http", false, false, false, false, false, false); +} + diff --git a/test/test_web_seed_socks5_pw.cpp b/test/test_web_seed_socks5_pw.cpp new file mode 100644 index 0000000..76a180d --- /dev/null +++ b/test/test_web_seed_socks5_pw.cpp @@ -0,0 +1,62 @@ +/* + +Copyright (c) 2008, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "web_seed_suite.hpp" + +using namespace lt; + +const int proxy = lt::settings_pack::socks5_pw; + +#ifdef TORRENT_USE_OPENSSL +TORRENT_TEST(http_seed_ssl) +{ + run_http_suite(proxy, "https", 0); +} + +TORRENT_TEST(url_seed_ssl) +{ + run_http_suite(proxy, "https", 1); +} +#endif + +TORRENT_TEST(http_seed) +{ + run_http_suite(proxy, "http", 0); +} + +TORRENT_TEST(url_seed) +{ + run_http_suite(proxy, "http", 1); +} + diff --git a/test/test_xml.cpp b/test/test_xml.cpp new file mode 100644 index 0000000..ccdd5e5 --- /dev/null +++ b/test/test_xml.cpp @@ -0,0 +1,439 @@ +/* + +Copyright (c) 2012, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/xml_parse.hpp" +#include "libtorrent/upnp.hpp" +#include "test.hpp" +#include +#include + +namespace { + +char upnp_xml[] = +"" +"" +"1" +"0" +"" +"http://192.168.0.1:5678" +"" +"" +"urn:schemas-upnp-org:device:InternetGatewayDevice:1" +"" +"http://192.168.0.1:80" +"D-Link Router" +"D-Link" +"http://www.dlink.com" +"Internet Access Router" +"D-Link Router" +"uuid:upnp-InternetGatewayDevice-1_0-12345678900001" +"123456789001" +"" +"" +"urn:schemas-upnp-org:service:Layer3Forwarding:1" +"urn:upnp-org:serviceId:L3Forwarding1" +"/Layer3Forwarding" +"/Layer3Forwarding" +"/Layer3Forwarding.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANDevice:1" +"WANDevice" +"D-Link" +"http://www.dlink.com" +"Internet Access Router" +"D-Link Router" +"1" +"http://support.dlink.com" +"12345678900001" +"uuid:upnp-WANDevice-1_0-12345678900001" +"123456789001" +"" +"" +"" +"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" +"" +"urn:upnp-org:serviceId:WANCommonInterfaceConfig" +"/WANCommonInterfaceConfig" +"/WANCommonInterfaceConfig" +"/WANCommonInterfaceConfig.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANConnectionDevice:1" +"WAN Connection Device" +"D-Link" +"http://www.dlink.com" +"Internet Access Router" +"D-Link Router" +"1" +"http://support.dlink.com" +"12345678900001" +"uuid:upnp-WANConnectionDevice-1_0-12345678900001" +"123456789001" +"" +"" +"urn:schemas-upnp-org:service:WANIPConnection:1" +"urn:upnp-org:serviceId:WANIPConnection" +"/WANIPConnection" +"/WANIPConnection" +"/WANIPConnection.xml" +"" +"" +"" +"" +"" +"" +"" +""; + +char upnp_xml2[] = +"" +"" +"1" +"0" +"" +"http://192.168.1.1:49152" +"" +"" +"urn:schemas-upnp-org:device:InternetGatewayDevice:1" +"" +"LINKSYS WAG200G Gateway" +"LINKSYS" +"http://www.linksys.com" +"LINKSYS WAG200G Gateway" +"Wireless-G ADSL Home Gateway" +"WAG200G" +"http://www.linksys.com" +"123456789" +"uuid:8d401597-1dd2-11b2-a7d4-001ee5947cac" +"WAG200G" +"" +"" +"urn:schemas-upnp-org:service:Layer3Forwarding:1" +"urn:upnp-org:serviceId:L3Forwarding1" +"/upnp/control/L3Forwarding1" +"/upnp/event/L3Forwarding1" +"/l3frwd.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANDevice:1" +"WANDevice" +"LINKSYS" +"http://www.linksys.com/" +"Residential Gateway" +"Internet Connection Sharing" +"1" +"http://www.linksys.com/" +"0000001" +"uuid:8d401596-1dd2-11b2-a7d4-001ee5947cac" +"WAG200G" +"" +"" +"" +"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" +"" +"urn:upnp-org:serviceId:WANCommonIFC1" +"/upnp/control/WANCommonIFC1" +"/upnp/event/WANCommonIFC1" +"/cmnicfg.xml" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:WANConnectionDevice:1" +"WANConnectionDevice" +"LINKSYS" +"http://www.linksys.com/" +"Residential Gateway" +"Internet Connection Sharing" +"1" +"http://www.linksys.com/" +"0000001" +"uuid:8d401597-1dd2-11b2-a7d3-001ee5947cac" +"WAG200G" +"" +"" +"" +"urn:schemas-upnp-org:service:WANEthernetLinkConfig:1" +"" +"urn:upnp-org:serviceId:WANEthLinkC1" +"/upnp/control/WANEthLinkC1" +"/upnp/event/WANEthLinkC1" +"/wanelcfg.xml" +"" +"" +"urn:schemas-upnp-org:service:WANPPPConnection:1" +"urn:upnp-org:serviceId:WANPPPConn1" +"/upnp/control/WANPPPConn1" +"/upnp/event/WANPPPConn1" +"/pppcfg.xml" +"" +"" +"" +"" +"" +"" +"urn:schemas-upnp-org:device:LANDevice:1" +"LANDevice" +"LINKSYS" +"http://www.linksys.com/" +"Residential Gateway" +"Residential Gateway" +"1" +"http://www.linksys.com/" +"0000001" +"uuid:8d401596-1dd2-11b2-a7d3-001ee5947cac" +"WAG200G" +"" +"" +"" +"urn:schemas-upnp-org:service:LANHostConfigManagement:1" +"" +"urn:upnp-org:serviceId:LANHostCfg1" +"/upnp/control/LANHostCfg1" +"/upnp/event/LANHostCfg1" +"/lanhostc.xml" +"" +"" +"" +"" +"http://192.168.1.1/index.htm" +"" +""; + +char upnp_xml3[] = +"" +"" +"" +"s:Client" +"UPnPError" +"" +"" +"402" +"Invalid Args" +"" +"" +"" +"" +""; + +char upnp_xml4[] = +"" +"" +"" +"" +"123.10.20.30" +"" +"" +""; + +using namespace lt; +using namespace std::placeholders; + +void parser_callback(std::string& out, int token, string_view s + , string_view val) +{ + switch (token) + { + case xml_start_tag: out += "B"; break; + case xml_end_tag: out += "F"; break; + case xml_empty_tag: out += "E"; break; + case xml_declaration_tag: out += "D"; break; + case xml_comment: out += "C"; break; + case xml_string: out += "S"; break; + case xml_attribute: out += "A"; break; + case xml_parse_error: out += "P"; break; + case xml_tag_content: out += "T"; break; + default: TEST_CHECK(false); + } + out.append(s.begin(), s.end()); + if (token == xml_attribute) + { + TEST_CHECK(!val.empty()); + out += "V"; + out.append(val.begin(), val.end()); + } + else + { + TEST_CHECK(val.empty()); + } +} + +void test_parse(char const* in, char const* expected) +{ + std::string out; + xml_parse(in, std::bind(&parser_callback + , std::ref(out), _1, _2, _3)); + std::printf("in: %s\n out: %s\nexpected: %s\n" + , in, out.c_str(), expected); + TEST_EQUAL(out, expected); +} + +} // anonymous namespace + +TORRENT_TEST(upnp_parser1) +{ + parse_state xml_s; + xml_parse(upnp_xml, std::bind(&find_control_url, _1, _2, std::ref(xml_s))); + + std::cout << "namespace " << xml_s.service_type << std::endl; + std::cout << "url_base: " << xml_s.url_base << std::endl; + std::cout << "control_url: " << xml_s.control_url << std::endl; + std::cout << "model: " << xml_s.model << std::endl; + TEST_EQUAL(xml_s.url_base, "http://192.168.0.1:5678"); + TEST_EQUAL(xml_s.control_url, "/WANIPConnection"); + TEST_EQUAL(xml_s.model, "D-Link Router"); +} + +TORRENT_TEST(upnp_parser2) +{ + parse_state xml_s; + xml_parse(upnp_xml2, std::bind(&find_control_url, _1, _2, std::ref(xml_s))); + + std::cout << "namespace " << xml_s.service_type << std::endl; + std::cout << "url_base: " << xml_s.url_base << std::endl; + std::cout << "control_url: " << xml_s.control_url << std::endl; + std::cout << "model: " << xml_s.model << std::endl; + TEST_EQUAL(xml_s.url_base, "http://192.168.1.1:49152"); + TEST_EQUAL(xml_s.control_url, "/upnp/control/WANPPPConn1"); + TEST_EQUAL(xml_s.model, "Wireless-G ADSL Home Gateway"); +} + +TORRENT_TEST(upnp_parser3) +{ + error_code_parse_state xml_s; + xml_parse(upnp_xml3, std::bind(&find_error_code, _1, _2, std::ref(xml_s))); + + std::cout << "error_code " << xml_s.error_code << std::endl; + TEST_EQUAL(xml_s.error_code, 402); +} + +TORRENT_TEST(upnp_parser4) +{ + ip_address_parse_state xml_s; + xml_parse(upnp_xml4, std::bind(&find_ip_address, _1, _2, std::ref(xml_s))); + + std::cout << "error_code " << xml_s.error_code << std::endl; + std::cout << "ip_address " << xml_s.ip_address << std::endl; + TEST_EQUAL(xml_s.error_code, -1); + TEST_EQUAL(xml_s.ip_address, "123.10.20.30"); +} + +TORRENT_TEST(tags) +{ + test_parse("foobar", "BaSfooEbSbarFa"); +} + +TORRENT_TEST(xml_tag_comment) +{ + test_parse("" + , "DxmlAversionV1.0EcAxV1AyV3BdAfooVbarFdAbooVfooCcomment"); +} + +TORRENT_TEST(empty_tag) +{ + test_parse("", "Efoo"); +} + +TORRENT_TEST(empty_tag_whitespace) +{ + test_parse("", "Efoo"); +} + +TORRENT_TEST(xml_tag_no_attribute) +{ + test_parse("", "Dxml"); +} + +TORRENT_TEST(xml_tag_no_attribute_whitespace) +{ + test_parse("", "Dxml"); +} + +TORRENT_TEST(attribute_missing_qoute) +{ + test_parse("foo +#include +#include "setup_transfer.hpp" + +#include + +using namespace lt; + +namespace { + +int peer_disconnects = 0; + +bool on_alert(alert const* a) +{ + if (alert_cast(a)) + ++peer_disconnects; + else if (alert_cast(a)) + ++peer_disconnects; + + return false; +} + +static char const* proxy_name[] = {"", "_socks4", "_socks5", "_socks5_pw", "_http", "_http_pw", "_i2p"}; + +} // anonymous namespace + +// proxy: 0=none, 1=socks4, 2=socks5, 3=socks5_pw 4=http 5=http_pw +void test_transfer(lt::session& ses, std::shared_ptr torrent_file + , int proxy, char const* protocol, bool url_seed + , bool chunked_encoding, bool test_ban, bool keepalive, bool proxy_peers) +{ + using namespace lt; + + TORRENT_ASSERT(torrent_file->web_seeds().size() > 0); + + std::string save_path = "tmp2_web_seed"; + save_path += proxy_name[proxy]; + + error_code ec; + remove_all(save_path, ec); + + static char const* test_name[] = {"no", "SOCKS4", "SOCKS5", "SOCKS5 password", "HTTP", "HTTP password"}; + + std::printf("\n\n ==== TESTING === proxy: %s ==== protocol: %s " + "==== seed: %s === transfer-encoding: %s === corruption: %s " + "==== keepalive: %s\n\n\n" + , test_name[proxy], protocol, url_seed ? "URL seed" : "HTTP seed" + , chunked_encoding ? "chunked": "none", test_ban ? "yes" : "no" + , keepalive ? "yes" : "no"); + + int proxy_port = 0; + if (proxy) + { + proxy_port = start_proxy(proxy); + if (proxy_port < 0) + { + std::printf("failed to start proxy"); + return; + } + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, "127.0.0.1"); + pack.set_str(settings_pack::proxy_username, "testuser"); + pack.set_str(settings_pack::proxy_password, "testpass"); + pack.set_int(settings_pack::proxy_type, proxy); + pack.set_int(settings_pack::proxy_port, proxy_port); + pack.set_bool(settings_pack::proxy_peer_connections, proxy_peers); + ses.apply_settings(pack); + } + else + { + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, ""); + pack.set_str(settings_pack::proxy_username, ""); + pack.set_str(settings_pack::proxy_password, ""); + pack.set_int(settings_pack::proxy_type, settings_pack::none); + pack.set_int(settings_pack::proxy_port, 0); + pack.set_bool(settings_pack::proxy_peer_connections, proxy_peers); + ses.apply_settings(pack); + } + + add_torrent_params p; + p.flags &= ~torrent_flags::paused; + p.flags &= ~torrent_flags::auto_managed; + + // the reason to set sequential download is to make sure that the order in + // which files are requested from the web server is consistent. Any specific + // scenario that needs testing should be an explicit test case + p.flags |= torrent_flags::sequential_download; + p.ti = torrent_file; + p.save_path = save_path; + torrent_handle th = ses.add_torrent(p, ec); + std::printf("adding torrent, save_path = \"%s\" cwd = \"%s\" torrent = \"%s\"\n" + , save_path.c_str(), current_working_directory().c_str() + , torrent_file->name().c_str()); + + std::vector empty; + th.replace_trackers(empty); + + const std::int64_t total_size = torrent_file->total_size(); + + file_storage const& fs = torrent_file->files(); + int pad_file_size = 0; + for (auto const i : fs.file_range()) + { + if (fs.file_flags(i) & file_storage::flag_pad_file) + pad_file_size += int(fs.file_size(i)); + } + + peer_disconnects = 0; + std::map cnt = get_counters(ses); + + for (int i = 0; i < 40; ++i) + { + torrent_status s = th.status(); + + cnt = get_counters(ses); + + print_ses_rate(i / 10.f, &s, nullptr); + print_alerts(ses, " >> ses", false, false, &on_alert); + + if (test_ban && th.url_seeds().empty() && th.http_seeds().empty()) + { + std::printf("testing ban: URL seed removed\n"); + // when we don't have any web seeds left, we know we successfully banned it + break; + } + + if (s.is_seeding) + { + std::printf("SEEDING\n"); + std::printf("session.payload: %d session.redundant: %d\n" + , int(cnt["net.recv_payload_bytes"]), int(cnt["net.recv_redundant_bytes"])); + std::printf("torrent.payload: %d torrent.redundant: %d\n" + , int(s.total_payload_download), int(s.total_redundant_bytes)); + + TEST_EQUAL(s.total_payload_download - s.total_redundant_bytes, total_size - pad_file_size); + break; + } + + // if the web seed connection is disconnected, we're going to fail + // the test. make sure to do so quickly + if (!test_ban && keepalive && peer_disconnects >= 1) break; + + std::this_thread::sleep_for(lt::milliseconds(100)); + } + + cnt = get_counters(ses); + + if (test_ban) + { + // for test_ban tests, make sure we removed + // the url seed (i.e. banned it) + // torrents that don't have very many pieces will not ban the web seeds, + // since they won't have an opportunity to accrue enough negative points + if (torrent_file->files().num_pieces() > 3) + { + TEST_CHECK(th.url_seeds().empty()); + TEST_CHECK(th.http_seeds().empty()); + } + } + else + { + // if the web seed sent corrupt data and we banned it, we probably didn't + // end up using all the cache anyway + torrent_status st = th.status(); + TEST_EQUAL(st.is_seeding, true); + + if (st.is_seeding) + { + std::int64_t const total_blocks = (torrent_file->total_size() + 0x3fff) / 0x4000; + // we need to sleep here a bit to let the session sync with the torrent stats + // commented out because it takes such a long time + for (int i = 0; i < 50; ++i) + { + cnt = get_counters(ses); + if (std::abs(int(cnt["disk.read_cache_blocks"] - total_blocks)) <= 2 && + std::abs(int(cnt["disk.disk_blocks_in_use"] - total_blocks)) <= 2) + break; + std::printf("cache_size: %d/%d\n", int(cnt["disk.read_cache_blocks"]) + , int(cnt["disk.disk_blocks_in_use"])); + std::this_thread::sleep_for(lt::milliseconds(100)); + } + TEST_CHECK(std::abs(int(cnt["disk.disk_blocks_in_use"] - total_blocks)) <= 2); + } + } + + std::cout << "total_size: " << total_size + << " read cache size: " << cnt["disk.disk_blocks_in_use"] + << " total used buffer: " << cnt["disk.disk_blocks_in_use"] + << " session total download: " << cnt["net.recv_payload_bytes"] + << " torrent total download: " << th.status().total_payload_download + << " redundant: " << th.status().total_redundant_bytes + << std::endl; + + // if test_ban is true, we're not supposed to have completed the download + // otherwise, we are supposed to have + TEST_CHECK(th.status().is_seeding == !test_ban); + + if (proxy) stop_proxy(proxy_port); + + th.flush_cache(); + + // synchronize to make sure the files have been created on disk + wait_for_alert(ses, cache_flushed_alert::alert_type, "ses"); + + print_alerts(ses, " >> ses", true, false, &on_alert, true); + + if (!test_ban) + { + for (auto const i : fs.file_range()) + { + bool const expect = !fs.pad_file_at(i); + std::string file_path = combine_path(save_path, fs.file_path(i)); + std::printf("checking file: %s\n", file_path.c_str()); + TEST_EQUAL(exists(file_path), expect); + } + } + + ses.remove_torrent(th); +} + +// proxy: 0=none, 1=socks4, 2=socks5, 3=socks5_pw 4=http 5=http_pw +// protocol: "http" or "https" +// test_url_seed determines whether to use url-seed or http-seed +int EXPORT run_http_suite(int proxy, char const* protocol, bool test_url_seed + , bool chunked_encoding, bool test_ban, bool keepalive, bool test_rename + , bool proxy_peers) +{ + using namespace lt; + + std::string save_path = "web_seed"; + save_path += proxy_name[proxy]; + + error_code ec; + int const port = start_web_server(protocol == "https"_sv, chunked_encoding, keepalive); + + std::vector test_cases; + + if (test_url_seed) + { + char url[512]; + std::snprintf(url, sizeof(url), "%s://127.0.0.1:%d/%s", protocol, port, save_path.c_str()); + std::printf("testing: %s\n", url); + + create_directories(combine_path(save_path, "torrent_dir"), ec); + + torrent_args args; + + // test case 1 + test_cases.push_back(torrent_args().file("0").file("5,padfile").file("11") + .file("16000").file("368,padfile") + .file("16384,padfile").file("16384,padfile").file("17").file("10") + .file("8000").file("8000").file("1").file("1").file("1").file("1") + .file("1").file("100").file("0").file("1").file("1").file("1") + .file("100").file("1").file("1").file("1").file("1").file("1,padfile") + .file("1,padfile").file("1,padfile").file("1").file("0").file("0") + .file("0").file("1").file("13").file("65000").file("34").file("75") + .file("2").file("30").file("400").file("500").file("23000") + .file("900").file("43000").file("400").file("4300").file("6") + .file("4,padfile") + .name("torrent_dir") + .url_seed(url)); + + // test case 2 (the end of the torrent are padfiles) + test_cases.push_back(torrent_args() + .file("0,padfile") + .file("11") + .file("5") + .file("16000") + .file("368,padfile") + .file("16384,padfile") + .name("torrent_dir") + .url_seed(url)); + + // test case 3 (misaligned) + test_cases.push_back(torrent_args() + .file("16383") + .file("11") + .file("5") + .file("16000") + .name("torrent_dir") + .url_seed(url)); + + // test case 4 (a full piece padfile) + test_cases.push_back(torrent_args() + .file("32768,padfile") + .file("16000") + .file("11") + .file("5") + .name("torrent_dir") + .url_seed(url)); + + // test case 5 (properly aligned padfile) + test_cases.push_back(torrent_args() + .file("32760") + .file("8,padfile") + .file("32760") + .file("8") + .file("32700") + .file("68,padfile") + .file("32000") + .name("torrent_dir") + .url_seed(url)); + + std::snprintf(url, sizeof(url), "%s://127.0.0.1:%d/%s/test-single-file" + , protocol, port, save_path.c_str()); + + // test case 6 (single file torrent) + test_cases.push_back(torrent_args() + .file("199092,name=test-single-file") + .name("torrent_dir") + .url_seed(url)); + } + else + { + char url[512]; + std::snprintf(url, sizeof(url), "%s://127.0.0.1:%d/%s/seed", protocol, port, save_path.c_str()); + std::printf("testing: %s\n", url); + + // there's really just one test case for http seeds + test_cases.push_back(torrent_args().file("589824,name=seed") + .http_seed(url)); + } + + int idx = 0; + for (auto const& c : test_cases) + { + std::printf("\n\n ==== test case %d ====\n\n\n", idx++); + + std::shared_ptr torrent_file = make_test_torrent(c); + + // if test_ban is true, we create the files with alternate content (that + // doesn't match the hashes in the .torrent file) + generate_files(*torrent_file, save_path, test_ban); + + if (ec) + { + std::printf("error creating hashes for test torrent: %s\n" + , ec.message().c_str()); + TEST_CHECK(false); + return 0; + } + + { + settings_pack pack = settings(); + pack.set_int(settings_pack::max_queued_disk_bytes, 256 * 1024); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:51000"); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_dht, false); + lt::session ses(pack, {}); + + test_transfer(ses, torrent_file, proxy, protocol, test_url_seed + , chunked_encoding, test_ban, keepalive, proxy_peers); + + if (test_url_seed && test_rename) + { + torrent_file->rename_file(file_index_t(0), combine_path(save_path, combine_path("torrent_dir", "renamed_test1"))); + test_transfer(ses, torrent_file, 0, protocol, test_url_seed + , chunked_encoding, test_ban, keepalive, proxy_peers); + } + } + } + + stop_web_server(); + return 0; +} diff --git a/test/web_seed_suite.hpp b/test/web_seed_suite.hpp new file mode 100644 index 0000000..0d1cafe --- /dev/null +++ b/test/web_seed_suite.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2008-2013, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "test.hpp" + +int EXPORT run_http_suite(int proxy, char const* protocol + , bool test_url_seed, bool chunked_encoding = false, bool test_ban = false + , bool keepalive = true, bool test_rename = false, bool proxy_peers = true); + +void EXPORT test_transfer(lt::session& ses + , std::shared_ptr torrent_file + , int proxy = 0, char const* protocol = "http" + , bool url_seed = true, bool chunked_encoding = false + , bool test_ban = false, bool keepalive = true, bool proxy_peers = true); diff --git a/test/web_server.py b/test/web_server.py new file mode 100644 index 0000000..d142ff2 --- /dev/null +++ b/test/web_server.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 + +import sys +import os +import ssl +import gzip +import base64 +import socket +import traceback + +from http.server import HTTPServer, BaseHTTPRequestHandler + +chunked_encoding = False +keepalive = True + +try: + fin = open('test_file', 'rb') + f = gzip.open('test_file.gz', 'wb') + f.writelines(fin) + f.close() + fin.close() +except Exception: + pass + + +class http_server_with_timeout(HTTPServer): + allow_reuse_address = True + timeout = 190 + + def handle_timeout(self): + raise Exception('timeout') + + +class http_handler(BaseHTTPRequestHandler): + + def do_GET(self): + try: + self.inner_do_GET() + except Exception: + print('EXCEPTION') + traceback.print_exc(file=sys.stdout) + sys.stdout.flush() + + def inner_do_GET(s): + + print('INCOMING-REQUEST [from: {}]: {}'.format(s.request.getsockname(), s.requestline)) + print(s.headers) + sys.stdout.flush() + + global chunked_encoding + global keepalive + + # if the request contains the hostname and port. strip it + if s.path.startswith('http://') or s.path.startswith('https://'): + s.path = s.path[8:] + s.path = s.path[s.path.find('/'):] + + file_path = os.path.normpath(s.path) + sys.stdout.flush() + + if s.path == '/password_protected': + passed = False + if 'Authorization' in s.headers: + auth = s.headers['Authorization'] + passed = auth == 'Basic %s' % base64.b64encode(b'testuser:testpass').decode() + + if not passed: + s.send_response(401) + s.send_header("Connection", "close") + s.end_headers() + return + + s.path = '/test_file' + file_path = os.path.normpath('/test_file') + + if s.path == '/redirect': + s.send_response(301) + s.send_header("Location", "/test_file") + s.send_header("Connection", "close") + s.end_headers() + elif s.path == '/infinite_redirect': + s.send_response(301) + s.send_header("Location", "/infinite_redirect") + s.send_header("Connection", "close") + s.end_headers() + elif s.path == '/relative/redirect': + s.send_response(301) + s.send_header("Location", "../test_file") + s.send_header("Connection", "close") + s.end_headers() + elif s.path.startswith('/announce'): + s.send_response(200) + response = b'd8:intervali1800e8:completei1e10:incompletei1e' + \ + b'12:min intervali' + min_interval.encode() + b'e' + \ + b'5:peers12:AAAABBCCCCDD' + \ + b'6:peers618:EEEEEEEEEEEEEEEEFF' + \ + b'e' + s.send_header("Content-Length", "%d" % len(response)) + s.send_header("Connection", "close") + s.end_headers() + s.wfile.write(response) + elif os.path.split(s.path)[1].startswith('seed?'): + query = s.path[6:] + args_raw = query.split('&') + args = {} + for a in args_raw: + kvp = a.split('=') + args[kvp[0]] = kvp[1] + piece = int(args['piece']) + ranges = args['ranges'].split('-') + + filename = '' + try: + filename = os.path.normpath(s.path[1:s.path.find('seed?') + 4]) + print('filename = %s' % filename) + sys.stdout.flush() + f = open(filename, 'rb') + f.seek(piece * 32 * 1024 + int(ranges[0])) + data = f.read(int(ranges[1]) - int(ranges[0]) + 1) + f.close() + + s.send_response(200) + print('sending %d bytes' % len(data)) + sys.stdout.flush() + s.send_header("Content-Length", "%d" % len(data)) + s.end_headers() + s.wfile.write(data) + except Exception as e: + print('FILE ERROR: ', filename, e) + traceback.print_exc(file=sys.stdout) + sys.stdout.flush() + s.send_response(404) + s.send_header("Content-Length", "0") + s.end_headers() + else: + filename = '' + try: + filename = os.path.normpath(file_path[1:]) + # serve file by invoking default handler + f = open(filename, 'rb') + size = int(os.stat(filename).st_size) + start_range = 0 + end_range = size + if 'Range' in s.headers: + s.send_response(206) + st, e = s.headers['range'][6:].split('-', 1) + sl = len(st) + el = len(e) + if sl > 0: + start_range = int(st) + if el > 0: + end_range = int(e) + 1 + elif el > 0: + ei = int(e) + if ei < size: + start_range = size - ei + s.send_header('Content-Range', 'bytes ' + str(start_range) + + '-' + str(end_range - 1) + '/' + str(size)) + else: + s.send_response(200) + s.send_header('Accept-Ranges', 'bytes') + if chunked_encoding: + s.send_header('Transfer-Encoding', 'chunked') + s.send_header('Content-Length', end_range - start_range) + if filename.endswith('.gz'): + s.send_header('Content-Encoding', 'gzip') + if not keepalive: + s.send_header("Connection", "close") + if not use_ssl: + s.request.shutdown(socket.SHUT_RD) + + s.end_headers() + + f.seek(start_range) + length = end_range - start_range + while length > 0: + to_send = min(length, 0x900) + if chunked_encoding: + s.wfile.write(b'%x\r\n' % to_send) + data = f.read(to_send) + print('read %d bytes' % to_send) + sys.stdout.flush() + s.wfile.write(data) + if chunked_encoding: + s.wfile.write(b'\r\n') + length -= to_send + print('sent %d bytes (%d bytes left)' % (len(data), length)) + sys.stdout.flush() + if chunked_encoding: + s.wfile.write(b'0\r\n\r\n') + except Exception as e: + print('FILE ERROR: ', filename, e) + traceback.print_exc(file=sys.stdout) + sys.stdout.flush() + s.send_response(404) + s.send_header("Content-Length", "0") + s.end_headers() + + print("...DONE") + sys.stdout.flush() + s.wfile.flush() + + +if __name__ == '__main__': + port = int(sys.argv[1]) + chunked_encoding = sys.argv[2] != '0' + use_ssl = sys.argv[3] != '0' + keepalive = sys.argv[4] != '0' + min_interval = sys.argv[5] + print('python version: %s' % sys.version_info.__str__()) + + http_handler.protocol_version = 'HTTP/1.1' + httpd = http_server_with_timeout(('127.0.0.1', port), http_handler) + if use_ssl: + httpd.socket = ssl.wrap_socket(httpd.socket, certfile='../ssl/server.pem', server_side=True) + + while True: + httpd.handle_request() diff --git a/test/zeroes.gz b/test/zeroes.gz new file mode 100644 index 0000000000000000000000000000000000000000..9c321d2350de0edb4a6a69363b151d72ddf17a8e GIT binary patch literal 538 zcmb2|=HOTykQ~gwT$NgspIXfD_Mjmn0|UdM1z-JhdE^%WnWJC?g}^>G_T?u-7%s3g F008xW4kG{n literal 0 HcmV?d00001 diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..1bb1b5e --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(dht dht_put.cpp) +target_link_libraries(dht PRIVATE torrent-rasterbar) + +add_executable(session_log_alerts session_log_alerts.cpp) +target_link_libraries(session_log_alerts PRIVATE torrent-rasterbar) diff --git a/tools/Jamfile b/tools/Jamfile new file mode 100644 index 0000000..0fee0b0 --- /dev/null +++ b/tools/Jamfile @@ -0,0 +1,46 @@ +import modules ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +use-project /torrent : .. ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; +} + +rule link_libtorrent ( properties * ) +{ + local result ; + if shared in $(properties) + { + result += + /torrent//torrent/shared/shared ; + } + else + { + result += + /torrent//torrent/static/static ; + } + return $(result) ; +} + +variant debug-mode : debug : on on full ; + +project tools + : requirements + multi +# disable warning C4275: non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + msvc:/wd4275 + # C4268: 'identifier' : 'const' static/global data initialized + # with compiler generated default constructor fills the object with zeros + msvc:/wd4268 + @link_libtorrent + : default-build + static + debug-mode + ; + +exe dht : dht_put.cpp : ../ed25519/src ; +exe session_log_alerts : session_log_alerts.cpp ; + diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..bdcca2d --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,30 @@ +tool_programs = \ + dht_put \ + session_log_alerts + +if ENABLE_EXAMPLES +bin_PROGRAMS = $(tool_programs) +endif + +EXTRA_PROGRAMS = $(tool_programs) +EXTRA_DIST = Jamfile \ + parse_dht_log.py \ + parse_dht_rtt.py \ + parse_dht_stats.py \ + parse_peer_log.py \ + parse_sample.py \ + parse_session_stats.py \ + parse_utp_log.py \ + CMakeLists.txt + +session_log_alerts_SOURCES = session_log_alerts.cpp +dht_put_SOURCES = dht_put.cpp + +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la + +AM_CPPFLAGS = -ftemplate-depth-50 @DEBUGFLAGS@ + +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ +#AM_LDFLAGS = $(LDFLAGS) @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +#AM_LDFLAGS = @OPENSSL_LDFLAGS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include diff --git a/tools/Makefile.in b/tools/Makefile.in new file mode 100644 index 0000000..98eadf5 --- /dev/null +++ b/tools/Makefile.in @@ -0,0 +1,728 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@ENABLE_EXAMPLES_TRUE@bin_PROGRAMS = $(am__EXEEXT_1) +EXTRA_PROGRAMS = $(am__EXEEXT_1) +subdir = tools +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_boost_base.m4 \ + $(top_srcdir)/m4/ax_boost_python.m4 \ + $(top_srcdir)/m4/ax_boost_system.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_devel.m4 \ + $(top_srcdir)/m4/gettext-lib.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkgconfig.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__EXEEXT_1 = dht_put$(EXEEXT) session_log_alerts$(EXEEXT) +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_dht_put_OBJECTS = dht_put.$(OBJEXT) +dht_put_OBJECTS = $(am_dht_put_OBJECTS) +dht_put_LDADD = $(LDADD) +dht_put_DEPENDENCIES = $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +am_session_log_alerts_OBJECTS = session_log_alerts.$(OBJEXT) +session_log_alerts_OBJECTS = $(am_session_log_alerts_OBJECTS) +session_log_alerts_LDADD = $(LDADD) +session_log_alerts_DEPENDENCIES = \ + $(top_builddir)/src/libtorrent-rasterbar.la +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/dht_put.Po \ + ./$(DEPDIR)/session_log_alerts.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(dht_put_SOURCES) $(session_log_alerts_SOURCES) +DIST_SOURCES = $(dht_put_SOURCES) $(session_log_alerts_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDFLAGS = @BOOST_LDFLAGS@ +BOOST_PYTHON_LIB = @BOOST_PYTHON_LIB@ +BOOST_SYSTEM_LIB = @BOOST_SYSTEM_LIB@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +COMPILETIME_OPTIONS = @COMPILETIME_OPTIONS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEBUGFLAGS = @DEBUGFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +ICONV_LIBS = @ICONV_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTERFACE_VERSION_INFO = @INTERFACE_VERSION_INFO@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBICONV = @LIBICONV@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_INCLUDES = @OPENSSL_INCLUDES@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTHON = @PYTHON@ +PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@ +PYTHON_CXXFLAGS = @PYTHON_CXXFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@ +PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@ +PYTHON_INSTALL_PARAMS = @PYTHON_INSTALL_PARAMS@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_SITE_PKG = @PYTHON_SITE_PKG@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +tool_programs = \ + dht_put \ + session_log_alerts + +EXTRA_DIST = Jamfile \ + parse_dht_log.py \ + parse_dht_rtt.py \ + parse_dht_stats.py \ + parse_peer_log.py \ + parse_sample.py \ + parse_session_stats.py \ + parse_utp_log.py \ + CMakeLists.txt + +session_log_alerts_SOURCES = session_log_alerts.cpp +dht_put_SOURCES = dht_put.cpp +LDADD = $(top_builddir)/src/libtorrent-rasterbar.la +AM_CPPFLAGS = -ftemplate-depth-50 @DEBUGFLAGS@ +AM_LDFLAGS = @BOOST_SYSTEM_LIB@ +#AM_LDFLAGS = $(LDFLAGS) @BOOST_SYSTEM_LIB@ @OPENSSL_LDFLAGS@ @OPENSSL_LIBS@ +#AM_LDFLAGS = @OPENSSL_LDFLAGS@ +DEFAULT_INCLUDES = -I$(top_srcdir)/include +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tools/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign tools/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +dht_put$(EXEEXT): $(dht_put_OBJECTS) $(dht_put_DEPENDENCIES) $(EXTRA_dht_put_DEPENDENCIES) + @rm -f dht_put$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(dht_put_OBJECTS) $(dht_put_LDADD) $(LIBS) + +session_log_alerts$(EXEEXT): $(session_log_alerts_OBJECTS) $(session_log_alerts_DEPENDENCIES) $(EXTRA_session_log_alerts_DEPENDENCIES) + @rm -f session_log_alerts$(EXEEXT) + $(AM_V_CXXLD)$(CXXLINK) $(session_log_alerts_OBJECTS) $(session_log_alerts_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dht_put.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session_log_alerts.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/dht_put.Po + -rm -f ./$(DEPDIR)/session_log_alerts.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/dht_put.Po + -rm -f ./$(DEPDIR)/session_log_alerts.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tools/dht_put.cpp b/tools/dht_put.cpp new file mode 100644 index 0000000..08feaf3 --- /dev/null +++ b/tools/dht_put.cpp @@ -0,0 +1,426 @@ +/* + +Copyright (c) 2014, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + + +#include "libtorrent/session.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" // for bencode() +#include "libtorrent/kademlia/item.hpp" // for sign_mutable_item +#include "libtorrent/kademlia/ed25519.hpp" +#include "libtorrent/span.hpp" + +#include +#include // for snprintf +#include // for PRId64 et.al. +#include +#include + +using namespace lt; +using namespace lt::dht; +using namespace std::placeholders; + +#ifdef TORRENT_DISABLE_DHT + +int main(int argc, char* argv[]) +{ + std::fprintf(stderr, "not built with DHT support\n"); + return 1; +} + +#else + +std::string to_hex(lt::span key) +{ + std::string out; + for (auto const b : key) + { + char buf[3]{}; + std::snprintf(buf, sizeof(buf), "%02x", static_cast(b)); + out += (char*)buf; + } + return out; +} + +int hex_to_int(char in) +{ + if (in >= '0' && in <= '9') return int(in) - '0'; + if (in >= 'A' && in <= 'F') return int(in) - 'A' + 10; + if (in >= 'a' && in <= 'f') return int(in) - 'a' + 10; + return -1; +} + +bool from_hex(span in, span out) +{ + if (in.size() != out.size() * 2) return false; + auto o = out.begin(); + for (auto i = in.begin(); i != in.end(); ++i, ++o) + { + int const t1 = hex_to_int(*i); + if (t1 == -1) return false; + ++i; + if (i == in.end()) return false; + int const t2 = hex_to_int(*i); + if (t2 == -1) return false; + *o = (t1 << 4) | (t2 & 0xf); + } + return true; +} + +namespace { +void usage() +{ + std::fprintf(stderr, + "USAGE:\ndht \n\nCOMMANDS:\n" + "get - retrieves and prints out the immutable\n" + " item stored under hash.\n" + "put - puts the specified string as an immutable\n" + " item onto the DHT. The resulting target hash\n" + "gen-key - generate ed25519 keypair and save it in\n" + " the specified file\n" + "dump-key - dump ed25519 keypair from the specified key\n" + " file.\n" + "mput [salt]\n" + " - puts the specified string as a mutable\n" + " object under the public key in key-file,\n" + " and optionally specified salt\n" + "mget [salt] - get a mutable object under the specified\n" + " public key, and salt (optional)\n" + ); + exit(1); +} + +alert* wait_for_alert(lt::session& s, int alert_type) +{ + alert* ret = nullptr; + bool found = false; + while (!found) + { + s.wait_for_alert(seconds(5)); + + std::vector alerts; + s.pop_alerts(&alerts); + for (auto const a : alerts) + { + if (a->type() != alert_type) + { + static int spinner = 0; + static const char anim[] = {'-', '\\', '|', '/'}; + std::printf("\r%c", anim[spinner]); + std::fflush(stdout); + spinner = (spinner + 1) & 3; + //print some alerts? + continue; + } + ret = a; + found = true; + } + } + std::printf("\n"); + return ret; +} + +void put_string(entry& e, std::array& sig + , std::int64_t& seq + , std::string const& salt + , std::array const& pk + , std::array const& sk + , char const* str) +{ + using lt::dht::sign_mutable_item; + + e = std::string(str); + std::vector buf; + bencode(std::back_inserter(buf), e); + dht::signature sign; + ++seq; + sign = sign_mutable_item(buf, salt, dht::sequence_number(seq) + , dht::public_key(pk.data()) + , dht::secret_key(sk.data())); + sig = sign.bytes; +} + +void bootstrap(lt::session& s) +{ + std::printf("bootstrapping\n"); + wait_for_alert(s, dht_bootstrap_alert::alert_type); + std::printf("bootstrap done.\n"); +} + +int dump_key(char const* filename) +{ + std::array seed; + + std::fstream f(filename, std::ios_base::in | std::ios_base::binary); + f.read(seed.data(), seed.size()); + if (f.fail()) + { + std::fprintf(stderr, "invalid key file.\n"); + return 1; + } + + public_key pk; + secret_key sk; + std::tie(pk, sk) = ed25519_create_keypair(seed); + + std::printf("public key: %s\nprivate key: %s\n" + , to_hex(pk.bytes).c_str() + , to_hex(sk.bytes).c_str()); + + return 0; +} + +int generate_key(char const* filename) +{ + std::array seed = ed25519_create_seed(); + + std::fstream f(filename, std::ios_base::out | std::ios_base::binary); + f.write(seed.data(), seed.size()); + if (f.fail()) + { + std::fprintf(stderr, "failed to write key file.\n"); + return 1; + } + + return 0; +} + +void load_dht_state(lt::session& s) +{ + std::fstream f(".dht", std::ios_base::in | std::ios_base::binary | std::ios_base::ate); + + auto const size = f.tellg(); + if (static_cast(size) <= 0) return; + f.seekg(0, std::ios_base::beg); + + std::vector state; + state.resize(static_cast(size)); + + f.read(state.data(), size); + if (f.fail()) + { + std::fprintf(stderr, "failed to read .dht\n"); + return; + } + + error_code ec; + bdecode_node e = bdecode(state, ec); + if (ec) + { + std::fprintf(stderr, "failed to parse .dht file: (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + else + { + std::printf("load dht state from .dht\n"); + s.load_state(e); + } +} + +int save_dht_state(lt::session& s) +{ + entry e; + s.save_state(e, session::save_dht_state); + std::vector state; + bencode(std::back_inserter(state), e); + + std::fstream f(".dht", std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + f.write(state.data(), static_cast(state.size())); + return 0; +} +} // anonymous namespace + +int main(int argc, char* argv[]) +{ + // skip pointer to self + ++argv; + --argc; + + if (argc < 1) usage(); + + if (argv[0] == "dump-key"_sv) + { + ++argv; + --argc; + if (argc < 1) usage(); + + return dump_key(argv[0]); + } + + if (argv[0] == "gen-key"_sv) + { + ++argv; + --argc; + if (argc < 1) usage(); + + return generate_key(argv[0]); + } + + settings_pack sett; + sett.set_bool(settings_pack::enable_dht, false); + sett.set_int(settings_pack::alert_mask, 0x7fffffff); + lt::session s(sett); + + sett.set_bool(settings_pack::enable_dht, true); + s.apply_settings(sett); + + load_dht_state(s); + + if (argv[0] == "get"_sv) + { + ++argv; + --argc; + + if (argc < 1) usage(); + + if (strlen(argv[0]) != 40) + { + std::fprintf(stderr, "the hash is expected to be 40 hex characters\n"); + usage(); + } + sha1_hash target; + if (!from_hex({argv[0], 40}, target)) + { + std::fprintf(stderr, "invalid hex encoding of target hash\n"); + return 1; + } + + bootstrap(s); + s.dht_get_item(target); + + std::printf("GET %s\n", to_hex(target).c_str()); + + alert* a = wait_for_alert(s, dht_immutable_item_alert::alert_type); + + dht_immutable_item_alert* item = alert_cast(a); + + std::string str = item->item.to_string(); + std::printf("%s\n", str.c_str()); + } + else if (argv[0] == "put"_sv) + { + ++argv; + --argc; + if (argc < 1) usage(); + + entry data; + data = std::string(argv[0]); + + bootstrap(s); + sha1_hash const target = s.dht_put_item(data); + + std::printf("PUT %s\n", to_hex(target).c_str()); + + alert* a = wait_for_alert(s, dht_put_alert::alert_type); + dht_put_alert* pa = alert_cast(a); + std::printf("%s\n", pa->message().c_str()); + } + else if (argv[0] == "mput"_sv) + { + ++argv; + --argc; + if (argc < 1) usage(); + + std::array seed; + + std::fstream f(argv[0], std::ios_base::in | std::ios_base::binary); + f.read(seed.data(), seed.size()); + + ++argv; + --argc; + if (argc < 1) usage(); + + public_key pk; + secret_key sk; + std::tie(pk, sk) = ed25519_create_keypair(seed); + std::string salt; + + if (argc > 1) salt = argv[1]; + + bootstrap(s); + s.dht_put_item(pk.bytes, std::bind(&put_string, _1, _2, _3, _4 + , pk.bytes, sk.bytes, argv[0]), salt); + + std::printf("MPUT public key: %s [salt: %s]\n", to_hex(pk.bytes).c_str() + , salt.c_str()); + + alert* a = wait_for_alert(s, dht_put_alert::alert_type); + dht_put_alert* pa = alert_cast(a); + std::printf("%s\n", pa->message().c_str()); + } + else if (argv[0] == "mget"_sv) + { + ++argv; + --argc; + if (argc < 1) usage(); + + auto const len = static_cast(strlen(argv[0])); + if (len != 64) + { + std::fprintf(stderr, "public key is expected to be 64 hex digits\n"); + return 1; + } + std::array public_key; + if (!from_hex({argv[0], len}, public_key)) + { + std::fprintf(stderr, "invalid hex encoding of public key\n"); + return 1; + } + + std::string salt; + if (argc > 1) salt = argv[1]; + + bootstrap(s); + s.dht_get_item(public_key, salt); + std::printf("MGET %s [salt: %s]\n", argv[0], salt.c_str()); + + bool authoritative = false; + + while (!authoritative) + { + alert* a = wait_for_alert(s, dht_mutable_item_alert::alert_type); + + dht_mutable_item_alert* item = alert_cast(a); + + authoritative = item->authoritative; + std::string str = item->item.to_string(); + std::printf("%s: %s\n", authoritative ? "auth" : "non-auth", str.c_str()); + } + } + else + { + usage(); + } + + return save_dht_state(s); +} + +#endif diff --git a/tools/parse_dht_log.py b/tools/parse_dht_log.py new file mode 100755 index 0000000..e45e02c --- /dev/null +++ b/tools/parse_dht_log.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +from __future__ import print_function + +import sys +import os +import pprint + +pp = pprint.PrettyPrinter(indent=4) + +up_time_quanta = 500 + +f = open(sys.argv[1]) + +announce_histogram = {} + +# TODO: make this histogram into a CDF + +node_uptime_histogram = {} + +counter = 0 + +# maps search_id to a list of events. Each event is a dict containing: +# t: timestamp +# d: distance (from target) +# o: outstanding searches +# e: event (NEW, COMPLETED, ADD, INVOKE, TIMEOUT) +# i: node-id +# a: IP address and port +# s: source node-id (only for ADD events) +outstanding_searches = {} + +# list of completed searches +searches = [] + + +def convert_timestamp(t): + parts = t.split('.') + hms = parts[0].split(':') + return (int(hms[0]) * 3600 + int(hms[1]) * 60 + int(hms[2])) * 1000 + int(parts[1]) + + +last_incoming = '' + +our_node_id = '' + +unique_ips = set() +client_version_histogram = {} +client_histogram = {} + +for line in f: + counter += 1 + # if counter % 1000 == 0: + # print '\r%d' % counter, + try: + ls = line.split(' ') + if 'starting DHT tracker with node id:' in line: + our_node_id = ls[ls.index('id:') + 1].strip() + + try: + if len(ls) > 4 and ls[2] == '<==' and ls[1] == '[dht_tracker]': + ip = ls[3].split(':')[0] + if ip not in unique_ips: + unique_ips.add(ip) + json_blob = line.split(ls[3])[1] + version = json_blob.split("'v': '")[1].split("'")[0] + if len(version) == 4: + v = '%s-%d' % (version[0:2], (ord(version[2]) << 8) + ord(version[3])) + elif len(version) == 8: + v = '%c%c-%d' % (chr(int(version[0:2], 16)), chr(int(version[2:4], 16)), int(version[4:8], 16)) + else: + v = 'unknown' + + if v not in client_version_histogram: + client_version_histogram[v] = 1 + else: + client_version_histogram[v] += 1 + + if not v[0:2] in client_histogram: + client_histogram[v[0:2]] = 1 + else: + client_histogram[v[0:2]] += 1 + except Exception: + pass + + if 'announce-distance:' in line: + idx = ls.index('announce-distance:') + + d = int(ls[idx + 1].strip()) + if d not in announce_histogram: + announce_histogram[d] = 0 + announce_histogram[d] += 1 + if 'NODE FAILED' in line: + idx = ls.index('fails:') + if int(ls[idx + 1].strip()) != 1: + continue + idx = ls.index('up-time:') + d = int(ls[idx + 1].strip()) + # quantize + d = d - (d % up_time_quanta) + if d not in node_uptime_histogram: + node_uptime_histogram[d] = 0 + node_uptime_histogram[d] += 1 + + search_id = ls[2] + ts = ls[0] + event = ls[3] + + if event == 'RESPONSE': + outstanding = int(ls[ls.index('invoke-count:') + 1]) + distance = int(ls[ls.index('distance:') + 1]) + nid = ls[ls.index('id:') + 1] + addr = ls[ls.index('addr:') + 1] + last_response = addr + outstanding_searches[search_id].append({'t': ts, 'd': distance, + 'o': outstanding + 1, 'a': addr, 'e': event, 'i': nid}) + elif event == 'NEW': + nid = ls[ls.index('target:') + 1] + outstanding_searches[search_id] = [{'t': ts, 'd': 0, 'o': 0, + 'e': event, 'abstime': ts, 'i': nid}] + last_response = '' + elif event == 'INVOKE' or event == 'ADD' or event == '1ST_TIMEOUT' or \ + event == 'TIMEOUT' or event == 'PEERS': + if search_id not in outstanding_searches: + print('orphaned event: %s' % line) + else: + outstanding = int(ls[ls.index('invoke-count:') + 1]) + distance = int(ls[ls.index('distance:') + 1]) + nid = ls[ls.index('id:') + 1] + addr = ls[ls.index('addr:') + 1] + source = '' + if event == 'ADD': + if last_response == '': + continue + source = last_response + + outstanding_searches[search_id].append( + {'t': ts, 'd': distance, 'o': outstanding + 1, 'a': addr, 'e': event, 'i': nid, 's': source}) + elif event == 'ABORTED': + outstanding_searches[search_id].append({'t': ts, 'e': event}) + elif event == 'COMPLETED': + distance = int(ls[ls.index('distance:') + 1]) + lookup_type = ls[ls.index('type:') + 1].strip() + outstanding_searches[search_id].append({'t': ts, 'd': distance, + 'o': 0, 'e': event, 'i': ''}) + + outstanding_searches[search_id][0]['type'] = lookup_type + + s = outstanding_searches[search_id] + + try: + start_time = convert_timestamp(s[0]['t']) + for i in range(len(s)): + s[i]['t'] = convert_timestamp(s[i]['t']) - start_time + except Exception: + pass + searches.append(s) + del outstanding_searches[search_id] + + except Exception as e: + print(e) + print(line.split(' ')) + +lookup_times_min = [] +lookup_times_max = [] + +# these are the timestamps for lookups crossing distance +# to target boundaries +lookup_distance = [] +for i in range(0, 15): + lookup_distance.append([]) + +for s in searches: + for i in s: + if 'last_dist' not in i: + i['last_dist'] = -1 + cur_dist = 160 - i['d'] + last_dist = i['last_dist'] + if cur_dist > last_dist: + for j in range(last_dist + 1, cur_dist + 1): + if j >= len(lookup_distance): + break + lookup_distance[j].append(i['t']) + i['last_dist'] = cur_dist + if i['e'] != 'PEERS': + continue + lookup_times_min.append(i['t']) + break + for i in reversed(s): + if i['e'] != 'PEERS': + continue + lookup_times_max.append(i['t']) + break + + +lookup_times_min.sort() +lookup_times_max.sort() +out = open('dht_lookup_times_cdf.txt', 'w+') +counter = 0 +for i in range(len(lookup_times_min)): + counter += 1 + print('%d\t%d\t%f' % (lookup_times_min[i], lookup_times_max[i], counter / float(len(lookup_times_min))), file=out) +out.close() + +for i in lookup_distance: + i.sort() + +dist = 0 +for i in lookup_distance: + out = open('dht_lookup_distance_%d.txt' % dist, 'w+') + dist += 1 + counter = 0 + for j in i: + counter += 1 + print('%d\t%f' % (j, counter / float(len(i))), file=out) + out.close() + +out = open('dht_lookups.txt', 'w+') +for s in searches: + for i in s: + if i['e'] == 'INVOKE': + print(' ->', i['t'], 160 - i['d'], i['i'], i['a'], file=out) + elif i['e'] == '1ST_TIMEOUT': + print(' x ', i['t'], 160 - i['d'], i['i'], i['a'], file=out) + elif i['e'] == 'TIMEOUT': + print(' X ', i['t'], 160 - i['d'], i['i'], i['a'], file=out) + elif i['e'] == 'ADD': + print(' + ', i['t'], 160 - i['d'], i['i'], i['a'], i['s'], file=out) + elif i['e'] == 'RESPONSE': + print(' <-', i['t'], 160 - i['d'], i['i'], i['a'], file=out) + elif i['e'] == 'PEERS': + print(' <-', i['t'], 160 - i['d'], i['i'], i['a'], file=out) + elif i['e'] == 'ABORTED': + print('abort', file=out) + elif i['e'] == 'COMPLETED': + print('***', i['t'], 160 - i['d'], '\n', file=out) + elif i['e'] == 'NEW': + print('===', i['abstime'], i['type'], '===', file=out) + print('<> ', 0, our_node_id, i['i'], file=out) +out.close() + +out = open('dht_announce_distribution.dat', 'w+') +print('announce distribution items: %d' % len(announce_histogram)) +for k, v in list(announce_histogram.items()): + print('%d %d' % (k, v), file=out) + print('%d %d' % (k, v)) +out.close() + +out = open('dht_node_uptime_cdf.txt', 'w+') +s = 0 + +total_uptime_nodes = 0 +for k, v in list(node_uptime_histogram.items()): + total_uptime_nodes += v + +for k, v in sorted(node_uptime_histogram.items()): + s += v + print('%f %f' % (k / float(60), s / float(total_uptime_nodes)), file=out) + print('%f %f' % (k / float(60), s / float(total_uptime_nodes))) +out.close() + + +print('clients by version') +client_version_histogram = sorted(list(client_version_histogram.items()), key=lambda x: x[1], reverse=True) +pp.pprint(client_version_histogram) + +print('clients') +client_histogram = sorted(list(client_histogram.items()), key=lambda x: x[1], reverse=True) +pp.pprint(client_histogram) + +out = open('dht.gnuplot', 'w+') +out.write(''' +set term png size 1200,700 small +set output "dht_lookup_times_cdf.png" +set title "portion of lookups that have received at least one data response" +set ylabel "portion of lookups" +set xlabel "time from start of lookup (ms)" +set grid +plot "dht_lookup_times_cdf.txt" using 1:3 with lines title "time to first result", \ + "dht_lookup_times_cdf.txt" using 2:3 with lines title "time to last result" + +set terminal postscript +set output "dht_lookup_times_cdf.ps" +replot + +set term png size 1200,700 small +set xtics 100 +set xrange [0:2000] +set output "dht_min_lookup_times_cdf.png" +plot "dht_lookup_times_cdf.txt" using 1:3 with lines title "time to first result" + +set terminal postscript +set output "dht_min_lookup_times_cdf.ps" +replot + +set term png size 1200,700 small +set output "dht_node_uptime_cdf.png" +set xrange [0:*] +set title "node up time" +set ylabel "portion of nodes being offline" +set xlabel "time from first seeing the node (minutes)" +set xtics 10 +unset grid +plot "dht_node_uptime_cdf.txt" using 1:2 title "nodes" with lines + +set term png size 1200,700 small +set output "dht_announce_distribution.png" +set xrange [0:30] +set xtics 1 +set title "bucket # announces are made against relative to target node-id" +set ylabel "# of announces" +set boxwidth 1 +set xlabel "bit prefix of nodes in announces" +set style fill solid border -1 pattern 2 +plot "dht_announce_distribution.dat" using 1:2 title "announces" with boxes + +set terminal postscript +set output "dht_announce_distribution.ps" +replot + +set term png size 1200,700 small +set output "dht_lookup_distance_cdf.png" +set title "portion of lookups that have reached a certain distance in their lookups" +set ylabel "portion of lookups" +set xlabel "time from start of lookup (ms)" +set xrange [0:2000] +set xtics 100 +set grid +plot ''') + +dist = 0 +for i in lookup_distance: + if dist > 0: + out.write(', ') + out.write('"dht_lookup_distance_%d.txt" using 1:2 title "%d" with lines' % (dist, dist)) + dist += 1 + +out.close() + +os.system('gnuplot dht.gnuplot') diff --git a/tools/parse_dht_rtt.py b/tools/parse_dht_rtt.py new file mode 100755 index 0000000..8f41679 --- /dev/null +++ b/tools/parse_dht_rtt.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +from __future__ import print_function + +import sys +import os + +quantize = 100 +max_rtt = 5000 + +f = open(sys.argv[1]) +distribution = {} +num_messages = 0 + +for i in range(0, max_rtt, quantize): + distribution[i] = 0 + +for line in f: + time = int(line.split('\t')[1]) + if (time < 0 or time > max_rtt - quantize): + continue + num_messages += 1 + time //= quantize + time *= quantize + distribution[time] += 1 + +f = open('round_trip_distribution.log', 'w+') + +for k, v in list(distribution.items()): + print('%f %d' % ((k + (quantize / 2)) / 1000.0, v), file=f) +f.close() + +f = open('round_trip_distribution.gnuplot', 'w+') + +f.write(''' +set term png size 1200,700 +set title "Message round trip times" +set terminal postscript +set ylabel "# of requests" +set xlabel "Round trip time (seconds)" +set xrange [0:*] +set grid +set style fill solid border -1 pattern 2 +set output "round_trip_distribution.ps" +set boxwidth %f +plot "round_trip_distribution.log" using 1:2 title "requests" with boxes + +set terminal png small +set output "round_trip_distribution.png" +replot +''' % (float(quantize) / 1000.0)) +f.close() + +os.system('gnuplot round_trip_distribution.gnuplot') diff --git a/tools/parse_dht_stats.py b/tools/parse_dht_stats.py new file mode 100755 index 0000000..2abaa90 --- /dev/null +++ b/tools/parse_dht_stats.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import sys +import os + +gnuplot_scripts = [] + + +def gen_stats_gnuplot(name, y, lines): + + global gnuplot_scripts + + stat = open(sys.argv[1]) + line = stat.readline() + while 'minute:' not in line: + line = stat.readline() + + names = line.strip().split(':') + counter = 1 + for i in names: + print('%d: %s' % (counter, i)) + counter += 1 + + out = open('%s.gnuplot' % name, 'w+') + out.write(''' +set term png size 1200,700 small +set output "%s.png" +set title "%s" +set ylabel "%s" +set xlabel "time (minutes)" +plot ''' % (name, name.strip('_'), y)) + first = True + for i in lines: + if not first: + out.write(', \\\n') + first = False + out.write('"%s" using 1:%d title "%s" with lines' % (sys.argv[1], names.index(i) + 1, i)) + out.write('\n') + + out.write('''set terminal postscript +set output "%s.ps" +replot +''' % (name)) + out.close() + gnuplot_scripts += [name] + + +gen_stats_gnuplot('dht_routing_table_size', 'nodes', ['active nodes', 'passive nodes', 'confirmed nodes']) +gen_stats_gnuplot('dht_tracker_table_size', '', ['num torrents', 'num peers']) +gen_stats_gnuplot('dht_announces', 'messages per minute', ['announces per min', 'failed announces per min']) +gen_stats_gnuplot('dht_clients', + 'messages per minute', + ['total msgs per min', + 'az msgs per min', + 'ut msgs per min', + 'lt msgs per min', + 'mp msgs per min', + 'gr msgs per min']) +gen_stats_gnuplot('dht_rate', 'bytes per second', ['bytes in per sec', 'bytes out per sec']) +gen_stats_gnuplot('dht_errors', 'messages per minute', ['error replies sent', 'error queries recvd']) + +for i in gnuplot_scripts: + os.system('gnuplot %s.gnuplot' % i) diff --git a/tools/parse_peer_log.py b/tools/parse_peer_log.py new file mode 100755 index 0000000..26d4871 --- /dev/null +++ b/tools/parse_peer_log.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +from __future__ import print_function + +import glob +import os +import sys + +# usage: parse_peer_log + +log_files = [] + +for p in glob.iglob(os.path.join(sys.argv[1], '*.log')): + name = os.path.split(p)[1] + if name == 'main_session.log': + continue + print(name) + f = open(p, 'r') + out_file = p + '.dat' + log_files.append(out_file) + out = open(out_file, 'w+') + + uploaded_blocks = 0 + downloaded_blocks = 0 + + for line in f: + t = line.split(': ')[0].split('.')[0] + log_line = False + if ' ==> PIECE' in line: + uploaded_blocks += 1 + log_line = True + + if ' <== PIECE' in line: + downloaded_blocks += 1 + log_line = True + + if log_line: + print('%s\t%d\t%d' % (t, uploaded_blocks, downloaded_blocks), file=out) + + out.close() + f.close() + +out = open('peers.gnuplot', 'wb') +print("set term png size 1200,700", file=out) +print('set xrange [0:*]', file=out) +print('set xlabel "time"', file=out) +print('set ylabel "blocks"', file=out) +print('set key box', file=out) +print('set xdata time', file=out) +print('set timefmt "%H:%M:%S"', file=out) +print('set title "uploaded blocks"', file=out) +print('set output "peers_upload.png"', file=out) +print('plot', end=' ', file=out) +first = True +for n in log_files: + if not first: + print(',', end=' ', file=out) + first = False + print(' "%s" using 1:2 title "%s" with steps' % (n, os.path.split(n)[1].split('.log')[0]), end=' ', file=out) +print('', file=out) + +print('set title "downloaded blocks"', file=out) +print('set output "peers_download.png"', file=out) +print('plot', end=' ', file=out) +first = True +for n in log_files: + if not first: + print(',', end=' ', file=out) + first = False + print(' "%s" using 1:3 title "%s" with steps' % (n, os.path.split(n)[1].split('.log')[0]), end=' ', file=out) +print('', file=out) +out.close() + +os.system('gnuplot peers.gnuplot') diff --git a/tools/parse_sample.py b/tools/parse_sample.py new file mode 100755 index 0000000..3d7bfe7 --- /dev/null +++ b/tools/parse_sample.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + +import sys + +# to use this script, first run 'sample' to sample your libtorrent based process +# the output can then be passed to this script to auto-fold call stacks at +# relevant depths and to filter out low sample counts +f = open(sys.argv[1]) + + +def parse_line(line): + indentation = 0 + while indentation < len(line) and line[indentation] == ' ': + indentation += 1 + if indentation == 0: + return (0, 0, '') + + line = line.strip().split(' ') + samples = int(line[0]) + fun = ' '.join(line[1:]) + + return (indentation, samples, fun) + + +fold = -1 + +try: + sample_limit = int(sys.argv[2]) +except Exception: + sample_limit = 5 + +fun_samples = {} + +for line in f: + if 'Sort by top of stack' in line: + break + + indentation, samples, fun = parse_line(line) + if samples < sample_limit: + continue + if fold != -1 and indentation > fold: + continue + fold = -1 + + if '__gnu_cxx::__normal_iterator<' in fun: + fold = indentation - 1 + continue + + if 'boost::_bi::bind_t' in fun: + continue + if 'boost::_bi::list' in fun: + continue + if 'boost::_mfi::mf' in fun: + continue + if 'boost::_bi::storage' in fun: + continue + +# should only add leaves + if fun in fun_samples: + fun_samples[fun] += samples + else: + fun_samples[fun] = samples + + output = '%s%-4d %s' % (' ' * (indentation / 2), samples, fun) + if len(output) > 200: + output = output[0:200] + print(output) + + if 'invariant_checker_impl' in fun: + fold = indentation + if 'free_multiple_buffers' in fun: + fold = indentation + if 'libtorrent::condition::wait' in fun: + fold = indentation + if 'allocate_buffer' in fun: + fold = indentation + if '::find_POD' in fun: + fold = indentation + if 'SHA1_Update' in fun: + fold = indentation + if 'boost::detail::function::basic_vtable' in fun: + fold = indentation + if 'operator new' in fun: + fold = indentation + if 'malloc' == fun: + fold = indentation + if 'free' == fun: + fold = indentation + if 'std::_Rb_tree' in fun: + fold = indentation + if 'pthread_cond_wait' in fun: + fold = indentation + if 'mp_exptmod' == fun: + fold = indentation + if '::check_invariant()' in fun: + fold = indentation + if 'libtorrent::condition::wait' in fun: + fold = indentation + if '_sigtramp' in fun: + fold = indentation + if 'time_now_hires' in fun: + fold = indentation + if 'libtorrent::sleep' in fun: + fold = indentation + if 'puts' == fun: + fold = indentation + if 'boost::asio::basic_stream_socket' in fun: + fold = indentation + if 'recvmsg' == fun: + fold = indentation + if 'sendmsg' == fun: + fold = indentation + if 'semaphore_signal_trap' == fun: + fold = indentation + if 'boost::detail::atomic_count::operator' in fun: + fold = indentation + if 'pthread_mutex_lock' == fun: + fold = indentation + if 'pthread_mutex_unlock' == fun: + fold = indentation + if '>::~vector()' == fun: + fold = indentation + if 'szone_free_definite_size' == fun: + fold = indentation + if 'snprintf' == fun: + fold = indentation + if 'usleep' == fun: + fold = indentation + if 'pthread_mutex_lock' == fun: + fold = indentation + if 'pthread_mutex_unlock' == fun: + fold = indentation + if 'std::string::append' in fun: + fold = indentation + if 'getipnodebyname' == fun: + fold = indentation + if '__gnu_debug::_Safe_iterator/dev/null' % script) + except Exception as e: + print('please install gnuplot: sudo apt install gnuplot') + raise e + if ret != 0 and ret != 256: + print('gnuplot failed: %d\n' % ret) + raise Exception("abort") + + sys.stdout.write('.') + sys.stdout.flush() + + +def to_title(key): + return key.replace('_', ' ').replace('.', ' - ') + + +def gen_report(name, unit, lines, short_unit, generation, log_file, options): + filename = os.path.join(output_dir, '%s_%04d.png' % (name, generation)) + thumb = os.path.join(output_dir, '%s_%04d_thumb.png' % (name, generation)) + + # don't re-render a graph unless the logfile has changed + try: + dst1 = os.stat(filename) + dst2 = os.stat(thumb) + src = os.stat(log_file) + + if dst1.st_mtime > src.st_mtime and dst2.st_mtime > src.st_mtime: + sys.stdout.write('.') + return None + + except Exception: + pass + + script = os.path.join(output_dir, '%s_%04d.gnuplot' % (name, generation)) + out = open(script, 'wb') + print("set term png size 1200,700", file=out) + print('set output "%s"' % filename, file=out) + if 'allow-negative' not in options: + print('set yrange [0:*]', file=out) + print("set tics nomirror", file=out) + print("set key box", file=out) + print("set key left top", file=out) + + colors = graph_colors + if options['type'] == line_graph: + colors = line_colors + + try: + if options['colors'] == 'gradient16': + colors = gradient16_colors + elif options['colors'] == 'gradient6': + colors = gradient6_colors + if options['colors'] == 'gradient18': + colors = gradient18_colors + except Exception: + pass + + if options['type'] == histogram: + binwidth = options['binwidth'] + numbins = int(options['numbins']) + + print('binwidth=%f' % binwidth, file=out) + print('set boxwidth binwidth', file=out) + print('bin(x,width)=width*floor(x/width) + binwidth/2', file=out) + print('set xrange [0:%f]' % (binwidth * numbins), file=out) + print('set xlabel "%s"' % unit, file=out) + print('set ylabel "number"', file=out) + + k = lines[0] + try: + column = keys.index(k) + 2 + except Exception: + print('"%s" not found' % k) + return + print('plot "%s" using (bin($%d,binwidth)):(1.0) smooth freq with boxes' % (log_file, column), file=out) + print('', file=out) + print('', file=out) + print('', file=out) + + elif options['type'] == stacked: + print('set xrange [0:*]', file=out) + print('set ylabel "%s"' % unit, file=out) + print('set xlabel "time (s)"', file=out) + print('set format y "%%.1s%%c%s";' % short_unit, file=out) + print('set style fill solid 1.0 noborder', file=out) + print('plot', end=' ', file=out) + first = True + graph = '' + plot_expression = '' + color = 0 + for k in lines: + try: + column = keys.index(k) + 2 + except Exception: + print('"%s" not found' % k) + continue + if not first: + plot_expression = ', ' + plot_expression + graph += '+' + axis = 'x1y1' + graph += '$%d' % column + plot_expression = ' "%s" using 1:(%s) title "%s" axes %s with filledcurves x1 lc rgb "%s"' % ( + log_file, graph, to_title(k), axis, colors[color % len(colors)]) + plot_expression + first = False + color += 1 + print(plot_expression, file=out) + elif options['type'] == diff: + print('set xrange [0:*]', file=out) + print('set ylabel "%s"' % unit, file=out) + print('set xlabel "time (s)"', file=out) + print('set format y "%%.1s%%c%s";' % short_unit, file=out) + first = True + graph = '' + title = '' + for k in lines: + try: + column = keys.index(k) + 2 + except Exception: + print('"%s" not found' % k) + continue + if not first: + graph += '-' + title += ' - ' + graph += '$%d' % column + title += to_title(k) + first = False + print('plot "%s" using 1:(%s) title "%s" with step' % (log_file, graph, title), file=out) + else: + print('set xrange [0:*]', file=out) + print('set ylabel "%s"' % unit, file=out) + print('set xlabel "time (s)"', file=out) + print('set format y "%%.1s%%c%s";' % short_unit, file=out) + print('plot', end=' ', file=out) + first = True + color = 0 + for k in lines: + try: + column = keys.index(k) + 2 + except Exception: + print('"%s" not found' % k) + continue + if not first: + print(', ', end=' ', file=out) + axis = 'x1y1' + print(' "%s" using 1:%d title "%s" axes %s with steps lc rgb "%s"' % + (log_file, column, to_title(k), axis, colors[color % len(colors)]), end=' ', file=out) + first = False + color += 1 + print('', file=out) + + print('set term png size 150,100', file=out) + print('set output "%s"' % thumb, file=out) + print('set key off', file=out) + print('unset tics', file=out) + print('set format x ""', file=out) + print('set format y ""', file=out) + print('set xlabel ""', file=out) + print('set ylabel ""', file=out) + print('set y2label ""', file=out) + print('set rmargin 0', file=out) + print('set lmargin 0', file=out) + print('set tmargin 0', file=out) + print('set bmargin 0', file=out) + print("replot", file=out) + out.close() + return script + + +def gen_html(reports, generations): + file = open(os.path.join(output_dir, 'index.html'), 'w+') + + css = '''img { margin: 0} +#head { display: block } +#graphs { white-space:nowrap; } +h1 { line-height: 1; display: inline } +h2 { line-height: 1; display: inline; font-size: 1em; font-weight: normal};''' + + print('' % css, file=file) + + for i in reports: + print('

    ghf;D=fu&@wMT7^P&V&leRKTG#M`=R|;UwuOy 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ÚwUNIF;&A=cZ34=QX6v_E2w~qV-%=k185CXkJ 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%0D*|J<)U z`ClUYc~zPRtq1H!IDdH3;jmvaCV{bVaBvJRvf3_CV;585&rYVm4;&8{H!lYl5AfCC z;uhu;66OQ`3JP;^vFfPJ{2u{Zd#I(E=l>Vr75J|}f$W(TAONQzt143_Z4~%FFj*(+ literal 0 HcmV?d00001 diff --git a/docs/tuning-ref.html b/docs/tuning-ref.html new file mode 100644 index 0000000..960791b --- /dev/null +++ b/docs/tuning-ref.html @@ -0,0 +1,437 @@ + + + + + + +tuning-ref.rst + + + + + + + +